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;