Constructor
const tool = new ExuluTool ( options : ExuluToolOptions );
Creates a new ExuluTool instance. See the configuration guide for all available options.
Methods
execute()
Executes the tool directly with provided inputs. Used for programmatic tool invocation outside of agent workflows.
async execute ({
agent ,
config ,
user ,
inputs ,
project ,
items
}: ExecuteOptions ): Promise <{
result ?: string ;
job ?: string ;
items ?: Item [];
}>
Agent ID that is executing the tool. Used for loading agent configuration and API keys.
ExuluApp configuration object
User object for access control and tracking
Input parameters for the tool, validated against the inputSchema
Project ID for context (used in multi-tenant scenarios)
Array of item IDs for context (used when tool operates on specific items)
The result returned by the tool execution
Job ID if the tool queued a background task
Array of items (typically used by context search tools)
const calculatorTool = new ExuluTool ({
id: "calculator" ,
name: "add" ,
description: "Adds two numbers" ,
type: "function" ,
inputSchema: z . object ({
a: z . number (),
b: z . number ()
}),
config: [],
execute : async ({ a , b }) => ({
result: JSON . stringify ({ sum: a + b })
})
});
// Direct execution
const result = await calculatorTool . execute ({
agent: "assistant" ,
config: exuluConfig ,
user: currentUser ,
inputs: {
a: 5 ,
b: 3
}
});
console . log ( result . result ); // {"sum":8}
The execute() method is primarily used for programmatic tool invocation. When tools are used by agents, the AI SDK calls the internal tool execution automatically.
This method loads the specified agent to access its provider API key and tool configuration, then converts the ExuluTool to an AI SDK tool before execution.
Properties
Unique identifier for the tool. Must start with a letter or underscore, contain only alphanumeric characters and underscores, 5-80 characters long.
console . log ( tool . id ); // "get_user_profile"
The ID should not change after creation as it’s used for database references.
name
Human-readable name used for tool invocation
console . log ( tool . name ); // "get_user_profile"
description
Description of what the tool does, shown to agents
console . log ( tool . description ); // "Retrieves a user's profile information"
category
Category for organizing tools (defaults to “default”)
console . log ( tool . category ); // "database"
type
type
'context' | 'function' | 'agent' | 'web_search'
The type of tool
console . log ( tool . type ); // "function"
Zod schema that defines and validates the tool’s input parameters
console . log ( tool . inputSchema ); // ZodObject { ... }
config
Array of configuration parameters for the tool
tool . config . forEach ( param => {
console . log ( ` ${ param . name } : ${ param . type } = ${ param . default } ` );
});
// Output:
// api_endpoint: string = https://api.example.com
// timeout_ms: number = 5000
// enable_cache: boolean = true
The AI SDK tool instance created from this ExuluTool
// The tool property is used internally by the AI SDK
const aiTool = tool . tool ;
console . log ( aiTool . description ); // Tool description
console . log ( aiTool . parameters ); // Tool input schema
This property is automatically created in the constructor by wrapping the execute function with the AI SDK’s tool() function.
Usage examples
import { ExuluApp , ExuluTool } from "@exulu/backend" ;
import { z } from "zod" ;
// Create tool
const weatherTool = new ExuluTool ({
id: "weather_lookup" ,
name: "get_weather" ,
description: "Gets current weather for a location" ,
type: "function" ,
category: "api" ,
inputSchema: z . object ({
location: z . string (). describe ( "City name" ),
units: z . enum ([ "metric" , "imperial" ]). optional ()
}),
config: [
{
name: "api_key" ,
description: "Weather API key" ,
type: "variable"
}
],
execute : async ({ location , units = "metric" }) => {
const weather = await fetchWeather ( location , units );
return {
result: JSON . stringify ( weather )
};
}
});
// Register with app
const app = new ExuluApp ();
await app . create ({
tools: {
weather: weatherTool
},
config: { /* ... */ }
});
// Now agents can use the tool automatically
// Execute tool programmatically
const result = await weatherTool . execute ({
agent: "assistant" ,
config: app . config ,
user: currentUser ,
inputs: {
location: "London" ,
units: "metric"
}
});
console . log ( result . result );
// {"temperature":15,"condition":"Partly cloudy","humidity":65}
const getUserTool = new ExuluTool ({
id: "get_user" ,
name: "get_user" ,
description: "Retrieves user information from database" ,
type: "function" ,
category: "database" ,
inputSchema: z . object ({
userId: z . string (),
includeHistory: z . boolean (). optional ()
}),
config: [
{
name: "database_url" ,
description: "Database connection URL" ,
type: "variable"
}
],
execute : async ({ userId , includeHistory = false }) => {
const user = await db . users . findOne ({ id: userId });
if ( ! user ) {
return {
result: JSON . stringify ({
error: "User not found" ,
userId
})
};
}
if ( includeHistory ) {
user . history = await db . userHistory . find ({ userId });
}
return {
result: JSON . stringify ( user )
};
}
});
// Use in agent workflow
const app = new ExuluApp ();
await app . create ({
tools: { getUser: getUserTool },
agents: { assistant: assistantAgent },
config: { /* ... */ }
});
const githubTool = new ExuluTool ({
id: "github_search" ,
name: "search_github" ,
description: "Searches GitHub repositories" ,
type: "function" ,
category: "api" ,
inputSchema: z . object ({
query: z . string (). describe ( "Search query" ),
sort: z . enum ([ "stars" , "forks" , "updated" ]). optional (),
limit: z . number (). int (). min ( 1 ). max ( 100 ). optional ()
}),
config: [
{
name: "github_token" ,
description: "GitHub API token" ,
type: "variable"
},
{
name: "api_endpoint" ,
description: "GitHub API base URL" ,
type: "string" ,
default: "https://api.github.com"
}
],
execute : async ({ query , sort = "stars" , limit = 10 }) => {
const url = new URL ( ` ${ config . api_endpoint } /search/repositories` );
url . searchParams . append ( "q" , query );
url . searchParams . append ( "sort" , sort );
url . searchParams . append ( "per_page" , limit . toString ());
const response = await fetch ( url , {
headers: {
Authorization: `Bearer ${ config . github_token } ` ,
Accept: "application/vnd.github.v3+json"
}
});
const data = await response . json ();
return {
result: JSON . stringify ({
total: data . total_count ,
repositories: data . items . map ( repo => ({
name: repo . full_name ,
description: repo . description ,
stars: repo . stargazers_count ,
url: repo . html_url
}))
})
};
}
});
const processBatchTool = new ExuluTool ({
id: "process_batch" ,
name: "process_batch" ,
description: "Processes multiple items and streams results" ,
type: "function" ,
category: "processing" ,
inputSchema: z . object ({
itemIds: z . array ( z . string ()). min ( 1 ). max ( 50 )
}),
config: [],
execute : async function* ({ itemIds }) {
for ( const itemId of itemIds ) {
try {
const result = await processItem ( itemId );
yield {
result: JSON . stringify ({
itemId ,
status: "success" ,
data: result
})
};
} catch ( error ) {
yield {
result: JSON . stringify ({
itemId ,
status: "error" ,
error: error . message
})
};
}
}
}
});
// When executed, results stream back as each item completes
const generator = await processBatchTool . execute ({
agent: "processor" ,
config: exuluConfig ,
inputs: {
itemIds: [ "item1" , "item2" , "item3" ]
}
});
// Consume the stream
for await ( const chunk of generator ) {
console . log ( chunk . result );
// {"itemId":"item1","status":"success","data":{...}}
// {"itemId":"item2","status":"success","data":{...}}
// {"itemId":"item3","status":"success","data":{...}}
}
const embedTool = new ExuluTool ({
id: "generate_embeddings" ,
name: "generate_embeddings" ,
description: "Generates embeddings for documents" ,
type: "function" ,
category: "processing" ,
inputSchema: z . object ({
contextId: z . string (),
itemIds: z . array ( z . string ()). optional ()
}),
config: [],
execute : async ({ contextId , itemIds }) => {
const context = app . context ( contextId );
if ( ! context ) {
throw new Error ( `Context not found: ${ contextId } ` );
}
if ( itemIds && itemIds . length > 0 ) {
const jobs = [];
for ( const itemId of itemIds ) {
const { job } = await context . embeddings . generate . one ({
item: { id: itemId },
trigger: "api" ,
config: exuluConfig
});
if ( job ) jobs . push ( job );
}
return {
result: JSON . stringify ({
status: "queued" ,
count: itemIds . length
}),
job: jobs . join ( "," )
};
} else {
const { jobs , items } = await context . embeddings . generate . all (
exuluConfig
);
return {
result: JSON . stringify ({
status: "queued" ,
count: items
}),
job: jobs . join ( "," )
};
}
}
});
const tool = new ExuluTool ({
id: "example_tool" ,
name: "example" ,
description: "Example tool" ,
type: "function" ,
category: "examples" ,
inputSchema: z . object ({
input: z . string ()
}),
config: [
{
name: "setting" ,
description: "Example setting" ,
type: "string" ,
default: "default_value"
}
],
execute : async ({ input }) => ({
result: input
})
});
// Access properties
console . log ( tool . id ); // "example_tool"
console . log ( tool . name ); // "example"
console . log ( tool . description ); // "Example tool"
console . log ( tool . type ); // "function"
console . log ( tool . category ); // "examples"
// Access config
tool . config . forEach ( param => {
console . log ( param . name ); // "setting"
console . log ( param . type ); // "string"
console . log ( param . default ); // "default_value"
});
// Access inputSchema
if ( tool . inputSchema ) {
const result = tool . inputSchema . safeParse ({ input: "test" });
console . log ( result . success ); // true
}
// Access AI SDK tool
console . log ( tool . tool . description ); // "Example tool"
const safeTool = new ExuluTool ({
id: "safe_operation" ,
name: "safe_operation" ,
description: "Performs operation with error handling" ,
type: "function" ,
inputSchema: z . object ({
data: z . string ()
}),
config: [],
execute : async ({ data }) => {
try {
// Validate input
if ( ! data || data . length === 0 ) {
return {
result: JSON . stringify ({
success: false ,
error: "Data is required"
})
};
}
// Perform operation
const result = await performRiskyOperation ( data );
// Return success
return {
result: JSON . stringify ({
success: true ,
data: result
})
};
} catch ( error ) {
// Handle errors gracefully
console . error ( "Tool execution error:" , error );
return {
result: JSON . stringify ({
success: false ,
error: error . message ,
code: error . code || "UNKNOWN_ERROR"
})
};
}
}
});
Type definitions
// Tool configuration parameter
type ConfigParameter = {
name : string ;
description : string ;
type : "boolean" | "string" | "number" | "variable" ;
default ?: string | boolean | number ;
};
// Execute function types
type ExecuteFunction =
| (( inputs : any ) => Promise < ExecuteResult >)
| (( inputs : any ) => AsyncGenerator < ExecuteResult >);
type ExecuteResult = {
result ?: string ;
job ?: string ;
items ?: Item [];
};
// Execute method options
type ExecuteOptions = {
agent : string ;
config : ExuluConfig ;
user ?: User ;
inputs : any ;
project ?: string ;
items ?: string [];
};
// Tool constructor options
type ExuluToolOptions = {
id : string ;
name : string ;
description : string ;
category ?: string ;
inputSchema ?: z . ZodType ;
type : "context" | "function" | "agent" | "web_search" ;
config : ConfigParameter [];
execute : ExecuteFunction ;
};
Internal implementation notes
ExuluTool wraps the AI SDK’s tool() function in the constructor: this . tool = tool ({
description: description ,
inputSchema: inputSchema || z . object ({}),
execute
});
This provides native integration with AI SDK streaming and tool calling.
When execute() is called:
Loads the specified agent to get provider API key
Converts the ExuluTool to an AI SDK tool
Generates a unique tool call ID
Executes the tool via AI SDK
Handles streaming results if using generator
Parses and returns the final result
This allows tools to be executed outside of agent workflows while maintaining consistency.
Tool names are sanitized during execution to ensure compatibility: const sanitizedName = sanitizeName ( this . name );
This handles special characters and ensures the name works with the AI SDK.
When config parameters use type: "variable":
The variable name is looked up in the variables table
If encrypted, the value is decrypted using NEXTAUTH_SECRET
The decrypted value is used in tool execution
This enables secure storage of API keys and secrets.
Best practices
Return JSON strings : Always stringify non-string results. The AI SDK and agents expect string results.// Good
return { result: JSON . stringify ({ data: value }) };
// Avoid
return { result: value }; // Will be coerced to string
Error handling : Always handle errors gracefully and return error information in the result rather than throwing (unless you want the framework to catch it).
ID stability : Never change a tool’s id after creation. It’s used for database references and changing it will break existing integrations.
Input descriptions : Always use .describe() on Zod schema fields. These descriptions are shown to agents and significantly improve tool selection accuracy.
Next steps