> ## Documentation Index
> Fetch the complete documentation index at: https://docs.standardagentbuilder.com/llms.txt
> Use this file to discover all available pages before exploring further.

# defineTool

> Define custom tools that agents can call

## Overview

`defineTool` creates a function tool that agents can call during execution. It uses an object-based API for clear, typed configuration.

```typescript theme={null}
import { defineTool } from '@standardagents/spec';
import { z } from 'zod';

export default defineTool({
  description: 'Search the knowledge base for relevant articles',
  args: z.object({
    query: z.string().describe('Search query'),
  }),
  execute: async (state, args) => {
    const results = await searchKnowledgeBase(args.query);
    return {
      status: 'success',
      result: JSON.stringify(results),
    };
  },
});
```

## Type Definition

```typescript theme={null}
import type { ThreadState } from '@standardagents/spec';
import type { z } from 'zod';

function defineTool<
  State = ThreadState,
  Args extends ToolArgs | null = null,
  Tenvs extends ToolTenvs | null = null,
>(
  options: DefineToolOptions<State, Args, Tenvs>
): ToolDefinition<State, Args, Tenvs>;

interface DefineToolOptions<
  State = ThreadState,
  Args extends ToolArgs | null = null,
  Tenvs extends ToolTenvs | null = null,
> {
  description: string;
  args?: Args;
  execute: Tool<State, Args>;
  tenvs?: Tenvs;
  executionMode?: 'local' | 'provider';
  executionProvider?: string;
}

interface ToolDefinition<
  State = ThreadState,
  Args extends ToolArgs | null = null,
  Tenvs extends ToolTenvs | null = null,
> {
  description: string;
  args: Args;
  execute: Tool<State, Args>;
  tenvs: Tenvs;
  executionMode?: 'local' | 'provider';
  executionProvider?: string;
}

type Tool<State, Args extends ToolArgs | null> = Args extends ToolArgs
  ? (state: State, args: z.infer<Args>) => Promise<ToolResult>
  : (state: State) => Promise<ToolResult>;

type ToolArgs = z.ZodObject<Record<string, ToolArgsNode>>;
type ToolTenvs = z.ZodObject<Record<string, ToolArgsNode>>;

// ToolArgsNode supports string, number, boolean, null, literal, enum, array,
// object, record, union, optional, nullable, and default schemas.
// Prefer explicit, bounded shapes for provider-visible tool args. For arbitrary
// JSON payloads, accept a JSON string and parse it inside execute; open-ended
// z.unknown(), z.any(), or catchall schemas can be rejected by strict providers.

interface ToolResult {
  status: 'success' | 'error';
  result?: string;
  error?: string;
  stack?: string;
  attachments?: Array<ToolAttachment | AttachmentRef>;
}

interface ToolAttachment {
  name: string;
  mimeType: string;
  data: string;
  width?: number;
  height?: number;
}
```

## Parameters

<ParamField body="description" type="string" required>
  Human-readable description of what the tool does. This is sent to the LLM to help it decide when to use the tool.

  **Good:**

  ```typescript theme={null}
  description: 'Search the knowledge base for product documentation, FAQs, and troubleshooting guides. Returns relevant articles sorted by relevance.'
  ```

  **Avoid:**

  ```typescript theme={null}
  description: 'Search stuff'
  ```
</ParamField>

<ParamField body="args" type="z.ZodObject">
  Zod object schema defining the tool's parameters. Omit for tools with no arguments.

  Use `.describe()` on each field to help the LLM understand what to pass.

  ```typescript theme={null}
  args: z.object({
    query: z.string().describe('Search query'),
    limit: z.number().optional().default(10).describe('Max results'),
  })
  ```
</ParamField>

<ParamField body="execute" type="(state, args?) => Promise<ToolResult>" required>
  Async function that executes when the tool is called.

  * First parameter: `ThreadState` (execution context)
  * Second parameter: Validated arguments (only if `args` is defined)

  ```typescript theme={null}
  execute: async (state, args) => {
    const result = await doSomething(args.query);
    return { status: 'success', result: JSON.stringify(result) };
  }
  ```
</ParamField>

<ParamField body="tenvs" type="z.ZodObject">
  Zod object schema for thread environment variables the tool requires.

  ```typescript theme={null}
  tenvs: z.object({
    vectorStoreId: z.string().describe('OpenAI Vector Store ID'),
  })
  ```

  Tenvs are validated at runtime. Missing required tenvs cause an error.
</ParamField>

<ParamField body="executionMode" type="'local' | 'provider'">
  Where this tool is executed:

  * `'local'` (default): Execute locally by the execution engine
  * `'provider'`: Executed by the LLM provider, results come in response
</ParamField>

<ParamField body="executionProvider" type="string">
  Which provider executes this tool (when `executionMode='provider'`). e.g., `'openai'`, `'anthropic'`
</ParamField>

## Execute Parameters

### ThreadState (first parameter)

The execution context providing access to thread data and environment:

