Skip to main content

Custom Tools (JS/TS)

Tools let the agent call functions in the user's browser. When the agent decides to use a tool, the SDK executes it client-side and sends the result back to continue the conversation.

Defining tools

Use createToolHelper for type-safe tool definitions:

import { Agent, createToolHelper } from '@kapaai/agent-core';
import { z } from 'zod';

const tool = createToolHelper();

const tools = [
tool({
name: 'get_current_time',
description: 'Get the current date and time',
parameters: z.object({}),
displayName: 'Current Time',
execute: async () => ({ time: new Date().toISOString() }),
}),
tool({
name: 'calculate',
description: 'Evaluate a math expression',
parameters: z.object({
expression: z.string().describe('The math expression to evaluate'),
}),
displayName: 'Calculator',
needsApproval: true,
execute: async ({ expression }) => {
const result = new Function(`return (${expression})`)();
return { expression, result };
},
}),
];

const agent = new Agent({
...config,
tools,
context: {},
});

Tool options

FieldTypeRequiredDescription
namestringYesUnique tool name (sent to the LLM).
descriptionstringYesDescription of what the tool does (sent to the LLM).
parametersZodType | objectYesZod schema or raw JSON Schema.
execute(args, context) => Promise<unknown>YesFunction to execute when the tool is called.
displayNamestringNoHuman-readable name for UI display.
needsApprovalbooleanNoIf true, the user must approve before execution.

Approval flow

When a tool has needsApproval: true, its ToolCallDisplay.status will be 'approval_requested' instead of immediately executing. You must call agent.approveToolCall(id) or agent.rejectToolCall(id) to proceed:

function renderToolCall(tc) {
if (tc.status === 'approval_requested') {
const allowBtn = document.createElement('button');
allowBtn.textContent = 'Allow';
allowBtn.onclick = () => agent.approveToolCall(tc.id);

const denyBtn = document.createElement('button');
denyBtn.textContent = 'Deny';
denyBtn.onclick = () => agent.rejectToolCall(tc.id);

// ... render buttons
}
}

Context

The second argument to execute is the context object you passed to the Agent:

const agent = new Agent({
tools,
context: { apiClient: myApiClient, userId: 'user-123' },
...config,
});

// In a tool definition:
execute: async ({ query }, ctx) => {
return ctx.apiClient.search(query);
},

For type-safe context, use the generic parameter on createToolHelper:

type MyContext = { apiClient: ApiClient; userId: string };
const tool = createToolHelper<MyContext>();

tool({
name: 'search',
description: 'Search for items',
parameters: z.object({ query: z.string() }),
execute: async ({ query }, ctx) => {
// ctx is typed as MyContext
return ctx.apiClient.search(query);
},
});

Without Zod

If you don't want to use Zod, pass a raw JSON Schema object:

{
name: 'search',
description: 'Search for items',
parameters: {
type: 'object',
properties: {
query: { type: 'string', description: 'Search query' },
},
required: ['query'],
},
execute: async (args) => {
return searchItems(args.query);
},
}
note

Without Zod, the execute args are typed as Record<string, unknown> — you'll need to cast or validate manually.

Built-in tools

The agent includes a built-in search_knowledge_base tool that searches your project's knowledge base server-side — the same sources you've configured in the Kapa dashboard (documentation, GitHub repos, API references, etc.). This tool is executed by Kapa's backend and cannot be customized.

You can provide display metadata via builtinToolMeta so it shows a friendly name in your UI instead of the raw tool name:

const agent = new Agent({
...config,
builtinToolMeta: {
search_knowledge_base: {
displayName: 'Search Knowledge Base',
},
},
});

Without builtinToolMeta, tool call displays will show the raw name search_knowledge_base.