How to Define Custom Tools for PrimeAgent: A Complete Extension Guide
Use the pi.registerTool() API in a PrimeAgent extension to register custom tools that the LLM can invoke alongside built‑in tools like bash and edit.
Custom tools are the primary way to extend PrimeAgent's capabilities. According to the PrimeIntellect-ai/prime-agent source code, you define tools through an extension—a TypeScript module loaded at runtime that receives an ExtensionAPI object with full access to the tool registry.
How PrimeAgent Extensions Work
Extensions are discovered from two locations:
~/.prime/agent/extensions/— user‑global extensions.prime/agent/extensions/— project‑local extensions
The loader in packages/coding-agent/src/core/extensions/loader.ts uses jiti to execute these files as native TypeScript, eliminating the need for a build step. When PrimeAgent starts, every .ts file in these directories is evaluated, and each default export function receives the pi API object.
The Extension API (pi)
The pi object implements ExtensionAPI as defined in packages/coding-agent/src/core/extensions/types.ts. It provides three primary methods:
| Method | Purpose |
|---|---|
pi.on(event, handler) |
Subscribe to lifecycle events |
pi.registerCommand(def) |
Add CLI commands |
pi.registerTool(def) |
Register custom tools for the LLM |
The registerTool signature appears at line 1081 of types.ts and accepts a ToolDefinition<TParams, TDetails, TState> object.
Tool Definition Structure
A complete tool definition requires five fields:
{
name: string; // LLM-facing identifier
label: string; // Human-readable UI label
description: string; // Help text for the model
parameters: TSchema; // Typebox JSON schema
execute: Function; // Async execution handler
}
Parameter Schema with Typebox
PrimeAgent uses typebox for runtime validation. Import Type from "typebox" and construct schemas that describe the JSON structure the LLM should emit:
import { Type } from "typebox";
// Required string parameter
Type.String({ description: "..." })
// Object with multiple fields
Type.Object({
path: Type.String({ description: "File path" }),
content: Type.String({ description: "File content" }),
});
Execute Function Signature
The execute callback receives five arguments as implemented in packages/coding-agent/src/core/tools/tool-definition-wrapper.ts:
async execute(
toolCallId: string, // Unique ID for this invocation
params: TParams, // Validated parameters from schema
signal: AbortSignal, // Cancellation signal
onUpdate: Function, // Stream partial results to UI
ctx: ExecutionContext // Agent context and utilities
): Promise<ToolResult>
The return value must contain a content array and optional details:
return {
content: [{ type: "text", text: "Result here" }],
details: {}, // Arbitrary structured data
};
Complete Custom Tool Example
Create ~/.prime/agent/extensions/greet.ts with this implementation based on the official example in packages/coding-agent/docs/extensions.md (lines 76‑90):
// ~/.prime/agent/extensions/greet.ts
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { Type } from "typebox";
export default function (pi: ExtensionAPI) {
pi.registerTool({
name: "greet",
label: "Greet",
description: "Greet someone by name",
parameters: Type.Object({
name: Type.String({ description: "Name to greet" }),
}),
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
return {
content: [{ type: "text", text: `Hello, ${params.name}!` }],
details: {},
};
},
});
}
This matches the concrete example found in packages/coding-agent/examples/extensions/hello.ts.
Running and Testing Custom Tools
Launch PrimeAgent with your extension loaded:
prime-agent -e ~/.prime/agent/extensions/greet.ts
Once running, the LLM will see greet in its tool catalog. When the model emits a tool call like:
{
"toolCallId": "tc-1",
"toolName": "greet",
"arguments": { "name": "Alice" }
}
The runtime in packages/coding-agent/src/modes/interactive/components/tool-execution.ts:
- Looks up
greetin the registry populated byloader.ts - Validates
argumentsagainst the Typebox schema - Executes your
executefunction - Streams the result back to the model and renders it in the TUI
Advanced Patterns for Custom Tools
Streaming Progress Updates
Use onUpdate to send partial results before completion—useful for long‑running operations:
async execute(toolCallId, params, signal, onUpdate, ctx) {
onUpdate({ content: [{ type: "text", text: "Starting..." }] });
const result = await expensiveOperation(params);
return {
content: [{ type: "text", text: result }],
details: { duration: Date.now() - start },
};
}
Accessing Context and State
The ctx parameter provides access to the session state, file system abstractions, and other agent services. This enables tools that interact with the codebase or persist data across invocations.
Cancellation Handling
Respect the signal parameter to abort operations when the user interrupts:
async execute(_toolCallId, params, signal, _onUpdate, _ctx) {
const controller = new AbortController();
signal.addEventListener("abort", () => controller.abort());
return await fetchData(params, controller.signal);
}
Key Source Files Reference
| File | Role in Custom Tool Lifecycle |
|---|---|
packages/coding-agent/docs/extensions.md |
User guide with examples (lines 76‑90) |
packages/coding-agent/src/core/extensions/types.ts |
ExtensionAPI interface and registerTool signature (line 1081) |
packages/coding-agent/src/core/extensions/loader.ts |
Extension discovery and registration |
packages/coding-agent/src/core/tools/tool-definition-wrapper.ts |
Tool result formatting and runtime adapter |
packages/coding-agent/examples/extensions/hello.ts |
Working example extension |
Summary
- PrimeAgent custom tools are defined in TypeScript extensions loaded from
~/.prime/agent/extensions/or project‑local paths. - Use
pi.registerTool()from theExtensionAPIto add tools, passing aToolDefinitionwith name, label, description, typebox parameters, and async execute function. - The runtime validates parameters, executes your handler, and streams results back to the LLM and UI.
- Extensions execute without compilation thanks to jiti, enabling rapid iteration.
Frequently Asked Questions
What programming language should I use for PrimeAgent extensions?
Write extensions in TypeScript. The loader uses jiti to execute .ts files directly without a build step, giving you full type safety and modern JavaScript features.
Where should I place my extension files?
Place them in ~/.prime/agent/extensions/ for global access, or .prime/agent/extensions/ in your project root for repository‑specific tools. The discovery mechanism checks both locations as documented in packages/coding-agent/docs/extensions.md.
How does parameter validation work for custom tools?
PrimeAgent uses typebox schemas defined in the parameters field. The runtime validates the LLM's JSON arguments against this schema before executing your execute function, ensuring type safety without manual parsing.
Can I use external npm packages in my custom tools?
Yes. Extensions run in a Node.js context with access to npm dependencies. Install packages in your extension directory or reference globally available modules—PrimeAgent's jiti loader resolves imports normally.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →