From 490c36caeb2fed27919ea62c1eb53cefbffadc9c Mon Sep 17 00:00:00 2001 From: "mingholy.lmh" Date: Tue, 23 Sep 2025 21:57:49 +0800 Subject: [PATCH] fix: use dedicated model names and settings --- packages/cli/src/config/settings.test.ts | 80 ++++++++++++++++--- packages/cli/src/config/settings.ts | 18 +++++ packages/cli/src/config/settingsSchema.ts | 2 +- packages/cli/src/ui/App.tsx | 4 +- packages/cli/src/ui/hooks/useGeminiStream.ts | 2 +- .../src/ui/hooks/useVisionAutoSwitch.test.ts | 24 +++--- .../cli/src/ui/hooks/useVisionAutoSwitch.ts | 4 +- packages/cli/src/ui/models/availableModels.ts | 4 +- packages/core/src/config/models.ts | 7 +- packages/core/src/core/prompts.ts | 8 ++ packages/core/src/core/tokenLimits.ts | 12 +++ 11 files changed, 129 insertions(+), 36 deletions(-) diff --git a/packages/cli/src/config/settings.test.ts b/packages/cli/src/config/settings.test.ts index 7d0e737d..89720114 100644 --- a/packages/cli/src/config/settings.test.ts +++ b/packages/cli/src/config/settings.test.ts @@ -69,7 +69,11 @@ const MOCK_WORKSPACE_SETTINGS_PATH = pathActual.join( ); // A more flexible type for test data that allows arbitrary properties. -type TestSettings = Settings & { [key: string]: unknown }; +type TestSettings = Settings & { + [key: string]: unknown; + nested?: { [key: string]: unknown }; + nestedObj?: { [key: string]: unknown }; +}; vi.mock('fs', async (importOriginal) => { // Get all the functions from the real 'fs' module @@ -137,6 +141,9 @@ describe('Settings Loading and Merging', () => { advanced: { excludedEnvVars: [], }, + experimental: {}, + contentGenerator: {}, + systemPromptMappings: {}, extensions: { disabled: [], workspacesWithMigrationNudge: [], @@ -197,6 +204,9 @@ describe('Settings Loading and Merging', () => { advanced: { excludedEnvVars: [], }, + experimental: {}, + contentGenerator: {}, + systemPromptMappings: {}, extensions: { disabled: [], workspacesWithMigrationNudge: [], @@ -260,6 +270,9 @@ describe('Settings Loading and Merging', () => { advanced: { excludedEnvVars: [], }, + experimental: {}, + contentGenerator: {}, + systemPromptMappings: {}, extensions: { disabled: [], workspacesWithMigrationNudge: [], @@ -320,6 +333,9 @@ describe('Settings Loading and Merging', () => { advanced: { excludedEnvVars: [], }, + experimental: {}, + contentGenerator: {}, + systemPromptMappings: {}, extensions: { disabled: [], workspacesWithMigrationNudge: [], @@ -385,6 +401,9 @@ describe('Settings Loading and Merging', () => { advanced: { excludedEnvVars: [], }, + experimental: {}, + contentGenerator: {}, + systemPromptMappings: {}, extensions: { disabled: [], workspacesWithMigrationNudge: [], @@ -477,6 +496,9 @@ describe('Settings Loading and Merging', () => { advanced: { excludedEnvVars: [], }, + experimental: {}, + contentGenerator: {}, + systemPromptMappings: {}, extensions: { disabled: [], workspacesWithMigrationNudge: [], @@ -562,6 +584,9 @@ describe('Settings Loading and Merging', () => { advanced: { excludedEnvVars: [], }, + experimental: {}, + contentGenerator: {}, + systemPromptMappings: {}, extensions: { disabled: [], workspacesWithMigrationNudge: [], @@ -691,6 +716,9 @@ describe('Settings Loading and Merging', () => { '/system/dir', ], }, + experimental: {}, + contentGenerator: {}, + systemPromptMappings: {}, extensions: { disabled: [], workspacesWithMigrationNudge: [], @@ -1431,6 +1459,9 @@ describe('Settings Loading and Merging', () => { advanced: { excludedEnvVars: [], }, + experimental: {}, + contentGenerator: {}, + systemPromptMappings: {}, extensions: { disabled: [], workspacesWithMigrationNudge: [], @@ -1516,7 +1547,11 @@ describe('Settings Loading and Merging', () => { 'workspace_endpoint_from_env/api', ); expect( - (settings.workspace.settings as TestSettings)['nested']['value'], + ( + (settings.workspace.settings as TestSettings).nested as { + [key: string]: unknown; + } + )['value'], ).toBe('workspace_endpoint_from_env'); expect((settings.merged as TestSettings)['endpoint']).toBe( 'workspace_endpoint_from_env/api', @@ -1766,19 +1801,39 @@ describe('Settings Loading and Merging', () => { ).toBeUndefined(); expect( - (settings.user.settings as TestSettings)['nestedObj']['nestedNull'], + ( + (settings.user.settings as TestSettings).nestedObj as { + [key: string]: unknown; + } + )['nestedNull'], ).toBeNull(); expect( - (settings.user.settings as TestSettings)['nestedObj']['nestedBool'], + ( + (settings.user.settings as TestSettings).nestedObj as { + [key: string]: unknown; + } + )['nestedBool'], ).toBe(true); expect( - (settings.user.settings as TestSettings)['nestedObj']['nestedNum'], + ( + (settings.user.settings as TestSettings).nestedObj as { + [key: string]: unknown; + } + )['nestedNum'], ).toBe(0); expect( - (settings.user.settings as TestSettings)['nestedObj']['nestedString'], + ( + (settings.user.settings as TestSettings).nestedObj as { + [key: string]: unknown; + } + )['nestedString'], ).toBe('literal'); expect( - (settings.user.settings as TestSettings)['nestedObj']['anotherEnv'], + ( + (settings.user.settings as TestSettings).nestedObj as { + [key: string]: unknown; + } + )['anotherEnv'], ).toBe('env_string_nested_value'); delete process.env['MY_ENV_STRING']; @@ -1864,6 +1919,9 @@ describe('Settings Loading and Merging', () => { advanced: { excludedEnvVars: [], }, + experimental: {}, + contentGenerator: {}, + systemPromptMappings: {}, extensions: { disabled: [], workspacesWithMigrationNudge: [], @@ -2336,14 +2394,14 @@ describe('Settings Loading and Merging', () => { vimMode: false, }, model: { - maxSessionTurns: 0, + maxSessionTurns: -1, }, context: { includeDirectories: [], }, security: { folderTrust: { - enabled: null, + enabled: false, }, }, }; @@ -2352,9 +2410,9 @@ describe('Settings Loading and Merging', () => { expect(v1Settings).toEqual({ vimMode: false, - maxSessionTurns: 0, + maxSessionTurns: -1, includeDirectories: [], - folderTrust: null, + folderTrust: false, }); }); diff --git a/packages/cli/src/config/settings.ts b/packages/cli/src/config/settings.ts index f3c5a2d6..b22df887 100644 --- a/packages/cli/src/config/settings.ts +++ b/packages/cli/src/config/settings.ts @@ -396,6 +396,24 @@ function mergeSettings( ]), ], }, + experimental: { + ...(systemDefaults.experimental || {}), + ...(user.experimental || {}), + ...(safeWorkspaceWithoutFolderTrust.experimental || {}), + ...(system.experimental || {}), + }, + contentGenerator: { + ...(systemDefaults.contentGenerator || {}), + ...(user.contentGenerator || {}), + ...(safeWorkspaceWithoutFolderTrust.contentGenerator || {}), + ...(system.contentGenerator || {}), + }, + systemPromptMappings: { + ...(systemDefaults.systemPromptMappings || {}), + ...(user.systemPromptMappings || {}), + ...(safeWorkspaceWithoutFolderTrust.systemPromptMappings || {}), + ...(system.systemPromptMappings || {}), + }, extensions: { ...(systemDefaults.extensions || {}), ...(user.extensions || {}), diff --git a/packages/cli/src/config/settingsSchema.ts b/packages/cli/src/config/settingsSchema.ts index 84261893..815b5c58 100644 --- a/packages/cli/src/config/settingsSchema.ts +++ b/packages/cli/src/config/settingsSchema.ts @@ -746,7 +746,7 @@ export const SETTINGS_SCHEMA = { label: 'Vision Model Preview', category: 'Experimental', requiresRestart: false, - default: false, + default: true, description: 'Enable vision model support and auto-switching functionality. When disabled, vision models like qwen-vl-max-latest will be hidden and auto-switching will not occur.', showInDialog: true, diff --git a/packages/cli/src/ui/App.tsx b/packages/cli/src/ui/App.tsx index 85691182..c813e71a 100644 --- a/packages/cli/src/ui/App.tsx +++ b/packages/cli/src/ui/App.tsx @@ -670,7 +670,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => { if (!contentGeneratorConfig) return []; const visionModelPreviewEnabled = - settings.merged.experimental?.visionModelPreview ?? false; + settings.merged.experimental?.visionModelPreview ?? true; switch (contentGeneratorConfig.authType) { case AuthType.QWEN_OAUTH: @@ -759,7 +759,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => { setModelSwitchedFromQuotaError, refreshStatic, () => cancelHandlerRef.current(), - settings.merged.experimental?.visionModelPreview ?? false, + settings.merged.experimental?.visionModelPreview ?? true, handleVisionSwitchRequired, ); diff --git a/packages/cli/src/ui/hooks/useGeminiStream.ts b/packages/cli/src/ui/hooks/useGeminiStream.ts index 7f34eaa2..2a1d4371 100644 --- a/packages/cli/src/ui/hooks/useGeminiStream.ts +++ b/packages/cli/src/ui/hooks/useGeminiStream.ts @@ -89,7 +89,7 @@ export const useGeminiStream = ( setModelSwitchedFromQuotaError: React.Dispatch>, onEditorClose: () => void, onCancelSubmit: () => void, - visionModelPreviewEnabled: boolean = false, + visionModelPreviewEnabled: boolean, onVisionSwitchRequired?: (query: PartListUnion) => Promise<{ modelOverride?: string; persistSessionModel?: string; diff --git a/packages/cli/src/ui/hooks/useVisionAutoSwitch.test.ts b/packages/cli/src/ui/hooks/useVisionAutoSwitch.test.ts index fa56a94b..e4322b83 100644 --- a/packages/cli/src/ui/hooks/useVisionAutoSwitch.test.ts +++ b/packages/cli/src/ui/hooks/useVisionAutoSwitch.test.ts @@ -41,7 +41,7 @@ describe('useVisionAutoSwitch helpers', () => { const result = shouldOfferVisionSwitch( parts, AuthType.QWEN_OAUTH, - 'qwen-vl-max-latest', + 'vision-model', true, ); expect(result).toBe(false); @@ -140,7 +140,7 @@ describe('useVisionAutoSwitch helpers', () => { const result = shouldOfferVisionSwitch( parts, AuthType.QWEN_OAUTH, - 'qwen-vl-max-latest', + 'vision-model', true, ); expect(result).toBe(false); @@ -314,7 +314,7 @@ describe('useVisionAutoSwitch hook', () => { const config = createMockConfig(AuthType.QWEN_OAUTH, initialModel); const onVisionSwitchRequired = vi .fn() - .mockResolvedValue({ modelOverride: 'qwen-vl-max-latest' }); + .mockResolvedValue({ modelOverride: 'coder-model' }); const { result } = renderHook(() => useVisionAutoSwitch(config, addItem as any, true, onVisionSwitchRequired), ); @@ -329,7 +329,7 @@ describe('useVisionAutoSwitch hook', () => { }); expect(res).toEqual({ shouldProceed: true, originalModel: initialModel }); - expect(config.setModel).toHaveBeenCalledWith('qwen-vl-max-latest', { + expect(config.setModel).toHaveBeenCalledWith('coder-model', { reason: 'vision_auto_switch', context: 'User-prompted vision switch (one-time override)', }); @@ -348,7 +348,7 @@ describe('useVisionAutoSwitch hook', () => { const config = createMockConfig(AuthType.QWEN_OAUTH, 'qwen3-coder-plus'); const onVisionSwitchRequired = vi .fn() - .mockResolvedValue({ persistSessionModel: 'qwen-vl-max-latest' }); + .mockResolvedValue({ persistSessionModel: 'coder-model' }); const { result } = renderHook(() => useVisionAutoSwitch(config, addItem as any, true, onVisionSwitchRequired), ); @@ -363,7 +363,7 @@ describe('useVisionAutoSwitch hook', () => { }); expect(res).toEqual({ shouldProceed: true }); - expect(config.setModel).toHaveBeenCalledWith('qwen-vl-max-latest', { + expect(config.setModel).toHaveBeenCalledWith('coder-model', { reason: 'vision_auto_switch', context: 'User-prompted vision switch (session persistent)', }); @@ -373,9 +373,7 @@ describe('useVisionAutoSwitch hook', () => { result.current.restoreOriginalModel(); }); // Last call should still be the persisted model set - expect((config.setModel as any).mock.calls.pop()?.[0]).toBe( - 'qwen-vl-max-latest', - ); + expect((config.setModel as any).mock.calls.pop()?.[0]).toBe('coder-model'); }); it('returns shouldProceed=true when dialog returns no special flags', async () => { @@ -507,7 +505,7 @@ describe('useVisionAutoSwitch hook', () => { it('does not switch in YOLO mode when already using vision model', async () => { const config = createMockConfig( AuthType.QWEN_OAUTH, - 'qwen-vl-max-latest', + 'vision-model', ApprovalMode.YOLO, ); const onVisionSwitchRequired = vi.fn(); @@ -709,7 +707,7 @@ describe('useVisionAutoSwitch hook', () => { expect(switchResult.shouldProceed).toBe(true); expect(switchResult.originalModel).toBe('qwen3-coder-plus'); - expect(config.setModel).toHaveBeenCalledWith('qwen-vl-max-latest', { + expect(config.setModel).toHaveBeenCalledWith('vision-model', { reason: 'vision_auto_switch', context: 'Default VLM switch mode: once (one-time override)', }); @@ -745,7 +743,7 @@ describe('useVisionAutoSwitch hook', () => { expect(switchResult.shouldProceed).toBe(true); expect(switchResult.originalModel).toBeUndefined(); // No original model for session switch - expect(config.setModel).toHaveBeenCalledWith('qwen-vl-max-latest', { + expect(config.setModel).toHaveBeenCalledWith('vision-model', { reason: 'vision_auto_switch', context: 'Default VLM switch mode: session (session persistent)', }); @@ -794,7 +792,7 @@ describe('useVisionAutoSwitch hook', () => { ); const onVisionSwitchRequired = vi .fn() - .mockResolvedValue({ modelOverride: 'qwen-vl-max-latest' }); + .mockResolvedValue({ modelOverride: 'vision-model' }); const { result } = renderHook(() => useVisionAutoSwitch( config, diff --git a/packages/cli/src/ui/hooks/useVisionAutoSwitch.ts b/packages/cli/src/ui/hooks/useVisionAutoSwitch.ts index 6e201876..6da989d0 100644 --- a/packages/cli/src/ui/hooks/useVisionAutoSwitch.ts +++ b/packages/cli/src/ui/hooks/useVisionAutoSwitch.ts @@ -121,7 +121,7 @@ export function shouldOfferVisionSwitch( parts: PartListUnion, authType: AuthType, currentModel: string, - visionModelPreviewEnabled: boolean = false, + visionModelPreviewEnabled: boolean = true, ): boolean { // Only trigger for qwen-oauth if (authType !== AuthType.QWEN_OAUTH) { @@ -198,7 +198,7 @@ export interface VisionSwitchHandlingResult { export function useVisionAutoSwitch( config: Config, addItem: UseHistoryManagerReturn['addItem'], - visionModelPreviewEnabled: boolean = false, + visionModelPreviewEnabled: boolean = true, onVisionSwitchRequired?: (query: PartListUnion) => Promise<{ modelOverride?: string; persistSessionModel?: string; diff --git a/packages/cli/src/ui/models/availableModels.ts b/packages/cli/src/ui/models/availableModels.ts index b2b643dd..9ac4d420 100644 --- a/packages/cli/src/ui/models/availableModels.ts +++ b/packages/cli/src/ui/models/availableModels.ts @@ -10,8 +10,8 @@ export type AvailableModel = { isVision?: boolean; }; -export const MAINLINE_VLM = 'qwen-vl-max-latest'; -export const MAINLINE_CODER = 'qwen3-coder-plus'; +export const MAINLINE_VLM = 'vision-model'; +export const MAINLINE_CODER = 'coder-model'; export const AVAILABLE_MODELS_QWEN: AvailableModel[] = [ { id: MAINLINE_CODER, label: MAINLINE_CODER }, diff --git a/packages/core/src/config/models.ts b/packages/core/src/config/models.ts index 2a743dad..fd548737 100644 --- a/packages/core/src/config/models.ts +++ b/packages/core/src/config/models.ts @@ -4,11 +4,10 @@ * SPDX-License-Identifier: Apache-2.0 */ -export const DEFAULT_QWEN_MODEL = 'qwen3-coder-plus'; -// We do not have a fallback model for now, but note it here anyway. -export const DEFAULT_QWEN_FLASH_MODEL = 'qwen3-coder-flash'; +export const DEFAULT_QWEN_MODEL = 'coder-model'; +export const DEFAULT_QWEN_FLASH_MODEL = 'coder-model'; -export const DEFAULT_GEMINI_MODEL = 'qwen3-coder-plus'; +export const DEFAULT_GEMINI_MODEL = 'coder-model'; export const DEFAULT_GEMINI_FLASH_MODEL = 'gemini-2.5-flash'; export const DEFAULT_GEMINI_FLASH_LITE_MODEL = 'gemini-2.5-flash-lite'; diff --git a/packages/core/src/core/prompts.ts b/packages/core/src/core/prompts.ts index e18987a8..f08cbf75 100644 --- a/packages/core/src/core/prompts.ts +++ b/packages/core/src/core/prompts.ts @@ -820,6 +820,14 @@ function getToolCallExamples(model?: string): string { if (/qwen[^-]*-vl/i.test(model)) { return qwenVlToolCallExamples; } + // Match coder-model pattern (same as qwen3-coder) + if (/coder-model/i.test(model)) { + return qwenCoderToolCallExamples; + } + // Match vision-model pattern (same as qwen3-vl) + if (/vision-model/i.test(model)) { + return qwenVlToolCallExamples; + } } return generalToolCallExamples; diff --git a/packages/core/src/core/tokenLimits.ts b/packages/core/src/core/tokenLimits.ts index 50ac191c..6a3e7e86 100644 --- a/packages/core/src/core/tokenLimits.ts +++ b/packages/core/src/core/tokenLimits.ts @@ -111,6 +111,9 @@ const PATTERNS: Array<[RegExp, TokenCount]> = [ // Commercial Qwen3-Coder-Flash: 1M token context [/^qwen3-coder-flash(-.*)?$/, LIMITS['1m']], // catches "qwen3-coder-flash" and date variants + // Generic coder-model: same as qwen3-coder-plus (1M token context) + [/^coder-model$/, LIMITS['1m']], + // Commercial Qwen3-Max-Preview: 256K token context [/^qwen3-max-preview(-.*)?$/, LIMITS['256k']], // catches "qwen3-max-preview" and date variants @@ -134,6 +137,9 @@ const PATTERNS: Array<[RegExp, TokenCount]> = [ // Qwen Vision Models [/^qwen-vl-max.*$/, LIMITS['128k']], + // Generic vision-model: same as qwen-vl-max (128K token context) + [/^vision-model$/, LIMITS['128k']], + // ------------------- // ByteDance Seed-OSS (512K) // ------------------- @@ -169,12 +175,18 @@ const OUTPUT_PATTERNS: Array<[RegExp, TokenCount]> = [ // Qwen3-Coder-Plus: 65,536 max output tokens [/^qwen3-coder-plus(-.*)?$/, LIMITS['64k']], + // Generic coder-model: same as qwen3-coder-plus (64K max output tokens) + [/^coder-model$/, LIMITS['64k']], + // Qwen3-Max-Preview: 65,536 max output tokens [/^qwen3-max-preview(-.*)?$/, LIMITS['64k']], // 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: 8,192 max output tokens [/^qwen3-vl-plus$/, LIMITS['8k']], ];