Skip to content

feat(webui): model selector upgrade — provider-aware groups, thinking effort, modality badges - #38

Merged
fengzhi09 merged 1 commit into
mainfrom
feat/model-selector-thinking-modality
Sep 26, 2026
Merged

fengzhi09 merged 1 commit into
mainfrom
feat/model-selector-thinking-modality

Conversation

@fengzhi09

Copy link
Copy Markdown
Collaborator

What

The final slice of the provider-configuration feature.

  • Provider-aware selector: groups with auth.hasKey === false render greyed with a config hint (clicks no-op); modality badges (文本/图像/文件) per model row.
  • Thinking effort: selecting a model with thinkingLevels mounts a level picker; levels map 1:1 to the engine's thinkingEffort config option with the engine's model-first-then-effort ordering enforced in both handleSetModel and applyRecordedModel; the engine's config_option_update mirrors back into cs.model.thinking. Pre-session record round-trips (currentThinking).
  • Persistence: model + effort survive refresh / new sessions; switching semantics unchanged (idle → immediate, running → next turn).

Acceptance (independent agent)

PASS-WITH-CONCERNS, engine contract verified LIVE in both directions: recorded "high" on a brand-new session hit the engine's advertisement check (Invalid params: Thinking effort is not advertised…) and degraded exactly as designed (logged, engine default stands, currentThinking reflects engine truth); "medium" accepted mid-session and round-tripped. First-turn guard / applyRecordedModel ticket-01 semantics intact. The "6 pre-existing failures" claim was confirmed properly this time (clean main clone WITH deps: 6/6 fail on main, 6/6 pass on this branch — incidental fix). Gates: typecheck 0 · test:webapp 264/264 · test:unit 825/825 · build ✓ · check:source ✓.

Non-blocking: webui thinkingLevels metadata (preset-authored) can disagree with the engine's per-model effortOptions — the engine rejects unknown levels safely; surfacing rejected picks in the UI is a possible follow-up.

Full pnpm verify deferred to CI.

Ticket 04 — model selector upgrade (final slice of the
provider-configuration feature). Built on the schema (01), presets
(02), and management UI (03) already merged on `main`.

Server (engine ↔ webui contract):
- GET /api/models now surfaces `currentThinking` from the engine's
  `thinkingEffort` configOption (falling back to `cs.model.thinking`,
  then `null`) and routes the per-model `thinkingLevels` /
  `modalities` through the response — the new selector reads them
  directly without a second round-trip.
- POST /api/set-model accepts `{model?, thinking?}` independently:
  a thinking-only update leaves the model alone, a model-only update
  carries the recorded effort with it, and `thinking: ""` clears the
  recorded override. When a session exists, both pushes go through
  `session/set_config_option{configId: "thinkingEffort"}` — the
  engine's `thinkingEffort` configId (see packages/tui/src/acp/
  control-state.ts#ACP_CONFIG_THINKING_EFFORT) maps 1:1 to the
  webui level; the engine validates the level against the selected
  model's `effortOptions` and answers invalidParams on a mismatch,
  which the route surfaces as `thinkingSynced: false` + `warning`.
  Pre-session the recorded effort also flows through the same path on
  the next `applyRecordedModel`, which now pushes model first then
  effort (engine contract: "Select a Session model before changing
  thinking effort.", agent.ts#1003).
- applyConfigOptionUpdate propagates the engine's
  `thinkingEffort.currentValue` into `cs.model.thinking` so a TUI
  change propagates back through the SSE snapshot.
- state-bus default for `model.thinking` switched from the cosmetic
  "On" to "" (the runtime "no override" sentinel); the snapshot is
  built through the existing `...cs` spread, so the new value rides
  on every push without further wiring.

Frontend (composer ModelSelect + ThinkingEffortSelect):
- ModelSelect now consumes the `groups[]` shape with `auth.{hasKey,
  type}` and a per-model `modalities[]`. Groups with `hasKey ===
  false` render greyed with a "configure in Settings" hint, models
  carry modality badges (text/image/audio/video/file → i18n). The
  engine session group (`__engine`) has no `auth` and stays usable.
- ThinkingEffortSelect is mounted next to the model selector only
  when the active model declares a non-empty `thinkingLevels`; the
  menu enumerates exactly the model's levels plus an "engine
  default" option (sends `thinking: ""`). Disabled while a run is
  active — the documented "running → next turn" semantic.
- Chip label appends `· <level>` when `cs.model.thinking` is set.
- composer.tsx wires both selectors through the new
  `api.setModel({model, thinking})` payload, threading the recorded
  effort across mid-session model changes.

Tests:
- model.check.mjs: handleGetModels surfaces `currentThinking`
  (engine / pre-session / null paths); handleSetModel persists,
  pushes, and surfaces `thinkingSynced` for the 6 new branches
  (both, model-only, thinking-only, clear, empty-payload 400,
  engine rejection, in-place configOptions mirror).
- mcode-acp-note.test.js: applyRecordedModel for the 6 new effort
  paths (both / effort-only / model-only / empty / no modelOption /
  engine-rejected-effort) plus applyConfigOptionUpdate for the 3 new
  propagation paths.
- composer-models.test.ts: 11 new tests covering `isGroupDisabled`,
  `modalityBadgeKey`, `thinkingLevelKey`, and the setModel payload
  shape the composer sends (model+effort together, model-only,
  effort-only, clear).

Gates: webapp typecheck 0 errs, webapp tests 264/264 pass (+11),
unit tests 825/825 pass (no regressions), `pnpm build` green,
`pnpm check:source` 4585 files clean.

Live self-check (isolated 18108/18109, own data dir, fresh
providers.json with one byok + two no-key providers): open model
selector → no-key groups greyed with "请在设置中配置 API Key" hint,
modality badges visible on every model row, pick `openai_compat/
gpt-4o` (thinkingLevels [low, medium, high]) → effort picker mounts,
pick "高" → chip becomes "GPT-4o · 高", reload → both selections
round-trip via `currentThinking` (engine-side pre-session record).
Screenshots under /tmp/dev-ms/shot-ms{1,2,3}-*.png.
@fengzhi09
fengzhi09 merged commit 67ad609 into main Sep 26, 2026
8 checks passed
@fengzhi09
fengzhi09 deleted the feat/model-selector-thinking-modality branch September 26, 2026 10:13
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.

1 participant