Add Qwen provider (OpenAI-compatible BYOK) - #57
Conversation
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.
There was a problem hiding this comment.
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
qwenprovider inmodels.ts— three aliases (qwen-coder→qwen3-coder-pluspreferred,qwen-plus,qwen-max), each with anopenRouterResolveequivalent. - Inject
qweninto action-ownedOPENCODE_CONFIG_CONTENT— bothagents/opencode.tsandagents/opencode_v2.tsmaterialize a@ai-sdk/openai-compatibleprovider, with key/baseURL resolved fromQWEN_*→DASHSCOPE_*→LLM_*and the DashScope international URL as default. - Relax
opencodekey validation inutils/apiKeys.ts— accept any recognized Qwen env var as sufficient, since the custom provider config doesn't exist yet atopencode modelsintrospection 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/qwenBaseUrlresolution and theqwenprovider block are duplicated verbatim inagents/opencode.ts(lines 97-157) andagents/opencode_v2.ts(lines 107-154). Other shared OpenCode config lives inopencodeShared.ts(e.g.geminiHighThinkingOverrides); a small shared helper would prevent the two copies drifting. Defensible to leave given v1 is slated for retirement.
Claude Opus (free via Pullfrog for OSS) | 𝕏
- 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).
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ 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.


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_CONTENTinternally, so this has to live in the action itself.Whats here:
models.ts: a qwen provider with three aliases.qwen-coder(resolves toqwen3-coder-plus, marked preferred),qwen-plus,qwen-max. Each one maps to its OpenRouter equivalent so the openRouterResolve completeness test stays green.agents/opencode.tsandagents/opencode_v2.ts: materialize the qwen provider inside the action-ownedOPENCODE_CONFIG_CONTENTvia@ai-sdk/openai-compatible. Key and baseURL come fromQWEN_API_KEY/QWEN_BASE_URL, thenDASHSCOPE_*, thenLLM_*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-plusandqwen3-coder-nextall respond on the DashScope OpenAI-compatible endpoint. typecheck passes and the full suite is green except one unrelated pre-existing failure intest/ci.test.tsthat also fails on a clean main, so its not from this change.One open question for you: do you prefer
DASHSCOPE_API_KEYas the primary env name overQWEN_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
qwenprovider (qwen-coder,qwen-plus,qwen-max) with a newbyokflag 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 embedqwenProviderConfig()—@ai-sdk/openai-compatiblewith models from the registry and credentials fromQWEN_*→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 modelsauthorized 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.