Skip to content

Custom Tools โ€‹

Custom tools expose your application's capabilities to agents. Each tool has a typed Zod schema, an execute function, and optional metadata like approval requirements, timeouts, and categories.

ts
import { tool, defineTool, createTools } from 'confused-ai';
import { z } from 'zod';

tool() โ€” primary helper โ€‹

ts
import { tool, createAgent } from 'confused-ai';
import { z } from 'zod';

const getOrder = tool({
  name: 'get_order',
  description: 'Retrieve an order by ID. Returns status, items, and shipping info.',
  schema: z.object({
    orderId: z.string().describe('The order ID to look up'),
  }),
  execute: async ({ orderId }, ctx) => {
    const order = await orderService.findById(orderId);
    if (!order) return { error: `Order ${orderId} not found.` };
    return { id: order.id, status: order.status, items: order.items };
  },
});

const agent = createAgent({
  name: 'support',
  instructions: 'Help customers with their orders.',
  model: 'gpt-4o-mini',
  apiKey: process.env.OPENAI_API_KEY!,
  tools: [getOrder],
});

ToolContext โ€‹

The second argument to execute is a ToolContext with request-scoped metadata:

ts
const auditedTool = tool({
  name: 'update_record',
  description: 'Update a database record.',
  schema: z.object({
    id: z.string(),
    patch: z.record(z.unknown()),
  }),
  execute: async ({ id, patch }, ctx) => {
    ctx.logger?.info('Updating record', { id, userId: ctx.userId, runId: ctx.runId });

    // Abort early if the run was cancelled
    if (ctx.signal?.aborted) {
      return { error: 'Run cancelled.' };
    }

    await db.update(id, patch);
    return { updated: true };
  },
});

// ToolContext fields:
// ctx.runId      โ€” current run ID
// ctx.userId     โ€” user who triggered the run
// ctx.sessionId  โ€” current session ID
// ctx.signal     โ€” AbortSignal (fires when agent is cancelled/timed out)
// ctx.logger     โ€” framework logger
// ctx.metadata   โ€” arbitrary key-value from agent.run() options

Approval gates โ€‹

Set needsApproval: true to require human approval before the tool runs:

ts
const sendEmail = tool({
  name: 'send_email',
  description: 'Send an email to a customer.',
  schema: z.object({ to: z.string().email(), subject: z.string(), body: z.string() }),
  needsApproval: true,   // agent will pause and wait for human approval
  execute: async ({ to, subject, body }) => {
    await mailer.send({ to, subject, body });
    return { sent: true };
  },
});

// Dynamic approval based on parameters
const chargeCard = tool({
  name: 'charge_card',
  description: 'Charge a customer credit card.',
  schema: z.object({ customerId: z.string(), amount: z.number() }),
  needsApproval: ({ amount }) => amount > 100,  // only require approval for large charges
  execute: async ({ customerId, amount }) => {
    await payments.charge(customerId, amount);
    return { charged: true };
  },
});

Tool timeout โ€‹

ts
const slowTool = tool({
  name: 'run_report',
  description: 'Generate a complex report (can take up to 2 minutes).',
  schema: z.object({ reportId: z.string() }),
  timeoutMs: 120_000,   // 2 minutes
  execute: async ({ reportId }) => {
    return await reportEngine.generate(reportId);
  },
});

Tool categories and tags โ€‹

ts
import { ToolCategory } from 'confused-ai';

const myTool = tool({
  name: 'search_products',
  description: 'Search the product catalogue.',
  schema: z.object({ query: z.string() }),
  category: ToolCategory.DATA,
  tags: ['search', 'products', 'catalogue'],
  execute: async ({ query }) => searchProducts(query),
});

defineTool (alias) โ€‹

ts
import { defineTool } from 'confused-ai';

// Identical to tool() โ€” just a named alias
const myTool = defineTool({
  name: 'hello',
  description: 'Say hello.',
  schema: z.object({ name: z.string() }),
  execute: async ({ name }) => `Hello, ${name}!`,
});

createTools() โ€” define multiple tools at once โ€‹

ts
import { createTools } from 'confused-ai';

const [getProduct, updateInventory, checkStock] = createTools([
  {
    name: 'get_product',
    description: 'Get product details by SKU.',
    schema: z.object({ sku: z.string() }),
    execute: async ({ sku }) => getProductBySku(sku),
  },
  {
    name: 'update_inventory',
    description: 'Update inventory count for a product.',
    schema: z.object({ sku: z.string(), delta: z.number() }),
    needsApproval: true,
    execute: async ({ sku, delta }) => adjustInventory(sku, delta),
  },
  {
    name: 'check_stock',
    description: 'Check if a product is in stock.',
    schema: z.object({ sku: z.string() }),
    execute: async ({ sku }) => checkProductStock(sku),
  },
]);

Streaming tool output (long-running) โ€‹

ts
const streamingTool = tool({
  name: 'process_large_file',
  description: 'Process a large file and stream progress.',
  schema: z.object({ fileUrl: z.string() }),
  execute: async ({ fileUrl }, ctx) => {
    const lines: string[] = [];
    for await (const line of streamFile(fileUrl)) {
      if (ctx.signal?.aborted) break;
      lines.push(processLine(line));
    }
    return { lines: lines.length, sample: lines.slice(0, 5) };
  },
});

Where to go next โ€‹

  • Tool composition โ€” extendTool, wrapTool, pipeTools.
  • Tools โ€” built-in tools (100+) and the tools: 'web' preset.
  • HITL โ€” durable approval stores for needsApproval tools.

Released under the MIT License.