@kingingwang/pi-windows-x64 0.99.2 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/CHANGELOG.md CHANGED
@@ -1,5 +1,105 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.0.2] - 2026-10-04
4
+
5
+ ### New Features
6
+
7
+ - **Sampling by thinking level** — `samplingParamsByThinkingLevel` in `models.json` sets sampling parameters such as `temperature` and `top_p` for each thinking level on OpenAI-compatible APIs. See [Configure sampling by thinking level](docs/models.md#configure-sampling-by-thinking-level).
8
+
9
+ ### Added
10
+
11
+ - Added `samplingParamsByThinkingLevel` to `models.json` for per-thinking-level sampling parameter overrides on OpenAI-compatible APIs. See [Configure sampling by thinking level](docs/models.md#configure-sampling-by-thinking-level) ([#9776](https://github.com/earendil-works/pi/pull/9776) by [@mrexodia](https://github.com/mrexodia))
12
+
13
+ ## [1.0.1] - 2026-10-03
14
+
15
+ ### New Features
16
+
17
+ - **Nix flake** — `nix run github:earendil-works/pi/stable` runs the latest release, and `nix profile add github:earendil-works/pi/stable` installs it. See [Install pi](docs/quickstart.md#1-install-pi).
18
+ - **Project overrides for MCP servers** — `.pi/mcp.json` and `/mcp` can enable, disable, or change the exposure of a user-level server for one project. See [Configure servers](docs/mcp.md#configure-servers).
19
+ - **MCP Client ID Metadata Documents** — `oauth.clientRegistration: "cimd"` lets authorization servers allow pi by its document URL instead of dynamic registration. See [Authenticate with OAuth](docs/mcp.md#authenticate-with-oauth).
20
+ - **Tool renderers for any tool** — `pi.registerToolRenderer()` draws calls to tools that are not registered yet, such as MCP tools in resumed sessions. See [Tool rendering](docs/extensions.md#tool-rendering).
21
+ - **Cloudflare Clef classifiers** — `@cf/cloudflare/clef` and `@cf/cloudflare/clef-flash` are usable from codemode scripts and extensions. See [Use classifier models](docs/models.md#use-classifier-models).
22
+
23
+ ### Added
24
+
25
+ - Added a copy key (`app.message.copy`, default `ctrl+x`) to OAuth sign-in screens in `/login`, `/mcp`, and `/mcp login`, which copies the sign-in URL when the browser cannot be opened or the wrapped link cannot be selected.
26
+ - Added `oauth.clientRegistration: "cimd"` for MCP servers, which identifies pi with its Client ID Metadata Document on pi.dev instead of dynamic client registration, so authorization servers can allow pi by URL ([#10302](https://github.com/earendil-works/pi/issues/10302))
27
+ - Added project overrides for user-level MCP servers: a `.pi/mcp.json` entry without `command` or `url` sets only `enabled`, `exposure`, and `toolExposure` of the user-level server, and `/mcp` can enable or disable a server for the current project ([#10277](https://github.com/earendil-works/pi/issues/10277))
28
+ - Added Cloudflare's Clef and Clef Flash classifier models to `cloudflare-workers-ai`, usable from codemode scripts and extensions ([#10316](https://github.com/earendil-works/pi/pull/10316) by [@ndisidore](https://github.com/ndisidore), [#10322](https://github.com/earendil-works/pi/pull/10322) by [@RealAlexandreAI](https://github.com/RealAlexandreAI))
29
+ - Added `pi.registerToolRenderer()`, which chooses how calls to a tool are drawn, including tools that are not registered ([#10285](https://github.com/earendil-works/pi/issues/10285))
30
+ - Added a Nix flake for macOS and Linux: `nix run github:earendil-works/pi/stable` runs the latest release, and `nix profile add github:earendil-works/pi/stable` installs it. See [Install pi](docs/quickstart.md#1-install-pi) ([#9137](https://github.com/earendil-works/pi/pull/9137))
31
+
32
+ ### Changed
33
+
34
+ - `pi update` on global npm installations now recommends migrating to the managed installation from the pi.dev installer, which pins all dependencies.
35
+ - Anthropic tools added or redefined mid-conversation are now defined inline in the conversation, so redefining a tool under the same name keeps the prompt cache instead of resending the full tool list.
36
+
37
+ ### Fixed
38
+
39
+ - Fixed installations resolving vulnerable `brace-expansion` 5.0.9 by pinning `brace-expansion` 5.0.12 as a direct dependency (GHSA-q2hr-2g5m-vwhr, GHSA-qhr7-859c-m2p7, GHSA-6j4f-fj2g-mc7p) ([#10288](https://github.com/earendil-works/pi/issues/10288))
40
+ - Fixed a trailing comma in `--models` adding an extra model to the model cycle ([#10334](https://github.com/earendil-works/pi/issues/10334))
41
+ - Fixed a `codemode` script that prints in a loop crashing pi by running out of memory: a script fails once its output passes 16 Mi characters or 100000 items ([#10283](https://github.com/earendil-works/pi/issues/10283))
42
+ - Fixed JPEG, GIF, and WebP images rendered by extensions through `Image` not appearing in Kitty, Ghostty, WezTerm, and Warp ([#10292](https://github.com/earendil-works/pi/issues/10292))
43
+ - Fixed MCP tool calls in resumed sessions and HTML exports rendering fully expanded until their server connected, or for good if it never did ([#10285](https://github.com/earendil-works/pi/issues/10285))
44
+ - Fixed fullscreen Kitty images collapsing to a one-row strip after scrolling in WezTerm ([#10319](https://github.com/earendil-works/pi/issues/10319))
45
+ - Fixed "Selected model is at capacity" provider errors ending the turn instead of being retried ([#10278](https://github.com/earendil-works/pi/issues/10278))
46
+ - Fixed Cloudflare AI Gateway Claude models failing with a 404 by using dashed model IDs (`claude-opus-5-5` instead of `claude-opus-5.5`)
47
+ - Fixed Sign in with ChatGPT continuing when its callback port is taken by another login, which made the browser show "OAuth state mismatch"; it now fails with a port-in-use error ([#10265](https://github.com/earendil-works/pi/issues/10265))
48
+ - Fixed Amazon Bedrock OpenAI models costing requests above 272k input tokens at the short-context rate; Bedrock models now include the pricing tiers listed on models.dev ([#10326](https://github.com/earendil-works/pi/issues/10326))
49
+ - Fixed Amazon Bedrock Claude requests failing with "Invalid `signature` in `thinking` block" after the system prompt or tools changed ([#10324](https://github.com/earendil-works/pi/issues/10324))
50
+ - Fixed Together DeepSeek V4 Pro losing its thinking level controls after Together renamed it to `deepseek-ai/DeepSeek-V4-Pro-0813` ([#10336](https://github.com/earendil-works/pi/pull/10336) by [@cv](https://github.com/cv))
51
+ - Fixed the default NVIDIA model pointing at `nvidia/nemotron-3-super-120b-a12b`, which NVIDIA no longer serves; the default is now `nvidia/nemotron-3-ultra-550b-a55b`
52
+
53
+ ### Removed
54
+
55
+ - Removed `npm-shrinkwrap.json` from the published package. npm installations no longer pin transitive dependencies, and library consumers can now override them. Use the pi.dev installer for pinned installations ([#5653](https://github.com/earendil-works/pi/issues/5653))
56
+
57
+ ## [1.0.0] - 2026-10-01
58
+
59
+ ### New Features
60
+
61
+ - **Fullscreen by default** — The TUI now runs fullscreen. Set `tuiMode` to `"regular"` to keep the terminal's normal scrollback. See [Terminal and display](docs/settings.md#terminal-and-display).
62
+ - **Leaner codemode** — About 40% fewer prompt tokens, and errors that tell the model how to recover. See [Codemode](docs/codemode.md).
63
+ - **Image generation in codemode** — Scripts call `models.generateImages()` with the session's credentials. See [Generate images](docs/codemode.md#generate-images) and [Use image models](docs/models.md#use-image-models).
64
+ - **Radius in `/login`** — Sign in with Radius and set up its MCP server in one step. See [Radius](docs/providers.md#radius).
65
+ - **Anthropic copy code login** — Sign in when the browser runs on another machine. See [Authenticate interactively](docs/providers.md#authenticate-interactively).
66
+ - **MCP OAuth hardening** — `oauth.authServerMetadataUrl`, RFC 9207 `iss` checks, credentials per server, and step-up sign-in that keeps granted scopes. See [Authenticate with OAuth](docs/mcp.md#authenticate-with-oauth).
67
+ - **Header-only quiet startup** — `quietStartup: "header"` keeps the version and key hints and hides the rest. See [Terminal and display](docs/settings.md#terminal-and-display).
68
+
69
+ ### Added
70
+
71
+ - Added an `oauth.authServerMetadataUrl` setting for MCP servers that advertise a wrong OAuth authorization server or none. Pi uses the configured metadata document instead of discovery ([#10172](https://github.com/earendil-works/pi/issues/10172)).
72
+ - Added `quietStartup: "header"`, which keeps the startup header with version and key hints but hides the model scope line and loaded-resource listing.
73
+ - Added `models.generateImages()` to codemode scripts. It runs image models such as OpenRouter's with the session's credentials and returns base64 image blocks that `image()` attaches to the result; usage counts toward the session cost like `models.classify()`. Extensions can call `ctx.modelRegistry.generateImages()`. See [Use image models](docs/models.md#use-image-models).
74
+ - Added a copy code login method to Anthropic `/login` for headless setups where the browser runs on another machine ([#10194](https://github.com/earendil-works/pi/pull/10194) by [@lucasmeijer](https://github.com/lucasmeijer)).
75
+
76
+ ### Changed
77
+
78
+ - Changed the default TUI mode to fullscreen. Set `tuiMode` to `"regular"` or pass `--tui-mode regular` to keep the terminal's normal scrollback.
79
+ - `/login` now offers "Sign in with Radius" at the top level, as the last option, with its status. After a Radius sign-in, `/login` offers to configure the Radius MCP server in the global `mcp.json` with `"auth": { "provider": "radius" }` and reloads. Cancelling a login returns to the menu it was started from.
80
+ - The provider docs page is renamed to [Providers](docs/providers.md), its "Cloud Providers" section is now "Provider Specific Config", and it documents Radius first.
81
+ - MCP OAuth credentials are now stored per server name and URL, so MCP servers with the same URL can sign in with different accounts. Credentials stored by URL alone move to the first server that uses them ([#10252](https://github.com/earendil-works/pi/issues/10252)).
82
+ - Codemode costs far fewer prompt tokens: with the default tools and codemode active, a GPT-5.6 request shrinks from about 5,300 to 3,300 tokens. The `codemode` description lists the script globals in one line each and points to the new [Codemode](docs/codemode.md) reference for the `models` API, which the model reads when it needs it. Declared tools say in one line how scripts call them and what the call resolves to, instead of repeating their full declaration, and the system prompt's codemode guidance and MCP server section are shorter.
83
+ - Codemode errors now say how to recover: reading a tool or `models` member that does not exist names the close matches (`tools.Bash` suggests `tools.bash`), `models.classify()` and `models.generateImages()` reject malformed arguments with the expected shape, an unknown model points to `models.getAvailableOfType()`, an oversized `store()` value explains what the store is for, and a script that generates images without showing them gets a note. Scripts that probed for a tool with `typeof tools.name` must use `"name" in tools`.
84
+ - `/login` and `/logout` now label providers without credentials as "not configured" instead of "unconfigured".
85
+ - OAuth browser pages now show the color Pi logo.
86
+
87
+ ### Fixed
88
+
89
+ - Fixed MCP OAuth sign-in accepting an authorization response whose `iss` parameter names another authorization server; the code is now rejected before it is exchanged (RFC 9207).
90
+ - Fixed MCP OAuth sign-in failing with `Invalid scope` when the token response contains `"scope": ""`, and similar failures for other empty or `null` optional OAuth fields ([#10266](https://github.com/earendil-works/pi/issues/10266)).
91
+ - Fixed the sign-in URL printed by `/mcp login` not being clickable when it wraps ([#10186](https://github.com/earendil-works/pi/issues/10186)).
92
+ - Fixed `--provider` without `--model` being silently ignored and running the default model from another provider; it now fails with an error ([#10236](https://github.com/earendil-works/pi/issues/10236)).
93
+ - Fixed MCP servers that ask for more scope (`insufficient_scope`) requesting sign-in over and over. The new sign-in requested only the missing scopes, so the new token lost access the previous one had; it now keeps the granted scopes.
94
+ - Fixed user messages in the transcript keeping two full-width copies of every rendered line; they keep one, with identical output.
95
+ - Fixed `/login` and `/logout` labeling every OAuth sign-in, including Radius, as a subscription; only subscription-backed providers say "subscription", other OAuth sign-ins say "account".
96
+ - Fixed the startup header logo rendering with gaps in Apple Terminal; it now shows a colored "Pi" with the version instead.
97
+ - Fixed deferred MCP tools that `tool_search` loaded being dropped on resume and `/reload` even when their server reconnected before the next prompt, because the session restored its tools before the MCP servers reconnected.
98
+ - Fixed the system theme making pastel palettes such as Catppuccin Frappe much more vivid; palette colors now keep their chroma ([#10255](https://github.com/earendil-works/pi/issues/10255), [#10293](https://github.com/earendil-works/pi/pull/10293) by [@dgtlntv](https://github.com/dgtlntv)).
99
+ - Fixed slash command autocompletion not triggering when the input starts with whitespace ([#10218](https://github.com/earendil-works/pi/pull/10218) by [@haoqixu](https://github.com/haoqixu)).
100
+ - Fixed color bleeding past mouse selections and search highlights in fullscreen mode when a styled token ends at the highlight boundary ([#10169](https://github.com/earendil-works/pi/issues/10169)).
101
+ - Fixed memory retained per rendered message in the transcript; a long assistant message keeps about a fifth of the heap it kept before.
102
+
3
103
  ## [0.99.2] - 2026-09-30
4
104
 
5
105
  ### New Features
package/bin/README.md CHANGED
@@ -12,26 +12,38 @@
12
12
 
13
13
  # Pi
14
14
 
15
- Pi is a minimal, extensible AI agent for the terminal. Adapt Pi to your workflow, not the other way around.
15
+ Pi is a minimal, extensible agent harness that you can make your own.
16
16
 
17
- Ask Pi to create the prompt templates, skills, extensions, and themes you need, or install a Pi package. Use Pi directly, automate it in print, JSON, or RPC mode, or build applications with the TypeScript SDK.
17
+ Adapt Pi to your workflows, not the other way around. Customize Pi with [extensions](docs/extensions.md), [skills](docs/skills.md), [prompt templates](docs/prompt-templates.md), and [themes](docs/themes.md). Bundle them as [Pi packages](docs/packages.md) and share via npm or git.
18
+
19
+ Pi ships with powerful defaults but skips features like sub-agents and plan mode. Ask Pi to build what you want, or install a package that does it your way.
20
+
21
+ Use Pi [interactively](docs/usage.md), automate it in [print or JSON mode](docs/cli.md), control it over [RPC](docs/rpc.md), or build apps with the [Pi TypeScript SDK](docs/sdk.md). See [OpenClaw](https://github.com/OpenClaw/OpenClaw) for a real-world integration.
18
22
 
19
23
  ## Getting started
20
24
 
21
- Install the command-line interface with npm:
25
+ Install the command-line interface:
22
26
 
23
27
  ```bash
24
- npm install -g --ignore-scripts @earendil-works/pi-coding-agent
28
+ curl -fsSL https://pi.dev/install.sh | sh
25
29
  ```
26
30
 
27
- This requires Node.js 22.19 or newer. Pi does not require dependency lifecycle scripts for a normal npm installation.
31
+ On Windows:
28
32
 
29
- On macOS or Linux, you can instead use the installer:
33
+ ```shell
34
+ powershell -c "irm https://pi.dev/install.ps1 | iex"
35
+ ```
36
+
37
+ The installer pins all dependencies and updates Pi with `pi update`. Alternatively, install directly with npm, which does not pin transitive dependencies:
30
38
 
31
39
  ```bash
32
- curl -fsSL https://pi.dev/install.sh | sh
40
+ npm install -g --ignore-scripts @earendil-works/pi-coding-agent
33
41
  ```
34
42
 
43
+ Pi requires Node.js 22.19 or newer. The macOS, Linux, and Windows installers can install it if needed. Pi does not require dependency lifecycle scripts for a normal npm installation.
44
+
45
+ On macOS and Linux, Nix users can install the latest release with `nix profile add github:earendil-works/pi/stable`. See the [quickstart](docs/quickstart.md#1-install-pi) for updating and pinning releases.
46
+
35
47
  Start Pi in the directory where you want it to work:
36
48
 
37
49
  ```bash
@@ -43,6 +55,19 @@ For a built-in AI provider, run `/login` inside Pi to connect a subscription or
43
55
 
44
56
  See the [documentation](docs/index.md) for full setup and usage instructions.
45
57
 
58
+ ## Share your OSS coding agent sessions
59
+
60
+ If you use Pi for open source work, please share your coding agent sessions.
61
+
62
+ Public OSS session data helps improve models, prompts, tools, and evaluations using real development workflows.
63
+
64
+ For the full explanation, see [this post on X](https://x.com/badlogicgames/status/2037811643774652911).
65
+
66
+ To publish sessions, use [`badlogic/pi-share-hf`](https://github.com/badlogic/pi-share-hf) using a Hugging Face account and the Hugging Face CLI.
67
+
68
+ - [Demo video](https://x.com/badlogicgames/status/2041151967695634619) on how to publish Pi sessions
69
+ - Published Pi development sessions: [`badlogicgames/pi-mono` on Hugging Face](https://huggingface.co/datasets/badlogicgames/pi-mono).
70
+
46
71
  ## Development
47
72
 
48
73
  Clone the repository, install its dependencies, and run Pi from source:
package/bin/docs/cli.md CHANGED
@@ -58,10 +58,10 @@ RPC mode rejects `@file` arguments. JSON and RPC modes reserve stdout for protoc
58
58
  pi --model sonnet:high
59
59
  ```
60
60
 
61
- See [Choose a Model](models.md) for model selection and [Provider Authentication](providers.md) for credentials.
61
+ See [Choose a Model](models.md) for model selection and [Providers](providers.md) for credentials.
62
62
 
63
63
  - `--provider <name>`<br>
64
- Restricts `--model` lookup to one provider.
64
+ Restricts `--model` lookup to one provider. It requires `--model`.
65
65
  - `--model <pattern>`<br>
66
66
  Selects by exact ID or fuzzy ID/name match. It accepts `provider/id` and an optional `:<thinking>` suffix.
67
67
  - `--api-key <key>`<br>
@@ -161,21 +161,11 @@ This keeps `read`, `bash`, `edit`, and `write` and adds `codemode`. For one invo
161
161
  pi --tools read,bash,edit,write,codemode
162
162
  ```
163
163
 
164
- Codemode is useful without MCP: scripts can run several tool calls in parallel, filter large output before it reaches the model, and call classifier models such as TypeSafe's Jev through `models.classify()` (see [Classifier models](models.md#use-classifier-models)).
164
+ Codemode is useful without MCP: scripts can run several tool calls in parallel, filter large output before it reaches the model, call classifier models such as TypeSafe's Jev through `models.classify()` (see [Classifier models](models.md#use-classifier-models)), and generate images through `models.generateImages()` (see [Image models](models.md#use-image-models)).
165
165
 
166
166
  ### How codemode works
167
167
 
168
- Codemode scripts run in a QuickJS sandbox that can only reach the other tools, through `tools.<name>(args)`; `ALL_TOOLS` lists them. Output comes from `text(value)`, `image(dataUrlOrImageContent)`, `console.*`, and a top-level `return value`; `exit()` ends the script early. The result starts with `Script completed` or `Script failed`, the wall time, and the output; a failed script keeps its partial output, followed by `Script error:` and the error.
169
-
170
- A script may start with an options line such as `// @options: {"max_output_tokens": 2000, "timeout_ms": 60000}`. `max_output_tokens` (default 10000) limits the output: longer output keeps its start and end, and the full text is written to a temp file whose path is included in the result. `timeout_ms` is a hard deadline, unset by default.
171
-
172
- While `codemode` is active, `codemode.mode` in [settings](settings.md#tools) decides how the other tools are presented. With `on` (default) declared tools keep being declared and their descriptions show how to call them from scripts. With `only` they are hidden from the model and listed in the `codemode` description instead, so the model calls them through scripts.
173
-
174
- The `codemode` description lists the callable tools with their TypeScript declarations, grouped by namespace (for example one MCP server). Tools with `deferred` exposure, which includes MCP tools with the default `codemode` exposure, are not listed and do not affect the description, so it stays the same while MCP servers connect. Declarations share a budget of 3000 estimated tokens (`codemode.inlineBudget` in [settings](settings.md#tools)). Scripts find the rest with `await searchTools(query, { limit, namespace })`, which ranks tools with BM25, and `await describeTool(name)`, or by filtering `ALL_TOOLS`. `await describeNamespace(name)` returns a namespace's description, its instructions (for MCP servers, the server instructions), and the names of its tools.
175
-
176
- Tools with an output schema resolve to structured values: `bash` to `{ output, truncated, full_output_path?, exit_code, wall_time_seconds }`, also for non-zero exit codes, and MCP tools to their `CallToolResult`. Other tools resolve to their text output. The `output` of `bash` is not limited to the 2000 lines or 50KB the model sees: it holds up to 1 MiB, and longer output keeps its first and last 512 KiB around an omission marker, with `truncated` set and the full output in `full_output_path`.
177
-
178
- `store(key, value)` and `load(key)` keep JSON values across `codemode` calls: each successful script that stores values appends a `codemode-store` custom entry to the session, so resumed sessions keep the values and each branch sees only the values written on its path. Scripts can also use `models`: `getModelsOfType`, `getAvailableOfType`, and `getModelOfType` list the model catalog, and `classify(model, context)` runs a classifier model with the session's credentials, at most four at a time per script.
168
+ Scripts run in a QuickJS sandbox and reach the other tools through `tools.<name>(args)`. [Codemode](codemode.md) describes the script API, how tools are listed and found, the `store()` and `models` globals, and the limits.
179
169
 
180
170
  ### Tool search
181
171
 
@@ -229,7 +219,7 @@ See [Configuration](configuration.md) for saved configuration, [Security](securi
229
219
  - `--append-system-prompt <text|path>`<br>
230
220
  Appends text or an existing file to the system prompt and is repeatable.
231
221
  - `--tui-mode <mode>`<br>
232
- Uses `regular` or `fullscreen` terminal mode.
222
+ Uses `fullscreen` (default) or `regular` terminal mode.
233
223
  - `--verbose`<br>
234
224
  Shows verbose interactive startup information, overriding `quietStartup`.
235
225
  - `-a`, `--approve`<br>
@@ -278,6 +268,8 @@ Running `pi update` without a target updates Pi itself.
278
268
 
279
269
  Add `--force` to reinstall Pi when the selected update includes Pi.
280
270
 
271
+ `pi update` cannot update Pi when another package manager provides it, such as Nix. Update Pi with that package manager, for example `nix profile upgrade pi`. Package and model catalog updates still work.
272
+
281
273
  ### Aliases and command options
282
274
 
283
275
  - `pi uninstall <source>` is an alias for `pi remove <source>`.
@@ -292,7 +284,7 @@ Add `--force` to reinstall Pi when the selected update includes Pi.
292
284
  pi auth check --provider openai --json
293
285
  ```
294
286
 
295
- Authentication commands require `--provider <provider>` or `--model <model>`. See [Provider Authentication](providers.md) for supported methods.
287
+ Authentication commands require `--provider <provider>` or `--model <model>`. See [Providers](providers.md) for supported methods.
296
288
 
297
289
  | Command | Description |
298
290
  |---|---|
@@ -0,0 +1,207 @@
1
+ # Codemode
2
+
3
+ The `codemode` tool lets the model write a JavaScript script that calls pi's other tools and runs non-LLM models, such as classifiers and image models. Only the script's output reaches the model, so a script can run calls in parallel and filter large results before the model sees them. To turn it on, see [Enable codemode](cli.md#enable-codemode).
4
+
5
+ ## Scripts
6
+
7
+ The tool input is raw JavaScript source, not JSON and not a markdown code fence. It runs as the body of an async function in a QuickJS sandbox, so top-level `await` and `return` work. The sandbox has no Node APIs, file system, network, or timers; scripts reach the outside world only through tools and `models`.
8
+
9
+ A script may start with an options line:
10
+
11
+ ```js
12
+ // @options: {"max_output_tokens": 2000, "timeout_ms": 60000}
13
+ ```
14
+
15
+ - `max_output_tokens` (default 10000) limits the output. Longer output keeps its start and end, and the full text is written to a temp file whose path is included in the result. A script fails when its output passes 16777216 characters of text and base64 image data or 100000 `text()`, `image()`, and `console` calls; write large data to a file with a tool instead.
16
+ - `timeout_ms` is a hard deadline for the whole script. It is unset by default. Image generation can take minutes, so do not set a short deadline for scripts that generate images.
17
+
18
+ The result starts with `Script completed` or `Script failed`, the wall time, and the output. A failed script keeps its partial output, followed by `Script error:` and the error. Tool calls are real: calls made before a failure are not undone. Calls still running when the script ends are cancelled, and unawaited promises are discarded.
19
+
20
+ ## Globals
21
+
22
+ | Global | Purpose |
23
+ |---|---|
24
+ | `tools.<name>(args)` | Call a tool. See [Call tools](#call-tools). |
25
+ | `text(value)` | Add a text item to the output. Strings are added as is, other values as JSON. |
26
+ | `image(value)` | Add an image to the output: a base64 `data:` URL, an `{ image_url }` object, or an image block `{ type: "image", data, mimeType }` such as those returned by MCP tools and `models.generateImages()`. Remote URLs are not supported. PNG, JPEG, GIF, and WebP are accepted. |
27
+ | `console.log(...)` | Like `text()`; `info`, `warn`, `error`, and `debug` do the same. |
28
+ | `return value` | A top-level `return` adds the value like `text()`. |
29
+ | `exit()` | End the script successfully. |
30
+ | `store(key, value)` / `load(key)` | Keep small JSON values across `codemode` calls. See [Store values](#store-values). |
31
+ | `ALL_TOOLS` | Every callable tool as `{ name, description }`, including tools the description does not list. |
32
+ | `searchTools(query, { limit?, namespace? })` | Rank callable tools by relevance (BM25, default limit 8). Resolves to `{ name, description }[]`. |
33
+ | `describeTool(name)` | Resolves to a tool's description and TypeScript declaration, or `undefined`. |
34
+ | `describeNamespace(name)` | Resolves to `{ name, description?, instructions?, tools }` for a namespace such as an MCP server, or `undefined`. |
35
+ | `models` | List and run non-LLM models. See [Models](#models). |
36
+
37
+ ## Call tools
38
+
39
+ Every tool the session can call is a method of `tools`, named by its identifier: characters that are not valid in a JavaScript identifier become `_`, so the MCP tool `mcp__dev-radius__search` is `tools.mcp__dev_radius__search`. Each method takes one object with the tool's arguments.
40
+
41
+ What a call resolves to depends on the tool:
42
+
43
+ - Tools with an output schema resolve to a structured value. `bash` resolves to `{ output, truncated, full_output_path?, exit_code, wall_time_seconds }`, also for non-zero exit codes. Its `output` is not limited to the 2000 lines or 50KB the model sees: it holds up to 1 MiB, and longer output keeps its first and last 512 KiB around an omission marker, with `truncated` set and the full output in `full_output_path`.
44
+ - MCP tools resolve to their `CallToolResult`, including `isError` and `structuredContent`.
45
+ - Other tools, such as `read`, `edit`, and `write`, resolve to their text output.
46
+
47
+ A call that fails, is blocked, or gets invalid arguments rejects with an `Error` that carries the tool's error text. Use `Promise.allSettled()` to keep the results of the calls that succeed.
48
+
49
+ The `codemode` description lists tools with their TypeScript declarations, grouped by namespace (for example one MCP server). Tools with `deferred` exposure, which includes MCP tools with the default `codemode` exposure, are not listed, so the description stays the same while MCP servers connect. Listed declarations share a budget of 3000 estimated tokens (`codemode.inlineBudget` in [settings](settings.md#tools)). Scripts find the other tools with `searchTools()`, `describeTool()`, `describeNamespace()`, or by filtering `ALL_TOOLS`.
50
+
51
+ While `codemode` is active, `codemode.mode` in [settings](settings.md#tools) decides how the other tools are presented. With `on` (default) declared tools stay declared, and their descriptions say how to call them from scripts. With `only` they are hidden from the model and listed in the `codemode` description instead, so the model calls them through scripts.
52
+
53
+ ## Store values
54
+
55
+ `store(key, value)` keeps a JSON value under a string key for later `codemode` calls; storing `undefined` deletes the key. `load(key)` returns the value, or `undefined`. Writes are kept only when the script succeeds: each successful script that stores values appends a `codemode-store` custom entry to the session, so resumed sessions keep the values and each branch sees only the values written on its path.
56
+
57
+ The store is for small state such as IDs, cursors, or summaries. One value may have at most 262144 characters of JSON and all values together at most 1048576. Do not store image data; show images with `image()` or write them to a file with a tool.
58
+
59
+ ## Models
60
+
61
+ `models` reaches the model catalog and runs non-LLM models with the session's credentials: classifiers, which answer typed questions about JSON state, and image models, which generate images. Chat models are listed but cannot be run from scripts. Which classifier and image models exist is described in [Use classifier models](models.md#use-classifier-models) and [Use image models](models.md#use-image-models).
62
+
63
+ ```ts
64
+ type ModelType = "chat" | "image" | "classifier";
65
+
66
+ /** A catalog entry. `provider` and `id` identify it; other fields depend on the type. */
67
+ interface ModelInfo {
68
+ type?: ModelType;
69
+ provider: string;
70
+ id: string;
71
+ name: string;
72
+ api: string;
73
+ input: ("text" | "image")[];
74
+ contextWindow?: number;
75
+ [key: string]: unknown;
76
+ }
77
+
78
+ declare const models: {
79
+ /** Every known model of a type, optionally for one provider. */
80
+ getModelsOfType(type: ModelType, provider?: string): Promise<ModelInfo[]>;
81
+ /** Models of a type whose provider has working credentials. */
82
+ getAvailableOfType(type: ModelType, provider?: string): Promise<ModelInfo[]>;
83
+ /** One catalog entry, or undefined. */
84
+ getModelOfType(type: ModelType, provider: string, id: string): Promise<ModelInfo | undefined>;
85
+ /** Answer `context.questions` about `context.state`; answers are in `result.answers` by question ID. */
86
+ classify(model: ModelInfo, context: ClassifierContext): Promise<ClassifierResult>;
87
+ /** Generate images from `context.input` text and image blocks; show `result.output` blocks with image(). Can take minutes. */
88
+ generateImages(model: ModelInfo, context: ImagesContext): Promise<ImagesResult>;
89
+ };
90
+ ```
91
+
92
+ `classify()` and `generateImages()` use only the `provider` and `id` of `model`, so `{ provider, id }` works as well. They do not throw on provider errors: check `stopReason` and `errorMessage`. At most four such calls run at once per script; more calls wait for a free slot, so `Promise.all()` over many items is fine. Their usage is added to the `codemode` tool result and counts toward the session cost.
93
+
94
+ Model IDs differ between providers, for example `typesafe/jev-latest` and `openrouter/typesafe/jev-1.13`. Use `models.getAvailableOfType(type)` to find the IDs that work with the current credentials.
95
+
96
+ ### Classify
97
+
98
+ ```ts
99
+ interface ClassifierContext {
100
+ /** The data to classify. */
101
+ state: Record<string, unknown>;
102
+ /** Questions by ID. One call answers all of them. */
103
+ questions: Record<string, ClassifierQuestion>;
104
+ }
105
+
106
+ type ClassifierQuestion =
107
+ /** Pick one label. `criteria` maps each label to what it means. */
108
+ | { type: "choice"; instructions: string; criteria: Record<string, string> }
109
+ /** Score on an ordered scale. `criteria` describes each level, lowest first. */
110
+ | { type: "score"; instructions: string; criteria: string[] }
111
+ /** Yes or no. */
112
+ | { type: "bool"; instructions: string; criteria: { true: string; false: string } };
113
+
114
+ interface ClassifierResult {
115
+ provider: string;
116
+ model: string;
117
+ /** Answers by question ID. */
118
+ answers: Record<string, ClassifierAnswer>;
119
+ usage?: ModelUsage;
120
+ stopReason: "stop" | "error" | "aborted";
121
+ errorMessage?: string;
122
+ }
123
+
124
+ type ClassifierAnswer =
125
+ | { type: "choice"; choice: string; probabilities: Record<string, number>; confidence: number }
126
+ /** `score` is the expected level index, from 0 to `criteria.length - 1`. */
127
+ | { type: "score"; score: number; confidence: number }
128
+ /** Probability of `true`. */
129
+ | { type: "bool"; probability: number };
130
+
131
+ /** Token counts and cost in USD, when the service reports them. */
132
+ type ModelUsage = { input: number; output: number; totalTokens: number; cost: { total: number } };
133
+ ```
134
+
135
+ Classify several items by calling `classify()` once per item. This script sorts feedback messages, for example ones a tool returned earlier in the script:
136
+
137
+ ```js
138
+ const jev = await models.getModelOfType("classifier", "typesafe", "jev-latest");
139
+ const results = await Promise.all(
140
+ messages.map((message) =>
141
+ models.classify(jev, {
142
+ state: { message },
143
+ questions: {
144
+ sentiment: {
145
+ type: "choice",
146
+ instructions: "How does the user feel about the product?",
147
+ criteria: { positive: "Satisfied or happy", negative: "Unhappy or frustrated", neutral: "Neither" },
148
+ },
149
+ urgency: {
150
+ type: "score",
151
+ instructions: "How urgently does this need a reply?",
152
+ criteria: ["no reply needed", "reply this week", "reply today"],
153
+ },
154
+ },
155
+ }),
156
+ ),
157
+ );
158
+ return results.map((result, i) =>
159
+ result.stopReason === "stop"
160
+ ? { message: messages[i], sentiment: result.answers.sentiment.choice, urgency: result.answers.urgency.score }
161
+ : { message: messages[i], error: result.errorMessage },
162
+ );
163
+ ```
164
+
165
+ ### Generate images
166
+
167
+ ```ts
168
+ interface ImagesContext {
169
+ /** The prompt as text blocks, plus image blocks to edit or use as references. */
170
+ input: (TextBlock | ImageBlock)[];
171
+ }
172
+
173
+ interface ImagesResult {
174
+ provider: string;
175
+ model: string;
176
+ /** Generated images, and text blocks for models that also return text. */
177
+ output: (TextBlock | ImageBlock)[];
178
+ usage?: ModelUsage;
179
+ stopReason: "stop" | "error" | "aborted";
180
+ errorMessage?: string;
181
+ }
182
+
183
+ type TextBlock = { type: "text"; text: string };
184
+ /** `data` is base64. */
185
+ type ImageBlock = { type: "image"; data: string; mimeType: string };
186
+ ```
187
+
188
+ Show generated images with `image(block)`. Do not print `data` with `text()`, `console`, or `return`: it is large and the model cannot read it as text. Generated images are not saved to disk; to keep one, write it to a file with a tool.
189
+
190
+ ```js
191
+ // @options: {"timeout_ms": 300000}
192
+ const painter = await models.getModelOfType("image", "openrouter", "google/gemini-2.5-flash-image");
193
+ const result = await models.generateImages(painter, {
194
+ input: [{ type: "text", text: "A red fox in the snow, watercolor" }],
195
+ });
196
+ if (result.stopReason !== "stop") return result.errorMessage;
197
+ for (const block of result.output) {
198
+ if (block.type === "image") image(block);
199
+ else text(block.text);
200
+ }
201
+ ```
202
+
203
+ ## Limits
204
+
205
+ - A script's VM has 256 MB of memory. Running out throws `InternalError: out of memory`; filter or aggregate large data instead of accumulating it.
206
+ - A script that waits on a promise that can never settle (no tool call pending) fails immediately, since there are no timers.
207
+ - Scripts cannot start other `codemode` scripts.
@@ -136,6 +136,10 @@
136
136
  "title": "CLI",
137
137
  "path": "cli.md"
138
138
  },
139
+ {
140
+ "title": "Codemode",
141
+ "path": "codemode.md"
142
+ },
139
143
  {
140
144
  "title": "Slash Commands",
141
145
  "path": "slash-commands.md"
@@ -153,7 +157,7 @@
153
157
  "path": "keybindings.md"
154
158
  },
155
159
  {
156
- "title": "Provider Authentication",
160
+ "title": "Providers",
157
161
  "path": "providers.md"
158
162
  },
159
163
  {
@@ -6,7 +6,7 @@ Pi uses environment variables in three ways:
6
6
  - Pi sets process markers so child processes can identify Pi as the launching agent.
7
7
  - Commands run by the LLM-callable shell tools receive `PI_*` variables describing the current session.
8
8
 
9
- Provider API-key variables are documented separately in [Provider Authentication](providers.md#use-an-api-key-from-the-environment).
9
+ Provider API-key variables are documented separately in [Providers](providers.md#use-an-api-key-from-the-environment).
10
10
 
11
11
  ## Process Marker
12
12
 
@@ -95,4 +95,4 @@ These variables are read by Pi itself:
95
95
  | `VISUAL`, `EDITOR` | External editor fallback when `externalEditor` is unset |
96
96
  | `HTTP_PROXY`, `HTTPS_PROXY` | Proxy outbound HTTP requests |
97
97
 
98
- Provider credentials such as `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, and cloud-provider configuration are listed in [Provider Authentication](providers.md#use-an-api-key-from-the-environment).
98
+ Provider credentials such as `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, and provider-specific configuration are listed in [Providers](providers.md#use-an-api-key-from-the-environment).
@@ -185,6 +185,10 @@ Register every tool first, keep optional tools inactive, and use `pi.setActiveTo
185
185
 
186
186
  Pi records the initial prompt and tool set in the transcript's first system message, then appends tool and prompt changes before the next model request. Providers that cannot represent the transition receive a complete transcript checkpoint, which can invalidate the cached prefix.
187
187
 
188
+ ### Tool rendering
189
+
190
+ A tool's `renderCall` and `renderResult` draw its calls in the interactive transcript and in HTML exports. `pi.registerToolRenderer((toolName, next) => renderers)` chooses renderers for calls to any tool, including tools that are not registered yet, such as MCP tools in a resumed session before their server connected. `next()` returns what the remaining resolvers (in extension load order), then the registered tool, would use, so `next() ?? mine` only fills in.
191
+
188
192
  ### MCP servers
189
193
 
190
194
  `pi.registerMcpServer(name, config)` adds an MCP server for the current session. `config` has the shape of an `mcpServers` entry in [`mcp.json`](mcp.md): `command`, `args`, `env`, and `cwd` for stdio servers, `url`, `headers`, and `oauth` for HTTP servers, plus `exposure`, `toolExposure`, `description`, `enabled`, and `timeout`.
package/bin/docs/index.md CHANGED
@@ -30,7 +30,7 @@ Use the [Quickstart customization chooser](quickstart.md#choose-how-to-customize
30
30
 
31
31
  ## Find reference and setup information
32
32
 
33
- Use the reference pages to look up [CLI options](cli.md), [settings](settings.md), [provider authentication](providers.md), [keybindings](keybindings.md), and [environment variables](environment-variables.md).
33
+ Use the reference pages to look up [CLI options](cli.md), [settings](settings.md), [providers](providers.md), [keybindings](keybindings.md), and [environment variables](environment-variables.md).
34
34
 
35
35
  For platform-specific help, see [Terminal Setup](terminal-setup.md), [Windows](windows.md), [tmux](tmux.md), [Termux on Android](termux.md), or [Containerization](containerization.md).
36
36
 
@@ -161,7 +161,7 @@ On native Windows, `app.suspend` has no default because Windows terminals do not
161
161
  | Keybinding id | Default | Description |
162
162
  |--------|---------|-------------|
163
163
  | `app.tools.expand` | `ctrl+o` | Collapse or expand tool output |
164
- | `app.message.copy` | `ctrl+x` | Copy the selected message in `/tree`; in fullscreen mode, copy the active selection when `fullscreenCopyOnSelect` is `false`; otherwise copy the last assistant message |
164
+ | `app.message.copy` | `ctrl+x` | Copy the selected message in `/tree`; in fullscreen mode, copy the active selection when `fullscreenCopyOnSelect` is `false`; otherwise copy the last assistant message. On OAuth sign-in screens, copy the sign-in URL |
165
165
  | `app.message.followUp` | `alt+enter` (`ctrl+q` on Windows and WSL) | Queue follow-up message |
166
166
  | `app.message.dequeue` | `alt+up` (`alt+q` on Windows and WSL) | Restore queued messages to editor |
167
167
 
package/bin/docs/mcp.md CHANGED
@@ -31,6 +31,16 @@ Use `/mcp` inside an interactive session to inspect connections, sign in, reconn
31
31
 
32
32
  Pi reads user-level servers from `~/.pi/agent/mcp.json` and project servers from `.pi/mcp.json`. Project configuration is read only after [project trust](security.md#understand-project-trust) is granted. A project entry replaces a user-level entry with the same name.
33
33
 
34
+ A project entry without `command`, `url`, or `type` overrides only `enabled`, `exposure`, and `toolExposure` of the user-level server with the same name and keeps the rest, including `env`, `headers`, and `auth`. For example, this turns off a user-level server in one project:
35
+
36
+ ```json
37
+ {
38
+ "mcpServers": {
39
+ "internal-tools": { "enabled": false }
40
+ }
41
+ }
42
+ ```
43
+
34
44
  The format matches other MCP clients:
35
45
 
36
46
  ```json
@@ -77,7 +87,7 @@ Keep personal servers and servers with credentials in the user-level file. Use t
77
87
 
78
88
  `/mcp` lists configured servers with their state, tool count, exposure, and configuration source. Servers that need attention appear first. Select a server to inspect its tools and connection details, reconnect, sign in or out, change exposure, or enable and disable it.
79
89
 
80
- Exposure and enabled-state changes are saved to the file that defines the server without replacing unrelated content. Disabled servers remain listed. Outside the interactive TUI, `/mcp` prints server status; `/mcp login <server>`, `/mcp logout <server>`, and `/mcp reconnect <server>` perform those actions directly.
90
+ Exposure and enabled-state changes are saved to the file that defines the server without replacing unrelated content. In a trusted project, "Enable in this project" and "Disable in this project" add a project override for a user-level server; later changes to that server are saved to the override. Disabled servers remain listed. Outside the interactive TUI, `/mcp` prints server status; `/mcp login <server>`, `/mcp logout <server>`, and `/mcp reconnect <server>` perform those actions directly.
81
91
 
82
92
  Shell commands work without a session: `pi mcp add`, `pi mcp remove`, `pi mcp list`, `pi mcp login`, and `pi mcp logout`. Shell commands do not load extensions.
83
93
 
@@ -118,6 +128,8 @@ When the server rejects an unauthenticated connection, `/mcp` shows that it need
118
128
 
119
129
  Pi registers itself with the authorization server, stores tokens in `~/.pi/agent/mcp-auth.json`, and refreshes access tokens when they expire or the server rejects them. If a server later requests additional scope, Pi asks for sign-in again. Signing out deletes the stored credentials.
120
130
 
131
+ Credentials belong to a server name and URL. Servers with the same URL under different names, such as one per account, sign in separately; servers with the same name and URL in different `mcp.json` files share one sign-in.
132
+
121
133
  OAuth applies to HTTP servers without an `Authorization` header. For a server that does not support dynamic client registration, configure a registered client:
122
134
 
123
135
  ```json
@@ -147,6 +159,33 @@ Pi registers as `pi`. Some servers only accept registrations from known clients.
147
159
 
148
160
  The name is only sent when Pi registers a client. To register again under a new name, sign out first.
149
161
 
162
+ Some authorization servers allow clients by their Client ID Metadata Document URL instead of registering them. Set `clientRegistration` to `cimd` to identify as Pi's document on pi.dev instead of registering:
163
+
164
+ ```json
165
+ {
166
+ "mcpServers": {
167
+ "example": { "url": "https://mcp.example.com/mcp", "oauth": { "clientRegistration": "cimd" } }
168
+ }
169
+ }
170
+ ```
171
+
172
+ The client ID is `https://pi.dev/oauth/client.json` with the redirect URI `http://127.0.0.1:<port>/callback`. If the authorization server does not send the `iss` parameter in authorization responses (RFC 9207), Pi uses a document and redirect path specific to the MCP server instead: `https://pi.dev/oauth/<id>/client.json` with `http://127.0.0.1:<port>/callback/<id>`. The authorization server must advertise Client ID Metadata Document support and public clients, or sign-in fails. `cimd` cannot be combined with `clientId` or `clientName`, and a `callbackUrl` must use `localhost` or `127.0.0.1` with the path `/callback`.
173
+
174
+ Pi finds the authorization server through the server's protected resource metadata (RFC 9728) and checks that the authorization server's metadata names the expected issuer (RFC 8414). Some servers advertise the wrong authorization server or none, so sign-in opens a page that does not exist. Set `authServerMetadataUrl` to the metadata document of the right authorization server:
175
+
176
+ ```json
177
+ {
178
+ "mcpServers": {
179
+ "example": {
180
+ "url": "https://mcp.example.com/mcp",
181
+ "oauth": { "authServerMetadataUrl": "https://example.okta.com/.well-known/openid-configuration" }
182
+ }
183
+ }
184
+ }
185
+ ```
186
+
187
+ Pi uses that document instead of discovery and trusts it as configured, so only point it at a document you trust. The URL must use HTTPS, except on `localhost`, `127.0.0.1`, or `[::1]`.
188
+
150
189
  ## Control tool exposure
151
190
 
152
191
  Each server tool is registered as `mcp__<server>__<tool>`. The server's `exposure` determines how the model reaches it:
@@ -18,7 +18,7 @@ Browse the [model catalog](https://pi.dev/models) for current providers, model I
18
18
 
19
19
  Run `/login` and select a provider. Pi stores credentials in [`auth.json`](configuration.md#agent-directory). Run `/logout` to remove stored credentials for a provider.
20
20
 
21
- You can instead provide an API key through the provider's environment variable. This is useful in CI and other environments where Pi should not write credentials. [Provider Authentication](providers.md) lists the variables and cloud-provider setup.
21
+ You can instead provide an API key through the provider's environment variable. This is useful in CI and other environments where Pi should not write credentials. [Providers](providers.md) lists the variables and provider-specific setup.
22
22
 
23
23
  When several credential sources are configured, Pi uses a runtime `--api-key` first, then a stored `auth.json` credential, an `apiKey` from `models.json`, and finally the provider's environment variables or ambient cloud credentials. Provider extensions can define their own authentication behavior.
24
24
 
@@ -98,17 +98,43 @@ Use `promptCache` to declare the provider's best-effort cache lifetime in second
98
98
 
99
99
  Choose the conservative end of any published range. A model without a lifetime for the active tier is not eligible for cache warming. A `modelOverrides` entry can set `inputLimits` or `promptCache` for a built-in or extension model, including a model accessed through a validated proxy. See [`cacheWarming`](settings.md#model-and-thinking).
100
100
 
101
+ ### Configure sampling by thinking level
102
+
103
+ OpenAI-compatible APIs support free-form `samplingParams` model defaults and `samplingParamsByThinkingLevel` overrides. The latter uses Pi thinking-level keys (`off`, `minimal`, `low`, `medium`, `high`, `xhigh`, and `max`), not provider values from `thinkingLevelMap`:
104
+
105
+ ```json
106
+ {
107
+ "id": "qwen-thinking-model",
108
+ "reasoning": true,
109
+ "samplingParams": {
110
+ "temperature": 1.0,
111
+ "top_p": 0.95
112
+ },
113
+ "samplingParamsByThinkingLevel": {
114
+ "off": {
115
+ "temperature": 0.7,
116
+ "top_p": 0.8
117
+ },
118
+ "high": {
119
+ "top_k": 20
120
+ }
121
+ }
122
+ }
123
+ ```
124
+
125
+ Pi first clamps unsupported thinking levels, then merges model `samplingParams`, the effective level's override, and request-level `samplingParams` in that order. Later values win per key. Missing levels inherit the model defaults. `modelOverrides` merges per-level entries per key with the base model. These fields apply only to `openai-completions`, `openai-responses`, and `azure-openai-responses`; other APIs ignore them.
126
+
101
127
  Compatibility settings should describe verified differences in the endpoint's request or response behavior. Do not enable them based only on an endpoint advertising OpenAI or Anthropic compatibility.
102
128
 
103
129
  ## Use classifier models
104
130
 
105
- Classifier models do not chat. They answer typed questions about JSON state: pick one of several choices, answer yes or no, or give a score, each with probabilities. Pi includes TypeSafe's Jev model from these providers:
131
+ Classifier models do not chat. They answer typed questions about JSON state: pick one of several choices, answer yes or no, or give a score, each with probabilities. Pi includes TypeSafe's Jev model from these providers, and Cloudflare's Clef and Clef Flash models from Workers AI:
106
132
 
107
133
  | Provider | Model IDs | Authentication |
108
134
  |---|---|---|
109
135
  | `typesafe` | `jev-latest` | `TYPESAFE_API_KEY` |
110
136
  | `openrouter` | `typesafe/jev-1.13`, `~typesafe/jev-latest` | `OPENROUTER_API_KEY` or `/login` |
111
- | `cloudflare-workers-ai` | `typesafe/jev` | `CLOUDFLARE_API_KEY` and `CLOUDFLARE_ACCOUNT_ID` |
137
+ | `cloudflare-workers-ai` | `typesafe/jev`, `@cf/cloudflare/clef`, `@cf/cloudflare/clef-flash` | `CLOUDFLARE_API_KEY` and `CLOUDFLARE_ACCOUNT_ID` |
112
138
  | `vercel-ai-gateway` | `typesafe-ai/jev` | `AI_GATEWAY_API_KEY` |
113
139
  | `opencode` | `jev-1.13`, `jev-1.13-free` | `OPENCODE_API_KEY` |
114
140
 
@@ -131,10 +157,31 @@ const result = await models.classify(jev, {
131
157
  return result.answers;
132
158
  ```
133
159
 
160
+ [Codemode](codemode.md#classify) describes the question and answer types.
161
+
134
162
  When the service reports token counts, as all System One services do, `result.usage` carries them with their cost. Pi adds the usage of a script's classifier calls to the `codemode` tool result, so it counts toward the session cost in the footer and `/session`. The cost uses the model's catalog price; models without one, such as TypeSafe's direct `jev-latest`, report tokens at no cost.
135
163
 
136
164
  Extensions call classifiers through `ctx.modelRegistry.classify()`, without codemode. [Virtual models](virtual-models.md#route-requests) can use them to route requests; see the `jev-router.ts` example.
137
165
 
166
+ ## Use image models
167
+
168
+ Image models generate images from a prompt and optional input images. Pi lists OpenRouter's image models, such as `google/gemini-2.5-flash-image` and `black-forest-labs/flux.2-pro`, under the `openrouter` provider; they use the same `OPENROUTER_API_KEY` or `/login` credential as its chat models.
169
+
170
+ Like classifier models, image models do not appear in `/model`; the model reaches them through the [`codemode`](cli.md#enable-codemode) tool. Scripts list them with `models.getAvailableOfType("image")` and call `models.generateImages(model, { input })`. The result's `output` holds base64 image blocks, which `image()` attaches to the `codemode` result so the model sees them:
171
+
172
+ ```js
173
+ const painter = await models.getModelOfType("image", "openrouter", "google/gemini-2.5-flash-image");
174
+ const result = await models.generateImages(painter, {
175
+ input: [{ type: "text", text: "A red fox in the snow, watercolor" }],
176
+ });
177
+ if (result.stopReason !== "stop") return result.errorMessage;
178
+ for (const block of result.output) if (block.type === "image") image(block);
179
+ ```
180
+
181
+ `input` can also contain `{ type: "image", data, mimeType }` blocks to edit or use as references. Pi adds the usage of a script's image calls to the `codemode` tool result, like classifier calls. Generated images are not saved to disk. [Codemode](codemode.md#generate-images) describes the full API.
182
+
183
+ Extensions generate images through `ctx.modelRegistry.generateImages()`, without codemode.
184
+
138
185
  ## Add a custom provider
139
186
 
140
187
  Use an extension when the provider needs custom streaming, model discovery, or authentication behavior. See [Custom Providers](custom-provider.md) for the extension workflow.
@@ -1,4 +1,4 @@
1
- # Provider Authentication
1
+ # Providers
2
2
 
3
3
  Most hosted providers support one or both of these authentication methods:
4
4
 
@@ -17,8 +17,6 @@ Run `/logout` and select a provider to remove its stored credential. This does n
17
17
 
18
18
  `auth.json` can contain API keys and OAuth tokens. Keep it private and do not commit it.
19
19
 
20
- Radius authentication uses its gateway catalog and caches refreshed model metadata for later offline startup. A custom Radius gateway configured in `models.json` uses its own catalog rather than inheriting the public `radius.pi.dev` catalog.
21
-
22
20
  ## Use an API key from the environment
23
21
 
24
22
  Environment variables are useful in CI and anywhere Pi should not store the key. Set the variable before starting Pi:
@@ -28,7 +26,7 @@ export ANTHROPIC_API_KEY=sk-ant-...
28
26
  pi
29
27
  ```
30
28
 
31
- This table covers providers with a single primary API-key variable. Providers that need additional configuration or support ambient credentials are covered under [Cloud providers](#cloud-providers).
29
+ This table covers providers with a single primary API-key variable. Providers that need additional configuration or support ambient credentials are covered under [Provider Specific Config](#provider-specific-config).
32
30
 
33
31
  | Provider | Environment variable |
34
32
  |---|---|
@@ -85,9 +83,9 @@ To use a secret manager without writing the resolved key to disk, set a provider
85
83
 
86
84
  Pi runs the command when the key is first needed and caches its standard output for the process lifetime. Empty output, a timeout, or a nonzero exit leaves the key unresolved until Pi restarts.
87
85
 
88
- ## Cloud Providers
86
+ ## Provider Specific Config
89
87
 
90
- The providers below need additional settings or can use credentials supplied by their cloud platform.
88
+ The providers below have additional setup, need additional settings, or can use credentials supplied by their platform.
91
89
 
92
90
  A stored API-key credential can include an `env` object. Its values take priority over the process environment for that provider:
93
91
 
@@ -103,6 +101,18 @@ A stored API-key credential can include an `env` object. Its values take priorit
103
101
  }
104
102
  ```
105
103
 
104
+ ### Radius
105
+
106
+ Radius is a service crafted for Pi by the builders of Pi, Earendil Works. It provides a customizable AI gateway with organization-level controls and analytics built in, and artifacts for sharing what you create with Pi.
107
+
108
+ To get started, run `/login radius` in Pi. This adds Radius as a provider, and its models appear in `/model` like any other provider's.
109
+
110
+ Radius also has an MCP server, so Pi can manage Radius for you.
111
+
112
+ Radius is currently in early alpha and evolving quickly. See [radius.earendil.com](https://radius.earendil.com) for more.
113
+
114
+ Radius authentication uses its gateway catalog and caches refreshed model metadata for later offline startup. A custom Radius gateway configured in `models.json` uses its own catalog rather than inheriting the public `radius.pi.dev` catalog.
115
+
106
116
  ### Azure OpenAI
107
117
 
108
118
  Set an API key plus either a base URL or resource name:
@@ -12,7 +12,7 @@ On macOS or Linux, you can use the installer:
12
12
  curl -fsSL https://pi.dev/install.sh | sh
13
13
  ```
14
14
 
15
- Alternatively, install Pi from npm. This requires Node.js 22.19 or newer:
15
+ The installer pins all dependencies and updates Pi with `pi update`. Alternatively, install Pi from npm, which does not pin transitive dependencies. This requires Node.js 22.19 or newer:
16
16
 
17
17
  ```bash
18
18
  npm install -g --ignore-scripts @earendil-works/pi-coding-agent
@@ -20,6 +20,14 @@ npm install -g --ignore-scripts @earendil-works/pi-coding-agent
20
20
 
21
21
  Pi does not require dependency lifecycle scripts for a normal npm installation.
22
22
 
23
+ With Nix on macOS or Linux, install the latest release from Pi's flake. Nix builds Pi from source:
24
+
25
+ ```bash
26
+ nix profile add github:earendil-works/pi/stable
27
+ ```
28
+
29
+ Older Nix versions use `nix profile install` instead. Update with `nix profile upgrade pi`; `pi update` cannot update a Nix installation. To pin a release, use a tag such as `github:earendil-works/pi/v1.0.0`.
30
+
23
31
  Verify the installation:
24
32
 
25
33
  ```bash
@@ -119,4 +127,10 @@ If you used the installer, run it again and choose **Uninstall Pi**:
119
127
  curl -fsSL https://pi.dev/install.sh | sh
120
128
  ```
121
129
 
122
- Neither method removes configuration, credentials, sessions, or installed Pi packages from `~/.pi/agent/`.
130
+ If you installed Pi with Nix, run:
131
+
132
+ ```bash
133
+ nix profile remove pi
134
+ ```
135
+
136
+ None of these methods removes configuration, credentials, sessions, or installed Pi packages from `~/.pi/agent/`.
package/bin/docs/sdk.md CHANGED
@@ -140,7 +140,7 @@ See the focused examples for [models](../examples/sdk/02-custom-model.ts), [tool
140
140
 
141
141
  ## Resources
142
142
 
143
- - [Choose a Model](models.md) covers model selection and compatible endpoints; [Provider Authentication](providers.md) covers credentials and cloud-provider setup.
143
+ - [Choose a Model](models.md) covers model selection and compatible endpoints; [Providers](providers.md) covers credentials and provider-specific setup.
144
144
  - [Configuration](configuration.md) explains normal discovery and settings; [Settings](settings.md) lists every setting.
145
145
  - [Sessions and Context](sessions.md) explains session behavior; [Session Format](session-format.md) defines persisted entries; [Message Types](message-types.md) defines shared transcript values.
146
146
  - [Extensions](extensions.md), [Skills](skills.md), and [Prompt Templates](prompt-templates.md) document resources supplied through a `ResourceLoader`.
@@ -38,7 +38,7 @@ See [Choose a Model](models.md) for model selection and thinking controls.
38
38
  | Setting | Type | Default | Description |
39
39
  |---|---|---|---|
40
40
  | `defaultTools` | `string[]` | `read`, `bash`, `edit`, `write` | Tools enabled at startup. Plain names replace the defaults; `+name` adds a tool and `-name` removes one. An empty array disables all built-in tools but not extension or SDK tools. |
41
- | `codemode.mode` | `"on"` \| `"only"` | `"on"` | How the `codemode` tool presents tools while it is active. `on`: declared tools get their `codemode` declaration appended to their description, and `codemode` lists only tools that are not declared. `only`: `codemode` lists every tool scripts can call, and active built-in and extension tools are hidden from the model, so it reaches them through `codemode`. |
41
+ | `codemode.mode` | `"on"` \| `"only"` | `"on"` | How the `codemode` tool presents tools while it is active. `on`: declared tools get a note on calling them from scripts appended to their description, and `codemode` lists only tools that are not declared. `only`: `codemode` lists every tool scripts can call, and active built-in and extension tools are hidden from the model, so it reaches them through `codemode`. |
42
42
  | `codemode.inlineBudget` | number | `3000` | Estimated tokens (characters / 4) the `codemode` tool's description may spend on tool declarations. Tools that do not fit are left out and found with `searchTools()`. `0` lists only namespaces. |
43
43
 
44
44
  Available built-in tools are `read`, `bash`, `powershell`, `edit`, `write`, `grep`, `find`, and `ls`. `defaultTools` can also name `codemode` and `tool_search`, which built-in extensions register inactive, and other extension tools registered inactive.
@@ -90,8 +90,8 @@ See [Compaction Reference](compaction.md) for trigger, summarization, and valida
90
90
  | Setting | Type | Default | Description |
91
91
  |---|---|---|---|
92
92
  | `theme` | string | `"system"` | Built-in or custom theme name. `system` derives colors from the terminal theme. |
93
- | `quietStartup` | boolean | `false` | Hide the startup header. |
94
- | `tuiMode` | `"regular" \| "fullscreen"` | `"regular"` | Interactive terminal UI mode. |
93
+ | `quietStartup` | boolean \| `"header"` | `false` | `true` hides the startup header and loaded-resource listing. `"header"` keeps the header (version and key hints) but hides the model scope line and loaded-resource listing. |
94
+ | `tuiMode` | `"regular" \| "fullscreen"` | `"fullscreen"` | Interactive terminal UI mode. |
95
95
  | `fullscreenExitOutput` | `"transcript" \| "resume-hint"` | `"transcript"` | Output printed when fullscreen mode exits. |
96
96
  | `fullscreenScrollbar` | `"auto" \| "always" \| "hidden"` | `"auto"` | Fullscreen transcript scrollbar behavior. |
97
97
  | `fullscreenCopyOnSelect` | boolean | `true` | Copy selected text automatically in fullscreen mode. |
package/bin/docs/usage.md CHANGED
@@ -83,7 +83,7 @@ Use `/share` to upload the session and get a viewer link. With Radius authentica
83
83
 
84
84
  ## Adjust the terminal
85
85
 
86
- Regular mode uses the terminal's normal scrollback. Fullscreen mode keeps the editor and status area fixed while the transcript scrolls within the terminal window. Choose a mode through `/settings` or `--tui-mode`.
86
+ Fullscreen mode, the default, keeps the editor and status area fixed while the transcript scrolls within the terminal window. Regular mode uses the terminal's normal scrollback. Choose a mode through `/settings` or `--tui-mode`.
87
87
 
88
88
  Terminal support for mouse input, keyboard shortcuts, and inline images varies. See [Terminal Setup](terminal-setup.md) for platform-specific configuration and [Keybindings](keybindings.md) for every configurable shortcut. Run `/hotkeys` to inspect the shortcuts active in your current session.
89
89
 
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider",
3
- "version": "0.99.2",
3
+ "version": "1.0.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-custom-provider",
9
- "version": "0.99.2",
9
+ "version": "1.0.2",
10
10
  "dependencies": {
11
11
  "@anthropic-ai/sdk": "^0.52.0"
12
12
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider-anthropic",
3
3
  "private": true,
4
- "version": "0.99.2",
4
+ "version": "1.0.2",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider-gitlab-duo",
3
3
  "private": true,
4
- "version": "0.99.2",
4
+ "version": "1.0.2",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pi-extension-gondolin",
3
- "version": "0.99.2",
3
+ "version": "1.0.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-gondolin",
9
- "version": "0.99.2",
9
+ "version": "1.0.2",
10
10
  "dependencies": {
11
11
  "@earendil-works/gondolin": "0.12.0"
12
12
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-gondolin",
3
3
  "private": true,
4
- "version": "0.99.2",
4
+ "version": "1.0.2",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pi-extension-sandbox",
3
- "version": "0.99.2",
3
+ "version": "1.0.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-sandbox",
9
- "version": "0.99.2",
9
+ "version": "1.0.2",
10
10
  "dependencies": {
11
11
  "@anthropic-ai/sandbox-runtime": "^0.0.26"
12
12
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-sandbox",
3
3
  "private": true,
4
- "version": "0.99.2",
4
+ "version": "1.0.2",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pi-extension-with-deps",
3
- "version": "0.99.2",
3
+ "version": "1.0.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-with-deps",
9
- "version": "0.99.2",
9
+ "version": "1.0.2",
10
10
  "dependencies": {
11
11
  "ms": "^2.1.3"
12
12
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-with-deps",
3
3
  "private": true,
4
- "version": "0.99.2",
4
+ "version": "1.0.2",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -5,10 +5,10 @@
5
5
  * and converting the ANSI output to HTML.
6
6
  */
7
7
  import type { Theme } from "../../modes/interactive/theme/theme.ts";
8
- import type { ToolDefinition } from "../extensions/types.ts";
8
+ import type { ToolRenderers } from "../extensions/types.ts";
9
9
  export interface ToolHtmlRendererDeps {
10
- /** Function to look up tool definition by name */
11
- getToolDefinition: (name: string) => ToolDefinition | undefined;
10
+ /** Renderers of calls to a tool, as resolved by extensions and the registered tool */
11
+ getToolRenderers: (name: string) => ToolRenderers | undefined;
12
12
  /** Theme for styling */
13
13
  theme: Theme;
14
14
  /** Working directory for render context */
@@ -1 +1 @@
1
- {"version":3,"file":"tool-renderer.d.ts","sourceRoot":"","sources":["../../../src/core/export-html/tool-renderer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,wCAAwC,CAAC;AACpE,OAAO,KAAK,EAAE,cAAc,EAAqB,MAAM,wBAAwB,CAAC;AAGhF,MAAM,WAAW,oBAAoB;IACpC,kDAAkD;IAClD,iBAAiB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,cAAc,GAAG,SAAS,CAAC;IAChE,wBAAwB;IACxB,KAAK,EAAE,KAAK,CAAC;IACb,2CAA2C;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAChC,oFAAoF;IACpF,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;IACpF,yGAAyG;IACzG,YAAY,CACX,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,EAChF,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,OAAO,GACd;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;CACzD;AAsBD,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,oBAAoB,GAAG,gBAAgB,CAkHnF"}
1
+ {"version":3,"file":"tool-renderer.d.ts","sourceRoot":"","sources":["../../../src/core/export-html/tool-renderer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,wCAAwC,CAAC;AACpE,OAAO,KAAK,EAAqB,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAG/E,MAAM,WAAW,oBAAoB;IACpC,sFAAsF;IACtF,gBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,aAAa,GAAG,SAAS,CAAC;IAC9D,wBAAwB;IACxB,KAAK,EAAE,KAAK,CAAC;IACb,2CAA2C;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAChC,oFAAoF;IACpF,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;IACpF,yGAAyG;IACzG,YAAY,CACX,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,EAChF,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,OAAO,GACd;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;CACzD;AAsBD,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,oBAAoB,GAAG,gBAAgB,CAkHnF"}
@@ -25,7 +25,7 @@ function trimRenderedResultLines(lines) {
25
25
  return lines.slice(start, end);
26
26
  }
27
27
  export function createToolHtmlRenderer(deps) {
28
- const { getToolDefinition, theme, cwd, width = 100 } = deps;
28
+ const { getToolRenderers, theme, cwd, width = 100 } = deps;
29
29
  const renderedCallComponents = new Map();
30
30
  const renderedResultComponents = new Map();
31
31
  const renderedStates = new Map();
@@ -58,7 +58,7 @@ export function createToolHtmlRenderer(deps) {
58
58
  renderCall(toolCallId, toolName, args) {
59
59
  try {
60
60
  renderedArgs.set(toolCallId, args);
61
- const toolDef = getToolDefinition(toolName);
61
+ const toolDef = getToolRenderers(toolName);
62
62
  if (!toolDef?.renderCall) {
63
63
  return undefined;
64
64
  }
@@ -74,7 +74,7 @@ export function createToolHtmlRenderer(deps) {
74
74
  },
75
75
  renderResult(toolCallId, toolName, result, details, isError) {
76
76
  try {
77
- const toolDef = getToolDefinition(toolName);
77
+ const toolDef = getToolRenderers(toolName);
78
78
  if (!toolDef?.renderResult) {
79
79
  return undefined;
80
80
  }
@@ -1 +1 @@
1
- {"version":3,"file":"tool-renderer.js","sourceRoot":"","sources":["../../../src/core/export-html/tool-renderer.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Tool HTML renderer for custom tools in HTML export.\n *\n * Renders custom tool calls and results to HTML by invoking their TUI renderers\n * and converting the ANSI output to HTML.\n */\n\nimport type { ImageContent, TextContent } from \"@earendil-works/pi-ai\";\nimport type { Component } from \"@earendil-works/pi-tui\";\nimport type { Theme } from \"../../modes/interactive/theme/theme.ts\";\nimport type { ToolDefinition, ToolRenderContext } from \"../extensions/types.ts\";\nimport { ansiLinesToHtml } from \"./ansi-to-html.ts\";\n\nexport interface ToolHtmlRendererDeps {\n\t/** Function to look up tool definition by name */\n\tgetToolDefinition: (name: string) => ToolDefinition | undefined;\n\t/** Theme for styling */\n\ttheme: Theme;\n\t/** Working directory for render context */\n\tcwd: string;\n\t/** Terminal width for rendering (default: 100) */\n\twidth?: number;\n}\n\nexport interface ToolHtmlRenderer {\n\t/** Render a tool call to HTML. Returns undefined if tool has no custom renderer. */\n\trenderCall(toolCallId: string, toolName: string, args: unknown): string | undefined;\n\t/** Render a tool result to collapsed/expanded HTML. Returns undefined if tool has no custom renderer. */\n\trenderResult(\n\t\ttoolCallId: string,\n\t\ttoolName: string,\n\t\tresult: Array<{ type: string; text?: string; data?: string; mimeType?: string }>,\n\t\tdetails: unknown,\n\t\tisError: boolean,\n\t): { collapsed?: string; expanded?: string } | undefined;\n}\n\n/**\n * Create a tool HTML renderer.\n *\n * The renderer looks up tool definitions and invokes their renderCall/renderResult\n * methods, converting the resulting TUI Component output (ANSI) to HTML.\n */\nconst ANSI_ESCAPE_REGEX = /\\x1b\\[[\\d;]*m/g;\n\nfunction isBlankRenderedLine(line: string): boolean {\n\treturn line.replace(ANSI_ESCAPE_REGEX, \"\").trim().length === 0;\n}\n\nfunction trimRenderedResultLines(lines: string[]): string[] {\n\tlet start = 0;\n\tlet end = lines.length;\n\twhile (start < end && isBlankRenderedLine(lines[start])) start++;\n\twhile (end > start && isBlankRenderedLine(lines[end - 1])) end--;\n\treturn lines.slice(start, end);\n}\n\nexport function createToolHtmlRenderer(deps: ToolHtmlRendererDeps): ToolHtmlRenderer {\n\tconst { getToolDefinition, theme, cwd, width = 100 } = deps;\n\n\tconst renderedCallComponents = new Map<string, Component>();\n\tconst renderedResultComponents = new Map<string, Component>();\n\tconst renderedStates = new Map<string, any>();\n\tconst renderedArgs = new Map<string, unknown>();\n\n\tconst getState = (toolCallId: string): any => {\n\t\tlet state = renderedStates.get(toolCallId);\n\t\tif (!state) {\n\t\t\tstate = {};\n\t\t\trenderedStates.set(toolCallId, state);\n\t\t}\n\t\treturn state;\n\t};\n\n\tconst createRenderContext = (\n\t\ttoolCallId: string,\n\t\tlastComponent: Component | undefined,\n\t\texpanded: boolean,\n\t\tisPartial: boolean,\n\t\tisError: boolean,\n\t): ToolRenderContext => {\n\t\treturn {\n\t\t\targs: renderedArgs.get(toolCallId),\n\t\t\ttoolCallId,\n\t\t\tinvalidate: () => {},\n\t\t\tlastComponent,\n\t\t\tstate: getState(toolCallId),\n\t\t\tcwd,\n\t\t\texecutionStarted: true,\n\t\t\targsComplete: true,\n\t\t\tisPartial,\n\t\t\texpanded,\n\t\t\tshowImages: false,\n\t\t\tisError,\n\t\t};\n\t};\n\n\treturn {\n\t\trenderCall(toolCallId: string, toolName: string, args: unknown): string | undefined {\n\t\t\ttry {\n\t\t\t\trenderedArgs.set(toolCallId, args);\n\t\t\t\tconst toolDef = getToolDefinition(toolName);\n\t\t\t\tif (!toolDef?.renderCall) {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\n\t\t\t\tconst component = toolDef.renderCall(\n\t\t\t\t\targs,\n\t\t\t\t\ttheme,\n\t\t\t\t\tcreateRenderContext(toolCallId, renderedCallComponents.get(toolCallId), false, true, false),\n\t\t\t\t);\n\t\t\t\trenderedCallComponents.set(toolCallId, component);\n\t\t\t\tconst lines = component.render(width);\n\t\t\t\treturn ansiLinesToHtml(lines);\n\t\t\t} catch {\n\t\t\t\t// On error, return undefined so HTML export can fall back to structured result rendering\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t},\n\n\t\trenderResult(\n\t\t\ttoolCallId: string,\n\t\t\ttoolName: string,\n\t\t\tresult: Array<{ type: string; text?: string; data?: string; mimeType?: string }>,\n\t\t\tdetails: unknown,\n\t\t\tisError: boolean,\n\t\t): { collapsed?: string; expanded?: string } | undefined {\n\t\t\ttry {\n\t\t\t\tconst toolDef = getToolDefinition(toolName);\n\t\t\t\tif (!toolDef?.renderResult) {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\n\t\t\t\t// Build AgentToolResult from content array\n\t\t\t\t// Cast content since session storage uses generic object types\n\t\t\t\tconst agentToolResult = {\n\t\t\t\t\tcontent: result as (TextContent | ImageContent)[],\n\t\t\t\t\tdetails,\n\t\t\t\t\tisError,\n\t\t\t\t};\n\n\t\t\t\t// Render collapsed\n\t\t\t\tconst collapsedComponent = toolDef.renderResult(\n\t\t\t\t\tagentToolResult,\n\t\t\t\t\t{ expanded: false, isPartial: false },\n\t\t\t\t\ttheme,\n\t\t\t\t\tcreateRenderContext(toolCallId, renderedResultComponents.get(toolCallId), false, false, isError),\n\t\t\t\t);\n\t\t\t\trenderedResultComponents.set(toolCallId, collapsedComponent);\n\t\t\t\tconst collapsed = ansiLinesToHtml(trimRenderedResultLines(collapsedComponent.render(width)));\n\n\t\t\t\t// Render expanded\n\t\t\t\tconst expandedComponent = toolDef.renderResult(\n\t\t\t\t\tagentToolResult,\n\t\t\t\t\t{ expanded: true, isPartial: false },\n\t\t\t\t\ttheme,\n\t\t\t\t\tcreateRenderContext(toolCallId, renderedResultComponents.get(toolCallId), true, false, isError),\n\t\t\t\t);\n\t\t\t\trenderedResultComponents.set(toolCallId, expandedComponent);\n\t\t\t\tconst expanded = ansiLinesToHtml(trimRenderedResultLines(expandedComponent.render(width)));\n\n\t\t\t\treturn {\n\t\t\t\t\t...(collapsed && collapsed !== expanded ? { collapsed } : {}),\n\t\t\t\t\texpanded,\n\t\t\t\t};\n\t\t\t} catch {\n\t\t\t\t// On error, return undefined so HTML export can fall back to structured result rendering\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t},\n\t};\n}\n"]}
1
+ {"version":3,"file":"tool-renderer.js","sourceRoot":"","sources":["../../../src/core/export-html/tool-renderer.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Tool HTML renderer for custom tools in HTML export.\n *\n * Renders custom tool calls and results to HTML by invoking their TUI renderers\n * and converting the ANSI output to HTML.\n */\n\nimport type { ImageContent, TextContent } from \"@earendil-works/pi-ai\";\nimport type { Component } from \"@earendil-works/pi-tui\";\nimport type { Theme } from \"../../modes/interactive/theme/theme.ts\";\nimport type { ToolRenderContext, ToolRenderers } from \"../extensions/types.ts\";\nimport { ansiLinesToHtml } from \"./ansi-to-html.ts\";\n\nexport interface ToolHtmlRendererDeps {\n\t/** Renderers of calls to a tool, as resolved by extensions and the registered tool */\n\tgetToolRenderers: (name: string) => ToolRenderers | undefined;\n\t/** Theme for styling */\n\ttheme: Theme;\n\t/** Working directory for render context */\n\tcwd: string;\n\t/** Terminal width for rendering (default: 100) */\n\twidth?: number;\n}\n\nexport interface ToolHtmlRenderer {\n\t/** Render a tool call to HTML. Returns undefined if tool has no custom renderer. */\n\trenderCall(toolCallId: string, toolName: string, args: unknown): string | undefined;\n\t/** Render a tool result to collapsed/expanded HTML. Returns undefined if tool has no custom renderer. */\n\trenderResult(\n\t\ttoolCallId: string,\n\t\ttoolName: string,\n\t\tresult: Array<{ type: string; text?: string; data?: string; mimeType?: string }>,\n\t\tdetails: unknown,\n\t\tisError: boolean,\n\t): { collapsed?: string; expanded?: string } | undefined;\n}\n\n/**\n * Create a tool HTML renderer.\n *\n * The renderer looks up tool definitions and invokes their renderCall/renderResult\n * methods, converting the resulting TUI Component output (ANSI) to HTML.\n */\nconst ANSI_ESCAPE_REGEX = /\\x1b\\[[\\d;]*m/g;\n\nfunction isBlankRenderedLine(line: string): boolean {\n\treturn line.replace(ANSI_ESCAPE_REGEX, \"\").trim().length === 0;\n}\n\nfunction trimRenderedResultLines(lines: string[]): string[] {\n\tlet start = 0;\n\tlet end = lines.length;\n\twhile (start < end && isBlankRenderedLine(lines[start])) start++;\n\twhile (end > start && isBlankRenderedLine(lines[end - 1])) end--;\n\treturn lines.slice(start, end);\n}\n\nexport function createToolHtmlRenderer(deps: ToolHtmlRendererDeps): ToolHtmlRenderer {\n\tconst { getToolRenderers, theme, cwd, width = 100 } = deps;\n\n\tconst renderedCallComponents = new Map<string, Component>();\n\tconst renderedResultComponents = new Map<string, Component>();\n\tconst renderedStates = new Map<string, any>();\n\tconst renderedArgs = new Map<string, unknown>();\n\n\tconst getState = (toolCallId: string): any => {\n\t\tlet state = renderedStates.get(toolCallId);\n\t\tif (!state) {\n\t\t\tstate = {};\n\t\t\trenderedStates.set(toolCallId, state);\n\t\t}\n\t\treturn state;\n\t};\n\n\tconst createRenderContext = (\n\t\ttoolCallId: string,\n\t\tlastComponent: Component | undefined,\n\t\texpanded: boolean,\n\t\tisPartial: boolean,\n\t\tisError: boolean,\n\t): ToolRenderContext => {\n\t\treturn {\n\t\t\targs: renderedArgs.get(toolCallId),\n\t\t\ttoolCallId,\n\t\t\tinvalidate: () => {},\n\t\t\tlastComponent,\n\t\t\tstate: getState(toolCallId),\n\t\t\tcwd,\n\t\t\texecutionStarted: true,\n\t\t\targsComplete: true,\n\t\t\tisPartial,\n\t\t\texpanded,\n\t\t\tshowImages: false,\n\t\t\tisError,\n\t\t};\n\t};\n\n\treturn {\n\t\trenderCall(toolCallId: string, toolName: string, args: unknown): string | undefined {\n\t\t\ttry {\n\t\t\t\trenderedArgs.set(toolCallId, args);\n\t\t\t\tconst toolDef = getToolRenderers(toolName);\n\t\t\t\tif (!toolDef?.renderCall) {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\n\t\t\t\tconst component = toolDef.renderCall(\n\t\t\t\t\targs,\n\t\t\t\t\ttheme,\n\t\t\t\t\tcreateRenderContext(toolCallId, renderedCallComponents.get(toolCallId), false, true, false),\n\t\t\t\t);\n\t\t\t\trenderedCallComponents.set(toolCallId, component);\n\t\t\t\tconst lines = component.render(width);\n\t\t\t\treturn ansiLinesToHtml(lines);\n\t\t\t} catch {\n\t\t\t\t// On error, return undefined so HTML export can fall back to structured result rendering\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t},\n\n\t\trenderResult(\n\t\t\ttoolCallId: string,\n\t\t\ttoolName: string,\n\t\t\tresult: Array<{ type: string; text?: string; data?: string; mimeType?: string }>,\n\t\t\tdetails: unknown,\n\t\t\tisError: boolean,\n\t\t): { collapsed?: string; expanded?: string } | undefined {\n\t\t\ttry {\n\t\t\t\tconst toolDef = getToolRenderers(toolName);\n\t\t\t\tif (!toolDef?.renderResult) {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\n\t\t\t\t// Build AgentToolResult from content array\n\t\t\t\t// Cast content since session storage uses generic object types\n\t\t\t\tconst agentToolResult = {\n\t\t\t\t\tcontent: result as (TextContent | ImageContent)[],\n\t\t\t\t\tdetails,\n\t\t\t\t\tisError,\n\t\t\t\t};\n\n\t\t\t\t// Render collapsed\n\t\t\t\tconst collapsedComponent = toolDef.renderResult(\n\t\t\t\t\tagentToolResult,\n\t\t\t\t\t{ expanded: false, isPartial: false },\n\t\t\t\t\ttheme,\n\t\t\t\t\tcreateRenderContext(toolCallId, renderedResultComponents.get(toolCallId), false, false, isError),\n\t\t\t\t);\n\t\t\t\trenderedResultComponents.set(toolCallId, collapsedComponent);\n\t\t\t\tconst collapsed = ansiLinesToHtml(trimRenderedResultLines(collapsedComponent.render(width)));\n\n\t\t\t\t// Render expanded\n\t\t\t\tconst expandedComponent = toolDef.renderResult(\n\t\t\t\t\tagentToolResult,\n\t\t\t\t\t{ expanded: true, isPartial: false },\n\t\t\t\t\ttheme,\n\t\t\t\t\tcreateRenderContext(toolCallId, renderedResultComponents.get(toolCallId), true, false, isError),\n\t\t\t\t);\n\t\t\t\trenderedResultComponents.set(toolCallId, expandedComponent);\n\t\t\t\tconst expanded = ansiLinesToHtml(trimRenderedResultLines(expandedComponent.render(width)));\n\n\t\t\t\treturn {\n\t\t\t\t\t...(collapsed && collapsed !== expanded ? { collapsed } : {}),\n\t\t\t\t\texpanded,\n\t\t\t\t};\n\t\t\t} catch {\n\t\t\t\t// On error, return undefined so HTML export can fall back to structured result rendering\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t},\n\t};\n}\n"]}
package/bin/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@earendil-works/pi-coding-agent",
3
- "version": "0.99.2",
3
+ "version": "1.0.2",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "piConfig": {
@@ -34,8 +34,7 @@
34
34
  "docs",
35
35
  "examples",
36
36
  "containerization.md",
37
- "CHANGELOG.md",
38
- "npm-shrinkwrap.json"
37
+ "CHANGELOG.md"
39
38
  ],
40
39
  "scripts": {
41
40
  "clean": "shx rm -rf dist",
@@ -45,17 +44,17 @@
45
44
  "copy-assets": "shx mkdir -p dist/modes/interactive/theme && shx cp src/modes/interactive/theme/*.json dist/modes/interactive/theme/ && shx mkdir -p dist/modes/interactive/assets && shx cp src/modes/interactive/assets/*.png dist/modes/interactive/assets/ && shx mkdir -p dist/core/export-html/vendor && shx cp src/core/export-html/template.html src/core/export-html/template.css src/core/export-html/template.js dist/core/export-html/ && shx cp src/core/export-html/vendor/*.js dist/core/export-html/vendor/",
46
45
  "copy-binary-assets": "shx cp package.json dist/ && shx cp README.md dist/ && shx cp CHANGELOG.md dist/ && shx mkdir -p dist/theme && shx cp src/modes/interactive/theme/*.json dist/theme/ && shx mkdir -p dist/assets && shx cp src/modes/interactive/assets/*.png dist/assets/ && shx mkdir -p dist/export-html/vendor && shx cp src/core/export-html/template.html dist/export-html/ && shx cp src/core/export-html/vendor/*.js dist/export-html/vendor/ && shx cp -r docs dist/ && shx cp -r examples dist/ && shx cp ../../node_modules/@silvia-odwyer/photon-node/photon_rs_bg.wasm dist/",
47
46
  "test": "vitest --run",
48
- "shrinkwrap": "node ../../scripts/generate-coding-agent-shrinkwrap.mjs",
49
- "prepublishOnly": "npm run clean && npm run build && npm run shrinkwrap"
47
+ "prepublishOnly": "npm run clean && npm run build"
50
48
  },
51
49
  "dependencies": {
52
- "@earendil-works/chord": "^0.99.2",
53
- "@earendil-works/pi-agent-core": "^0.99.2",
54
- "@earendil-works/pi-ai": "^0.99.2",
55
- "@earendil-works/pi-codemode": "^0.99.2",
56
- "@earendil-works/pi-mcp": "^0.99.2",
57
- "@earendil-works/pi-tui": "^0.99.2",
50
+ "@earendil-works/chord": "^1.0.2",
51
+ "@earendil-works/pi-agent-core": "^1.0.2",
52
+ "@earendil-works/pi-ai": "^1.0.2",
53
+ "@earendil-works/pi-codemode": "^1.0.2",
54
+ "@earendil-works/pi-mcp": "^1.0.2",
55
+ "@earendil-works/pi-tui": "^1.0.2",
58
56
  "@silvia-odwyer/photon-node": "0.3.4",
57
+ "brace-expansion": "5.0.12",
59
58
  "chalk": "6.0.0",
60
59
  "cross-spawn": "7.0.6",
61
60
  "diff": "8.0.4",
@@ -76,9 +75,9 @@
76
75
  "protobufjs": "7.6.6"
77
76
  },
78
77
  "devDependencies": {
79
- "@earendil-works/pi-client": "^0.99.2",
80
- "@earendil-works/pi-protocol": "^0.99.2",
81
- "@earendil-works/pi-server": "^0.99.2",
78
+ "@earendil-works/pi-client": "^1.0.2",
79
+ "@earendil-works/pi-protocol": "^1.0.2",
80
+ "@earendil-works/pi-server": "^1.0.2",
82
81
  "@types/cross-spawn": "6.0.6",
83
82
  "@types/hosted-git-info": "3.0.5",
84
83
  "@types/node": "22.19.19",
package/bin/pi.exe CHANGED
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: package/bin/pi.exe
3
- size: 111432704 bytes
4
- sha256: 40d318b511f56935961c6caef7da12eeab12d27d418182fbd44878defd91a399
3
+ size: 111058432 bytes
4
+ sha256: 8f3aff254efbe27cfba248b17c533c19eee0c33fc5499e21de032622318564c9
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kingingwang/pi-windows-x64",
3
- "version": "0.99.2",
3
+ "version": "1.0.2",
4
4
  "description": "Pi standalone binary for windows-x64 (fork distribution)",
5
5
  "license": "MIT",
6
6
  "os": [