File size: 4,911 Bytes
ce37a9c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | const fs = require('fs-extra');
const path = require('path');
const pino = require('pino');
const logger = pino({
level: 'info',
transport: {
target: 'pino-pretty',
options: { colorize: true, translateTime: 'SYS:standard', ignore: 'pid,hostname' }
}
});
class ToolHandler {
constructor() {
this.toolsPath = path.join(__dirname, '../tools');
this.schemasPath = path.join(__dirname, '../tools/schemas');
this.registry = new Map();
this._initRegistry();
}
_initRegistry() {
if (!fs.existsSync(this.schemasPath)) return;
const schemas = fs.readdirSync(this.schemasPath).filter(f => f.endsWith('.json'));
for (const schemaFile of schemas) {
const toolName = path.basename(schemaFile, '.json');
const implFile = path.join(this.toolsPath, `${toolName}.js`);
if (fs.existsSync(implFile)) {
this.registry.set(toolName, {
schemaPath: path.join(this.schemasPath, schemaFile),
implPath: implFile
});
} else {
logger.warn(`Tool schema found for "${toolName}" but implementation file is missing.`);
}
}
}
getTools() {
const tools = [];
for (const [name, paths] of this.registry) {
try {
const schema = fs.readJSONSync(paths.schemaPath);
schema.name = name;
tools.push(schema);
} catch (err) {
logger.error(`Error reading schema for ${name}: ${err.message}`);
}
}
return [{ function_declarations: tools }];
}
/**
* Get tools in OpenAI/Groq format
*/
getOpenAITools() {
const tools = [];
for (const [name, paths] of this.registry) {
try {
const schema = fs.readJSONSync(paths.schemaPath);
// Convert Gemini-style schema to OpenAI-style
const convertSchema = (obj) => {
if (typeof obj !== 'object' || obj === null) return obj;
const newObj = Array.isArray(obj) ? [] : {};
for (const key in obj) {
if (key === 'type' && typeof obj[key] === 'string') {
// Map Gemini types to JSON Schema types
const typeMap = {
'STRING': 'string',
'NUMBER': 'number',
'INTEGER': 'integer',
'BOOLEAN': 'boolean',
'ARRAY': 'array',
'OBJECT': 'object'
};
newObj[key] = typeMap[obj[key]] || obj[key].toLowerCase();
} else {
newObj[key] = convertSchema(obj[key]);
}
}
return newObj;
};
const openAISchema = {
type: "function",
function: {
name: name,
description: schema.description,
parameters: convertSchema(schema.parameters)
}
};
tools.push(openAISchema);
} catch (err) {
logger.error(`Error formatting OpenAI schema for ${name}: ${err.message}`);
}
}
return tools.length > 0 ? tools : undefined;
}
async executeTool(name, args, context = {}) {
if (!this.registry.has(name)) {
logger.error(`Execution failed: Tool "${name}" not found in registry.`);
throw new Error(`Tool ${name} not found`);
}
const { implPath } = this.registry.get(name);
try {
logger.info({ event: 'TOOL_EXEC_START', tool: name, args });
const toolModule = require(implPath);
if (typeof toolModule.execute !== 'function') {
throw new Error(`Tool ${name} does not export an 'execute' function.`);
}
const result = await toolModule.execute(args, context);
logger.info({
event: 'TOOL_EXEC_END',
tool: name,
success: !!(result && !result.error),
output: typeof result === 'object' ? JSON.stringify(result).slice(0, 500) : result
});
return result;
} catch (error) {
logger.error({ event: 'TOOL_EXEC_ERROR', tool: name, error: error.message });
return { error: error.message };
}
}
}
const toolHandler = new ToolHandler();
module.exports = toolHandler; |