Skip to main content
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.

Tool Interface

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>;
}

Executable Tools

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

  1. Input: Takes an array of tools with name, inputSchema, and handler
  2. Validation: For each tool, creates a wrapper around the handler
  3. Interception: When the handler is called, Veto intercepts with validateToolCall()
  4. Decision:
    • Allow: Original handler executes (with modified args if applicable)
    • Deny: Throws ToolCallDeniedError
  5. 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: '[email protected]',
  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

Build docs developers (and LLMs) love