Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 22 additions & 6 deletions docs/users/configuration/auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,12 +157,28 @@ The key concept is **Model Providers** (`modelProviders`): Qwen Code supports mu

#### Supported protocols

| Protocol | `modelProviders` key | Environment variables | Providers |
| ----------------- | -------------------- | ---------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| OpenAI-compatible | `openai` | `OPENAI_API_KEY`, `OPENAI_BASE_URL`, `OPENAI_MODEL` (alias: `QWEN_MODEL`) | OpenAI, Azure OpenAI, OpenRouter, Requesty, ModelScope, Alibaba Cloud, any OpenAI-compatible endpoint |
| Anthropic | `anthropic` | `ANTHROPIC_API_KEY`, `ANTHROPIC_BASE_URL`, `ANTHROPIC_MODEL` | Anthropic Claude |
| Google GenAI | `gemini` | `GEMINI_API_KEY`, `GEMINI_MODEL` | Google Gemini |
| Vertex AI | `vertex-ai` | `GOOGLE_API_KEY`, `GOOGLE_MODEL` (sets `GOOGLE_GENAI_USE_VERTEXAI=true`; uses the `gemini` protocol) | Google Vertex AI |
| Protocol | `modelProviders` key | Environment variables | Providers |
| ------------------ | -------------------- | ---------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| OpenAI-compatible | `openai` | `OPENAI_API_KEY`, `OPENAI_BASE_URL`, `OPENAI_MODEL` (alias: `QWEN_MODEL`) | OpenAI, Azure OpenAI, OpenRouter, Requesty, ModelScope, Alibaba Cloud, any OpenAI-compatible endpoint |
| Anthropic | `anthropic` | `ANTHROPIC_API_KEY`, `ANTHROPIC_BASE_URL`, `ANTHROPIC_MODEL` | Anthropic Claude |
| Google GenAI | `gemini` | `GEMINI_API_KEY`, `GEMINI_MODEL` | Google Gemini |
| Vertex AI | `vertex-ai` | `GOOGLE_API_KEY`, `GOOGLE_MODEL` (sets `GOOGLE_GENAI_USE_VERTEXAI=true`; uses the `gemini` protocol) | Google Vertex AI |
| DashScope (native) | `dashscope` | `DASHSCOPE_API_KEY`, `DASHSCOPE_BASE_URL`, `DASHSCOPE_MODEL` | Alibaba ModelStudio's native DashScope API (`qwen3.8-max`) |

#### DashScope (native API)

