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:

  1. Looks up greet in the registry populated by loader.ts
  2. Validates arguments against the Typebox schema
  3. Executes your execute function
  4. 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 the ExtensionAPI to add tools, passing a ToolDefinition with 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →