Skip to content

Add Qwen provider (OpenAI-compatible BYOK) - #57

Open
ivnnv wants to merge 4 commits into
pullfrog:mainfrom
ivnnv:qwen-provider
Open

Add Qwen provider (OpenAI-compatible BYOK)#57
ivnnv wants to merge 4 commits into
pullfrog:mainfrom
ivnnv:qwen-provider

Conversation

@ivnnv

@ivnnv ivnnv commented Jun 13, 2026

Copy link
Copy Markdown

Adds first-class Qwen support so coding/review runs can target an OpenAI-compatible Qwen endpoint (Alibaba DashScope, or any compatible gateway) with a bring-your-own key. Right now theres no Alibaba/Qwen entry in the catalog, and a Qwen provider injected from the workflow doesnt survive because the action rebuilds OPENCODE_CONFIG_CONTENT internally, so this has to live in the action itself.

Whats here:

models.ts: a qwen provider with three aliases. qwen-coder (resolves to qwen3-coder-plus, marked preferred), qwen-plus, qwen-max. Each one maps to its OpenRouter equivalent so the openRouterResolve completeness test stays green.

agents/opencode.ts and agents/opencode_v2.ts: materialize the qwen provider inside the action-owned OPENCODE_CONFIG_CONTENT via @ai-sdk/openai-compatible. Key and baseURL come from QWEN_API_KEY/QWEN_BASE_URL, then DASHSCOPE_*, then LLM_* as a generic OpenAI-compatible fallback, with the DashScope international base URL as the default.

utils/apiKeys.ts: accept any of the recognized Qwen env vars during opencode key validation, since that runs before the custom provider config exists.

Plus tests for the model resolution and the key validation.

I verified qwen3-coder-plus, qwen-plus and qwen3-coder-next all respond on the DashScope OpenAI-compatible endpoint. typecheck passes and the full suite is green except one unrelated pre-existing failure in test/ci.test.ts that also fails on a clean main, so its not from this change.

One open question for you: do you prefer DASHSCOPE_API_KEY as the primary env name over QWEN_API_KEY? happy to reorder the fallback chain if so :)


Note

Medium Risk
Touches pre-run API key validation and OpenCode provider config for a new external endpoint; scope is additive but affects how runs authenticate before the custom provider exists.

Overview
Adds first-class Qwen support so review/coding runs can target DashScope (or any OpenAI-compatible gateway) with a user-supplied key, instead of relying on workflow-injected config that gets overwritten when the action rebuilds OPENCODE_CONFIG_CONTENT.

The model catalog gains a qwen provider (qwen-coder, qwen-plus, qwen-max) with a new byok flag so these aliases skip models.dev drift checks and are omitted from CI workflow env expectations (no managed Pullfrog key). Both OpenCode harnesses (opencode.ts / opencode_v2.ts) now embed qwenProviderConfig()@ai-sdk/openai-compatible with models from the registry and credentials from QWEN_*DASHSCOPE_*LLM_*, defaulting to the international DashScope compatible base URL.

Pre-run validation accepts any of the Qwen-related env vars for opencode even when the model is not in the opencode models authorized set. Tests cover resolution, env vars, API key validation, catalog skips, and CI env filtering.

Reviewed by Cursor Bugbot for commit 42fc80f. Bugbot is set up for automated code reviews on this repo. Configure here.

Adds a first-class Qwen provider so PR-review/coding runs can use an
OpenAI-compatible Qwen endpoint (Alibaba DashScope or any compatible
gateway) with a bring-your-own key.

- models.ts: qwen provider with qwen-coder (qwen3-coder-plus, preferred),
  qwen-plus, qwen-max aliases, each mapped to its OpenRouter equivalent.
- agents/opencode.ts, agents/opencode_v2.ts: materialize the qwen provider
  inside the action-owned OPENCODE_CONFIG_CONTENT via @ai-sdk/openai-compatible,
  reading key/baseURL from QWEN_*/DASHSCOPE_*/LLM_* env (LLM_* as a generic
  OpenAI-compatible fallback). Workflow-injected config alone can't work since
  the action overwrites OPENCODE_CONFIG_CONTENT internally.
- utils/apiKeys.ts: accept any of the qwen env vars during opencode key
  validation, which runs before the custom provider config exists.
- tests for model resolution and key validation.
Comment thread agents/opencode.ts Outdated

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

The three Qwen aliases resolve under a qwen/ provider that does not exist on models.dev (Qwen models live under alibaba-cn), so the catalog drift gate will fail its "exists on models.dev" check once this runs. That gate is excluded from this PR's own CI, so a green local pnpm test doesn't cover it — see the section below before merging.

