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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,7 @@ jobs:
IDEALAB_API_KEY: ''
MINIMAX_API_KEY: ''
MODELSCOPE_API_KEY: ''
MOONSHOT_API_KEY: ''
OPENROUTER_API_KEY: ''
REQUESTY_API_KEY: ''
XAI_API_KEY: ''
Expand Down
2 changes: 1 addition & 1 deletion docs/users/configuration/auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Qwen Code's first-run `/auth` menu has three top-level options. Pick the one that matches how you want to run the CLI:

- **Alibaba ModelStudio**: official recommended setup. Opens a sub-menu with **Coding Plan** (for individual developers · weekly quota included), **Token Plan** (for teams and companies · usage-based billing with a dedicated endpoint), or **Standard API Key** (connect with an existing ModelStudio API key).
- **Third-party Providers**: choose a built-in provider and connect with an API key (DeepSeek, MiniMax, Z.AI, Idealab, ModelScope, OpenRouter, Requesty).
- **Third-party Providers**: choose a built-in provider and connect with an API key (DeepSeek, Grok, MiniMax, Z.AI, Kimi, Idealab, ModelScope, OpenRouter, Requesty).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] This PR adds Kimi to two provider lists (this one and the one in docs/users/overview.md) but not to the third sibling list at docs/users/quickstart.md:75 — "choose a built-in provider (DeepSeek, MiniMax, Z.AI, ModelScope, OpenRouter, Requesty, and more)" — so immediately after this change ships a built-in Kimi, the three first-run docs disagree about which providers are built in. The quickstart list is explicitly non-exhaustive ("and more") and already omitted Grok and Idealab before this PR, so leaving it as a summary is defensible — but syncing it would fix every stale entry at once. If the list is meant to track the registry, update it to (DeepSeek, Grok, MiniMax, Z.AI, Kimi, Idealab, ModelScope, OpenRouter, Requesty, and more).

中文说明

本 PR 在两处 provider 列表(此处的 auth.mddocs/users/overview.md)中加入了 Kimi,但第三处同类列表 docs/users/quickstart.md:75 —— "choose a built-in provider (DeepSeek, MiniMax, Z.AI, ModelScope, OpenRouter, Requesty, and more)" —— 未同步,导致本改动上线后三个入门文档对内置 provider 的口径不一致。quickstart 的列表本身是非穷举的("and more"),且在本 PR 之前就已缺少 Grok 与 Idealab,因此维持现状作为摘要也说得通;但若同步一次,可以一并修复所有过期条目。若该列表意在跟随注册表,可更新为 (DeepSeek, Grok, MiniMax, Z.AI, Kimi, Idealab, ModelScope, OpenRouter, Requesty, and more)

— qwen3.8-max via Qwen Code /review (v0.22.0)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed and synced in e9d712f. The list now reads (DeepSeek, Grok, MiniMax, Z.AI, Kimi, Idealab, ModelScope, OpenRouter, Requesty, and more), so all three first-run docs now name the same set. As you noted, that also clears the Grok and Idealab entries that were already stale before this branch.

中文说明

同意,已在 e9d712f 同步。该列表现为 (DeepSeek, Grok, MiniMax, Z.AI, Kimi, Idealab, ModelScope, OpenRouter, Requesty, and more),三处入门文档口径已一致。正如你指出的,这同时修正了本分支之前就已过期的 Grok 与 Idealab 条目。

- **Custom Provider**: manually connect a local server, proxy, or unsupported provider — supports OpenAI, Anthropic, Gemini, and other compatible endpoints.

