feat(models): user-managed model shortlist with /provider catalog - #107
Merged
Conversation
Replace the hardcoded 9-model list with a user-managed shortlist that mirrors the iOS mechanism: the chat picker shows only the shortlist, a new Settings screen adds/removes/reorders/renames models, and the candidate catalog is built dynamically from GET /provider (falling back to config/providers without a connected filter on older servers). Model selection is now persisted by model ID (providerId/modelId) instead of list index, with a one-time idempotent migration guarded by schema version 2 and first-launch seeding of the existing 9 presets so existing users are unaffected. Auto-add paths (session switch + message history inference) keep the picker in sync with what a session actually runs. Docs: RFC §4.5/§5.14, PRD feature + decisions, working log.
A reasoning_gpt code review of the shortlist PR surfaced 2 P0 and 6 P1 bugs; all are fixed here. - Migration no longer double-applies the legacy index remap: the stored index is already normalized by migrateRemovedGpt56SolProModelIndices() before the ID migration runs, so map it straight onto the seed. - Seed the shortlist only when the key is genuinely absent; a present-but undecodable list is left on disk untouched instead of being clobbered by the defaults and sealed at schema 2. - launchLoadMessages now auto-adds the session's saved model (not just the history-inferred one) when it's missing from the shortlist, so the selected ID and index can't split and send a different model. - removeModelShortlistItem persists the fallback selection to both the global and per-session stores; the selectedModelId setter now clears the key on null (putString(null) was a no-op) and a removeModelIdForSession helper is added. - resolveModelCatalog returns null when both /provider and config/providers fail, so a previously loaded catalog is preserved instead of wiped. - The migration drops malformed/out-of-range legacy per-session values (mapNotNull) instead of writing them as permanent invalid IDs. - Catalog picker confirm uses the full catalog (cross-search selections no longer lost), shows the count, and is disabled when empty. - Tablet "Manage models" now also expands a collapsed Sessions pane so the deep-link is actually consumed. - Shortlist rows show the stable provider/modelId; buildCatalog falls back to the model id for blank names. Tests: 3 new regression tests (remove-current-selection persistence, missing saved-ID auto-add, blank model name). 351 unit tests pass; lint adds no new errors.
The user-editable short name (简称) was only rendered in the toolbar capsule and the edit dialog. After editing it, the change was invisible in the two lists users actually look at: the Settings shortlist row and the chat model dropdown both showed only the full display name. Show the short name in both places so an edit is visible where it's made and where models are picked: - Settings shortlist row now shows display name / short name / provider-model id (three lines). - Chat model dropdown shows the short name as a subtitle under each model's full name. Display-only change; no state or persistence changes. 351 unit tests pass.
User walkthrough feedback on the previous fix: - The chat model dropdown didn't need changes - restore the original single-line display name per item. - The Settings shortlist row was cluttered: three text lines crammed left of four inline icon buttons (~190dp of 48dp touch targets). Redesign the row around the HostProfileRow pattern the RFC already specified: the whole card is tappable and opens the short-name editor; the display name and provider/model id subtitle get the full width with ellipsis; the short name renders as a compact primary-tinted badge next to the name (echoing the chat capsule label, capped at 120dp); all management actions (edit / move up / move down / delete) collapse into one 32dp MoreVert overflow menu with leading icons, move actions disabled at the list edges, and delete tinted as destructive.
The chat model dropdown now displays two lines per entry: - displayName (bodyLarge) on top - modelId (bodySmall, muted) below This gives users a stable identifier to cross-reference with server logs and API calls, without cluttering the capsule badge.
P1 fixes: - addModelsToShortlist now maintains the selectedModelId invariant: when the shortlist transitions from empty to non-empty, anchor to the first item and persist globally + per-session. - resolveModelCatalog falls back to ModelPresets when both /provider and config/providers fail, so the Add Model catalog is never empty. - launchLoadMessages only auto-adds a saved/inferred model when its provider is present in the loaded providers list, preventing stale or retired models from being resurrected. - AddModelCatalogDialog uses LazyColumn with stable keys instead of eager Column.verticalScroll for large catalogs. - ModelShortlist sub-screen uses rememberSaveable + BackHandler so system Back returns to Settings root instead of popping Settings. P2 fixes: - MoreVert IconButton uses default 48dp touch target (was 32dp). - model_shortlist_count uses <plurals> for correct English singular. Tests: 352 passing (new unknown-provider regression test).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Replaces the hardcoded 9-model list with a user-managed model shortlist that mirrors the iOS mechanism:
GET /provider(connected providers, chat-capable models), with a fallback toconfig/providers(no connected filter) on older servers that lack the route.Key changes
ModelShortlistItem(persisted unit),ProviderRegistryResponse+ProviderModel.capabilities(chat-capable filter).OpenCodeApi.getProviderRegistry()(@GET("provider")) + repository method.providerId/modelId) instead of list index — required because the shortlist is user-mutable. One-time idempotent migration (schema version 2) + first-launch seeding of the existing 9 presets, so existing users are unaffected.ModelShortlistScreen(up/down reorder, edit short-name dialog, delete, catalog picker with search + multi-select), Settings entry row with count badge, chat-picker "Manage models" deep-link row.Decisions (confirmed)
/providerendpoint +config/providersfallback (no connected filter).displayNamefollows the catalog on refresh; user-editedshortNameis preserved.Verification
compileDebugKotlin,compileDebugUnitTestKotlinpass.testDebugUnitTest: 348 tests, 0 failures (newModelShortlistTest16 pure-function tests; updatedAppStateTest/MainViewModelTest/ForkSessionTest/NfcQuickPromptTeststubs + model-selection tests to the ID-based API).lintDebug: 0 new errors from this feature (16MissingTranslationfixed with zh strings); the only 3 remaining errors are pre-existing on master inMainViewModelSupport.kt, not in this diff.