diff --git a/packages/cli/src/acp-integration/acpAgent.ts b/packages/cli/src/acp-integration/acpAgent.ts index 6c40bffee..a33091586 100644 --- a/packages/cli/src/acp-integration/acpAgent.ts +++ b/packages/cli/src/acp-integration/acpAgent.ts @@ -290,7 +290,7 @@ class GeminiAgent { } private async ensureAuthenticated(config: Config): Promise { - const selectedType = this.settings.merged.security?.auth?.selectedType; + const selectedType = config.getModelsConfig().getCurrentAuthType(); if (!selectedType) { throw acp.RequestError.authRequired( 'Use Qwen Code CLI to authenticate first.', diff --git a/packages/cli/src/config/auth.test.ts b/packages/cli/src/config/auth.test.ts index ce3173c62..aee42208d 100644 --- a/packages/cli/src/config/auth.test.ts +++ b/packages/cli/src/config/auth.test.ts @@ -168,7 +168,7 @@ describe('validateAuthMethod', () => { expect(validateAuthMethod(AuthType.USE_VERTEX_AI)).toBeNull(); }); - it('should use config.modelsConfig.getModel() when Config is provided', () => { + it('should use config.getModelsConfig().getModel() when Config is provided', () => { // Settings has a different model vi.mocked(settings.loadSettings).mockReturnValue({ merged: { @@ -184,18 +184,18 @@ describe('validateAuthMethod', () => { // Mock Config object that returns a different model (e.g., from CLI args) const mockConfig = { - modelsConfig: { + getModelsConfig: vi.fn().mockReturnValue({ getModel: vi.fn().mockReturnValue('cli-model'), - }, + }), } as unknown as import('@qwen-code/qwen-code-core').Config; // Set the env key for the CLI model, not the settings model process.env['CLI_API_KEY'] = 'cli-key'; - // Should use 'cli-model' from config.modelsConfig.getModel(), not 'settings-model' + // Should use 'cli-model' from config.getModelsConfig().getModel(), not 'settings-model' const result = validateAuthMethod(AuthType.USE_OPENAI, mockConfig); expect(result).toBeNull(); - expect(mockConfig.modelsConfig.getModel).toHaveBeenCalled(); + expect(mockConfig.getModelsConfig).toHaveBeenCalled(); }); it('should fail validation when Config provides different model without matching env key', () => { @@ -217,9 +217,9 @@ describe('validateAuthMethod', () => { } as unknown as ReturnType); const mockConfig = { - modelsConfig: { + getModelsConfig: vi.fn().mockReturnValue({ getModel: vi.fn().mockReturnValue('cli-model'), - }, + }), } as unknown as import('@qwen-code/qwen-code-core').Config; // Don't set CLI_API_KEY - validation should fail diff --git a/packages/cli/src/config/auth.ts b/packages/cli/src/config/auth.ts index 5fbe07dce..46eed24d0 100644 --- a/packages/cli/src/config/auth.ts +++ b/packages/cli/src/config/auth.ts @@ -60,9 +60,9 @@ function hasApiKeyForAuth( | ModelProvidersConfig | undefined; - // Use config.modelsConfig.getModel() if available for accurate model ID resolution + // Use config.getModelsConfig().getModel() if available for accurate model ID resolution // that accounts for CLI args, env vars, and settings. Fall back to settings.model.name. - const modelId = config?.modelsConfig.getModel() ?? settings.model?.name; + const modelId = config?.getModelsConfig().getModel() ?? settings.model?.name; // Try to find model-specific envKey from modelProviders const modelConfig = findModelConfig(modelProviders, authType, modelId); @@ -184,9 +184,9 @@ export function validateAuthMethod( const modelProviders = settings.merged.modelProviders as | ModelProvidersConfig | undefined; - // Use config.modelsConfig.getModel() if available for accurate model ID + // Use config.getModelsConfig().getModel() if available for accurate model ID const modelId = - config?.modelsConfig.getModel() ?? settings.merged.model?.name; + config?.getModelsConfig().getModel() ?? settings.merged.model?.name; const modelConfig = findModelConfig(modelProviders, authMethod, modelId); if (modelConfig && !modelConfig.baseUrl) { diff --git a/packages/cli/src/core/initializer.ts b/packages/cli/src/core/initializer.ts index fe81816d9..8d39627fa 100644 --- a/packages/cli/src/core/initializer.ts +++ b/packages/cli/src/core/initializer.ts @@ -47,7 +47,7 @@ export async function initializeApp( // Use authType from modelsConfig which respects CLI --auth-type argument // over settings.security.auth.selectedType - const authType = config.modelsConfig.getCurrentAuthType(); + const authType = config.getModelsConfig().getCurrentAuthType(); const authError = await performInitialAuth(config, authType); // Fallback to user select when initial authentication fails @@ -61,7 +61,7 @@ export async function initializeApp( const themeError = validateTheme(settings); const shouldOpenAuthDialog = - !config.modelsConfig.wasAuthTypeExplicitlyProvided() || !!authError; + !config.getModelsConfig().wasAuthTypeExplicitlyProvided() || !!authError; if (config.getIdeMode()) { const ideClient = await IdeClient.getInstance(); diff --git a/packages/cli/src/gemini.tsx b/packages/cli/src/gemini.tsx index ea2dee43b..66fa327d1 100644 --- a/packages/cli/src/gemini.tsx +++ b/packages/cli/src/gemini.tsx @@ -252,7 +252,7 @@ export async function main() { if (!settings.merged.security?.auth?.useExternal) { // Validate authentication here because the sandbox will interfere with the Oauth2 web redirect. try { - const authType = partialConfig.modelsConfig.getCurrentAuthType(); + const authType = partialConfig.getModelsConfig().getCurrentAuthType(); // Fresh users may not have selected/persisted an authType yet. // In that case, defer auth prompting/selection to the main interactive flow. if (authType) { diff --git a/packages/cli/src/ui/AppContainer.tsx b/packages/cli/src/ui/AppContainer.tsx index 856ff4417..9ea338932 100644 --- a/packages/cli/src/ui/AppContainer.tsx +++ b/packages/cli/src/ui/AppContainer.tsx @@ -434,7 +434,7 @@ export const AppContainer = (props: AppContainerProps) => { // Check for enforced auth type mismatch useEffect(() => { // Check for initialization error first - const currentAuthType = config.modelsConfig.getCurrentAuthType(); + const currentAuthType = config.getModelsConfig().getCurrentAuthType(); if ( settings.merged.security?.auth?.enforcedType && diff --git a/packages/cli/src/validateNonInterActiveAuth.test.ts b/packages/cli/src/validateNonInterActiveAuth.test.ts index dcaf6b118..11dd3289f 100644 --- a/packages/cli/src/validateNonInterActiveAuth.test.ts +++ b/packages/cli/src/validateNonInterActiveAuth.test.ts @@ -14,18 +14,24 @@ import * as JsonOutputAdapterModule from './nonInteractive/io/JsonOutputAdapter. import * as StreamJsonOutputAdapterModule from './nonInteractive/io/StreamJsonOutputAdapter.js'; import * as cleanupModule from './utils/cleanup.js'; +type ModelsConfig = ReturnType; + // Helper to create a mock Config with modelsConfig function createMockConfig(overrides?: Partial): Config { - return { + const baseModelsConfig = { + getModel: vi.fn().mockReturnValue('default-model'), + getCurrentAuthType: vi.fn().mockReturnValue(AuthType.QWEN_OAUTH), + } as unknown as ModelsConfig; + const baseConfig: Partial = { refreshAuth: vi.fn().mockResolvedValue('refreshed'), getOutputFormat: vi.fn().mockReturnValue(OutputFormat.TEXT), getContentGeneratorConfig: vi.fn().mockReturnValue({ authType: undefined }), - modelsConfig: { - getModel: vi.fn().mockReturnValue('default-model'), - getCurrentAuthType: vi.fn().mockReturnValue(AuthType.QWEN_OAUTH), - }, + getModelsConfig: vi.fn().mockReturnValue(baseModelsConfig), + }; + return { + ...baseConfig, ...overrides, - } as unknown as Config; + } as Config; } describe('validateNonInterActiveAuth', () => { @@ -128,10 +134,10 @@ describe('validateNonInterActiveAuth', () => { ); const nonInteractiveConfig = createMockConfig({ refreshAuth: refreshAuthMock, - modelsConfig: { + getModelsConfig: vi.fn().mockReturnValue({ getModel: vi.fn().mockReturnValue('default-model'), getCurrentAuthType: vi.fn().mockReturnValue(AuthType.QWEN_OAUTH), - }, + }), }); try { await validateNonInteractiveAuth( @@ -153,10 +159,10 @@ describe('validateNonInterActiveAuth', () => { process.env['OPENAI_API_KEY'] = 'fake-openai-key'; const nonInteractiveConfig = createMockConfig({ refreshAuth: refreshAuthMock, - modelsConfig: { + getModelsConfig: vi.fn().mockReturnValue({ getModel: vi.fn().mockReturnValue('default-model'), getCurrentAuthType: vi.fn().mockReturnValue(AuthType.USE_OPENAI), - }, + }), }); await validateNonInteractiveAuth( undefined, @@ -169,10 +175,10 @@ describe('validateNonInterActiveAuth', () => { it('uses configured QWEN_OAUTH if provided', async () => { const nonInteractiveConfig = createMockConfig({ refreshAuth: refreshAuthMock, - modelsConfig: { + getModelsConfig: vi.fn().mockReturnValue({ getModel: vi.fn().mockReturnValue('default-model'), getCurrentAuthType: vi.fn().mockReturnValue(AuthType.QWEN_OAUTH), - }, + }), }); await validateNonInteractiveAuth( undefined, @@ -222,7 +228,7 @@ describe('validateNonInterActiveAuth', () => { expect(validateAuthMethodSpy).not.toHaveBeenCalled(); expect(consoleErrorSpy).not.toHaveBeenCalled(); expect(processExitSpy).not.toHaveBeenCalled(); - // refreshAuth is called with the authType from config.modelsConfig.getCurrentAuthType() + // refreshAuth is called with the authType from config.getModelsConfig().getCurrentAuthType() expect(refreshAuthMock).toHaveBeenCalledWith(AuthType.QWEN_OAUTH); }); @@ -233,10 +239,10 @@ describe('validateNonInterActiveAuth', () => { process.env['OPENAI_API_KEY'] = 'fake-key'; const nonInteractiveConfig = createMockConfig({ refreshAuth: refreshAuthMock, - modelsConfig: { + getModelsConfig: vi.fn().mockReturnValue({ getModel: vi.fn().mockReturnValue('default-model'), getCurrentAuthType: vi.fn().mockReturnValue(AuthType.USE_OPENAI), - }, + }), }); await validateNonInteractiveAuth( undefined, @@ -251,10 +257,10 @@ describe('validateNonInterActiveAuth', () => { process.env['OPENAI_API_KEY'] = 'fake-key'; const nonInteractiveConfig = createMockConfig({ refreshAuth: refreshAuthMock, - modelsConfig: { + getModelsConfig: vi.fn().mockReturnValue({ getModel: vi.fn().mockReturnValue('default-model'), getCurrentAuthType: vi.fn().mockReturnValue(AuthType.USE_OPENAI), - }, + }), }); try { await validateNonInteractiveAuth( @@ -297,10 +303,10 @@ describe('validateNonInterActiveAuth', () => { const nonInteractiveConfig = createMockConfig({ refreshAuth: refreshAuthMock, getOutputFormat: vi.fn().mockReturnValue(OutputFormat.JSON), - modelsConfig: { + getModelsConfig: vi.fn().mockReturnValue({ getModel: vi.fn().mockReturnValue('default-model'), getCurrentAuthType: vi.fn().mockReturnValue(AuthType.QWEN_OAUTH), - }, + }), }); try { @@ -334,10 +340,10 @@ describe('validateNonInterActiveAuth', () => { const nonInteractiveConfig = createMockConfig({ refreshAuth: refreshAuthMock, getOutputFormat: vi.fn().mockReturnValue(OutputFormat.JSON), - modelsConfig: { + getModelsConfig: vi.fn().mockReturnValue({ getModel: vi.fn().mockReturnValue('default-model'), getCurrentAuthType: vi.fn().mockReturnValue(AuthType.USE_OPENAI), - }, + }), }); try { @@ -373,10 +379,10 @@ describe('validateNonInterActiveAuth', () => { const nonInteractiveConfig = createMockConfig({ refreshAuth: refreshAuthMock, getOutputFormat: vi.fn().mockReturnValue(OutputFormat.JSON), - modelsConfig: { + getModelsConfig: vi.fn().mockReturnValue({ getModel: vi.fn().mockReturnValue('default-model'), getCurrentAuthType: vi.fn().mockReturnValue(AuthType.USE_OPENAI), - }, + }), }); try { @@ -433,10 +439,10 @@ describe('validateNonInterActiveAuth', () => { refreshAuth: refreshAuthMock, getOutputFormat: vi.fn().mockReturnValue(OutputFormat.STREAM_JSON), getIncludePartialMessages: vi.fn().mockReturnValue(false), - modelsConfig: { + getModelsConfig: vi.fn().mockReturnValue({ getModel: vi.fn().mockReturnValue('default-model'), getCurrentAuthType: vi.fn().mockReturnValue(AuthType.QWEN_OAUTH), - }, + }), }); try { @@ -471,10 +477,10 @@ describe('validateNonInterActiveAuth', () => { refreshAuth: refreshAuthMock, getOutputFormat: vi.fn().mockReturnValue(OutputFormat.STREAM_JSON), getIncludePartialMessages: vi.fn().mockReturnValue(false), - modelsConfig: { + getModelsConfig: vi.fn().mockReturnValue({ getModel: vi.fn().mockReturnValue('default-model'), getCurrentAuthType: vi.fn().mockReturnValue(AuthType.USE_OPENAI), - }, + }), }); try { @@ -511,10 +517,10 @@ describe('validateNonInterActiveAuth', () => { refreshAuth: refreshAuthMock, getOutputFormat: vi.fn().mockReturnValue(OutputFormat.STREAM_JSON), getIncludePartialMessages: vi.fn().mockReturnValue(false), - modelsConfig: { + getModelsConfig: vi.fn().mockReturnValue({ getModel: vi.fn().mockReturnValue('default-model'), getCurrentAuthType: vi.fn().mockReturnValue(AuthType.USE_OPENAI), - }, + }), }); try { diff --git a/packages/cli/src/validateNonInterActiveAuth.ts b/packages/cli/src/validateNonInterActiveAuth.ts index f5d71b08d..ce60264c0 100644 --- a/packages/cli/src/validateNonInterActiveAuth.ts +++ b/packages/cli/src/validateNonInterActiveAuth.ts @@ -19,7 +19,9 @@ export async function validateNonInteractiveAuth( ): Promise { try { // Get the actual authType from config which has already resolved CLI args, env vars, and settings - const authType = nonInteractiveConfig.modelsConfig.getCurrentAuthType(); + const authType = nonInteractiveConfig + .getModelsConfig() + .getCurrentAuthType(); if (!authType) { throw new Error( 'No auth type is selected. Please configure an auth type (e.g. via settings or `--auth-type`) before running in non-interactive mode.', diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index 4dced82b7..1285635e7 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -413,7 +413,7 @@ export class Config { private contentGenerator!: ContentGenerator; private readonly embeddingModel: string; - private _modelsConfig!: ModelsConfig; + private modelsConfig!: ModelsConfig; private readonly modelProvidersConfig?: ModelProvidersConfig; private readonly sandbox: SandboxConfig | undefined; private readonly targetDir: string; @@ -630,7 +630,7 @@ export class Config { // Prefer params.authType over generationConfig.authType because: // - params.authType preserves undefined (user hasn't selected yet) // - generationConfig.authType may have a default value from resolvers - this._modelsConfig = new ModelsConfig({ + this.modelsConfig = new ModelsConfig({ initialAuthType: params.authType ?? params.generationConfig?.authType, modelProvidersConfig: this.modelProvidersConfig, generationConfig: { @@ -727,8 +727,8 @@ export class Config { * Get the ModelsConfig instance for model-related operations. * External code (e.g., CLI) can use this to access model configuration. */ - get modelsConfig(): ModelsConfig { - return this._modelsConfig; + getModelsConfig(): ModelsConfig { + return this.modelsConfig; } /** @@ -744,7 +744,7 @@ export class Config { }, settingsGenerationConfig?: Partial, ): void { - this._modelsConfig.updateCredentials(credentials, settingsGenerationConfig); + this.modelsConfig.updateCredentials(credentials, settingsGenerationConfig); } /** @@ -752,21 +752,20 @@ export class Config { */ async refreshAuth(authMethod: AuthType, isInitialAuth?: boolean) { // Sync modelsConfig state for this auth refresh - const modelId = this._modelsConfig.getModel(); - this._modelsConfig.syncAfterAuthRefresh(authMethod, modelId); + const modelId = this.modelsConfig.getModel(); + this.modelsConfig.syncAfterAuthRefresh(authMethod, modelId); // Check and consume cached credentials flag const requireCached = - this._modelsConfig.consumeRequireCachedCredentialsFlag(); + this.modelsConfig.consumeRequireCachedCredentialsFlag(); const { config, sources } = resolveContentGeneratorConfigWithSources( this, authMethod, - this._modelsConfig.getGenerationConfig(), - this._modelsConfig.getGenerationConfigSources(), + this.modelsConfig.getGenerationConfig(), + this.modelsConfig.getGenerationConfigSources(), { - strictModelProvider: - this._modelsConfig.isStrictModelProviderSelection(), + strictModelProvider: this.modelsConfig.isStrictModelProviderSelection(), }, ); const newContentGeneratorConfig = config; @@ -856,15 +855,15 @@ export class Config { // get sources from ModelsConfig if ( Object.keys(this.contentGeneratorConfigSources).length === 0 && - this._modelsConfig + this.modelsConfig ) { - return this._modelsConfig.getGenerationConfigSources(); + return this.modelsConfig.getGenerationConfigSources(); } return this.contentGeneratorConfigSources; } getModel(): string { - return this.contentGeneratorConfig?.model || this._modelsConfig.getModel(); + return this.contentGeneratorConfig?.model || this.modelsConfig.getModel(); } /** @@ -875,7 +874,7 @@ export class Config { newModel: string, metadata?: { reason?: string; context?: string }, ): Promise { - await this._modelsConfig.setModel(newModel, metadata); + await this.modelsConfig.setModel(newModel, metadata); // Also update contentGeneratorConfig for hot-update compatibility if (this.contentGeneratorConfig) { this.contentGeneratorConfig.model = newModel; @@ -905,11 +904,11 @@ export class Config { const { config, sources } = resolveContentGeneratorConfigWithSources( this, authType, - this._modelsConfig.getGenerationConfig(), - this._modelsConfig.getGenerationConfigSources(), + this.modelsConfig.getGenerationConfig(), + this.modelsConfig.getGenerationConfigSources(), { strictModelProvider: - this._modelsConfig.isStrictModelProviderSelection(), + this.modelsConfig.isStrictModelProviderSelection(), }, ); @@ -942,7 +941,7 @@ export class Config { * Delegates to ModelsConfig. */ getAvailableModels(): AvailableModel[] { - return this._modelsConfig.getAvailableModels(); + return this.modelsConfig.getAvailableModels(); } /** @@ -950,7 +949,7 @@ export class Config { * Delegates to ModelsConfig. */ getAvailableModelsForAuthType(authType: AuthType): AvailableModel[] { - return this._modelsConfig.getAvailableModelsForAuthType(authType); + return this.modelsConfig.getAvailableModelsForAuthType(authType); } /** @@ -969,7 +968,7 @@ export class Config { options?: { requireCachedCredentials?: boolean }, metadata?: { reason?: string; context?: string }, ): Promise { - await this._modelsConfig.switchModel(authType, modelId, options, metadata); + await this.modelsConfig.switchModel(authType, modelId, options, metadata); } getMaxSessionTurns(): number {