Client Tools
Client tools are the most powerful feature of Cognipeer SDK. They enable the AI to call JavaScript functions that you define, executing them on your side and using the results to continue the conversation.
What are Client Tools?
Client tools are JavaScript functions that:
- Are defined in your code
- Follow the OpenAI function calling format
- Can be automatically invoked by the AI
- Execute on your infrastructure (not Cognipeer's servers)
- Return results that the AI uses to continue the conversation
Basic Example
typescript
const response = await client.conversations.create({
peerId: 'your-peer-id',
messages: [
{ role: 'user', content: 'What is the weather in London?' }
],
clientTools: [{
type: 'function',
function: {
name: 'getWeather',
description: 'Get current weather for a city',
parameters: {
type: 'object',
properties: {
city: {
type: 'string',
description: 'City name'
}
},
required: ['city']
}
},
implementation: async ({ city }) => {
// Call your weather API
const weather = await fetch(`https://api.weather.com/${city}`);
const data = await weather.json();
return `Temperature: ${data.temp}°C, Conditions: ${data.conditions}`;
}
}]
});Tool Definition Structure
Each client tool must follow this structure:
typescript
{
type: 'function', // Always 'function'
function: {
name: string, // Unique tool name
description?: string, // What the tool does
parameters: { // JSON Schema
type: 'object',
properties: {
[key: string]: {
type: 'string' | 'number' | 'boolean' | 'array' | 'object',
description?: string,
enum?: any[]
}
},
required?: string[]
}
},
implementation: (args) => Promise<any> | any // Your function
}Important Notes
- Return Type: Always return a string or something JSON-serializable
- Async Support: Can be
asyncor synchronous - Error Handling: Throw errors to indicate failures
- OpenAI Compatible: Uses the same format as OpenAI's function calling
Multiple Tools
You can provide multiple tools in a single conversation:
typescript
const tools = [
{
type: 'function',
function: {
name: 'searchDatabase',
description: 'Search for records in the database',
parameters: {
type: 'object',
properties: {
query: { type: 'string' },
limit: { type: 'number' }
},
required: ['query']
}
},
implementation: async ({ query, limit = 10 }) => {
const results = await db.search(query, limit);
return JSON.stringify(results);
}
},
{
type: 'function',
function: {
name: 'sendEmail',
description: 'Send an email to a user',
parameters: {
type: 'object',
properties: {
to: { type: 'string' },
subject: { type: 'string' },
body: { type: 'string' }
},
required: ['to', 'subject', 'body']
}
},
implementation: async ({ to, subject, body }) => {
await emailService.send(to, subject, body);
return 'Email sent successfully';
}
}
];
const response = await client.conversations.create({
peerId: 'your-peer-id',
messages: [{ role: 'user', content: 'Search for John and email him' }],
clientTools: tools
});Automatic Execution
By default, the SDK automatically executes client tools when the AI requests them:
typescript
const client = new CognipeerClient({
token: 'your-token',
autoExecuteTools: true, // default
maxToolExecutions: 10 // max calls per request
});How it works:
- AI decides to call a tool
- SDK intercepts the call
- Executes your
implementationfunction - Sends result back to AI
- AI continues with the result
- Repeats if AI calls another tool
Manual Execution
For more control, disable automatic execution:
typescript
const client = new CognipeerClient({
token: 'your-token',
autoExecuteTools: false
});
const response = await client.conversations.create({
peerId: 'your-peer-id',
messages: [{ role: 'user', content: 'What time is it?' }],
clientTools: [timeTools]
});
if (response.status === 'client_tool_call' && response.pendingAction) {
// Manual execution
const { toolName, args, executionId } = response.pendingAction;
// Find and execute the tool
const tool = clientTools.find(t => t.function.name === toolName);
const result = await tool.implementation(args);
// Resume with result
const finalResponse = await client.conversations.resumeMessage({
conversationId: response.conversationId,
messageId: response.messageId,
toolResult: {
executionId,
success: true,
output: result
}
});
console.log(finalResponse.content);
}Error Handling in Tools
Handle errors gracefully in your tool implementations:
typescript
{
type: 'function',
function: {
name: 'riskyOperation',
description: 'Perform a risky operation',
parameters: {
type: 'object',
properties: {
id: { type: 'string' }
},
required: ['id']
}
},
implementation: async ({ id }) => {
try {
const result = await performOperation(id);
return JSON.stringify({ success: true, result });
} catch (error) {
// Return error as string so AI can handle it
return JSON.stringify({
success: false,
error: error.message
});
}
}
}Best Practices
1. Clear Descriptions
typescript
// ✅ Good
description: 'Get the current weather forecast for a specific city including temperature, humidity, and conditions'
// ❌ Bad
description: 'weather'2. Validate Inputs
typescript
implementation: async ({ email }) => {
if (!email || !email.includes('@')) {
return 'Invalid email address';
}
// proceed...
}3. Return Structured Data
typescript
implementation: async ({ userId }) => {
const user = await db.getUser(userId);
// Return as JSON string for complex data
return JSON.stringify({
name: user.name,
email: user.email,
status: user.status
});
}4. Use Enums for Limited Options
typescript
parameters: {
type: 'object',
properties: {
priority: {
type: 'string',
enum: ['low', 'medium', 'high'],
description: 'Task priority level'
}
},
required: ['priority']
}5. Timeout Protection
typescript
implementation: async ({ query }) => {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);
try {
const response = await fetch(url, { signal: controller.signal });
return await response.text();
} finally {
clearTimeout(timeout);
}
}Advanced: Chaining Tools
The AI can call multiple tools in sequence:
typescript
const tools = [
{
type: 'function',
function: {
name: 'getUserId',
description: 'Get user ID by email',
parameters: {
type: 'object',
properties: {
email: { type: 'string' }
},
required: ['email']
}
},
implementation: async ({ email }) => {
const user = await db.findUserByEmail(email);
return user.id;
}
},
{
type: 'function',
function: {
name: 'getUserOrders',
description: 'Get orders for a user ID',
parameters: {
type: 'object',
properties: {
userId: { type: 'string' }
},
required: ['userId']
}
},
implementation: async ({ userId }) => {
const orders = await db.getOrders(userId);
return JSON.stringify(orders);
}
}
];
// User asks: "What are john@example.com's recent orders?"
// AI will:
// 1. Call getUserId with email
// 2. Call getUserOrders with the returned ID
// 3. Format and present the resultsReal-World Examples
Database Integration
typescript
{
type: 'function',
function: {
name: 'queryDatabase',
description: 'Execute a read-only SQL query',
parameters: {
type: 'object',
properties: {
table: { type: 'string' },
conditions: { type: 'object' },
limit: { type: 'number' }
},
required: ['table']
}
},
implementation: async ({ table, conditions = {}, limit = 100 }) => {
const results = await db
.select('*')
.from(table)
.where(conditions)
.limit(limit);
return JSON.stringify(results);
}
}API Integration
typescript
{
type: 'function',
function: {
name: 'createJiraTicket',
description: 'Create a new Jira ticket',
parameters: {
type: 'object',
properties: {
title: { type: 'string' },
description: { type: 'string' },
priority: { type: 'string', enum: ['low', 'medium', 'high'] }
},
required: ['title', 'description']
}
},
implementation: async ({ title, description, priority = 'medium' }) => {
const ticket = await jira.createIssue({
fields: {
project: { key: 'PROJ' },
summary: title,
description,
priority: { name: priority }
}
});
return `Ticket created: ${ticket.key}`;
}
}File System Operations
typescript
{
type: 'function',
function: {
name: 'readFile',
description: 'Read contents of a file',
parameters: {
type: 'object',
properties: {
path: { type: 'string' }
},
required: ['path']
}
},
implementation: async ({ path }) => {
// Validate path for security
if (path.includes('..')) {
return 'Invalid path';
}
const fs = require('fs').promises;
const content = await fs.readFile(path, 'utf-8');
return content;
}
}Security Considerations
Security
Client tools execute with your application's permissions. Always:
- Validate all inputs
- Sanitize file paths and SQL queries
- Implement rate limiting
- Use authentication/authorization
- Never expose sensitive credentials in tool results
typescript
implementation: async ({ userId }) => {
// ✅ Validate user has permission
if (!await checkPermission(currentUser, userId)) {
return 'Access denied';
}
// ✅ Sanitize inputs
const sanitizedId = sanitize(userId);
// Proceed with operation
}Next Steps
- Conversations - Learn about conversation management
- Examples - See complete client tool examples
- API Reference - Full API documentation