mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
192 lines
9.4 KiB
Markdown
192 lines
9.4 KiB
Markdown
# Extensions
|
||
|
||
Extensions are TypeScript modules that add executable behavior to Pi. Use one when a workflow needs tools, commands, event handlers, model providers, session state, or terminal UI rather than instructions alone.
|
||
|
||
An extension runs inside the Pi process with the same operating-system permissions. It can inspect prompts, tool calls, files, credentials, and session history, so load extensions only from sources you trust.
|
||
|
||
Typical extensions add an agent tool, protect paths, confirm dangerous commands, react to session events, modify context, expose a command, or display persistent status.
|
||
|
||
<a id="quick-start"></a>
|
||
<a id="writing-an-extension"></a>
|
||
<a id="create-an-extension"></a>
|
||
|
||
## Create and load an extension
|
||
|
||
An extension exports a default factory that receives `ExtensionAPI`. The factory registers capabilities for the current extension runtime.
|
||
|
||
Create `~/.pi/agent/extensions/hello.ts`:
|
||
|
||
```typescript
|
||
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
||
|
||
export default function (pi: ExtensionAPI) {
|
||
pi.registerCommand("hello", {
|
||
description: "Show a greeting",
|
||
handler: async (name, ctx) => {
|
||
ctx.ui.notify(`Hello, ${name || "world"}!`, "info");
|
||
},
|
||
});
|
||
}
|
||
```
|
||
|
||
Start Pi and run `/hello`. During development, load a file directly:
|
||
|
||
```bash
|
||
pi --extension ./hello.ts
|
||
```
|
||
|
||
Pi uses `jiti`, so local TypeScript extensions do not need a separate compilation step. Use [Pi packages](packages.md) for distributed extensions and dependencies.
|
||
|
||
<a id="extension-locations"></a>
|
||
<a id="available-imports"></a>
|
||
<a id="choose-where-it-loads"></a>
|
||
|
||
## Add it to Pi
|
||
|
||
Place the extension in your user or project extensions directory. Pi loads direct TypeScript or JavaScript files and subdirectories containing an `index.ts` or `index.js` entry point.
|
||
|
||
Use a single file for a small extension and a directory for a multi-file implementation. Put npm dependencies in a nearby `package.json`. See [Loading resources](configuration.md#resources) for all supported locations and options.
|
||
|
||
Reload replaces the extension runtime, so code after `await ctx.reload()` must not reuse state from the old runtime. Only personal and explicit command-line extensions can participate in the `project_trust` event that runs before project extensions load.
|
||
|
||
<a id="understand-the-lifecycle"></a>
|
||
|
||
## Respect the runtime lifecycle
|
||
|
||
The factory can be synchronous or asynchronous. Pi waits for an asynchronous factory before startup continues, allowing it to fetch configuration or register providers needed during startup.
|
||
|
||
Do not start processes, sockets, watchers, or timers in the factory because some invocations load extensions without starting a session.
|
||
Start long-lived resources from `session_start` or from the command or tool that needs them.
|
||
Close session-scoped resources from an idempotent `session_shutdown` handler.
|
||
|
||
A run proceeds from input and `before_agent_start`, through model, message, and tool events, to `agent_end`.
|
||
Automatic retries, compaction, or queued work can continue afterward.
|
||
Use `agent_settled` when an integration needs to know Pi will not continue automatically.
|
||
|
||
<a id="extensionapi-methods"></a>
|
||
|
||
## Choose an integration point
|
||
|
||
| Capability | Main API |
|
||
|---|---|
|
||
| Observe or modify lifecycle behavior | `pi.on()` |
|
||
| Add a model-callable operation | `pi.registerTool()` |
|
||
| Add a `/` command | `pi.registerCommand()` |
|
||
| Add a shortcut or CLI flag | `pi.registerShortcut()` or `pi.registerFlag()` |
|
||
| Send user or custom messages | `pi.sendUserMessage()` or `pi.sendMessage()` |
|
||
| Persist non-context session data | `pi.appendEntry()` |
|
||
| Change active tools, model, or thinking level | Session control methods on `pi` |
|
||
| Add a model provider | `pi.registerProvider()` |
|
||
| Add terminal rendering | Renderer registration and `ctx.ui` |
|
||
| Communicate with another extension | `pi.events` |
|
||
|
||
Use the exported declarations in [`extensions/types.ts`](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/src/core/extensions/types.ts) for exact event, context, tool, and result types.
|
||
|
||
## Follow the extension contracts
|
||
|
||
<a id="events"></a>
|
||
<a id="work-with-events"></a>
|
||
|
||
### Events and concurrency
|
||
|
||
Handlers run in extension load order.
|
||
Some events notify; others transform data, replace results, or cancel an operation.
|
||
Use each event’s declared result type rather than assuming every return value has an effect.
|
||
|
||
Events cover resource discovery, sessions, agent and message lifecycle, providers, tools, and raw input.
|
||
|
||
`message_end` can replace a finalized message while preserving its role. `tool_call` can mutate input or block execution. `tool_result` handlers compose, with each handler seeing prior changes.
|
||
|
||
Tool calls from one assistant message can run in parallel.
|
||
Do not assume a sibling call or result exists when another tool event runs.
|
||
Use `ctx.signal` for nested work owned by an active turn; commands and idle session events often have no operation signal.
|
||
|
||
<a id="custom-tools"></a>
|
||
<a id="register-tools"></a>
|
||
|
||
### Tools
|
||
|
||
A custom tool defines a name, model-facing description, TypeBox parameter schema, and `execute()` function.
|
||
Its result requires model-facing `content` and a `details` field for rendering or state reconstruction.
|
||
Use `details: undefined` when there are no structured details.
|
||
|
||
Throw from `execute()` to produce a failed tool result.
|
||
Returning an object does not mark it as an error.
|
||
Return `terminate: true` only when the agent should skip its automatic follow-up after every completed tool in that batch agrees to terminate.
|
||
|
||
Use sequential execution when tools share mutable in-memory state.
|
||
File-mutating tools should wrap the complete read-modify-write operation with `withFileMutationQueue()`.
|
||
Truncate large model-facing results and tell the model where to read the complete output.
|
||
|
||
See [`hello.ts`](../examples/extensions/hello.ts), [`todo.ts`](../examples/extensions/todo.ts), [`dynamic-tools.ts`](../examples/extensions/dynamic-tools.ts), and [`truncated-tool.ts`](../examples/extensions/truncated-tool.ts).
|
||
|
||
<a id="extensioncontext"></a>
|
||
<a id="extensioncommandcontext"></a>
|
||
<a id="use-extension-context"></a>
|
||
|
||
### Context and session changes
|
||
|
||
`ExtensionContext` provides the working directory, mode, UI, session manager, model runtime, abort signal, context usage, and controls for compaction and shutdown.
|
||
Use `ctx.modelRegistry.streamSimple()` for provider-neutral nested model calls, and report nested usage so session totals remain accurate.
|
||
|
||
Command handlers receive `ExtensionCommandContext`, which adds operations for waiting until idle, reloading, tree navigation, and session replacement.
|
||
These operations are command-only because calling them from lifecycle handlers can deadlock the runtime.
|
||
|
||
Session replacement invalidates the old context. Capture only plain data before switching, then use the fresh context supplied to `withSession` for session-bound work.
|
||
|
||
<a id="state-management"></a>
|
||
<a id="persist-state"></a>
|
||
|
||
### State
|
||
|
||
Choose storage based on how state participates in the conversation:
|
||
|
||
| State | Storage |
|
||
|---|---|
|
||
| Tool state that follows the active branch | Tool-result `details` |
|
||
| Durable data excluded from model context | `pi.appendEntry()` |
|
||
| Custom content stored and sent to the model | `pi.sendMessage()` |
|
||
| Data outside one session | External storage |
|
||
|
||
Reconstruct branch-sensitive state from `ctx.sessionManager.getBranch()` during `session_start`.
|
||
Do not rebuild it from every file entry because abandoned branches represent alternative histories.
|
||
Register an entry or message renderer when custom stored content should appear in the transcript.
|
||
|
||
<a id="custom-ui"></a>
|
||
<a id="mode-behavior"></a>
|
||
<a id="interact-with-the-user"></a>
|
||
<a id="account-for-each-mode"></a>
|
||
|
||
### UI and modes
|
||
|
||
`ctx.ui` provides dialogs, notifications, status text, widgets, titles, editor access, and custom components.
|
||
Use `ctx.ui.custom()` only when the interaction needs its own rendering and input.
|
||
See [Terminal UI](tui.md) for component, focus, overlay, theme, and performance guidance.
|
||
|
||
Extensions load in interactive, RPC, JSON, and print modes.
|
||
Interactive mode provides the complete terminal UI.
|
||
RPC can forward supported dialogs and notifications, but not custom terminal components; JSON and print modes have no UI.
|
||
Guard terminal-only behavior with `ctx.mode === "tui"` and use `ctx.hasUI` for interactions supported by interactive and RPC clients.
|
||
|
||
Keep tool and event behavior independent from rendering so non-interactive modes remain functional.
|
||
|
||
<a id="error-handling"></a>
|
||
<a id="handle-errors-and-shutdown"></a>
|
||
|
||
### Errors and cleanup
|
||
|
||
Pi reports handler errors and continues where possible. A `tool_call` handler failure blocks the tool as a fail-safe; a tool execution failure becomes an error result for the model.
|
||
|
||
Release resources in `session_shutdown` even when normal operation attempted cleanup.
|
||
Keep cleanup idempotent because cancellation, reload, session replacement, and process exit can converge on the same path.
|
||
Use `ctx.shutdown()` to request an orderly process shutdown.
|
||
|
||
<a id="examples-reference"></a>
|
||
<a id="use-examples-as-the-implementation-reference"></a>
|
||
|
||
## Examples and reference
|
||
|
||
The checked [extension examples](../examples/extensions/) cover tools, lifecycle events, commands, flags, shortcuts, state, rendering, providers, OAuth, remote execution, and terminal components.
|
||
Start with the smallest example matching your integration point.
|
||
|
||
Use [Custom Providers](custom-provider.md) for model-service integrations, [Terminal UI](tui.md) for custom components, and [Pi Packages](packages.md) to install or distribute extensions with other resources.
|