Skip to main content

Constructor parameters

The ExuluTool constructor accepts a configuration object with the following parameters:
const tool = new ExuluTool({
  id: string,
  name: string,
  description: string,
  category?: string,
  inputSchema?: z.ZodType,
  type: "context" | "function" | "agent" | "web_search",
  config: ConfigParameter[],
  execute: ExecuteFunction
});

Required parameters

id

id
string
required
Unique identifier for the tool. Must start with a letter (a-z) or underscore (_). Subsequent characters can be letters, digits (0-9), or underscores. Must be 5-80 characters long.
id: "get_user_profile"
The ID is used for database references and should not change after creation. Changing the ID requires updating all references in your database.
The ID must follow PostgreSQL identifier rules since it’s used for storing tool references.

name

name
string
required
Human-readable name used for tool invocation. Agents use this name when calling the tool.
name: "get_user_profile"
The name is sanitized during execution to ensure compatibility with the AI SDK. Use snake_case or camelCase for consistency.

description

description
string
required
Clear description of what the tool does. This is shown to agents to help them understand when to use the tool.
description: "Retrieves a user's profile information including name, email, and preferences"
Write descriptions from the agent’s perspective. Explain what the tool does and when to use it. Good descriptions improve tool selection accuracy.

type

type
'context' | 'function' | 'agent' | 'web_search'
required
The type of tool determines its behavior and execution context
type: "function"
Custom function tools that execute your business logic, API calls, or data processing.
type: "function"
Use for: Database queries, calculations, API integrations, file processing

config

config
ConfigParameter[]
required
Array of configuration parameters that define runtime settings for the tool
config: [
  {
    name: "api_endpoint",
    description: "Base URL for the API",
    type: "string",
    default: "https://api.example.com"
  },
  {
    name: "enable_cache",
    description: "Enable response caching",
    type: "boolean",
    default: true
  },
  {
    name: "timeout_ms",
    description: "Request timeout in milliseconds",
    type: "number",
    default: 5000
  },
  {
    name: "api_key",
    description: "API authentication key",
    type: "variable" // References ExuluVariables
  }
]

Config parameter properties

config[].name
string
required
Parameter name used to reference this configuration value
config[].description
string
required
Description of what this parameter controls
config[].type
'boolean' | 'string' | 'number' | 'variable'
required
Data type of the parameter
config[].default
string | boolean | number
Default value if not specified (not applicable for type "variable")

execute

execute
ExecuteFunction
required
The function that implements the tool’s logic. Can be async function or async generator.
execute: async (inputs: any) => {
  // Your logic here
  return {
    result?: string,
    job?: string,
    items?: Item[]
  };
}
Standard async function for single result:
execute: async ({ userId, includeHistory }) => {
  const user = await db.users.findOne({ id: userId });

  if (includeHistory) {
    user.history = await db.userHistory.find({ userId });
  }

  return {
    result: JSON.stringify(user)
  };
}

Execute function return type

The execute function should return an object with one or more of these properties:
result
string
The main result to return to the agent. Typically JSON stringified data.
job
string
Optional job ID if the tool queued a background task for async processing
items
Item[]
Optional array of items (typically used by context search tools)
return {
  result: JSON.stringify({
    status: "success",
    data: processedData
  }),
  job: "job-uuid-123" // If queued
};

Optional parameters

category

category
string
Category for organizing tools. Defaults to "default" if not specified.
category: "database"
Common categories:
  • "database" - Database queries and operations
  • "api" - External API integrations
  • "file" - File operations and processing
  • "math" - Mathematical calculations
  • "search" - Search and retrieval operations
  • "workflow" - Multi-step workflows

inputSchema

inputSchema
z.ZodType
Zod schema that defines and validates the tool’s input parameters. If not provided, defaults to an empty object schema.
import { z } from "zod";

inputSchema: z.object({
  userId: z.string().describe("The user ID to look up"),
  includeHistory: z.boolean()
    .optional()
    .describe("Include user's history in the response"),
  maxResults: z.number()
    .int()
    .positive()
    .max(100)
    .optional()
    .describe("Maximum number of results to return")
})
Always use .describe() on schema fields. These descriptions are shown to agents and help them understand what each parameter does.