> [!note]
Expand Down
2 changes: 1 addition & 1 deletion docs/users/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ cd your-project
qwen
```

On first launch you'll be prompted to connect a model provider. The menu offers **Alibaba ModelStudio** (Coding Plan, Token Plan, or Standard API Key), **Third-party Providers** (built-in providers such as DeepSeek, MiniMax, Z.AI, and OpenRouter, connected with an API key), and **Custom Provider** (a local server, proxy, or unsupported provider). For the [Alibaba Cloud Coding Plan](https://bailian.console.aliyun.com/cn-beijing/?tab=coding-plan#/efm/coding-plan-index) ([intl](https://modelstudio.console.alibabacloud.com/?tab=coding-plan#/efm/coding-plan-index)), choose **Alibaba ModelStudio → Coding Plan**; to use a ModelStudio API key, choose **Alibaba ModelStudio → Standard API Key** and follow the API setup guide ([Beijing](https://bailian.console.aliyun.com/cn-beijing/?tab=doc#/doc/?type=model&url=3023091) / [intl](https://modelstudio.console.alibabacloud.com/ap-southeast-1?tab=doc#/doc/?type=model&url=2974721)). Then let's start with understanding your codebase. Try one of these commands:
On first launch you'll be prompted to connect a model provider. The menu offers **Alibaba ModelStudio** (Coding Plan, Token Plan, or Standard API Key), **Third-party Providers** (built-in providers such as DeepSeek, MiniMax, Z.AI, Kimi, and OpenRouter, connected with an API key), and **Custom Provider** (a local server, proxy, or unsupported provider). For the [Alibaba Cloud Coding Plan](https://bailian.console.aliyun.com/cn-beijing/?tab=coding-plan#/efm/coding-plan-index) ([intl](https://modelstudio.console.alibabacloud.com/?tab=coding-plan#/efm/coding-plan-index)), choose **Alibaba ModelStudio → Coding Plan**; to use a ModelStudio API key, choose **Alibaba ModelStudio → Standard API Key** and follow the API setup guide ([Beijing](https://bailian.console.aliyun.com/cn-beijing/?tab=doc#/doc/?type=model&url=3023091) / [intl](https://modelstudio.console.alibabacloud.com/ap-southeast-1?tab=doc#/doc/?type=model&url=2974721)). Then let's start with understanding your codebase. Try one of these commands:

```
what does this project do?
Expand Down
2 changes: 1 addition & 1 deletion docs/users/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ The first-run menu lets you connect a model provider. Choose one of:
- **Coding Plan**: for individual developers, with an included weekly quota and diverse model options. See the [Coding Plan guide](https://bailian.console.aliyun.com/cn-beijing/?tab=coding-plan#/efm/coding-plan-index) ([intl](https://modelstudio.console.alibabacloud.com/?tab=coding-plan#/efm/coding-plan-index)) for setup instructions.
- **Token Plan**: usage-based billing with a dedicated endpoint, aimed at teams and companies.
- **Standard API Key**: connect with an existing API key from Alibaba Cloud ModelStudio ([Beijing](https://bailian.console.aliyun.com/) / [intl](https://modelstudio.console.alibabacloud.com/)). See the API setup guide ([Beijing](https://bailian.console.aliyun.com/cn-beijing/?tab=doc#/doc/?type=model&url=3023091) / [intl](https://modelstudio.console.alibabacloud.com/ap-southeast-1?tab=doc#/doc/?type=model&url=2974721)) for details.
- **Third-party Providers** — choose a built-in provider (DeepSeek, MiniMax, Z.AI, ModelScope, OpenRouter, Requesty, and more) and connect with an API key.
- **Third-party Providers** — choose a built-in provider (DeepSeek, Grok, MiniMax, Z.AI, Kimi, Idealab, ModelScope, OpenRouter, Requesty, and more) and connect with an API key.
- **Custom Provider** — manually connect a local server, proxy, or unsupported provider.

> ⚠️ **Note**: Qwen OAuth was discontinued on April 15, 2026. If you were previously using Qwen OAuth, please switch to one of the methods above.
Expand Down
142 changes: 142 additions & 0 deletions packages/core/src/providers/__tests__/presets/moonshot.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/**
* @license
* Copyright 2026 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/

import { describe, expect, it } from 'vitest';
import { AuthType } from '../../../core/contentGenerator.js';
import { moonshotProvider } from '../../presets/moonshot.js';
import {
ALL_PROVIDERS,
THIRD_PARTY_PROVIDERS,
findProviderByCredentials,
findProviderById,
getAllProviderBaseUrls,
} from '../../all-providers.js';
import { buildInstallPlan } from '../../provider-config.js';

const INTL_BASE_URL = 'https://api.moonshot.ai/v1';
const CHINA_BASE_URL = 'https://api.moonshot.cn/v1';

describe('moonshotProvider', () => {
it('has correct provider config', () => {
expect(moonshotProvider).toMatchObject({
id: 'moonshot',
label: 'Kimi (Moonshot AI) API Key',
protocol: AuthType.USE_OPENAI,
envKey: 'MOONSHOT_API_KEY',
modelNamePrefix: 'Kimi',
modelsEditable: true,
uiGroup: 'third-party',
});
});

it('offers international and China endpoints', () => {
expect(Array.isArray(moonshotProvider.baseUrl)).toBe(true);
const urls = (moonshotProvider.baseUrl as Array<{ url: string }>).map(
(o) => o.url,
);
expect(urls).toEqual([INTL_BASE_URL, CHINA_BASE_URL]);
});

it('is registered and discoverable in the provider registry', () => {
expect(findProviderById('moonshot')).toBe(moonshotProvider);
expect(ALL_PROVIDERS).toContain(moonshotProvider);
expect(THIRD_PARTY_PROVIDERS).toContain(moonshotProvider);
expect(getAllProviderBaseUrls()).toContain(INTL_BASE_URL);
expect(getAllProviderBaseUrls()).toContain(CHINA_BASE_URL);
});

it('is found by its env key + base URL credentials', () => {
expect(
findProviderByCredentials(INTL_BASE_URL, 'MOONSHOT_API_KEY')?.id,
).toBe('moonshot');
expect(
findProviderByCredentials(CHINA_BASE_URL, 'MOONSHOT_API_KEY')?.id,
).toBe('moonshot');
// Wrong base URL for the right key must not match.
expect(
findProviderByCredentials(
'https://wrong.example.com/v1',
'MOONSHOT_API_KEY',
),
).toBeUndefined();
});

it('creates an install plan with per-model metadata for known IDs', () => {
const plan = buildInstallPlan(moonshotProvider, {
baseUrl: INTL_BASE_URL,
apiKey: 'sk-moonshot',
modelIds: [
'kimi-k3',
'kimi-k2.7-code',
'kimi-k2.7-code-highspeed',
'kimi-k2.6',
],
});

const models = plan.modelProviders?.[0]?.models;
expect(models).toHaveLength(4);

// K3 always thinks: mandatory, never a plain enable_thinking toggle.
expect(models?.[0]).toMatchObject({
id: 'kimi-k3',
name: '[Kimi] kimi-k3',
generationConfig: {
contextWindowSize: 1000000,
thinkingMandatory: true,
modalities: { image: true, video: true },
},
});
expect(models?.[0]?.generationConfig?.extra_body).toBeUndefined();

expect(models?.[1]).toMatchObject({
id: 'kimi-k2.7-code',
name: '[Kimi] kimi-k2.7-code',
generationConfig: {
contextWindowSize: 262144,
extra_body: { enable_thinking: true },
modalities: { image: true, video: true },
},
});
expect(models?.[1]?.generationConfig?.thinkingMandatory).toBeUndefined();

expect(models?.[2]).toMatchObject({
id: 'kimi-k2.7-code-highspeed',
name: '[Kimi] kimi-k2.7-code-highspeed',
Comment on lines +105 to +107

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] The new kimi-k2.7-code-highspeed assertion uses a partial toMatchObject and never pins the absence of thinkingMandatory — unlike the K3 block above, which pins extra_body as toBeUndefined(). Mutation probe: adding thinkingMandatory: true to the highspeed spec in presets/moonshot.ts survives the whole test file (all 6 tests still pass). The mutant is not equivalent: thinkingMandatory changes wire behaviour — pipeline.ts strips the thinking-disable shape (enable_thinking: false, reasoning_effort: 'none') for such models. So if a future edit marks kimi-k2.7-code-highspeed (or kimi-k2.7-code, whose block has the same shape) as thinking-mandatory, buildGenerationConfig would emit thinkingMandatory: true, the pipeline would silently stop shipping the thinking-disable shape on the wire, users would lose the ability to turn thinking off on a toggleable model, and this test would stay green.

Witness (mutation run in a scratch tree, npx vitest run src/providers/__tests__/presets/moonshot.test.ts):

baseline (unmodified PR):          Tests 6 passed (6)
mutant (thinkingMandatory: true):  Tests 6 passed (6)   <- survives
mutant + negative assertion:       AssertionError: expected true to be undefined -> 1 failed | 5 passed

Mirror the K3 negative assertion — after the highspeed block closes, add:

expect(models?.[2]?.generationConfig?.thinkingMandatory).toBeUndefined();

(optionally the same for models?.[1]).

中文说明

新增的 kimi-k2.7-code-highspeed 断言使用了部分的 toMatchObject,没有钉住 thinkingMandatory 的缺席——不像上面的 K3 断言那样把 extra_body 钉为 toBeUndefined()。变异探测证实:在 presets/moonshot.ts 的 highspeed 规格中加入 thinkingMandatory: true 后,整个测试文件仍然全部通过(6/6)。该变异体并非等价变异:thinkingMandatory 会改变线上行为——pipeline.ts 会为此类模型剥离关闭 thinking 的请求形状(enable_thinking: falsereasoning_effort: 'none')。因此,如果未来某次修改把 kimi-k2.7-code-highspeed(或断言形状相同的 kimi-k2.7-code)标记为强制 thinking,buildGenerationConfig 会输出 thinkingMandatory: true,管线会悄然在线上剥离关闭 thinking 的形状,用户将无法在可开关 thinking 的模型上关闭它,而该测试依然绿灯。

见证(在临时树中运行变异,npx vitest run src/providers/__tests__/presets/moonshot.test.ts):基线(未修改的 PR)Tests 6 passed (6);变异(加入 thinkingMandatory: trueTests 6 passed (6),变异存活;变异 + 负向断言 AssertionError: expected true to be undefined,1 failed | 5 passed。

建议参照 K3 的负向断言——在 highspeed 断言块结束后补充 expect(models?.[2]?.generationConfig?.thinkingMandatory).toBeUndefined();(也可同样为 models?.[1] 补充)。

— qwen3.8-max via Qwen Code /review (v0.22.0)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed — fixed in 03b9651. Added expect(models?.[N]?.generationConfig?.thinkingMandatory).toBeUndefined(); after both code-model blocks, not just highspeed, since as you noted kimi-k2.7-code had the identical shape.

Re-ran your probe against the fix: adding thinkingMandatory: true to the highspeed spec now gives 1 failed | 5 passed where it previously survived at 6/6.

中文说明

同意,已在 03b9651 修复。在两个代码模型断言块之后都补充了 expect(models?.[N]?.generationConfig?.thinkingMandatory).toBeUndefined();,而不只是 highspeed —— 正如你指出的,kimi-k2.7-code 的断言形状完全相同。

已用你的探针回验:向 highspeed 规格加入 thinkingMandatory: true 后现在为 1 failed | 5 passed,此前该变异以 6/6 存活。

generationConfig: {
contextWindowSize: 262144,
extra_body: { enable_thinking: true },
modalities: { image: true, video: true },
},
});
expect(models?.[2]?.generationConfig?.thinkingMandatory).toBeUndefined();

expect(models?.[3]).toMatchObject({
id: 'kimi-k2.6',
name: '[Kimi] kimi-k2.6',
generationConfig: {
contextWindowSize: 262144,
extra_body: { enable_thinking: true },
modalities: { image: true, video: true },
},
});
expect(models?.[3]?.generationConfig?.thinkingMandatory).toBeUndefined();
});

it('falls back gracefully for unknown model IDs', () => {
const plan = buildInstallPlan(moonshotProvider, {
baseUrl: CHINA_BASE_URL,
apiKey: 'sk-moonshot',
modelIds: ['kimi-k4-preview'],
});

const models = plan.modelProviders?.[0]?.models;
expect(models?.[0]).toMatchObject({
id: 'kimi-k4-preview',
name: '[Kimi] kimi-k4-preview',
});
expect(models?.[0]?.generationConfig).toBeUndefined();
});
});
3 changes: 3 additions & 0 deletions packages/core/src/providers/all-providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { deepseekProvider } from './presets/deepseek.js';
import { grokProvider } from './presets/grok.js';
import { minimaxProvider } from './presets/minimax.js';
import { zaiProvider } from './presets/zai.js';
import { moonshotProvider } from './presets/moonshot.js';
import { idealabProvider } from './presets/idealab.js';
import { modelscopeProvider } from './presets/modelscope.js';
import { customProvider } from './presets/custom-provider.js';
Expand All @@ -33,6 +34,7 @@ export {
grokProvider,
minimaxProvider,
zaiProvider,
moonshotProvider,
idealabProvider,
modelscopeProvider,
customProvider,
Expand All @@ -58,6 +60,7 @@ export const ALL_PROVIDERS: readonly ProviderConfig[] = [
grokProvider,
minimaxProvider,
zaiProvider,
moonshotProvider,
idealabProvider,
modelscopeProvider,
openRouterProvider,
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export {
idealabProvider,
minimaxProvider,
modelscopeProvider,
moonshotProvider,
openRouterProvider,
requestyProvider,
THIRD_PARTY_PROVIDERS,
Expand Down
61 changes: 61 additions & 0 deletions packages/core/src/providers/presets/moonshot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* @license
* Copyright 2026 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/

import { AuthType } from '../../core/contentGenerator.js';
import type { ProviderConfig } from '../types.js';

export const moonshotProvider: ProviderConfig = {
id: 'moonshot',
label: 'Kimi (Moonshot AI) API Key',
description: 'Quick setup for Kimi models',
protocol: AuthType.USE_OPENAI,
baseUrl: [
{
id: 'international',
label: 'International',
url: 'https://api.moonshot.ai/v1',
documentationUrl: 'https://platform.kimi.ai/docs',
},
{
id: 'china',
label: 'China',
url: 'https://api.moonshot.cn/v1',
documentationUrl: 'https://platform.moonshot.cn/docs',
},
],
envKey: 'MOONSHOT_API_KEY',
models: [
{
id: 'kimi-k3',
contextWindowSize: 1000000,
// K3 always thinks: the API exposes `reasoning_effort` but no way to
// turn thinking off, so never emit a disable shape on the wire.
thinkingMandatory: true,
modalities: { image: true, video: true },
},
{
id: 'kimi-k2.7-code',
contextWindowSize: 262144,
enableThinking: true,
modalities: { image: true, video: true },
},
{
id: 'kimi-k2.7-code-highspeed',
contextWindowSize: 262144,
enableThinking: true,
modalities: { image: true, video: true },
},
{
id: 'kimi-k2.6',
contextWindowSize: 262144,
enableThinking: true,
modalities: { image: true, video: true },
},
],
modelsEditable: true,
modelNamePrefix: 'Kimi',
uiGroup: 'third-party',
};
3 changes: 3 additions & 0 deletions packages/core/src/telemetry/gen-ai-provider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ describe('GenAI provider resolution', () => {
['https://api.deepseek.com/v1', 'deepseek'],
['https://api.x.ai/v1', 'x_ai'],
['https://api.mistral.ai/v1', 'mistral_ai'],
['https://api.moonshot.ai/v1', 'moonshot'],
['https://api.moonshot.cn/v1', 'moonshot'],
['https://api.minimax.io/v1', 'minimax'],
['https://api.minimaxi.com/v1', 'minimax'],
['https://api.z.ai/api/paas/v4', 'z_ai'],
Expand Down Expand Up @@ -126,6 +128,7 @@ describe('GenAI provider resolution', () => {
['MIMO_API_KEY', 'mimo'],
['ZAI_API_KEY', 'z_ai'],
['MODELSCOPE_API_KEY', 'modelscope'],
['MOONSHOT_API_KEY', 'moonshot'],
['OPENAI_API_KEY', 'openai'],
['XIAOMI_MIMO_API_KEY', 'mimo'],
['OPENROUTER_API_KEY', 'openrouter'],
Expand Down
7 changes: 7 additions & 0 deletions packages/core/src/telemetry/gen-ai-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const PROVIDER_BY_ENV_KEY: Readonly<Record<string, string>> = {
MIMO_API_KEY: 'mimo',
MISTRAL_API_KEY: 'mistral_ai',
MODELSCOPE_API_KEY: 'modelscope',
MOONSHOT_API_KEY: 'moonshot',
OPENAI_API_KEY: 'openai',
OPENROUTER_API_KEY: 'openrouter',
REQUESTY_API_KEY: 'requesty',
Expand Down Expand Up @@ -63,6 +64,12 @@ function providerFromHostname(hostname: string): string | undefined {
if (isHostOrSubdomain(hostname, 'deepseek.com')) return 'deepseek';
if (isHostOrSubdomain(hostname, 'x.ai')) return 'x_ai';
if (isHostOrSubdomain(hostname, 'mistral.ai')) return 'mistral_ai';
if (
isHostOrSubdomain(hostname, 'moonshot.ai') ||
isHostOrSubdomain(hostname, 'moonshot.cn')
) {
return 'moonshot';
}
if (
isHostOrSubdomain(hostname, 'minimax.io') ||
isHostOrSubdomain(hostname, 'minimaxi.com')
Expand Down
1 change: 1 addition & 0 deletions scripts/tests/no-ak-integration-ci.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ describe('no-AK integration CI wiring', () => {
'IDEALAB_API_KEY',
'MINIMAX_API_KEY',
'MODELSCOPE_API_KEY',
'MOONSHOT_API_KEY',
'OPENAI_API_KEY',
'OPENAI_BASE_URL',
'OPENAI_MODEL',
Expand Down
Loading