Framework-agnostic AI Agent SDK with tool calling, conversation management, and automatic summarization
npm install concevent-ai-agent-sdkA framework-agnostic AI Agent SDK for building intelligent conversational agents with tool calling, automatic conversation summarization, and comprehensive event handling. Designed and built specifically for OpenRouter, providing access to a wide range of AI models (Claude, GPT-4, Gemini, Llama, and more) through a single, unified API.
> Why OpenRouter? OpenRouter provides access to 200+ AI models from multiple providers (Anthropic, OpenAI, Google, Meta, etc.) through a single API endpoint and consistent interface. This SDK leverages OpenRouter's OpenAI-compatible API to give you flexibility in model selection while maintaining a consistent development experience.
- 🤖 AI Agent Framework - Create intelligent agents with tool/function calling capabilities
- 🔧 Tool Execution - Define and execute custom tools with typed parameters using Zod schemas
- 🧰 Built-in Tools - Ready-to-use tools for file operations, shell commands, and task management
- 🖼️ Vision/Image Support - Send images with messages for multimodal AI interactions
- 💬 Conversation Management - Automatic history tracking with built-in summarization
- 📊 Token Usage Tracking - Monitor token consumption across conversations
- 🔄 Auto-Summarization - Automatically summarize long conversations to stay within context limits
- 🎯 Event Callbacks - Comprehensive event system for real-time updates
- ⛔ Abort Support - Cancel ongoing requests with AbortController
- 🔄 Retry Logic - Built-in retry mechanism for resilient operations
- ⏱️ Timeout Control - Configurable timeouts for API requests and tool execution
- 📝 Reasoning Support - Access model reasoning/thinking outputs
- 🔌 Middleware System - Extensible plugin architecture for logging, sanitization, and more
- 📋 Structured Output - JSON schema validation with Zod for type-safe responses
- 🎭 Multi-Agent Orchestration - Route tasks to specialized sub-agents based on their capabilities
- 📊 Flow Visualization - Automatic Mermaid sequence diagrams showing orchestration flow
- 🔢 Embeddings - Generate text embeddings for semantic search and RAG applications
``bash`
npm install concevent-ai-agent-sdkor
yarn add concevent-ai-agent-sdkor
pnpm add concevent-ai-agent-sdk
`typescript
import { createAgent } from "concevent-ai-agent-sdk";
import type {
ToolDefinition,
ToolExecutorContext,
} from "concevent-ai-agent-sdk";
import { z } from "zod";
import { zodToJsonSchema } from "zod-to-json-schema";
// Define your tools
const tools: ToolDefinition[] = [
{
declaration: {
name: "getWeather",
description: "Get current weather for a city",
parametersJsonSchema: zodToJsonSchema(
z.object({
city: z.string().describe("City name"),
})
),
},
executor: async (args) => {
const city = args.city as string;
// Your weather API logic here
return { city, temperature: 22, condition: "sunny" };
},
},
];
// Create the agent
const agent = createAgent({
apiKey: process.env.OPENROUTER_API_KEY!,
model: "anthropic/claude-3.5-sonnet",
systemPrompts: ["You are a helpful weather assistant."],
tools,
});
// Chat with the agent
const result = await agent.chat("What's the weather in Tokyo?", {
appContext: { userId: "user-123", timezone: "Asia/Tokyo" },
});
console.log(result.message);
// "The weather in Tokyo is currently sunny with a temperature of 22°C."
`
- Installation
- Quick Start
- Core Concepts
- API Reference
- createAgent
- Agent Interface
- Tool Definitions
- Built-in Tools
- Callbacks & Events
- Types
- Streaming
- Advanced Usage
- Image/Vision Input
- Parallel Tool Execution
- Conversation Summarization
- Error Handling
- Retry Configuration
- Timeout Configuration
- Structured Output
- Abort Requests
- Middleware
- Multi-Agent Orchestration
- Orchestration Flow Diagrams
- Embeddings
---
This SDK is built specifically for OpenRouter and uses it as the default API endpoint. Key benefits:
- Model Flexibility: Access Claude, GPT-4, Gemini, Llama, Mistral, and 200+ other models through a single API
- Provider Fallbacks: OpenRouter automatically handles provider outages and rate limits
- Unified Billing: One API key, one billing relationship for all models
- Consistent API: OpenAI-compatible API means familiar patterns and easy migration
The SDK defaults to https://openrouter.ai/api/v1 as the base URL. Model names follow OpenRouter's format (e.g., anthropic/claude-3.5-sonnet, openai/gpt-4o, google/gemini-pro).
An Agent is the main interface for interacting with AI models. It manages conversation history, executes tools, handles token usage tracking, and provides automatic summarization when context limits are reached.
Tools (also known as functions) extend the agent's capabilities. Each tool has:
- A declaration describing its name, purpose, and parameter schema
- An executor function that performs the actual work
The agent automatically maintains conversation history, including user messages, assistant responses, and tool calls. This history can be retrieved, set, or cleared as needed.
---
Creates a new agent instance with the specified configuration.
`typescript
import { createAgent } from 'concevent-ai-agent-sdk';
const agent = createAgent(config: AgentConfig): Agent;
`
#### AgentConfig
| Property | Type | Required | Default | Description |
| ----------------- | ----------------------------- | -------- | ------------------- | -------------------------------------------------------- |
| apiKey | string | ✅ | - | API key for the AI provider |model
| | string | ✅ | - | Model identifier (e.g., 'anthropic/claude-3.5-sonnet') |systemPrompts
| | string[] | ✅ | - | Array of system prompt messages |tools
| | ToolDefinition[] | ✅ | - | Array of tool definitions |baseURL
| | string | ❌ | https://openrouter.ai/api/v1 | API base URL (defaults to OpenRouter) |temperature
| | number | ❌ | 0.1 | Sampling temperature (0-2) |reasoning
| | ReasoningConfig \| ReasoningEffort \| false | ❌ | { enabled: true } | Reasoning configuration (see below) |maxIterations
| | number | ❌ | 50 | Maximum tool execution iterations per chat |stream
| | boolean | ❌ | true | Enable streaming responses with delta callbacks |postVerification
| | boolean | ❌ | false | Enable post-response verification (see below) |maxVerificationAttempts
| | number | ❌ | 5 | Max consecutive verification failures before graceful fallback |summarization
| | SummarizationConfig | ❌ | { enabled: true } | Summarization settings |parallelToolExecution
| | ParallelExecutionConfig | ❌ | { maxConcurrency: 5 } | Parallel tool execution settings |errorMessages
| | ErrorMessages | ❌ | Default messages | Custom error messages |retry
| | RetryConfig | ❌ | { maxAttempts: 3 } | Retry configuration for API failures and validation retries |timeout
| | TimeoutConfig | ❌ | See TimeoutConfig | Timeout configuration for API requests and tool execution |resultStorage
| | ResultStorageConfig | ❌ | { enabled: true } | Large result storage for chunked reading (prevents context overflow) |responseFormat
| | ResponseFormatConfig | ❌ | undefined (text) | Structured output format (text, json_object, json_schema) |middleware
| | Middleware[] | ❌ | [] | Middleware array for intercepting agent behavior |
#### ReasoningConfig
Controls model reasoning behavior. By default, reasoning is enabled with the model's default settings.
`typescript
// Effort levels
type ReasoningEffort = 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh';
// Full configuration object
interface ReasoningConfig {
enabled?: boolean; // Enable/disable reasoning (default: true)
effort?: ReasoningEffort; // Reasoning effort level (let API choose if not specified)
maxTokens?: number; // Maximum tokens for reasoning
exclude?: boolean; // Hide reasoning from output (model still thinks)
}
// Usage examples:
createAgent({ / default / }); // Reasoning enabled with model defaults
createAgent({ reasoning: false }); // Explicitly disable reasoning
createAgent({ reasoning: 'high' }); // Shorthand for { effort: 'high' }
createAgent({ reasoning: { effort: 'medium' } }); // Specific effort level
createAgent({ reasoning: { effort: 'high', maxTokens: 2000 } }); // With token limit
createAgent({ reasoning: { exclude: true } }); // Hide reasoning from output
createAgent({ reasoning: { enabled: false } }); // Disable via config object
`
Model Compatibility:
- Reasoning is enabled by default - models that don't support it typically ignore the parameter safely
- For models that error on reasoning params, use reasoning: falseexclude
- OpenAI o-series models support full reasoning configuration
- Anthropic Claude models support reasoning with option
Migration from v3.x:
`typescript
// Before (v3.x)
createAgent({ reasoningEffort: 'high' });
// After (v4.x)
createAgent({ reasoning: 'high' }); // Shorthand for specific effort
createAgent({ reasoning: { effort: 'high' } }); // Explicit config
createAgent({ reasoning: false }); // Disable reasoning
createAgent({ / default / }); // Enable with model defaults (recommended)
`
#### Post-Response Verification
When postVerification: true, the agent performs an additional verification step after each text response (not tool calls) to ensure all user requirements have been addressed. This is useful for high-stakes applications where accuracy is critical.
`typescript
// Post-verification disabled (default) - immediate streaming, lower latency
createAgent({ postVerification: false }); // Or omit entirely
// Post-verification enabled - suppresses deltas until verified
createAgent({ postVerification: true });
// Custom verification attempt limit (default: 5)
createAgent({ postVerification: true, maxVerificationAttempts: 3 });
`
Behavior comparison:
| Setting | Text Deltas | Verification API Call | Latency | Use Case |
|---------|-------------|----------------------|---------|----------|
| postVerification: false (default) | Streamed immediately | Skipped | Lower | Real-time chat, general use |postVerification: true
| | Suppressed until verified | Runs | Higher | High-stakes, accuracy-critical |
When enabled:
- Text deltas are suppressed during streaming until verification passes
- An additional API call verifies the response addresses all requirements
- Callbacks onVerificationStart, onVerificationPassed, onVerificationFailed are firedmaxVerificationAttempts
- If verification fails, the agent continues iterating to address missing requirements
- After consecutive failures (without tool calls), the agent returns the last response as a graceful fallback to prevent infinite loops
- The failure counter resets whenever tool calls are executed (indicating progress)
Breaking change note: In versions prior to v3.4, verification was always enabled. Starting from v3.4, it is disabled by default for improved performance and simpler streaming behavior.
#### SummarizationConfig
`typescript`
interface SummarizationConfig {
enabled: boolean;
model?: string; // Model for summarization (defaults to main agent model)
prompt?: string; // Custom summarization prompt
contextLimitTokens?: number; // Default: 128,000 tokens
}
#### ParallelExecutionConfig
`typescript`
interface ParallelExecutionConfig {
maxConcurrency?: number; // Default: 5 - Max parallel tools to run simultaneously
}
#### RetryConfig
`typescript`
interface RetryConfig {
maxAttempts?: number; // Default: 3 - Total attempts including initial (also used for validation retries)
baseDelayMs?: number; // Default: 1000 - Initial delay before first retry
maxDelayMs?: number; // Default: 30000 - Maximum delay cap
backoffMultiplier?: number; // Default: 2 - Exponential backoff multiplier
}
#### TimeoutConfig
`typescript`
interface TimeoutConfig {
toolExecutionMs?: number; // Default: 60000 (60 seconds) - Per-tool execution timeout
apiRequestMs?: number; // Default: 120000 (2 minutes) - OpenAI API request timeout
}
#### ResultStorageConfig
When tool results exceed size thresholds, they're stored to files and the AI reads them in chunks using the auto-injected ReadResult tool. This prevents context window overflow from large outputs.
`typescript`
interface ResultStorageConfig {
enabled?: boolean; // Default: true
directory?: string; // Default: os.tmpdir()/ai-agent-results
sizeThresholdBytes?: number; // Default: 8192 (8KB)
lineThreshold?: number; // Default: 200 lines
maxAgeMs?: number; // Default: 3600000 (1 hour) - Auto-cleanup
autoCleanup?: boolean; // Default: true
}
#### ResponseFormatConfig
`typescript`
type ResponseFormatConfig =
| { type: 'text' } // Default free-form text
| { type: 'json_object' } // JSON without validation
| {
type: 'json_schema';
schema: ZodType
name?: string; // Schema name (default: 'response')
strict?: boolean; // Strict mode (default: true)
};
#### Example with Full Configuration
`typescript`
const agent = createAgent({
apiKey: process.env.OPENROUTER_API_KEY!,
model: "anthropic/claude-3.5-sonnet",
baseURL: "https://openrouter.ai/api/v1",
temperature: 0.7,
reasoning: { effort: "high" }, // Or just "high" as shorthand, or false to disable
maxIterations: 10,
systemPrompts: [
"You are a helpful assistant.",
"Always be concise and accurate.",
],
tools: myTools,
summarization: {
enabled: true,
contextLimitTokens: 50000,
prompt: "Summarize the key points of this conversation...",
},
errorMessages: {
maxIterations: "Too many steps required. Please simplify your request.",
genericError: "Something went wrong. Please try again.",
},
});
---
The agent instance returned by createAgent implements the following interface:
`typescript
interface Agent {
chat(
message: string,
context: ToolExecutorContext,
callbacks?: AgentCallbacks,
options?: ChatOptions // Optional: for images and other multimodal input
): Promise
abort(): void;
getHistory(): ChatMessage[];
setHistory(history: ChatMessage[]): void;
clearHistory(): void;
getTokenUsage(): UsageMetadata;
}
`
#### agent.chat()
Sends a message to the agent and receives a response. The agent may execute multiple tool calls before returning a final response.
`typescript`
const result = await agent.chat(
message: string,
context: ToolExecutorContext,
callbacks?: AgentCallbacks,
options?: ChatOptions
): Promise
Parameters:
| Parameter | Type | Description |
| ----------- | --------------------- | --------------------------------- |
| message | string | The user's message |context
| | ToolExecutorContext | Execution context passed to tools |callbacks
| | AgentCallbacks | Optional event callbacks |options
| | ChatOptions | Optional chat options (e.g., images) |
Returns: AgentResult
`typescript`
interface AgentResult
message: string; // Final response message
parsedResponse?: T; // Parsed/validated response (when using json_schema)
reasoning?: {
// Model's reasoning (if available)
text?: string;
details?: ReasoningDetail[];
tokenCount?: number;
};
error?: {
// Error information when internal error occurs
code: string;
message: string;
recoverable: boolean;
};
conversationHistory: ChatMessage[]; // Full conversation history
usageMetadata: UsageMetadata; // Token usage statistics
requestId?: string; // API request ID
iterations: number; // Number of iterations taken
summarized: boolean; // Whether summarization occurred
}
Example:
`typescript`
const result = await agent.chat(
"Calculate 25 * 4 and tell me the weather in Paris",
{ appContext: { userId: "user-123", timezone: "Europe/Paris" } },
{
onToolCallStart: (calls) => {
console.log(
"Executing tools:",
calls.map((c) => c.name)
);
},
onMessage: (message) => {
console.log("Response:", message);
},
}
);
#### agent.abort()
Aborts the current chat request.
`typescript`
agent.abort();
#### agent.getHistory()
Returns the current conversation history.
`typescript`
const history: ChatMessage[] = agent.getHistory();
#### agent.setHistory()
Sets the conversation history (useful for restoring sessions).
`typescript`
agent.setHistory(previousHistory);
#### agent.clearHistory()
Clears all conversation history.
`typescript`
agent.clearHistory();
#### agent.getTokenUsage()
Returns cumulative token usage statistics.
`typescriptTotal tokens used: ${usage.totalTokenCount}
const usage: UsageMetadata = agent.getTokenUsage();
console.log();`
---
Tools extend the agent's capabilities by allowing it to perform actions or retrieve information.
`typescript
interface ToolDefinition
declaration: FunctionDeclaration;
executor: ToolExecutor
parallel?: boolean; // Set to true for tools safe to run concurrently (default: false)
}
interface FunctionDeclaration {
name: string;
description: string;
parametersJsonSchema: JsonSchema7Type;
}
type ToolExecutor
args: Record
context: ToolExecutorContext
) => Promise
interface ToolExecutorContext
appContext: TAppContext; // Application-specific context (userId, timezone, etc.)
abortSignal?: AbortSignal;
}
`
#### Creating Tools with Zod
Using Zod for parameter validation is recommended:
`typescript
import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';
import type { ToolDefinition } from 'concevent-ai-agent-sdk';
// Define your application context type
interface MyAppContext {
userId: string;
timezone: string;
}
// Define parameter schema with Zod
const searchSchema = z.object({
query: z.string().describe('Search query'),
limit: z.number().optional().default(10).describe('Maximum results'),
});
// Create the tool with typed context
const searchTool: ToolDefinition
declaration: {
name: 'search',
description: 'Search for information in the database',
parametersJsonSchema: zodToJsonSchema(searchSchema),
},
executor: async (args, context) => {
const { query, limit } = args as z.infer
// Access application context information
console.log(User ${context.appContext.userId} searching for: ${query});
// Perform search...
return { results: [...], total: 42 };
},
};
`
#### Tool with Abort Support
`typescript
const longRunningTool: ToolDefinition = {
declaration: {
name: "processData",
description: "Process large dataset",
parametersJsonSchema: zodToJsonSchema(
z.object({
datasetId: z.string(),
})
),
},
executor: async (args, context) => {
// Check for abort signal
if (context.abortSignal?.aborted) {
throw new Error("Operation cancelled");
}
// Pass abort signal to async operations
const response = await fetch(/api/process/${args.datasetId}, {
signal: context.abortSignal,
});
return response.json();
},
};
`
---
The SDK provides a collection of general-purpose, OS-agnostic built-in tools that can be used directly with agents or orchestrators. These tools cover common operations like file system access, shell execution, and task management.
#### Quick Start
You must explicitly specify which tools you need using the tools property. Tool names are strongly typed with autocomplete support:
`typescript
import { createAgent, createBuiltInTools } from 'concevent-ai-agent-sdk';
// Explicitly specify which tools you need
const { tools, cleanup } = createBuiltInTools({
workingDirectory: '/path/to/project',
tools: ['Read', 'Write', 'Edit', 'Glob', 'Grep'],
});
const agent = createAgent({
apiKey: process.env.OPENROUTER_API_KEY!,
model: 'anthropic/claude-3.5-sonnet',
systemPrompts: ['You are a helpful coding assistant.'],
tools: [...tools],
});
// Don't forget to cleanup background processes when done
process.on('exit', () => cleanup());
`
#### Available Tools
| Tool | Description | Parallel-Safe |
|------|-------------|---------------|
| 'Read' | Read file contents with optional line range | ✅ |'Write'
| | Create or overwrite files | ❌ |'Edit'
| | Targeted string replacement in files | ❌ |'Glob'
| | Find files matching glob patterns | ✅ |'Grep'
| | Search for patterns in file contents | ✅ |'Bash'
| | Execute shell commands | ❌ |
#### Grep and Read Tool - Context Efficiency
Both Grep and Read tools include built-in safeguards to prevent context overflow:
Grep Tool Parameters:
| Parameter | Default | Description |
|-----------|---------|-------------|
| pattern | (required) | Regex pattern to search for |path
| | working dir | File or directory to search |include
| | - | Glob filter (e.g., "*.ts") |context_lines
| | 0 | Lines before/after each match |max_matches
| | 50 | Maximum matches to return (cap: 1000) |max_line_length
| | 500 | Truncate lines longer than this |max_total_chars
| | 20000 | Max output chars (~5K tokens) |offset
| | 0 | Skip N matches for pagination |
Read Tool Parameters:
| Parameter | Default | Description |
|-----------|---------|-------------|
| file_path | (required) | Path to the file to read |offset
| | 1 | Line number to start from (1-based) |limit
| | 200 | Max lines to read |max_line_length
| | 500 | Truncate lines longer than this |max_total_chars
| | 20000 | Max output chars (~5K tokens) |
Best Practice: Use Grep with context_lines to see surrounding content instead of reading full files. This is more efficient for most search queries. Only use Read when you need a large continuous section of a file.
Pagination: Both tools return has_more: true when results are truncated. Use offset to paginate through results.'BashOutput'
| | Get output from background processes | ✅ |'KillBash'
| | Terminate background processes | ❌ |'Task'
| | Launch sub-agents for complex tasks | ❌ |'TodoWrite'
| | Manage structured todo items | ❌ |
#### Tool Selection Examples
`typescript
import { createBuiltInTools } from 'concevent-ai-agent-sdk';
// Read-only tools for safe analysis
const { tools: readOnlyTools } = createBuiltInTools({
workingDirectory: '/project',
tools: ['Read', 'Glob', 'Grep'],
});
// File tools only (no shell access)
const { tools: fileTools } = createBuiltInTools({
workingDirectory: '/project',
tools: ['Read', 'Write', 'Edit', 'Glob', 'Grep'],
});
// Shell tools only
const { tools: shellTools } = createBuiltInTools({
workingDirectory: '/project',
tools: ['Bash', 'BashOutput', 'KillBash'],
});
`
#### Configuration
`typescript
interface BuiltInToolsConfig {
/* Required: Which tools to include (strongly typed with autocomplete). /
tools: ToolName[];
/* Working directory for file operations (defaults to process.cwd()) /
workingDirectory?: string;
/* Allowed file paths for sandboxing /
allowedPaths?: string[];
/* Blocked shell command patterns (defaults to dangerous patterns) /
blockedCommands?: RegExp[];
/* Default timeout for shell commands in ms (default: 30000) /
defaultTimeout?: number;
/* Agent factory for Task tool (required if using Task) /
agentFactory?: (config: TaskAgentConfig) => Promise<{ message: string }>;
/* Custom todo storage (defaults to in-memory) /
todoStorage?: TodoStorage;
}
#### Individual Tool Creation
Create specific tools when you need fine-grained control:
`typescript
import {
createReadTool,
createBashTool,
createBuiltInTool,
} from 'concevent-ai-agent-sdk';
// Create a specific tool using its creator function
const readTool = createReadTool({ workingDirectory: '/project' });
// Or use the generic factory with tool name (strongly typed)
const bashTool = createBuiltInTool('Bash', {
workingDirectory: '/project',
defaultTimeout: 60000,
});
`
#### Security Features
Path Sandboxing:
`typescript`
const { tools } = createBuiltInTools({
workingDirectory: '/project',
tools: ['Read', 'Write'],
allowedPaths: ['/project/src', '/project/tests'],
// File tools will reject paths outside these directories
});
Command Blocking:
`typescript`
const { tools } = createBuiltInTools({
workingDirectory: '/project',
tools: ['Bash'],
blockedCommands: [
/rm\s+(-rf?|--recursive)/i,
/sudo\s+/i,
/curl.\|\sbash/i,
],
// Bash tool will reject commands matching these patterns
});
#### Task Tool with Agent Factory
The Task tool requires an agent factory to launch sub-agents:
`typescript
import { createAgent, createBuiltInTools } from 'concevent-ai-agent-sdk';
const { tools } = createBuiltInTools({
workingDirectory: '/project',
tools: ['Task'],
agentFactory: async (config) => {
// Create a sub-agent for the task
const subAgent = createAgent({
apiKey: process.env.OPENROUTER_API_KEY!,
model: 'anthropic/claude-3.5-sonnet',
systemPrompts: [config.description],
tools: [], // Configure tools based on config.allowedTools
});
const result = await subAgent.chat(config.prompt, {
appContext: { userId: 'task-agent', timezone: 'UTC' },
});
return { message: result.message };
},
});
`
#### Custom Todo Storage
Implement custom storage for persisting todos:
`typescript
import type { TodoStorage, TodoItem } from 'concevent-ai-agent-sdk';
const customStorage: TodoStorage = {
async getAll(): Promise
// Load from database, file, etc.
return JSON.parse(await fs.readFile('todos.json', 'utf-8'));
},
async save(todos: TodoItem[], merge: boolean): Promise
if (merge) {
const existing = await this.getAll();
const merged = new Map(existing.map(t => [t.id, t]));
todos.forEach(t => merged.set(t.id, t));
const all = Array.from(merged.values());
await fs.writeFile('todos.json', JSON.stringify(all));
return all;
}
await fs.writeFile('todos.json', JSON.stringify(todos));
return todos;
},
async clear(): Promise
await fs.writeFile('todos.json', '[]');
},
};
const { tools } = createBuiltInTools({
workingDirectory: '/project',
todoStorage: customStorage,
});
`
---
The SDK provides a comprehensive callback system for real-time updates during agent execution.
#### AgentCallbacks
`typescript`
interface AgentCallbacks {
onThinking?: (thinking: string, details?: ReasoningDetail[]) => void;
onMessage?: (
message: string,
reasoning?: {
text?: string;
details?: ReasoningDetail[];
tokenCount?: number;
}
) => void;
onMessageDelta?: (delta: string) => void;
onReasoningDelta?: (detail: ReasoningDetail) => void;
onToolCallStart?: (calls: ToolCallStartData[]) => void;
onToolResult?: (result: ToolResultData) => void;
onUsageUpdate?: (usage: UsageMetadata) => void;
onSummarizationStart?: (originalMessageCount: number) => void;
onSummarizationEnd?: (summary: string, tokensEstimate: number) => void;
onIterationStart?: (iteration: number, maxIterations: number) => void;
onRetry?: (attempt: number, maxAttempts: number, error: Error, delayMs: number) => void;
onError?: (error: {
code: string;
message: string;
recoverable: boolean;
}) => void;
onComplete?: (result: AgentResult) => void;
onAborted?: () => void;
// Orchestrator-specific callbacks
onSubAgentStart?: (data: SubAgentStartData) => void;
onSubAgentComplete?: (data: SubAgentCompleteData) => void;
onSubAgentToolCallStart?: (data: SubAgentToolCallStartData) => void;
onSubAgentToolResult?: (data: SubAgentToolResultData) => void;
// Verification lifecycle callbacks
onVerificationStart?: () => void;
onVerificationPassed?: () => void;
onVerificationFailed?: (data: { reason: string }) => void;
}
> ⚠️ Note on Streaming and Verification
>
> When verification is enabled (the default), text message deltas (onMessageDelta) and reasoning deltas (onReasoningDelta) are suppressed until the response passes verification. This ensures users only see verified, accurate content. The onMessage callback is called once with the complete verified response.onVerificationStart
>
> Use , onVerificationPassed, and onVerificationFailed to show loading states in your UI during verification.
> ⚠️ Security Note for Browser/Client-Side Usage
>
> When using callbacks in browser environments, be careful not to expose sensitive information. Callbacks like onToolResult, onThinking, onSummarizationEnd, and onComplete may contain internal data, tool execution details, or conversation content that should not be logged to the browser console or sent to client-side analytics in production. The SDK does not restrict this by design, as server-side integrations may safely log such data. It is the consumer's responsibility to sanitize or filter callback data before exposing it in client-side contexts.
#### Callback Examples
`typescript
await agent.chat("Help me with my task", context, {
// Called when the model is "thinking" (for reasoning models)
onThinking: (thinking, details) => {
console.log("Model thinking:", thinking);
},
// Called when a final message is received
onMessage: (message, reasoning) => {
console.log("Assistant:", message);
if (reasoning?.text) {
console.log("Reasoning:", reasoning.text);
}
},
// Called for each chunk of streaming message content
onMessageDelta: (delta) => {
process.stdout.write(delta); // Real-time output
},
// Called for each chunk of streaming reasoning (for reasoning models)
onReasoningDelta: (detail) => {
if (detail.text) {
process.stdout.write(detail.text);
}
},
// Called before tool execution starts
onToolCallStart: (calls) => {
calls.forEach((call) => {
console.log(Calling ${call.name} with:, call.args);
});
},
// Called after each tool completes
onToolResult: (result) => {
if (result.error) {
console.error(Tool ${result.functionName} failed:, result.error);Tool ${result.functionName} returned:
} else {
console.log(, result.result);
}
},
// Called when token usage is updated
onUsageUpdate: (usage) => {
console.log(Tokens used: ${usage.totalTokenCount});
},
// Called when summarization begins
onSummarizationStart: (messageCount) => {
console.log(Summarizing ${messageCount} messages...);
},
// Called when summarization completes
onSummarizationEnd: (summary, tokens) => {
console.log(Summarized to ~${tokens} tokens);
},
// Called at the start of each iteration
onIterationStart: (iteration, max) => {
console.log(Iteration ${iteration}/${max});
},
// Called on errors
onError: (error) => {
console.error(Error [${error.code}]:, error.message);
if (!error.recoverable) {
// Handle fatal error
}
},
// Called when processing completes
onComplete: (result) => {
console.log(Completed in ${result.iterations} iterations);
},
// Called when request is aborted
onAborted: () => {
console.log("Request was cancelled");
},
});
`
#### Event Types
The SDK also exports event types and a createEvent helper for building event-driven systems:
`typescript
import { createEvent } from "concevent-ai-agent-sdk";
import type { AgentEventType, AgentEvent } from "concevent-ai-agent-sdk";
// Create typed events
const messageEvent = createEvent("message", {
message: "Hello!",
reasoning: undefined,
});
const errorEvent = createEvent("error", {
code: "TOOL_EXECUTION_FAILED",
message: "Tool failed to execute",
recoverable: true,
});
// Event types available:
// 'thinking' | 'message' | 'tool_call_start' | 'tool_result' |
// 'usage_update' | 'summarization_start' | 'summarization_end' |
// 'iteration_start' | 'error' | 'complete' | 'aborted'
`
---
#### ChatMessage
`typescript`
interface ChatMessage {
role: "user" | "assistant" | "system" | "tool-call" | "tool-call-results";
content: string;
contentParts?: ContentPart[]; // For multimodal messages (images)
timestamp: Date;
toolCalls?: ToolCall[];
toolCallId?: string;
reasoning?: string;
reasoning_details?: ReasoningDetail[];
reasoningTokenCount?: number;
}
#### Image/Multimodal Types
`typescript
/* Detail level for image processing /
type ImageDetail = "auto" | "low" | "high";
/* Image input for chat messages /
interface ImageInput {
url: string; // URL or base64 data URI
detail?: ImageDetail; // Processing detail level
}
/* Options for agent.chat() /
interface ChatOptions {
images?: ImageInput[];
}
/* Content parts for multimodal messages /
type ContentPart = TextContentPart | ImageContentPart;
interface TextContentPart {
type: "text";
text: string;
}
interface ImageContentPart {
type: "image_url";
image_url: {
url: string;
detail?: ImageDetail;
};
}
`
#### UsageMetadata
`typescript`
interface UsageMetadata {
promptTokenCount?: number;
candidatesTokenCount?: number;
totalTokenCount?: number;
cachedContentTokenCount?: number;
reasoningTokenCount?: number;
}
#### ReasoningDetail
`typescript`
interface ReasoningDetail {
id?: string;
type: string;
text?: string;
data?: string;
format: string;
index: number;
}
#### ToolCallStartData & ToolResultData
`typescript
interface ToolCallStartData {
id: string;
name: string;
args: Record
}
interface ToolResultData {
id: string;
functionName: string;
result: unknown;
error?: string;
}
`
---
The SDK supports streaming responses by default, providing real-time updates as the model generates content.
Streaming is enabled by default. You can disable it in the agent configuration:
`typescript`
const agent = createAgent({
apiKey: process.env.OPENROUTER_API_KEY!,
model: "anthropic/claude-3.5-sonnet",
systemPrompts: ["You are a helpful assistant."],
tools: [],
stream: false, // Disable streaming (default: true)
});
When streaming is enabled, you can use delta callbacks to receive real-time updates.
> ⚠️ Important: Verification and Streaming
>
> For final text responses, onMessageDelta and onReasoningDelta are suppressed until verification passes. This ensures users never see unverified content that may be incorrect. Use onVerificationStart/onVerificationPassed/onVerificationFailed callbacks to show loading states during verification.
>
> Tool call streaming is unaffected - tool executions still stream in real-time.
#### onMessageDelta
Called whenever a new chunk of the message content is received (for tool-call iterations only; suppressed for final verified responses):
`typescript`
await agent.chat("Tell me a story", context, {
onMessageDelta: (delta) => {
// Note: For final responses, this is suppressed until verification passes
// The verified content is delivered via onMessage instead
process.stdout.write(delta);
},
onMessage: (fullMessage) => {
// Called when the complete verified message is ready
console.log("\n\nFull message:", fullMessage);
},
onVerificationStart: () => {
console.log("Verifying response...");
},
onVerificationPassed: () => {
console.log("Verification passed!");
},
});
#### onReasoningDelta
Called whenever a new chunk of model reasoning is received (for models that support reasoning):
`typescript`
await agent.chat("Solve this problem step by step", context, {
onReasoningDelta: (detail) => {
if (detail.type === "reasoning.text" && detail.text) {
// Stream the reasoning/thinking output
process.stdout.write(detail.text);
}
},
onThinking: (fullThinking, details) => {
// Called when reasoning is complete
console.log("\n\nFull reasoning:", fullThinking);
},
});
`typescript
import { createAgent } from "concevent-ai-agent-sdk";
const agent = createAgent({
apiKey: process.env.OPENROUTER_API_KEY!,
model: "anthropic/claude-3.5-sonnet",
systemPrompts: ["You are a helpful assistant."],
tools: myTools,
stream: true, // Enabled by default
});
let messageBuffer = "";
let reasoningBuffer = "";
const result = await agent.chat(
"Explain quantum computing",
{ appContext: { userId: "user-123", timezone: "UTC" } },
{
// Real-time message chunks
onMessageDelta: (delta) => {
messageBuffer += delta;
updateUI(messageBuffer); // Update your UI with partial content
},
// Real-time reasoning chunks (for reasoning models)
onReasoningDelta: (detail) => {
if (detail.text) {
reasoningBuffer += detail.text;
updateReasoningUI(reasoningBuffer);
}
},
// Tool execution still works with streaming
onToolCallStart: (calls) => {
showToolIndicator(calls.map((c) => c.name));
},
onToolResult: (result) => {
hideToolIndicator(result.functionName);
},
// Final complete message
onMessage: (message, reasoning) => {
console.log("Complete message received");
},
}
);
`
The SDK exports event types for building event-driven streaming systems:
`typescript
import { createEvent } from "concevent-ai-agent-sdk";
import type {
MessageDeltaEventData,
ReasoningDeltaEventData,
} from "concevent-ai-agent-sdk";
// Create typed streaming events
const messageDeltaEvent = createEvent("message_delta", {
delta: "Hello, ",
});
const reasoningDeltaEvent = createEvent("reasoning_delta", {
detail: {
type: "reasoning.text",
text: "Let me think about this...",
format: "text",
index: 0,
},
});
`
| Feature | Streaming (stream: true) | Non-Streaming (stream: false) |onMessageDelta
| ------------------- | --------------------------------------- | ------------------------------- |
| Message delivery | Real-time chunks via | Complete message only |onReasoningDelta
| Reasoning output | Real-time via | Complete reasoning only |
| Perceived latency | Lower (immediate feedback) | Higher (wait for completion) |
| Tool calls | Fully supported | Fully supported |
| Token usage | Included in final chunk | Included in response |
| Default | ✅ Enabled | Must explicitly disable |
---
The SDK supports sending images with messages for multimodal AI interactions. This enables vision capabilities like image analysis, screenshot understanding, and visual Q&A with models that support it (e.g., GPT-4o, Claude 3, Gemini Pro Vision).
#### Basic Usage
Pass images in the options parameter of agent.chat():
`typescript`
const result = await agent.chat(
"What is in this image?",
{ appContext: { userId: "user-123", timezone: "UTC" } },
{}, // callbacks (empty object if not needed)
{
images: [{ url: "https://example.com/photo.jpg" }],
}
);
#### ChatOptions
`typescript
interface ChatOptions {
/* Images to include with the message /
images?: ImageInput[];
}
interface ImageInput {
/* URL or base64 data URI /
url: string;
/* Detail level for image processing (default: 'auto') /
detail?: "auto" | "low" | "high";
}
`
#### Image Sources
Images can be provided as URLs or base64 data URIs:
`typescript
// URL-based image
await agent.chat("Describe this", context, {}, {
images: [{ url: "https://example.com/image.png" }],
});
// Base64 data URI
await agent.chat("What color is this?", context, {}, {
images: [{
url: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJ...",
}],
});
`
#### Multiple Images
Send multiple images for comparison or multi-image analysis:
`typescript`
await agent.chat("Compare these two images", context, {}, {
images: [
{ url: "https://example.com/before.png" },
{ url: "https://example.com/after.png" },
],
});
#### Detail Levels
Control image processing quality with the detail option:
| Detail | Description | Use Case |
|--------|-------------|----------|
| auto | Model decides based on image size (default) | General use |low
| | Fast, lower cost, 512x512 processing | Simple images, icons |high
| | Detailed analysis, higher cost | Complex images, documents |
`typescript
// High detail for document analysis
await agent.chat("Extract all text from this document", context, {}, {
images: [{ url: documentUrl, detail: "high" }],
});
// Low detail for simple recognition
await agent.chat("What icon is this?", context, {}, {
images: [{ url: iconUrl, detail: "low" }],
});
`
#### Model Compatibility
Vision capabilities require a multimodal model. Common options:
| Provider | Models |
|----------|--------|
| OpenAI | openai/gpt-4o, openai/gpt-4-turbo |anthropic/claude-3.5-sonnet
| Anthropic | , anthropic/claude-3-opus |google/gemini-pro-vision
| Google | , google/gemini-1.5-pro |
`typescript`
const agent = createAgent({
apiKey: process.env.OPENROUTER_API_KEY!,
model: "openai/gpt-4o", // Vision-capable model
systemPrompts: ["You are a helpful visual assistant."],
tools: [],
});
#### Conversation History
Images are stored in conversation history with their content parts, allowing for context preservation across multiple turns:
`typescript
const result = await agent.chat("What is this?", context, {}, {
images: [{ url: imageUrl }],
});
// History includes the image
const userMessage = result.conversationHistory.find((m) => m.role === "user");
console.log(userMessage?.contentParts); // Contains text and image parts
`
Tools can be marked as safe for parallel execution using the parallel flag. When the model requests multiple tool calls, parallel-safe tools run concurrently while sequential tools run one at a time after the parallel batch completes.
`typescript
import { createAgent } from "concevent-ai-agent-sdk";
import type { ToolDefinition } from "concevent-ai-agent-sdk";
const tools: ToolDefinition[] = [
{
declaration: {
name: "readFile",
description: "Read contents of a file",
parametersJsonSchema: { / ... / },
},
executor: async (args) => { / read file / },
parallel: true, // ✅ Safe for concurrent execution
},
{
declaration: {
name: "fetchWeather",
description: "Get weather for a city",
parametersJsonSchema: { / ... / },
},
executor: async (args) => { / API call / },
parallel: true, // ✅ Independent API calls are safe
},
{
declaration: {
name: "writeFile",
description: "Write contents to a file",
parametersJsonSchema: { / ... / },
},
executor: async (args) => { / write file / },
// parallel defaults to false — runs sequentially
},
];
const agent = createAgent({
apiKey: process.env.API_KEY!,
model: "anthropic/claude-3.5-sonnet",
systemPrompts: ["You are a helpful assistant."],
tools,
parallelToolExecution: {
maxConcurrency: 3, // Optional: limit concurrent tools (default: 5)
},
});
`
Execution Order:
When the model requests [readFile(a), readFile(b), writeFile(c), readFile(d)]:readFile(a)
1. Parallel tools run concurrently: , readFile(b), readFile(d)writeFile(c)
2. Sequential tools run in order:
3. All results are collected and returned to the model
When conversations become too long, the agent automatically summarizes them to stay within context limits.
`typescript
const agent = createAgent({
apiKey: process.env.API_KEY!,
model: "anthropic/claude-3.5-sonnet",
systemPrompts: ["You are a helpful assistant."],
tools: [],
summarization: {
enabled: true,
model: "anthropic/claude-3-haiku", // Use a faster/cheaper model for summarization
contextLimitTokens: 50000, // Summarize when approaching 50k tokens
prompt:
Summarize this conversation, preserving:
1. Key user requests and questions
2. Important decisions made
3. Any data or numbers mentioned
4. Context needed to continue the conversation
,
},
});
// Listen for summarization events
await agent.chat("Continue our discussion...", context, {
onSummarizationStart: (messageCount) => {
console.log(Summarizing ${messageCount} messages to save context...);Conversation summarized. Estimated tokens saved: ${tokensEstimate}
},
onSummarizationEnd: (summary, tokensEstimate) => {
console.log(
`
);
},
});
The SDK provides typed errors and customizable error messages. When an internal error occurs, the SDK returns a result with an error field instead of throwing an exception.
`typescript
import { createAgent } from "concevent-ai-agent-sdk";
const agent = createAgent({
// ... config
errorMessages: {
apiKeyRequired: "Please provide an API key",
modelRequired: "Please specify a model",
emptyResponse: "No response received, please try again",
maxIterations: "Request too complex. Please simplify.",
toolExecutionFailed: "A tool failed to execute",
genericError: "Something went wrong",
},
});
// Check for errors in the result
const result = await agent.chat("Do something", context);
if (result.error) {
console.error("Agent error:", result.error.code, result.error.message);
// Handle error case
} else {
// Process successful result
console.log(result.message);
}
// Or handle errors via callbacks
await agent.chat("Do something", context, {
onError: (error) => {
switch (error.code) {
case "MAX_ITERATIONS":
console.log("Too many iterations, simplify the request");
break;
case "TOOL_EXECUTION_FAILED":
console.log("Tool failed:", error.message);
break;
case "REQUEST_ABORTED":
console.log("Request was cancelled");
break;
default:
console.log("Error:", error.message);
}
if (!error.recoverable) {
// Handle fatal error
}
},
});
`
#### Error Codes
| Code | Description | Recoverable |
| ----------------------- | --------------------------- | ----------- |
| API_KEY_REQUIRED | API key not provided | No |MODEL_REQUIRED
| | Model name not provided | No |EMPTY_RESPONSE
| | API returned empty response | Yes |REQUEST_ABORTED
| | Request was aborted | No |NO_RESPONSE
| | No response from API | No |NO_RESPONSE_MESSAGE
| | Response missing message | No |MAX_ITERATIONS
| | Max iterations reached | Yes |TOOL_EXECUTION_FAILED
| | Tool execution failed | Yes |TOOL_EXECUTION_TIMEOUT
| | Tool execution timed out | Yes |SUMMARIZATION_FAILED
| | Summarization failed | Yes |GENERIC_ERROR
| | Unknown error occurred | No |
The SDK includes automatic retry logic with exponential backoff and jitter for handling transient API failures (e.g., rate limits, network errors).
#### Configuration
`typescript`
const agent = createAgent({
apiKey: process.env.OPENROUTER_API_KEY!,
model: "anthropic/claude-3.5-sonnet",
systemPrompts: ["You are a helpful assistant."],
tools: [],
retry: {
maxAttempts: 5, // Total attempts including initial (default: 3)
baseDelayMs: 1000, // Initial delay before first retry (default: 1000)
maxDelayMs: 30000, // Maximum delay cap (default: 30000)
backoffMultiplier: 2, // Exponential multiplier (default: 2)
},
});
#### How It Works
1. Exponential Backoff: Delay doubles with each retry attempt
2. Jitter: Random ±25% variation prevents thundering herd problems
3. Retry-After Support: Honors server-provided Retry-After headers
4. Abort Integration: Respects abort signals during retry delays
Delay Calculation:
``
delay = min(baseDelayMs (backoffMultiplier ^ attempt), maxDelayMs) jitter
For example, with defaults (base=1000ms, multiplier=2, max=30000ms):
- Retry 1: ~1000ms (±250ms jitter)
- Retry 2: ~2000ms (±500ms jitter)
- Retry 3: ~4000ms (±1000ms jitter)
#### Retry Callback
Monitor retry attempts using the onRetry callback:
`typescriptRetry ${attempt}/${maxAttempts} after ${delayMs}ms
await agent.chat("Hello", context, {
onRetry: (attempt, maxAttempts, error, delayMs) => {
console.log();Error: ${error.message}
console.log();`
},
});
#### Retryable Errors
The following HTTP status codes trigger automatic retries:
- 408 - Request Timeout429
- - Too Many Requests (Rate Limited)500
- - Internal Server Error502
- - Bad Gateway503
- - Service Unavailable504
- - Gateway Timeout
The SDK provides configurable timeouts to prevent hanging on long-running operations. There are two timeout settings:
- Tool Execution Timeout: Limits how long individual tool executions can run
- API Request Timeout: Limits how long OpenAI API requests can take
#### Default Values
| Setting | Default | Description |
|---------|---------|-------------|
| toolExecutionMs | 60 seconds | Timeout for each tool execution |apiRequestMs
| | 2 minutes | Timeout for OpenAI API requests |
#### Configuration
`typescript
import { createAgent } from "concevent-ai-agent-sdk";
const agent = createAgent({
apiKey: process.env.OPENROUTER_API_KEY!,
model: "anthropic/claude-3.5-sonnet",
systemPrompts: ["You are a helpful assistant."],
tools: myTools,
timeout: {
toolExecutionMs: 60000, // 60 seconds per tool
apiRequestMs: 180000, // 3 minutes for API calls
},
});
`
#### Disabling Timeouts
Set timeout values to 0 or omit them to use defaults:
`typescript`
const agent = createAgent({
// ... config
timeout: {
toolExecutionMs: 0, // No tool timeout (not recommended)
apiRequestMs: 300000, // 5 minutes for API calls
},
});
#### Behavior
Tool Execution Timeout:
- When a tool execution exceeds the timeout, it returns an error result (does not throw)
- The LLM receives the timeout error and can decide how to proceed
- Other tools in the same batch are not affected
API Request Timeout:
- When an API request times out, it throws an APIConnectionTimeoutErrorRetry-After
- The request is automatically retried according to your retry configuration
- Respects the header if provided by the server
#### Error Handling
Tool timeout errors are returned as part of the tool result, allowing the LLM to recover:
`typescript
// The LLM will receive an error like:
// "Tool execution timed out after 60000ms"
// You can provide a custom error message:
const agent = createAgent({
// ... config
errorMessages: {
toolExecutionTimeout: "The operation took too long. Please try a simpler request.",
},
});
`
#### Using with AbortSignal
Timeouts work alongside abort signals. If both are set, whichever triggers first will cancel the operation:
`typescript
const abortController = new AbortController();
// Set a 10 second manual timeout
setTimeout(() => abortController.abort(), 10000);
await agent.chat("Process this data", {
userId: "user-123",
timezone: "UTC",
abortSignal: abortController.signal, // Manual abort at 10s
});
// Tool timeout at 60s (default) or API timeout at 2min (default)
// Whichever comes first will cancel the operation
`
The SDK supports structured JSON output with automatic Zod schema validation. This ensures type-safe responses from the AI model.
#### Response Format Types
| Type | Description | Validation |
|------|-------------|------------|
| text | Default free-form text response | None |json_object
| | Forces JSON response | JSON parsing only |json_schema
| | Forces JSON with schema validation | Zod schema validation |
#### Basic JSON Mode
Force the model to return valid JSON without schema validation:
`typescript
const agent = createAgent({
apiKey: process.env.OPENROUTER_API_KEY!,
model: "anthropic/claude-3.5-sonnet",
systemPrompts: ["Return responses as JSON objects."],
tools: [],
responseFormat: { type: "json_object" },
});
const result = await agent.chat("List 3 colors", context);
// result.message: '{"colors": ["red", "blue", "green"]}'
`
#### Schema-Validated JSON
Define a Zod schema for type-safe, validated responses:
`typescript
import { z } from "zod";
// Define the expected response structure
const WeatherSchema = z.object({
location: z.string(),
temperature: z.number(),
unit: z.enum(["celsius", "fahrenheit"]),
conditions: z.array(z.string()),
});
type WeatherResponse = z.infer
const agent = createAgent({
apiKey: process.env.OPENROUTER_API_KEY!,
model: "anthropic/claude-3.5-sonnet",
systemPrompts: ["You provide weather information."],
tools: [],
responseFormat: {
type: "json_schema",
schema: WeatherSchema,
name: "WeatherResponse", // Optional: schema name for API
strict: true, // Optional: strict mode (default: true)
},
});
const result = await agent.chat("Weather in Tokyo", context);
// Access typed, validated response
const weather = result.parsedResponse as WeatherResponse;
console.log(weather.temperature); // Typed as number
console.log(weather.conditions); // Typed as string[]
`
#### Validation Retries
When schema validation fails, the SDK can automatically retry with error feedback. This uses the same retry configuration as API failures:
`typescript`
const agent = createAgent({
// ... config
responseFormat: {
type: "json_schema",
schema: MySchema,
},
retry: { maxAttempts: 3 }, // 3 total attempts (2 validation retries)
});
When validation fails:
1. The SDK captures the Zod validation error
2. Sends the error details back to the model as feedback
3. Model attempts to generate a corrected response
4. Process repeats until validation passes or retries exhausted
#### Custom Error Messages
Customize validation error messages:
`typescript`
const agent = createAgent({
// ... config
errorMessages: {
jsonParseError: "Failed to parse JSON response",
responseSchemaValidationFailed: "Response didn't match expected format",
},
});
#### AgentResult with Parsed Response
When using json_schema format, AgentResult includes the validated data:
`typescript`
interface AgentResult
message: string; // Raw JSON string
parsedResponse?: T; // Parsed & validated object (only with json_schema)
// ... other fields
}
Cancel ongoing requests using the abort functionality.
`typescript
const agent = createAgent(config);
// Start a chat
const chatPromise = agent.chat("Process this large dataset", context, {
onAborted: () => {
console.log("Request cancelled by user");
},
});
// Cancel after 5 seconds
setTimeout(() => {
agent.abort();
}, 5000);
try {
const result = await chatPromise;
} catch (error) {
if (error.name === "AbortError") {
console.log("Chat was aborted");
}
}
`
#### With Custom AbortSignal
`typescript
const abortController = new AbortController();
// Pass abort signal in context
agent.chat("Hello", {
appContext: { userId: "user-123", timezone: "UTC" },
abortSignal: abortController.signal,
});
// Cancel from external source
abortController.abort();
`
When deploying in serverless environments (e.g., AWS Lambda, Vercel, Cloudflare Workers) or stateless API routes (e.g., Next.js API routes), the agent instance is created fresh for each request. To maintain conversation continuity, the client must store and forward the conversation history with each request.
#### Pattern
1. Client maintains conversationHistory statesetHistory()
2. Client sends the history with each chat request
3. Server creates a fresh agent, restores history via , processes the messageconversationHistory
4. Server returns the result including
5. Client updates its local history from the response
#### Server-Side Example (Next.js API Route)
`typescript
import { createAgent } from "concevent-ai-agent-sdk";
import type { ChatMessage } from "concevent-ai-agent-sdk";
export async function POST(request: Request) {
const { message, conversationHistory = [] } = await request.json();
const agent = createAgent({
apiKey: process.env.API_KEY!,
model: "anthropic/claude-3.5-sonnet",
systemPrompts: ["You are a helpful assistant."],
tools: myTools,
});
// Restore conversation history from the client
if (conversationHistory.length > 0) {
agent.setHistory(conversationHistory);
}
const result = await agent.chat(message, {
appContext: { userId: "user-123", timezone: "UTC" },
});
// Return result - client should use result.conversationHistory for next request
return Response.json({
message: result.message,
conversationHistory: result.conversationHistory,
});
}
`
#### Client-Side Example
`typescript
const [conversationHistory, setConversationHistory] = useState
[]
);
async function sendMessage(message: string) {
const response = await fetch("/api/chat", {
method: "POST",
body: JSON.stringify({ message, conversationHistory }),
});
const result = await response.json();
// Update local history for the next request
setConversationHistory(result.conversationHistory);
return result.message;
}
`
> Note: The SDK handles summarization automatically when context limits are approached. The summarized history is included in result.conversationHistory, so clients always receive the properly managed history state.
Middleware allows you to intercept and modify the agent's execution flow at key points. Use middleware for logging, analytics, rate limiting, input sanitization, and output filtering.
#### Middleware Interface
`typescript
import type { Middleware } from "concevent-ai-agent-sdk";
interface Middleware {
name: string;
beforeChat?: (ctx: BeforeChatContext) => Promise
afterChat?: (ctx: AfterChatContext) => Promise
beforeToolCall?: (ctx: BeforeToolCallContext) => Promise
afterToolCall?: (ctx: AfterToolCallContext) => Promise
onError?: (ctx: ErrorContext) => Promise
}
`
#### Middleware Hooks
| Hook | When Called | Can Modify |
|------|-------------|------------|
| beforeChat | Before chat processing begins | User message |afterChat
| | After chat completes successfully | AgentResult |beforeToolCall
| | Before each tool executes | ToolCall args |afterToolCall
| | After each tool executes | FunctionCallResult |onError
| | When an error occurs | Observation only |
#### Creating Middleware
`typescript
import { createAgent } from "concevent-ai-agent-sdk";
import type { Middleware } from "concevent-ai-agent-sdk";
// Logging middleware
const loggingMiddleware: Middleware = {
name: "logging",
beforeChat: async (ctx) => {
console.log([${new Date().toISOString()}] Chat started:, ctx.message);[${new Date().toISOString()}] Chat completed:
return ctx.message;
},
afterChat: async (ctx) => {
console.log(, ctx.result.message);[${new Date().toISOString()}] Tool call:
return ctx.result;
},
beforeToolCall: async (ctx) => {
console.log(, ctx.toolCall.name);[${new Date().toISOString()}] Error:
return ctx.toolCall;
},
onError: async (ctx) => {
console.error(, ctx.error.code, ctx.error.message);
},
};
// Input sanitization middleware
const sanitizationMiddleware: Middleware = {
name: "sanitization",
beforeChat: async (ctx) => {
// Remove potentially harmful content from user input
const sanitized = ctx.message.replace(/