Documentation Index
Fetch the complete documentation index at: https://mintlify.com/plawio/veto/llms.txt
Use this file to discover all available pages before exploring further.
Veto is designed to work with any AI framework. This guide shows how to build custom integrations using Veto’s tool interface and provider adapters.
Veto uses a unified tool definition format that works across all providers:
interface ToolDefinition {
name: string;
description?: string;
inputSchema: {
type: 'object';
properties?: Record<string, JsonSchemaProperty>;
required?: readonly string[];
additionalProperties?: boolean;
};
metadata?: Record<string, unknown>;
}
To wrap tools with handlers, use the ExecutableTool interface:
interface ExecutableTool extends ToolDefinition {
handler: (args: Record<string, unknown>) => unknown | Promise<unknown>;
}
Basic Wrapping
The simplest way to add Veto to any framework is with veto.wrap():
import { Veto } from 'veto-sdk';
const myTools = [
{
name: 'search',
description: 'Search the web',
inputSchema: {
type: 'object',
properties: {
query: { type: 'string', description: 'Search query' },
},
required: ['query'],
},
handler: async (args: { query: string }) => {
// Implementation
return { results: [] };
},
},
];
const veto = await Veto.init();
const wrappedTools = veto.wrap(myTools);
// Pass wrappedTools to your framework
How wrap() Works
- Input: Takes an array of tools with
name, inputSchema, and handler
- Validation: For each tool, creates a wrapper around the
handler
- Interception: When the handler is called, Veto intercepts with
validateToolCall()
- Decision:
- Allow: Original handler executes (with modified args if applicable)
- Deny: Throws
ToolCallDeniedError
- Output: Returns wrapped tools with same interface as input
// Before wrap
const tool = {
name: 'send_email',
handler: async (args) => { /* ... */ },
};
// After wrap
const wrapped = veto.wrap([tool])[0];
// wrapped.handler is now: async (args) => {
// await veto.validateToolCall({ name: 'send_email', arguments: args });
// if (denied) throw ToolCallDeniedError;
// return original_handler(args);
// }
Provider Adapters
Veto includes adapters for converting between tool formats.
OpenAI Adapter
Convert between Veto and OpenAI tool formats:
import { toOpenAI, fromOpenAI, fromOpenAIToolCall } from 'veto-sdk/providers/adapters';
// Veto → OpenAI
const vetoTool: ToolDefinition = {
name: 'get_weather',
description: 'Get current weather',
inputSchema: {
type: 'object',
properties: {
city: { type: 'string' },
},
required: ['city'],
},
};
const openAITool = toOpenAI(vetoTool);
// {
// type: 'function',
// function: {
// name: 'get_weather',
// description: 'Get current weather',
// parameters: { type: 'object', properties: { city: { type: 'string' } }, required: ['city'] },
// },
// }
// OpenAI → Veto
const vetoTool2 = fromOpenAI(openAITool);
// Parse tool call from OpenAI response
const openAIToolCall = {
id: 'call_abc123',
type: 'function',
function: {
name: 'get_weather',
arguments: '{"city":"San Francisco"}',
},
};
const vetoToolCall = fromOpenAIToolCall(openAIToolCall);
// {
// id: 'call_abc123',
// name: 'get_weather',
// arguments: { city: 'San Francisco' },
// rawArguments: '{"city":"San Francisco"}',
// }
Anthropic Adapter
import { toAnthropic, fromAnthropic, fromAnthropicToolUse } from 'veto-sdk/providers/adapters';
// Veto → Anthropic
const anthropicTool = toAnthropic(vetoTool);
// {
// name: 'get_weather',
// description: 'Get current weather',
// input_schema: { type: 'object', properties: { city: { type: 'string' } }, required: ['city'] },
// }
// Anthropic → Veto
const vetoTool3 = fromAnthropic(anthropicTool);
// Parse tool use from Anthropic response
const anthropicToolUse = {
type: 'tool_use',
id: 'toolu_abc123',
name: 'get_weather',
input: { city: 'San Francisco' },
};
const vetoToolCall2 = fromAnthropicToolUse(anthropicToolUse);
Google (Gemini) Adapter
import {
toGoogleTool,
fromGoogleTool,
fromGoogleFunctionCall,
} from 'veto-sdk/providers/adapters';
// Veto → Google (wraps all tools in single object)
const googleTool = toGoogleTool([vetoTool1, vetoTool2]);
// {
// functionDeclarations: [
// { name: 'get_weather', description: '...', parameters: {...} },
// { name: 'search', description: '...', parameters: {...} },
// ],
// }
// Google → Veto
const vetoTools = fromGoogleTool(googleTool);
// Parse function call from Google response
const googleFunctionCall = {
name: 'get_weather',
args: { city: 'San Francisco' },
};
const vetoToolCall3 = fromGoogleFunctionCall(googleFunctionCall);
MCP Adapter
import { toMCP, fromMCP, fromMCPToolCall } from 'veto-sdk/providers/adapters';
// Veto → MCP
const mcpTool = toMCP(vetoTool);
// {
// name: 'get_weather',
// description: 'Get current weather',
// inputSchema: { type: 'object', properties: {...}, required: [...] },
// }
// MCP → Veto
const vetoTool4 = fromMCP(mcpTool);
// Parse MCP tool call
const mcpToolCall = {
name: 'get_weather',
arguments: { city: 'San Francisco' },
};
const vetoToolCall4 = fromMCPToolCall(mcpToolCall);
Generic Adapter
Use the generic adapter interface for consistent conversion:
import { getAdapter } from 'veto-sdk/providers/adapters';
const adapter = getAdapter('openai');
// or: getAdapter('anthropic'), getAdapter('mcp')
// Convert tools
const providerTools = adapter.toProviderTools(vetoTools);
const vetoTools2 = providerTools.map(adapter.fromProviderTool);
// Convert tool calls
const vetoToolCall = adapter.fromProviderToolCall(providerToolCall);
Custom Integration Example
Here’s how to build a custom integration for a hypothetical framework:
import { Veto, ToolCallDeniedError } from 'veto-sdk';
import { toOpenAI } from 'veto-sdk/providers/adapters';
import type { ToolDefinition } from 'veto-sdk/types/tool';
// Hypothetical framework types
import type { Agent, AgentTool } from 'my-framework';
interface VetoAgentOptions {
veto: Veto;
tools: ToolDefinition[];
onDeny?: (toolName: string, reason: string) => void;
}
export async function createVetoAgent(options: VetoAgentOptions): Promise<Agent> {
const { veto, tools, onDeny } = options;
// Wrap tools with Veto
const wrappedTools: AgentTool[] = tools.map((tool) => {
return {
// Convert schema to framework format
...toOpenAI(tool),
// Wrap handler with validation
execute: async (args: Record<string, unknown>) => {
try {
// Validate with Veto
const result = await veto.validateToolCall({
id: generateToolCallId(),
name: tool.name,
arguments: args,
});
if (!result.allowed) {
const reason = result.validationResult?.reason ?? 'Policy violation';
if (onDeny) onDeny(tool.name, reason);
throw new ToolCallDeniedError(tool.name, '', result.validationResult);
}
// Use modified arguments if Veto sanitized them
const finalArgs = result.finalArguments ?? args;
// Execute original handler
if ('handler' in tool && typeof tool.handler === 'function') {
return await tool.handler(finalArgs);
}
throw new Error(`Tool ${tool.name} has no handler`);
} catch (error) {
if (error instanceof ToolCallDeniedError) {
throw error; // Re-throw validation errors
}
throw new Error(`Tool execution failed: ${error}`);
}
},
};
});
// Create agent with wrapped tools
return new Agent({
tools: wrappedTools,
// ... other agent config
});
}
Usage
import { Veto } from 'veto-sdk';
import { createVetoAgent } from './veto-agent';
const veto = await Veto.init();
const agent = await createVetoAgent({
veto,
tools: [
{
name: 'search',
description: 'Search the web',
inputSchema: {
type: 'object',
properties: {
query: { type: 'string' },
},
required: ['query'],
},
handler: async (args) => ({ results: [] }),
},
],
onDeny: (toolName, reason) => {
console.error(`🛑 ${toolName}: ${reason}`);
},
});
const result = await agent.run('Search for AI safety research');
Manual Validation
For frameworks that don’t support tool wrapping, validate manually:
import { Veto, ToolCallDeniedError } from 'veto-sdk';
const veto = await Veto.init();
// In your agent loop
while (true) {
const message = await model.generate();
if (message.toolCalls) {
for (const toolCall of message.toolCalls) {
// Validate with Veto
const result = await veto.validateToolCall({
id: toolCall.id,
name: toolCall.name,
arguments: toolCall.arguments,
});
if (!result.allowed) {
const reason = result.validationResult?.reason ?? 'Policy violation';
console.error(`Tool call denied: ${reason}`);
continue; // Skip this tool call
}
// Execute tool
const toolResult = await executeTool(
toolCall.name,
result.finalArguments ?? toolCall.arguments
);
// Add to messages
messages.push({ role: 'tool', content: toolResult });
}
}
if (message.done) break;
}
Standalone Guard Checks
Use veto.guard() for validation outside the tool call flow:
// Validate arbitrary input
const result = await veto.guard('user_input', {
input: userMessage,
});
if (result.decision === 'deny') {
return { error: result.reason };
}
// Validate specific action
const result2 = await veto.guard('send_email', {
to: 'external@example.com',
body: emailBody,
});
if (result2.decision === 'deny') {
console.error(`Email blocked: ${result2.reason}`);
}
Output Validation
Validate agent output before returning to user:
const output = await agent.generate();
// Validate output
const validation = veto.validateOutput('agent_output', output);
if (validation.decision === 'block') {
console.error(`Output blocked: ${validation.reason}`);
return { error: 'Response blocked by policy' };
}
// Return sanitized output
return { text: validation.sanitized ?? output };
Python Custom Integration
For Python frameworks:
from veto import Veto, VetoOptions
from veto.types.tool import ToolCall
from veto.core.interceptor import ToolCallDeniedError
from typing import Any, Callable, Dict, List
class VetoAgent:
def __init__(self, veto: Veto, tools: List[Dict[str, Any]]):
self.veto = veto
self.tools = {tool["name"]: tool for tool in tools}
async def execute_tool(self, name: str, args: Dict[str, Any]) -> Any:
# Validate with Veto
result = await self.veto.validate_tool_call(
ToolCall(
id=generate_tool_call_id(),
name=name,
arguments=args,
)
)
if not result.allowed:
reason = result.validation_result.reason or "Policy violation"
raise ToolCallDeniedError(name, "", result.validation_result)
# Use modified arguments if sanitized
final_args = result.final_arguments or args
# Execute original handler
handler = self.tools[name]["handler"]
return await handler(final_args)
async def run(self, prompt: str) -> Any:
# Agent implementation with validated tool calls
pass
Type Safety
Veto preserves TypeScript types through wrapping:
import { Veto } from 'veto-sdk';
import { tool } from '@langchain/core/tools';
// Original tools with specific types
const myTools = [
tool(
async (args: { query: string }) => ({ results: [] }),
{ name: 'search' }
),
];
const veto = await Veto.init();
// TypeScript infers wrappedTools has the same type as myTools
const wrappedTools = veto.wrap(myTools);
// Type-safe: TypeScript knows wrappedTools[0].handler accepts { query: string }
await wrappedTools[0].handler({ query: 'AI safety' });
Next Steps
Tool Types
Full tool interface documentation
Provider Types
Provider-specific type definitions
Configure Rules
Define validation rules for your tools
Error Handling
Handle validation errors gracefully