Skip to content

feat(web): add a Pi-native thinking-level selector to the composer - #531

Open
Matt-qwq wants to merge 1 commit into
openpi-dev:mainfrom
Matt-qwq:feat/web-thinking-selector
Open

feat(web): add a Pi-native thinking-level selector to the composer#531
Matt-qwq wants to merge 1 commit into
openpi-dev:mainfrom
Matt-qwq:feat/web-thinking-selector

Conversation

@Matt-qwq

@Matt-qwq Matt-qwq commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Problem

The Web workbench can inspect the reasoning/thinking state but cannot change it. InspectionPanel renders the current level and the available set read-only, and the composer's model picker only changes the model. There is no way to raise or lower reasoning effort for the active Session without leaving the workbench. This is the unmet change thinking/reasoning level half of #348: #375/#444 already landed read-only GET /api/thinking inspection with an explicit unknown fallback; the setter and the UI control are still missing.

Related to #348 (advances the thinking/reasoning criterion; does not close it), #76 (Web workbench roadmap), #455 (Web UI/UX task), and #195 (Pi owns reasoning state). Continues #375 / #444 and credits seekskyworld for the read-only inspection slice.

Value

Approach

  • Protocol (web/protocol/types.ts): new WebThinkingState { level, available, supported, revision }; optional thinking on WebSnapshot. Protocol version unchanged (optional field).
  • Runtime (web/runtime/pi-runtime.ts): getThinkingState() gains supported; new setThinkingLevel(level, { expectedSessionId }) runs through serializeControllerMutation (synchronous Pi setter, no retained runtime reference) and enforces a non-configurable merge-to-latest bound (≤1 in flight + 1 pending; every waiter resolves to the final authoritative projection or its own 409 SESSION_CONFLICT).
  • Adapter (web/adapter/pi-adapter.ts): fail-open safeThinking() — a throwing getter omits thinking and never breaks /api/snapshot; the projection is bounded (≤16 levels, each ≤500 chars).
  • Host (web/host/web-host.ts): POST /api/thinking placed before the non-GET fallback and made lease-sensitive (400 invalid body, 409 WORKSPACE_REQUIRED/SESSION_CONFLICT, 501 unavailable, THINKING_LEVEL_NOT_AVAILABLE for unsupported models). GET /api/thinking keeps feat(web): integrate terminal details, thinking state and unarchive #444's shape and never-500 unknown fallback; both responses now share the host-bound projection helper and carry the monotonic host revision.
  • Store (web/ui/src/store/web-store.ts): last-intent-wins single-flight flush; a monotonic revision gate (scoped to the Session epoch, reset on cursor reset) so a stale snapshot or reordered response can never regress the level; thinking_level_changed is applied as a local revision-gated patch rather than forcing a full snapshot refresh.
  • UI (Composer.tsx, Transcript.tsx, InspectionPanel.tsx, i18n.ts, styles.css): icon-only picker beside the model picker with a section title, current-level check, disabled-reason tooltip, pending visual, and a mismatch warning. The level is always present in the accessible name, and the check/data-level are redundant signals (WCAG 1.4.1).
  • Non-goals: per-turn historical level annotation, global default persistence, a server-side admission cap or user-visible concurrency knob, and any new /openpi-setup configuration contract.

Validation

bun run check            # config contract, discipline ledger, UI tsc + Vite build, format, lint, tsc — all green
bun run test             # Node 1544 pass / 0 fail / 1 skip; Vitest 164 pass
bun run test:web:e2e     # 22 passed: 11 existing + 11 thinking-picker browser matrix, axe clean
bun run test:web:e2e:provider  # 1 passed: hermetic end-to-end provider proof
  • Committed web/dist was rebuilt from source and bun run check leaves it clean (CI's stale-build guard).
  • The provider e2e seeds a local fake OpenAI-compatible model (models.json) and asserts the real backend request body carries reasoning_effort: "high" after selecting high in the browser, and omits the field for off; it also asserts the picker is disabled while a turn runs and the read path still reports high after reload.
  • The browser state matrix covers open/section/check, zero-write on the confirmed level, one-write on change, keyboard open/arrow/enter/escape, last-intent-wins while a write is in flight, failed write → notice + GET /api/thinking reconcile, 501 → notice, out-of-set mismatch warning, running lock, narrow-viewport overflow, and axe.
  • Real-browser manual evidence captured on a live workbench with real models (DeepSeek V4.1 Flash and GLM 5.3 Flash): placement beside the model picker, the per-level icon colors (off/low/medium/high/max), and the opened menus for both models (GLM exposes medium, DeepSeek exposes off).
  • Not run/claimed: installed-package (npm pack) acceptance and the native TUI. No merge-tree check against a newer main was needed because the branch is based on main@5bf2fe2; it will be rebased if main moves.

Real-browser evidence (live workbench, real models)

Captured from a running workbench with real models; each dropdown was captured after its animation settled. The picker's Brain icon is tinted by the active level. medium is offered only by GLM 5.3 Flash and off only by DeepSeek V4.1 Flash — each model exposes exactly the levels its thinkingLevelMap allows, so the two menus differ by design.

Placement — right of the model picker, left of send:

Toolbar placement

Icon color by level (off from DeepSeek; low/medium/high/max from GLM):

off — grey
low — blue
medium — green/teal
high — orange
max — red

Opened menus:

GLM menu — low/medium/high/max, medium present
DeepSeek menu — off/low/high/max

Impact

  • User-visible behavior: adds a thinking-level picker to the composer; disabled placeholder with a reason for unsupported/unselected models; disabled while a turn runs.
  • Model-visible context/tools: changes the reasoning effort carried on the next provider request (proven by the provider e2e); no new model-facing tools or schemas.
  • Runtime/lifecycle: adds a synchronous thinking setter and one lease-sensitive POST route; no retained runtime references, no persistence.
  • Persisted config/data: None — session-only, no new setup field or preference source.
  • Compatibility/risk: protocol adds an optional field without bumping the version; stale snapshots are rejected by the monotonic revision; there is intentionally no server-side rate limit (client is single-flight), documented as a known gap.

@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Sep 10, 2026
@Matt-qwq
Matt-qwq force-pushed the feat/web-thinking-selector branch from 109e352 to 23b6b73 Compare September 10, 2026 20:11
@github-actions github-actions Bot added the area:github GitHub workflows, templates, ownership, or tests label Sep 10, 2026
@Matt-qwq
Matt-qwq force-pushed the feat/web-thinking-selector branch 3 times, most recently from 2f9351e to 8106646 Compare September 11, 2026 00:20
@github-actions github-actions Bot removed the documentation Improvements or additions to documentation label Sep 11, 2026
Expose Pi's native thinking/reasoning level in the Web workbench: a
runtime setter with a bounded merge-to-latest, a lease-sensitive
POST /api/thinking, a bounded optional thinking projection with a
monotonic revision on the snapshot, a last-intent-wins client store
with a revision gate, and an icon-only composer picker with a disabled
placeholder for unsupported models. Levels come from Pi's
getAvailableThinkingLevels()/supportsThinking(); OpenPI adds no second
reasoning store and no persisted configuration (session-only).

Advances the change-thinking-level criterion of openpi-dev#348 and continues the
read-only GET /api/thinking inspection from openpi-dev#375/openpi-dev#444, preserving its
unknown fallback. Adds browser coverage (22 e2e incl. an axe pass) and a
hermetic provider e2e that proves the selected level reaches the next
provider request as reasoning_effort.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:github GitHub workflows, templates, ownership, or tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant