/** * Tool Builder Agent — генерирует новые инструменты через LLM и добавляет их в реестр */ import { invokeLLM } from "./_core/llm"; import { getDb } from "./db"; import { toolDefinitions } from "../drizzle/schema"; import { eq } from "drizzle-orm"; import { TOOL_REGISTRY, type ToolDefinition } from "./tools"; export interface GenerateToolRequest { name: string; description: string; category?: string; exampleInput?: string; exampleOutput?: string; dangerous?: boolean; } export interface GeneratedToolData { toolId: string; name: string; description: string; category: string; dangerous: boolean; parameters: Record; implementation: string; warnings?: string[]; } export interface GenerateToolResult { success: boolean; tool?: GeneratedToolData; error?: string; } /** * Генерирует новый инструмент через LLM на основе описания */ export async function generateTool(request: GenerateToolRequest): Promise { const systemPrompt = `You are an expert JavaScript developer specializing in creating tool functions for AI agents. Your task is to generate a complete, working JavaScript tool implementation. Rules: 1. The tool must be a single async function named "execute" that accepts a params object 2. Use only built-in Node.js modules (fs, path, crypto, http, https, url) or global fetch API 3. Always handle errors gracefully and return meaningful error messages 4. The function must return a JSON-serializable result object 5. Add input validation for all required parameters 6. Keep the implementation concise but complete Return ONLY valid JSON with this exact structure: { "toolId": "snake_case_id", "name": "Human Readable Name", "description": "What this tool does", "category": "web|data|file|system|ai|custom", "dangerous": false, "parameters": { "paramName": { "type": "string|number|boolean|array|object", "description": "What this parameter does", "required": true } }, "implementation": "async function execute(params) { /* your code here */ return { result: 'value' }; }" }`; const userPrompt = `Create a tool with the following specification: Name: ${request.name} Description: ${request.description} Category: ${request.category || "custom"} ${request.exampleInput ? `Example Input: ${request.exampleInput}` : ""} ${request.exampleOutput ? `Example Output: ${request.exampleOutput}` : ""} ${request.dangerous ? "Note: This tool may perform dangerous operations, add appropriate safety checks." : ""} Generate a complete, production-ready implementation.`; try { const response = await invokeLLM({ messages: [ { role: "system", content: systemPrompt }, { role: "user", content: userPrompt }, ], response_format: { type: "json_schema", json_schema: { name: "tool_definition", strict: true, schema: { type: "object", properties: { toolId: { type: "string" }, name: { type: "string" }, description: { type: "string" }, category: { type: "string" }, dangerous: { type: "boolean" }, parameters: { type: "object", additionalProperties: true }, implementation: { type: "string" }, }, required: ["toolId", "name", "description", "category", "dangerous", "parameters", "implementation"], additionalProperties: false, }, }, }, }); const content = response.choices[0].message.content; const toolData = typeof content === "string" ? JSON.parse(content) : content; // Validate the implementation is safe const warnings: string[] = []; const dangerousPatterns = ["child_process", "exec(", "spawn(", "eval(", "new Function("]; for (const pattern of dangerousPatterns) { if (toolData.implementation.includes(pattern) && !request.dangerous) { warnings.push(`Warning: Implementation contains potentially dangerous pattern: ${pattern}`); } } return { success: true, tool: { ...toolData, warnings }, }; } catch (error: any) { return { success: false, error: `Failed to generate tool: ${error.message}`, }; } } /** * Сохраняет инструмент в БД и регистрирует в реестре */ export async function installTool( toolData: GeneratedToolData, createdBy?: number ): Promise<{ success: boolean; toolId?: string; error?: string }> { const db = await getDb(); if (!db) return { success: false, error: "Database not available" }; try { // Save to DB await db.insert(toolDefinitions).values({ toolId: toolData.toolId, name: toolData.name, description: toolData.description, category: toolData.category, dangerous: toolData.dangerous, parameters: toolData.parameters, implementation: toolData.implementation, isActive: true, createdBy: createdBy || null, }).onDuplicateKeyUpdate({ set: { name: toolData.name, description: toolData.description, implementation: toolData.implementation, parameters: toolData.parameters, updatedAt: new Date(), }, }); // Register in runtime TOOL_REGISTRY registerToolInRegistry(toolData); return { success: true, toolId: toolData.toolId }; } catch (error: any) { return { success: false, error: error.message }; } } /** * Динамически регистрирует инструмент в TOOL_REGISTRY (массив) */ function registerToolInRegistry(toolData: GeneratedToolData): void { try { // Create the execute function from implementation string const executeFunc = new Function("return " + toolData.implementation)() as (params: any) => Promise; const toolEntry: ToolDefinition = { id: toolData.toolId, name: toolData.name, description: toolData.description, category: toolData.category as ToolDefinition["category"], dangerous: toolData.dangerous, parameters: toolData.parameters, execute: executeFunc, }; // Remove existing entry if present, then add new one const existingIdx = TOOL_REGISTRY.findIndex(t => t.id === toolData.toolId); if (existingIdx >= 0) { TOOL_REGISTRY.splice(existingIdx, 1, toolEntry); } else { TOOL_REGISTRY.push(toolEntry); } } catch (error: any) { console.warn(`[ToolBuilder] Failed to register tool ${toolData.toolId} in runtime:`, error.message); } } /** * Загружает все кастомные инструменты из БД в реестр при старте */ export async function loadCustomToolsFromDb(): Promise { const db = await getDb(); if (!db) return; try { const tools = await db.select().from(toolDefinitions) .where(eq(toolDefinitions.isActive, true)); for (const tool of tools) { registerToolInRegistry({ toolId: tool.toolId, name: tool.name, description: tool.description, category: tool.category, dangerous: tool.dangerous || false, parameters: (tool.parameters as any) || {}, implementation: tool.implementation, }); } console.log(`[ToolBuilder] Loaded ${tools.length} custom tools from DB`); } catch (error: any) { console.warn("[ToolBuilder] Failed to load custom tools:", error.message); } } /** * Получает все инструменты из БД */ export async function getCustomTools() { const db = await getDb(); if (!db) return []; return db.select().from(toolDefinitions); } /** * Удаляет инструмент из БД и реестра */ export async function deleteTool(toolId: string): Promise<{ success: boolean; error?: string }> { const db = await getDb(); if (!db) return { success: false, error: "Database not available" }; try { await db.delete(toolDefinitions).where(eq(toolDefinitions.toolId, toolId)); // Remove from TOOL_REGISTRY const idx = TOOL_REGISTRY.findIndex(t => t.id === toolId); if (idx >= 0) TOOL_REGISTRY.splice(idx, 1); return { success: true }; } catch (error: any) { return { success: false, error: error.message }; } } /** * Тестирует инструмент с заданными параметрами */ export async function testTool( toolId: string, params: Record ): Promise<{ success: boolean; result?: any; error?: string; executionTimeMs: number }> { const start = Date.now(); const tool = TOOL_REGISTRY.find(t => t.id === toolId); if (!tool) { return { success: false, error: `Tool '${toolId}' not found in registry`, executionTimeMs: 0 }; } if (!tool.execute) { return { success: false, error: `Tool '${toolId}' has no execute function (built-in tools use executeToolImpl)`, executionTimeMs: 0 }; } try { const result = await tool.execute(params); return { success: true, result, executionTimeMs: Date.now() - start }; } catch (error: any) { return { success: false, error: error.message, executionTimeMs: Date.now() - start }; } }