Skip to content

feat(pi): offer Pi's thinking levels in Ask AI - #1704

Merged
backnotprop merged 4 commits into
backnotprop:mainfrom
josdirksen:feat/pi-ask-ai-thinking-levels
Oct 5, 2026
Merged

backnotprop merged 4 commits into
backnotprop:mainfrom
josdirksen:feat/pi-ask-ai-thinking-levels

Conversation

@josdirksen

@josdirksen josdirksen commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Ask AI's Pi provider now has a thinking-level picker, and the level you pick reaches the Pi session.

What it does

  • The levels come from each model, as reported by your installed pi. They are read from each get_available_models row (reasoning + thinkingLevelMap) with pi's own rule (getSupportedThinkingLevels, mirrored by piSupportedThinkingLevels in packages/core/model-catalog.ts):

    • off through high are offered unless the model's map sets that level to null.
    • xhigh and max are offered only when the model's map names them.
    • A non-reasoning model gets no picker.

    The endpoint's effort validation already checks against each model's own list, so a level the selected model does not list is never sent.

  • The level is applied after the model. The session sends set_thinking_level after set_model, because the levels a model accepts depend on the model. With nothing picked, the bar reads Auto, nothing is sent, and pi keeps your own default. Pi does not report a default level, so the catalog does not claim one. The code-review Ask AI bar no longer shows the first listed level as if it were active, and it has an Auto item.

  • Levels need pi 0.84.3 or newer. Before pi 0.84.3 (pi commit 2ff8ba622), RPC set_thinking_level also rewrote the user's global default thinking level in pi's settings. The provider now runs pi --version alongside model discovery. It offers and sends levels only on pi >= 0.84.3. On an older pi, or when the version cannot be read, there is no picker and nothing is sent. The version is kept as the provider's toolVersion. A session that wants a level before discovery has run uses the same probe, so pi --version runs once. Pre-0.84.3 set_model has the same global-persistence behaviour. A code comment notes it, and this PR does not change it.

  • Model labels read Name (provider), like OpenCode's. Two providers that serve the same model stay distinguishable. If two rows would still have the same label, both fall back to provider/id. The review/guide launchers' Pi picker is not affected, because it reads pi --list-models separately.

  • The launcher's PI_THINKING is now built from core's PI_THINKING_LEVELS and is no longer a second hand-written list. Its behaviour is unchanged: it still leaves out max, which --thinking accepts only on pi 0.80.6 and newer.

Tests

  • packages/ai/providers/pi-sdk-models.test.ts: a fake pi (--version + --mode rpc), run against both the Bun and Node providers. It covers:
    • per-model levels, including max and a model with no off;
    • Name (provider) labels;
    • the level sent after set_model;
    • nothing sent on Auto;
    • on pi 0.84.2, no levels offered and none sent, even when a level arrives.
  • packages/core/model-catalog.test.ts: piCatalogFromRpc (level derivation, label collisions) and the version gate.
  • bun test packages/ai packages/core apps/pi-extension: all pass. DOM_TESTS=1 runs of review-editor and ui pass, except four DocBadges tests that fail only in the combined run. That file is not changed here, and it passes when run alone. bun run typecheck (re-vendors Pi, including the new pi-version.ts) passes.

The Pi provider reported no reasoningEfforts, so Ask AI showed no thinking
dropdown for it and a chosen level never reached the runtime.

- piSupportedThinkingLevels (core/model-catalog) derives a model's levels
  from the get_available_models row's `reasoning` and `thinkingLevelMap`,
  mirroring the runtime's own getSupportedThinkingLevels: off through high
  unless the map nulls the level, xhigh and max only when mapped, nothing
  for a non-reasoning model. The picker therefore never offers a level Pi
  would clamp away. EFFORT_LABELS gains the missing `off`.
- Both providers (Bun and Node) emit reasoningEfforts, with
  defaultReasoningEffort 'medium' when the model takes it: Pi's own default,
  matching the review/guide launcher convention.
- The session carries options.reasoningEffort and sends set_thinking_level
  after set_model, because the levels a model accepts depend on the model.
  With no level chosen nothing is sent, so Pi keeps its configured default.
- Model labels are the canonical `provider/id` rather than the CLI display
  name, so two providers exposing the same model name no longer render as
  identical rows in the picker.

No server, endpoint or UI change was needed: CatalogModel.reasoningEfforts
and the effort validation already existed for Claude and Codex.

Verified with a fake `pi --mode rpc` in pi-sdk-models.test.ts (6 pass, both
runtimes) covering labels and levels, the level sent after the model, and
the level left alone when none was chosen. The derivation was also
cross-checked against the live runtime's get_available_thinking_levels for
12 sampled models: 10 reasoning models matched exactly, and the 2
non-reasoning ones deliberately get no dropdown where the runtime answers
["off"].
Review feedback on the branch: the model-independent list is what the
review/guide launcher already does, so the per-model derivation goes away.

- PI_THINKING_LEVELS is the shared knob: off, minimal, low, medium, high,
  xhigh, in Pi's own order. That is the CLI's documented `--thinking` set and
  the same list the launcher's picker offers (PI_THINKING in AgentsTab).
  piSupportedThinkingLevels and the thinkingLevelMap handling are removed.
- reasoning stays the caller's check: both providers emit reasoningEfforts
  only when the discovery row reports the model reasons, so a non-reasoning
  model still gets no dropdown.
- A level the selected model rejects is clamped silently by Pi
  (off -> minimal, xhigh/max -> high on a model that nulls them), which is
  the launcher's existing behavior; the picker therefore offers the knob's
  full range and lets the runtime resolve it. `max` stays out for the same
  reason it is out there.
- pi-sdk-models.test.ts is removed per review.
@josdirksen
josdirksen force-pushed the feat/pi-ask-ai-thinking-levels branch from 61ce495 to f954c39 Compare October 5, 2026 09:07
…ssion-scoped pi

- Levels come from each get_available_models row again (piSupportedThinkingLevels,
  mirroring pi-ai's getSupportedThinkingLevels): no Off where the map nulls it,
  xhigh and max only where the model maps them, none for a non-reasoning model.
  The endpoint's effort validation reads these per-model lists unchanged.
- Version gate: before pi 0.84.3 (pi 2ff8ba622) RPC set_thinking_level also
  rewrote the user's global default thinking level. The provider now runs
  `pi --version` alongside discovery (kept as toolVersion; a session that
  wants a level shares the same probe) and only offers and sends levels on
  pi >= 0.84.3. Older or unknown versions: no picker, nothing sent.
  set_model has the same pre-0.84.3 behaviour; noted in a comment, unchanged.
- No defaultReasoningEffort: pi's default level is the user's own setting,
  which the RPC does not report, so an unpicked level is Auto and nothing is sent.
- Labels read "Name (provider)" like OpenCode's, falling back to provider/id
  only where two rows would still collide.
- Restores the fake `pi` test for both runtimes (per-model levels incl. max
  and no-off, level sent after set_model, nothing on Auto, version gate), plus
  catalog unit tests.
…fort; one Pi level list

- AIConfigBar no longer shows the first listed effort when nothing is picked
  and the model reports no default (nothing would be sent). It reads Auto and
  offers an Auto item to go back to the provider's own default (Pi).
- The launcher's PI_THINKING is derived from core's PI_THINKING_LEVELS instead
  of a second hand list (still without max, which --thinking only accepts
  since pi 0.80.6).
@josdirksen
josdirksen marked this pull request as ready for review October 5, 2026 09:19
@backnotprop
backnotprop merged commit 94278af into backnotprop:main Oct 5, 2026
24 checks passed
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.

2 participants