`dashscope` connects to Alibaba ModelStudio's native DashScope generation API instead of
the OpenAI-compatible endpoint used by the `openai` protocol above. Get an API key from
[Alibaba Cloud ModelStudio](https://modelstudio.console.alibabacloud.com/), then either:

- Run `/auth`, select **Alibaba ModelStudio**, then choose **Native DashScope API** from
the sub-menu, or
- Set `DASHSCOPE_API_KEY` and run `qwen --auth-type=dashscope`.

`dashscope` uses the exact same API key as the ModelStudio **Standard API Key** option
(the `openai`-protocol, OpenAI-compatible setup) — only the wire protocol differs. See
[Alibaba ModelStudio — Native DashScope API](model-providers.md#alibaba-modelstudio--native-dashscope-api-dashscope-protocol)
for prompt caching, thinking control, and full `modelProviders` configuration details.

#### Step 1: Configure models and providers in `~/.qwen/settings.json`

Expand Down
104 changes: 97 additions & 7 deletions docs/users/configuration/model-providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,14 @@ Below are comprehensive configuration examples for different authentication type

The `modelProviders` object keys must be valid `authType` values. Currently supported auth types are:

| Auth Type | Description |
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `openai` | OpenAI-compatible APIs (OpenAI, Azure OpenAI, local inference servers like vLLM/Ollama) |
| `anthropic` | Anthropic Claude API |
| `gemini` | Google Gemini API |
| `qwen-oauth` | Qwen OAuth (hard-coded, cannot be overridden in `modelProviders`) |
| `vertex-ai` | Google Vertex AI (uses the `gemini` protocol and the `@google/genai` SDK in Vertex AI mode; selecting it sets `GOOGLE_GENAI_USE_VERTEXAI=true`) |
| Auth Type | Description |
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `openai` | OpenAI-compatible APIs (OpenAI, Azure OpenAI, local inference servers like vLLM/Ollama) |
| `anthropic` | Anthropic Claude API |
| `gemini` | Google Gemini API |
| `qwen-oauth` | Qwen OAuth (hard-coded, cannot be overridden in `modelProviders`) |
| `vertex-ai` | Google Vertex AI (uses the `gemini` protocol and the `@google/genai` SDK in Vertex AI mode; selecting it sets `GOOGLE_GENAI_USE_VERTEXAI=true`) |
| `dashscope` | Alibaba ModelStudio's native DashScope generation API (not OpenAI-compatible) — see [Alibaba ModelStudio — Native DashScope API](#alibaba-modelstudio--native-dashscope-api-dashscope-protocol) below |

> [!warning]
> A provider id that is neither a built-in protocol nor mapped via `providerProtocol` (e.g. a typo like `"openai-custom"`) cannot be routed, so its whole entry is **skipped** with a warning — its models simply won't appear in the `/model` picker. Use one of the supported auth type values above for built-in providers, or add a [`providerProtocol`](#custom-provider-ids-providerprotocol) mapping for a custom id.
Expand Down Expand Up @@ -70,6 +71,7 @@ Qwen Code uses the following official SDKs to send requests to each provider:
| `anthropic` | [`@anthropic-ai/sdk`](https://www.npmjs.com/package/@anthropic-ai/sdk) - Official Anthropic SDK |
| `gemini` | [`@google/genai`](https://www.npmjs.com/package/@google/genai) - Official Google GenAI SDK |
| `qwen-oauth` | [`openai`](https://www.npmjs.com/package/openai) with custom provider (DashScope-compatible) |
| `dashscope` | Built-in `fetch`-based transport speaking DashScope's native wire format directly (no SDK) |

This means the `baseUrl` you configure should be compatible with the corresponding SDK's expected API format. For example, when using `openai` auth type, the endpoint must accept OpenAI API format requests.

Expand Down Expand Up @@ -204,6 +206,94 @@ This auth type supports not only OpenAI's official API but also any OpenAI-compa
}
```

### Alibaba ModelStudio — Native DashScope API (`dashscope` protocol)

`dashscope` is a separate auth type from `openai`: instead of going through the
OpenAI-compatible `/compatible-mode/v1` endpoint, it speaks Alibaba ModelStudio's
**native** DashScope generation API
(`/api/v1/services/aigc/multimodal-generation/generation`) directly. It currently
supports the `qwen3.8-max` model.

Prefer `dashscope` over the OpenAI-compatible `openai` protocol (with a DashScope
`baseUrl`) when you want:

- **Explicit prompt caching** with separate cache-read and cache-creation token counts
reported in usage — cached input is billed at roughly 0.1x, cache writes at roughly
1.25x. Cache markers are placed automatically; disable them per model with
`generationConfig.enableCacheControl: false`.
- **Raw `reasoning_content` streaming** — thinking output arrives in its own field, with
no `<think>` tag scraping.
- **Native parallel tool calls.**

Both `dashscope` and the OpenAI-compatible `openai` protocol (pointed at a DashScope
`baseUrl`) read the same `DASHSCOPE_API_KEY` — only the wire protocol differs.

#### Regions

| Region | `baseUrl` |
| ------------------- | -------------------------------------------- |
| Singapore (default) | `https://dashscope-intl.aliyuncs.com/api/v1` |
| Beijing | `https://dashscope.aliyuncs.com/api/v1` |
| US (Virginia) | `https://dashscope-us.aliyuncs.com/api/v1` |

#### Settings example

```json
{
"security": { "auth": { "selectedType": "dashscope" } },
"model": { "name": "qwen3.8-max" },
"modelProviders": {
"dashscope": [
{
"id": "qwen3.8-max",
"name": "[ModelStudio Native] qwen3.8-max",
"baseUrl": "https://dashscope-intl.aliyuncs.com/api/v1",
"envKey": "DASHSCOPE_API_KEY",
"generationConfig": {
"contextWindowSize": 1000000,
"reasoning": { "effort": "medium" }
}
}
]
}
}
```

#### Headless / CI recipe

```bash
export DASHSCOPE_API_KEY="sk-..."
export DASHSCOPE_MODEL="qwen3.8-max"
qwen --auth-type=dashscope -p "your prompt"
```

Optionally set `DASHSCOPE_BASE_URL` to pick a different region than the Singapore
default.

#### Thinking control

`generationConfig.reasoning` maps to the native `reasoning_effort` parameter:

- `reasoning: { "effort": "low" | "medium" | "xhigh" }` sets
`reasoning_effort` to that native tier. Effort controls reject `high` and
`max`; if either remains in persisted configuration, request conversion
clamps it to `xhigh`.
- `reasoning: false` disables thinking (`reasoning_effort: "none"`).

Native-only knobs not covered by `reasoning` — e.g. `thinking_budget`,
`max_completion_tokens`, `enable_search` — can be passed through via
`generationConfig.extra_body`.

#### Caveats

- Forcing a tool choice (function-calling mode `ANY` / a specific function name) disables
thinking for that request — this is a native DashScope API constraint, not a qwen-code
limitation.
- Embeddings are not supported on this protocol; use the OpenAI-compatible `openai`
protocol against a DashScope embeddings endpoint instead.
- Multimodal input currently supports images. Video/PDF/audio content blocks are passed
through but not yet battle-tested.

### Google Gemini (`gemini`)

```json
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/acp-integration/acpAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -916,6 +916,7 @@ export const AUTH_PREFLIGHT_ENV_KEYS: Readonly<
anthropic: ['ANTHROPIC_API_KEY'],
gemini: ['GEMINI_API_KEY'],
'vertex-ai': ['GOOGLE_API_KEY'],
dashscope: ['DASHSCOPE_API_KEY'],
};

/**
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/acp-integration/authMethods.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ describe('ACP auth methods', () => {

expect(authMethods.map((method) => method.id)).toEqual([
AuthType.USE_OPENAI,
AuthType.USE_DASHSCOPE,
]);
});

Expand All @@ -25,6 +26,7 @@ describe('ACP auth methods', () => {

expect(authMethods.map((method) => method.id)).toEqual([
AuthType.USE_OPENAI,
AuthType.USE_DASHSCOPE,
]);
});
});
7 changes: 7 additions & 0 deletions packages/cli/src/acp-integration/authMethods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ export function buildAuthMethods(): AuthMethod[] {
args: ['--auth-type=openai'],
},
},
{
id: AuthType.USE_DASHSCOPE,
name: 'Use DashScope API key (native)',
description:
'Requires setting the `DASHSCOPE_API_KEY` environment variable',
_meta: { type: 'terminal', args: ['--auth-type=dashscope'] },
},
];
}

Expand Down
12 changes: 12 additions & 0 deletions packages/cli/src/config/auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ describe('validateAuthMethod', () => {
delete process.env['GOOGLE_API_KEY'];
delete process.env['IDEALAB_KEY'];
delete process.env['TOKEN_PLAN_KEY'];
delete process.env['DASHSCOPE_API_KEY'];
});

it('should return null for USE_OPENAI with default env key', () => {
Expand All @@ -49,6 +50,17 @@ describe('validateAuthMethod', () => {
);
});

it('should return null for USE_DASHSCOPE with default env key', () => {
process.env['DASHSCOPE_API_KEY'] = 'fake-key';
expect(validateAuthMethod(AuthType.USE_DASHSCOPE)).toBeNull();
});

it('should return an error message for USE_DASHSCOPE if no API key is available', () => {
expect(validateAuthMethod(AuthType.USE_DASHSCOPE)).toBe(
"Missing API key for DashScope native auth. Set settings.security.auth.apiKey, or set the 'DASHSCOPE_API_KEY' environment variable.",
);
});

it('should return null for USE_OPENAI with custom envKey from modelProviders', () => {
vi.mocked(settings.loadSettings).mockReturnValue({
merged: {
Expand Down
27 changes: 27 additions & 0 deletions packages/cli/src/config/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const DEFAULT_ENV_KEYS: Record<string, string> = {
[AuthType.USE_ANTHROPIC]: 'ANTHROPIC_API_KEY',
[AuthType.USE_GEMINI]: 'GEMINI_API_KEY',
[AuthType.USE_VERTEX_AI]: 'GOOGLE_API_KEY',
[AuthType.USE_DASHSCOPE]: 'DASHSCOPE_API_KEY',
};

/**
Expand Down Expand Up @@ -259,6 +260,32 @@ export function validateAuthMethod(
return null;
}

if (authMethod === AuthType.USE_DASHSCOPE) {
const { hasKey, checkedEnvKey, isExplicitEnvKey } = hasApiKeyForAuth(
authMethod,
settings.merged,
config,
);
if (!hasKey) {
const envKeyHint = checkedEnvKey
? `'${checkedEnvKey}'`
: "'DASHSCOPE_API_KEY'";
if (isExplicitEnvKey) {
// Explicit envKey configured - only suggest setting the env var
return t(
'Missing API key for DashScope native auth. Set the {{envKeyHint}} environment variable.',
{ envKeyHint },
);
}
// Default env key - can use either apiKey or env var
return t(
'Missing API key for DashScope native auth. Set settings.security.auth.apiKey, or set the {{envKeyHint}} environment variable.',
{ envKeyHint },
);
}
return null;
}

if (authMethod === AuthType.QWEN_OAUTH) {
// Qwen OAuth free tier was discontinued on 2026-04-15.
// Block new OAuth setups; existing cached tokens still work until server rejects them.
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -955,6 +955,7 @@ export async function parseArguments(): Promise<CliArgs> {
AuthType.QWEN_OAUTH,
AuthType.USE_GEMINI,
AuthType.USE_VERTEX_AI,
AuthType.USE_DASHSCOPE,
],
description: 'Authentication type',
})
Expand Down
3 changes: 3 additions & 0 deletions packages/cli/src/config/settingsSchema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,9 @@ describe('SettingsSchema', () => {
expect(getSettingsSchema().permissions.showInDialog).toBe(false);
expect(getSettingsSchema().mcpServers.showInDialog).toBe(false);
expect(getSettingsSchema().telemetry.showInDialog).toBe(false);
expect(
getSettingsSchema().model.properties.reasoningEffort.showInDialog,
).toBe(true);

// Check that some settings are appropriately hidden
expect(getSettingsSchema().ui.properties.theme.showInDialog).toBe(true);
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/config/settingsSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ const SETTINGS_SCHEMA = {
requiresRestart: true,
default: {} as ProviderProtocolConfig,
description:
'Maps a custom modelProviders provider id to the SDK protocol that routes its requests (e.g. {"idealab": "openai"}). Lets a custom provider id reuse a built-in protocol. Built-in provider ids (openai, gemini, anthropic, vertex-ai, qwen-oauth) are routed automatically and need no entry.',
'Maps a custom modelProviders provider id to the SDK protocol that routes its requests (e.g. {"idealab": "openai"}). Lets a custom provider id reuse a built-in protocol. Built-in provider ids (openai, gemini, anthropic, vertex-ai, qwen-oauth, dashscope) are routed automatically and need no entry.',
showInDialog: false,
mergeStrategy: MergeStrategy.REPLACE,
},
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/i18n/locales/ca.js
Original file line number Diff line number Diff line change
Expand Up @@ -1969,8 +1969,8 @@ export default {
'(Useu les fletxes ↑ ↓ per navegar, Return per seleccionar, Ctrl+C per sortir)\n',
'Switch to plan mode or exit plan mode':
'Canviar al mode de planificació o sortir del mode de planificació',
'Set how hard reasoning-capable models think ({{tiers}}); mapped and clamped per provider.':
'Defineix la intensitat de raonament dels models compatibles ({{tiers}}); mapejada i limitada segons el proveïdor.',
'Set how hard reasoning-capable models think; available tiers depend on the active provider/model.':
'Defineix la intensitat de raonament dels models compatibles; els nivells disponibles depenen del proveïdor/model actiu.',
'Exited plan mode. Previous approval mode restored.':
"S'ha sortit del mode de planificació. S'ha restaurat el mode d'aprovació anterior.",
'Enabled plan mode. The agent will analyze and plan without executing tools.':
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/i18n/locales/de.js
Original file line number Diff line number Diff line change
Expand Up @@ -1928,8 +1928,8 @@ export default {
'to expand details': 'zum Erweitern der Details',
'Switch to plan mode or exit plan mode':
'In den Plan-Modus wechseln oder den Plan-Modus verlassen',
'Set how hard reasoning-capable models think ({{tiers}}); mapped and clamped per provider.':
'Legt fest, wie intensiv schlussfolgerungsfähige Modelle denken ({{tiers}}); je nach Anbieter zugeordnet und begrenzt.',
'Set how hard reasoning-capable models think; available tiers depend on the active provider/model.':
'Legt fest, wie intensiv schlussfolgerungsfähige Modelle denken; verfügbare Stufen hängen vom aktiven Anbieter/Modell ab.',
'Exited plan mode. Previous approval mode restored.':
'Plan-Modus verlassen. Vorheriger Genehmigungsmodus wiederhergestellt.',
'Enabled plan mode. The agent will analyze and plan without executing tools.':
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/i18n/locales/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -2488,8 +2488,8 @@ export default {
'(Use ↑ ↓ arrows to navigate, Enter to select, Ctrl+C to exit)\n',
'Switch to plan mode or exit plan mode':
'Switch to plan mode or exit plan mode',
'Set how hard reasoning-capable models think ({{tiers}}); mapped and clamped per provider.':
'Set how hard reasoning-capable models think ({{tiers}}); mapped and clamped per provider.',
'Set how hard reasoning-capable models think; available tiers depend on the active provider/model.':
'Set how hard reasoning-capable models think; available tiers depend on the active provider/model.',
'Set a goal — keep working until the condition is met':
'Set a goal — keep working until the condition is met',
'Set or control a session goal': 'Set or control a session goal',
Expand Down
Loading
Loading