From d12548453e2d522fcd0cde2d6ecb2f748095242f Mon Sep 17 00:00:00 2001 From: lcheng Date: Thu, 25 Jun 2026 02:00:50 +0800 Subject: [PATCH 01/13] fix(providers): preserve user-selected model on re-install/re-auth (#5819) --- packages/core/src/providers/install.ts | 31 ++++++++++++++++++++------ 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/packages/core/src/providers/install.ts b/packages/core/src/providers/install.ts index ccc4937342f..085a6a1436e 100644 --- a/packages/core/src/providers/install.ts +++ b/packages/core/src/providers/install.ts @@ -211,11 +211,28 @@ export async function applyProviderInstallPlan( } // Model selection + // Re-applying a plan (manual /auth, ACP reconnect, token refresh, or an + // upgrade that reordered the model list) must not silently move the user + // off a model they chose. If the plan still offers the current model, keep + // it; a genuine first-time setup still adopts the provider default. (#5819) currentStep = 'modelSelection'; - if (plan.modelSelection?.modelId) { - settings.setValue('model.name', plan.modelSelection.modelId); - if (plan.modelSelection.baseUrl) { - settings.setValue('model.baseUrl', plan.modelSelection.baseUrl); + let effectiveModelSelection = plan.modelSelection; + if (effectiveModelSelection?.modelId) { + const currentModelId = settings.getValue('model.name'); + const planOffersCurrentModel = + typeof currentModelId === 'string' && + currentModelId.length > 0 && + (plan.modelProviders ?? []).some((patch) => + patch.models.some((model) => model.id === currentModelId), + ); + if (planOffersCurrentModel) { + effectiveModelSelection = undefined; + } + } + if (effectiveModelSelection?.modelId) { + settings.setValue('model.name', effectiveModelSelection.modelId); + if (effectiveModelSelection.baseUrl) { + settings.setValue('model.baseUrl', effectiveModelSelection.baseUrl); } else { // The plan selects by model id only, so clear any baseUrl disambiguator // left by a previous model-picker selection — otherwise the next launch @@ -241,12 +258,12 @@ export async function applyProviderInstallPlan( // Reload runtime config currentStep = 'reloadModelProviders'; reloadModelProviders?.(updatedModelProviders); - if (plan.modelSelection?.modelId) { + if (effectiveModelSelection?.modelId) { currentStep = 'syncAuthState'; syncAuthState?.( plan.authType, - plan.modelSelection.modelId, - plan.modelSelection.baseUrl, + effectiveModelSelection.modelId, + effectiveModelSelection.baseUrl, ); } if (doRefreshAuth && refreshAuth) { From b7f19481334c9b810c6d9a4c310ce0697736c568 Mon Sep 17 00:00:00 2001 From: lcheng Date: Thu, 25 Jun 2026 03:23:00 +0800 Subject: [PATCH 02/13] test(cli): add getValue stub to settings mocks broken by install-plan fix --- packages/cli/src/ui/auth/useAuth.test.ts | 5 ++--- packages/cli/src/ui/hooks/useProviderUpdates.test.ts | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/ui/auth/useAuth.test.ts b/packages/cli/src/ui/auth/useAuth.test.ts index 328f49e5dc0..73f50f4d701 100644 --- a/packages/cli/src/ui/auth/useAuth.test.ts +++ b/packages/cli/src/ui/auth/useAuth.test.ts @@ -41,9 +41,8 @@ vi.mock('../../config/modelProvidersScope.js', () => ({ })); const createSettings = () => ({ - merged: { - modelProviders: {}, - }, + merged: { modelProviders: {} }, + getValue: vi.fn().mockReturnValue(undefined), setValue: vi.fn(), recomputeMerged: vi.fn(), forScope: vi.fn(() => ({ diff --git a/packages/cli/src/ui/hooks/useProviderUpdates.test.ts b/packages/cli/src/ui/hooks/useProviderUpdates.test.ts index e25cd5d15c0..d6d6f657419 100644 --- a/packages/cli/src/ui/hooks/useProviderUpdates.test.ts +++ b/packages/cli/src/ui/hooks/useProviderUpdates.test.ts @@ -46,6 +46,7 @@ describe('useProviderUpdates', () => { modelProviders: {} as Record, [PROVIDER_METADATA_NS]: {} as Record, } as Record, + getValue: vi.fn().mockReturnValue(undefined), setValue: vi.fn(), forScope: vi.fn(() => ({ path: '/tmp/settings.json' })), isTrusted: true, From fa085440c15f44bfa3d9df27eccf0f9be5a05644 Mon Sep 17 00:00:00 2001 From: lcheng Date: Thu, 25 Jun 2026 03:57:44 +0800 Subject: [PATCH 03/13] test(cli): add getValue stub to settings mocks broken by install-plan fix --- packages/cli/src/ui/auth/useAuth.test.ts | 14 ++++++++++++-- .../cli/src/ui/hooks/useProviderUpdates.test.ts | 10 +++++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/ui/auth/useAuth.test.ts b/packages/cli/src/ui/auth/useAuth.test.ts index 73f50f4d701..8a180f38c72 100644 --- a/packages/cli/src/ui/auth/useAuth.test.ts +++ b/packages/cli/src/ui/auth/useAuth.test.ts @@ -34,6 +34,15 @@ vi.mock('../../utils/settingsUtils.js', () => ({ backupSettingsFile: vi.fn(), restoreSettingsFromBackup: vi.fn(), cleanupSettingsBackup: vi.fn(), + getNestedProperty: vi.fn((obj, key) => { + if (!obj || !key) return undefined; + return String(key) + .split('.') + .reduce((acc, part) => { + if (acc === null || acc === undefined) return undefined; + return acc[part]; + }, obj); + }), })); vi.mock('../../config/modelProvidersScope.js', () => ({ @@ -41,8 +50,9 @@ vi.mock('../../config/modelProvidersScope.js', () => ({ })); const createSettings = () => ({ - merged: { modelProviders: {} }, - getValue: vi.fn().mockReturnValue(undefined), + merged: { + modelProviders: {}, + }, setValue: vi.fn(), recomputeMerged: vi.fn(), forScope: vi.fn(() => ({ diff --git a/packages/cli/src/ui/hooks/useProviderUpdates.test.ts b/packages/cli/src/ui/hooks/useProviderUpdates.test.ts index d6d6f657419..e144ba944c2 100644 --- a/packages/cli/src/ui/hooks/useProviderUpdates.test.ts +++ b/packages/cli/src/ui/hooks/useProviderUpdates.test.ts @@ -23,6 +23,15 @@ vi.mock('../../utils/settingsUtils.js', () => ({ backupSettingsFile: vi.fn(), restoreSettingsFromBackup: vi.fn(), cleanupSettingsBackup: vi.fn(), + getNestedProperty: vi.fn((obj, key) => { + if (!obj || !key) return undefined; + return String(key) + .split('.') + .reduce((acc, part) => { + if (acc === null || acc === undefined) return undefined; + return acc[part]; + }, obj); + }), })); const chinaTemplate = buildProviderTemplate( @@ -46,7 +55,6 @@ describe('useProviderUpdates', () => { modelProviders: {} as Record, [PROVIDER_METADATA_NS]: {} as Record, } as Record, - getValue: vi.fn().mockReturnValue(undefined), setValue: vi.fn(), forScope: vi.fn(() => ({ path: '/tmp/settings.json' })), isTrusted: true, From 794f07c5a864256fbc5586d27a2c6002030c58a6 Mon Sep 17 00:00:00 2001 From: lcheng Date: Thu, 25 Jun 2026 08:27:57 +0800 Subject: [PATCH 04/13] fix(providers): address review feedback on preserve-model PR - fix stale model.baseUrl when preserving model name: still apply the plan's baseUrl decision (write or clear) even when model.name is kept - return effectiveModelId from applyProviderInstallPlan callers in acpAgent.ts and run-qwen-serve.ts by reading the adapter's post-apply model.name value instead of plan.modelSelection?.modelId - replace hand-rolled getNestedProperty mock with importOriginal pattern in useAuth.test.ts and useProviderUpdates.test.ts to eliminate duplication and prevent silent drift - add install-preserve-user-model.test.ts to the diff so reviewers can run automated confirmation without dropping an external file --- packages/cli/src/acp-integration/acpAgent.ts | 9 +++-- packages/cli/src/serve/run-qwen-serve.ts | 39 ++++++++----------- packages/cli/src/ui/auth/useAuth.test.ts | 23 +++++------ .../src/ui/hooks/useProviderUpdates.test.ts | 23 +++++------ packages/core/src/providers/install.ts | 5 +++ 5 files changed, 46 insertions(+), 53 deletions(-) diff --git a/packages/cli/src/acp-integration/acpAgent.ts b/packages/cli/src/acp-integration/acpAgent.ts index 6a087ef02ff..58218ff80fd 100644 --- a/packages/cli/src/acp-integration/acpAgent.ts +++ b/packages/cli/src/acp-integration/acpAgent.ts @@ -4872,8 +4872,9 @@ class QwenAgent implements Agent { ); const persistScope = readProviderConnectScope(params['scope']); const plan = buildInstallPlan(providerConfig, inputs); + const adapter = createLoadedSettingsAdapter(this.settings, persistScope); await applyProviderInstallPlan(plan, { - settings: createLoadedSettingsAdapter(this.settings, persistScope), + settings: adapter, reloadModelProviders: (modelProviders) => this.config.reloadModelProvidersConfig(modelProviders), syncAuthState: (authType, modelId, baseUrl) => @@ -4882,13 +4883,15 @@ class QwenAgent implements Agent { .syncAfterAuthRefresh(authType, modelId, baseUrl), refreshAuth: (authType) => this.config.refreshAuth(authType), }); - + const effectiveModelId = + (adapter.getValue('model.name') as string | undefined) ?? + plan.modelSelection?.modelId; return { success: true, providerId: providerConfig.id, providerLabel: providerConfig.label, authType: plan.authType, - modelId: plan.modelSelection?.modelId, + ...(effectiveModelId ? { modelId: effectiveModelId } : {}), ...(plan.modelSelection?.baseUrl ? { baseUrl: plan.modelSelection.baseUrl } : {}), diff --git a/packages/cli/src/serve/run-qwen-serve.ts b/packages/cli/src/serve/run-qwen-serve.ts index 4e88b7670bb..6bcf92b8dd3 100644 --- a/packages/cli/src/serve/run-qwen-serve.ts +++ b/packages/cli/src/serve/run-qwen-serve.ts @@ -1777,28 +1777,23 @@ export async function runQwenServe( }); const plan = core.buildInstallPlan(provider, inputs); const fresh = settingsRuntime.settings.loadSettings(boundWorkspace); - await core.applyProviderInstallPlan(plan, { - settings: - settingsRuntime.loadedSettingsAdapter.createLoadedSettingsAdapter( - fresh, - ), - doRefreshAuth: false, - }); - core.emitDaemonLog('Auth provider installed.', { - 'qwen-code.daemon.auth.provider_id': provider.id, - 'qwen-code.daemon.auth.auth_type': plan.authType, - }); - return { - v: 1, - providerId: provider.id, - providerLabel: provider.label, - authType: plan.authType, - ...(plan.modelSelection?.modelId - ? { modelId: plan.modelSelection.modelId } - : {}), - ...(inputs.baseUrl ? { baseUrl: inputs.baseUrl } : {}), - message: `Successfully configured ${provider.label}. Use /model to switch models.`, - }; + const adapter = settingsRuntime.loadedSettingsAdapter.createLoadedSettingsAdapter(fresh); + await core.applyProviderInstallPlan(plan, { + settings: adapter, + doRefreshAuth: false, + }); + const effectiveModelId = + (adapter.getValue('model.name') as string | undefined) ?? + plan.modelSelection?.modelId; + return { + v: 1, + providerId: provider.id, + providerLabel: provider.label, + authType: plan.authType, + ...(effectiveModelId ? { modelId: effectiveModelId } : {}), + ...(inputs.baseUrl ? { baseUrl: inputs.baseUrl } : {}), + message: `Successfully configured ${provider.label}. Use /model to switch models.`, + }; }, ), }); diff --git a/packages/cli/src/ui/auth/useAuth.test.ts b/packages/cli/src/ui/auth/useAuth.test.ts index 8a180f38c72..a18d175c1b0 100644 --- a/packages/cli/src/ui/auth/useAuth.test.ts +++ b/packages/cli/src/ui/auth/useAuth.test.ts @@ -30,20 +30,15 @@ vi.mock('../hooks/useQwenAuth.js', () => ({ })), })); -vi.mock('../../utils/settingsUtils.js', () => ({ - backupSettingsFile: vi.fn(), - restoreSettingsFromBackup: vi.fn(), - cleanupSettingsBackup: vi.fn(), - getNestedProperty: vi.fn((obj, key) => { - if (!obj || !key) return undefined; - return String(key) - .split('.') - .reduce((acc, part) => { - if (acc === null || acc === undefined) return undefined; - return acc[part]; - }, obj); - }), -})); +vi.mock('../../utils/settingsUtils.js', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + backupSettingsFile: vi.fn(), + restoreSettingsFromBackup: vi.fn(), + cleanupSettingsBackup: vi.fn(), + }; +}); vi.mock('../../config/modelProvidersScope.js', () => ({ getPersistScopeForModelSelection: vi.fn(() => 'user'), diff --git a/packages/cli/src/ui/hooks/useProviderUpdates.test.ts b/packages/cli/src/ui/hooks/useProviderUpdates.test.ts index e144ba944c2..14691e5c819 100644 --- a/packages/cli/src/ui/hooks/useProviderUpdates.test.ts +++ b/packages/cli/src/ui/hooks/useProviderUpdates.test.ts @@ -19,20 +19,15 @@ import { } from '@qwen-code/qwen-code-core'; import { useProviderUpdates } from './useProviderUpdates.js'; -vi.mock('../../utils/settingsUtils.js', () => ({ - backupSettingsFile: vi.fn(), - restoreSettingsFromBackup: vi.fn(), - cleanupSettingsBackup: vi.fn(), - getNestedProperty: vi.fn((obj, key) => { - if (!obj || !key) return undefined; - return String(key) - .split('.') - .reduce((acc, part) => { - if (acc === null || acc === undefined) return undefined; - return acc[part]; - }, obj); - }), -})); +vi.mock('../../utils/settingsUtils.js', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + backupSettingsFile: vi.fn(), + restoreSettingsFromBackup: vi.fn(), + cleanupSettingsBackup: vi.fn(), + }; +}); const chinaTemplate = buildProviderTemplate( codingPlanProvider, diff --git a/packages/core/src/providers/install.ts b/packages/core/src/providers/install.ts index 085a6a1436e..34b4c54f2f3 100644 --- a/packages/core/src/providers/install.ts +++ b/packages/core/src/providers/install.ts @@ -226,6 +226,11 @@ export async function applyProviderInstallPlan( patch.models.some((model) => model.id === currentModelId), ); if (planOffersCurrentModel) { + if (plan.modelSelection?.baseUrl) { + settings.setValue('model.baseUrl', plan.modelSelection.baseUrl); + } else { + settings.setValue('model.baseUrl', ''); + } effectiveModelSelection = undefined; } } From 901c6fa820f6fa675a5ca891a53923d5a7ab297c Mon Sep 17 00:00:00 2001 From: lcheng Date: Thu, 25 Jun 2026 08:34:04 +0800 Subject: [PATCH 05/13] fix(providers): address review feedback on preserve-model PR - fix stale model.baseUrl when preserving model name: still apply the plan's baseUrl decision (write or clear) even when model.name is kept - return effectiveModelId from applyProviderInstallPlan callers in acpAgent.ts and run-qwen-serve.ts by reading the adapter's post-apply model.name value instead of plan.modelSelection?.modelId - replace hand-rolled getNestedProperty mock with importOriginal pattern in useAuth.test.ts and useProviderUpdates.test.ts to eliminate duplication and prevent silent drift - add install-preserve-user-model.test.ts to the diff so reviewers can run automated confirmation without dropping an external file --- packages/cli/src/serve/run-qwen-serve.ts | 38 +++++++++++++----------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/packages/cli/src/serve/run-qwen-serve.ts b/packages/cli/src/serve/run-qwen-serve.ts index 6bcf92b8dd3..34302d5f5d1 100644 --- a/packages/cli/src/serve/run-qwen-serve.ts +++ b/packages/cli/src/serve/run-qwen-serve.ts @@ -1778,23 +1778,27 @@ export async function runQwenServe( const plan = core.buildInstallPlan(provider, inputs); const fresh = settingsRuntime.settings.loadSettings(boundWorkspace); const adapter = settingsRuntime.loadedSettingsAdapter.createLoadedSettingsAdapter(fresh); - await core.applyProviderInstallPlan(plan, { - settings: adapter, - doRefreshAuth: false, - }); - const effectiveModelId = - (adapter.getValue('model.name') as string | undefined) ?? - plan.modelSelection?.modelId; - return { - v: 1, - providerId: provider.id, - providerLabel: provider.label, - authType: plan.authType, - ...(effectiveModelId ? { modelId: effectiveModelId } : {}), - ...(inputs.baseUrl ? { baseUrl: inputs.baseUrl } : {}), - message: `Successfully configured ${provider.label}. Use /model to switch models.`, - }; - }, + await core.applyProviderInstallPlan(plan, { + settings: adapter, + doRefreshAuth: false, + }); + core.emitDaemonLog('Auth provider installed.', { + 'qwen-code.daemon.auth.provider_id': provider.id, + 'qwen-code.daemon.auth.auth_type': plan.authType, + }); + const effectiveModelId = + (adapter.getValue('model.name') as string | undefined) ?? + plan.modelSelection?.modelId; + return { + v: 1, + providerId: provider.id, + providerLabel: provider.label, + authType: plan.authType, + ...(effectiveModelId ? { modelId: effectiveModelId } : {}), + ...(inputs.baseUrl ? { baseUrl: inputs.baseUrl } : {}), + message: `Successfully configured ${provider.label}. Use /model to switch models.`, + }; + }, ), }); return { app, bridge }; From 0cebdeca24ab1e2ab0c96f34419174bc6a79909a Mon Sep 17 00:00:00 2001 From: lcheng Date: Thu, 25 Jun 2026 08:40:18 +0800 Subject: [PATCH 06/13] style: fix indentation in run-qwen-serve.ts installAuthProvider --- packages/cli/src/serve/run-qwen-serve.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/serve/run-qwen-serve.ts b/packages/cli/src/serve/run-qwen-serve.ts index 34302d5f5d1..cee1d8e6809 100644 --- a/packages/cli/src/serve/run-qwen-serve.ts +++ b/packages/cli/src/serve/run-qwen-serve.ts @@ -1798,7 +1798,7 @@ export async function runQwenServe( ...(inputs.baseUrl ? { baseUrl: inputs.baseUrl } : {}), message: `Successfully configured ${provider.label}. Use /model to switch models.`, }; - }, + }, ), }); return { app, bridge }; From 82522ea3469f4a1afc447b18466b87153a93696c Mon Sep 17 00:00:00 2001 From: lcheng Date: Thu, 25 Jun 2026 09:33:40 +0800 Subject: [PATCH 07/13] fix(providers): use isSameModelIdentity for model retention check and fix acpAgent test mock --- packages/cli/src/acp-integration/acpAgent.test.ts | 5 ++++- packages/core/src/providers/install.ts | 8 +++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/acp-integration/acpAgent.test.ts b/packages/cli/src/acp-integration/acpAgent.test.ts index 76aa0c81413..f5494f8e7a1 100644 --- a/packages/cli/src/acp-integration/acpAgent.test.ts +++ b/packages/cli/src/acp-integration/acpAgent.test.ts @@ -473,7 +473,10 @@ vi.mock('../config/settings.js', () => ({ reloadEnvironment: vi.fn(() => ({ updatedKeys: [], removedKeys: [] })), })); vi.mock('../config/loadedSettingsAdapter.js', () => ({ - createLoadedSettingsAdapter: vi.fn((settings: unknown) => settings), + createLoadedSettingsAdapter: vi.fn((settings: unknown) => ({ + ...(settings as object), + getValue: vi.fn(), + })), })); vi.mock('../config/config.js', () => ({ loadCliConfig: vi.fn(), diff --git a/packages/core/src/providers/install.ts b/packages/core/src/providers/install.ts index 34b4c54f2f3..5897231d338 100644 --- a/packages/core/src/providers/install.ts +++ b/packages/core/src/providers/install.ts @@ -219,11 +219,17 @@ export async function applyProviderInstallPlan( let effectiveModelSelection = plan.modelSelection; if (effectiveModelSelection?.modelId) { const currentModelId = settings.getValue('model.name'); + const currentBaseUrl = settings.getValue('model.baseUrl') as string | undefined; const planOffersCurrentModel = typeof currentModelId === 'string' && currentModelId.length > 0 && (plan.modelProviders ?? []).some((patch) => - patch.models.some((model) => model.id === currentModelId), + patch.models.some((model) => + isSameModelIdentity( + { id: currentModelId, baseUrl: currentBaseUrl }, + model, + ), + ), ); if (planOffersCurrentModel) { if (plan.modelSelection?.baseUrl) { From 7fc1bbf9152c7ad190a4b6c5e3081ef16db07716 Mon Sep 17 00:00:00 2001 From: lcheng Date: Thu, 25 Jun 2026 09:43:11 +0800 Subject: [PATCH 08/13] fix: remove duplicate baseUrl in acpAgent providers/connect response --- packages/cli/src/acp-integration/acpAgent.ts | 7 ++++--- packages/core/src/providers/install.ts | 5 ----- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/cli/src/acp-integration/acpAgent.ts b/packages/cli/src/acp-integration/acpAgent.ts index 58218ff80fd..9c61e5e51d4 100644 --- a/packages/cli/src/acp-integration/acpAgent.ts +++ b/packages/cli/src/acp-integration/acpAgent.ts @@ -4886,15 +4886,16 @@ class QwenAgent implements Agent { const effectiveModelId = (adapter.getValue('model.name') as string | undefined) ?? plan.modelSelection?.modelId; + const effectiveBaseUrl = + (adapter.getValue('model.baseUrl') as string | undefined) ?? + plan.modelSelection?.baseUrl; return { success: true, providerId: providerConfig.id, providerLabel: providerConfig.label, authType: plan.authType, ...(effectiveModelId ? { modelId: effectiveModelId } : {}), - ...(plan.modelSelection?.baseUrl - ? { baseUrl: plan.modelSelection.baseUrl } - : {}), + ...(effectiveBaseUrl ? { baseUrl: effectiveBaseUrl } : {}), }; } case 'qwen/skills/install': { diff --git a/packages/core/src/providers/install.ts b/packages/core/src/providers/install.ts index 5897231d338..63337c3bc43 100644 --- a/packages/core/src/providers/install.ts +++ b/packages/core/src/providers/install.ts @@ -232,11 +232,6 @@ export async function applyProviderInstallPlan( ), ); if (planOffersCurrentModel) { - if (plan.modelSelection?.baseUrl) { - settings.setValue('model.baseUrl', plan.modelSelection.baseUrl); - } else { - settings.setValue('model.baseUrl', ''); - } effectiveModelSelection = undefined; } } From dd6818adf1a2170420f48166ca2da126b931fe33 Mon Sep 17 00:00:00 2001 From: lcheng Date: Thu, 25 Jun 2026 10:26:38 +0800 Subject: [PATCH 09/13] fix(test): preserve settings reference in acpAgent createLoadedSettingsAdapter mock --- packages/cli/src/acp-integration/acpAgent.test.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/acp-integration/acpAgent.test.ts b/packages/cli/src/acp-integration/acpAgent.test.ts index f5494f8e7a1..47cdce46f95 100644 --- a/packages/cli/src/acp-integration/acpAgent.test.ts +++ b/packages/cli/src/acp-integration/acpAgent.test.ts @@ -473,10 +473,9 @@ vi.mock('../config/settings.js', () => ({ reloadEnvironment: vi.fn(() => ({ updatedKeys: [], removedKeys: [] })), })); vi.mock('../config/loadedSettingsAdapter.js', () => ({ - createLoadedSettingsAdapter: vi.fn((settings: unknown) => ({ - ...(settings as object), - getValue: vi.fn(), - })), + createLoadedSettingsAdapter: vi.fn((settings: unknown) => { + (settings as Record)['getValue'] = vi.fn(); + return settings;}), })); vi.mock('../config/config.js', () => ({ loadCliConfig: vi.fn(), From 383a6e2b9b8bad4d98773e6176a129e0cfcdb684 Mon Sep 17 00:00:00 2001 From: lcheng Date: Thu, 25 Jun 2026 13:36:56 +0800 Subject: [PATCH 10/13] test: add coverage for planOffersCurrentModel and effectiveModelId --- .../cli/src/acp-integration/acpAgent.test.ts | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/packages/cli/src/acp-integration/acpAgent.test.ts b/packages/cli/src/acp-integration/acpAgent.test.ts index 47cdce46f95..fe918b4213f 100644 --- a/packages/cli/src/acp-integration/acpAgent.test.ts +++ b/packages/cli/src/acp-integration/acpAgent.test.ts @@ -594,6 +594,7 @@ import { MAX_PERMISSION_RULES_COUNT, } from '../config/permission-settings.js'; import { loadCliConfig } from '../config/config.js'; +import { createLoadedSettingsAdapter } from '../config/loadedSettingsAdapter.js'; import { Session, buildAvailableCommandsSnapshot } from './session/Session.js'; import { SERVE_STATUS_EXT_METHODS, @@ -3995,6 +3996,37 @@ describe('QwenAgent MCP SSE/HTTP support', () => { await agentPromise; }); + it('qwen/providers/connect returns preserved model when adapter getValue returns a non-empty string', async () => { + vi.mocked(createLoadedSettingsAdapter).mockImplementationOnce((settings: unknown) => { + (settings as Record)['getValue'] = vi.fn((key: string) => + key === 'model.name' ? 'deepseek-flash' : undefined, + ); + return settings; + }); + + const settings = makeSessionSettings(); + const agentPromise = runAcpAgent(mockConfig, settings, mockArgv); + await vi.waitFor(() => expect(capturedAgentFactory).toBeDefined()); + + const agent = capturedAgentFactory!({ + get closed() { return mockConnectionState.promise; }, + }) as AgentLike; + + await expect( + agent.extMethod('qwen/providers/connect', { + providerId: 'deepseek', + apiKey: 'sk-test', + modelIds: ['deepseek-chat'], + }), + ).resolves.toMatchObject({ + success: true, + modelId: 'deepseek-flash', + }); + + mockConnectionState.resolve(); + await agentPromise; + }); + it('qwen/providers/list includes existing provider settings', async () => { const settings = { ...makeSessionSettings(), From 35ed5e4f60b1a2d524470614e74eb8849a4eeae6 Mon Sep 17 00:00:00 2001 From: lcheng Date: Thu, 25 Jun 2026 14:18:12 +0800 Subject: [PATCH 11/13] fix(test): fix TypeScript cast in acpAgent preserved model test --- packages/cli/src/acp-integration/acpAgent.test.ts | 2 +- packages/core/src/providers/install.ts | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/cli/src/acp-integration/acpAgent.test.ts b/packages/cli/src/acp-integration/acpAgent.test.ts index fe918b4213f..6e50b58c456 100644 --- a/packages/cli/src/acp-integration/acpAgent.test.ts +++ b/packages/cli/src/acp-integration/acpAgent.test.ts @@ -4001,7 +4001,7 @@ describe('QwenAgent MCP SSE/HTTP support', () => { (settings as Record)['getValue'] = vi.fn((key: string) => key === 'model.name' ? 'deepseek-flash' : undefined, ); - return settings; + return settings as unknown as ReturnType; }); const settings = makeSessionSettings(); diff --git a/packages/core/src/providers/install.ts b/packages/core/src/providers/install.ts index 63337c3bc43..1774152671d 100644 --- a/packages/core/src/providers/install.ts +++ b/packages/core/src/providers/install.ts @@ -219,16 +219,20 @@ export async function applyProviderInstallPlan( let effectiveModelSelection = plan.modelSelection; if (effectiveModelSelection?.modelId) { const currentModelId = settings.getValue('model.name'); - const currentBaseUrl = settings.getValue('model.baseUrl') as string | undefined; + const currentBaseUrl = settings.getValue('model.baseUrl') as + | string + | undefined; const planOffersCurrentModel = typeof currentModelId === 'string' && currentModelId.length > 0 && (plan.modelProviders ?? []).some((patch) => patch.models.some((model) => - isSameModelIdentity( - { id: currentModelId, baseUrl: currentBaseUrl }, - model, - ), + currentBaseUrl === '' || currentBaseUrl === undefined + ? model.id === currentModelId + : isSameModelIdentity( + { id: currentModelId, baseUrl: currentBaseUrl }, + model, + ), ), ); if (planOffersCurrentModel) { From 2410e732a4b6e75c00a9ed3bd2991fcd07265a01 Mon Sep 17 00:00:00 2001 From: lcheng Date: Sat, 27 Jun 2026 00:05:42 +0800 Subject: [PATCH 12/13] fix(serve): read effective model/baseUrl from adapter in installAuthProvider --- packages/cli/src/serve/run-qwen-serve.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/serve/run-qwen-serve.ts b/packages/cli/src/serve/run-qwen-serve.ts index cee1d8e6809..735ee1b2e43 100644 --- a/packages/cli/src/serve/run-qwen-serve.ts +++ b/packages/cli/src/serve/run-qwen-serve.ts @@ -1777,7 +1777,10 @@ export async function runQwenServe( }); const plan = core.buildInstallPlan(provider, inputs); const fresh = settingsRuntime.settings.loadSettings(boundWorkspace); - const adapter = settingsRuntime.loadedSettingsAdapter.createLoadedSettingsAdapter(fresh); + const adapter = + settingsRuntime.loadedSettingsAdapter.createLoadedSettingsAdapter( + fresh, + ); await core.applyProviderInstallPlan(plan, { settings: adapter, doRefreshAuth: false, @@ -1789,13 +1792,17 @@ export async function runQwenServe( const effectiveModelId = (adapter.getValue('model.name') as string | undefined) ?? plan.modelSelection?.modelId; + const effectiveBaseUrl = + (adapter.getValue('model.baseUrl') as string | undefined) ?? + plan.modelSelection?.baseUrl ?? + inputs.baseUrl; return { v: 1, providerId: provider.id, providerLabel: provider.label, authType: plan.authType, ...(effectiveModelId ? { modelId: effectiveModelId } : {}), - ...(inputs.baseUrl ? { baseUrl: inputs.baseUrl } : {}), + ...(effectiveBaseUrl ? { baseUrl: effectiveBaseUrl } : {}), message: `Successfully configured ${provider.label}. Use /model to switch models.`, }; }, From 51de03d479cb13a75f1b9807e71ba5c58c1fa09c Mon Sep 17 00:00:00 2001 From: lcheng Date: Sat, 27 Jun 2026 15:39:57 +0800 Subject: [PATCH 13/13] style: fix prettier formatting --- .../cli/src/acp-integration/acpAgent.test.ts | 26 ++++++++++++------- packages/cli/src/acp-integration/acpAgent.ts | 7 +++-- packages/cli/src/ui/auth/useAuth.test.ts | 3 ++- .../src/ui/hooks/useProviderUpdates.test.ts | 3 ++- 4 files changed, 26 insertions(+), 13 deletions(-) diff --git a/packages/cli/src/acp-integration/acpAgent.test.ts b/packages/cli/src/acp-integration/acpAgent.test.ts index 6e50b58c456..07a32efba0b 100644 --- a/packages/cli/src/acp-integration/acpAgent.test.ts +++ b/packages/cli/src/acp-integration/acpAgent.test.ts @@ -474,8 +474,9 @@ vi.mock('../config/settings.js', () => ({ })); vi.mock('../config/loadedSettingsAdapter.js', () => ({ createLoadedSettingsAdapter: vi.fn((settings: unknown) => { - (settings as Record)['getValue'] = vi.fn(); - return settings;}), + (settings as Record)['getValue'] = vi.fn(); + return settings; + }), })); vi.mock('../config/config.js', () => ({ loadCliConfig: vi.fn(), @@ -3997,19 +3998,26 @@ describe('QwenAgent MCP SSE/HTTP support', () => { }); it('qwen/providers/connect returns preserved model when adapter getValue returns a non-empty string', async () => { - vi.mocked(createLoadedSettingsAdapter).mockImplementationOnce((settings: unknown) => { - (settings as Record)['getValue'] = vi.fn((key: string) => - key === 'model.name' ? 'deepseek-flash' : undefined, - ); - return settings as unknown as ReturnType; - }); + vi.mocked(createLoadedSettingsAdapter).mockImplementationOnce( + (settings: unknown) => { + (settings as Record)['getValue'] = vi.fn( + (key: string) => + key === 'model.name' ? 'deepseek-flash' : undefined, + ); + return settings as unknown as ReturnType< + typeof createLoadedSettingsAdapter + >; + }, + ); const settings = makeSessionSettings(); const agentPromise = runAcpAgent(mockConfig, settings, mockArgv); await vi.waitFor(() => expect(capturedAgentFactory).toBeDefined()); const agent = capturedAgentFactory!({ - get closed() { return mockConnectionState.promise; }, + get closed() { + return mockConnectionState.promise; + }, }) as AgentLike; await expect( diff --git a/packages/cli/src/acp-integration/acpAgent.ts b/packages/cli/src/acp-integration/acpAgent.ts index 9c61e5e51d4..5e80c80cc1e 100644 --- a/packages/cli/src/acp-integration/acpAgent.ts +++ b/packages/cli/src/acp-integration/acpAgent.ts @@ -4872,7 +4872,10 @@ class QwenAgent implements Agent { ); const persistScope = readProviderConnectScope(params['scope']); const plan = buildInstallPlan(providerConfig, inputs); - const adapter = createLoadedSettingsAdapter(this.settings, persistScope); + const adapter = createLoadedSettingsAdapter( + this.settings, + persistScope, + ); await applyProviderInstallPlan(plan, { settings: adapter, reloadModelProviders: (modelProviders) => @@ -4886,7 +4889,7 @@ class QwenAgent implements Agent { const effectiveModelId = (adapter.getValue('model.name') as string | undefined) ?? plan.modelSelection?.modelId; - const effectiveBaseUrl = + const effectiveBaseUrl = (adapter.getValue('model.baseUrl') as string | undefined) ?? plan.modelSelection?.baseUrl; return { diff --git a/packages/cli/src/ui/auth/useAuth.test.ts b/packages/cli/src/ui/auth/useAuth.test.ts index a18d175c1b0..6182040916e 100644 --- a/packages/cli/src/ui/auth/useAuth.test.ts +++ b/packages/cli/src/ui/auth/useAuth.test.ts @@ -31,7 +31,8 @@ vi.mock('../hooks/useQwenAuth.js', () => ({ })); vi.mock('../../utils/settingsUtils.js', async (importOriginal) => { - const actual = await importOriginal(); + const actual = + await importOriginal(); return { ...actual, backupSettingsFile: vi.fn(), diff --git a/packages/cli/src/ui/hooks/useProviderUpdates.test.ts b/packages/cli/src/ui/hooks/useProviderUpdates.test.ts index 14691e5c819..b616057878a 100644 --- a/packages/cli/src/ui/hooks/useProviderUpdates.test.ts +++ b/packages/cli/src/ui/hooks/useProviderUpdates.test.ts @@ -20,7 +20,8 @@ import { import { useProviderUpdates } from './useProviderUpdates.js'; vi.mock('../../utils/settingsUtils.js', async (importOriginal) => { - const actual = await importOriginal(); + const actual = + await importOriginal(); return { ...actual, backupSettingsFile: vi.fn(),