Pi Extension Development
Guide for creating and maintaining Pi extensions. Read the relevant reference files before implementing.
Key Imports
typescript
// Core types
import type { ExtensionAPI, ExtensionContext, ToolDefinition, ProviderDefinition } from "@mariozechner/pi-coding-agent";
// Schema (TypeBox)
import { Type } from "@mariozechner/pi-coding-agent";
// TUI components
import type { Component, Theme } from "@mariozechner/pi-tui";
import { Text, Box, Container, SelectList } from "@mariozechner/pi-tui";
// Utilities
import { truncateHead, highlightCode, getLanguageFromPath, DynamicBorder, BorderedLoader } from "@mariozechner/pi-coding-agent";
Workflow
Creating a New Extension
- •Read
references/structure.mdfor the project layout and package.json template. - •Create the entry point (
src/index.ts) with a default export function. - •Decide what the extension provides:
- •Tools (LLM-callable): Read
references/tools.md. - •Commands (user-invoked): Read
references/commands.md. - •Providers (LLM backends): Read
references/providers.md. - •Hooks (event handlers): Read
references/hooks.md. Includes bothtool_callblocking hooks and spawn hooks for transparent command rewriting viacreateBashTool.
- •Tools (LLM-callable): Read
- •Read
references/modes.mdfor mode-awareness guidelines. Every extension must handle Interactive, RPC, and Print modes. - •If the extension displays rich UI: Read
references/components.mdfor TUI components andreferences/messages.mdfor message display patterns. - •If the extension tracks state: Read
references/state.md. - •For less common APIs: Read
references/additional-apis.md. - •Before publishing: Read
references/publish.mdandreferences/documentation.md.
Modifying an Existing Extension
- •Read the extension's
index.tsto understand its structure. - •Read the relevant reference file for the area you are modifying.
- •Check
references/modes.mdif adding any UI interaction. - •Run type checking after changes.
Reference Files
| File | Content |
|---|---|
references/structure.md | Project layout, package.json, tsconfig, entry point, API key pattern, imports |
references/tools.md | Tool registration, execute signature, parameters, streaming, rendering, naming, renderCall/renderResult UI guidelines |
references/hooks.md | Events, blocking/cancelling, input transformation, system prompt modification, bash spawn hooks (command rewriting) |
references/commands.md | Command registration, three-tier pattern, component extraction |
references/components.md | TUI components (pi-tui + pi-coding-agent), custom(), theme styling, keyboard handling |
references/providers.md | Provider registration, model definition, compat field, API key gating |
references/modes.md | Mode behavior matrix, ctx.hasUI, dialog vs fire-and-forget, three-tier pattern |
references/messages.md | sendMessage, registerMessageRenderer, notify, when to use each |
references/state.md | appendEntry, state reconstruction, appendEntry vs sendMessage |
references/additional-apis.md | Shortcuts, flags, exec, sendUserMessage, session name, labels, model control, EventBus, theme, UI customization |
references/publish.md | npm publishing, changesets, versioning, pre-publish checklist |
references/testing.md | Local development, type checking, manual testing, debugging |
references/documentation.md | README template, what to document, changelog |
Reference Extensions
When implementing, look at these existing extensions for patterns:
Standalone repos (recommended structure):
- •
pi-linkup(/Users/alioudiallo/code/src/github.com/aliou/pi-linkup/): Tools wrapping a third-party API. Has tools, a command, custom message rendering, API key gating. - •
pi-synthetic(/Users/alioudiallo/code/src/github.com/aliou/pi-synthetic/): Provider + tools. Has a provider with models, a command withcustom()component, API key gating, async entry point.
Monorepo extensions (simpler structure):
- •
extensions/defaults/in this repo: Simple tool registration (get_current_time). - •
extensions/guardrails/in this repo: Event hooks (tool_call blocking). Hashooks/,commands/,components/,utils/directories with config types inconfig.ts. - •
extensions/toolchain/in this repo: Bash spawn hooks (command rewriting viacreateBashTool) combined with tool_call blockers. Hasblockers/,rewriters/,commands/,utils/directories. - •
extensions/processes/in this repo: Multi-action tool with StringEnum parameters.
Critical Rules
- •Execute parameter order:
(toolCallId, params, signal, onUpdate, ctx). Signal before onUpdate. - •Always use
onUpdate?.(): Optional chaining. The parameter can beundefined. - •No
.jsin imports: Use bare module paths (./tools/my-tool, not./tools/my-tool.js). - •Mode awareness: Every
ctx.ui.custom()call needs an RPC fallback (useselect/confirm/notify-- they work in RPC). Everytool_callhook with dialogs needs actx.hasUIcheck. - •API key gating: Check before registering tools that require the key. Providers handle missing keys internally via their
models()function. - •Tool naming: Prefix with API name for third-party integrations (
linkup_web_search). No prefix for internal tools (get_current_time). - •Tool call header pattern: Keep
renderCallconsistent: first line[Tool Name]: [Action] [Main arg] [Option args], extra lines for long args. Use display names, not raw tool IDs. - •Long args placement: Put long prompt/task/question/context strings on following lines. Keep first line scannable.
- •Footer spacing: If a tool result has a footer, keep one blank line before it for readability.
- •peerDependencies: Use
>=CURRENT_VERSIONrange, not*. - •Check existing components: Before creating a new TUI component, check if
pi-tuiorpi-coding-agentalready exports one that fits. - •Forward abort signals: Always pass
signalthrough tofetch(), child processes, and API client methods. A tool that ignores its signal prevents cancellation from reaching the underlying operation. Never prefix with_signalunless the tool truly has no async work to cancel. - •Never use
homedir()for pi paths: Use the SDK helpers from@mariozechner/pi-coding-agentinstead. They respect thePI_CODING_AGENT_DIRenv var which is used for testing and custom setups. Key functions:getAgentDir(),getSettingsPath(),getSessionsDir(),getPromptsDir(),getToolsDir(),getCustomThemesDir(),getModelsPath(),getAuthPath(),getBinDir(),getDebugLogPath(). All exported from the main package entry point.
Checklist
Before considering an extension complete:
- • Entry point has correct default export signature.
- • All tools have correct execute parameter order.
- • All
onUpdatecalls use optional chaining. - • No
.jsfile extensions in imports. - •
renderCalluses a consistent first-line pattern (tool, action if any, main arg, options). - • Long call arguments are moved to follow-up lines, not crammed into first line.
- • If result includes a footer, there is a blank line above it.
- •
ctx.ui.custom()calls have RPC fallback (undefined check). - •
tool_callhooks checkctx.hasUIbefore dialog methods. - • Fire-and-forget methods (notify, setStatus, etc.) are used without hasUI guards.
- •
signalis forwarded to all async operations (fetch, child processes, API clients). No unused_signal. - • Missing API keys produce a notification, not a crash.
- • If in a monorepo: package doesn't depend on private workspace packages (run
pnpm run check:public-depsif available). - •
pnpm typecheckpasses. - • No
homedir()calls for pi paths -- uses SDK helpers (getAgentDir(), etc.). - • README documents tools, commands, env vars.