```typescript theme={null}
interface ThreadState {
  threadId: string;
  flowId: string;
  agentConfig: Agent;
  currentSide: 'a' | 'b';
  turnCount: number;
  stopped: boolean;
  messageHistory: Message[];
  storage: DurableObjectStorage;
  arguments: Record<string, unknown>;
  context: Record<string, unknown>;
  env(propertyName: string): Promise<string>;
  envType(propertyName: string): Promise<'text' | 'secret'>;
  setEnv(
    propertyName: string,
    value: string,
    options?: { type?: 'text' | 'secret' }
  ): Promise<void>;
  rootState: ThreadState;
  runCode(source: string, options?: CodeExecutionOptions): CodeExecution;
}
```

See [ThreadState](/core-concepts/flowstate) for full documentation.

`state.envType(name)` defaults to `secret`. Use `state.setEnv(name, value, { type: 'text' })` only when the value is safe to show in tool output and errors; use `{ type: 'secret' }` for redacted values.

Use `state.arguments` for thread invocation data and resumable subagent creation arguments. Use `state.context` only for mutable scratch data during the current execution.

`CodeExecutionOptions.execute` selects the export to run and optional args, defaulting to `{ fn: 'default', args: [] }`. `CodeExecutionOptions.modules` can provide local relative ES modules to sandboxed code, such as `{ './helpers.js': 'export const ok = true' }`.

### Args (second parameter)

Validated arguments matching your Zod schema:

```typescript theme={null}
// If args is:
args: z.object({
  query: z.string(),
  limit: z.number().optional(),
})

// Then the second parameter is typed as:
{ query: string; limit?: number }
```

## Return Value

Tools must return a `ToolResult` object:

<ResponseField name="status" type="'success' | 'error'" required>
  Whether the tool executed successfully.
</ResponseField>

<ResponseField name="result" type="string">
  The result to return to the LLM. For complex data, use `JSON.stringify()`.
</ResponseField>

<ResponseField name="error" type="string">
  Error message (when status is `'error'`).
</ResponseField>

<ResponseField name="stack" type="string">
  Stack trace for debugging (when status is `'error'`).
</ResponseField>

<ResponseField name="attachments" type="Array<ToolAttachment | AttachmentRef>">
  File attachments to include with the tool result. Attachments are stored in the thread's file system and linked to the message.

  ```typescript theme={null}
  attachments: [{
    name: 'generated.png',
    mimeType: 'image/png',
    data: base64Data,
    width: 1024,
    height: 1024,
  }]
  ```
</ResponseField>

## Examples

### Basic Tool

```typescript theme={null}
import { defineTool } from '@standardagents/spec';
import { z } from 'zod';

export default defineTool({
  description: 'Get the current weather for a location',
  args: z.object({
    location: z.string().describe('City name or coordinates'),
    units: z.enum(['celsius', 'fahrenheit']).default('celsius'),
  }),
  execute: async (state, args) => {
    const weather = await fetchWeather(args.location, args.units);
    return {
      status: 'success',
      result: JSON.stringify(weather),
    };
  },
});
```

### Tool Without Arguments

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

### Using ThreadState

```typescript theme={null}
export default defineTool({
  description: 'Look up customer information',
  args: z.object({
    customerId: z.string().describe('Customer ID'),
  }),
  execute: async (state, args) => {
    // Access thread/account/instance/agent/prompt environment values
    const databaseUrl = await state.env('DATABASE_URL');

    // Access thread invocation or subagent creation arguments
    const userId = state.arguments.userId;

    // Access storage
    const cached = await state.storage.get(`customer:${args.customerId}`);
    if (cached) {
      return { status: 'success', result: cached };
    }

    const customer = await lookupCustomer(databaseUrl, args.customerId);

    return {
      status: 'success',
      result: JSON.stringify(customer),
    };
  },
});
```

### With Thread Environment Variables

```typescript theme={null}
export default defineTool({
  description: 'Search through uploaded files using vector embeddings',
  args: z.object({
    query: z.string().describe('Search query'),
    maxResults: z.number().optional().default(5),
  }),
  execute: async (state, args) => {
    const vectorStoreId = await state.env('VECTOR_STORE_ID');
    const results = await searchVectorStore(vectorStoreId, args.query, args.maxResults);
    return { status: 'success', result: JSON.stringify(results) };
  },
  variables: [
    {
      name: 'VECTOR_STORE_ID',
      type: 'secret',
      required: true,
      description: 'OpenAI Vector Store ID',
    },
  ],
});
```

### Error Handling

```typescript theme={null}
export default defineTool({
  description: 'Process payment',
  args: z.object({
    amount: z.number().positive(),
    currency: z.string().length(3),
  }),
  execute: async (state, args) => {
    try {
      const result = await processPayment(args.amount, args.currency);
      return {
        status: 'success',
        result: `Payment processed: ${result.transactionId}`,
      };
    } catch (error) {
      return {
        status: 'error',
        error: `Payment failed: ${error.message}`,
      };
    }
  },
});
```

### Using Utilities