Zod schema best practices

// Good
z.object({
  userEmail: z.string(),
  includeDeletedRecords: z.boolean()
})

// Avoid
z.object({
  email: z.string(), // Ambiguous - could be any email
  deleted: z.boolean() // Unclear what this controls
})
z.object({
  query: z.string()
    .min(1)
    .describe("Search query to find relevant documents"),
  limit: z.number()
    .int()
    .positive()
    .max(50)
    .optional()
    .describe("Maximum number of results to return (default: 10)")
})
z.object({
  sortBy: z.enum(["date", "relevance", "popularity"])
    .describe("Sort order for results"),
  format: z.enum(["json", "csv", "xml"])
    .optional()
    .describe("Output format (default: json)")
})
z.object({
  email: z.string()
    .email()
    .describe("User's email address"),
  age: z.number()
    .int()
    .min(0)
    .max(120)
    .describe("User's age in years"),
  username: z.string()
    .min(3)
    .max(20)
    .regex(/^[a-zA-Z0-9_]+$/)
    .describe("Username (3-20 alphanumeric characters)")
})
z.object({
  // Required
  userId: z.string()
    .describe("User ID (required)"),

  // Optional with default behavior
  includeArchived: z.boolean()
    .optional()
    .describe("Include archived items (default: false)"),

  // Optional with no default
  filterTag: z.string()
    .optional()
    .describe("Optional tag to filter by")
})

Configuration examples

Simple function tool

import { ExuluTool } from "@exulu/backend";
import { z } from "zod";

const addTool = new ExuluTool({
  id: "add_numbers",
  name: "add",
  description: "Adds two numbers together",
  type: "function",
  category: "math",
  inputSchema: z.object({
    a: z.number().describe("First number"),
    b: z.number().describe("Second number")
  }),
  config: [],
  execute: async ({ a, b }) => {
    return {
      result: JSON.stringify({ sum: a + b })
    };
  }
});

Database query tool

const getUserTool = new ExuluTool({
  id: "get_user_profile",
  name: "get_user_profile",
  description: "Retrieves user profile information from the database",
  type: "function",
  category: "database",
  inputSchema: z.object({
    userId: z.string().describe("User ID to look up"),
    includePreferences: z.boolean()
      .optional()
      .describe("Include user preferences (default: false)")
  }),
  config: [
    {
      name: "database_url",
      description: "Database connection URL",
      type: "variable"
    },
    {
      name: "timeout_seconds",
      description: "Query timeout in seconds",
      type: "number",
      default: 10
    }
  ],
  execute: async ({ userId, includePreferences = false }) => {
    const user = await db.users.findOne({ id: userId });

    if (!user) {
      return {
        result: JSON.stringify({
          error: "User not found",
          userId
        })
      };
    }

    if (includePreferences) {
      user.preferences = await db.preferences.findOne({ userId });
    }

    return {
      result: JSON.stringify(user)
    };
  }
});

API integration tool

const weatherTool = new ExuluTool({
  id: "weather_lookup",
  name: "get_weather",
  description: "Gets current weather and forecast for a location",
  type: "function",
  category: "api",
  inputSchema: z.object({
    location: z.string().describe("City name or coordinates (lat,long)"),
    units: z.enum(["metric", "imperial"])
      .optional()
      .describe("Temperature units (default: metric)"),
    includeForecast: z.boolean()
      .optional()
      .describe("Include 7-day forecast (default: false)")
  }),
  config: [
    {
      name: "api_key",
      description: "Weather API authentication key",
      type: "variable"
    },
    {
      name: "api_endpoint",
      description: "Weather API base URL",
      type: "string",
      default: "https://api.weatherapi.com/v1"
    },
    {
      name: "cache_duration",
      description: "Cache responses for this many seconds",
      type: "number",
      default: 300
    }
  ],
  execute: async ({ location, units = "metric", includeForecast = false }) => {
    const url = `${config.api_endpoint}/current.json?key=${config.api_key}&q=${location}`;

    const response = await fetch(url);
    const data = await response.json();

    if (includeForecast) {
      const forecastUrl = `${config.api_endpoint}/forecast.json?key=${config.api_key}&q=${location}&days=7`;
      const forecastResponse = await fetch(forecastUrl);
      data.forecast = await forecastResponse.json();
    }

    return {
      result: JSON.stringify(data)
    };
  }
});

