Tools API Reference
Complete technical reference for creating and configuring tools
Overview
Tools in Standard Agents Builder are callable capabilities that extend AI agents beyond text generation. This document provides the complete API specification for all tool types.
defineTool
The defineTool function creates a function tool with full TypeScript support and Zod validation.
Function Signature
defineTool takes one options object:
import type {
DefineToolOptions, ToolDefinition, ToolArgs, ToolTenvs, ThreadState,
} from '@standardagents/spec';
function defineTool<
State = ThreadState,
Args extends ToolArgs | null = null,
Tenvs extends ToolTenvs | null = null,
Uses extends readonly string[] = readonly string[],
>(
options: DefineToolOptions<State, Args, Tenvs, Uses>
): ToolDefinition<State, Args, Tenvs, Uses>A tool with arguments:
import { defineTool } from '@standardagents/spec';
import { z } from 'zod';
export default defineTool({
description: 'Look up an order by ID',
args: z.object({
orderId: z.string().describe('Order ID'),
}),
execute: async (state, args) => {
return { status: 'success', result: `Order ${args.orderId} found` };
},
});A tool without arguments omits args:
export default defineTool({
description: 'Get the current server time in ISO format',
execute: async () => ({
status: 'success',
result: new Date().toISOString(),
}),
});Parameters
descriptionstringrequiredTool description shown to LLMs. Be specific about what the tool does.
Best practices:
- Include what the tool does
- Mention expected inputs/outputs
- Describe when to use it
'Search the knowledge base for product documentation, FAQs, and troubleshooting guides. Returns relevant articles sorted by relevance.'argsZodSchemaZod object schema defining tool parameters. Requires Zod 4 (zod@^4.0.0); Zod 3 schemas are unsupported.
Supported Zod types:
z.string(),z.number(),z.boolean()z.enum(),z.literal(),z.array()z.object(),z.record(),z.union()z.optional(),z.nullable(),z.default()
Default recursive type depth: 7 levels. This is a TypeScript constraint.
z.object({
query: z.string().describe('Search query'),
limit: z.number().optional().default(10).describe('Max results'),
filters: z.object({
category: z.string().optional(),
}).optional(),
})executefunctionrequiredAsync function that executes the tool logic.
Signature:
async (state: ThreadState, args?: InferredArgs) => Promise<ToolResult>Parameters:
state: TheThreadStatefor the current threadargs: Validated arguments (ifargsschema provided)
Returns: ToolResult object
A tool that produces structured output validates it inside execute and returns the serialized value in result.
variablesVariableDefinition[]Thread environment variables that the tool requires or optionally uses. See defineTool.
usesreadonly string[]Callable dependencies for this tool. A const tuple constrains queueTool()
and invokeTool() in TypeScript. Builder also checks this list at runtime.
Packed tools must declare their callable dependencies.
progressArgumentstringA string argument to include in the tool_call_started hook and thread event.
The runtime waits for this argument to finish streaming. Put it early in the
schema. See tool start timing.
annotationsRecord<string, unknown>Opaque annotations for tool metadata.
executionMode'local' | 'provider'default: 'local'Select local execution or execution by the upstream provider.
executionProviderstringThe provider that executes a provider-mode tool.
Type Definitions
Tool
type Tool<State = ThreadState, Args extends ToolArgs | null = null> = Args extends ToolArgs
? (state: State, args: z.infer<Args>) => Promise<ToolResult>
: (state: State) => Promise<ToolResult>;ToolArgs
import type { ToolArgs, ToolArgsRawShape, ToolArgsNode } from '@standardagents/spec';
// ToolArgs is a Zod object schema.
// ToolArgsNode is the supported field-schema union.
// The default recursive type depth is 7.ToolResult
interface ToolResult {
status: 'success' | 'error';
result?: string;
error?: string;
stack?: string;
attachments?: Array<ToolAttachment | AttachmentRef>;
}
interface ToolAttachment {
name: string; // Filename, for example "generated.png"
mimeType: string; // MIME type
data: string; // Base64-encoded file data
width?: number; // Image width in pixels
height?: number; // Image height in pixels
}ThreadState (Partial)
interface ThreadState {
// Identity
readonly threadId: string;
readonly agentId: string;
readonly userId: string | null;
readonly createdAt: number;
// Thread invocation or creation arguments
readonly arguments: Record<string, unknown>;
// Execution state. The value is null when the thread is at rest.
execution: ExecutionState | null;
// Runtime scratch context for the current execution only
context: Record<string, unknown>;
// Messages
getMessages(options?: GetMessagesOptions): Promise<MessagesResult>;
injectMessage(message: InjectMessageInput): Promise<Message>;
// Definitions
loadAgent<T = unknown>(name: string): Promise<T>;
// Tools and events
queueTool(toolName: string, args: Record<string, unknown>): void;
invokeTool(toolName: string, args: Record<string, unknown>): Promise<ToolResult>;
emit(event: string, data: unknown): void;
// Durable key-value store
getValue<T = unknown>(key: string): Promise<T | null>;
setValue(key: string, value: unknown): Promise<void>;
// Thread environment values
env(propertyName: string): Promise<string>;
envType(propertyName: string): Promise<'text' | 'secret'>;
setEnv(
propertyName: string,
value: string,
options?: { type?: 'text' | 'secret' }
): Promise<void>;
// Sandboxed code execution
runCode(source: string, options?: CodeExecutionOptions): CodeExecution;
}
interface ExecutionState {
readonly flowId: string;
readonly currentSide: 'a' | 'b';
readonly stepCount: number;
readonly sideAStepCount: number;
readonly sideBStepCount: number;
readonly stopped: boolean;
readonly stoppedBy?: 'a' | 'b';
readonly messageHistory: Message[];
forceTurn(side: 'a' | 'b'): void;
stop(): void;
readonly abortSignal: AbortSignal;
readonly promptPath: string[];
}CodeExecutionOptions supports execute, imports, globals, modules, language, memoryLimitBytes, filename, and report. execute defaults to { fn: 'default', args: [] }; use it to run a named export or pass args, for example { execute: { fn: 'addOne', args: [100] } }. Use modules for relative local ES modules, for example { './math.js': 'export const add = (a, b) => a + b' }. The memory limit of a sandbox is 128 MB, the Cloudflare ceiling for a Workers isolate. memoryLimitBytes cannot lower that ceiling, and a higher value is clamped to it. See ThreadState.
state.env(name) resolves a value in this order: thread environment, agent definition defaults, prompt definition defaults, user-account environment, then runtime-instance environment. state.envType(name) returns secret by default; values marked text may be displayed in tool output, while secret values should be redacted. Use state.setEnv(name, value, { type }) to write thread-scoped values as either text or secret; omitted type preserves the existing type or defaults new keys to secret.
Use state.arguments for thread invocation data and subagent creation arguments. Subagent creation arguments persist with the child thread and are available to both child sides. Use state.context only for execution-local scratch data shared during the current run.
ThreadState Properties
threadIdstringCurrent thread's Durable Object ID.
console.log('Thread:', state.threadId);agentIdstringName of the agent that the thread runs.
console.log('Agent:', state.agentId);
const agent = await state.loadAgent(state.agentId);executionExecutionState | nullState of the active execution. The value is null when the thread is at rest, for example in a thread endpoint.
if (state.execution) {
console.log(state.execution.flowId);
console.log(state.execution.currentSide);
console.log(state.execution.stepCount);
const lastMessage = state.execution.messageHistory.at(-1);
}flowId: unique ID for this executioncurrentSide: the side that takes the current turn,'a'or'b'stepCount: the number of model request/response cycles in the active executionstoppedandstoppedBy: whether the execution has stopped, and the side that stopped itmessageHistory: the live persisted history for the current execution
argumentsRecord<string, unknown>Read-only thread invocation or creation arguments. Subagent creation arguments appear here for both child sides and are also available as prompt variables.
getValue(key) / setValue(key, value)durable key-value storeRead and write JSON-serializable values that persist with the thread.
await state.setValue('custom_value', { id: args.id });
const value = await state.getValue('custom_value');env(propertyName)(propertyName: string) => Promise<string>Resolve an environment value from the thread, agent, prompt, user account, or runtime instance, in that order.
const apiKey = await state.env('WEATHER_API_KEY');envType(propertyName)(propertyName: string) => Promise<'text' | 'secret'>Resolve whether an environment value should be treated as display-safe text or as a redacted secret.
setEnv(propertyName, value, options)(propertyName: string, value: string, options?: { type?: 'text' | 'secret' }) => Promise<void>Write a thread-scoped environment value. Pass { type: 'text' } for display-safe values or { type: 'secret' } for redacted values.
ThreadState methods
Tools call these methods on the state parameter. They need no import.
queueTool
Queue another tool to execute after the current one.
queueTool(toolName: string, args: Record<string, unknown>): voidtoolNamestringrequiredName of tool to queue
argsRecord<string, unknown>requiredArguments for the tool
Example:
state.queueTool('send_email', {
to: '[email protected]',
subject: 'Order Confirmation',
});injectMessage
Add a message to the conversation without triggering execution.
injectMessage(message: InjectMessageInput): Promise<Message>messageInjectMessageInputrequiredMessage to inject
InjectMessageInput:
interface InjectMessageInput {
role: 'user' | 'assistant' | 'system';
content: string;
silent?: boolean; // Hidden from ordinary UI lists
metadata?: Record<string, unknown>;
}Injection persists the message, runs the ordinary create-message lifecycle hooks, and updates live execution history. Silent messages remain eligible for model context. History hooks build the request context from one stable snapshot per provider request; injections made during that assembly enter the following request.
Example:
await state.injectMessage({
role: 'system',
content: 'User context loaded',
silent: true,
});getMessages
Retrieve message history from thread storage.
getMessages(options?: GetMessagesOptions): Promise<MessagesResult>options.limitnumberMaximum messages to retrieve
options.offsetnumberNumber of messages to skip
options.order'asc' | 'desc'Sort order
options.includeSilentbooleanInclude silent messages
options.maxDepthnumberMaximum nesting depth for sub-prompt messages
The result contains messages, total, and hasMore.
Example:
const { messages } = await state.getMessages({ limit: 20, offset: 0, order: 'desc' });emit
Send custom events to WebSocket clients.
emit(event: string, data: unknown): voideventstringrequiredEvent type identifier
dataunknownrequiredEvent payload
Example:
state.emit('progress', {
current: 5,
total: 10,
percentage: 50,
});Tool Results
Text Results
return {
status: 'success',
result: 'Operation completed successfully',
};JSON Results
return {
status: 'success',
result: JSON.stringify({ id: '123', name: 'John' }),
};Multiple Text Items
result is one string. Join several text items before the tool returns.
return {
status: 'success',
result: ['Found 3 results:', JSON.stringify(results)].join('\n'),
};Image Results
Return files in attachments. The runtime stores each attachment in the thread file system.
return {
status: 'success',
result: 'Generated image',
attachments: [{
name: 'generated.png',
mimeType: 'image/png',
data: base64ImageData,
}],
};Error Results
return {
status: 'error',
error: 'Failed to find user: User not found',
};Complete Examples
Database Query Tool
import { defineTool } from '@standardagents/spec';
import { z } from 'zod';
export default defineTool({
description: 'Look up customer information by ID or email',
args: z.object({
customerId: z.string().optional().describe('Customer ID'),
email: z.string().email().optional().describe('Customer email'),
}),
execute: async (state, args) => {
if (!args.customerId && !args.email) {
return {
status: 'error',
error: 'Either customerId or email is required',
};
}
const databaseUrl = await state.env('DATABASE_URL');
let customer;
if (args.customerId) {
customer = await lookupCustomer(databaseUrl, { customerId: args.customerId });
} else {
customer = await lookupCustomer(databaseUrl, { email: args.email });
}
if (!customer) {
return {
status: 'error',
error: 'Customer not found',
};
}
return {
status: 'success',
result: JSON.stringify(customer),
};
},
});API Integration Tool
import { defineTool } from '@standardagents/spec';
import { z } from 'zod';
export default defineTool({
description: 'Fetch current weather for a city',
args: z.object({
city: z.string().describe('City name'),
units: z.enum(['metric', 'imperial']).default('metric'),
}),
execute: async (state, args) => {
const apiKey = await state.env('WEATHER_API_KEY');
try {
const response = await fetch(
`https://api.weather.com/v1/current?city=${encodeURIComponent(args.city)}&units=${args.units}&key=${apiKey}`
);
if (!response.ok) {
return {
status: 'error',
error: `Weather API error: ${response.status}`,
};
}
const data = (await response.json()) as { temperature: number; conditions: string };
return {
status: 'success',
result: `Weather in ${args.city}: ${data.temperature}°, ${data.conditions}`,
};
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
return {
status: 'error',
error: `Failed to fetch weather: ${message}`,
};
}
},
});Tool with Queue Chaining
import { defineTool } from '@standardagents/spec';
import { z } from 'zod';
export default defineTool({
description: 'Process a customer order',
args: z.object({
orderId: z.string().describe('Order ID'),
action: z.enum(['confirm', 'cancel', 'ship']),
}),
execute: async (state, args) => {
const result = await processOrder(args.orderId, args.action);
// Queue follow-up notifications
state.queueTool('send_notification', {
customerId: result.customerId,
message: `Order ${args.orderId} has been ${args.action}ed`,
});
return {
status: 'success',
result: `Order ${args.orderId} ${args.action}ed successfully`,
};
},
});Tool with Message Injection
import { defineTool } from '@standardagents/spec';
import { z } from 'zod';
export default defineTool({
description: 'Update conversation context with new information',
args: z.object({
contextType: z.enum(['user_info', 'preferences', 'history']),
dataJson: z.string().describe('Context data as a JSON string'),
}),
execute: async (state, args) => {
const data: unknown = JSON.parse(args.dataJson);
// Store context in the thread's durable key-value store
await state.setValue(`context:${args.contextType}`, data);
// Inject a system message with the context
await state.injectMessage({
role: 'system',
content: `Updated ${args.contextType}: ${JSON.stringify(data)}`,
silent: false,
});
return {
status: 'success',
result: `Context updated: ${args.contextType}`,
};
},
});Simple Tool Without Arguments
import { defineTool } from '@standardagents/spec';
export default defineTool({
description: 'Get the current server time in ISO format',
execute: async () => {
return {
status: 'success',
result: new Date().toISOString(),
};
},
});File Structure
agents/
└── tools/
├── search_database.ts # export default defineTool({ ... })
├── create_ticket.ts # export default defineTool({ ... })
├── send_notification.ts # export default defineTool({ ... })
└── get_current_time.ts # export default defineTool({ ... })Requirements:
- One tool per file
- Default export required
- Use snake_case for file names
- No nested directories