@counterposition/skill-pi 1.0.2 → 1.4.0
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/README.md +1 -1
- package/package.json +3 -3
- package/skills/pi/README.md +1 -1
- package/skills/pi/SKILL.md +2 -3
- package/skills/pi/references/codemode.md +13 -7
- package/skills/pi/references/extensions.md +16 -11
- package/skills/pi/references/mcp.md +5 -4
- package/skills/pi/references/packages.md +2 -2
- package/skills/pi/references/patterns.md +1 -1
- package/skills/pi/references/providers.md +25 -18
- package/skills/pi/references/sdk.md +7 -9
- package/skills/pi/references/settings.md +23 -12
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ If you develop inside this repo, Pi already loads the skill via `.pi/settings.js
|
|
|
28
28
|
|
|
29
29
|
## What this covers
|
|
30
30
|
|
|
31
|
-
Pi's architecture and package layout. Skill authoring, discovery, and routing. Extensions, tools, and UI hooks. MCP servers, codemode, and nested tool calls. Settings, models, providers, classifiers, and packages. Sessions, forks, and compaction. The SDK and RPC interface. Checked against Pi 1.0.
|
|
31
|
+
Pi's architecture and package layout. Skill authoring, discovery, and routing. Extensions, tools, and UI hooks. MCP servers, codemode, and nested tool calls. Settings, models, providers, classifiers, and packages. Sessions, forks, and compaction. The SDK and RPC interface. Checked against Pi 1.4.0.
|
|
32
32
|
|
|
33
33
|
The reference material lives in `references/` as task-specific documents — not a single monolith, but pieces sized for injection into a conversation when relevant.
|
|
34
34
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@counterposition/skill-pi",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Pi coding agent reference skill — extensions, settings, providers, SDK/RPC, packages, and .pi/ config",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-skill",
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
"access": "public"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"@earendil-works/pi-ai": "^1.0
|
|
34
|
-
"@earendil-works/pi-coding-agent": "^1.0
|
|
33
|
+
"@earendil-works/pi-ai": "^1.4.0",
|
|
34
|
+
"@earendil-works/pi-coding-agent": "^1.4.0",
|
|
35
35
|
"@types/node": "^25.5.0",
|
|
36
36
|
"typebox": "^1.3.16",
|
|
37
37
|
"typescript": "^6.0.2",
|
package/skills/pi/README.md
CHANGED
|
@@ -28,7 +28,7 @@ If you develop inside this repo, Pi already loads the skill via `.pi/settings.js
|
|
|
28
28
|
|
|
29
29
|
## What this covers
|
|
30
30
|
|
|
31
|
-
Pi's architecture and package layout. Skill authoring, discovery, and routing. Extensions, tools, and UI hooks. MCP servers, codemode, and nested tool calls. Settings, models, providers, classifiers, and packages. Sessions, forks, and compaction. The SDK and RPC interface. Checked against Pi 1.0.
|
|
31
|
+
Pi's architecture and package layout. Skill authoring, discovery, and routing. Extensions, tools, and UI hooks. MCP servers, codemode, and nested tool calls. Settings, models, providers, classifiers, and packages. Sessions, forks, and compaction. The SDK and RPC interface. Checked against Pi 1.4.0.
|
|
32
32
|
|
|
33
33
|
The reference material lives in `references/` as task-specific documents — not a single monolith, but pieces sized for injection into a conversation when relevant.
|
|
34
34
|
|
package/skills/pi/SKILL.md
CHANGED
|
@@ -7,7 +7,7 @@ compatibility: "Pi-specific guidance for agents that support Agent Skills"
|
|
|
7
7
|
|
|
8
8
|
# Pi Coding Agent
|
|
9
9
|
|
|
10
|
-
Pi is a minimal terminal coding harness. Default tools: `read`, `bash`, `edit`, `write`; `grep`, `find`, `ls`, and the Windows `powershell` tool are opt-in via the `defaultTools` setting or `--tools`. Built-in extensions (Pi 0.99.0) add MCP servers, the `codemode` tool (model-written JavaScript that calls other tools), and `tool_search`. `codemode` and `tool_search` start inactive: enable them with `defaultTools`/`--tools`, or let MCP activate them when a server needs them. Modes: interactive (`pi`), print (`pi -p`), JSON (`--mode json`), RPC (`--mode rpc`), or embedded (`createAgentSession()`). Sub-agents, plan mode, and permission flows are intentionally left to extensions and Pi packages. Skill synced with Pi 1.0.
|
|
10
|
+
Pi is a minimal terminal coding harness. Default tools: `read`, `bash`, `edit`, `write`; `grep`, `find`, `ls`, and the Windows `powershell` tool are opt-in via the `defaultTools` setting or `--tools`. Built-in extensions (Pi 0.99.0) add MCP servers, the `codemode` tool (model-written JavaScript that calls other tools), and `tool_search`. `codemode` and `tool_search` start inactive: enable them with `defaultTools`/`--tools`, or let MCP activate them when a server needs them. Modes: interactive (`pi`), print (`pi -p`), JSON (`--mode json`), RPC (`--mode rpc`), or embedded (`createAgentSession()`). Sub-agents, plan mode, and permission flows are intentionally left to extensions and Pi packages. Skill synced with Pi 1.4.0.
|
|
11
11
|
|
|
12
12
|
Pi's philosophy: **adapt Pi to your workflows, not the other way around**.
|
|
13
13
|
|
|
@@ -20,10 +20,9 @@ Core packages on npm (source: [github.com/earendil-works/pi](https://github.com/
|
|
|
20
20
|
| `@earendil-works/pi-ai` | Unified LLM API across 20+ providers |
|
|
21
21
|
| `@earendil-works/pi-agent-core` | `Agent`, the agent loop, and the proxy stream (Pi 1.0.0 removed the experimental harness, sessions, and its `./node`/`./harness/*` subpaths) |
|
|
22
22
|
| `@earendil-works/pi-tui` | Terminal UI components |
|
|
23
|
-
| `@earendil-works/pi-coding-agent` | CLI, extensions, skills, sessions, settings (also exports `./
|
|
23
|
+
| `@earendil-works/pi-coding-agent` | CLI, extensions, skills, sessions, settings (also exports `./rpc-entry`) |
|
|
24
24
|
| `@earendil-works/pi-codemode` | QuickJS sandbox that runs model-written JavaScript against injected tools (Pi 0.99.0) |
|
|
25
25
|
| `@earendil-works/pi-mcp` | Standalone MCP client: stdio and streamable HTTP, OAuth (Pi 0.99.0) |
|
|
26
|
-
| `@earendil-works/pi-client` / `@earendil-works/pi-protocol` | Experimental remote-session client and wire protocol (Pi 0.84.0) |
|
|
27
26
|
| `@earendil-works/pi-telemetry` | Vendor-neutral telemetry contracts (Pi 0.84.0) |
|
|
28
27
|
| `@earendil-works/pi-durable` | Experimental durable agent harness: conversations, turns, and tool calls committed to storage and resumed after a crash (Pi 1.0.0; replaces the pi-agent-core harness) |
|
|
29
28
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Codemode and Tool Search
|
|
2
2
|
|
|
3
|
-
`codemode` is a built-in tool (Pi 0.99) that runs model-written JavaScript in a QuickJS sandbox. Scripts call Pi's other tools, filter their results, and return only what the model needs. `tool_search` declares tools the model cannot see yet. Checked against Pi v1.0
|
|
3
|
+
`codemode` is a built-in tool (Pi 0.99) that runs model-written JavaScript in a QuickJS sandbox. Scripts call Pi's other tools, filter their results, and return only what the model needs. `tool_search` declares tools the model cannot see yet. Checked against Pi v1.4.0: [codemode.md](https://github.com/earendil-works/pi/blob/v1.4.0/packages/coding-agent/docs/codemode.md), [cli.md#tools](https://github.com/earendil-works/pi/blob/v1.4.0/packages/coding-agent/docs/cli.md#tools), [extensions.md#tool-exposure](https://github.com/earendil-works/pi/blob/v1.4.0/packages/coding-agent/docs/extensions.md#tool-exposure), [settings.md#tools](https://github.com/earendil-works/pi/blob/v1.4.0/packages/coding-agent/docs/settings.md#tools), [codemode tool source](https://github.com/earendil-works/pi/blob/v1.4.0/packages/coding-agent/src/extensions/codemode/tool.ts).
|
|
4
4
|
|
|
5
5
|
## Enable
|
|
6
6
|
|
|
@@ -10,7 +10,7 @@ Both tools are registered inactive. The default tools stay `read`, `bash`, `edit
|
|
|
10
10
|
{ "defaultTools": ["+codemode"] }
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
- `+name` adds to the default selection
|
|
13
|
+
- `+name` adds to the default selection, also on the command line (`pi --tools +codemode`, Pi 1.1.0). `--tools` with plain names replaces the selection and unregisters unlisted built-in and extension tools, so scripts cannot call them; MCP tools stay unless an entry starts with `mcp__` (Pi 1.0.4). See `references/settings.md`.
|
|
14
14
|
- The MCP extension activates `codemode` for servers with `codemode` exposure and `tool_search` for `deferred` servers (see `references/mcp.md`).
|
|
15
15
|
- SDK sessions need `createCodemodeExtension()` / `createToolSearchExtension()` in the resource loader (see `references/sdk.md`).
|
|
16
16
|
|
|
@@ -19,6 +19,8 @@ Both tools are registered inactive. The default tools stay `read`, `bash`, `edit
|
|
|
19
19
|
| `codemode.mode` | `"on"` | `on`: declared tools stay declared and their descriptions show the script call; `codemode` lists only undeclared tools. `only`: active built-in and extension tools are hidden from the model and listed in the `codemode` description instead |
|
|
20
20
|
| `codemode.inlineBudget` | `3000` | Estimated tokens the `codemode` description may spend on tool declarations; the rest are found with `searchTools()`. `0` lists only namespaces |
|
|
21
21
|
|
|
22
|
+
Since Pi 1.2.0, tool declarations in scripts show parameter constraints (integer, minimum/maximum, length, pattern, default) as `@` tags on property comments, so a tool need not repeat them in its descriptions. Use `Type.Integer` for counts and offsets.
|
|
23
|
+
|
|
22
24
|
## Script API
|
|
23
25
|
|
|
24
26
|
The script body is an async function: top-level `await` and `return` work.
|
|
@@ -26,14 +28,16 @@ The script body is an async function: top-level `await` and `return` work.
|
|
|
26
28
|
- `await tools.<name>(args)` calls a tool. Characters that are not valid in an identifier become `_` (`mcp__dev-radius__search` → `tools.mcp__dev_radius__search`).
|
|
27
29
|
- Since Pi 1.0.0, reading a member of `tools`, `models`, or another global namespace that does not exist throws an error naming the close matches (`tools.Bash` suggests `tools.bash`). Check for a tool with `"name" in tools`; `typeof tools.name` now throws.
|
|
28
30
|
- `ALL_TOOLS` lists `{ name, description }`. `await searchTools(query, { limit, namespace })` ranks tools with BM25; `await describeTool(name)` returns one declaration; `await describeNamespace(name)` returns `{ name, description?, instructions?, tools }` for a namespace such as an MCP server (`mcp__dev-radius`, `dev_radius`, and similar spellings all work).
|
|
29
|
-
- Output: `text(value)`, `image(dataUrlOrImageContent)`, `console.*`, and the returned value. Since Pi 0.99.2 `image()` accepts only base64 PNG, JPEG, GIF, or WebP (a `data:` URL, `{ image_url }`, or an image block); it derives the MIME type from the image bytes, rejects remote URLs, and throws a `TypeError` for anything else, such as an SVG. `exit()` ends early and keeps output.
|
|
31
|
+
- Output: `text(value)`, `image(dataUrlOrImageContent)`, `console.*`, and the returned value. Since Pi 0.99.2 `image()` accepts only base64 PNG, JPEG, GIF, or WebP (a `data:` URL, `{ image_url }`, or an image block); it derives the MIME type from the image bytes, rejects remote URLs, and throws a `TypeError` for anything else, such as an SVG. Since Pi 1.0.3 it also saves each image to a temp file and names the path in the result, so a later turn can copy or move it. `exit()` ends early and keeps output.
|
|
30
32
|
- `store(key, value)` / `load(key)` keep JSON values across `codemode` calls (storing `undefined` deletes). Successful scripts append a `codemode-store` custom entry, so values follow the session branch. Limits: 262144 characters of JSON per value, 1048576 in total; do not store image data.
|
|
31
|
-
- `models.getModelsOfType`, `getAvailableOfType`, `getModelOfType`, `models.classify(model, { state, questions })`, and `models.generateImages(model, { input })` (Pi 1.0.0) reach the model catalog, classifiers, and image models with session credentials. Both calls use only the model's `provider` and `id`, never throw on provider errors (check `stopReason`), and run at most four at a time per script. Their usage and cost are added to the `codemode` result. Show generated images with `image(block)` rather than printing `data`;
|
|
33
|
+
- `models.getModelsOfType`, `getAvailableOfType`, `getModelOfType`, `models.classify(model, { state, questions })`, and `models.generateImages(model, { input })` (Pi 1.0.0) reach the model catalog, classifiers, and image models with session credentials. Both calls use only the model's `provider` and `id`, never throw on provider errors (check `stopReason`), and run at most four at a time per script. Their usage and cost are added to the `codemode` result. Show generated images with `image(block)` rather than printing `data`; generation can take minutes, so avoid a short `timeout_ms`.
|
|
34
|
+
- `models.classify()` also takes `images` (Pi 1.1.0): image blocks judged with the state, for classifiers whose `input` includes `"image"`: `openai/gpt-6-luna` and, since Pi 1.4.0, Jev, Clef, and OpenRouter classifiers that list image input (see `references/providers.md`). Other classifiers return an error result. `await tools.read({ path })` on an image file resolves to such a block (Pi 1.0.4).
|
|
32
35
|
- Malformed `classify()`/`generateImages()` arguments are rejected with the expected shape, and an unknown model points to `models.getAvailableOfType()`.
|
|
33
|
-
- An optional first line `// @options: {"max_output_tokens": 2000, "timeout_ms": 60000}`. Output is capped at 10000 estimated tokens by default (start and end kept, full text in a temp file); there is no timeout unless set. Since Pi 1.0.1 a script fails with a `RangeError` once its output passes 16777216 characters (text plus base64 image data) or 100000 `text()`/`image()`/`console` calls; write large data to a file with a tool.
|
|
36
|
+
- An optional first line `// @options: {"max_output_tokens": 2000, "timeout_ms": 60000}`. It must be the first line: since Pi 1.2.0 an options line anywhere else fails the call before the script runs. Output is capped at 10000 estimated tokens by default (start and end kept, full text in a temp file); there is no timeout unless set. Since Pi 1.0.1 a script fails with a `RangeError` once its output passes 16777216 characters (text plus base64 image data) or 100000 `text()`/`image()`/`console` calls; write large data to a file with a tool.
|
|
34
37
|
- No timers, `fetch`, `process`, `require`, or modules. The VM has 256 MB of memory; a script awaiting a promise that can never settle fails at once; scripts cannot start other `codemode` scripts.
|
|
38
|
+
- At most two codemode workers are alive at once across all sessions in one Pi process (Pi 1.3.0); a cancelled worker holds its slot until it exits. Since Pi 1.4.0 further scripts wait for a free worker in arrival order instead of failing, and fail without running after 60 seconds or their `timeout_ms`. `PI_CODEMODE_MAX_WORKERS` raises the limit, for example for in-process subagents.
|
|
35
39
|
|
|
36
|
-
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:`.
|
|
40
|
+
The result starts with `Script completed` or `Script failed`, the wall time, and the output. Since Pi 1.1.0, with more than one text item each starts with a `==> text N/M <==` line, and `console` calls follow the other output in one `<console_output>` block, one line per call. A failed script keeps its partial output followed by `Script error:`.
|
|
37
41
|
|
|
38
42
|
## What a Script Receives
|
|
39
43
|
|
|
@@ -41,6 +45,7 @@ The result starts with `Script completed` or `Script failed`, the wall time, and
|
|
|
41
45
|
|------|--------------------------------|
|
|
42
46
|
| Declares `outputSchema` | Its `structuredContent`, also when the result has `isError: true` |
|
|
43
47
|
| MCP tool | The whole `CallToolResult` (`content`, `structuredContent`, `isError`) |
|
|
48
|
+
| `read` on an image | An image block `{ type: "image", data, mimeType, note }` that `image()` shows (Pi 1.0.4) |
|
|
44
49
|
| `bash` | `{ output, truncated, full_output_path?, exit_code, wall_time_seconds }`, also for non-zero exits; `output` holds up to 1 MiB |
|
|
45
50
|
| Anything else | Its text content as one string |
|
|
46
51
|
|
|
@@ -88,5 +93,6 @@ For a permission extension this means:
|
|
|
88
93
|
- An extension that registers a tool named `codemode` or `tool_search` replaces the built-in one.
|
|
89
94
|
- `"extensions": ["-builtin:codemode"]` or `["-builtin:tool-search"]` disables one (note the hyphen in the extension name); `pi config` lists them under Built-in.
|
|
90
95
|
- `codemode` uses only `exposure`, `prepareLoadout()`, and `ctx.executeTool()`, and `tool_search` only ranks registered tools (`pi.getAllTools()`) and adds matches with `pi.setActiveTools()`, so an extension can build either under another name.
|
|
96
|
+
- A replacement should declare `reachesTools` (Pi 1.2.0, see `references/extensions.md`): only then does Pi activate it for MCP servers and wait for the servers its calls need.
|
|
91
97
|
|
|
92
|
-
`@earendil-works/pi-codemode` publishes the sandbox (`CodemodeSandbox`) without Pi dependencies, for exposing any functions to model-written scripts.
|
|
98
|
+
`@earendil-works/pi-codemode` publishes the sandbox (`CodemodeSandbox`) without Pi dependencies, for exposing any functions to model-written scripts. Since Pi 1.2.0 its `/portable` entry adds sandboxes with the same API for Cloudflare Workers and other runtimes without worker threads. Breaking in Pi 1.3.0: results and `close()` settle once shutdown is requested, not when the worker exits; a host that must count workers until they exit uses the `onWorkerStarted(exited)` callback or reserves capacity with `acquireWorker()`.
|
|
@@ -25,6 +25,7 @@ import { Text } from "@earendil-works/pi-tui";
|
|
|
25
25
|
|
|
26
26
|
- Use `typebox` (1.x) for schemas. Pi 0.83.0 bundles TypeBox 1.3.7, which removed deprecated APIs (`Type.Base`, `Type.Awaited`, `Type.Promise`, `Type.AsyncIterator`, `Type.Iterator`, `Type.Options`, `Value.Mutate`) — migrate extension sources off those before typechecking against current Pi.
|
|
27
27
|
- Use `StringEnum` from `@earendil-works/pi-ai` for Google-compatible string enums.
|
|
28
|
+
- Since Pi 1.4.0 extensions can import `@earendil-works/pi-ai/models` (for example `clampThinkingLevel`); earlier the extension loader could not resolve that subpath.
|
|
28
29
|
- Pi 0.80.0 moved pi-ai's old global API (`getModel`, `getModels`, `stream`, `complete`, `registerApiProvider`, ...) off the pi-ai root entrypoint to `@earendil-works/pi-ai/compat`. Extensions keep working unchanged at runtime (the extension loader aliases the root to the compat superset), but extension sources that typecheck against pi-ai's published types must import those APIs from `@earendil-works/pi-ai/compat`. `StringEnum` and the type surface remain on the root. The compat entrypoint and loader alias will be removed in a future release.
|
|
29
30
|
|
|
30
31
|
## Quick Start
|
|
@@ -81,7 +82,7 @@ Useful session events:
|
|
|
81
82
|
- `resources_discover`
|
|
82
83
|
- `project_trust` — user/global and CLI extensions only, fired before project resources load; return `{ trusted: "yes" | "no" | "undecided" }` (optionally `remember: true` to persist). The first yes/no decision wins and suppresses the built-in trust prompt.
|
|
83
84
|
|
|
84
|
-
`session_before_compact` and `session_compact` carry `reason` and `willRetry` (Pi 0.79.10) so handlers can distinguish manual `/compact`, threshold auto-compaction, and overflow-retry compaction. `session_compact_failed` (Pi 0.84.3) reports failed or aborted compactions with `reason`, `aborted`, `willRetry`, `fromExtension`, and `errorMessage`.
|
|
85
|
+
`session_before_compact` and `session_compact` carry `reason` and `willRetry` (Pi 0.79.10) so handlers can distinguish manual `/compact`, threshold auto-compaction, and overflow-retry compaction. `session_compact_failed` (Pi 0.84.3) reports failed or aborted compactions with `reason`, `aborted`, `willRetry`, `fromExtension`, and `errorMessage`. Since Pi 1.2.0 `serializeConversation()`, which compaction and branch summaries use, omits thinking blocks.
|
|
85
86
|
|
|
86
87
|
`session_start` carries `event.reason` — `"startup" | "reload" | "new" | "resume" | "fork"` — and `event.previousSessionFile` for `"new"`/`"resume"`/`"fork"`. The old post-transition `session_switch` / `session_fork` events were removed; route all of those flows through `session_start`.
|
|
87
88
|
|
|
@@ -89,10 +90,11 @@ Useful agent events:
|
|
|
89
90
|
|
|
90
91
|
- `input` — carries `event.streamingBehavior` (`"steer" | "followUp" | undefined`) so handlers can distinguish idle prompts, mid-stream steers, and queued follow-ups; return `{ action: "transform" | "handled" | "continue" }`
|
|
91
92
|
- `before_agent_start` — `event.systemPrompt` is read-only; mutate `event.systemPromptOptions` (sections, selected tools, guidelines) so Pi appends a transcript delta that survives resume/branching and keeps the cached prefix (Pi 0.86.0). Returning `{ systemPrompt }` replaces the whole prompt for that run and is sent as the provider's leading system prompt
|
|
93
|
+
- Breaking in Pi 1.2.0: it also fires for runs started by `pi.sendMessage(..., { triggerTurn: true })` while idle. `event.messageType` is `"user"` (with `prompt` and `images`) or `"custom"` (with the starting `message`, no `prompt`). A returned `message` is added to both kinds of run, so check `event.messageType === "user"` before adding one meant for user prompts
|
|
92
94
|
- `agent_start`
|
|
93
95
|
- `agent_end` — one low-level run; Pi may still auto-retry, auto-compact and retry, or continue with queued follow-ups
|
|
94
96
|
- `agent_before_settle` (Pi 0.87.0) — the final actionable boundary before settlement (see below)
|
|
95
|
-
- `agent_settled` (Pi 0.80.4) — notification-only; fired when no retry/compaction/follow-up remains, for status integrations that need to know Pi will not continue automatically. Since Pi 0.87.0, runs requested from `agent_settled` handlers are deferred until every settled handler finishes (`ctx.isIdle()` stays true, no reentrant `agent_start`)
|
|
97
|
+
- `agent_settled` (Pi 0.80.4) — notification-only; fired when no retry/compaction/follow-up remains, for status integrations that need to know Pi will not continue automatically. `event.aborted` (Pi 1.1.0) tells a cancelled run from a finished one. Since Pi 0.87.0, runs requested from `agent_settled` handlers are deferred until every settled handler finishes (`ctx.isIdle()` stays true, no reentrant `agent_start`)
|
|
96
98
|
- `turn_start`
|
|
97
99
|
- `turn_end` — actionable since Pi 0.87.0; carries `turnIndex`, `message`, `toolResults`, and their session entry IDs
|
|
98
100
|
- `message_start`
|
|
@@ -100,7 +102,7 @@ Useful agent events:
|
|
|
100
102
|
- `message_end` — return a replacement message to override usage/cost or rewrite the finalized assistant message
|
|
101
103
|
- `context` — transforms conversation messages; since Pi 0.87.0 handlers no longer see prompt/tool system messages, and Pi restores that state afterward (filtering or slicing messages is safe)
|
|
102
104
|
- `context_with_system` (Pi 0.87.0) — runs after `context` on the full transcript including system messages and is sent verbatim; use only when a request-local transform must own the whole transcript, and keep a system message at index 0
|
|
103
|
-
- `before_provider_headers` (Pi 0.80.4) — mutate `event.headers` in place after outgoing HTTP headers are assembled: set a key to a string to add/override, `null` to delete. Runs once per provider request; retries reuse the same headers
|
|
105
|
+
- `before_provider_headers` (Pi 0.80.4) — mutate `event.headers` in place after outgoing HTTP headers are assembled: set a key to a string to add/override, `null` to delete. Runs once per provider request; retries reuse the same headers. Since Pi 1.4.0 handlers no longer see the OpenRouter, NVIDIA NIM, and OpenCode attribution headers, which pi-ai adds from its client identity; setting one to `null` still removes it
|
|
104
106
|
- `before_provider_request`
|
|
105
107
|
- `after_provider_response` — inspect the provider HTTP status and headers before stream consumption
|
|
106
108
|
- `provider_stream_event` (Pi 0.99.0) — notification-only view of each parsed provider stream event before Pi normalizes it (`provider`, `api`, `model`, `data`). Treat `data` as read-only; handlers are awaited in stream order, so keep them fast. Not persisted
|
|
@@ -120,7 +122,7 @@ Useful tool events:
|
|
|
120
122
|
- `tool_call`
|
|
121
123
|
- `tool_execution_update`
|
|
122
124
|
- `tool_result`
|
|
123
|
-
- `tool_execution_end`
|
|
125
|
+
- `tool_execution_end` — carries `durationMs` (Pi 1.1.0), the tool's execution time, absent when it did not run; tool renderers get it in their render context
|
|
124
126
|
|
|
125
127
|
Calls another tool makes through `ctx.executeTool()` (codemode scripts, orchestrating tools) fire the same tool events, so permission gates see them; see [Nested Calls and Permissions](codemode.md#nested-calls-and-permissions).
|
|
126
128
|
|
|
@@ -143,7 +145,7 @@ Tool results may include `terminate: true` to end the current tool batch without
|
|
|
143
145
|
- `ctx.modelRegistry.findOfType(type, provider, id)`, `getModelsOfType()`, `getAvailableOfType()`, and `classify(model, { state, questions }, { signal })` (Pi 0.99.0), plus `generateImages(model, { input }, options?)` (Pi 1.0.0) — classifier and image models live beside chat models; neither call rejects, so check `result.stopReason`, and report the returned `usage` on the tool result (see `references/providers.md`)
|
|
144
146
|
- `ctx.hasUI` — `true` in TUI and RPC modes
|
|
145
147
|
- `ctx.signal` — the active agent abort signal (or `undefined` when idle); pass it to `fetch`/model calls for abort-aware nested work
|
|
146
|
-
- `ctx.isIdle()` / `ctx.hasPendingMessages()`
|
|
148
|
+
- `ctx.isIdle()` / `ctx.hasPendingMessages()` — since Pi 1.2.0 a session is busy while a run starts, including during `before_agent_start`: `ctx.isIdle()` is false, `pi.sendMessage()` queues into the starting run, `pi.sendUserMessage()` needs `deliverAs`, and an abort requested meanwhile aborts the run once it starts
|
|
147
149
|
- `ctx.abort()` / `ctx.shutdown()`
|
|
148
150
|
- `ctx.getContextUsage()`
|
|
149
151
|
- `ctx.compact(options?)` — trigger compaction without awaiting completion
|
|
@@ -193,16 +195,17 @@ Important rules:
|
|
|
193
195
|
|
|
194
196
|
## Tool Exposure & Structured Results
|
|
195
197
|
|
|
196
|
-
Pi 0.99.0 added fields for tools that other tools, codemode scripts, and permission extensions consume ([docs](https://github.com/earendil-works/pi/blob/v1.0
|
|
198
|
+
Pi 0.99.0 added fields for tools that other tools, codemode scripts, and permission extensions consume ([docs](https://github.com/earendil-works/pi/blob/v1.4.0/packages/coding-agent/docs/extensions.md#tool-exposure), [types](https://github.com/earendil-works/pi/blob/v1.4.0/packages/coding-agent/src/core/extensions/types.ts)):
|
|
197
199
|
|
|
198
200
|
- `outputSchema` (TypeBox) plus `structuredContent` on every result: the model still reads `content`; codemode scripts receive `structuredContent` instead of text. Without `outputSchema`, scripts get the text. `structuredContent` must be JSON-compatible. A `tool_result` handler that replaces `content` must also return `structuredContent`, or it is dropped.
|
|
199
201
|
- `isError: true` on a returned result: the model sees an error, while `details` and `structuredContent` are kept for the UI and scripts.
|
|
200
202
|
- `exposure`: `direct` (default), `model-only`, `codemode`, `deferred`, or `hidden`. `direct` and `model-only` tools activate on registration; the others do not (`defaultActive: false` also keeps a `direct` tool inactive until `--tools`/`defaultTools`/`setActiveTools()` names it). Tools cannot be unregistered: re-register with `exposure: "hidden"` to withdraw one. The table of what each value declares and allows is in `references/codemode.md`.
|
|
201
|
-
- `namespace: { name, description?, instructions? }` groups related tools, as MCP servers do. Codemode lists a namespace under one heading with its short `description`. `instructions` (Pi 0.99.2) holds longer usage guidance that is not listed; scripts read it with `describeNamespace(name)`.
|
|
203
|
+
- `namespace: { name, aliases?, description?, instructions? }` groups related tools, as MCP servers do. Codemode lists a namespace under one heading with its short `description`. `instructions` (Pi 0.99.2) holds longer usage guidance that is not listed; scripts read it with `describeNamespace(name)`. `aliases` (Pi 1.2.0) are other names `describeNamespace()` and `searchTools({ namespace })` match; MCP namespaces use the server name.
|
|
202
204
|
- `annotations`: `readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`, with MCP meanings. Missing hints mean "not read-only, may be destructive, open world". They are unverified; mark writes neither read-only nor casually idempotent.
|
|
203
|
-
- `prepareLoadout(loadout)`: for tools that orchestrate others. Runs whenever the active tools change with `declared`, `callable`, and `registered` tools, and returns replacement `descriptions` and `hiddenDeclarations` (active tools left out of requests but still callable). The built-in `codemode` uses only this, `exposure`, and `ctx.executeTool()`.
|
|
205
|
+
- `prepareLoadout(loadout)`: for tools that orchestrate others. Runs whenever the active tools change with `declared`, `callable`, and `registered` tools, and returns replacement `descriptions` and `hiddenDeclarations` (active tools left out of requests but still callable). The built-in `codemode` uses only this, `exposure`, and `ctx.executeTool()`. Since Pi 1.0.4 hidden declarations are left out of the system prompt's tool rules, and `loadout.getPromptGuidelines()` returns each tool's guidelines so the orchestrator can show them with its declarations.
|
|
206
|
+
- `reachesTools(input)` (Pi 1.2.0): for tools that reach other tools, which ones a call may reach — `"all"`, or the words of the input that may name tools or namespaces (a word refers to every tool or namespace whose name it contains: `mcp__radius__search` reaches the `mcp__radius` server, `radius` alone does not). Extensions that register tools late, such as the MCP extension for servers still connecting, read it from their `tool_call` handler to finish registering those tools before the call runs.
|
|
204
207
|
|
|
205
|
-
`pi.getAllTools()` reports each tool's `exposure`, `namespace`, and `
|
|
208
|
+
`pi.getAllTools()` reports each tool's `exposure`, `namespace`, `annotations`, and `reachesTools`. `pi.setActiveTools()` ignores unknown and `hidden` names. Compilable examples: `examples/structured-tool.ts` and `examples/nested-tools.ts`.
|
|
206
209
|
|
|
207
210
|
## Dynamic Tool Loading
|
|
208
211
|
|
|
@@ -215,7 +218,7 @@ Pi 0.80.7 lets an extension register many tools while keeping only a small initi
|
|
|
215
218
|
3. During loader execution, call `pi.setActiveTools([...pi.getActiveTools(), ...matches])`. Unknown names are ignored. Additive changes are the most cache-friendly everywhere.
|
|
216
219
|
4. Pi records the added tools on that tool result and exposes their definitions before the next model response. Since Pi 0.86.0 prompt and tool changes are transcript-backed system messages, so they survive resume and branch navigation.
|
|
217
220
|
|
|
218
|
-
|
|
221
|
+
Adding tools keeps the cached prompt prefix on models that support native tool changes, which Pi's generated `compat` metadata marks (recent Claude, GPT, and Kimi models). On Anthropic's own API, Opus 4.8+, Sonnet and Haiku 5.5, and Fable 5+ use the `inline-tools-2026-09-15` beta (Pi 1.0.1), so removals and same-name redefinitions keep the prefix too. Since Pi 1.4.0 OpenAI Responses models with `additional_tools` declare every tool in those items and announce removals as text, so removals and redefinitions keep the prefix there as well. Any change a model cannot take natively falls back to a checkpoint with the full tool list: activation still works, but may invalidate the cached prefix. pi-ai's `hasToolRedefinitions()` is deprecated.
|
|
219
222
|
|
|
220
223
|
Cache tips: keep the loader active for the whole session; add rather than replace. Activating a tool that has `promptSnippet`/`promptGuidelines` rebuilds the system prompt and can invalidate the prefix even with native support — lazily loaded tools should rely on their `description` alone.
|
|
221
224
|
|
|
@@ -227,7 +230,7 @@ Cache tips: keep the loader active for the whole session; add rather than replac
|
|
|
227
230
|
- `{ type: "grammar", variants: { openai_lark?: string, openai_regex?: string } }` — OpenAI Lark/regex grammar encodings
|
|
228
231
|
- Set `false` to explicitly disable for that tool
|
|
229
232
|
|
|
230
|
-
Model capability metadata gates what is actually sent: `compat.supportsStrictTools` / Anthropic built-ins enable strict JSON-schema tools, and `compat.supportsOpenAIGrammarTools` marks endpoints that accept Lark/regex grammar tools (enabled in generated metadata for GPT-5+ across OpenAI, Codex, Azure, GitHub Copilot, opencode, Cloudflare AI Gateway). With `strict: "prefer"`, unsupported combinations fall back to normal function tools; with `strict: "require"`, the request fails when the model has no strict tools or the schema uses keywords strict mode rejects. Since Pi 0.99.2 Anthropic strict tools also reject keywords such as `minimum`/`maximum`, so such `prefer` tools are sent non-strict. Since Pi 0.86.0, strict-prefer JSON-schema sampling is on by default for the built-in `read`, `bash`, `powershell`, `edit`, and `write` tools (no `PI_EXPERIMENTAL` needed); re-register a definition with `constrainedSampling: false` to opt out.
|
|
233
|
+
Model capability metadata gates what is actually sent: `compat.supportsStrictTools` / Anthropic built-ins enable strict JSON-schema tools, and `compat.supportsOpenAIGrammarTools` marks endpoints that accept Lark/regex grammar tools (enabled in generated metadata for GPT-5+ across OpenAI, Codex, Azure, GitHub Copilot, opencode, Cloudflare AI Gateway). With `strict: "prefer"`, unsupported combinations fall back to normal function tools; with `strict: "require"`, the request fails when the model has no strict tools or the schema uses keywords strict mode rejects. Since Pi 0.99.2 Anthropic strict tools also reject keywords such as `minimum`/`maximum`, so such `prefer` tools are sent non-strict. Since Pi 0.86.0, strict-prefer JSON-schema sampling is on by default for the built-in `read`, `bash`, `powershell`, `edit`, and `write` tools (no `PI_EXPERIMENTAL` needed); re-register a definition with `constrainedSampling: false` to opt out. Since Pi 1.2.0, when Anthropic rejects a strict tool set for grammar size, tool count, or union limits, Pi retries once with `prefer` tools non-strict; `require` tools stay strict.
|
|
231
234
|
|
|
232
235
|
## Display Transformers
|
|
233
236
|
|
|
@@ -248,6 +251,8 @@ Context carries `messageType` (`"user" | "assistant" | "assistant-thinking"`), `
|
|
|
248
251
|
|
|
249
252
|
Check `ctx.hasUI` first. Non-interactive modes may not support UI.
|
|
250
253
|
|
|
254
|
+
In interactive mode, a `select`/`confirm`/`input`/`editor` dialog or inline `custom()` component opened while another is visible shows on top, and closing it shows the previous one again (Pi 1.4.0). Before, the second replaced the first and the first promise never settled, so tools running in parallel could hang on their prompts.
|
|
255
|
+
|
|
251
256
|
```typescript
|
|
252
257
|
const choice = await ctx.ui.select("Pick one:", ["A", "B"]);
|
|
253
258
|
const ok = await ctx.ui.confirm("Delete?", "This cannot be undone");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# MCP Servers
|
|
2
2
|
|
|
3
|
-
Pi connects to MCP servers through a built-in extension. There is no package to install. Checked against Pi v1.0
|
|
3
|
+
Pi connects to MCP servers through a built-in extension. There is no package to install. Checked against Pi v1.4.0: [mcp.md](https://github.com/earendil-works/pi/blob/v1.4.0/packages/coding-agent/docs/mcp.md), [cli.md#mcp-commands](https://github.com/earendil-works/pi/blob/v1.4.0/packages/coding-agent/docs/cli.md#mcp-commands), [extensions.md#mcp-servers](https://github.com/earendil-works/pi/blob/v1.4.0/packages/coding-agent/docs/extensions.md#mcp-servers).
|
|
4
4
|
|
|
5
5
|
## Configure
|
|
6
6
|
|
|
@@ -53,7 +53,7 @@ pi mcp remove docs # keeps stored OAuth credentials
|
|
|
53
53
|
pi mcp logout sentry
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
`pi mcp add` also takes `--oauth-client-id`, `--oauth-client-secret`, `--oauth-callback-port`, and `--oauth-client-name`.
|
|
56
|
+
`pi mcp add` also takes `--oauth-client-id`, `--oauth-client-secret`, `--oauth-callback-port`, and `--oauth-client-name`. `add` does not connect; verify with `pi mcp list`. `pi mcp login --timeout <seconds>` (default 300) limits the whole sign-in since Pi 1.1.0, not only the wait for the browser. A running session picks up added or changed servers after `/reload` or a new session.
|
|
57
57
|
|
|
58
58
|
In a session, `/mcp` opens the server manager (state, tools, errors, sign in/out, reconnect, exposure, enable/disable). Changes are written back to the `mcp.json` that defines the server, or to the project override once one exists for a user-level server. Outside the TUI, `/mcp` prints status and `/mcp login|logout|reconnect <server>` run directly.
|
|
59
59
|
|
|
@@ -104,8 +104,8 @@ Undeclared tools (`codemode`, `deferred`) are reachable through either tool: cod
|
|
|
104
104
|
## Results
|
|
105
105
|
|
|
106
106
|
- Text results over 20KB reach the model with the middle cut out; the full text goes to a temp file named in the result.
|
|
107
|
-
- Codemode scripts receive the whole `CallToolResult` (`content`, `structuredContent`, `isError`). A result with `isError` resolves in scripts and is reported to the model as an error for direct calls. `image(result.content[0])` forwards an image block. Server instructions are not part of any tool description; scripts read them with `describeNamespace("mcp__<server>")`.
|
|
108
|
-
- Servers with resources add `list_mcp_resources`, `list_mcp_resource_templates`, and `read_mcp_resource` (marked read-only).
|
|
107
|
+
- Codemode scripts receive the whole `CallToolResult` (`content`, `structuredContent`, `isError`). Since Pi 1.2.0 `structuredContent` may be any JSON value, not only an object. A result with `isError` resolves in scripts and is reported to the model as an error for direct calls. `image(result.content[0])` forwards an image block. Server instructions are not part of any tool description; scripts read them with `describeNamespace("mcp__<server>")`.
|
|
108
|
+
- Servers with resources add `list_mcp_resources`, `list_mcp_resource_templates`, and `read_mcp_resource` (marked read-only). Since Pi 1.2.0 `pi mcp list --json` reports `resources: true` instead of resource counts.
|
|
109
109
|
- Tool calls are never retried (the server may have run them); resource reads retry once on 408/429/5xx.
|
|
110
110
|
|
|
111
111
|
## Permissions
|
|
@@ -129,6 +129,7 @@ pi.unregisterMcpServer("jira");
|
|
|
129
129
|
|
|
130
130
|
- `"extensions": ["-builtin:mcp"]` in settings, or `pi config` → Built-in, disables the built-in support. `pi mcp` shell commands still work.
|
|
131
131
|
- `--no-extensions` disables built-ins too; `pi -ne -e builtin:mcp` keeps only MCP.
|
|
132
|
+
- `--no-mcp` (Pi 1.0.4) disables built-in MCP for one run; `--exclude-tools 'mcp__*'` removes only its tools.
|
|
132
133
|
- An extension that registers the `/mcp` command (for example `pi-mcp-adapter`) replaces the built-in support: `mcp.json` is then not read in sessions.
|
|
133
134
|
|
|
134
135
|
## SDK
|
|
@@ -96,7 +96,7 @@ If there is no `pi` manifest, Pi auto-discovers:
|
|
|
96
96
|
|
|
97
97
|
## Dependencies
|
|
98
98
|
|
|
99
|
-
Checked against Pi v1.0
|
|
99
|
+
Checked against Pi v1.4.0 [packages.md](https://github.com/earendil-works/pi/blob/v1.4.0/packages/coding-agent/docs/packages.md#declare-dependencies).
|
|
100
100
|
|
|
101
101
|
- Normal runtime dependencies belong in `dependencies`
|
|
102
102
|
- Pi supplies these to extensions; declare them as peer deps with `"*"` ranges and never bundle them:
|
|
@@ -110,7 +110,7 @@ Checked against Pi v1.0.2 [packages.md](https://github.com/earendil-works/pi/blo
|
|
|
110
110
|
- Git package installs omit dev dependencies — runtime needs go in `dependencies`, not `devDependencies`
|
|
111
111
|
- Installed packages load with separate module roots. Do not rely on two packages sharing one dependency instance, or on one package resolving another package's undeclared dependency.
|
|
112
112
|
- Other Pi packages used as dependencies must be included in the published tarball and referenced through their `node_modules` resource paths.
|
|
113
|
-
- Pin dev dependencies to the Pi line you test against (e.g. `^1.0
|
|
113
|
+
- Pin dev dependencies to the Pi line you test against (e.g. `^1.4.0`) so typecheck/tests validate against what users run; keep peers `"*"` so installs stay compatible across Pi versions. `"*"` does not prove compatibility: document the minimum supported Pi version and feature-check newer APIs (for example `ctx.executeTool`, `pi.registerMcpServer`) if you support older hosts.
|
|
114
114
|
|
|
115
115
|
## Package Filtering
|
|
116
116
|
|
|
@@ -23,7 +23,7 @@ Practical recipes for extending Pi. Each pattern is a self-contained example.
|
|
|
23
23
|
17. [Nested Tool Calls](#nested-tool-calls)
|
|
24
24
|
18. [Classifier Gate](#classifier-gate)
|
|
25
25
|
|
|
26
|
-
Patterns 16–18 are full files under `examples/` that type-check against Pi 1.0
|
|
26
|
+
Patterns 16–18 are full files under `examples/` that type-check against Pi 1.2.0 in this skill's package; read them rather than retyping.
|
|
27
27
|
|
|
28
28
|
---
|
|
29
29
|
|
|
@@ -10,9 +10,9 @@ Use `/login` in interactive mode, then select a provider. On OAuth sign-in scree
|
|
|
10
10
|
- OpenAI with a ChatGPT subscription (Pi 0.99.0) — `/login openai` offers **Sign in with ChatGPT**, an OAuth login that uses the subscription with the OpenAI API on the regular `openai` provider. Pi stores a stable `deviceId` for it in global settings. Subscription usage-limit errors are not retried
|
|
11
11
|
- OpenAI Codex (legacy) — the older `openai-codex` provider, renamed in Pi 0.99.0 and superseded by Sign in with ChatGPT. Its `/login` still offers browser auth or a device-code flow; its default model is `gpt-6.1-sol` (Pi 0.99.1). Prefer Sign in with ChatGPT on `openai` for new setups
|
|
12
12
|
- GitHub Copilot
|
|
13
|
-
- OpenRouter (Pi 0.82.0) — `/login openrouter` runs a PKCE flow that mints a user-controlled API key billed from OpenRouter credits; on headless/SSH hosts paste the redirect URL or authorization code into the prompt (Pi 0.83.0). `OPENROUTER_API_KEY` still works via **Use an API key
|
|
13
|
+
- OpenRouter (Pi 0.82.0) — `/login openrouter` runs a PKCE flow that mints a user-controlled API key billed from OpenRouter credits; on headless/SSH hosts paste the redirect URL or authorization code into the prompt (Pi 0.83.0). `OPENROUTER_API_KEY` still works via **Use an API key**. Since Pi 1.2.0 Pi fetches the chat models a key may use, with that key's limits and prices; a regional `baseUrl` in `models.json` (e.g. `https://us.openrouter.ai/api/v1`) narrows them to that region
|
|
14
14
|
- xAI (Grok/X subscription, Pi 0.80.8) — `/login xai` then **Use a subscription** (device-code OAuth); `XAI_API_KEY` remains available via **Use an API key**
|
|
15
|
-
- Radius (Pi 0.80.8) — a dynamic `pi-messages` gateway; `/login radius` stores OAuth tokens, and custom Radius gateways can be declared in `models.json` with `"oauth": "radius"` plus a gateway `baseUrl`. Since Pi 0.86.0 a public Radius catalog ships for immediate/offline selection
|
|
15
|
+
- Radius (Pi 0.80.8) — a dynamic `pi-messages` gateway; `/login radius` stores OAuth tokens, and custom Radius gateways can be declared in `models.json` with `"oauth": "radius"` plus a gateway `baseUrl`. Since Pi 0.86.0 a public Radius catalog ships for immediate/offline selection, and since Pi 1.1.0 an account's fetched or cached catalog replaces it; custom gateways use their own catalog. Since Pi 1.0.0 `/login` lists **Sign in with Radius** at the top level and, after sign-in, offers to add the Radius MCP server to the global `mcp.json` with `"auth": { "provider": "radius" }`
|
|
16
16
|
- Meta Muse (Pi 0.86.1) — `/login meta` with automatic Model API key refresh; `META_API_KEY` also works
|
|
17
17
|
|
|
18
18
|
`/login <provider>` with autocomplete works since Pi 0.80.4, and login methods are provider-owned since 0.80.8 (registered pi-ai providers expose their own auth options and status in `/login`). Use `/logout` to clear stored OAuth credentials. Pi 0.71.0 removed built-in Google Gemini CLI and Google Antigravity providers. `pi auth check [provider|model]` (Pi 0.84.1) preflights credential readiness; `pi auth print-api-key` / `pi auth print-bearer-token` (Pi 0.83.0) export resolved credentials to external clients, refreshing OAuth as needed.
|
|
@@ -25,7 +25,7 @@ Since Pi 0.80.8, built-in catalogs are complemented by dynamic ones: `/model` re
|
|
|
25
25
|
|----------|---------|-----------------|
|
|
26
26
|
| Anthropic | `ANTHROPIC_API_KEY` | `anthropic` |
|
|
27
27
|
| Ant Ling | `ANT_LING_API_KEY` | `ant-ling` |
|
|
28
|
-
| Azure OpenAI
|
|
28
|
+
| Azure OpenAI | `AZURE_OPENAI_API_KEY` | `azure` |
|
|
29
29
|
| OpenAI | `OPENAI_API_KEY` | `openai` |
|
|
30
30
|
| DeepSeek | `DEEPSEEK_API_KEY` | `deepseek` |
|
|
31
31
|
| NVIDIA NIM | `NVIDIA_API_KEY` | `nvidia` |
|
|
@@ -49,7 +49,7 @@ Since Pi 0.80.8, built-in catalogs are complemented by dynamic ones: `/model` re
|
|
|
49
49
|
| Fireworks | `FIREWORKS_API_KEY` | `fireworks` |
|
|
50
50
|
| Together AI | `TOGETHER_API_KEY` | `together` |
|
|
51
51
|
| Baseten | `BASETEN_API_KEY` | `baseten` |
|
|
52
|
-
| Kimi For Coding | `KIMI_API_KEY` | `kimi-coding` |
|
|
52
|
+
| Kimi For Coding | `KIMI_API_KEY` | `kimi-coding` (Chat Completions since Pi 1.3.0; base URL overrides must include `/v1`) |
|
|
53
53
|
| Meta (Muse) | `META_API_KEY` | `meta` |
|
|
54
54
|
| MiniMax | `MINIMAX_API_KEY` | `minimax` |
|
|
55
55
|
| MiniMax (China) | `MINIMAX_CN_API_KEY` | `minimax-cn` |
|
|
@@ -60,6 +60,8 @@ Since Pi 0.80.8, built-in catalogs are complemented by dynamic ones: `/model` re
|
|
|
60
60
|
|
|
61
61
|
With no Anthropic key or token set, Pi 0.99.2+ uses Anthropic workload identity federation when `ANTHROPIC_FEDERATION_RULE_ID`, `ANTHROPIC_ORGANIZATION_ID`, and `ANTHROPIC_IDENTITY_TOKEN_FILE` are set (plus optional `ANTHROPIC_SERVICE_ACCOUNT_ID` and `ANTHROPIC_WORKSPACE_ID`). The Anthropic SDK exchanges and refreshes the token, re-reading the identity token file, so keep that file fresh for long sessions.
|
|
62
62
|
|
|
63
|
+
Breaking in Pi 1.0.3: the Azure provider `azure-openai-responses` is now `azure`; rename it in `auth.json`, `models.json`, and `settings.json`. The API id and `AZURE_OPENAI_*` variables are unchanged. It also serves Microsoft Foundry models through Chat Completions (`api: "openai-completions"`), and pi-ai exports it from `@earendil-works/pi-ai/providers/azure`.
|
|
64
|
+
|
|
63
65
|
Model IDs that changed in Pi 1.0.1, for explicit `--model`, `enabledModels`, and `modelOverrides` entries: Cloudflare AI Gateway Claude models use dashed IDs (`claude-opus-5-5`, not `claude-opus-5.5`); Together's DeepSeek V4 Pro is `deepseek-ai/DeepSeek-V4-Pro-0813`; NVIDIA's default is `nvidia/nemotron-3-ultra-550b-a55b`, since NVIDIA no longer serves `nvidia/nemotron-3-super-120b-a12b`.
|
|
64
66
|
|
|
65
67
|
## Auth File
|
|
@@ -155,7 +157,7 @@ export GOOGLE_CLOUD_LOCATION=us-central1
|
|
|
155
157
|
|
|
156
158
|
## Custom Providers via `models.json`
|
|
157
159
|
|
|
158
|
-
Use `models.json` for OpenAI-compatible, Anthropic-compatible, Google-compatible, or other supported APIs:
|
|
160
|
+
Use `models.json` for OpenAI-compatible, Anthropic-compatible, Google-compatible, or other supported APIs. Every provider, model, override, and `compat` field is listed in [models-configuration.md](https://github.com/earendil-works/pi/blob/v1.4.0/packages/coding-agent/docs/models-configuration.md) (Pi 1.4.0):
|
|
159
161
|
|
|
160
162
|
```json
|
|
161
163
|
{
|
|
@@ -209,13 +211,13 @@ Common `api` values:
|
|
|
209
211
|
- `modelOverrides` applies to built-in and (since Pi 0.80.4) extension-registered provider models; per-model fields: `name`, `reasoning`, `thinkingLevelMap`, `input`, `cost` (partial), `contextWindow`, `maxTokens`, `samplingParams`, `samplingParamsByThinkingLevel` (Pi 1.0.2), `headers`, `compat`, `inputLimits`, `promptCache`.
|
|
210
212
|
- `inputLimits.images.resize` (Pi 0.87.0) sets per-model cache-safe image encoding — `maxWidth`, `maxHeight`, `maxBytes` (base64 payload), `jpegQuality`; defaults 2000×2000, 4.5 MiB, quality 80 — applied once to attachments, `read` images, and tool-result images (switching models does not re-encode history).
|
|
211
213
|
- `promptCache: { short?, long? }` (Pi 0.86.0) declares best-effort cache lifetimes in seconds per retention tier; models without a lifetime for the active tier are not eligible for cache warming (see `cacheWarming` in `references/settings.md`).
|
|
212
|
-
- `compat.allowedFallbackModels` (Pi 0.86.0) overrides or disables Anthropic server-side fallback models; `compat.supportsMidConvoEffort` (Pi 0.84.4) marks custom Anthropic Messages models that accept per-turn effort changes.
|
|
214
|
+
- `compat.allowedFallbackModels` (Pi 0.86.0) overrides or disables Anthropic server-side fallback models; `compat.supportsMidConvoEffort` (Pi 0.84.4) marks custom Anthropic Messages models that accept per-turn effort changes; on `openai-responses` (Pi 1.3.0, default `false`) it sends thinking level changes as `configuration_update` input items so they keep the prompt cache, and Sign in with ChatGPT ignores it.
|
|
213
215
|
- OpenAI-compatible local servers: `vllmPriority` and `supportsMaxOutputTokens` (Pi 0.85.0) plus configurable thinking-token budget fields for vLLM, Qwen/SGLang, and llama.cpp (Pi 0.84.3).
|
|
214
216
|
- `openRouterRouting` is forwarded as-is in the OpenRouter `provider` field (fallbacks, ZDR, ignore lists, throughput/latency).
|
|
215
217
|
- `compat.thinkingFormat` supports OpenAI-compatible reasoning variants: `openrouter` sends `reasoning: { effort }`, `together` sends `reasoning: { enabled }` plus `reasoning_effort` when supported, `qwen-chat-template` targets local Qwen-compatible servers that read `chat_template_kwargs.enable_thinking`, `chat-template` (Pi 0.79.9) sends configurable `chat_template_kwargs` via `compat.chatTemplateKwargs` — e.g. `{ "thinking": { "$var": "thinking.enabled" } }` for DeepSeek models behind vLLM/Hugging Face chat templates — and `baseten` (Pi 0.84.0) sends `chat_template_args` via `compat.chatTemplateArgs`.
|
|
216
218
|
- **Breaking (Pi 0.80.7):** `compat.sendSessionIdHeader` was removed. Session affinity is now controlled by `compat.sessionAffinityFormat` (`"openai"` sends `session_id`/`x-client-request-id`, `"openai-nosession"` omits the underscore-containing `session_id` header, `"openrouter"` sends `x-session-id`; default auto-detected). Replace `sendSessionIdHeader: false` with `sessionAffinityFormat: "openai-nosession"`. `sendSessionAffinityHeaders` still gates the behavior for `openai-completions`.
|
|
217
|
-
- `compat.
|
|
218
|
-
- Advanced `compat` flags exist for proxy quirks (`cacheControlFormat` — now also covering tool-result text content, `supportsReasoningEffort`, `supportsLongCacheRetention`, `supportsEagerToolInputStreaming`, `supportsStrictMode`). See [Pi `docs/models.md`](https://github.com/earendil-works/pi/blob/v1.0
|
|
219
|
+
- The `compat.supportsMidConvo*`, `supportsToolSearch`, and `supportsAdditionalTools` flags enable native tool changes; set them only for verified custom endpoints (see `references/extensions.md#dynamic-tool-loading`). `compat.supportsStrictTools` / `compat.supportsOpenAIGrammarTools` / per-model `constrainedSampling` gate constrained sampling (see `references/extensions.md`). `compat.supportsFinishReason: false` (Pi 0.84.0) tells pi to infer stop/toolUse for OpenAI-compatible streams that omit `finish_reason`.
|
|
220
|
+
- Advanced `compat` flags exist for proxy quirks (`cacheControlFormat` — now also covering tool-result text content, `supportsReasoningEffort`, `supportsLongCacheRetention`, `supportsEagerToolInputStreaming`, `supportsStrictMode`). See [Pi `docs/models.md`](https://github.com/earendil-works/pi/blob/v1.4.0/packages/coding-agent/docs/models.md) when a proxy rejects pi's defaults.
|
|
219
221
|
|
|
220
222
|
### Context Overflow Recovery
|
|
221
223
|
|
|
@@ -223,11 +225,11 @@ Pi can auto-compact and retry when a provider fails with a recognized context-wi
|
|
|
223
225
|
|
|
224
226
|
### llama.cpp
|
|
225
227
|
|
|
226
|
-
Pi supports the llama.cpp router server (Pi 0.81.0): configure with `/login llama.cpp`, search/download Hugging Face models and load/unload with live progress via `/llama`, then select loaded models in `/model`. The model catalog persists across restarts (fixed in Pi 0.82.1). Every llama.cpp chat model is also listed as a classifier model (see [Classifier Models](#classifier-models)). `"extensions": ["-builtin:llama.cpp"]` removes the provider and `/llama`. See [Pi `docs/llama-cpp.md`](https://github.com/earendil-works/pi/blob/v1.0
|
|
228
|
+
Pi supports the llama.cpp router server (Pi 0.81.0): configure with `/login llama.cpp`, search/download Hugging Face models and load/unload with live progress via `/llama`, then select loaded models in `/model`. The model catalog persists across restarts (fixed in Pi 0.82.1). Every llama.cpp chat model is also listed as a classifier model (see [Classifier Models](#classifier-models)). Since Pi 1.1.0, decision models such as Julia-1, Laya, Kev, lev, and OpenJev served by llama.cpp 0.6.0+ are listed only as classifiers, answering natively through `/v1/systemone` with the `typesafe-system-one` API. `"extensions": ["-builtin:llama.cpp"]` removes the provider and `/llama`. See [Pi `docs/llama-cpp.md`](https://github.com/earendil-works/pi/blob/v1.4.0/packages/coding-agent/docs/llama-cpp.md).
|
|
227
229
|
|
|
228
230
|
## Model Types
|
|
229
231
|
|
|
230
|
-
Since Pi 0.99.0 catalogs hold typed models: `type` is `"chat"` (the default when omitted), `"image"`, or `"classifier"`. Chat-facing reads (`getModels()`, `getAvailable()`, `ctx.modelRegistry.getAll()`, `/model`) stay chat-only. `ModelRuntime`, `ctx.modelRegistry`, and pi-ai `Models` all have `getModelsOfType(type, provider?)`, `getModelOfType(type, provider, id)`, and async `getAvailableOfType(type, provider?)`. Only `ModelRuntime` and `Models` have `getAllModels()` and `getAllAvailable()` for every type; `ctx.modelRegistry` adds `findOfType(type, provider, id)` instead. pi-ai exports `isModelType()` and `getModelType()` for mixed lists. One upstream ID may have separate chat and image entries. Sources: [models.md](https://github.com/earendil-works/pi/blob/v1.0
|
|
232
|
+
Since Pi 0.99.0 catalogs hold typed models: `type` is `"chat"` (the default when omitted), `"image"`, or `"classifier"`. Chat-facing reads (`getModels()`, `getAvailable()`, `ctx.modelRegistry.getAll()`, `/model`) stay chat-only. `ModelRuntime`, `ctx.modelRegistry`, and pi-ai `Models` all have `getModelsOfType(type, provider?)`, `getModelOfType(type, provider, id)`, and async `getAvailableOfType(type, provider?)`. Only `ModelRuntime` and `Models` have `getAllModels()` and `getAllAvailable()` for every type; `ctx.modelRegistry` adds `findOfType(type, provider, id)` instead. pi-ai exports `isModelType()` and `getModelType()` for mixed lists. One upstream ID may have separate chat and image entries. Sources: [models.md](https://github.com/earendil-works/pi/blob/v1.4.0/packages/coding-agent/docs/models.md#use-classifier-models), [pi-ai types](https://github.com/earendil-works/pi/blob/v1.4.0/packages/ai/src/types.ts).
|
|
231
233
|
|
|
232
234
|
### Classifier Models
|
|
233
235
|
|
|
@@ -236,11 +238,12 @@ Classifiers do not chat. They answer typed questions about JSON state with proba
|
|
|
236
238
|
| Provider | Model IDs | Auth |
|
|
237
239
|
|----------|-----------|------|
|
|
238
240
|
| `typesafe` | `jev-latest` | `TYPESAFE_API_KEY` |
|
|
239
|
-
| `openrouter` | `typesafe/jev-1.13`, `~typesafe/jev-latest` | `OPENROUTER_API_KEY` or `/login` |
|
|
240
|
-
| `cloudflare-workers-ai` | `typesafe/jev`, `@cf/cloudflare/clef`, `@cf/cloudflare/clef-flash` (Clef, Pi 1.0.1) | `CLOUDFLARE_API_KEY` + `CLOUDFLARE_ACCOUNT_ID` (or the credential's `env` in `auth.json`) |
|
|
241
|
+
| `openrouter` | `typesafe/jev-1.13`, `typesafe/jev-1.14`, `~typesafe/jev-latest`, `cloudflare/clef`, `cloudflare/clef-flash`, `cloudflare/clef-omni` (Pi 1.4.0) | `OPENROUTER_API_KEY` or `/login` |
|
|
242
|
+
| `cloudflare-workers-ai` | `typesafe/jev`, `typesafe/jev-1.14.0`, `@cf/cloudflare/clef`, `@cf/cloudflare/clef-flash` (Clef, Pi 1.0.1), `@cf/cloudflare/clef-omni` (Pi 1.4.0) | `CLOUDFLARE_API_KEY` + `CLOUDFLARE_ACCOUNT_ID` (or the credential's `env` in `auth.json`) |
|
|
241
243
|
| `vercel-ai-gateway` | `typesafe-ai/jev` | `AI_GATEWAY_API_KEY` |
|
|
242
244
|
| `opencode` | `jev-1.13`, `jev-1.13-free` | `OPENCODE_API_KEY` |
|
|
243
|
-
|
|
|
245
|
+
| `openai` | `gpt-6-luna` (Pi 1.1.0) | `OPENAI_API_KEY`, not Sign in with ChatGPT |
|
|
246
|
+
| llama.cpp router | decision models (Julia-1, Laya, Kev, lev, OpenJev; Pi 1.1.0) and every loaded chat model ID | `/login llama.cpp` |
|
|
244
247
|
|
|
245
248
|
```typescript
|
|
246
249
|
const jev = ctx.modelRegistry.findOfType("classifier", "typesafe", "jev-latest");
|
|
@@ -266,21 +269,23 @@ if (jev) {
|
|
|
266
269
|
|
|
267
270
|
- Question types: `choice` (`criteria` maps labels to descriptions; answer has `choice`, `probabilities`, `confidence`), `bool` (`criteria: { true, false }`; answer has `probability`), and `score` (`criteria` is an ordered list; answer has `score`, `confidence`). Pi translates `bool` to TypeSafe's wire format.
|
|
268
271
|
- `classify()` never rejects. Failures and aborts return `stopReason: "error"` or `"aborted"` with `errorMessage`. Check `stopReason` and each answer's `type` before use, and validate probabilities yourself if a decision depends on them (for example finite and within `[0, 1]`).
|
|
272
|
+
- `classify(model, { state, questions, images })` judges image blocks with the state for classifiers whose `input` includes `"image"`; others return an error result. That covers GPT-6 Luna (Pi 1.1.0) and, since Pi 1.4.0, Jev on `typesafe` (experimental there) and `cloudflare-workers-ai`, Clef on `cloudflare-workers-ai`, and OpenRouter classifiers whose catalog lists image input. Jev takes PNG and JPEG; Clef takes PNG, JPEG, and WebP, at most four per request. Jev on `vercel-ai-gateway` and `opencode` takes text only.
|
|
273
|
+
- Each System One service takes images in its own wire form, set by the classifier's `compat.imageInput` (`"assets"`, `"images"`, or `"state-image-urls"`) plus `compat.sendImageOptInHeader` (Pi 1.4.0). Built-in models set them, so a `baseUrl` override keeps them; a custom provider proxying another service's classifiers sets them in its provider-level `compat` in `models.json`.
|
|
269
274
|
- `result.usage` carries token counts and catalog cost when the service reports them; TypeSafe's direct `jev-latest` has no catalog price.
|
|
270
275
|
- `jev-latest` moves with the service. Pin a versioned ID such as `openrouter`'s `typesafe/jev-1.13` when thresholds were tuned against one model.
|
|
271
|
-
- Codemode scripts reach classifiers through `models.classify()` (see `references/codemode.md`). The SDK equivalent is `modelRuntime.classify()`. A llama.cpp classifier accepts a per-request `temperature` to soften overconfident label probabilities.
|
|
276
|
+
- Codemode scripts reach classifiers through `models.classify()` (see `references/codemode.md`). The SDK equivalent is `modelRuntime.classify()`. A llama.cpp chat-model classifier accepts a per-request `temperature` to soften overconfident label probabilities; System One and OpenAI Decisions ignore it.
|
|
272
277
|
|
|
273
278
|
A gate that fails closed on classifier errors is in `examples/classifier-gate.ts`.
|
|
274
279
|
|
|
275
280
|
### Image Generation
|
|
276
281
|
|
|
277
|
-
`ModelRuntime.generateImages(model, { input }, options?)` (Pi 0.99.0) generates images with runtime-resolved auth; list image models with `getModelsOfType("image")`. OpenRouter image models such as `google/gemini-2.5-flash-image` appear under the `openrouter` provider with its credential. Like `classify()`, it returns an error result rather than rejecting. Image models do not appear in `/model`: extensions call `ctx.modelRegistry.generateImages()` and codemode scripts call `models.generateImages()` (both Pi 1.0.0). Codemode adds a script's image usage to its result; an extension tool must report the returned `usage` on its own tool result, combined across calls. `input` takes text blocks plus `{ type: "image", data, mimeType }` blocks to edit or use as references; output images are base64 blocks and are not saved to disk.
|
|
282
|
+
`ModelRuntime.generateImages(model, { input }, options?)` (Pi 0.99.0) generates images with runtime-resolved auth; list image models with `getModelsOfType("image")`. OpenRouter image models such as `google/gemini-2.5-flash-image` appear under the `openrouter` provider with its credential. Like `classify()`, it returns an error result rather than rejecting. Image models do not appear in `/model`: extensions call `ctx.modelRegistry.generateImages()` and codemode scripts call `models.generateImages()` (both Pi 1.0.0). Codemode adds a script's image usage to its result; an extension tool must report the returned `usage` on its own tool result, combined across calls. `input` takes text blocks plus `{ type: "image", data, mimeType }` blocks to edit or use as references; output images are base64 blocks and are not saved to disk (codemode's `image()` saves the images it shows to temp files).
|
|
278
283
|
|
|
279
284
|
In pi-ai, image models live in the same catalog as chat models: `builtinModels()` includes them, `models.getModelOfType("image", provider, id)` finds one (type `ImageModel`), `models.generateImages()` runs it, and `createProvider({ models, images })` registers image implementations. The separate `*Images*` API was removed in pi-ai 0.99.0.
|
|
280
285
|
|
|
281
286
|
## Virtual Models
|
|
282
287
|
|
|
283
|
-
A virtual model (experimental, Pi 0.99.0) is a selectable entry that routes each request to a physical model. It is for routing by task, cost, or conversation state, not for permission decisions. Source: [virtual-models.md](https://github.com/earendil-works/pi/blob/v1.0
|
|
288
|
+
A virtual model (experimental, Pi 0.99.0) is a selectable entry that routes each request to a physical model. It is for routing by task, cost, or conversation state, not for permission decisions. Source: [virtual-models.md](https://github.com/earendil-works/pi/blob/v1.4.0/packages/coding-agent/docs/virtual-models.md), [`jev-router.ts`](https://github.com/earendil-works/pi/blob/v1.4.0/packages/coding-agent/examples/extensions/jev-router.ts).
|
|
284
289
|
|
|
285
290
|
```typescript
|
|
286
291
|
pi.registerVirtualModel({
|
|
@@ -311,7 +316,7 @@ pi.registerVirtualModel({
|
|
|
311
316
|
|
|
312
317
|
Use extensions when you need custom streaming, custom headers, OAuth/device-flow logic, or want to override an existing provider's `baseUrl`/`headers`:
|
|
313
318
|
|
|
314
|
-
A config-form `models` list replaces the provider's models across chat, image, and classifier operations; image and classifier entries need an explicit `type` and implementations keyed by `api` through `images` and `classifiers`. Registering only `baseUrl` or `headers` keeps the built-in models. See [custom-provider.md](https://github.com/earendil-works/pi/blob/v1.0
|
|
319
|
+
A config-form `models` list replaces the provider's models across chat, image, and classifier operations; image and classifier entries need an explicit `type` and implementations keyed by `api` through `images` and `classifiers`. Registering only `baseUrl` or `headers` keeps the built-in models. See [custom-provider.md](https://github.com/earendil-works/pi/blob/v1.4.0/packages/coding-agent/docs/custom-provider.md).
|
|
315
320
|
|
|
316
321
|
```typescript
|
|
317
322
|
// Full registration
|
|
@@ -337,7 +342,9 @@ pi.registerProvider("anthropic", { baseUrl: "https://proxy.example.com" });
|
|
|
337
342
|
|
|
338
343
|
### Custom Streaming (Breaking, Pi 0.86.0)
|
|
339
344
|
|
|
340
|
-
|
|
345
|
+
Since pi-ai 1.1.0 a stream function must return an `AssistantMessageEventStream`, for example from `createAssistantMessageEventStream()`; a hand-written `EventStream<AssistantMessageEvent, AssistantMessage>` subclass no longer type-checks. Dynamic catalogs receive `RefreshModelsContext.baseUrl` (pi-ai 1.2.0), the provider's base URL with configuration overrides, so `refreshModels()` can follow a `models.json` base URL.
|
|
346
|
+
|
|
347
|
+
Custom `streamSimple` implementations receive a normalized `TranscriptContext` instead of `Context`. System prompts and tool declarations live in transcript system messages: read them with `getCurrentSystemPrompt(context.messages)` and `getCurrentTools(context.messages)` (not `context.systemPrompt` / `context.tools`), and call `collapseSystemMessages(context)` if the backend cannot accept mid-conversation system messages. Breaking in pi-ai 1.4.0: `resolveTranscriptTools()` takes a `TranscriptToolSupport` (`"none"`, `"additions"`, or `"changes"`) instead of a boolean, and `TranscriptTools.anchorsAdditions` became `foldedSystemMessages`. `ToolCall.arguments` and `ToolResultMessage.details` must be JSON-compatible. Since pi-ai 1.3.0 a tool call whose complete arguments text is not a JSON object carries `arguments: {}` and `argumentsError` (the problem plus the raw text), and validation fails with it; a custom adapter should report malformed arguments that way rather than run the fields it managed to parse. Pi 0.84.3 also renamed `GoogleThinkingLevel` to `GoogleApiThinkingLevel`.
|
|
341
348
|
|
|
342
349
|
## SDK / Extension Auth Lookup
|
|
343
350
|
|
|
@@ -38,7 +38,7 @@ try {
|
|
|
38
38
|
}
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
Run with `npx tsx my-script.ts` (or Node's built-in type stripping). `prompt()` resolves when the run finishes, including automatic retries. `session.dispose()` aborts active work, invalidates extension contexts, and removes listeners. Checked against Pi v1.0
|
|
41
|
+
Run with `npx tsx my-script.ts` (or Node's built-in type stripping). `prompt()` resolves when the run finishes, including automatic retries. `session.dispose()` aborts active work, invalidates extension contexts, and removes listeners. Checked against Pi v1.4.0: [sdk.md](https://github.com/earendil-works/pi/blob/v1.4.0/packages/coding-agent/docs/sdk.md), [SDK examples](https://github.com/earendil-works/pi/tree/v1.4.0/packages/coding-agent/examples/sdk).
|
|
42
42
|
|
|
43
43
|
**Breaking (Pi 0.80.8):** `ModelRuntime` replaced the old `AuthStorage`/`ModelRegistry` pair as the SDK model/auth facade. `CreateAgentSessionOptions.authStorage` and `modelRegistry` are gone — pass the async `modelRuntime` instead. `AuthStorage` is no longer exported; use `ModelRuntime` (or a custom pi-ai `CredentialStore`), or `readStoredCredential()` for one-off reads of `auth.json`. `ModelRegistry` still exists only as the synchronous extension-facing compatibility facade, and its `refresh()` is now `Promise<void>`.
|
|
44
44
|
|
|
@@ -58,6 +58,8 @@ import { getBuiltinModel } from "@earendil-works/pi-ai/providers/all";
|
|
|
58
58
|
|
|
59
59
|
**Lightweight model construction (pi-ai 0.99.2):** `@earendil-works/pi-ai/models` builds model collections and providers without loading TypeBox, the built-in catalogs, or provider SDKs; use it in startup-sensitive code that brings its own models.
|
|
60
60
|
|
|
61
|
+
**Client identity (pi-ai 1.4.0):** `createModels({ identity })` or a per-request `identity` option sets the `User-Agent` (`"detailed"`, `"short"`, or `{ custom: "..." }`) and the OpenRouter, NVIDIA NIM, and OpenCode attribution headers (`attribution: { name, url, categories? }`). `withClientIdentity()` applies one to a `Models` received from a host, and `AgentOptions.identity` does the same for agent-loop requests. Breaking: `@earendil-works/pi-ai/utils/pi-user-agent` (`getPiUserAgent()`) was removed, so use `formatUserAgent()`; the request option `telemetryContext` became `telemetry`, a parent `TelemetrySpan`; and `generateSummary()`/`generateSummaryWithUsage()` ignore `reserveTokens`, using the model's output limit.
|
|
62
|
+
|
|
61
63
|
**pi-ai 0.80.0 API move:** the old global API (`getModel`, `getModels`, `getProviders`, `stream`, `complete`, `completeSimple`, `registerApiProvider`, `getEnvApiKey`, ...) moved off the `@earendil-works/pi-ai` root entrypoint to `@earendil-works/pi-ai/compat` (a deprecated shim slated for removal). For built-in model lookup use `getBuiltinModel(provider, modelId)` from `@earendil-works/pi-ai/providers/all`, or the provider-factory API (`createModels()` / `Models.getModel()`). Extensions loaded by Pi are unaffected at runtime — the extension loader aliases the pi-ai root to the compat superset — but standalone SDK scripts and typechecked extension sources must use the new paths. The selective `@earendil-works/pi-ai/base` / `@earendil-works/pi-agent-core/base` entrypoints introduced in 0.79.8 were removed again in 0.80.0.
|
|
62
64
|
|
|
63
65
|
## Core Options
|
|
@@ -96,7 +98,7 @@ Notes:
|
|
|
96
98
|
|
|
97
99
|
## Built-in Extensions: Codemode, Tool Search, MCP
|
|
98
100
|
|
|
99
|
-
The CLI loads `codemode`, `tool_search`, MCP, and llama.cpp as built-in extensions. SDK sessions load none of them. Opt in through the resource loader (Pi 0.99.0, [example](https://github.com/earendil-works/pi/blob/v1.0
|
|
101
|
+
The CLI loads `codemode`, `tool_search`, MCP, and llama.cpp as built-in extensions. SDK sessions load none of them. Opt in through the resource loader (Pi 0.99.0, [example](https://github.com/earendil-works/pi/blob/v1.4.0/packages/coding-agent/examples/sdk/14-codemode-mcp.ts)):
|
|
100
102
|
|
|
101
103
|
```typescript
|
|
102
104
|
import {
|
|
@@ -139,7 +141,7 @@ try {
|
|
|
139
141
|
}
|
|
140
142
|
```
|
|
141
143
|
|
|
142
|
-
- `codemode` and `tool_search` are registered inactive. Enable them with `defaultTools` (`+name` keeps the other defaults), or let MCP activate them for servers that need them. Passing `tools: [...]` instead restricts the session to the named tools
|
|
144
|
+
- `codemode` and `tool_search` are registered inactive. Enable them with `defaultTools` (`+name` keeps the other defaults), or let MCP activate them for servers that need them. Passing `tools: [...]` instead restricts the session to the named tools; since Pi 1.0.4 MCP tools stay unless an entry starts with `mcp__`, so `tools: ["read", "codemode"]` keeps them callable from scripts.
|
|
143
145
|
- Extension `session_start` handlers run only when the host calls `session.bindExtensions()`. Without it, MCP servers never connect.
|
|
144
146
|
- Inline extensions can be named `{ name, factory, replaceable?: true, builtin?: true }`. `replaceable` drops the extension when another one registers the same tool, command, or flag name. `builtin` supplies the code for `builtin:<name>`: it loads by default, is listed in `pi config`, is disabled by `-builtin:<name>` or `noExtensions`, and loads after project trust (so it cannot handle `project_trust`).
|
|
145
147
|
|
|
@@ -317,16 +319,12 @@ session.subscribe((event) => {
|
|
|
317
319
|
|
|
318
320
|
**Streaming deltas (Pi 0.84.x, JSON/RPC wire only):** `message_update` records are delta-only — the cumulative `message` field and `assistantMessageEvent.partial` were removed to keep stream size linear. A top-level `usage` carries latest cumulative provider usage; assemble partial text from `contentIndex` + `delta` between `message_start` and `message_end`, which remains authoritative. In-process SDK subscribers still receive full events.
|
|
319
321
|
|
|
320
|
-
## Remote Sessions (experimental)
|
|
321
|
-
|
|
322
|
-
Pi 0.84.0 added transport-neutral remote-session client APIs: `@earendil-works/pi-client` (`PiClient`) with a CBOR protocol over Unix sockets, `@earendil-works/pi-protocol` for the wire contract, and `@earendil-works/pi-coding-agent/client` exporting a `RemoteSession` controller with transcript reducers. Sessions list durable `SessionMetadata`; runtime phase/model/lock state lives on acquired `SessionSnapshot` values. Expect these APIs to evolve while experimental.
|
|
323
|
-
|
|
324
322
|
## RPC Mode
|
|
325
323
|
|
|
326
|
-
`pi --mode rpc` speaks newline-delimited JSON over stdio ([rpc.md](https://github.com/earendil-works/pi/blob/v1.0
|
|
324
|
+
`pi --mode rpc` speaks newline-delimited JSON over stdio ([rpc.md](https://github.com/earendil-works/pi/blob/v1.4.0/packages/coding-agent/docs/rpc.md), [rpc-commands.md](https://github.com/earendil-works/pi/blob/v1.4.0/packages/coding-agent/docs/rpc-commands.md)). Additions since Pi 0.79:
|
|
327
325
|
|
|
328
326
|
- Input dispositions (Pi 0.99.0): a successful `prompt` response carries `data.disposition` — `"started"` (a run started), `"queued"` (queued during a run), or `"handled"` (an extension command or input handler consumed it). `steer` and `follow_up` responses carry `"queued"` or `"handled"`. On `"handled"` no run started for that input: do not wait for `agent_settled`. Otherwise keep consuming events and wait for `agent_settled`, not `agent_end`. Failures after acceptance arrive as events, never as a second response. `RpcClient.prompt(message, images?, streamingBehavior?)`, `steer()`, and `followUp()` return the disposition.
|
|
329
|
-
- `agent_settled` event (Pi 0.80.4) fires when a run is fully settled — no automatic retry, compaction retry, or queued continuation remains; `agent_end` now carries `willRetry`. `set_thinking_level` accepts `"max"` where the model supports it.
|
|
327
|
+
- `agent_settled` event (Pi 0.80.4) fires when a run is fully settled — no automatic retry, compaction retry, or queued continuation remains; `agent_end` now carries `willRetry`. Since Pi 1.1.0 `agent_settled` carries `aborted`, and `tool_execution_end` carries `durationMs`. `set_thinking_level` accepts `"max"` where the model supports it.
|
|
330
328
|
- `clear_queue` (Pi 0.84.4) returns and removes queued steering and follow-up messages; direct RPC `steer`/`follow_up` commands pass through extension `input` handlers since Pi 0.86.0.
|
|
331
329
|
- `get_entries` / `get_tree` (Pi 0.80.3) read session entries and tree snapshots over RPC; `get_available_thinking_levels` (Pi 0.81.0) lists the current model's supported thinking levels.
|
|
332
330
|
- Direct RPC `bash` commands stream `bash_execution_update` chunks correlated by request `id` (Pi 0.82.0); include an `id` on the command to associate them.
|
|
@@ -7,7 +7,7 @@ Pi uses JSON settings files with project settings overriding global settings:
|
|
|
7
7
|
| `~/.pi/agent/settings.json` | Global |
|
|
8
8
|
| `.pi/settings.json` | Project |
|
|
9
9
|
|
|
10
|
-
Edit JSON directly or use `/settings` for common interactive options.
|
|
10
|
+
Edit JSON directly or use `/settings` for common interactive options. Add `"$schema": "https://pi.dev/schemas/settings.schema.json"` (Pi 1.2.0) for editor validation; `models.json`, keybindings, and themes have matching schemas there.
|
|
11
11
|
|
|
12
12
|
## Model & Thinking
|
|
13
13
|
|
|
@@ -56,7 +56,9 @@ Edit JSON directly or use `/settings` for common interactive options.
|
|
|
56
56
|
- `treeFilterMode`: `"default"`, `"no-tools"`, `"user-only"`, `"labeled-only"`, or `"all"`
|
|
57
57
|
- In `/tree`, `Shift+T` toggles timestamps on entry labels
|
|
58
58
|
- `theme` defaults to `"system"` (Pi 0.99.0), which builds Pi's colors from the terminal's reported foreground, background, and ANSI palette and follows light/dark switches; `system` is a reserved theme name. `dark` and `light` are the other built-ins. `"light-name/dark-name"` (Pi 0.79.7) switches themes with the terminal color scheme; `/` is reserved in theme names for this. `--use-theme <name[/name]>` (Pi 0.84.2) sets a per-run initial theme without changing saved settings.
|
|
59
|
-
- `outputPad` (Pi 0.80.3, `0` or `1`) sets horizontal padding for user messages, assistant messages, and thinking blocks; custom message renderers receive it as `options.outputPad` (Pi 0.82.1).
|
|
59
|
+
- `outputPad` (Pi 0.80.3, `0` or `1`) sets horizontal padding for user messages, assistant messages, and thinking blocks, and since Pi 1.1.0 for tool output, `!` command output, and summary blocks; custom message renderers receive it as `options.outputPad` (Pi 0.82.1), and tool renderers in their render context (Pi 1.1.0).
|
|
60
|
+
- `showHardwareCursor` (changed in Pi 1.2.0) uses only the terminal cursor instead of also drawing Pi's reverse-video cursor. Custom editor components should wrap their cursor cell in pi-tui's `renderFakeCursor()` after `CURSOR_MARKER`, so the TUI hides it when the hardware cursor is shown.
|
|
61
|
+
- Themes are strict since Pi 1.2.0: define reusable colors under `vars`; custom keys under `colors` or `export` and extra top-level fields are rejected.
|
|
60
62
|
- `externalEditor` (Pi 0.80.3) sets the `Ctrl+G` external editor command and takes precedence over `$VISUAL`/`$EDITOR`.
|
|
61
63
|
|
|
62
64
|
## Fullscreen TUI Mode
|
|
@@ -113,6 +115,7 @@ Fullscreen is the default since Pi 1.0.0 (added in 0.84.0):
|
|
|
113
115
|
|
|
114
116
|
Notes:
|
|
115
117
|
|
|
118
|
+
- `compaction.reserveTokens` only decides when auto-compaction starts: once free context falls below it. Since Pi 1.4.0 compaction and branch summaries use the model's output limit instead of a fixed cap, so long reasoning no longer makes them fail.
|
|
116
119
|
- `compaction.modelOverrides` (Pi 0.86.0) sets per-model `reserveTokens`/`keepRecentTokens` keyed by exact `provider/modelId`; each value falls back to the ordinary compaction setting, then the default.
|
|
117
120
|
- `retry.maxRetries` (default `3`) and `retry.baseDelayMs` (default `2000`) govern Pi's own agent-level retry with exponential backoff, capped per attempt by `retry.maxAgentDelayMs` (default `60000`, Pi 0.86.0).
|
|
118
121
|
- `retry.provider.*` controls the underlying provider SDK's request timeout and retry/backoff — useful for slow local LLMs and flaky proxies. `retry.provider.timeoutMs` sets the SDK request timeout; `retry.provider.maxRetries` defaults to `0` (keep it there unless you need SDK-level retries, since raising it can let provider retries swallow usage-limit errors before Pi handles them).
|
|
@@ -183,6 +186,7 @@ Notes:
|
|
|
183
186
|
```json
|
|
184
187
|
{
|
|
185
188
|
"sessionDir": ".pi/sessions",
|
|
189
|
+
"sessionShareDestination": "auto",
|
|
186
190
|
"enabledModels": ["claude-*", "gpt-4o", "gemini-2*"],
|
|
187
191
|
"markdown": {
|
|
188
192
|
"codeBlockIndent": " ",
|
|
@@ -193,6 +197,7 @@ Notes:
|
|
|
193
197
|
|
|
194
198
|
When multiple sources specify a session directory, `--session-dir` takes precedence over `sessionDir` in settings. Pi 0.65.0 removed the old `session_directory` extension/settings hook.
|
|
195
199
|
|
|
200
|
+
- `sessionShareDestination` (Pi 1.3.0): `"auto"` (default) uploads `/share` to Radius when signed in and otherwise to a private GitHub gist; `"gist"` or `"radius"` forces one, and `"radius"` never falls back.
|
|
196
201
|
- `markdown.mermaid` (Pi 0.84.0): `"off"`, `"final"`, or `"streaming"` — themed Unicode rendering of supported Mermaid diagrams in interactive messages. Pi 0.84.0 also renders terminal-friendly Unicode for LaTeX expressions.
|
|
197
202
|
|
|
198
203
|
## Tools & Codemode
|
|
@@ -205,8 +210,9 @@ When multiple sources specify a session directory, `--session-dir` takes precede
|
|
|
205
210
|
```
|
|
206
211
|
|
|
207
212
|
- `defaultTools` picks the tools enabled at startup (default `read`, `bash`, `edit`, `write`; also `grep`, `find`, `ls`, `powershell`, and the built-in extension tools `codemode` and `tool_search`). Plain names replace the defaults. A list of only `+name`/`-name` entries (Pi 0.99.0) changes the inherited selection instead; in a mixed list the plain names form the selection and `+`/`-` apply in order. A project list of only `+`/`-` entries applies on top of the user setting; a project list with a plain name replaces it. An empty array disables built-in tools but keeps extension/SDK tools.
|
|
208
|
-
- `--tools` replaces the whole selection for one run and
|
|
209
|
-
-
|
|
213
|
+
- `--tools` with plain names replaces the whole selection for one run; entries may be `*` patterns (Pi 1.0.4), and MCP tools stay unless an entry starts with `mcp__`. A list of only `+name`/`-name` entries (Pi 1.1.0, exact names) changes the `defaultTools` selection instead, e.g. `pi --tools +codemode,-write`; the two forms cannot be mixed. `--exclude-tools` filters the result, takes patterns, and covers MCP tools; `--no-mcp` (Pi 1.0.4) turns off built-in MCP for one run.
|
|
214
|
+
- Resumed sessions restore their active tools (Pi 1.3.0), and `--tools +name`/`-name` applies on top of them; `--tools` with plain names, `--no-tools`, and `--no-builtin-tools` still pick tools explicitly. Since Pi 1.4.0, resume and `/tree` also activate extension tools registered after the branch recorded its tools, unless they use `defaultActive: false`; tools the branch removed stay inactive.
|
|
215
|
+
- `/reload` (Pi 0.99.2) enables tools newly added to `defaultTools`. It does not disable tools removed from it or re-enable unchanged tools turned off in the session; `--tools` with plain names, `--no-tools`, and `--no-builtin-tools` still win on reload, and a tool removed with `--tools -name` stays removed.
|
|
210
216
|
- `codemode.mode`: `"on"` (default) keeps declared tools declared; `"only"` hides active built-in and extension tools from the model so it reaches them through `codemode`. `codemode.inlineBudget` (default `3000`, estimated tokens) caps tool declarations in the `codemode` description. See `references/codemode.md`.
|
|
211
217
|
- MCP servers are configured in `~/.pi/agent/mcp.json` and the trust-gated `.pi/mcp.json`, not in `settings.json`. See `references/mcp.md`.
|
|
212
218
|
|
|
@@ -271,7 +277,7 @@ Arrays support filtering:
|
|
|
271
277
|
}
|
|
272
278
|
```
|
|
273
279
|
|
|
274
|
-
- `!pattern` excludes glob matches
|
|
280
|
+
- `!pattern` excludes glob matches; `~` expands in `!pattern`, `+path`, and `-path` since Pi 1.2.0
|
|
275
281
|
- `+path` force-includes an exact path
|
|
276
282
|
- `-path` force-excludes an exact path
|
|
277
283
|
|
|
@@ -286,16 +292,16 @@ pi [options] [@files...] [messages...]
|
|
|
286
292
|
# MCP commands: pi mcp add|remove|list|login|logout (see references/mcp.md)
|
|
287
293
|
|
|
288
294
|
# modes
|
|
289
|
-
-p, --print single-shot text output
|
|
290
|
-
--mode json|rpc newline-delimited events /
|
|
295
|
+
-p, --print single-shot text output; nonzero exit on error, abort, or (Pi 1.2.0) a reply cut off at the token limit
|
|
296
|
+
--mode json|rpc newline-delimited JSON events / RPC commands over stdio
|
|
291
297
|
--export <in> [out] export a session to HTML
|
|
292
298
|
|
|
293
299
|
# model
|
|
294
300
|
--provider <name> e.g. anthropic, openai, google; needs --model (Pi 1.0.0)
|
|
295
|
-
--model <pattern> supports provider/id and optional :<thinking
|
|
301
|
+
--model <pattern> supports provider/id and optional :<thinking>; prefers the provider with credentials when several match (Pi 1.3.0)
|
|
296
302
|
--api-key <key>
|
|
297
303
|
--thinking off|minimal|low|medium|high|xhigh|max
|
|
298
|
-
--models <patterns> Ctrl+P cycling allowlist
|
|
304
|
+
--models <patterns> Ctrl+P cycling allowlist; a model several providers offer is added for each, with a warning (Pi 1.3.0)
|
|
299
305
|
--list-models [search]
|
|
300
306
|
|
|
301
307
|
# sessions
|
|
@@ -309,9 +315,11 @@ pi [options] [@files...] [messages...]
|
|
|
309
315
|
--no-session ephemeral
|
|
310
316
|
|
|
311
317
|
# tools and resources
|
|
312
|
-
--tools <list>, -t
|
|
313
|
-
|
|
314
|
-
--
|
|
318
|
+
--tools <list>, -t allowlist of names or * patterns; keeps MCP tools unless an entry starts with mcp__
|
|
319
|
+
a list of only +name/-name entries adjusts the defaults instead
|
|
320
|
+
--exclude-tools <list>, -xt disable tools by name or pattern, MCP included; keep the rest
|
|
321
|
+
--no-tools, -nt disable everything, MCP tools included
|
|
322
|
+
--no-mcp disable built-in MCP for this run (Pi 1.0.4)
|
|
315
323
|
--no-builtin-tools, -nbt disable built-ins, keep extension/custom
|
|
316
324
|
-e, --extension <source> repeatable; npm/git/path, or builtin:<name>
|
|
317
325
|
--skill <path> repeatable
|
|
@@ -349,7 +357,10 @@ Use `/bug [description]` (Pi 0.86.0) to report a problem with redacted diagnosti
|
|
|
349
357
|
| `PI_CACHE_RETENTION` | Set to `long` for extended prompt-cache retention where supported |
|
|
350
358
|
| `PI_OAUTH_CALLBACK_HOST` | Bind the OAuth callback server to a custom interface |
|
|
351
359
|
| `PI_SHARE_VIEWER_URL` | Override the base URL used by `/share` |
|
|
360
|
+
| `PI_CODEMODE_MAX_WORKERS` | Maximum codemode workers alive at once across the process (default `2`, Pi 1.4.0); raise it when one process runs several sessions |
|
|
352
361
|
| `PI_HARDWARE_CURSOR` | `1` shows the hardware cursor (see terminal-setup docs) |
|
|
362
|
+
| `PI_CLIPBOARD_OSC52` | `1` (Pi 1.2.0) also sends copied text to the terminal with OSC 52, for remote clients Pi cannot detect, such as a multiplexer attached from another machine |
|
|
363
|
+
| `PI_PROGRAM_STATUS` | OSC 7501 program status (Pi 1.1.0): Pi reports working/blocked/done/error/idle to terminals and agent dashboards that confirm support; `1` always reports, `0` never |
|
|
353
364
|
| `PI_TUI_ESC_TIMEOUT` | Ms to wait after a lone ESC before treating it as Escape (default 100 over SSH, 10 otherwise; Pi 0.84.2) — raise it if Alt-key input is misread |
|
|
354
365
|
| `PI_EXPERIMENTAL` | `1` enables experimental features (strict JSON-schema sampling for built-in tools no longer needs it since Pi 0.86.0) |
|
|
355
366
|
| `AI_AGENT=pi` / `PI_CODING_AGENT=true` | Set automatically by CLI/RPC entry points so child processes can detect Pi (generic vs Pi-specific marker) |
|