UNPKG

contaigents

Version:

Modular AI Content Ecosystem with Audio Generation

344 lines (343 loc) 13.5 kB
import { FileReadTool } from './FileReadTool.js'; import { FileWriteTool } from './FileWriteTool.js'; import { FileSearchTool } from './FileSearchTool.js'; import { FileReadLinesTool } from './FileReadLinesTool.js'; import { FileWriteLinesTool } from './FileWriteLinesTool.js'; import { CommandExecutionTool } from './CommandExecutionTool.js'; import { AudioGenerationTool } from './AudioGenerationTool.js'; import { AudioProjectTool } from './AudioProjectTool.js'; import { ImageGenerationTool } from './ImageGenerationTool.js'; import { FFmpegTool } from './FFmpegTool.js'; import { SpeechToTextTool } from './SpeechToTextTool.js'; import { TextToImageTool } from './TextToImageTool.js'; import { ImageManipulationTool } from './ImageManipulationTool.js'; import { MemoryTool } from './MemoryTool.js'; import { CinematicWorkflowTool } from './CinematicWorkflowTool.js'; import { FileService } from '../fileService.js'; export class ToolManager { constructor(baseDir) { this.tools = new Map(); this.fileService = new FileService(baseDir); this.initializeTools(baseDir); } initializeTools(baseDir) { // Register built-in tools const fileReadTool = new FileReadTool(baseDir); const fileWriteTool = new FileWriteTool(baseDir); const fileSearchTool = new FileSearchTool(baseDir); const fileReadLinesTool = new FileReadLinesTool(baseDir); const fileWriteLinesTool = new FileWriteLinesTool(baseDir); const commandExecutionTool = new CommandExecutionTool(baseDir); const audioGenerationTool = new AudioGenerationTool(baseDir); const audioProjectTool = new AudioProjectTool(baseDir); const imageGenerationTool = new ImageGenerationTool(baseDir); const ffmpegTool = new FFmpegTool(baseDir); const speechToTextTool = new SpeechToTextTool(baseDir); const textToImageTool = new TextToImageTool(baseDir || process.cwd()); const imageManipulationTool = new ImageManipulationTool(baseDir || process.cwd()); const memoryTool = new MemoryTool(baseDir); const cinematicWorkflowTool = new CinematicWorkflowTool(baseDir); this.registerTool(fileReadTool); this.registerTool(fileWriteTool); this.registerTool(fileSearchTool); this.registerTool(fileReadLinesTool); this.registerTool(fileWriteLinesTool); this.registerTool(commandExecutionTool); this.registerTool(audioGenerationTool); this.registerTool(audioProjectTool); this.registerTool(imageGenerationTool); this.registerTool(memoryTool); this.registerTool(ffmpegTool); this.registerTool(speechToTextTool); this.registerTool(textToImageTool); this.registerTool(imageManipulationTool); this.registerTool(cinematicWorkflowTool); } registerTool(tool) { this.tools.set(tool.getName(), tool); } unregisterTool(toolName) { return this.tools.delete(toolName); } getAvailableTools() { return Array.from(this.tools.values()).map(tool => tool.getDefinition()); } getTool(toolName) { return this.tools.get(toolName); } async executeTool(toolCall) { const tool = this.tools.get(toolCall.tool_name); if (!tool) { return { success: false, error: `Tool '${toolCall.tool_name}' not found. Available tools: ${Array.from(this.tools.keys()).join(', ')}` }; } try { return await tool.execute(toolCall.parameters); } catch (error) { return { success: false, error: `Error executing tool '${toolCall.tool_name}': ${error.message}` }; } } /** * Generate a file tree context for the system prompt */ async generateFileTreeContext() { try { const fileTree = await this.fileService.getFileTree(); return this.formatFileTree(fileTree); } catch (error) { console.error('Error generating file tree context:', error); return 'File tree context unavailable.'; } } /** * Get memory content for system prompt */ async getMemoryContext() { try { const memoryTool = this.getTool('memory'); if (memoryTool && typeof memoryTool.getMemoryForSystemPrompt === 'function') { const memoryContent = await memoryTool.getMemoryForSystemPrompt(); if (memoryContent) { return `## Conversation Memory\n\nRecent context from previous interactions:\n${memoryContent}`; } } return ''; } catch (error) { return ''; } } formatFileTree(items, indent = '') { let result = ''; for (const item of items) { if (item.type === 'directory') { result += `${indent}📁 ${item.name}/\n`; if (item.children && item.children.length > 0) { result += this.formatFileTree(item.children, indent + ' '); } } else if (item.type === 'file') { const icon = this.getFileIcon(item.name); result += `${indent}${icon} ${item.name}\n`; } } return result; } getFileIcon(filename) { const ext = filename.split('.').pop()?.toLowerCase(); switch (ext) { case 'md': case 'mdx': return '📝'; case 'js': case 'jsx': return '🟨'; case 'ts': case 'tsx': return '🔷'; case 'json': return '📋'; case 'txt': return '📄'; case 'py': return '🐍'; case 'html': return '🌐'; case 'css': case 'scss': return '🎨'; case 'wav': case 'mp3': case 'aac': case 'flac': return '🎵'; case 'mp4': case 'avi': case 'mov': case 'mkv': return '🎬'; case 'jpg': case 'jpeg': case 'png': case 'gif': case 'svg': return '🖼️'; case 'pdf': return '📕'; case 'zip': case 'tar': case 'gz': return '📦'; default: return '📄'; } } /** * Parse tool calls from LLM response * XML format: <tool_call name="tool_name" id="1"><param_name>value</param_name></tool_call> */ parseToolCalls(content) { return this.parseXMLToolCalls(content); } /** * Parse XML-based tool calls * Format: <tool_call name="tool_name" id="1"><param_name>value</param_name></tool_call> */ parseXMLToolCalls(content) { const toolCalls = []; const xmlRegex = /<tool_call\s+name="(\w+)"(?:\s+id="([^"]*)")?>(.*?)<\/tool_call>/gs; let match; while ((match = xmlRegex.exec(content)) !== null) { const toolName = match[1]; const toolId = match[2]; // Optional ID const parametersXml = match[3]; try { const parameters = this.parseXMLParameters(parametersXml); toolCalls.push({ tool_name: toolName, parameters, id: toolId }); } catch (error) { console.error(`Failed to parse XML tool call parameters for ${toolName}:`, error); console.error(`Original XML: ${parametersXml}`); } } return toolCalls; } /** * Parse parameters from XML content */ parseXMLParameters(xmlContent) { const parameters = {}; // Match parameter tags: <param_name>value</param_name> const paramRegex = /<(\w+)>(.*?)<\/\1>/gs; let match; while ((match = paramRegex.exec(xmlContent)) !== null) { const paramName = match[1]; let paramValue = match[2].trim(); // Handle CDATA sections if (paramValue.startsWith('<![CDATA[') && paramValue.endsWith(']]>')) { paramValue = paramValue.slice(9, -3); } // Check for <item> array format if (paramValue.includes('<item>')) { const items = []; const itemRegex = /<item>(.*?)<\/item>/gs; let itemMatch; while ((itemMatch = itemRegex.exec(paramValue)) !== null) { items.push(itemMatch[1].trim()); } if (items.length > 0) { parameters[paramName] = items; continue; } } // Try to parse as JSON for complex values, otherwise use as string try { // Check if it looks like JSON (starts with { or [ or is a number/boolean) if (paramValue.match(/^[\{\[\d]/) || paramValue === 'true' || paramValue === 'false' || paramValue === 'null') { parameters[paramName] = JSON.parse(paramValue); } else { parameters[paramName] = paramValue; } } catch { // If JSON parsing fails, use as string parameters[paramName] = paramValue; } } return parameters; } /** * Generate tool usage instructions for the system prompt */ generateToolInstructions() { const tools = this.getAvailableTools(); if (tools.length === 0) { return ''; } let instructions = '\n\n## Available Tools\n\n'; instructions += 'You have access to the following tools. To use a tool, include a tool call in your response using XML format:\n\n'; instructions += '```xml\n<tool_call name="tool_name" id="1">\n <parameter_name>parameter_value</parameter_name>\n</tool_call>\n```\n\n'; instructions += 'For complex content (multiline text, JSON, code), use CDATA:\n'; instructions += '```xml\n<tool_call name="write_file" id="2">\n <operation_type>create</operation_type>\n <file_path>example.md</file_path>\n <content><![CDATA[\n# Complex Content\nWith "quotes" and [brackets] and {braces}\nMultiline content works perfectly!\n]]></content>\n</tool_call>\n```\n\n'; for (const tool of tools) { instructions += `### ${tool.name}\n`; instructions += `${tool.description}\n\n`; instructions += 'Parameters:\n'; for (const param of tool.parameters) { const required = param.required ? ' (required)' : ' (optional)'; const defaultValue = param.default !== undefined ? ` (default: ${param.default})` : ''; instructions += `- **${param.name}** (${param.type})${required}${defaultValue}: ${param.description}\n`; } instructions += '\n'; } // Add tool-specific guidance from each tool instructions += this.generateToolGuidance(); instructions += 'Example usage:\n'; instructions += '<tool_call name="read_file" id="1">\n <file_path>README.md</file_path>\n</tool_call>\n\n'; return instructions; } /** * Generate tool-specific guidance from all registered tools */ generateToolGuidance() { let guidance = ''; for (const [, tool] of this.tools) { const toolGuidance = tool.getAgentGuidance(); if (toolGuidance) { guidance += toolGuidance + '\n'; } } return guidance; } /** * Generate XML tool response format */ generateToolResponse(toolResponse) { const { id, tool_call, result } = toolResponse; let xml = `<tool_response id="${id}">\n`; xml += ` <tool_call name="${tool_call.tool_name}">\n`; // Add tool call parameters for (const [key, value] of Object.entries(tool_call.parameters)) { if (typeof value === 'string' && (value.includes('\n') || value.includes('<') || value.includes('>'))) { xml += ` <${key}><![CDATA[${value}]]></${key}>\n`; } else { xml += ` <${key}>${value}</${key}>\n`; } } xml += ` </tool_call>\n`; xml += ` <result>\n`; if (result.success) { if (result.data?.content) { xml += ` <content><![CDATA[${result.data.content}]]></content>\n`; } else if (result.message) { xml += ` <message>${result.message}</message>\n`; } } else { xml += ` <error>${result.error}</error>\n`; } xml += ` </result>\n`; xml += `</tool_response>`; return xml; } /** * Generate multiple tool responses */ generateToolResponses(toolResponses) { return toolResponses.map(response => this.generateToolResponse(response)).join('\n\n'); } }