ts/docs/advanced/modifiers.md
Composio SDK provides powerful middleware capabilities through modifiers that allow you to customize and extend the behavior of tools. This guide explains how to use modifiers to transform tool schemas, modify inputs before execution, and transform outputs after execution.
Important: Not all modifiers are supported by all provider types. Schema modifiers work with all providers, but execution modifiers (beforeExecute and afterExecute) are only supported by agentic providers. See the Provider-Specific Modifier Support section for details.
Modifiers are functions that intercept and modify the normal flow of tool operations. Composio supports three types of modifiers:
Schema modifiers allow you to transform a tool's schema before it's wrapped for the provider. This is useful for customizing tool descriptions, parameter definitions, or adding metadata.
// Modify tool schema when getting tools
const tools = await composio.tools.get(
'default',
{
toolkits: ['github'],
},
{
modifySchema: ({ toolSlug, toolkitSlug, schema }) => {
// Add a prefix to all tool descriptions
if (tool.description) {
tool.description = `[Enhanced] ${tool.description}`;
}
// Modify parameters for a specific tool
if (toolSlug === 'GITHUB_GET_REPO') {
if (tool.inputParameters?.properties?.owner) {
// Add more detailed description
tool.inputParameters.properties.owner.description =
'GitHub organization or user name (e.g., "composio")';
}
}
return tool;
},
}
);
Before execution modifiers allow you to modify the input parameters before a tool is executed. This is useful for parameter validation, transformation, or adding default values.
// Modify parameters before execution
const result = await composio.tools.execute(
'GITHUB_GET_REPO',
{
userId: 'default',
arguments: {
owner: 'composio',
repo: 'sdk',
},
},
{
beforeExecute: ({ toolSlug, toolkitSlug, params }) => {
// Convert owner names to lowercase
if (params.arguments.owner) {
params.arguments.owner = params.arguments.owner.toLowerCase();
}
// Add a default branch parameter
if (toolSlug === 'GITHUB_GET_REPO' && !params.arguments.branch) {
params.arguments.branch = 'main';
}
// Add tracing for debugging
console.log(`Executing ${toolSlug} from ${toolkitSlug} toolkit`);
return params;
},
}
);
After execution modifiers allow you to transform the output after a tool has executed. This is useful for data formatting, filtering, or enrichment.
// Modify results after execution
const result = await composio.tools.execute(
'GITHUB_GET_REPO',
{
userId: 'default',
arguments: {
owner: 'composio',
repo: 'sdk',
},
},
{
afterExecute: ({ toolSlug, toolkitSlug, result }) => {
// Only when execution was successful
if (result.successful) {
// Filter out sensitive data
if (result.data.token) {
delete result.data.token;
}
// Add a timestamp
result.data.fetchedAt = new Date().toISOString();
// Transform data formats
if (result.data.created_at) {
result.data.createdAt = new Date(result.data.created_at).toLocaleString();
delete result.data.created_at;
}
}
return result;
},
}
);
You can use multiple modifiers together, but remember that execution modifiers (beforeExecute and afterExecute) only work with agentic providers:
// With an agentic provider (e.g., Vercel, Langchain)
const agenticProvider = new VercelProvider();
const composio = new Composio({
apiKey: 'your-api-key',
provider: agenticProvider,
});
// All modifiers work with agentic providers
const tools = await composio.tools.get(
'default',
{
toolkits: ['github'],
},
{
// Schema modifier
modifySchema: ({ toolSlug, toolkitSlug, schema }) => {
// Enhance tool schema
return schema;
},
// Before execution modifier (only works with agentic providers)
beforeExecute: ({ toolSlug, toolkitSlug, params }) => {
// Modify execution parameters
return params;
},
// After execution modifier (only works with agentic providers)
afterExecute: ({ toolSlug, toolkitSlug, result }) => {
// Transform execution results
return result;
},
}
);
// With a non-agentic provider (e.g., OpenAI)
const nonAgenticProvider = new OpenAIProvider();
const composioNonAgentic = new Composio({
apiKey: 'your-api-key',
provider: nonAgenticProvider,
});
// Only schema modifiers work with non-agentic providers
const openaiTools = await composioNonAgentic.tools.get(
'default',
{
toolkits: ['github'],
},
{
// Schema modifier (works with all providers)
modifySchema: (toolSlug, toolkitSlug, tool) => {
// Enhance tool schema
return tool;
},
// These will be ignored by non-agentic providers
beforeExecute: () => {}, // No effect with non-agentic providers
afterExecute: () => {}, // No effect with non-agentic providers
}
);
For better code organization, you can create reusable modifier functions:
// Define reusable modifiers
const addTimestamps = ({ toolSlug, toolkitSlug, result }) => {
if (result.successful) {
result.data.executedAt = new Date().toISOString();
}
return result;
};
const logExecutions = ({ toolSlug, toolkitSlug, params }) => {
console.log(`Executing ${toolSlug} from ${toolkitSlug} at ${new Date().toISOString()}`);
return params;
};
const sanitizeInputs = ({ toolSlug, toolkitSlug, params }) => {
// Sanitize input parameters to prevent injection attacks
if (params.arguments) {
Object.keys(params.arguments).forEach(key => {
if (typeof params.arguments[key] === 'string') {
params.arguments[key] = sanitizeString(params.arguments[key]);
}
});
}
return params;
};
// Use the reusable modifiers
const result = await composio.tools.execute(
'GITHUB_GET_REPO',
{
userId: 'default',
arguments: {
owner: 'composio',
repo: 'sdk',
},
},
{
beforeExecute: sanitizeInputs,
afterExecute: addTimestamps,
}
);
Here's a side-by-side comparison of how to use modifiers with different provider types:
// Example from examples/modifiers/src/index.ts
// Non-agentic provider example (OpenAI)
const openai = new Composio({
apiKey: process.env.COMPOSIO_API_KEY,
provider: new OpenAIProvider(),
});
// Schema modifiers work with non-agentic providers
const nonAgenticTools = await openai.tools.get('default', 'HACKERNEWS_GET_USER', {
// Schema modifier works with all providers
modifySchema: (toolSlug, toolkitSlug, tool) => {
// Customize the input parameters
if (tool.inputParameters?.properties?.userId) {
tool.inputParameters.properties.userId.description = 'HackerNews username (e.g., "pg")';
}
return tool;
},
});
// Agentic provider example (Vercel)
const vercel = new Composio({
apiKey: process.env.COMPOSIO_API_KEY,
provider: new VercelProvider(),
});
// All modifiers work with agentic providers
const agenticTools = await vercel.tools.get('default', 'HACKERNEWS_GET_USER', {
// Schema modifier
modifySchema: ({ toolSlug, toolkitSlug, schema }) => {
if (tool.inputParameters?.properties?.userId) {
tool.inputParameters.properties.userId.description = 'HackerNews username (e.g., "pg")';
}
return schema;
},
// Execution modifiers (only work with agentic providers)
beforeExecute: ({ toolSlug, toolkitSlug, params }) => {
console.log(`Executing ${toolSlug} from ${toolkitSlug}`);
return params;
},
afterExecute: ({ toolSlug, toolkitSlug, result }) => {
if (result.successful) {
result.data.processedAt = new Date().toISOString();
}
return result;
},
});
For non-agentic providers, you can use the provider's helper methods to apply execution modifiers:
// Get the OpenAI provider instance
const openaiProvider = composio.provider as OpenAIProvider;
// When handling a tool call from OpenAI
const toolOutputs = await openaiProvider.handleToolCalls(
'default', // userId
completion, // OpenAI completion object
{ connectedAccountId: 'account_123' }, // Options
{
// Manually apply execution modifiers
beforeExecute: ({ toolSlug, toolkitSlug, params }) => {
console.log(`Executing ${toolSlug}`);
return params;
},
afterExecute: ({ toolSlug, toolkitSlug, result }) => {
result.data.processedAt = new Date().toISOString();
return result;
},
}
);
Note: This example uses execution modifiers and will only work with agentic providers.
// Simple in-memory cache
const cache = new Map();
const cacheModifier = ({ toolSlug, toolkitSlug, params }) => {
// Create a cache key based on tool slug and arguments
const cacheKey = `${toolSlug}-${JSON.stringify(params.arguments)}`;
// Add cache key to params for the after modifier to use
params.__cacheKey = cacheKey;
// Check if we have a cached result
if (cache.has(cacheKey)) {
const cachedResult = cache.get(cacheKey);
if (Date.now() - cachedResult.timestamp < 60000) {
// 1 minute cache
// Throw a special error to short-circuit execution
throw { __cached: true, result: cachedResult.result };
}
}
return params;
};
const cacheAfterModifier = ({ toolSlug, toolkitSlug, result }) => {
if (result.successful && result.__cacheKey) {
// Store in cache
cache.set(result.__cacheKey, {
result,
timestamp: Date.now(),
});
// Remove the cache key from the result
delete result.__cacheKey;
}
return result;
};
// Use with try/catch to handle the cache hit
try {
const result = await composio.tools.execute(
'GITHUB_GET_REPO',
{
userId: 'default',
arguments: {
owner: 'composio',
repo: 'sdk',
},
},
{
beforeExecute: cacheModifier,
afterExecute: cacheAfterModifier,
}
);
console.log('Fresh result:', result);
} catch (error) {
if (error.__cached) {
console.log('Cached result:', error.result);
} else {
throw error; // Re-throw if it's a real error
}
}
const authModifier = ({ toolSlug, toolkitSlug, params }) => {
// Add authentication parameters for specific toolkits
if (toolkitSlug === 'custom-api') {
if (!params.customAuthParams) {
params.customAuthParams = {};
}
if (!params.customAuthParams.parameters) {
params.customAuthParams.parameters = [];
}
// Add an API key to the headers
params.customAuthParams.parameters.push({
in: 'header',
name: 'X-API-Key',
value: process.env.CUSTOM_API_KEY,
});
}
return params;
};
// Apply the auth modifier to all tools from a specific toolkit
const tools = await composio.tools.get(
'default',
{
toolkits: ['custom-api'],
},
{
beforeExecute: authModifier,
}
);
// Create a pipeline of modifiers
const pipeModifiers = (...modifiers) => {
return ({ toolSlug, toolkitSlug, result }) => {
return modifiers.reduce((modifiedResult, modifier) => {
return modifier({ toolSlug, toolkitSlug, result: modifiedResult });
}, result);
};
};
// Individual transformation steps
const addMetadata = ({ toolSlug, toolkitSlug, result }) => {
if (result.successful) {
result.data.metadata = { source: toolkitSlug, tool: toolSlug };
}
return result;
};
const formatDates = ({ toolSlug, toolkitSlug, result }) => {
if (result.successful) {
// Convert all dates to a consistent format
Object.keys(result.data).forEach(key => {
if (key.includes('date') || key.includes('time') || key.endsWith('At')) {
if (result.data[key]) {
result.data[key] = new Date(result.data[key]).toLocaleString();
}
}
});
}
return result;
};
const removeNulls = ({ toolSlug, toolkitSlug, result }) => {
if (result.successful) {
// Remove null and undefined values
Object.keys(result.data).forEach(key => {
if (result.data[key] === null || result.data[key] === undefined) {
delete result.data[key];
}
});
}
return result;
};
// Apply the pipeline
const result = await composio.tools.execute(
'GITHUB_GET_REPO',
{
userId: 'default',
arguments: {
owner: 'composio',
repo: 'sdk',
},
},
{
afterExecute: pipeModifiers(addMetadata, formatDates, removeNulls),
}
);
Composio SDK supports two types of providers:
The key technical distinction between these provider types affects which modifiers they support:
Agentic Providers receive an ExecuteToolFn parameter in their wrapTool/wrapTools methods, allowing them to embed execution modifiers directly into the wrapped tools. This means they support all three types of modifiers: schema, beforeExecute, and afterExecute.
Non-Agentic Providers only format tools for external consumption and don't control execution. When tools are wrapped by non-agentic providers, the context is lost, making it impossible to automatically apply execution modifiers. These providers only support schema modifiers.
Here's how the provider implementations differ:
// Non-Agentic Provider (e.g., OpenAI)
class OpenAIProvider extends BaseNonAgenticProvider<OpenAiToolCollection, OpenAiTool> {
// No executeToolFn parameter
override wrapTool(tool: Tool): OpenAiTool {
// Simply formats the tool for OpenAI
return {
type: 'function',
function: {
name: tool.slug,
description: tool.description,
parameters: tool.inputParameters,
},
};
}
}
// Agentic Provider (e.g., Vercel)
class VercelProvider extends BaseAgenticProvider<VercelToolCollection, VercelTool> {
// Receives executeToolFn parameter
wrapTool(tool: Tool, executeTool: ExecuteToolFn): VercelTool {
return tool({
description: tool.description,
parameters: jsonSchema(tool.inputParameters ?? {}),
execute: async params => {
// Can apply modifiers through executeTool
return await executeTool(tool.slug, params);
},
});
}
}
For non-agentic providers like OpenAI, you can still apply execution modifiers manually using the provider's helper methods:
// Get OpenAI provider instance
const openaiProvider = composio.provider as OpenAIProvider;
// Execute a tool call with modifiers
const result = await openaiProvider.executeToolCall(
'default', // userId
toolCall, // OpenAI tool call object
{ connectedAccountId: 'account_123' }, // Options
{
// Manually apply execution modifiers
beforeExecute: ({ toolSlug, toolkitSlug, params }) => {
console.log(`Executing ${toolSlug}`);
return params;
},
afterExecute: ({ toolSlug, toolkitSlug, result }) => {
result.data.processedAt = new Date().toISOString();
return result;
},
}
);
Tool Router sessions support enhanced modifier types that include session context. This is useful for tracking and managing tool execution across different user sessions.
import { Composio } from '@composio/core';
import { SessionExecuteMetaModifiers } from '@composio/core';
const composio = new Composio();
const session = await composio.create('user_123', {
toolkits: ['gmail', 'slack'],
});
// Use session-specific modifiers
const tools = await session.tools({
modifySchema: ({ toolSlug, toolkitSlug, schema }) => {
// Customize tool schemas
console.log(`Modifying schema for ${toolSlug} from ${toolkitSlug}`);
return schema;
},
beforeExecute: ({ toolSlug, toolkitSlug, sessionId, params }) => {
// Access session ID for tracking
console.log(`[Session: ${sessionId}] Executing ${toolSlug} from ${toolkitSlug}`);
// Add session-specific metadata
params.sessionMetadata = {
sessionId,
timestamp: new Date().toISOString(),
};
return params;
},
afterExecute: ({ toolSlug, toolkitSlug, sessionId, result }) => {
// Transform results with session context
console.log(`[Session: ${sessionId}] Completed ${toolSlug}`);
if (result.successful) {
result.data.sessionId = sessionId;
result.data.executedAt = new Date().toISOString();
}
return result;
},
});
The session-specific modifiers provide several advantages:
sessionId allows you to track which session executed which toolsYou can also fetch tool router meta tools directly using the new method:
import { Composio } from '@composio/core';
const composio = new Composio();
// Get raw meta tools for a session
const metaTools = await composio.tools.getRawToolRouterMetaTools('session_123', {
modifySchema: ({ toolSlug, toolkitSlug, schema }) => {
// Customize meta tool schemas
if (toolSlug === 'composio_authorize_toolkit') {
schema.description = 'Custom description for authorization';
}
return schema;
}
});
// Use the meta tools with your AI framework
console.log('Available meta tools:', metaTools.map(t => t.name));
// Schema Modifier (supported by all providers)
type TransformToolSchemaModifier = (toolSlug: string, toolkitSlug: string, tool: Tool) => Tool;
// Before Execution Modifier (supported by agentic providers only)
type beforeExecuteModifier = (
toolSlug: string,
toolkitSlug: string,
params: ToolExecuteParams
) => ToolExecuteParams;
// After Execution Modifier (supported by agentic providers only)
type afterExecuteModifier = (
toolSlug: string,
toolkitSlug: string,
result: ToolExecuteResponse
) => ToolExecuteResponse;
// Modifiers Object
interface ExecuteToolModifiers {
beforeExecute?: beforeExecuteModifier;
afterExecute?: afterExecuteModifier;
}
// Provider Options
interface ProviderOptions<TProvider> {
modifySchema?: TransformToolSchemaModifier;
beforeExecute?: beforeExecuteModifier; // Only applied by agentic providers
afterExecute?: afterExecuteModifier; // Only applied by agentic providers
}
// Session-Specific Modifiers (v0.4.0+)
interface SessionExecuteMetaModifiers {
modifySchema?: (context: {
toolSlug: string;
toolkitSlug: string;
schema: any;
}) => any;
beforeExecute?: (context: {
toolSlug: string;
toolkitSlug: string;
sessionId: string;
params: any;
}) => any;
afterExecute?: (context: {
toolSlug: string;
toolkitSlug: string;
sessionId: string;
result: any;
}) => any;
}
// Session Meta Tool Options (v0.4.0+)
interface SessionMetaToolOptions {
modifySchema?: (context: {
toolSlug: string;
toolkitSlug: string;
schema: any;
}) => any;
}