diff --git a/src/components/ProviderManager.tsx b/src/components/ProviderManager.tsx index 681cd1a2..0fe8a8a7 100644 --- a/src/components/ProviderManager.tsx +++ b/src/components/ProviderManager.tsx @@ -1032,6 +1032,16 @@ export function ProviderManager({ mode, onDone }: Props): React.ReactNode { label: 'LM Studio', description: 'Local LM Studio endpoint', }, + { + value: 'dashscope-cn', + label: 'Alibaba Coding Plan (China)', + description: 'Alibaba DashScope China endpoint', + }, + { + value: 'dashscope-intl', + label: 'Alibaba Coding Plan', + description: 'Alibaba DashScope International endpoint', + }, { value: 'custom', label: 'Custom', diff --git a/src/utils/context.test.ts b/src/utils/context.test.ts index 3ce3fde7..3d6c96ec 100644 --- a/src/utils/context.test.ts +++ b/src/utils/context.test.ts @@ -141,3 +141,116 @@ test('MiniMax-M2.5 and M2.1 use explicit provider-specific context and output ca upperLimit: 131_072, }) }) + +test('DashScope qwen3.6-plus uses provider-specific context and output caps', () => { + process.env.CLAUDE_CODE_USE_OPENAI = '1' + delete process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS + + expect(getContextWindowForModel('qwen3.6-plus')).toBe(1_000_000) + expect(getModelMaxOutputTokens('qwen3.6-plus')).toEqual({ + default: 65_536, + upperLimit: 65_536, + }) + expect(getMaxOutputTokensForModel('qwen3.6-plus')).toBe(65_536) +}) + +test('DashScope qwen3.5-plus uses provider-specific context and output caps', () => { + process.env.CLAUDE_CODE_USE_OPENAI = '1' + delete process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS + + expect(getContextWindowForModel('qwen3.5-plus')).toBe(1_000_000) + expect(getModelMaxOutputTokens('qwen3.5-plus')).toEqual({ + default: 65_536, + upperLimit: 65_536, + }) + expect(getMaxOutputTokensForModel('qwen3.5-plus')).toBe(65_536) +}) + +test('DashScope qwen3-coder-plus uses provider-specific context and output caps', () => { + process.env.CLAUDE_CODE_USE_OPENAI = '1' + delete process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS + + expect(getContextWindowForModel('qwen3-coder-plus')).toBe(1_000_000) + expect(getModelMaxOutputTokens('qwen3-coder-plus')).toEqual({ + default: 65_536, + upperLimit: 65_536, + }) +}) + +test('DashScope qwen3-coder-next uses provider-specific context and output caps', () => { + process.env.CLAUDE_CODE_USE_OPENAI = '1' + delete process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS + + expect(getContextWindowForModel('qwen3-coder-next')).toBe(262_144) + expect(getModelMaxOutputTokens('qwen3-coder-next')).toEqual({ + default: 65_536, + upperLimit: 65_536, + }) +}) + +test('DashScope qwen3-max uses provider-specific context and output caps', () => { + process.env.CLAUDE_CODE_USE_OPENAI = '1' + delete process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS + + expect(getContextWindowForModel('qwen3-max')).toBe(262_144) + expect(getModelMaxOutputTokens('qwen3-max')).toEqual({ + default: 32_768, + upperLimit: 32_768, + }) +}) + +test('DashScope qwen3-max dated variant resolves to base entry via prefix match', () => { + process.env.CLAUDE_CODE_USE_OPENAI = '1' + delete process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS + + expect(getContextWindowForModel('qwen3-max-2026-01-23')).toBe(262_144) + expect(getModelMaxOutputTokens('qwen3-max-2026-01-23')).toEqual({ + default: 32_768, + upperLimit: 32_768, + }) +}) + +test('DashScope kimi-k2.5 uses provider-specific context and output caps', () => { + process.env.CLAUDE_CODE_USE_OPENAI = '1' + delete process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS + + expect(getContextWindowForModel('kimi-k2.5')).toBe(262_144) + expect(getModelMaxOutputTokens('kimi-k2.5')).toEqual({ + default: 32_768, + upperLimit: 32_768, + }) +}) + +test('DashScope glm-5 uses provider-specific context and output caps', () => { + process.env.CLAUDE_CODE_USE_OPENAI = '1' + delete process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS + + expect(getContextWindowForModel('glm-5')).toBe(202_752) + expect(getModelMaxOutputTokens('glm-5')).toEqual({ + default: 16_384, + upperLimit: 16_384, + }) +}) + +test('DashScope glm-4.7 uses provider-specific context and output caps', () => { + process.env.CLAUDE_CODE_USE_OPENAI = '1' + delete process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS + + expect(getContextWindowForModel('glm-4.7')).toBe(202_752) + expect(getModelMaxOutputTokens('glm-4.7')).toEqual({ + default: 16_384, + upperLimit: 16_384, + }) +}) + +test('DashScope models clamp oversized max output overrides to the provider limit', () => { + process.env.CLAUDE_CODE_USE_OPENAI = '1' + process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS = '100000' + + expect(getMaxOutputTokensForModel('qwen3.6-plus')).toBe(65_536) + expect(getMaxOutputTokensForModel('qwen3.5-plus')).toBe(65_536) + expect(getMaxOutputTokensForModel('qwen3-coder-next')).toBe(65_536) + expect(getMaxOutputTokensForModel('qwen3-max')).toBe(32_768) + expect(getMaxOutputTokensForModel('kimi-k2.5')).toBe(32_768) + expect(getMaxOutputTokensForModel('glm-5')).toBe(16_384) +}) diff --git a/src/utils/model/openaiContextWindows.ts b/src/utils/model/openaiContextWindows.ts index fc815f30..8af1ffb5 100644 --- a/src/utils/model/openaiContextWindows.ts +++ b/src/utils/model/openaiContextWindows.ts @@ -202,6 +202,21 @@ const OPENAI_CONTEXT_WINDOWS: Record = { 'llama3.2:1b': 128_000, 'qwen3:8b': 128_000, 'codestral': 32_768, + + // Alibaba DashScope (Coding Plan) + // Model context windows from DashScope API /models endpoint (April 2026). + // Values sourced from: qwen3.5-plus/qwen3-coder-plus (1M), qwen3-coder-next/max (256K), + // kimi-k2.5 (256K), glm-5/glm-4.7 (198K). + // Max output tokens: Qwen variants (64K/32K), GLM (16K). + 'qwen3.6-plus': 1_000_000, + 'qwen3.5-plus': 1_000_000, + 'qwen3-coder-plus': 1_000_000, + 'qwen3-coder-next': 262_144, + 'qwen3-max': 262_144, + 'qwen3-max-2026-01-23': 262_144, + 'kimi-k2.5': 262_144, + 'glm-5': 202_752, + 'glm-4.7': 202_752, } /** @@ -330,6 +345,11 @@ const OPENAI_MAX_OUTPUT_TOKENS: Record = { 'deepseek-r1:14b': 8_192, 'mistral:7b': 4_096, 'phi4:14b': 4_096, + 'gemma2:27b': 4_096, + 'codellama:13b': 4_096, + 'llama3.2:1b': 4_096, + 'qwen3:8b': 8_192, + 'codestral': 8_192, // NVIDIA NIM models 'nvidia/llama-3.1-nemotron-70b-instruct': 32_768, @@ -356,6 +376,17 @@ const OPENAI_MAX_OUTPUT_TOKENS: Record = { 'databricks/dbrx-instruct': 32_768, 'ai21labs/jamba-1.5-large-instruct': 32_768, '01-ai/yi-large': 8_192, + + // Alibaba DashScope (Coding Plan) + 'qwen3.6-plus': 65_536, + 'qwen3.5-plus': 65_536, + 'qwen3-coder-plus': 65_536, + 'qwen3-coder-next': 65_536, + 'qwen3-max': 32_768, + 'qwen3-max-2026-01-23': 32_768, + 'kimi-k2.5': 32_768, + 'glm-5': 16_384, + 'glm-4.7': 16_384, } function lookupByModel(table: Record, model: string): T | undefined { diff --git a/src/utils/providerProfiles.ts b/src/utils/providerProfiles.ts index b2b2549f..540f7e7f 100644 --- a/src/utils/providerProfiles.ts +++ b/src/utils/providerProfiles.ts @@ -20,6 +20,8 @@ export type ProviderPreset = | 'azure-openai' | 'openrouter' | 'lmstudio' + | 'dashscope-cn' + | 'dashscope-intl' | 'custom' | 'nvidia-nim' | 'minimax' @@ -220,6 +222,24 @@ export function getProviderPresetDefaults( apiKey: '', requiresApiKey: false, } + case 'dashscope-cn': + return { + provider: 'openai', + name: 'Alibaba Coding Plan (China)', + baseUrl: 'https://coding.dashscope.aliyuncs.com/v1', + model: 'qwen3.6-plus', + apiKey: process.env.DASHSCOPE_API_KEY ?? '', + requiresApiKey: true, + } + case 'dashscope-intl': + return { + provider: 'openai', + name: 'Alibaba Coding Plan', + baseUrl: 'https://coding-intl.dashscope.aliyuncs.com/v1', + model: 'qwen3.6-plus', + apiKey: process.env.DASHSCOPE_API_KEY ?? '', + requiresApiKey: true, + } case 'custom': return { provider: 'openai',