Skip to content

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

descriptionstringrequired

Tool 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.'
argsZodSchema

Zod 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(),
})
executefunctionrequired

Async function that executes the tool logic.

Signature:

async (state: ThreadState, args?: InferredArgs) => Promise<ToolResult>

Parameters:

  • state: The ThreadState for the current thread
  • args: Validated arguments (if args schema 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.

progressArgumentstring

A 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.

executionProviderstring

The 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

threadIdstring

Current thread's Durable Object ID.

console.log('Thread:', state.threadId);
agentIdstring

Name of the agent that the thread runs.

console.log('Agent:', state.agentId);
const agent = await state.loadAgent(state.agentId);
executionExecutionState | null

State 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 execution
  • currentSide: the side that takes the current turn, 'a' or 'b'
  • stepCount: the number of model request/response cycles in the active execution
  • stopped and stoppedBy: whether the execution has stopped, and the side that stopped it
  • messageHistory: 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 store

Read 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>): void
toolNamestringrequired

Name of tool to queue

argsRecord<string, unknown>required

Arguments 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>
messageInjectMessageInputrequired

Message 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.limitnumber

Maximum messages to retrieve

options.offsetnumber

Number of messages to skip

options.order'asc' | 'desc'

Sort order

options.includeSilentboolean

Include silent messages

options.maxDepthnumber

Maximum 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): void
eventstringrequired

Event type identifier

dataunknownrequired

Event 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