171 lines
7.5 KiB
TypeScript

/**
* Handler for LLM tool executions
*/
import log from "../../../log.js";
import type { Message } from "../../ai_interface.js";
/**
* Handles the execution of LLM tools
*/
export class ToolHandler {
/**
* Execute tool calls from the LLM response
* @param response The LLM response containing tool calls
* @param chatNoteId Optional chat note ID for tracking
*/
static async executeToolCalls(response: any, chatNoteId?: string): Promise<Message[]> {
log.info(`========== TOOL EXECUTION FLOW ==========`);
if (!response.tool_calls || response.tool_calls.length === 0) {
log.info(`No tool calls to execute, returning early`);
return [];
}
log.info(`Executing ${response.tool_calls.length} tool calls`);
try {
// Import tool registry directly to avoid circular dependencies
const toolRegistry = (await import('../../tools/tool_registry.js')).default;
// Check if tools are available
const availableTools = toolRegistry.getAllTools();
log.info(`Available tools in registry: ${availableTools.length}`);
if (availableTools.length === 0) {
log.error('No tools available in registry for execution');
// Try to initialize tools
try {
// Ensure tools are initialized
const initResult = await this.ensureToolsInitialized();
if (!initResult) {
throw new Error('Failed to initialize tools');
}
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error);
log.error(`Failed to initialize tools: ${errorMessage}`);
throw new Error('Tool execution failed: No tools available');
}
}
// Execute each tool call and collect results
const toolResults = await Promise.all(response.tool_calls.map(async (toolCall: any) => {
try {
log.info(`Executing tool: ${toolCall.function.name}, ID: ${toolCall.id || 'unknown'}`);
// Get the tool from registry
const tool = toolRegistry.getTool(toolCall.function.name);
if (!tool) {
throw new Error(`Tool not found: ${toolCall.function.name}`);
}
// Parse arguments
let args;
if (typeof toolCall.function.arguments === 'string') {
try {
args = JSON.parse(toolCall.function.arguments);
} catch (e: unknown) {
log.error(`Failed to parse tool arguments: ${e instanceof Error ? e.message : String(e)}`);
// Try cleanup and retry
try {
const cleaned = toolCall.function.arguments
.replace(/^['"]|['"]$/g, '') // Remove surrounding quotes
.replace(/\\"/g, '"') // Replace escaped quotes
.replace(/([{,])\s*'([^']+)'\s*:/g, '$1"$2":') // Replace single quotes around property names
.replace(/([{,])\s*(\w+)\s*:/g, '$1"$2":'); // Add quotes around unquoted property names
args = JSON.parse(cleaned);
} catch (cleanErr) {
// If all parsing fails, use as-is
args = { text: toolCall.function.arguments };
}
}
} else {
args = toolCall.function.arguments;
}
// Log what we're about to execute
log.info(`Executing tool with arguments: ${JSON.stringify(args)}`);
// Execute the tool and get result
const startTime = Date.now();
const result = await tool.execute(args);
const executionTime = Date.now() - startTime;
log.info(`Tool execution completed in ${executionTime}ms`);
// Log the result
const resultPreview = typeof result === 'string'
? result.substring(0, 100) + (result.length > 100 ? '...' : '')
: JSON.stringify(result).substring(0, 100) + '...';
log.info(`Tool result: ${resultPreview}`);
// Format result as a proper message
return {
role: 'tool',
content: typeof result === 'string' ? result : JSON.stringify(result),
name: toolCall.function.name,
tool_call_id: toolCall.id || `tool-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`
};
} catch (error: any) {
log.error(`Error executing tool ${toolCall.function.name}: ${error.message}`);
// Return error as tool result
return {
role: 'tool',
content: `Error: ${error.message}`,
name: toolCall.function.name,
tool_call_id: toolCall.id || `tool-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`
};
}
}));
log.info(`Completed execution of ${toolResults.length} tools`);
return toolResults;
} catch (error: any) {
log.error(`Error in tool execution handler: ${error.message}`);
throw error;
}
}
/**
* Ensure LLM tools are initialized
*/
static async ensureToolsInitialized(): Promise<boolean> {
try {
log.info("Checking LLM tool initialization...");
// Import tool registry
const toolRegistry = (await import('../../tools/tool_registry.js')).default;
// Check if tools are already initialized
const registeredTools = toolRegistry.getAllTools();
if (registeredTools.length === 0) {
log.info("No tools found in registry.");
log.info("Note: Tools should be initialized in the AIServiceManager constructor.");
// Create AI service manager instance to trigger tool initialization
const aiServiceManager = (await import('../../ai_service_manager.js')).default;
aiServiceManager.getInstance();
// Check again after AIServiceManager instantiation
const tools = toolRegistry.getAllTools();
log.info(`After AIServiceManager instantiation: ${tools.length} tools available`);
} else {
log.info(`LLM tools already initialized: ${registeredTools.length} tools available`);
}
// Get all available tools for logging
const availableTools = toolRegistry.getAllTools().map(t => t.definition.function.name);
log.info(`Available tools: ${availableTools.join(', ')}`);
log.info("LLM tools initialized successfully: " + availableTools.length + " tools available");
return true;
} catch (error) {
log.error(`Failed to initialize LLM tools: ${error}`);
return false;
}
}
}