Skip to content

Tool Handling ​

Control which tools any agent can use and intercept tool executions at runtime. The same toolHandler interface works across all 12 agents.

Allow List ​

Only permit specific tools:

ts
const result = await agents.ask(
  "claude",
  "Analyze the codebase",
  {
    permission: "auto-allow",
    toolHandler: {
      allowed: ["Read", "Glob", "Grep"],
    },
  },
);

Any tool not in the list is automatically denied.

Block List ​

Block specific tools while allowing everything else:

ts
const result = await agents.ask(
  "claude",
  "Refactor utils",
  {
    permission: "auto-allow",
    toolHandler: {
      blocked: ["Bash", "Write"],
    },
  },
);

Custom Handler ​

Intercept each tool use with a callback:

ts
const result = await agents.ask(
  "claude",
  "Fix the bug",
  {
    permission: "auto-allow",
    toolHandler: {
      onToolUse: async (event) => {
        console.log(`Agent wants to use ${event.tool}`);

        if (event.tool === "Edit") {
          return "allow";
        }

        if (event.tool === "Bash") {
          return { decision: "deny", reason: "shell commands not permitted" };
        }

        // Provide a custom result instead of running the tool
        return { result: "mocked file contents" };
      },
    },
  },
);

The handler receives an IToolUseEvent and must return one of:

  • "allow" - let the tool execute
  • "deny" - block the tool (short form; no reason)
  • { decision: "deny", reason: string } - block with a reason surfaced to the model
  • { result: unknown } - skip execution, send this as the (successful) tool result
  • { result: unknown, isError: true } - mark result as an error so the model can react (retry, fall back) instead of treating it as success
ts
toolHandler: {
  onToolUse: async (event) => {
    if (event.tool === "Bash" && isDestructive(event.input)) {
      return {
        result: "Destructive shell commands are disabled in this sandbox.",
        isError: true,
      };
    }
    return "allow";
  },
},

Human-in-the-Loop (HITL) ​

Build approval gates where humans or other systems decide:

ts
const result = await agents.ask(
  "claude",
  "Deploy the new version",
  {
    permission: "stream",  // stream permission requests to your handler
    toolHandler: {
      onToolUse: async (event) => {
        const callStr = JSON.stringify(event.input);
        await slackNotify(`Agent wants to run: ${event.tool}(${callStr})`);
        const approved = await waitForApproval(event.toolCallId);
        return approved ? "allow" : { decision: "deny", reason: "operator denied" };
      },
    },
  },
);

Error Recovery ​

If onToolUse throws, the stream rejects with the thrown error. Provide onError to log, recover, or force a decision:

ts
const result = await agents.ask(
  "claude",
  "Fix the bug",
  {
    permission: "auto-allow",
    toolHandler: {
      onToolUse: async (event) => {
        return await riskyPolicyCheck(event);
      },
      onError: (err, event) => {
        logger.warn(`policy check failed for ${event.tool}`, err);
        return "deny";
      },
    },
  },
);

onError returns the same decision shape as onToolUse. If it also throws, the error propagates.

Precedence ​

When multiple options are set, they're evaluated in this order:

  1. blocked - if the tool is blocked, deny immediately
  2. allowed - if an allow list exists and the tool isn't in it, deny
  3. onToolUse - call the custom handler
  4. Default - allow (if permission: "auto-allow") or surface to the caller (if permission: "stream")

Built-in Tool Names ​

ts
import { BUILT_IN_TOOL_NAMES, isBuiltInTool } from "@pivanov/agents-wire";

console.log(BUILT_IN_TOOL_NAMES); // flattened set across every known agent
console.log(isBuiltInTool("Read")); // true (any agent declares it)
console.log(isBuiltInTool("Read", "claude")); // true (Claude declares it)
console.log(isBuiltInTool("Read", "codex")); // false (Codex uses snake_case)
console.log(isBuiltInTool("my-mcp-tool")); // false

Pass the optional agentId to scope the lookup to one agent's namespace — handy when allow/block lists need to differ per agent (Claude's Read vs Codex's read_file).

WARNING

BUILT_IN_TOOL_NAMES is a best-effort snapshot. For the authoritative list for any agent, check the tools array in the session-meta event from a live session.