server-log-monitor
Version:
AI-powered server log monitoring with BlackBox AI analysis, ElevenLabs conversational agents, file source detection, and intelligent voice alerts
429 lines (373 loc) • 13.8 kB
JavaScript
const axios = require('axios');
class ElevenLabsToolsManager {
constructor(config = {}) {
this.apiKey = config.apiKey || process.env.ELEVENLABS_API_KEY;
this.agentId = config.agentId || process.env.ELEVENLABS_AGENT_ID;
this.toolServerUrl = config.toolServerUrl || 'http://localhost:3001';
this.baseURL = 'https://api.elevenlabs.io/v1';
if (!this.apiKey) {
console.warn('⚠️ ElevenLabs API key not provided - tools management disabled');
this.enabled = false;
return;
}
this.enabled = true;
this.axiosInstance = axios.create({
baseURL: this.baseURL,
headers: {
'xi-api-key': this.apiKey,
'Content-Type': 'application/json'
},
timeout: 30000
});
// Define the tools that should be created
this.toolDefinitions = [
{
name: 'codebase_search',
description: 'Search for code patterns, functions, error messages, or specific text in the project codebase. Use this when the user asks about where specific code or errors might be located in their project files.',
endpoint: '/tools/codebase-search'
},
{
name: 'terminal_execute',
description: 'Execute safe diagnostic commands on the server to investigate issues, check logs, gather system information, or run troubleshooting commands. Only use for investigating problems, not for making changes. Examples: checking process status, viewing log files, network connectivity tests.',
endpoint: '/tools/terminal-execute'
},
{
name: 'file_content_viewer',
description: 'View the contents of a specific file in the codebase. Use this when you need to examine the actual code after finding it with codebase search, or when you need to see specific configuration files, logs, or source code.',
endpoint: '/tools/file-content'
},
{
name: 'error_location_finder',
description: 'Find where specific errors occur in the codebase using error messages, stack traces, or error types. Use this when analyzing critical alerts to locate the exact source of problems in the code.',
endpoint: '/tools/find-error-location'
},
{
name: 'project_structure',
description: 'Get an overview of the project structure to understand the codebase organization. Use this when you need to understand how the project is organized or when exploring an unfamiliar codebase.',
endpoint: '/tools/project-structure'
}
];
}
async createTools() {
if (!this.enabled) {
console.log('⚠️ ElevenLabs tools management not enabled - skipping tool creation');
return [];
}
console.log('🔧 Creating ElevenLabs tools for codebase integration...');
const createdTools = [];
for (const toolDef of this.toolDefinitions) {
try {
console.log(`📡 Creating tool: ${toolDef.name}`);
const response = await this.axiosInstance.post('/convai/tools', {
tool_config: {
type: 'webhook',
name: toolDef.name,
description: toolDef.description,
api_schema: {
url: `${this.toolServerUrl}${toolDef.endpoint}`,
method: 'POST',
request_body_schema: {
type: 'object',
properties: this.getSchemaPropertiesForTool(toolDef.endpoint)
}
}
}
});
console.log(`✅ Created tool: ${toolDef.name} (ID: ${response.data.id})`);
createdTools.push({
name: toolDef.name,
id: response.data.id,
endpoint: toolDef.endpoint,
config: response.data
});
} catch (error) {
console.error(`❌ Failed to create tool ${toolDef.name}:`, error.response?.data || error.message);
}
}
return createdTools;
}
async listExistingTools() {
if (!this.enabled) {
return [];
}
try {
console.log('📋 Fetching existing ElevenLabs tools...');
const response = await this.axiosInstance.get('/convai/tools');
return response.data.tools || [];
} catch (error) {
console.error('❌ Failed to list existing tools:', error.response?.data || error.message);
return [];
}
}
async updateTool(toolId, toolConfig) {
if (!this.enabled) {
return null;
}
try {
console.log(`🔄 Updating tool: ${toolId}`);
const response = await this.axiosInstance.patch(`/convai/tools/${toolId}`, {
tool_config: toolConfig
});
console.log(`✅ Updated tool: ${toolId}`);
return response.data;
} catch (error) {
console.error(`❌ Failed to update tool ${toolId}:`, error.response?.data || error.message);
throw error;
}
}
async deleteTool(toolId) {
if (!this.enabled) {
return null;
}
try {
console.log(`🗑️ Deleting tool: ${toolId}`);
await this.axiosInstance.delete(`/convai/tools/${toolId}`);
console.log(`✅ Deleted tool: ${toolId}`);
return true;
} catch (error) {
console.error(`❌ Failed to delete tool ${toolId}:`, error.response?.data || error.message);
throw error;
}
}
async assignToolsToAgent(toolIds) {
if (!this.enabled || !this.agentId) {
console.log('⚠️ Agent ID not provided - cannot assign tools to agent');
return null;
}
try {
console.log(`🤖 Assigning ${toolIds.length} tools to agent: ${this.agentId}`);
// First, get current agent configuration
const agentResponse = await this.axiosInstance.get(`/convai/agents/${this.agentId}`);
const currentConfig = agentResponse.data;
// Update the agent with the tools using the new tool_ids format
// Remove the old 'tools' field if it exists to avoid conflicts
const existingPrompt = currentConfig.conversation_config?.agent?.prompt || {};
const { tools, ...promptWithoutTools } = existingPrompt;
const updatedConfig = {
...currentConfig.conversation_config,
agent: {
...currentConfig.conversation_config?.agent,
prompt: {
...promptWithoutTools,
tool_ids: toolIds
}
}
};
const response = await this.axiosInstance.patch(`/convai/agents/${this.agentId}`, {
conversation_config: updatedConfig
});
console.log(`✅ Successfully assigned tools to agent: ${this.agentId}`);
return response.data;
} catch (error) {
console.error(`❌ Failed to assign tools to agent ${this.agentId}:`, error.response?.data || error.message);
throw error;
}
}
async setupCompleteIntegration() {
if (!this.enabled) {
console.log('⚠️ ElevenLabs tools management not enabled');
return null;
}
console.log('🚀 Setting up complete ElevenLabs tools integration...');
try {
// 1. Check if tool server is running
console.log('🔍 Checking tool server availability...');
try {
await axios.get(`${this.toolServerUrl}/health`, { timeout: 5000 });
console.log('✅ Tool server is running');
} catch (error) {
console.error('❌ Tool server is not accessible. Please start it with: server-log-monitor tool-server');
return null;
}
// 2. List existing tools to avoid duplicates
const existingTools = await this.listExistingTools();
console.log(`📋 Found ${existingTools.length} existing tools`);
// 3. Create new tools (skip if already exist)
const createdTools = [];
for (const toolDef of this.toolDefinitions) {
const existingTool = existingTools.find(t =>
t.tool_config && t.tool_config.name === toolDef.name
);
if (existingTool) {
console.log(`⏭️ Tool "${toolDef.name}" already exists (ID: ${existingTool.id})`);
createdTools.push({
name: toolDef.name,
id: existingTool.id,
endpoint: toolDef.endpoint,
existing: true
});
} else {
const newTool = await this.createSingleTool(toolDef);
if (newTool) {
createdTools.push(newTool);
}
}
}
// 4. Assign tools to agent if agent ID is provided
if (this.agentId && createdTools.length > 0) {
const toolIds = createdTools.map(t => t.id);
await this.assignToolsToAgent(toolIds);
}
console.log(`🎉 Integration setup complete! Created/verified ${createdTools.length} tools`);
return {
success: true,
tools: createdTools,
toolServerUrl: this.toolServerUrl,
agentId: this.agentId
};
} catch (error) {
console.error('❌ Failed to setup complete integration:', error.message);
return {
success: false,
error: error.message
};
}
}
async createSingleTool(toolDef) {
try {
console.log(`📡 Creating tool: ${toolDef.name}`);
const response = await this.axiosInstance.post('/convai/tools', {
tool_config: {
type: 'webhook',
name: toolDef.name,
description: toolDef.description,
api_schema: {
url: `${this.toolServerUrl}${toolDef.endpoint}`,
method: 'POST',
request_body_schema: {
type: 'object',
properties: this.getSchemaPropertiesForTool(toolDef.endpoint)
}
}
}
});
console.log(`✅ Created tool: ${toolDef.name} (ID: ${response.data.id})`);
return {
name: toolDef.name,
id: response.data.id,
endpoint: toolDef.endpoint,
config: response.data,
existing: false
};
} catch (error) {
console.error(`❌ Failed to create tool ${toolDef.name}:`, error.response?.data || error.message);
return null;
}
}
async updateToolServerUrls(newToolServerUrl) {
if (!this.enabled) {
return null;
}
console.log(`🔄 Updating all tools to use new server URL: ${newToolServerUrl}`);
this.toolServerUrl = newToolServerUrl;
try {
const existingTools = await this.listExistingTools();
const toolsToUpdate = existingTools.filter(tool =>
this.toolDefinitions.some(def => tool.name === def.name)
);
const updatePromises = toolsToUpdate.map(async (tool) => {
const toolDef = this.toolDefinitions.find(def => def.name === tool.name);
if (toolDef) {
return this.updateTool(tool.id, {
name: tool.name,
description: toolDef.description,
api_schema: {
url: `${newToolServerUrl}${toolDef.endpoint}`
}
});
}
});
await Promise.all(updatePromises);
console.log(`✅ Updated ${toolsToUpdate.length} tools with new server URL`);
return {
success: true,
updatedCount: toolsToUpdate.length,
newUrl: newToolServerUrl
};
} catch (error) {
console.error('❌ Failed to update tool server URLs:', error.message);
throw error;
}
}
getSchemaPropertiesForTool(endpoint) {
const schemas = {
'/tools/codebase-search': {
query: {
type: 'string',
description: 'Text to search for in the codebase (e.g., function names, error messages, code patterns)'
},
fileTypes: {
type: 'string',
description: 'Comma-separated file extensions to search in (default: js,ts,tsx,jsx,py)'
},
maxResults: {
type: 'integer',
description: 'Maximum number of search results to return (default: 50)'
}
},
'/tools/terminal-execute': {
command: {
type: 'string',
description: 'Diagnostic command to execute (e.g., "ls -la", "ps aux", "git status")'
},
workingDir: {
type: 'string',
description: 'Working directory for command execution (optional)'
},
timeout: {
type: 'integer',
description: 'Timeout in milliseconds (default: 30000)'
}
},
'/tools/file-content': {
filePath: {
type: 'string',
description: 'Relative path to the file to read (e.g., "src/components/App.js")'
},
lines: {
type: 'integer',
description: 'Number of lines to read (default: 100)'
},
startLine: {
type: 'integer',
description: 'Starting line number (default: 0)'
}
},
'/tools/find-error-location': {
errorMessage: {
type: 'string',
description: 'Error message to search for in the codebase'
},
errorType: {
type: 'string',
description: 'Type of error (e.g., "TypeError", "ConnectionError")'
},
stackTrace: {
type: 'string',
description: 'Full stack trace to parse for file locations'
}
},
'/tools/project-structure': {
maxDepth: {
type: 'integer',
description: 'Maximum directory depth to explore (default: 3)'
},
includeFiles: {
type: 'boolean',
description: 'Whether to include files in the structure (default: true)'
}
}
};
return schemas[endpoint] || {};
}
getConfiguration() {
return {
enabled: this.enabled,
apiKey: this.apiKey ? '***configured***' : 'Not set',
agentId: this.agentId || 'Not set',
toolServerUrl: this.toolServerUrl,
toolCount: this.toolDefinitions.length
};
}
}
module.exports = ElevenLabsToolsManager;