diff --git a/packages/cli/src/config/auth.ts b/packages/cli/src/config/auth.ts index b77145edf..c0d33b0b4 100644 --- a/packages/cli/src/config/auth.ts +++ b/packages/cli/src/config/auth.ts @@ -66,12 +66,14 @@ function hasApiKeyForAuth( const defaultEnvKey = DEFAULT_ENV_KEYS[authType]; if (defaultEnvKey) { const hasKey = !!process.env[defaultEnvKey]; - return { hasKey, checkedEnvKey: defaultEnvKey }; + if (hasKey) { + return { hasKey, checkedEnvKey: defaultEnvKey }; + } } // Also check settings.security.auth.apiKey as fallback if (settings.security?.auth?.apiKey) { - return { hasKey: true, checkedEnvKey: undefined }; + return { hasKey: true, checkedEnvKey: defaultEnvKey || undefined }; } return { hasKey: false, checkedEnvKey: undefined }; diff --git a/packages/cli/src/ui/auth/useAuth.ts b/packages/cli/src/ui/auth/useAuth.ts index 5b97ead5f..bfc80ca70 100644 --- a/packages/cli/src/ui/auth/useAuth.ts +++ b/packages/cli/src/ui/auth/useAuth.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type { Config } from '@qwen-code/qwen-code-core'; +import type { Config, ModelProvidersConfig } from '@qwen-code/qwen-code-core'; import { AuthEvent, AuthType, @@ -152,6 +152,29 @@ export const useAuthCommand = ( [config, handleAuthSuccess, handleAuthFailure], ); + const isProviderManagedModel = useCallback( + (authType: AuthType, modelId: string | undefined) => { + if (!modelId) { + return false; + } + + const modelProviders = settings.merged.modelProviders as + | ModelProvidersConfig + | undefined; + if (!modelProviders) { + return false; + } + const providerModels = modelProviders[authType]; + if (!Array.isArray(providerModels)) { + return false; + } + return providerModels.some( + (providerModel) => providerModel.id === modelId, + ); + }, + [settings], + ); + const handleAuthSelect = useCallback( async (authType: AuthType | undefined, credentials?: OpenAICredentials) => { if (!authType) { @@ -160,6 +183,20 @@ export const useAuthCommand = ( return; } + if ( + authType === AuthType.USE_OPENAI && + credentials?.model && + isProviderManagedModel(authType, credentials.model) + ) { + onAuthError( + t( + 'Model "{{modelName}}" is managed via settings.modelProviders. Please complete the fields in settings, or use another model id.', + { modelName: credentials.model }, + ), + ); + return; + } + setPendingAuthType(authType); setAuthError(null); setIsAuthDialogOpen(false); @@ -179,7 +216,7 @@ export const useAuthCommand = ( await performAuth(authType); }, - [config, performAuth], + [config, performAuth, isProviderManagedModel, onAuthError], ); const openAuthDialog = useCallback(() => { diff --git a/packages/cli/src/ui/components/ModelDialog.tsx b/packages/cli/src/ui/components/ModelDialog.tsx index 3d573dc06..84612b902 100644 --- a/packages/cli/src/ui/components/ModelDialog.tsx +++ b/packages/cli/src/ui/components/ModelDialog.tsx @@ -255,26 +255,10 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element { ); } catch (e) { const baseErrorMessage = e instanceof Error ? e.message : String(e); - - // Some auth types (notably openai without modelProviders configured) can present - // env-based "raw" model IDs in the list. These are not registry-backed and will - // fail switchModel(). Fall back to setModel() to keep UX functional. - const isNotFound = - baseErrorMessage.includes('not found for authType') || - (baseErrorMessage.includes('Model') && - baseErrorMessage.includes('not found')); - if (!isNotFound) { - setErrorMessage( - `Failed to switch model to '${modelId}'.\n\n${baseErrorMessage}`, - ); - - // Keep the dialog open so the user can choose another model. - return; - } - await config.setModel(modelId, { - reason: 'user_manual', - context: 'Model set via /model dialog (raw)', - }); + setErrorMessage( + `Failed to switch model to '${modelId}'.\n\n${baseErrorMessage}`, + ); + return; } const event = new ModelSlashCommandEvent(modelId); logModelSlashCommand(config, event); diff --git a/packages/core/src/models/modelsConfig.ts b/packages/core/src/models/modelsConfig.ts index 022737074..5e066b96f 100644 --- a/packages/core/src/models/modelsConfig.ts +++ b/packages/core/src/models/modelsConfig.ts @@ -452,9 +452,6 @@ export class ModelsConfig { */ private applyResolvedModelDefaults(model: ResolvedModelConfig): void { this.strictModelProviderSelection = true; - const previousApiKey = this._generationConfig.apiKey; - const previousApiKeyEnvKey = this._generationConfig.apiKeyEnvKey; - const hadManualCredentials = this.hasManualCredentials; // We're explicitly applying modelProvider defaults now, so manual overrides // should no longer block syncAfterAuthRefresh from applying provider defaults. this.hasManualCredentials = false; @@ -503,28 +500,6 @@ export class ModelsConfig { detail: 'envKey', }, }; - } else { - // If the user provided an API key via CLI/settings/updateCredentials, keep it. - // We only refuse to reuse a previous key when it is explicitly tied to a - // different envKey (e.g. switching between two configured accounts). - const canPreservePreviousKey = - !!previousApiKey && - (hadManualCredentials || - previousApiKeyEnvKey === undefined || - previousApiKeyEnvKey === model.envKey); - - if (canPreservePreviousKey) { - this._generationConfig.apiKey = previousApiKey; - this.generationConfigSources['apiKey'] = { - kind: 'computed', - detail: `preserved previous apiKey (missing env: ${model.envKey})`, - }; - } else { - console.warn( - `[ModelsConfig] Environment variable '${model.envKey}' is not set for model '${model.id}'. ` + - `API key will not be available.`, - ); - } } this._generationConfig.apiKeyEnvKey = model.envKey; this.generationConfigSources['apiKeyEnvKey'] = {