Skip to content

Custom Agents

Create custom agents without using the Agent SDK.

Overview

While @cognipeer/agent-sdk provides a powerful agent framework, you can also integrate any AI backend using the custom agent interface.

Agent Handler Interface

typescript
interface AgentHandler {
  processMessage(params: ProcessMessageParams): Promise<ProcessMessageResult>;
}

interface ProcessMessageParams {
  conversationId: string;
  message: string;
  files?: FileAttachment[];
  state?: Record<string, unknown>;
  metadata?: Record<string, unknown>;
}

interface ProcessMessageResult {
  content: string;
  files?: FileAttachment[];
  state?: Record<string, unknown>;
  metadata?: Record<string, unknown>;
  usage?: {
    inputTokens?: number;
    outputTokens?: number;
    totalTokens?: number;
  };
}

Basic Example

typescript
agentServer.registerCustomAgent('echo', {
  processMessage: async ({ message }) => ({
    content: `Echo: ${message}`,
  }),
}, {
  name: 'Echo Bot',
  description: 'A simple echo bot',
  version: '1.0.0',
});

With OpenAI

typescript
import OpenAI from 'openai';

const openai = new OpenAI();

agentServer.registerCustomAgent('gpt-assistant', {
  processMessage: async ({ message, state }) => {
    // Get conversation history from state
    const history = (state?.history as any[]) || [];
    
    // Add user message
    history.push({ role: 'user', content: message });
    
    // Call OpenAI
    const completion = await openai.chat.completions.create({
      model: 'gpt-4',
      messages: [
        { role: 'system', content: 'You are a helpful assistant.' },
        ...history,
      ],
    });
    
    const response = completion.choices[0].message.content;
    
    // Add assistant message to history
    history.push({ role: 'assistant', content: response });
    
    return {
      content: response,
      state: { history },
      usage: {
        inputTokens: completion.usage?.prompt_tokens,
        outputTokens: completion.usage?.completion_tokens,
        totalTokens: completion.usage?.total_tokens,
      },
    };
  },
}, {
  name: 'GPT Assistant',
  description: 'OpenAI GPT-4 powered assistant',
});

With Anthropic

typescript
import Anthropic from '@anthropic-ai/sdk';

const anthropic = new Anthropic();

agentServer.registerCustomAgent('claude-assistant', {
  processMessage: async ({ message, state }) => {
    const history = (state?.history as any[]) || [];
    
    history.push({ role: 'user', content: message });
    
    const response = await anthropic.messages.create({
      model: 'claude-3-opus-20240229',
      max_tokens: 1024,
      messages: history,
    });
    
    const content = response.content[0].type === 'text' 
      ? response.content[0].text 
      : '';
    
    history.push({ role: 'assistant', content });
    
    return {
      content,
      state: { history },
      usage: {
        inputTokens: response.usage.input_tokens,
        outputTokens: response.usage.output_tokens,
      },
    };
  },
}, {
  name: 'Claude Assistant',
  description: 'Anthropic Claude powered assistant',
});

With File Processing

typescript
agentServer.registerCustomAgent('document-analyzer', {
  processMessage: async ({ message, files }) => {
    if (!files || files.length === 0) {
      return { content: 'Please upload a document to analyze.' };
    }
    
    const results = [];
    
    for (const file of files) {
      // Get file content from storage
      const content = await storage.getFileContent(file.id);
      
      if (file.mimeType === 'application/pdf') {
        const text = await extractPDFText(content);
        results.push(`PDF "${file.name}": ${text.slice(0, 500)}...`);
      } else if (file.mimeType.startsWith('image/')) {
        // Use vision API
        const description = await analyzeImage(content);
        results.push(`Image "${file.name}": ${description}`);
      }
    }
    
    return {
      content: `Analysis complete:\n\n${results.join('\n\n')}`,
    };
  },
});

With State Management

typescript
agentServer.registerCustomAgent('stateful-assistant', {
  processMessage: async ({ message, state, conversationId }) => {
    // Initialize state
    const currentState = {
      messageCount: (state?.messageCount as number) || 0,
      context: (state?.context as string[]) || [],
      lastTopic: state?.lastTopic as string,
    };
    
    // Update state
    currentState.messageCount++;
    currentState.context.push(message);
    
    // Keep only last 10 messages for context
    if (currentState.context.length > 10) {
      currentState.context = currentState.context.slice(-10);
    }
    
    // Process with context
    const response = await processWithContext(message, currentState);
    
    return {
      content: response.text,
      state: {
        ...currentState,
        lastTopic: response.topic,
      },
    };
  },
});

Streaming Custom Agents

For streaming responses, use an async generator:

typescript
agentServer.registerCustomAgent('streaming-agent', {
  processMessage: async function* ({ message }) {
    // Stream text chunks
    const chunks = ['Hello', ' there!', ' How', ' can', ' I', ' help?'];
    
    for (const chunk of chunks) {
      yield { type: 'text', text: chunk };
      await delay(100); // Simulate processing
    }
  },
});

With External APIs

typescript
agentServer.registerCustomAgent('weather-bot', {
  processMessage: async ({ message }) => {
    // Extract location from message
    const location = extractLocation(message);
    
    if (!location) {
      return { content: 'Please specify a location for the weather forecast.' };
    }
    
    // Call weather API
    const weather = await fetch(
      `https://api.weather.com/v1/current?location=${location}`
    ).then(r => r.json());
    
    return {
      content: `The weather in ${location} is ${weather.conditions} ` +
               `with a temperature of ${weather.temperature}°C.`,
      metadata: { weatherData: weather },
    };
  },
}, {
  name: 'Weather Bot',
  description: 'Get current weather for any location',
  metadata: {
    capabilities: ['weather', 'forecast'],
    apiProvider: 'weather.com',
  },
});

Error Handling

typescript
agentServer.registerCustomAgent('robust-agent', {
  processMessage: async ({ message }) => {
    try {
      const result = await processMessage(message);
      return { content: result };
    } catch (error) {
      console.error('Agent error:', error);
      
      // Return user-friendly error
      return {
        content: 'I encountered an error processing your request. Please try again.',
        metadata: {
          error: error.message,
          errorCode: error.code,
        },
      };
    }
  },
});

Agent Registration Options

typescript
agentServer.registerCustomAgent('my-agent', handler, {
  // Display name
  name: 'My Custom Agent',
  
  // Description for documentation
  description: 'A custom AI agent',
  
  // Version string
  version: '1.0.0',
  
  // Custom metadata
  metadata: {
    capabilities: ['chat', 'analysis'],
    model: 'custom',
    maxTokens: 4096,
  },
});

Testing Custom Agents

typescript
import { describe, it, expect } from 'vitest';

describe('MyCustomAgent', () => {
  it('should process messages correctly', async () => {
    const handler = {
      processMessage: async ({ message }) => ({
        content: `Processed: ${message}`,
      }),
    };
    
    const result = await handler.processMessage({
      conversationId: 'test-123',
      message: 'Hello',
    });
    
    expect(result.content).toBe('Processed: Hello');
  });
});

Next Steps

Studio · Pulse · Console · Agent SDK and more — the Cognipeer documentation hub