diff --git a/packages/cli/src/ui/auth/useAuth.ts b/packages/cli/src/ui/auth/useAuth.ts index 7237ac33d..05f811741 100644 --- a/packages/cli/src/ui/auth/useAuth.ts +++ b/packages/cli/src/ui/auth/useAuth.ts @@ -83,12 +83,26 @@ export const useAuthCommand = ( async (authType: AuthType, credentials?: OpenAICredentials) => { try { const authTypeScope = getPersistScopeForModelSelection(settings); + + // Persist authType settings.setValue( authTypeScope, 'security.auth.selectedType', authType, ); + // Persist model from ContentGenerator config (handles fallback cases) + // This ensures that when syncAfterAuthRefresh falls back to default model, + // it gets persisted to settings.json + const contentGeneratorConfig = config.getContentGeneratorConfig(); + if (contentGeneratorConfig?.model) { + settings.setValue( + authTypeScope, + 'model.name', + contentGeneratorConfig.model, + ); + } + // Only update credentials if not switching to QWEN_OAUTH, // so that OpenAI credentials are preserved when switching to QWEN_OAUTH. if (authType !== AuthType.QWEN_OAUTH && credentials) { @@ -106,9 +120,6 @@ export const useAuthCommand = ( credentials.baseUrl, ); } - if (credentials?.model != null) { - settings.setValue(authTypeScope, 'model.name', credentials.model); - } } } catch (error) { handleAuthFailure(error); diff --git a/packages/cli/src/utils/modelConfigUtils.ts b/packages/cli/src/utils/modelConfigUtils.ts index 4a025ed1f..305e50d15 100644 --- a/packages/cli/src/utils/modelConfigUtils.ts +++ b/packages/cli/src/utils/modelConfigUtils.ts @@ -120,7 +120,7 @@ export function resolveCliGenerationConfig( // Log warnings if any for (const warning of resolved.warnings) { - console.warn(`[modelProviderUtils] ${warning}`); + console.warn(warning); } // Resolve OpenAI logging config (CLI-specific, not part of core resolver) diff --git a/packages/core/src/models/constants.ts b/packages/core/src/models/constants.ts index fcb1be985..1ef9919a2 100644 --- a/packages/core/src/models/constants.ts +++ b/packages/core/src/models/constants.ts @@ -106,15 +106,6 @@ export const QWEN_OAUTH_MODELS: ModelConfig[] = [ description: 'The latest Qwen Coder model from Alibaba Cloud ModelStudio (version: qwen3-coder-plus-2025-09-23)', capabilities: { vision: false }, - generationConfig: { - samplingParams: { - temperature: 0.7, - top_p: 0.9, - max_tokens: 8192, - }, - timeout: 60000, - maxRetries: 3, - }, }, { id: 'vision-model', @@ -122,14 +113,5 @@ export const QWEN_OAUTH_MODELS: ModelConfig[] = [ description: 'The latest Qwen Vision model from Alibaba Cloud ModelStudio (version: qwen3-vl-plus-2025-09-23)', capabilities: { vision: true }, - generationConfig: { - samplingParams: { - temperature: 0.7, - top_p: 0.9, - max_tokens: 8192, - }, - timeout: 60000, - maxRetries: 3, - }, }, ]; diff --git a/packages/core/src/models/modelsConfig.test.ts b/packages/core/src/models/modelsConfig.test.ts index 1b220294e..06896a382 100644 --- a/packages/core/src/models/modelsConfig.test.ts +++ b/packages/core/src/models/modelsConfig.test.ts @@ -480,6 +480,91 @@ describe('ModelsConfig', () => { expect(gc.apiKeyEnvKey).toBeUndefined(); }); + it('should use default model for new authType when switching from different authType with env vars', () => { + // Simulate cold start with OPENAI env vars (OPENAI_MODEL and OPENAI_API_KEY) + // This sets the model in generationConfig but no authType is selected yet + const modelsConfig = new ModelsConfig({ + generationConfig: { + model: 'gpt-4o', // From OPENAI_MODEL env var + apiKey: 'openai-key-from-env', + }, + }); + + // User switches to qwen-oauth via AuthDialog + // refreshAuth calls syncAfterAuthRefresh with the current model (gpt-4o) + // which doesn't exist in qwen-oauth registry, so it should use default + modelsConfig.syncAfterAuthRefresh(AuthType.QWEN_OAUTH, 'gpt-4o'); + + const gc = currentGenerationConfig(modelsConfig); + // Should use default qwen-oauth model (coder-model), not the OPENAI model + expect(gc.model).toBe('coder-model'); + expect(gc.apiKey).toBe('QWEN_OAUTH_DYNAMIC_TOKEN'); + expect(gc.apiKeyEnvKey).toBeUndefined(); + }); + + it('should clear manual credentials when switching from USE_OPENAI to QWEN_OAUTH', () => { + // User manually set credentials for OpenAI + const modelsConfig = new ModelsConfig({ + initialAuthType: AuthType.USE_OPENAI, + generationConfig: { + model: 'gpt-4o', + apiKey: 'manual-openai-key', + baseUrl: 'https://manual.example.com/v1', + }, + }); + + // Manually set credentials via updateCredentials + modelsConfig.updateCredentials({ + apiKey: 'manual-openai-key', + baseUrl: 'https://manual.example.com/v1', + model: 'gpt-4o', + }); + + // User switches to qwen-oauth + // Since authType is not USE_OPENAI, manual credentials should be cleared + // and default qwen-oauth model should be applied + modelsConfig.syncAfterAuthRefresh(AuthType.QWEN_OAUTH, 'gpt-4o'); + + const gc = currentGenerationConfig(modelsConfig); + // Should use default qwen-oauth model, not preserve manual OpenAI credentials + expect(gc.model).toBe('coder-model'); + expect(gc.apiKey).toBe('QWEN_OAUTH_DYNAMIC_TOKEN'); + // baseUrl should be set to qwen-oauth default, not preserved from manual OpenAI config + expect(gc.baseUrl).toBe('DYNAMIC_QWEN_OAUTH_BASE_URL'); + expect(gc.apiKeyEnvKey).toBeUndefined(); + }); + + it('should preserve manual credentials when switching to USE_OPENAI', () => { + // User manually set credentials + const modelsConfig = new ModelsConfig({ + initialAuthType: AuthType.USE_OPENAI, + generationConfig: { + model: 'gpt-4o', + apiKey: 'manual-openai-key', + baseUrl: 'https://manual.example.com/v1', + samplingParams: { temperature: 0.9 }, + }, + }); + + // Manually set credentials via updateCredentials + modelsConfig.updateCredentials({ + apiKey: 'manual-openai-key', + baseUrl: 'https://manual.example.com/v1', + model: 'gpt-4o', + }); + + // User switches to USE_OPENAI (same or different model) + // Since authType is USE_OPENAI, manual credentials should be preserved + modelsConfig.syncAfterAuthRefresh(AuthType.USE_OPENAI, 'gpt-4o'); + + const gc = currentGenerationConfig(modelsConfig); + // Should preserve manual credentials + expect(gc.model).toBe('gpt-4o'); + expect(gc.apiKey).toBe('manual-openai-key'); + expect(gc.baseUrl).toBe('https://manual.example.com/v1'); + expect(gc.samplingParams?.temperature).toBe(0.9); // Preserved from initial config + }); + it('should maintain consistency between currentModelId and _generationConfig.model after initialization', () => { const modelProvidersConfig: ModelProvidersConfig = { openai: [ diff --git a/packages/core/src/models/modelsConfig.ts b/packages/core/src/models/modelsConfig.ts index 1c88903c2..36435143f 100644 --- a/packages/core/src/models/modelsConfig.ts +++ b/packages/core/src/models/modelsConfig.ts @@ -600,7 +600,7 @@ export class ModelsConfig { // If credentials were manually set, don't apply modelProvider defaults // Just update the authType and preserve the manually set credentials - if (preserveManualCredentials) { + if (preserveManualCredentials && authType === AuthType.USE_OPENAI) { this.strictModelProviderSelection = false; this.currentAuthType = authType; if (modelId) { @@ -621,7 +621,17 @@ export class ModelsConfig { this.applyResolvedModelDefaults(resolved); } } else { + // If the provided modelId doesn't exist in the registry for the new authType, + // use the default model for that authType instead of keeping the old model. + // This handles the case where switching from one authType (e.g., OPENAI with + // env vars) to another (e.g., qwen-oauth) - we should use the default model + // for the new authType, not the old model. this.currentAuthType = authType; + const defaultModel = + this.modelRegistry.getDefaultModelForAuthType(authType); + if (defaultModel) { + this.applyResolvedModelDefaults(defaultModel); + } } } diff --git a/packages/sdk-typescript/src/utils/cliPath.ts b/packages/sdk-typescript/src/utils/cliPath.ts index e4a7924bc..fb795ec80 100644 --- a/packages/sdk-typescript/src/utils/cliPath.ts +++ b/packages/sdk-typescript/src/utils/cliPath.ts @@ -125,8 +125,9 @@ function normalizeForRegex(dirPath: string): string { function tryResolveCliFromImportMeta(): string | null { try { if (typeof import.meta !== 'undefined' && import.meta.url) { - const cliUrl = new URL('./cli/cli.js', import.meta.url); - const cliPath = fileURLToPath(cliUrl); + const currentFilePath = fileURLToPath(import.meta.url); + const currentDir = path.dirname(currentFilePath); + const cliPath = path.join(currentDir, 'cli', 'cli.js'); if (fs.existsSync(cliPath)) { return cliPath; }