Skip to content

packages coding agent extensions

Zachary BENSALEM edited this page Aug 15, 2026 · 1 revision

Extensions

Active contributors: Mario Zechner, kt, Armin Ronacher

Purpose

Extensions are TypeScript modules that customize Prime Agent behavior in-process. An extension exports a default factory function that receives the ExtensionAPI and can subscribe to lifecycle events, register tools callable by the LLM, add slash commands and keybindings, and render custom UI. The reference is packages/coding-agent/docs/extensions.md, with working samples in packages/coding-agent/examples/extensions/. The implementation lives under packages/coding-agent/src/core/extensions/.

Extensions are permanently process-local: they expose executable callbacks (argument completions, tool renderers, keyboard shortcut handlers) and process state (extension bindings) that cannot cross the daemon wire. On the AgentConnection seam this surface lives on the AgentConnection.extensions sub-interface; daemon adapters throw AgentConnectionUnsupportedError.

Directory layout

packages/coding-agent/src/core/extensions/
├── index.ts                 # Public type and helper re-exports
├── loader.ts                # jiti-based module loading and discovery
├── runner.ts                # ExtensionRunner: event dispatch and lifecycle
├── types.ts                 # Extension, ExtensionAPI, events, contexts
├── wrapper.ts               # Wrap registered tools into AgentTools
├── bundled-modules.ts       # Virtual module map for the compiled Bun binary
└── builtin/
    └── herdr-agent-state.ts # Built-in Herdr pane state reporter

packages/coding-agent/examples/extensions/
├── with-deps/               # Extension with its own npm dependency (ms)
├── custom-provider-anthropic/ # Register a custom Anthropic provider
├── custom-provider-gitlab-duo/ # Register a custom GitLab Duo provider
└── sandbox/                 # Custom tool backed by the Anthropic sandbox runtime

Key abstractions

Type Path Description
Extension packages/coding-agent/src/core/extensions/types.ts Loaded extension: paths, source info, handlers, tools, commands, flags, shortcuts, message renderers.
ExtensionAPI packages/coding-agent/src/core/extensions/types.ts The pi object passed to the factory; registration and action methods.
ExtensionFactory packages/coding-agent/src/core/extensions/types.ts The default-export function an extension module must provide.
ExtensionRuntime packages/coding-agent/src/core/extensions/types.ts Shared runtime holding action methods and flag values across extensions.
ExtensionRunner packages/coding-agent/src/core/extensions/runner.ts Dispatches events to handlers and manages extension lifecycle.
ExtensionContext packages/coding-agent/src/core/extensions/types.ts Per-call context: UI, cwd, model, session manager, signal, abort, compact.
ExtensionCommandContext packages/coding-agent/src/core/extensions/types.ts Command handler context with newSession, fork, switchSession, reload.
ExtensionUIContext packages/coding-agent/src/core/extensions/types.ts TUI surface: select, confirm, input, notify, widgets, footer, header, editors.
RegisteredTool / ToolDefinition packages/coding-agent/src/core/extensions/types.ts A tool registered via pi.registerTool.
LoadExtensionsResult packages/coding-agent/src/core/extensions/types.ts Extensions plus per-path load errors and the runtime.
AgentConnectionExtensions packages/coding-agent/src/modes/agent-connection/types.ts Process-local extension surface exposed on AgentConnection.

How it works

flowchart TD
    dirs["~/.prime/agent/extensions · .prime/agent/extensions · packages · CLI paths"]
    loader["loader.ts: discover + jiti import"]
    factory["default export factory(pi: ExtensionAPI)"]
    api["createExtensionAPI: writes tools/commands/events"]
    runtime["createExtensionRuntime: shared action methods"]
    runner["ExtensionRunner.bindCore/bindCommandContext"]
    session["AgentSession + mode"]

    dirs --> loader --> factory --> api
    api --> runtime
    runtime --> runner --> session
    runner --> conn["AgentConnection.extensions (process-local)"]
Loading

