Tool Executors
Custom functions the AI agent can call on the server.
Tools are custom functions that the AI agent can call. They are defined on the server side and passed to createAgentHandler.
Two Styles
1. Simple Function
Simple style
toolExecutors: {
weather: async (params, context) => {
const res = await fetch(`https://api.weather.com?city=${params.city}`);
return { success: true, result: await res.json() };
},
}2. Full Config (Recommended)
Includes metadata for prompt generation — the SDK uses description, whenToUse, and schema to tell the LLM when and how to call the tool.
Full config style
toolExecutors: {
product_search: {
description: 'Search the product database',
whenToUse: 'When the user wants to find products, compare items, or asks about specific products',
schema: {
query: { type: 'string', required: true, description: 'Search keywords (1-3 words)' },
topK: { type: 'number', required: false, default: 10, description: 'Number of results' },
},
output: 'Array of products with title, price, imageUrl, description',
execute: async (params, context) => {
// context.userId — the authenticated user
// context.sessionId — current session
const res = await fetch('https://your-api.com/products/search', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ...params, userId: context?.userId }),
});
return await res.json();
},
},
}Type Definitions
Types
/** Simple function style */
type ToolExecutorFunction = (
params: any,
context?: SessionContext
) => Promise<ToolResult>;
/** Full config style */
interface ToolExecutorConfig {
execute: ToolExecutorFunction;
description?: string;
whenToUse?: string;
schema?: Record<string, {
type: string;
required?: boolean;
default?: any;
description?: string;
}>;
output?: string;
}
/** Context passed to every executor */
interface SessionContext {
sessionId: string;
userId: string;
currentUrl: string;
currentPage: PageState | null;
}
/** Tool return value */
interface ToolResult {
success: boolean;
result?: any;
error?: string;
}Tool Priority Resolution
The SDK resolves tools in this order:
Priority
1. Manual toolExecutors (passed to createAgentHandler) ← highest priority
2. CUSTOMER mode (configured in Lens OS dashboard)
3. PLATFORM mode (built-in SDK tools) ← lowest priority- Manual toolExecutors — Code-defined in your project (highest priority)
- CUSTOMER mode — Database-configured external endpoints via the Lens OS dashboard
- PLATFORM mode — Built-in SDK implementations (lowest priority)
Organizing Tool Executors
For projects with many tools, extract them into a separate file:
src/lib/tools.ts
import type { ToolExecutorConfig, SessionContext } from '@lens-os/sdk';
export function buildToolExecutors(): Record<string, ToolExecutorConfig> {
return {
product_search: {
description: 'Search products',
schema: { query: { type: 'string', required: true } },
execute: async (params) => {
const res = await fetch('/api/products/search', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(params),
});
return await res.json();
},
},
user_orders: {
description: 'Query user orders',
schema: {
action: { type: 'string', required: true, description: '"get" | "update"' },
orderId: { type: 'string', required: false },
},
execute: async (params, context?: SessionContext) => {
const res = await fetch('/api/orders', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ...params, userId: context?.userId }),
});
return await res.json();
},
},
};
}src/app/api/agent/chat/route.ts
import { createAgentHandler } from '@lens-os/sdk/server';
import { buildToolExecutors } from '@/lib/tools';
export const handler = createAgentHandler({
apiKey: process.env.LENS_API_KEY!,
openaiKey: process.env.OPENAI_API_KEY!,
toolExecutors: buildToolExecutors(),
});
export const { POST } = handler;Tip
The more metadata you provide (
description, whenToUse, schema, output), the better the LLM knows when and how to use the tool.