import { getAgentById, getAgentAccessControl } from "./agents"; /** * Определение инструмента */ export interface ToolDefinition { id: string; name: string; description: string; category: "browser" | "shell" | "file" | "docker" | "http" | "system" | "custom" | "web" | "data" | "ai"; icon?: string; parameters: Record; dangerous: boolean; /** Optional custom execute function for dynamically registered tools */ execute?: (params: Record) => Promise; } /** * Реестр доступных инструментов */ export const TOOL_REGISTRY: ToolDefinition[] = [ { id: "http_get", name: "HTTP GET", description: "Выполнить GET-запрос к URL", category: "http", icon: "Globe", parameters: { url: { type: "string", description: "URL для запроса", required: true }, headers: { type: "object", description: "Заголовки запроса" }, }, dangerous: false, }, { id: "http_post", name: "HTTP POST", description: "Выполнить POST-запрос к URL с телом", category: "http", icon: "Send", parameters: { url: { type: "string", description: "URL для запроса", required: true }, body: { type: "object", description: "Тело запроса" }, headers: { type: "object", description: "Заголовки запроса" }, }, dangerous: false, }, { id: "shell_exec", name: "Shell Execute", description: "Выполнить bash-команду в изолированной среде", category: "shell", icon: "Terminal", parameters: { command: { type: "string", description: "Команда для выполнения", required: true }, timeout: { type: "number", description: "Таймаут в секундах (по умолчанию 30)" }, }, dangerous: true, }, { id: "file_read", name: "File Read", description: "Прочитать содержимое файла", category: "file", icon: "FileText", parameters: { path: { type: "string", description: "Путь к файлу", required: true }, }, dangerous: false, }, { id: "file_write", name: "File Write", description: "Записать содержимое в файл", category: "file", icon: "FilePlus", parameters: { path: { type: "string", description: "Путь к файлу", required: true }, content: { type: "string", description: "Содержимое файла", required: true }, }, dangerous: true, }, { id: "docker_list", name: "Docker List", description: "Получить список контейнеров Docker", category: "docker", icon: "Box", parameters: { all: { type: "boolean", description: "Показать все контейнеры (включая остановленные)" }, }, dangerous: false, }, { id: "docker_exec", name: "Docker Exec", description: "Выполнить команду в контейнере Docker", category: "docker", icon: "Play", parameters: { container: { type: "string", description: "ID или имя контейнера", required: true }, command: { type: "string", description: "Команда для выполнения", required: true }, }, dangerous: true, }, { id: "docker_logs", name: "Docker Logs", description: "Получить логи контейнера", category: "docker", icon: "FileText", parameters: { container: { type: "string", description: "ID или имя контейнера", required: true }, tail: { type: "number", description: "Количество последних строк (по умолчанию 100)" }, }, dangerous: false, }, { id: "browser_navigate", name: "Browser Navigate", description: "Открыть URL в браузере и получить содержимое", category: "browser", icon: "Globe", parameters: { url: { type: "string", description: "URL для открытия", required: true }, }, dangerous: false, }, { id: "browser_screenshot", name: "Browser Screenshot", description: "Сделать скриншот текущей страницы", category: "browser", icon: "Camera", parameters: {}, dangerous: false, }, ]; /** * Получить все доступные инструменты */ export function getAllTools(): ToolDefinition[] { return TOOL_REGISTRY; } /** * Получить инструмент по ID */ export function getToolById(id: string): ToolDefinition | undefined { return TOOL_REGISTRY.find((t) => t.id === id); } /** * Выполнить инструмент от имени агента */ export async function executeTool( agentId: number, toolId: string, params: Record ): Promise<{ success: boolean; result?: any; error?: string; executionTimeMs: number }> { const startTime = Date.now(); // Проверяем существование агента const agent = await getAgentById(agentId); if (!agent) { return { success: false, error: "Agent not found", executionTimeMs: 0 }; } // Проверяем доступность инструмента const tool = getToolById(toolId); if (!tool) { return { success: false, error: `Unknown tool: ${toolId}`, executionTimeMs: 0 }; } // Проверяем разрешения агента const accessControls = await getAgentAccessControl(agentId); const toolAccess = accessControls.find((ac) => ac.tool === toolId); if (toolAccess && !toolAccess.isAllowed) { return { success: false, error: `Tool '${toolId}' is blocked for this agent`, executionTimeMs: Date.now() - startTime, }; } // Проверяем, есть ли инструмент в allowedTools агента const allowedTools = (agent.allowedTools as string[]) || []; if (allowedTools.length > 0 && !allowedTools.includes(toolId)) { return { success: false, error: `Tool '${toolId}' is not in agent's allowed tools list`, executionTimeMs: Date.now() - startTime, }; } try { const result = await executeToolImpl(toolId, params, toolAccess); return { success: true, result, executionTimeMs: Date.now() - startTime, }; } catch (err: any) { return { success: false, error: err.message, executionTimeMs: Date.now() - startTime, }; } } /** * Реализация выполнения инструментов */ async function executeToolImpl( toolId: string, params: Record, _accessControl?: any ): Promise { switch (toolId) { case "http_get": { const response = await fetch(params.url, { headers: params.headers || {}, signal: AbortSignal.timeout(30000), }); const text = await response.text(); return { status: response.status, statusText: response.statusText, body: text.slice(0, 10000), // Limit response size headers: Object.fromEntries(response.headers.entries()), }; } case "http_post": { const response = await fetch(params.url, { method: "POST", headers: { "Content-Type": "application/json", ...(params.headers || {}), }, body: JSON.stringify(params.body || {}), signal: AbortSignal.timeout(30000), }); const text = await response.text(); return { status: response.status, statusText: response.statusText, body: text.slice(0, 10000), headers: Object.fromEntries(response.headers.entries()), }; } case "shell_exec": { const { exec } = await import("child_process"); const { promisify } = await import("util"); const execAsync = promisify(exec); const timeout = (params.timeout || 30) * 1000; // Safety: block dangerous commands const blockedPatterns = ["rm -rf /", "mkfs", "dd if=", ":(){ :|:& };:"]; for (const pattern of blockedPatterns) { if (params.command.includes(pattern)) { throw new Error(`Command blocked for safety: contains '${pattern}'`); } } const { stdout, stderr } = await execAsync(params.command, { timeout }); return { stdout: stdout.slice(0, 10000), stderr: stderr.slice(0, 2000) }; } case "file_read": { const { readFile } = await import("fs/promises"); const content = await readFile(params.path, "utf-8"); return { content: content.slice(0, 50000), size: content.length }; } case "file_write": { const { writeFile, mkdir } = await import("fs/promises"); const { dirname } = await import("path"); await mkdir(dirname(params.path), { recursive: true }); await writeFile(params.path, params.content, "utf-8"); return { written: params.content.length, path: params.path }; } case "docker_list": { const { exec } = await import("child_process"); const { promisify } = await import("util"); const execAsync = promisify(exec); const flag = params.all ? "-a" : ""; const { stdout } = await execAsync(`docker ps ${flag} --format json`); const containers = stdout .trim() .split("\n") .filter(Boolean) .map((line) => { try { return JSON.parse(line); } catch { return line; } }); return { containers }; } case "docker_exec": { const { exec } = await import("child_process"); const { promisify } = await import("util"); const execAsync = promisify(exec); const { stdout, stderr } = await execAsync( `docker exec ${params.container} ${params.command}`, { timeout: 30000 } ); return { stdout: stdout.slice(0, 10000), stderr: stderr.slice(0, 2000) }; } case "docker_logs": { const { exec } = await import("child_process"); const { promisify } = await import("util"); const execAsync = promisify(exec); const tail = params.tail || 100; const { stdout, stderr } = await execAsync( `docker logs --tail ${tail} ${params.container}`, { timeout: 15000 } ); return { logs: (stdout + stderr).slice(0, 20000) }; } case "browser_navigate": { // Simple HTTP fetch as browser substitute (no JS rendering) const response = await fetch(params.url, { headers: { "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", }, signal: AbortSignal.timeout(30000), }); const html = await response.text(); // Strip HTML tags for readable output const text = html .replace(/]*>[\s\S]*?<\/script>/gi, "") .replace(/]*>[\s\S]*?<\/style>/gi, "") .replace(/<[^>]+>/g, " ") .replace(/\s+/g, " ") .trim() .slice(0, 10000); return { url: params.url, status: response.status, text }; } case "browser_screenshot": { return { error: "Screenshot requires headless browser (not available in this environment)" }; } default: throw new Error(`Tool '${toolId}' not implemented`); } }