Streaming tool with generator

const batchProcessTool = new ExuluTool({
  id: "batch_process_files",
  name: "batch_process_files",
  description: "Processes multiple files and streams results as they complete",
  type: "function",
  category: "file",
  inputSchema: z.object({
    fileKeys: z.array(z.string())
      .min(1)
      .max(50)
      .describe("S3 keys of files to process (max 50)"),
    operation: z.enum(["extract_text", "generate_thumbnail", "convert_format"])
      .describe("Operation to perform on each file")
  }),
  config: [
    {
      name: "s3_bucket",
      description: "S3 bucket name",
      type: "variable"
    },
    {
      name: "parallel_limit",
      description: "Maximum number of parallel operations",
      type: "number",
      default: 5
    }
  ],
  execute: async function* ({ fileKeys, operation }) {
    for (const fileKey of fileKeys) {
      try {
        const result = await processFile(fileKey, operation);

        yield {
          result: JSON.stringify({
            fileKey,
            status: "success",
            operation,
            output: result
          })
        };
      } catch (error) {
        yield {
          result: JSON.stringify({
            fileKey,
            status: "error",
            operation,
            error: error.message
          })
        };
      }
    }
  }
});

Tool with background job queuing

const embedGeneratorTool = new ExuluTool({
  id: "generate_embeddings",
  name: "generate_embeddings",
  description: "Generates embeddings for documents (queued for background processing)",
  type: "function",
  category: "processing",
  inputSchema: z.object({
    contextId: z.string().describe("Context ID to generate embeddings for"),
    itemIds: z.array(z.string())
      .optional()
      .describe("Specific item IDs (if not provided, processes all items)")
  }),
  config: [
    {
      name: "queue_name",
      description: "BullMQ queue name",
      type: "string",
      default: "embeddings"
    },
    {
      name: "priority",
      description: "Job priority (1-10, higher is more important)",
      type: "number",
      default: 5
    }
  ],
  execute: async ({ contextId, itemIds }) => {
    const context = app.context(contextId);

    if (!context) {
      return {
        result: JSON.stringify({
          error: "Context not found",
          contextId
        })
      };
    }

    if (itemIds && itemIds.length > 0) {
      // Process specific items
      const jobs = [];
      for (const itemId of itemIds) {
        const { job } = await context.embeddings.generate.one({
          item: { id: itemId },
          trigger: "api",
          config: exuluConfig
        });
        jobs.push(job);
      }

      return {
        result: JSON.stringify({
          status: "queued",
          itemCount: itemIds.length
        }),
        job: jobs.join(",")
      };
    } else {
      // Process all items
      const { jobs, items } = await context.embeddings.generate.all(
        exuluConfig
      );

      return {
        result: JSON.stringify({
          status: "queued",
          itemCount: items
        }),
        job: jobs.join(",")
      };
    }
  }
});

Configuration types reference

Text configuration values
{
  name: "api_endpoint",
  description: "API base URL",
  type: "string",
  default: "https://api.example.com"
}
Numeric configuration values
{
  name: "timeout_ms",
  description: "Request timeout in milliseconds",
  type: "number",
  default: 5000
}
True/false flags
{
  name: "enable_cache",
  description: "Enable response caching",
  type: "boolean",
  default: true
}
References to ExuluVariables (for secrets and environment values)
{
  name: "api_key",
  description: "API authentication key",
  type: "variable"
  // No default - must be set in variables table
}
Variables are stored in the database and can be encrypted. Perfect for API keys, credentials, and environment-specific values.

Error handling patterns

execute: async (inputs) => {
  try {
    const data = await riskyOperation(inputs);
    return {
      result: JSON.stringify({ success: true, data })
    };
  } catch (error) {
    return {
      result: JSON.stringify({
        success: false,
        error: error.message
      })
    };
  }
}

Next steps