co-mono/packages/coding-agent/examples/custom-tools
Mario Zechner d6283f99dc refactor(hooks): split session events into individual typed events
Major changes:
- Replace monolithic SessionEvent with reason discriminator with individual
  event types: session_start, session_before_switch, session_switch,
  session_before_new, session_new, session_before_branch, session_branch,
  session_before_compact, session_compact, session_shutdown
- Each event has dedicated result type (SessionBeforeSwitchResult, etc.)
- HookHandler type now allows bare return statements (void in return type)
- HookAPI.on() has proper overloads for each event with correct typing

Additional fixes:
- AgentSession now always subscribes to agent in constructor (was only
  subscribing when external subscribe() called, breaking internal handlers)
- Standardize on undefined over null throughout codebase
- HookUIContext methods return undefined instead of null
- SessionManager methods return undefined instead of null
- Simplify hook exports to 'export type * from types.js'
- Add detailed JSDoc for skipConversationRestore vs cancel
- Fix createBranchedSession to rebuild index in persist mode
- newSession() now returns the session file path

Updated all example hooks, tests, and emission sites to use new event types.
2025-12-30 22:42:22 +01:00
..
hello WIP: Remove global state from pi-ai OAuth/API key handling 2025-12-25 01:01:03 +01:00
question refactor(hooks): split session events into individual typed events 2025-12-30 22:42:22 +01:00
subagent WIP: Major cleanup - move Attachment to consumers, simplify agent API 2025-12-30 22:42:20 +01:00
todo WIP: Remove global state from pi-ai OAuth/API key handling 2025-12-25 01:01:03 +01:00
README.md Update custom tools README for subdirectory/index.ts structure 2025-12-19 04:54:02 +01:00

Custom Tools Examples

Example custom tools for pi-coding-agent.

Examples

Each example uses the subdirectory/index.ts structure required for tool discovery.

hello/

Minimal example showing the basic structure of a custom tool.

question/

Demonstrates pi.ui.select() for asking the user questions with options.

todo/

Full-featured example demonstrating:

  • onSession for state reconstruction from session history
  • Custom renderCall and renderResult
  • Proper branching support via details storage
  • State management without external files

subagent/

Delegate tasks to specialized subagents with isolated context windows. Includes:

  • index.ts - The custom tool (single, parallel, and chain modes)
  • agents.ts - Agent discovery helper
  • agents/ - Sample agent definitions (scout, planner, reviewer, worker)
  • commands/ - Workflow presets (/implement, /scout-and-plan, /implement-and-review)

See subagent/README.md for full documentation.

Usage

# Test directly (can point to any .ts file)
pi --tool examples/custom-tools/todo/index.ts

# Or copy entire folder to tools directory for persistent use
cp -r todo ~/.pi/agent/tools/

Then in pi:

> add a todo "test custom tools"
> list todos
> toggle todo #1
> clear todos

Writing Custom Tools

See docs/custom-tools.md for full documentation.

Key Points

Factory pattern:

import { Type } from "@sinclair/typebox";
import { StringEnum } from "@mariozechner/pi-ai";
import { Text } from "@mariozechner/pi-tui";
import type { CustomToolFactory } from "@mariozechner/pi-coding-agent";

const factory: CustomToolFactory = (pi) => ({
  name: "my_tool",
  label: "My Tool",
  description: "Tool description for LLM",
  parameters: Type.Object({
    action: StringEnum(["list", "add"] as const),
  }),
  
  // Called on session start/switch/branch/clear
  onSession(event) {
    // Reconstruct state from event.entries
  },
  
  async execute(toolCallId, params) {
    return {
      content: [{ type: "text", text: "Result" }],
      details: { /* for rendering and state reconstruction */ },
    };
  },
});

export default factory;

Custom rendering:

renderCall(args, theme) {
  return new Text(
    theme.fg("toolTitle", theme.bold("my_tool ")) + args.action,
    0, 0  // No padding - Box handles it
  );
},

renderResult(result, { expanded, isPartial }, theme) {
  if (isPartial) {
    return new Text(theme.fg("warning", "Working..."), 0, 0);
  }
  return new Text(theme.fg("success", "✓ Done"), 0, 0);
},

Use StringEnum for string parameters (required for Google API compatibility):

import { StringEnum } from "@mariozechner/pi-ai";

// Good
action: StringEnum(["list", "add"] as const)

// Bad - doesn't work with Google
action: Type.Union([Type.Literal("list"), Type.Literal("add")])