contaigents
Version:
Modular AI Content Ecosystem with Audio Generation
344 lines (343 loc) • 13.5 kB
JavaScript
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');
}
}