Skip to content

Adding a Tool

CortexPrism edited this page Jun 17, 2026 · 1 revision

Adding a New Tool

This guide walks through adding a new built-in tool to CortexPrism.

Steps

1. Create the Tool File

Create src/tools/builtin/your_tool.ts:

import type { Tool, ToolCallResult, ToolContext } from "../../tools/types.ts";

const yourTool: Tool = {
  definition: {
    name: "your_tool",
    description: "What this tool does — be descriptive, this is read by the LLM",
    params: [
      {
        name: "input",
        type: "string",
        description: "The input value",
        required: true,
      },
    ],
    capabilities: ["network:fetch"], // Declare required permissions
  },
  execute: async (
    args: Record<string, unknown>,
    ctx: ToolContext,
  ): Promise<ToolCallResult> => {
    const start = Date.now();
    try {
      const result = await doSomething(args.input as string);
      return {
        toolName: "your_tool",
        success: true,
        output: JSON.stringify(result),
        durationMs: Date.now() - start,
      };
    } catch (err) {
      return {
        toolName: "your_tool",
        success: false,
        output: "",
        error: err.message,
        durationMs: Date.now() - start,
      };
    }
  },
};

export default yourTool;

2. Register in Registry

Add to src/tools/registry.ts:

import yourTool from "./builtin/your_tool.ts";

// In registerBuiltinTools():
registry.register(yourTool);

3. Wire into WebSocket (if needed)

If the tool should be available in the Web UI chat, add it to the WebSocket handler's tool set in src/server/ws.ts.

4. Add Policy Rules (if needed)

If the tool executes shell commands or makes network requests, add a policy rule in the default seeded policies or document the required policy configuration.

5. Add Tests

Create tests/your_tool_test.ts with Deno test runner:

import { assertEquals } from "@std/assert";

Deno.test("your_tool - basic functionality", async () => {
  // Test setup and assertions
});

Tool Interface Reference

interface Tool {
  definition: ToolDefinition;
  execute(args: Record<string, unknown>, ctx: ToolContext): Promise<ToolCallResult>;
}

interface ToolDefinition {
  name: string;
  description: string;
  params: ToolParam[];
  capabilities: ToolCapability[];
}

interface ToolParam {
  name: string;
  type: "string" | "number" | "boolean" | "object" | "array";
  description: string;
  required?: boolean;
  enum?: string[];
}

interface ToolCallResult {
  toolName: string;
  success: boolean;
  output: string;
  error?: string;
  durationMs: number;
}

Best Practices

  • Track durationMs accurately
  • Return success: false with error message on failure (never throw)
  • Keep output as a string (serialize objects)
  • Declare per-tool capabilities matching what the tool needs
  • Write clear description and params — the LLM reads these to decide when to use the tool

See Also

Clone this wiki locally