ts/docs/providers/custom.md
Composio SDK allows you to create custom providers to integrate with different AI platforms and frameworks. This guide explains how to create custom providers for both agentic and non-agentic platforms.
Composio supports two types of providers:
Non-agentic providers extend the BaseNonAgenticProvider class:
import { BaseNonAgenticProvider, Tool } from '@composio/core';
// Define the tool type for your provider
type AnthropicTool = {
name: string;
description: string;
parameters: Record<string, unknown>;
};
// Define the collection type for your provider
type AnthropicToolCollection = AnthropicTool[];
// Create the provider class
export class AnthropicProvider extends BaseNonAgenticProvider<
AnthropicToolCollection,
AnthropicTool
> {
// Define the provider name (used for telemetry)
readonly name = 'anthropic';
// Implement the wrapTool method
override wrapTool(tool: Tool): AnthropicTool {
return {
name: tool.slug,
description: tool.description || '',
parameters: tool.inputParameters || {},
};
}
// Implement the wrapTools method
override wrapTools(tools: Tool[]): AnthropicToolCollection {
return tools.map(tool => this.wrapTool(tool));
}
// Add provider-specific methods
async handleToolUsage(userId: string, toolName: string, params: Record<string, unknown>) {
const result = await this.executeTool(toolName, {
userId,
arguments: params,
});
return JSON.stringify(result);
}
}
Agentic providers extend the BaseAgenticProvider class:
import { BaseAgenticProvider, Tool, ExecuteToolFn } from '@composio/core';
// Define the tool type for your provider
type LangchainTool = {
name: string;
description: string;
func: Function;
};
// Define the collection type for your provider
type LangchainToolCollection = {
tools: LangchainTool[];
executor: (tool: LangchainTool, input: Record<string, unknown>) => Promise<unknown>;
};
// Create the provider class
export class LangchainProvider extends BaseAgenticProvider<LangchainToolCollection, LangchainTool> {
// Define the provider name (used for telemetry)
readonly name = 'langchain';
// Implement the wrapTool method (note the executeToolFn parameter)
override wrapTool(tool: Tool, executeToolFn: ExecuteToolFn): LangchainTool {
return {
name: tool.slug,
description: tool.description || '',
func: async (input: Record<string, unknown>) => {
const result = await executeToolFn(tool.slug, input);
return result.data;
},
};
}
// Implement the wrapTools method (note the executeToolFn parameter)
override wrapTools(tools: Tool[], executeToolFn: ExecuteToolFn): LangchainToolCollection {
const langchainTools = tools.map(tool => this.wrapTool(tool, executeToolFn));
return {
tools: langchainTools,
executor: async (tool, input) => {
return await tool.func(input);
},
};
}
// Add provider-specific methods
async createAgent(tools: LangchainToolCollection, model: any) {
// Create a Langchain agent with the tools
// This is just an example, the actual implementation would depend on the Langchain API
return {
run: async (prompt: string) => {
// Run the agent with the prompt
// The agent would use the tools.executor to execute tools
},
};
}
}
Once you've created a custom provider, you can use it with the Composio SDK:
import { Composio } from '@composio/core';
import { AnthropicProvider } from './providers/anthropic-provider';
// Create an instance of your custom provider
const anthropicProvider = new AnthropicProvider();
// Initialize Composio with your custom provider
const composio = new Composio({
apiKey: 'your-composio-api-key',
provider: anthropicProvider,
});
// Get tools using your custom provider
const tools = await composio.tools.get('default', {
toolkits: ['github'],
});
// Now you can use these tools with Anthropic's API
// The tools will be formatted according to your provider's implementation
Both types of providers have these methods available:
Executes a tool using the global execute function. This is useful for implementing provider-specific helper methods that need to execute tools.
async executeTool(
toolSlug: string,
body: ToolExecuteParams,
modifiers?: ExecuteToolModifiers
): Promise<ToolExecuteResponse>;
Example:
class MyProvider extends BaseNonAgenticProvider<MyToolCollection, MyTool> {
// Other methods...
async executeWithContext(
userId: string,
toolSlug: string,
args: Record<string, unknown>,
context: string
) {
// Add context to the arguments
const argsWithContext = { ...args, context };
// Execute the tool
return this.executeTool(toolSlug, {
userId,
arguments: argsWithContext,
});
}
}
Non-agentic providers must implement these methods:
Transforms a Composio tool into the provider-specific format.
wrapTool(tool: Tool): TTool;
Transforms a list of Composio tools into the provider-specific collection format.
wrapTools(tools: Tool[]): TToolCollection;
Agentic providers must implement these methods:
Transforms a Composio tool into the provider-specific format, with access to an execute function.
wrapTool(tool: Tool, executeToolFn: ExecuteToolFn): TTool;
Transforms a list of Composio tools into the provider-specific collection format, with access to an execute function.
wrapTools(tools: Tool[], executeToolFn: ExecuteToolFn): TToolCollection;
import { BaseNonAgenticProvider, Tool } from '@composio/core';
type ClaudeTool = {
name: string;
description: string;
input_schema: {
type: string;
properties: Record<string, unknown>;
required?: string[];
};
};
type ClaudeToolCollection = ClaudeTool[];
export class ClaudeProvider extends BaseNonAgenticProvider<ClaudeToolCollection, ClaudeTool> {
readonly name = 'claude';
override wrapTool(tool: Tool): ClaudeTool {
// Convert the input parameters to Claude's format
const inputSchema = {
type: 'object',
properties: tool.inputParameters?.properties || {},
required: tool.inputParameters?.required || [],
};
return {
name: tool.slug,
description: tool.description || '',
input_schema: inputSchema,
};
}
override wrapTools(tools: Tool[]): ClaudeToolCollection {
return tools.map(tool => this.wrapTool(tool));
}
// Helper method for Claude tool execution
async executeClaudeTool(userId: string, toolName: string, input: Record<string, unknown>) {
const result = await this.executeTool(toolName, {
userId,
arguments: input,
});
return result.data;
}
}
import { BaseAgenticProvider, Tool, ExecuteToolFn } from '@composio/core';
// These types would match the actual LangChain API
type LangChainTool = {
name: string;
description: string;
schema: any;
call: (input: Record<string, unknown>) => Promise<any>;
};
type LangChainToolCollection = LangChainTool[];
export class LangChainProvider extends BaseAgenticProvider<LangChainToolCollection, LangChainTool> {
readonly name = 'langchain';
override wrapTool(tool: Tool, executeToolFn: ExecuteToolFn): LangChainTool {
return {
name: tool.slug,
description: tool.description || '',
schema: tool.inputParameters || {},
call: async (input: Record<string, unknown>) => {
const result = await executeToolFn(tool.slug, input);
if (!result.successful) {
throw new Error(result.error || 'Tool execution failed');
}
return result.data;
},
};
}
override wrapTools(tools: Tool[], executeToolFn: ExecuteToolFn): LangChainToolCollection {
return tools.map(tool => this.wrapTool(tool, executeToolFn));
}
// LangChain-specific helper methods
createAgent(tools: LangChainToolCollection, model: any) {
// Implement LangChain agent creation
// This is just a placeholder - the actual implementation would use the LangChain API
}
}
You can store provider-specific context or state in your provider class:
class ProviderWithContext extends BaseNonAgenticProvider<MyToolCollection, MyTool> {
readonly name = 'provider-with-context';
// Provider-specific context
private cache = new Map<string, any>();
private config: any;
constructor(config: any) {
super();
this.config = config;
}
override wrapTool(tool: Tool): MyTool {
// Use the config when wrapping tools
const customizedTool = {
name: tool.slug,
description: this.config.addDescriptionPrefix
? `[${this.config.descriptionPrefix}] ${tool.description}`
: tool.description,
// Other properties...
};
// Cache the tool for later use
this.cache.set(tool.slug, customizedTool);
return customizedTool;
}
override wrapTools(tools: Tool[]): MyToolCollection {
return tools.map(tool => this.wrapTool(tool));
}
// Custom method that uses the cache
getTool(slug: string): MyTool | undefined {
return this.cache.get(slug);
}
}