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

> **Info:**
>
> Tools are auto-discovered from `agents/tools/`. A prompt is callable as a tool through its required `toolDescription`. An agent is callable as a tool when it sets `exposeAsTool: true`.

## defineTool

The `defineTool` function creates a function tool with full TypeScript support and Zod validation.

### Function Signature

`defineTool` takes one options object:

```typescript
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:

```typescript
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`:

```typescript
export default defineTool({
  description: 'Get the current server time in ISO format',
  execute: async () => ({
    status: 'success',
    result: new Date().toISOString(),
  }),
});
```

### Parameters

- description (string), required

  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

  ```typescript
  'Search the knowledge base for product documentation, FAQs, and troubleshooting guides. Returns relevant articles sorted by relevance.'
  ```

* args (ZodSchema)

  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.

  ```typescript
  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(),
  })
  ```

- execute (function), required

  Async function that executes the tool logic.

  **Signature:**

  ```typescript
  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`.

* variables (VariableDefinition\[])

  Thread environment variables that the tool requires or optionally uses. See [defineTool](/docs/api-reference/define/tool).

- uses (readonly 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.

* progressArgument (string)

  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](/docs/api-reference/hooks#tool_call_started).

- annotations (Record\<string, unknown>)

  Opaque annotations for tool metadata.

* executionMode ('local' | 'provider'), default: 'local'

  Select local execution or execution by the upstream provider.

- executionProvider (string)

  The provider that executes a provider-mode tool.

## Type Definitions

### Tool

```typescript
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

```typescript
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

```typescript
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)

```typescript
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](/docs/core-concepts/flowstate).

`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

- threadId (string)

  Current thread's Durable Object ID.

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

* agentId (string)

  Name of the agent that the thread runs.

  ```typescript
  console.log('Agent:', state.agentId);
  const agent = await state.loadAgent(state.agentId);
  ```

- execution (ExecutionState | null)

  State of the active execution. The value is `null` when the thread is at rest, for example in a thread endpoint.

  ```typescript
  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

* arguments (Record\<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.

  ```typescript
  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.

  ```typescript
  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.

```typescript
queueTool(toolName: string, args: Record<string, unknown>): void
```

- toolName (string), required

  Name of tool to queue

* args (Record\<string, unknown>), required

  Arguments for the tool

**Example:**

```typescript
state.queueTool('send_email', {
  to: 'user@example.com',
  subject: 'Order Confirmation',
});
```

### injectMessage

Add a message to the conversation without triggering execution.

```typescript
injectMessage(message: InjectMessageInput): Promise<Message>
```

- message (InjectMessageInput), required

  Message to inject

**InjectMessageInput:**

```typescript
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:**

```typescript
await state.injectMessage({
  role: 'system',
  content: 'User context loaded',
  silent: true,
});
```

### getMessages

Retrieve message history from thread storage.

```typescript
getMessages(options?: GetMessagesOptions): Promise<MessagesResult>
```

- options.limit (number)

  Maximum messages to retrieve

* options.offset (number)

  Number of messages to skip

- options.order ('asc' | 'desc')

  Sort order

* options.includeSilent (boolean)

  Include silent messages

- options.maxDepth (number)

  Maximum nesting depth for sub-prompt messages

The result contains `messages`, `total`, and `hasMore`.

**Example:**

```typescript
const { messages } = await state.getMessages({ limit: 20, offset: 0, order: 'desc' });
```

### emit

Send custom events to WebSocket clients.

```typescript
emit(event: string, data: unknown): void
```

- event (string), required

  Event type identifier

* data (unknown), required

  Event payload

**Example:**

```typescript
state.emit('progress', {
  current: 5,
  total: 10,
  percentage: 50,
});
```

## Tool Results

### Text Results

```typescript
return {
  status: 'success',
  result: 'Operation completed successfully',
};
```

### JSON Results

```typescript
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.

```typescript
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.

```typescript
return {
  status: 'success',
  result: 'Generated image',
  attachments: [{
    name: 'generated.png',
    mimeType: 'image/png',
    data: base64ImageData,
  }],
};
```

### Error Results

```typescript
return {
  status: 'error',
  error: 'Failed to find user: User not found',
};
```

## Complete Examples

### Database Query Tool

```typescript
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

```typescript
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

```typescript
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

```typescript
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

```typescript
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

## Related Documentation

- [Core Concepts](/docs/core-concepts/tools)

  Learn tool fundamentals

* [ThreadState API](/docs/core-concepts/flowstate)

  Complete ThreadState reference

- [Prompts API](/docs/api-reference/prompts)

  Configure prompts that use tools

* [Hooks API](/docs/api-reference/hooks)

  Intercept tool execution
