qwen-code/packages/cli
jinye 4b9f597fa8
fix(model): remember selected provider when multiple share a model id (#5173) (#5179)
* fix(model): disambiguate providers sharing a model id by baseUrl (#5173)

When multiple modelProviders.openai entries share the same model id but
have different baseUrl values, selecting one in the model picker was not
remembered across restarts — startup always resolved to the first id match.

The gap was at the persistence -> startup-resolution boundary:
- persistModelSelection only saved model.name, dropping the baseUrl that
  distinguishes the providers.
- resolveCliGenerationConfig matched providers by id alone (first hit).

Fix (CLI only; runtime switching and ModelRegistry composite keys were
already correct):
- Add optional model.baseUrl setting paired with model.name.
- ModelDialog persists the selected provider's baseUrl, and clears it when
  the selection has none (prevents a stale disambiguator).
- /model <id> is an id-only switch, so it clears model.baseUrl too.
- resolveCliGenerationConfig disambiguates by the persisted baseUrl when the
  model comes from settings, falling back to the first id match when the
  paired provider was edited/removed or no baseUrl was saved.

This keeps the persistence layer consistent with ModelRegistry's existing
id+baseUrl composite key and syncAfterAuthRefresh's exact-match path.

* fix(model): clear model.baseUrl on ACP setModel and provider install

Round-1 review follow-up: extend the "any writer of model.name must set or
clear model.baseUrl" invariant to the remaining id-only persistence paths so a
stale disambiguator from a prior model-picker selection can't restore the wrong
provider on next launch.

- ACP Session.setModel clears model.baseUrl alongside model.name.
- Provider install plan (core) clears model.baseUrl when selecting a model id.

* fix(auth): disambiguate provider by baseUrl in pre-flight auth validation

Round-2 review follow-up: validateAuthMethod / hasApiKeyForAuth looked up the
provider by model id only, so for duplicate-id configs the auth and Anthropic
baseUrl checks could validate the first provider's credentials instead of the
selected one.

- findModelConfig now prefers the exact id+baseUrl match and falls back to the
  first id match (mirrors resolveCliGenerationConfig).
- New resolveSelectedModel helper pairs the resolved/persisted model id with its
  baseUrl (runtime generationConfig first, then settings.model.{name,baseUrl}).
- Thread the baseUrl into both the API-key and Anthropic-baseUrl lookups.

* fix(auth): pair model with its own baseUrl; document cross-scope assumption

Round-3 review follow-up:
- resolveSelectedModel no longer falls back to settings.model.baseUrl when a
  live Config is present, so a runtime-selected model is never paired with a
  stale persisted baseUrl from a previous selection.
- Document the merged-settings assumption at the resolver disambiguation site:
  every writer pairs model.name + model.baseUrl in the same scope, and the
  id-only fallback bounds the blast radius if a hand-edited config desyncs them
  across scopes.

* fix(model): use empty-string tombstone to clear model.baseUrl across scopes

Round-4 review follow-up: clearing model.baseUrl by writing undefined drops the
key from JSON, so a higher-priority scope's clear could not override a stale
model.baseUrl left in a lower-priority scope — the stale disambiguator would
resurface on merge and re-select the wrong duplicate provider after restart
(e.g. pick a provider in user scope, then /model <id> in a workspace that has
its own modelProviders).

All clear sites (model picker, /model, ACP setModel, provider install) now write
an empty-string tombstone, which is a present value that overrides lower scopes
on merge. The resolver and auth lookup already treat '' as "no disambiguator".

* fix(acp): clear model.baseUrl when setCoreValue sets model.name

Round-5 review follow-up: the generic qwen/settings/setCoreValue RPC
(ACP/serve) can change model.name directly without touching model.baseUrl,
leaving a stale disambiguator from a prior model-picker selection that would
re-select the wrong duplicate provider on next launch. Clear it with the same
empty-string tombstone used by the other id-only model.name writers.

* fix(model): warn on silent provider fallback; pair baseUrl with resolved config

Address review comments:
- Emit a warning when the persisted model.baseUrl no longer matches any provider
  and resolution falls back to the first id match (thread 1 [Critical]).
- Source effectiveBaseUrl from the resolved config (after?.baseUrl) rather than
  the picker entry alone, so the persisted (model.name, model.baseUrl) pair
  stays consistent even if switchModel transforms the id (thread 2 [Critical]).

* test(model): cover effectiveBaseUrl fallback to picker entry baseUrl

Add a ModelDialog test where switchModel succeeds but getContentGeneratorConfig
returns a config without baseUrl, exercising the
'after?.baseUrl ?? selectedEntry?.model.baseUrl' fallback. Without coverage, a
regression there would silently write an empty-string tombstone and resolve the
wrong same-id provider on next launch.
2026-06-18 10:18:41 +08:00
..
src fix(model): remember selected provider when multiple share a model id (#5173) (#5179) 2026-06-18 10:18:41 +08:00
index.ts feat(cli): add CPU profiling support for Chrome DevTools analysis (#4620) 2026-06-03 03:01:29 +00:00
package.json chore(release): v0.18.3 [skip ci] 2026-06-18 00:28:48 +08:00
test-setup.ts perf(cli): code-split lowlight to cut startup V8 parse cost (#4070) 2026-05-15 17:26:18 +08:00
tsconfig.json fix(cli): fix vim mode Esc leak, Enter submit, render lag and implement missing VIM commands (#4677) 2026-06-08 14:38:06 +08:00
vitest.config.ts feat(daemon): merge daemon-mode feature batch into main (#4490) 2026-06-12 00:34:49 +08:00