Overview
defineTool creates a function tool that agents can call during execution. It uses an object-based API for clear, typed configuration.
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
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 >>;
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
Human-readable description of what the tool does. This is sent to the LLM to help it decide when to use the tool. Good: description : 'Search the knowledge base for product documentation, FAQs, and troubleshooting guides. Returns relevant articles sorted by relevance.'
Avoid: description : 'Search stuff'
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. args : z . object ({
query: z . string (). describe ( 'Search query' ),
limit: z . number (). optional (). default ( 10 ). describe ( 'Max results' ),
})
execute
(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)
execute : async ( state , args ) => {
const result = await doSomething ( args . query );
return { status: 'success' , result: JSON . stringify ( result ) };
}
Zod object schema for thread environment variables the tool requires. tenvs : z . object ({
vectorStoreId: z . string (). describe ( 'OpenAI Vector Store ID' ),
})
Tenvs are validated at runtime. Missing required tenvs cause an error.
Where this tool is executed:
'local' (default): Execute locally by the execution engine
'provider': Executed by the LLM provider, results come in response
Which provider executes this tool (when executionMode='provider'). e.g., 'openai', 'anthropic'
Execute Parameters
ThreadState (first parameter)
The execution context providing access to thread data and environment:
interface ThreadState {
threadId : string ;
flowId : string ;
agentConfig : Agent ;
currentSide : 'a' | 'b' ;
turnCount : number ;
stopped : boolean ;
messageHistory : Message [];
storage : DurableObjectStorage ;
env : Env ;
context : Record < string , unknown >;
tenvs : Record < string , unknown >;
rootState : ThreadState ;
}
See ThreadState for full documentation.
Args (second parameter)
Validated arguments matching your Zod schema:
// 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:
status
'success' | 'error'
required
Whether the tool executed successfully.
The result to return to the LLM. For complex data, use JSON.stringify().
Error message (when status is 'error').
Stack trace for debugging (when status is 'error').
attachments
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. attachments : [{
name: 'generated.png' ,
mimeType: 'image/png' ,
data: base64Data ,
width: 1024 ,
height: 1024 ,
}]
Examples
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 ),
};
} ,
}) ;
export default defineTool ({
description: 'Get the current server time' ,
execute : async ( state ) => {
return {
status: 'success' ,
result: new Date (). toISOString (),
};
} ,
}) ;
Using ThreadState
export default defineTool ({
description: 'Look up customer information' ,
args: z . object ({
customerId: z . string (). describe ( 'Customer ID' ),
}) ,
execute : async ( state , args ) => {
// Access environment bindings
const db = state . env . DB ;
// Access custom context
const userId = state . context . userId ;
// Access storage
const cached = await state . storage . get ( `customer: ${ args . customerId } ` );
if ( cached ) {
return { status: 'success' , result: cached };
}
const customer = await db . prepare (
'SELECT * FROM customers WHERE id = ?'
). bind ( args . customerId ). first ();
return {
status: 'success' ,
result: JSON . stringify ( customer ),
};
} ,
}) ;
With Thread Environment Variables (tenvs)
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 = state . tenvs . vectorStoreId ;
const results = await searchVectorStore ( vectorStoreId , args . query , args . maxResults );
return { status: 'success' , result: JSON . stringify ( results ) };
} ,
tenvs: z . object ({
vectorStoreId: z . string (). describe ( 'OpenAI Vector Store ID' ),
userLocation: z . string (). optional (). describe ( 'User location for relevance' ),
}) ,
}) ;
Error Handling
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
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
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.
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
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:
Wrappers:
z.optional(...)
z.nullable(...)
z.default(...)
Collections:
z.array(...)
z.object({ ... })
z.record(z.string(), ...)
Unions:
Nested schemas are supported up to 7 levels deep.
Best Practices
The description helps the LLM decide when to use the tool: Good: description : 'Search the knowledge base for product documentation, FAQs, and troubleshooting guides. Returns relevant articles ranked by relevance.'
Avoid: description : 'Search stuff'
Use .describe() on every field: 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' ),
})
Return Structured Results
Return JSON strings for complex data: return {
status: 'success' ,
result: JSON . stringify ({
articles: results ,
totalCount: count ,
hasMore: offset + limit < count ,
}),
};
Always catch and return meaningful errors: try {
// ... operation
} catch ( error ) {
console . error ( '[Tool] Operation failed:' , error );
return {
status: 'error' ,
error: `Failed to complete operation: ${ error . message } ` ,
};
}
Use rootState for Queueing
When queueing tools from sub-prompts, use state.rootState: // Correct
queueTool ( state . rootState , 'next_tool' , args );
// Incorrect (may not work in sub-prompts)
queueTool ( state , 'next_tool' , args );