```typescript theme={null}
import { defineTool } from '@standardagents/spec';
import { queueTool, emitThreadEvent } from '@standardagents/builder';
import { z } from 'zod';

export default defineTool({
  description: 'Start a multi-step workflow',
  args: z.object({
    workflowId: z.string(),
  }),
  execute: async (state, args) => {
    // Queue another tool to run next
    queueTool(state.rootState, 'execute_step', {
      workflowId: args.workflowId,
      step: 1,
    });

    // Emit event to frontend
    await emitThreadEvent(state, 'workflow_started', {
      workflowId: args.workflowId,
    });

    return {
      status: 'success',
      result: 'Workflow initiated',
    };
  },
});
```

### Returning File Attachments

```typescript theme={null}
export default defineTool({
  description: 'Generate an image based on a text prompt',
  args: z.object({
    prompt: z.string().describe('Text description of the image'),
  }),
  execute: async (state, args) => {
    const image = await generateImage(args.prompt);

    return {
      status: 'success',
      result: `Generated image for: "${args.prompt}"`,
      attachments: [{
        name: `generated-${Date.now()}.png`,
        mimeType: 'image/png',
        data: image.base64,
        width: 1024,
        height: 1024,
      }],
    };
  },
});
```

Attachments are automatically stored in the thread's `/attachments/` directory and linked to the tool result message. Large files (>1.75MB) are automatically chunked.

### Provider-Executed Tool

```typescript theme={null}
export default defineTool({
  description: 'Generate images using DALL-E',
  args: z.object({
    prompt: z.string().describe('Image description'),
  }),
  execute: async () => {
    // This won't be called - the provider handles execution
    return { status: 'success', result: 'Handled by provider' };
  },
  executionMode: 'provider',
  executionProvider: 'openai',
});
```

### Complex Schema

```typescript theme={null}
export default defineTool({
  description: 'Create a support ticket',
  args: z.object({
    title: z.string().min(5).max(200).describe('Ticket title'),
    description: z.string().describe('Detailed description'),
    priority: z.enum(['low', 'medium', 'high', 'urgent']).default('medium'),
    category: z.string().describe('Ticket category'),
    tags: z.array(z.string()).optional().describe('Optional tags'),
    assignee: z.string().optional().describe('User ID to assign to'),
  }),
  execute: async (state, args) => {
    const ticket = await createTicket(args);
    return {
      status: 'success',
      result: `Created ticket #${ticket.id}: ${ticket.title}`,
    };
  },
});
```

## File Location

Tools are auto-discovered from `agents/tools/`:

```
agents/
└── tools/
    ├── search_knowledge_base.ts
    ├── lookup_customer.ts
    └── create_ticket.ts
```

**Requirements:**

* Use snake\_case for file names
* Tool name is derived from file name
* One tool per file
* Default export required

## Supported Zod Types

Tool argument schemas support the following Zod types:

**Primitives:**

* `z.string()`
* `z.number()`
* `z.boolean()`
* `z.null()`
* `z.literal(value)`

**Enums:**

* `z.enum(['a', 'b', 'c'])`

**Wrappers:**

* `z.optional(...)`
* `z.nullable(...)`
* `z.default(...)`

**Collections:**

* `z.array(...)`
* `z.object({ ... })`
* `z.record(z.string(), ...)`

**Unions:**

* `z.union([...])`

Nested schemas are supported up to 7 levels deep.

## Best Practices

<AccordionGroup>
  <Accordion title="Write Clear Descriptions">
    The description helps the LLM decide when to use the tool:

    **Good:**

    ```typescript theme={null}
    description: 'Search the knowledge base for product documentation, FAQs, and troubleshooting guides. Returns relevant articles ranked by relevance.'
    ```

    **Avoid:**

    ```typescript theme={null}
    description: 'Search stuff'
    ```
  </Accordion>

  <Accordion title="Describe Schema Fields">
    Use `.describe()` on every field:

    ```typescript theme={null}
    args: z.object({
      query: z.string().describe('Natural language search query'),
      limit: z.number().min(1).max(20).default(5).describe('Max results'),
      category: z.string().optional().describe('Filter by category name'),
    })
    ```
  </Accordion>

  <Accordion title="Return Structured Results">
    Return JSON strings for complex data:

    ```typescript theme={null}
    return {
      status: 'success',
      result: JSON.stringify({
        articles: results,
        totalCount: count,
        hasMore: offset + limit < count,
      }),
    };
    ```
  </Accordion>

  <Accordion title="Handle Errors Gracefully">
    Always catch and return meaningful errors:

    ```typescript theme={null}
    try {
      // ... operation
    } catch (error) {
      console.error('[Tool] Operation failed:', error);
      return {
        status: 'error',
        error: `Failed to complete operation: ${error.message}`,
      };
    }
    ```
  </Accordion>

  <Accordion title="Use rootState for Queueing">
    When queueing tools from sub-prompts, use `state.rootState`:

    ```typescript theme={null}
    // Correct
    queueTool(state.rootState, 'next_tool', args);

    // Incorrect (may not work in sub-prompts)
    queueTool(state, 'next_tool', args);
    ```
  </Accordion>
</AccordionGroup>
