feat: add tool framework foundation (types, registry, executor, shell tool, model types, SOUL.md)
- Task 0: SOUL.md + loadSystemPrompt() in daemon - Task 1: Tool type definitions (Tool, ToolCall, ToolResult, etc.) - Task 2: ToolRegistry with Anthropic/OpenAI serialization - Task 3: ToolExecutor with hooks, timeout, truncation - Task 4: shell.exec builtin tool - Task 8: Model types updated for tool use (ToolDefinition, ModelToolCall, etc.) - Task 15: Model index exports for tool types
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
import type { ToolResult } from './types.js';
|
||||
import type { ToolRegistry } from './registry.js';
|
||||
import type { HookEngine } from '../hooks/engine.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): Promise<ToolResult> {
|
||||
const tool = this.registry.get(toolName);
|
||||
if (!tool) {
|
||||
return { success: false, output: '', error: `Tool '${toolName}' not found` };
|
||||
}
|
||||
|
||||
// Check hooks
|
||||
const action = this.hooks.getAction(toolName);
|
||||
if (action === 'confirm') {
|
||||
const hookResult = await this.hooks.requestConfirmation(
|
||||
toolName,
|
||||
args as Record<string, unknown>,
|
||||
);
|
||||
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<ToolResult>((_, 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),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user