diff --git a/packages/cli/src/ui/hooks/useVisionAutoSwitch.test.ts b/packages/cli/src/ui/hooks/useVisionAutoSwitch.test.ts index 782986ce9..168f3a585 100644 --- a/packages/cli/src/ui/hooks/useVisionAutoSwitch.test.ts +++ b/packages/cli/src/ui/hooks/useVisionAutoSwitch.test.ts @@ -62,7 +62,7 @@ describe('useVisionAutoSwitch helpers', () => { const result = shouldOfferVisionSwitch( parts, AuthType.QWEN_OAUTH, - 'vision-model', + 'qwen3.5-plus', true, ); expect(result).toBe(false); @@ -161,7 +161,7 @@ describe('useVisionAutoSwitch helpers', () => { const result = shouldOfferVisionSwitch( parts, AuthType.QWEN_OAUTH, - 'vision-model', + 'qwen3.5-plus', true, ); expect(result).toBe(false); @@ -526,7 +526,7 @@ describe('useVisionAutoSwitch hook', () => { it('does not switch in YOLO mode when already using vision model', async () => { const config = createMockConfig( AuthType.QWEN_OAUTH, - 'vision-model', + 'qwen3.5-plus', ApprovalMode.YOLO, ); const onVisionSwitchRequired = vi.fn(); @@ -728,7 +728,7 @@ describe('useVisionAutoSwitch hook', () => { expect(switchResult.shouldProceed).toBe(true); expect(switchResult.originalModel).toBe('qwen3-coder-plus'); - expect(config.setModel).toHaveBeenCalledWith('vision-model', { + expect(config.setModel).toHaveBeenCalledWith('qwen3.5-plus', { reason: 'vision_auto_switch', context: 'Default VLM switch mode: once (one-time override)', }); @@ -764,7 +764,7 @@ describe('useVisionAutoSwitch hook', () => { expect(switchResult.shouldProceed).toBe(true); expect(switchResult.originalModel).toBeUndefined(); // No original model for session switch - expect(config.setModel).toHaveBeenCalledWith('vision-model', { + expect(config.setModel).toHaveBeenCalledWith('qwen3.5-plus', { reason: 'vision_auto_switch', context: 'Default VLM switch mode: session (session persistent)', }); @@ -813,7 +813,7 @@ describe('useVisionAutoSwitch hook', () => { ); const onVisionSwitchRequired = vi .fn() - .mockResolvedValue({ modelOverride: 'vision-model' }); + .mockResolvedValue({ modelOverride: 'qwen3.5-plus' }); const { result } = renderHook(() => useVisionAutoSwitch( config, diff --git a/packages/cli/src/ui/models/availableModels.test.ts b/packages/cli/src/ui/models/availableModels.test.ts index a17405d38..619ddbbdd 100644 --- a/packages/cli/src/ui/models/availableModels.test.ts +++ b/packages/cli/src/ui/models/availableModels.test.ts @@ -20,16 +20,23 @@ describe('availableModels', () => { describe('Qwen models', () => { const qwenModels = getFilteredQwenModels(true); - it('should include coder model', () => { - const coderModel = qwenModels.find((m) => m.id === MAINLINE_CODER); + it('should include default model (qwen3.5-plus)', () => { + const defaultModel = qwenModels.find((m) => m.id === MAINLINE_CODER); + expect(defaultModel).toBeDefined(); + expect(defaultModel?.id).toBe('qwen3.5-plus'); + }); + + it('should include coder-model as non-vision alternative', () => { + const coderModel = qwenModels.find((m) => m.id === 'coder-model'); expect(coderModel).toBeDefined(); expect(coderModel?.isVision).toBeFalsy(); }); - it('should include vision model', () => { + it('should include vision model (qwen3.5-plus with vision capability)', () => { const visionModel = qwenModels.find((m) => m.id === MAINLINE_VLM); expect(visionModel).toBeDefined(); expect(visionModel?.isVision).toBe(true); + expect(visionModel?.id).toBe('qwen3.5-plus'); }); }); @@ -197,8 +204,8 @@ describe('availableModels', () => { expect(isVisionModel(MAINLINE_VLM)).toBe(true); }); - it('should return false for non-vision model', () => { - expect(isVisionModel(MAINLINE_CODER)).toBe(false); + it('should return false for coder-model (non-vision)', () => { + expect(isVisionModel('coder-model')).toBe(false); }); it('should return false for unknown model', () => { diff --git a/packages/cli/src/ui/models/availableModels.ts b/packages/cli/src/ui/models/availableModels.ts index a68cc69cc..54f36cae1 100644 --- a/packages/cli/src/ui/models/availableModels.ts +++ b/packages/cli/src/ui/models/availableModels.ts @@ -20,7 +20,7 @@ export type AvailableModel = { isVision?: boolean; }; -export const MAINLINE_VLM = 'vision-model'; +export const MAINLINE_VLM = 'qwen3.5-plus'; export const MAINLINE_CODER = DEFAULT_QWEN_MODEL; const CACHED_QWEN_OAUTH_MODELS: AvailableModel[] = QWEN_OAUTH_MODELS.map( diff --git a/packages/core/src/config/config.test.ts b/packages/core/src/config/config.test.ts index 4d2af04b3..9126c0983 100644 --- a/packages/core/src/config/config.test.ts +++ b/packages/core/src/config/config.test.ts @@ -370,9 +370,9 @@ describe('Server Config (config.ts)', () => { // Spy after initial refresh to ensure model switch does not re-trigger refreshAuth. const refreshSpy = vi.spyOn(config, 'refreshAuth'); - await config.switchModel(AuthType.QWEN_OAUTH, 'vision-model'); + await config.switchModel(AuthType.QWEN_OAUTH, 'qwen3.5-plus'); - expect(config.getModel()).toBe('vision-model'); + expect(config.getModel()).toBe('qwen3.5-plus'); expect(refreshSpy).not.toHaveBeenCalled(); // Called once during initial refreshAuth + once during handleModelChange diffing. expect( diff --git a/packages/core/src/core/contentGenerator.test.ts b/packages/core/src/core/contentGenerator.test.ts index eef7f5ac8..312af8069 100644 --- a/packages/core/src/core/contentGenerator.test.ts +++ b/packages/core/src/core/contentGenerator.test.ts @@ -90,11 +90,11 @@ describe('createContentGeneratorConfig', () => { it('should preserve provided fields and set authType for QWEN_OAUTH', () => { const cfg = createContentGeneratorConfig(mockConfig, AuthType.QWEN_OAUTH, { - model: 'vision-model', + model: 'qwen3.5-plus', apiKey: 'QWEN_OAUTH_DYNAMIC_TOKEN', }); expect(cfg.authType).toBe(AuthType.QWEN_OAUTH); - expect(cfg.model).toBe('vision-model'); + expect(cfg.model).toBe('qwen3.5-plus'); expect(cfg.apiKey).toBe('QWEN_OAUTH_DYNAMIC_TOKEN'); }); diff --git a/packages/core/src/core/openaiContentGenerator/provider/dashscope.test.ts b/packages/core/src/core/openaiContentGenerator/provider/dashscope.test.ts index a57bbacb7..f73e2c54b 100644 --- a/packages/core/src/core/openaiContentGenerator/provider/dashscope.test.ts +++ b/packages/core/src/core/openaiContentGenerator/provider/dashscope.test.ts @@ -884,9 +884,9 @@ describe('DashScopeOpenAICompatibleProvider', () => { ).toBe(true); }); - it('should set high resolution flag for the vision-model alias', () => { + it('should set high resolution flag for the qwen3.5-plus model', () => { const request: OpenAI.Chat.ChatCompletionCreateParams = { - model: 'vision-model', + model: 'qwen3.5-plus', messages: [ { role: 'user', diff --git a/packages/core/src/core/openaiContentGenerator/provider/dashscope.ts b/packages/core/src/core/openaiContentGenerator/provider/dashscope.ts index ccf201e24..c1c4d0536 100644 --- a/packages/core/src/core/openaiContentGenerator/provider/dashscope.ts +++ b/packages/core/src/core/openaiContentGenerator/provider/dashscope.ts @@ -286,7 +286,7 @@ export class DashScopeOpenAICompatibleProvider const normalized = model.toLowerCase(); - if (normalized === 'vision-model') { + if (normalized === 'qwen3.5-plus') { return true; } diff --git a/packages/core/src/core/prompts.ts b/packages/core/src/core/prompts.ts index 60c2c56d9..fd321e7ff 100644 --- a/packages/core/src/core/prompts.ts +++ b/packages/core/src/core/prompts.ts @@ -801,9 +801,9 @@ function getToolCallExamples(model?: string): string { if (/coder-model/i.test(model)) { return qwenCoderToolCallExamples; } - // Match vision-model pattern (same as qwen3-vl) - if (/vision-model/i.test(model)) { - return qwenVlToolCallExamples; + // Match qwen3.5-plus pattern (uses same tool call examples as coder) + if (/qwen3\.5-plus/i.test(model)) { + return qwenCoderToolCallExamples; } } diff --git a/packages/core/src/core/tokenLimits.ts b/packages/core/src/core/tokenLimits.ts index e65734362..d88dd28f7 100644 --- a/packages/core/src/core/tokenLimits.ts +++ b/packages/core/src/core/tokenLimits.ts @@ -150,9 +150,6 @@ const PATTERNS: Array<[RegExp, TokenCount]> = [ [/^qwen3-vl-plus$/, LIMITS['256k']], // Qwen3-VL-Plus: 256K input [/^qwen-vl-max.*$/, LIMITS['128k']], - // Generic vision-model: same as qwen-vl-max (128K token context) - [/^vision-model$/, LIMITS['128k']], - // ------------------- // ByteDance Seed-OSS (512K) // ------------------- @@ -216,9 +213,6 @@ const OUTPUT_PATTERNS: Array<[RegExp, TokenCount]> = [ // Qwen-VL-Max-Latest: 8,192 max output tokens [/^qwen-vl-max-latest$/, LIMITS['8k']], - // Generic vision-model: same as qwen-vl-max-latest (8K max output tokens) - [/^vision-model$/, LIMITS['8k']], - // Qwen3-VL-Plus: 32K max output tokens [/^qwen3-vl-plus$/, LIMITS['32k']], diff --git a/packages/core/src/models/constants.ts b/packages/core/src/models/constants.ts index c4c72d9e5..f918e3691 100644 --- a/packages/core/src/models/constants.ts +++ b/packages/core/src/models/constants.ts @@ -102,7 +102,7 @@ export const QWEN_OAUTH_MODELS: ModelConfig[] = [ name: 'qwen3.5-plus', description: 'The Qwen3.5 native vision-language series Plus models deliver outstanding performance comparable to the very latest state-of-the-art models, with significant leaps in both pure-text and multimodal capabilities compared to the 3 series.', - capabilities: { vision: false }, + capabilities: { vision: true }, }, { id: 'coder-model', @@ -111,12 +111,6 @@ export const QWEN_OAUTH_MODELS: ModelConfig[] = [ 'Qwen 3.5 Plus — efficient hybrid model with leading coding performance', capabilities: { vision: false }, }, - { - id: 'vision-model', - name: 'vision-model', - description: 'The latest Qwen Vision model from Alibaba Cloud ModelStudio', - capabilities: { vision: true }, - }, ]; /** diff --git a/packages/core/src/models/modelConfigResolver.test.ts b/packages/core/src/models/modelConfigResolver.test.ts index b045bcbdd..bc31e81e5 100644 --- a/packages/core/src/models/modelConfigResolver.test.ts +++ b/packages/core/src/models/modelConfigResolver.test.ts @@ -157,17 +157,17 @@ describe('modelConfigResolver', () => { expect(result.sources['apiKey'].kind).toBe('computed'); }); - it('allows vision-model for Qwen OAuth', () => { + it('allows qwen3.5-plus for Qwen OAuth', () => { const result = resolveModelConfig({ authType: AuthType.QWEN_OAUTH, cli: { - model: 'vision-model', + model: 'qwen3.5-plus', }, settings: {}, env: {}, }); - expect(result.config.model).toBe('vision-model'); + expect(result.config.model).toBe('qwen3.5-plus'); expect(result.sources['model'].kind).toBe('cli'); }); diff --git a/packages/core/src/models/modelRegistry.test.ts b/packages/core/src/models/modelRegistry.test.ts index 67510d5ec..f3c08ec44 100644 --- a/packages/core/src/models/modelRegistry.test.ts +++ b/packages/core/src/models/modelRegistry.test.ts @@ -18,7 +18,6 @@ describe('ModelRegistry', () => { expect(qwenModels.length).toBe(QWEN_OAUTH_MODELS.length); expect(qwenModels[0].id).toBe('qwen3.5-plus'); expect(qwenModels[1].id).toBe('coder-model'); - expect(qwenModels[2].id).toBe('vision-model'); }); it('should initialize with empty config', () => { diff --git a/packages/core/src/models/modelsConfig.test.ts b/packages/core/src/models/modelsConfig.test.ts index 7215d3905..64c54b0ab 100644 --- a/packages/core/src/models/modelsConfig.test.ts +++ b/packages/core/src/models/modelsConfig.test.ts @@ -454,7 +454,7 @@ describe('ModelsConfig', () => { }); // Switching within qwen-oauth triggers applyResolvedModelDefaults(). - await modelsConfig.switchModel(AuthType.QWEN_OAUTH, 'vision-model'); + await modelsConfig.switchModel(AuthType.QWEN_OAUTH, 'qwen3.5-plus'); const gc = currentGenerationConfig(modelsConfig); expect(gc.apiKey).toBe('QWEN_OAUTH_DYNAMIC_TOKEN'); diff --git a/packages/core/src/models/modelsConfig.ts b/packages/core/src/models/modelsConfig.ts index 9311c9279..ff4543746 100644 --- a/packages/core/src/models/modelsConfig.ts +++ b/packages/core/src/models/modelsConfig.ts @@ -309,9 +309,10 @@ export class ModelsConfig { metadata?: ModelSwitchMetadata, ): Promise { // Special case: qwen-oauth VLM auto-switch - hot update in place + // Support hot-update for both qwen3.5-plus (vision-capable) and coder-model if ( this.currentAuthType === AuthType.QWEN_OAUTH && - (newModel === DEFAULT_QWEN_MODEL || newModel === 'vision-model') + (newModel === 'qwen3.5-plus' || newModel === 'coder-model') ) { this.strictModelProviderSelection = false; this._generationConfig.model = newModel; @@ -782,7 +783,7 @@ export class ModelsConfig { * - We're checking if switching between two models within the SAME authType needs refresh * * Examples: - * - Qwen OAuth: coder-model -> vision-model (same authType, hot-update safe) + * - Qwen OAuth: coder-model -> qwen3.5-plus (same authType, hot-update safe) * - OpenAI: model-a -> model-b with same envKey (same authType, hot-update safe) * - OpenAI: gpt-4 -> deepseek-chat with different envKey (same authType, needs refresh) * @@ -799,7 +800,7 @@ export class ModelsConfig { } // For Qwen OAuth, model switches within the same authType can always be hot-updated - // (coder-model <-> vision-model don't require ContentGenerator recreation) + // (coder-model <-> qwen3.5-plus don't require ContentGenerator recreation) if (authType === AuthType.QWEN_OAUTH) { return false; }