loader.ts resolves extension entry points from files (*.ts/*.js), subdirectories with an index.ts/index.js, or a package.json with a pi.extensions field. Modules are imported with jiti so TypeScript runs without compilation. In the compiled Bun binary, imports of @earendil-works/*, typebox, and @sinclair/typebox resolve through bundled-modules.ts virtual modules so extensions share the bundle's module instances; in Node.js dev they resolve through jiti aliases.

The factory runs during loading and uses registration methods (pi.on, pi.registerTool, pi.registerCommand, pi.registerShortcut, pi.registerFlag, pi.registerMessageRenderer) that write into the Extension object, plus action methods that delegate to the shared ExtensionRuntime. The runtime starts with throwing stubs; ExtensionRunner.bindCore replaces them with real implementations once the model registry and context actions are available. Provider registrations queued during loading (pi.registerProvider) are flushed on bind.

ExtensionRunner owns lifecycle. Its emit methods walk every extension's handlers for a given event type and fold results (for example message_end transforms a message, tool_result can rewrite content, context can rewrite the message list, before_agent_start can alter the system prompt and inject messages). Command names are deduplicated into invocationNames when two extensions register the same name. Extension shortcuts are checked against reserved built-in keybindings so extensions cannot steal core editor actions.

What extensions can contribute

  • Tools: pi.registerTool({ name, label, description, parameters, execute }) adds a tool the model can call; wrapper.ts wraps registered tools into AgentTools that receive the runner context.
  • Commands: pi.registerCommand("name", { description, handler }) adds a /name slash command.
  • Keybindings: pi.registerShortcut("ctrl+x", { description, handler }) adds a shortcut resolved against the client's effective keybindings.
  • Flags: pi.registerFlag("my-flag", { type, default }) adds a CLI flag read through pi.getFlag.
  • Events: pi.on("event_name", handler) subscribes to lifecycle events (session, agent, tool, model, input, message, resource discovery).
  • UI: ctx.ui provides select, confirm, input, notify, setStatus, setWidget, setFooter, setHeader, setTitle, editors, autocomplete providers, and ctx.ui.custom() for full custom TUI components.
  • Message/tool rendering: pi.registerMessageRenderer and tool renderer definitions control how custom messages and tool calls appear.
  • Session persistence: pi.appendEntry(customType, data) writes session entries that survive restarts.
  • Providers: pi.registerProvider registers a custom LLM provider (used by the custom-provider examples).

The process-local boundary

The AgentConnection seam (packages/coding-agent/src/modes/agent-connection/types.ts) exposes AgentConnection.extensions, whose members are permanently process-local: getArgumentCompletions, getCommandDiagnostics, getShortcutDiagnostics, getShortcuts, getKeyboardShortcuts, getMessageRenderer, getToolRendererDefinition, and bindExtensions. These carry executable callbacks and process state that cannot cross a network boundary, so daemon-backed adapters throw AgentConnectionUnsupportedError. bindExtensions(bindings) wires the extension runner's actions into the session and mode; extension context is invalidated after session replacement or reload so captured contexts cannot be used across a newSession, fork, switchSession, or reload.

The built-in Herdr extension

packages/coding-agent/src/core/extensions/builtin/herdr-agent-state.ts is the in-tree equivalent of the extension that herdr integration install pi writes. It reports agent lifecycle state (working/blocked/idle) to the Herdr terminal workspace manager over a Unix socket. It is a no-op unless HERDR_ENV=1 with HERDR_SOCKET_PATH and HERDR_PANE_ID set. It defers to Herdr's own file-based integration when that file actually loads (checked via the resource loader's loaded paths), reports with a monotonic per-source sequence, and releases the pane on a real quit rather than on session replacement.

Examples

  • with-deps: a tool (parse_duration) that imports the ms npm package, proving jiti resolves dependencies from the extension's own node_modules. Requires npm install in that directory.
  • custom-provider-anthropic: registers a custom Anthropic provider via pi.registerProvider.
  • custom-provider-gitlab-duo: registers a custom GitLab Duo provider; includes a test.ts.
  • sandbox: a custom tool backed by the Anthropic sandbox runtime (@anthropic-ai/sandbox-runtime).

Integration points

  • DefaultResourceLoader.reload() in packages/coding-agent/src/core/resource-loader.ts calls loadExtensions, runs inline extensionFactories, detects tool/flag name conflicts, and attaches SourceInfo.
  • wrapper.ts converts extension tools into AgentTools consumed by the session.
  • The interactive mode and bindExtensions wire runner actions into the session, model registry, and TUI.
  • Extensions can contribute skills, prompts, and themes through the resources_discover event, which emitResourcesDiscover in the runner aggregates into resource paths for the loader.

Entry points for modification

  • To add a new event type or change dispatch, edit packages/coding-agent/src/core/extensions/types.ts (event union) and packages/coding-agent/src/core/extensions/runner.ts (emit methods).
  • To change discovery or module loading, edit packages/coding-agent/src/core/extensions/loader.ts.
  • To change how the shared runtime and action stubs behave, edit packages/coding-agent/src/core/extensions/loader.ts (createExtensionRuntime, createExtensionAPI).
  • To add a built-in extension, add a module under packages/coding-agent/src/core/extensions/builtin/ and re-export it from index.ts.

Key source files

File Role
packages/coding-agent/src/core/extensions/index.ts Public re-exports of extension types and helpers.
packages/coding-agent/src/core/extensions/loader.ts jiti loading, discovery, runtime and API creation.
packages/coding-agent/src/core/extensions/runner.ts Event dispatch, lifecycle, context creation.
packages/coding-agent/src/core/extensions/types.ts Extension, API, event, context, and UI types.
packages/coding-agent/src/core/extensions/wrapper.ts Tool wrapper into AgentTool.
packages/coding-agent/src/core/extensions/bundled-modules.ts Virtual modules for the compiled binary.
packages/coding-agent/src/core/extensions/builtin/herdr-agent-state.ts Built-in Herdr reporter.

Related pages

Clone this wiki locally