diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0c3e0f329bf..53d817b3c2a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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: '' diff --git a/docs/users/configuration/auth.md b/docs/users/configuration/auth.md index 158d6c48e7c..e8b58d3045b 100644 --- a/docs/users/configuration/auth.md +++ b/docs/users/configuration/auth.md @@ -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). - **Custom Provider**: manually connect a local server, proxy, or unsupported provider — supports OpenAI, Anthropic, Gemini, and other compatible endpoints. > [!note] diff --git a/docs/users/overview.md b/docs/users/overview.md index 367cef2b6ef..ff9042e0958 100644 --- a/docs/users/overview.md +++ b/docs/users/overview.md @@ -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? diff --git a/docs/users/quickstart.md b/docs/users/quickstart.md index 83896f5f36f..ad5d7538294 100644 --- a/docs/users/quickstart.md +++ b/docs/users/quickstart.md @@ -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. diff --git a/packages/core/src/providers/__tests__/presets/moonshot.test.ts b/packages/core/src/providers/__tests__/presets/moonshot.test.ts new file mode 100644 index 00000000000..98ca14edb57 --- /dev/null +++ b/packages/core/src/providers/__tests__/presets/moonshot.test.ts @@ -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', + 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(); + }); +}); diff --git a/packages/core/src/providers/all-providers.ts b/packages/core/src/providers/all-providers.ts index aa5aad3dba6..d2df5813c7e 100644 --- a/packages/core/src/providers/all-providers.ts +++ b/packages/core/src/providers/all-providers.ts @@ -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'; @@ -33,6 +34,7 @@ export { grokProvider, minimaxProvider, zaiProvider, + moonshotProvider, idealabProvider, modelscopeProvider, customProvider, @@ -58,6 +60,7 @@ export const ALL_PROVIDERS: readonly ProviderConfig[] = [ grokProvider, minimaxProvider, zaiProvider, + moonshotProvider, idealabProvider, modelscopeProvider, openRouterProvider, diff --git a/packages/core/src/providers/index.ts b/packages/core/src/providers/index.ts index fbe784bae52..1ec27bddcab 100644 --- a/packages/core/src/providers/index.ts +++ b/packages/core/src/providers/index.ts @@ -51,6 +51,7 @@ export { idealabProvider, minimaxProvider, modelscopeProvider, + moonshotProvider, openRouterProvider, requestyProvider, THIRD_PARTY_PROVIDERS, diff --git a/packages/core/src/providers/presets/moonshot.ts b/packages/core/src/providers/presets/moonshot.ts new file mode 100644 index 00000000000..c3dc3cd436c --- /dev/null +++ b/packages/core/src/providers/presets/moonshot.ts @@ -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', +}; diff --git a/packages/core/src/telemetry/gen-ai-provider.test.ts b/packages/core/src/telemetry/gen-ai-provider.test.ts index bd8c94bfe44..b48d1e5762b 100644 --- a/packages/core/src/telemetry/gen-ai-provider.test.ts +++ b/packages/core/src/telemetry/gen-ai-provider.test.ts @@ -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'], @@ -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'], diff --git a/packages/core/src/telemetry/gen-ai-provider.ts b/packages/core/src/telemetry/gen-ai-provider.ts index e699d8dbee4..2d40d34108e 100644 --- a/packages/core/src/telemetry/gen-ai-provider.ts +++ b/packages/core/src/telemetry/gen-ai-provider.ts @@ -30,6 +30,7 @@ const PROVIDER_BY_ENV_KEY: Readonly> = { 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', @@ -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') diff --git a/scripts/tests/no-ak-integration-ci.test.js b/scripts/tests/no-ak-integration-ci.test.js index 0756b0236e9..b122dfdbbe5 100644 --- a/scripts/tests/no-ak-integration-ci.test.js +++ b/scripts/tests/no-ak-integration-ci.test.js @@ -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',