import type { ToolResult } from './types.js'; import type { ToolRegistry } from './registry.js'; import type { HookEngine } from '../hooks/engine.js'; import type { ToolPolicyContext } from './policy.js'; export interface ToolExecutorConfig { defaultTimeoutMs?: number; maxOutputBytes?: number; } export class ToolExecutor { private registry: ToolRegistry; private hooks: HookEngine; private defaultTimeoutMs: number; private maxOutputBytes: number; constructor(registry: ToolRegistry, hooks: HookEngine, config?: ToolExecutorConfig) { this.registry = registry; this.hooks = hooks; this.defaultTimeoutMs = config?.defaultTimeoutMs ?? 30_000; this.maxOutputBytes = config?.maxOutputBytes ?? 51_200; } async execute(toolName: string, args: unknown, context?: ToolPolicyContext): Promise { const tool = this.registry.getByApiName(toolName); if (!tool) { return { success: false, output: '', error: `Tool '${toolName}' not found` }; } // Policy check (defense in depth — tools should also be filtered at listing time) const policy = this.registry.getPolicy(); if (policy) { const allNames = this.registry.list().map(t => t.name); if (!policy.isAllowed(toolName, allNames, context)) { return { success: false, output: '', error: `Tool '${toolName}' is not allowed by tool policy`, }; } } // Check hooks const action = this.hooks.getAction(toolName); if (action === 'confirm') { const hookResult = await this.hooks.requestConfirmation( toolName, args as Record, ); if (!hookResult.approved) { return { success: false, output: '', error: `Tool '${toolName}' denied by user: ${hookResult.reason ?? 'no reason'}`, }; } } // Execute with timeout try { const result = await Promise.race([ tool.execute(args), new Promise((_, reject) => setTimeout(() => reject(new Error(`Tool '${toolName}' timed out after ${this.defaultTimeoutMs}ms`)), this.defaultTimeoutMs) ), ]); // Truncate output if too large if (result.output.length > this.maxOutputBytes) { result.output = result.output.slice(0, this.maxOutputBytes) + '\n[truncated]'; } return result; } catch (error) { return { success: false, output: '', error: error instanceof Error ? error.message : String(error), }; } } }