Reviewed changes — adds a first-class Qwen provider (OpenAI-compatible BYOK) so coding/review runs can target DashScope or any compatible gateway with a bring-your-own key.

  • Register qwen provider in models.ts — three aliases (qwen-coderqwen3-coder-plus preferred, qwen-plus, qwen-max), each with an openRouterResolve equivalent.
  • Inject qwen into action-owned OPENCODE_CONFIG_CONTENT — both agents/opencode.ts and agents/opencode_v2.ts materialize a @ai-sdk/openai-compatible provider, with key/baseURL resolved from QWEN_*DASHSCOPE_*LLM_* and the DashScope international URL as default.
  • Relax opencode key validation in utils/apiKeys.ts — accept any recognized Qwen env var as sufficient, since the custom provider config doesn't exist yet at opencode models introspection time.
  • Tests — model env-var/slug resolution and the new key-validation early return.

⚠️ Qwen resolve targets a provider models.dev doesn't have, breaking the catalog drift gate

The catalog drift test walks every non-routing, non-deprecated alias and asserts data[provider] exists on models.dev, where provider is the slug prefix of resolve. The three Qwen aliases resolve to qwen/…, but models.dev has no qwen provider — its Qwen models live under alibaba-cn. Every other provider in the catalog (anthropic, openai, opencode, …) resolves to a real models.dev provider key, which is why this gate passes on main today; qwen is the first that won't.

This gate lives in *.main.test.ts, which vitest.config.ts excludes from pnpm test, so a clean local run won't surface it — it only fires via pnpm test:catalog (the documented integrity gate for catalog edits). The runtime feature is unaffected: OpenCode uses the injected custom provider config, not models.dev, to resolve qwen/qwen3-coder-plus.

Technical details
# Qwen `resolve` provider prefix is absent from models.dev

## Affected sites
- `models.ts:282``resolve: "qwen/qwen3-coder-plus"`
- `models.ts:288``resolve: "qwen/qwen-plus"`
- `models.ts:293``resolve: "qwen/qwen-max"`
- `test/models-catalog.main.test.ts:44-77``models.dev validity` block: `expect(data[parsed.provider]).toBeDefined()` fails because `data["qwen"]` is undefined (Qwen is under `alibaba-cn`).

## Why CI here is green but the gate isn't
- `vitest.config.ts` excludes `**/*.main.test.ts` from `pnpm test`; the drift suite only runs via `pnpm test:catalog` (`vitest.main.config.ts`). The PR's `pnpm test` therefore never exercises this.

## Required outcome
- The Qwen aliases must not fail the `models.dev validity` existence check, while keeping `resolve: "qwen/…"` (the prefix must match the injected OpenCode provider key for runtime resolution).

## Suggested approach
- Treat Qwen as a BYOK custom provider with no upstream models.dev entry — analogous to the `routing` slugs the test already skips (`if (alias.routing) continue;`). Add a comparable skip for the `qwen` provider in the `models.dev validity` block (and confirm the `openRouterResolve` blocks below behave as intended).

## Open questions for the human
- Run `pnpm test:catalog` and confirm the `openRouterResolve` checks pass too. `qwen-max` maps to `openrouter/qwen/qwen3-max` (note: `qwen3-max`, not `qwen-max`) — verify that ID is live on both models.dev's `openrouter` provider and the OpenRouter `/api/v1/models` list, since those two checks aren't excluded and would fail independently if the ID drifted.
- Decide whether Qwen aliases should be subject to drift checks at all, or fully exempt like the routing slugs.

ℹ️ Nitpicks

  • The qwenApiKey / qwenBaseUrl resolution and the qwen provider block are duplicated verbatim in agents/opencode.ts (lines 97-157) and agents/opencode_v2.ts (lines 107-154). Other shared OpenCode config lives in opencodeShared.ts (e.g. geminiHighThinkingOverrides); a small shared helper would prevent the two copies drifting. Defensible to leave given v1 is slated for retirement.

Pullfrog  | Fix it ➔View workflow run | Using Claude Opus (free via Pullfrog for OSS) | 𝕏

ivnnv added 3 commits June 13, 2026 20:25
- mark qwen aliases byok and skip them in the models.dev validity gate
  (resolve prefix has no models.dev entry; provider is injected at runtime).
- derive the OpenCode qwen models map from modelAliases via a shared
  qwenProviderConfig() helper, so a resolve bump flows through and the two
  opencode files stop duplicating the block.
…API_KEY

- ci.test.ts: byok-only providers (qwen) have no Pullfrog-held key, so exclude
  them from the agents-job env-var consistency check (Codex P1: the new
  provider otherwise expands expectedAgentEnvVars and breaks the check).
- models.ts: comment the qwen envVars precedence (LLM_API_KEY is the generic
  OpenAI-compatible fallback, last).

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 7776a48. Configure here.

Comment thread models.ts Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant