Skip to content

Group model picker by provider for ACP route models - #2465

Open
bradhallett wants to merge 2 commits into
get-bb:mainfrom
bradhallett:fix/2062-model-picker-provider-groups
Open

Group model picker by provider for ACP route models#2465
bradhallett wants to merge 2 commits into
get-bb:mainfrom
bradhallett:fix/2062-model-picker-provider-groups

Conversation

@bradhallett

@bradhallett bradhallett commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Human comments

What was wrong

buildModelCatalogFromConfigOptions hard-coded description: "" for every ACP model select option, and acpConfigOptionSelectOptionSchema did not type description (it only survived via .passthrough()). Agents such as omp send description: "provider/modelId" and advertise the same display name under several routes (on this machine: 279 models across 9 providers, 37 duplicate display names). The desktop picker rendered only the display name, so those rows were identical in text, tooltip, and accessible name. Issue: #2062. Closed PR #2226 had the bridge fix plus inline qualifiers; this redo keeps the bridge hunks and replaces the desktop UX with provider-grouped headers.

What changed

Bridge (packages/provider-bridge-acp):

  • src/wire.ts: description: acpOptionalString on the select-option schema.
  • src/bridge/model-catalog.ts: description: option.description ?? "", mirroring buildModelCatalogFromSessionModels.
  • No HOST_DAEMON_PROTOCOL_VERSION bump: AvailableModel.description is already a required string.

Desktop (apps/app):

  • Two-level grouped list by route key (routeProviderId, else the first segment of a slashed id). Sticky MenuSectionLabel headers only when ≥2 routes are visible. Search that narrows to one route hides the headers (same rule).
  • Within-group label collisions get an inline qualifier (id remainder after provider/). The trigger shows a distinguishing token only when the committed label is ambiguous across modelOptions + moreModelOptions.
  • Tooltips stay on the inner span[title] with the full raw id; keyboard order matches DOM order (contiguous route runs, not merged).
  • Curated short display names (zai → Z.ai, xai-oauth → xAI, …) with a hyphen→title-case fallback. omp 18.0.5 does not expose provider display names on omp models --json; group keys and tooltips stay on raw ids.
  • Single-provider lists (Claude Code, Codex) render with no headers and no qualifiers.
  • .ladle fixture + story cover the omp-style multi-route catalog.

The former mobile assertion (apps/mobile/src/data/compose/execution-options.test.ts) was dropped during the rebase: apps/mobile/src/data/compose/ no longer exists on main. The bridge fix itself is unchanged, so whichever surface consumes AvailableModel.description now gets the provider-qualified subtitle.

Deviation from closed #2226: grouping + headers instead of always-on inline qualifiers.

How you verified

Tests (fail before, pass after):

  • packages/provider-bridge-acp/src/bridge/model-catalog.test.ts — catalog keeps the per-option description (17 files / 301 tests green).
  • packages/provider-bridge-acp/src/wire.test.ts — schema returns description and normalizes nullundefined.
  • apps/app/src/components/pickers/model-picker-option.test.ts — grouping, ≥2-route header gate, within-group qualifiers, trigger token, display-name map.
  • apps/app/src/components/pickers/ModelReasoningPicker.test.tsx — grouped headers, role="group" / aria-labelledby, raw-id tooltips, single-route lists stay flat, sticky offsets, unique-trigger shape, submenu + compact drawer grouping (65 tests across the two picker files).

Mutation-kills (reverted after): hasMultipleRouteGroupsreturn false fails the grouped-catalog test; title always undefined fails getByTitle("zai/glm-5.3"); qualifyCollidingLabels early return new Map() fails the within-provider qualifier test.

Commands (rebased onto 688eb42, then pnpm install --frozen-lockfile): pnpm exec turbo run typecheck lint test --filter=@bb/provider-bridge-acp --filter=@bb/app --filter=@bb/mobile11/11 tasks green. New code carries no comments, per the repo-wide bb(no-comments) rule.

Manual: Ladle pickers--model-reasoning-picker--overview, "open: acp grouped" — pretty headers (Cursor / Z.ai / CommandCode / Mistral), collision tails, full-id tooltips, role=group.

Fixes #2062

AGENT GENERATED: by GLM-5.3 (Z.ai)

@bradhallett

Copy link
Copy Markdown
Contributor Author

cc @SawyerHood @ymichael — re-review ping; this one has had no maintainer feedback since it opened.

Where it stands: bridge fix (type + map the per-option description) plus the provider-grouped desktop picker; 11/11 turbo tasks green on the latest rebase (bridge, app, mobile). Fixes #2062, whose repro report already confirmed the root cause; the REQUEST CHANGES items from the #2063 review (always-on second line, centered-text regression) are incorporated — qualifiers appear only on colliding rows and single-provider pickers stay byte-identical to today.

If the grouped-headers desktop treatment isn't the direction you want, the bridge half is still worth taking on its own — it's two lines and every surface that renders AvailableModel.description benefits. Happy to split or rework the UX either way.

ACP agents such as omp advertise the same display name under multiple
providers. Type description on the select-option schema, keep it through
the catalog, and group the desktop picker by route — headers only when
two or more providers are present.
@bradhallett
bradhallett force-pushed the fix/2062-model-picker-provider-groups branch from 07ed492 to fde50c3 Compare September 3, 2026 17:40
@bradhallett

Copy link
Copy Markdown
Contributor Author

Rebase

  • Rebased onto f6868ad0c (upstream/main). Single commit preserved verbatim (git range-diff shows no lost commits).
  • ModelReasoningPicker.tsx conflict: main centralized picker search in picker-search.ts (Unify picker search matching #2943), so the local buildFuzzyRegex/fuzzyFilter/modelSearchText copies were dropped in favor of main's searchPickerOptions. The route-aware search intent now lives in its getAliases (route key + provider display name + value).
  • Test adaptation: "keeps keyboard selection working across group wrappers" now sends a second ArrowDown before Enter — main's ranked fuzzy search lists commandcode/zai-org/GLM-5 first for "glm" (shorter-label tiebreak), and the extra keypress keeps the selection crossing group wrappers to cursor/glm-5.3 as before.

Gates

  • pnpm exec turbo run typecheck lint test --filter=@bb/provider-bridge-acp --filter=@bb/app --filter=@bb/mobile — 11/11 tasks successful; @bb/app 3884 passed / 4 skipped, @bb/provider-bridge-acp 312 passed.
  • pnpm exec oxfmt --check on every touched file — clean.
  • node packages/plugin-sdk/scripts/check-npm-version-guard.mjs — FAIL (bridge dist changed) → bumped @get-bb/plugin-sdk 0.4.40 → 0.4.41 in a separate commit → PASS. Note: Bound and clear the ACP compaction prose buffer #3013 also claims 0.4.41 while unpublished; whichever merges second will need a re-bump.

AGENT GENERATED

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ACP bridge drops model descriptions, so the model picker shows indistinguishable duplicate rows

1 participant