diff --git a/serializer/__tests__/serializer.test.ts b/serializer/__tests__/serializer.test.ts index e14c32be86..f867beb754 100644 --- a/serializer/__tests__/serializer.test.ts +++ b/serializer/__tests__/serializer.test.ts @@ -1,369 +1,467 @@ -import { describe, expect, test } from '@jest/globals'; +import { Node, Edge } from 'reactflow'; import { Serializer } from '../index'; -import { BlockConfig, BlockType } from '@/blocks/types/block'; -import { Node } from 'reactflow'; import { SerializedWorkflow } from '../types'; -jest.mock('@/components/icons', () => ({ - AgentIcon: jest.fn(), - ApiIcon: jest.fn(), - ConditionalIcon: jest.fn() -})); - -import { AgentIcon, ApiIcon, ConditionalIcon } from '@/components/icons'; - describe('Serializer', () => { - const serializer = new Serializer(); + let serializer: Serializer; - test('should serialize an agent block correctly', () => { - const mockAgentBlock: Node = { - id: 'agent-1', - type: 'custom', - position: { x: 100, y: 200 }, - data: { - type: 'agent', - toolbar: { - title: 'Agent', - description: 'Use any LLM', - bgColor: '#7F2FFF', - icon: AgentIcon, - category: 'basic', - }, - workflow: { - inputs: { - prompt: 'string', - context: 'string' - }, - outputs: { - response: 'string', - tokens: 'number' - }, - subBlocks: [ - { - title: 'System Prompt', - type: 'long-input', - layout: 'full', - placeholder: 'Enter prompt' - }, - { - title: 'Model', - type: 'dropdown', - layout: 'half', - options: ['GPT-4o', 'Gemini 2.0'] - } - ] - } - } - }; - - const serialized = serializer.serializeWorkflow([mockAgentBlock], []); - - // Check basic structure - expect(serialized.version).toBe('1.0'); - expect(serialized.blocks).toHaveLength(1); - - const serializedBlock = serialized.blocks[0]; - - // Check block properties - expect(serializedBlock.id).toBe('agent-1'); - expect(serializedBlock.type).toBe('agent'); - expect(serializedBlock.position).toEqual({ x: 100, y: 200 }); - - // Check config - expect(serializedBlock.config.inputs).toEqual({ - prompt: 'string', - context: 'string' - }); - expect(serializedBlock.config.outputs).toEqual({ - response: 'string', - tokens: 'number' - }); - - // Check extracted values from subBlocks - expect(serializedBlock.config.system_prompt).toBe(''); - expect(serializedBlock.config.model).toBe('GPT-4o'); + beforeEach(() => { + serializer = new Serializer(); }); - test('should serialize an HTTP block correctly', () => { - const mockHttpBlock: Node = { - id: 'http-1', - type: 'custom', - position: { x: 150, y: 250 }, - data: { - type: 'api' as BlockType, - toolbar: { - title: 'HTTP', - description: 'Make HTTP requests', - bgColor: '#FF4D4D', - icon: ApiIcon, - category: 'basic', - }, - workflow: { - inputs: { - headers: 'object', - body: 'object' - }, - outputs: { - response: 'object', - status: 'number' - }, - subBlocks: [ - { - title: 'URL', - type: 'short-input', - layout: 'full', - placeholder: 'Enter URL' - }, - { - title: 'Method', - type: 'dropdown', - layout: 'half', - options: ['GET', 'POST', 'PUT', 'DELETE'] - }, - { - title: 'Headers', - type: 'code', - layout: 'full' - }, - { - title: 'Body', - type: 'code', - layout: 'full' - } - ] - } - } - }; - - const serialized = serializer.serializeWorkflow([mockHttpBlock], []); - const serializedBlock = serialized.blocks[0]; - - // Check block properties - expect(serializedBlock.id).toBe('http-1'); - expect(serializedBlock.type).toBe('api'); - expect(serializedBlock.position).toEqual({ x: 150, y: 250 }); - - // Check config - expect(serializedBlock.config.inputs).toEqual({ - headers: 'object', - body: 'object' - }); - expect(serializedBlock.config.outputs).toEqual({ - response: 'object', - status: 'number' - }); - - // Check extracted values from subBlocks - expect(serializedBlock.config.url).toBe(''); - expect(serializedBlock.config.method).toBe('GET'); - expect(serializedBlock.config.headers).toBe(''); - expect(serializedBlock.config.body).toBe(''); - }); - - test('should serialize a conditional block correctly', () => { - const mockConditionBlock: Node = { - id: 'condition-1', - type: 'custom', - position: { x: 200, y: 300 }, - data: { - type: 'conditional' as BlockType, - toolbar: { - title: 'Condition', - description: 'Branch based on condition', - bgColor: '#00B8D9', - icon: ConditionalIcon, - category: 'basic', - }, - workflow: { - inputs: { - value: 'any' - }, - outputs: { - result: 'boolean' - }, - subBlocks: [ - { - title: 'Condition', - type: 'code', - layout: 'full', - placeholder: 'Enter condition' - }, - { - title: 'Operator', - type: 'dropdown', - layout: 'half', - options: ['equals', 'contains', 'greater than', 'less than'] - }, - { - title: 'Value', - type: 'short-input', - layout: 'half', - placeholder: 'Compare value' - } - ] - } - } - }; - - const serialized = serializer.serializeWorkflow([mockConditionBlock], []); - const serializedBlock = serialized.blocks[0]; - - // Check block properties - expect(serializedBlock.id).toBe('condition-1'); - expect(serializedBlock.type).toBe('conditional'); - expect(serializedBlock.position).toEqual({ x: 200, y: 300 }); - - // Check config - expect(serializedBlock.config.inputs).toEqual({ - value: 'any' - }); - expect(serializedBlock.config.outputs).toEqual({ - result: 'boolean' - }); - - // Check extracted values from subBlocks - expect(serializedBlock.config.condition).toBe(''); - expect(serializedBlock.config.operator).toBe('equals'); - expect(serializedBlock.config.value).toBe(''); - }); - - test('should serialize connections correctly', () => { - const mockConnections = [ - { - id: 'conn-1', - source: 'agent-1', - target: 'agent-2', - sourceHandle: 'output', - targetHandle: 'input' - } - ]; - - const serialized = serializer.serializeWorkflow([], mockConnections); - - expect(serialized.connections).toHaveLength(1); - const conn = serialized.connections[0]; - expect(conn.source).toBe('agent-1'); - expect(conn.target).toBe('agent-2'); - expect(conn.sourceHandle).toBe('output'); - expect(conn.targetHandle).toBe('input'); - }); - - test('should deserialize back to ReactFlow format', () => { - const mockWorkflow = { - version: '1.0', - blocks: [{ - id: 'agent-1', - type: 'agent', - position: { x: 100, y: 200 }, - config: { - system_prompt: 'You are a helpful assistant', - model: 'GPT-4o', - inputs: { prompt: 'string' }, - outputs: { response: 'string' } - } - }], - connections: [{ - source: 'agent-1', - target: 'agent-2', - sourceHandle: 'output', - targetHandle: 'input' - }] - }; - - const deserialized = serializer.deserializeWorkflow(mockWorkflow); - - // Check blocks - expect(deserialized.blocks).toHaveLength(1); - const block = deserialized.blocks[0]; - expect(block.id).toBe('agent-1'); - expect(block.position).toEqual({ x: 100, y: 200 }); - expect(block.data.type).toBe('agent'); - - // Check connections - expect(deserialized.connections).toHaveLength(1); - const conn = deserialized.connections[0]; - expect(conn.source).toBe('agent-1'); - expect(conn.target).toBe('agent-2'); - expect(conn.sourceHandle).toBe('output'); - expect(conn.targetHandle).toBe('input'); - }); - - test('should handle empty workflow', () => { - const serialized = serializer.serializeWorkflow([], []); - expect(serialized.blocks).toHaveLength(0); - expect(serialized.connections).toHaveLength(0); - - const deserialized = serializer.deserializeWorkflow(serialized); - expect(deserialized.blocks).toHaveLength(0); - expect(deserialized.connections).toHaveLength(0); - }); - - test('should handle a complex workflow with multiple block types', () => { - const mockWorkflow: SerializedWorkflow = { - version: '1.0', - blocks: [ + describe('serializeWorkflow', () => { + it('should serialize a workflow with model and http blocks', () => { + const blocks: Node[] = [ { - id: 'agent-1', - type: 'agent', + id: 'model-1', + type: 'custom', position: { x: 100, y: 100 }, - config: { - system_prompt: 'Analyze this', - model: 'GPT-4o', - inputs: { prompt: 'string' }, - outputs: { response: 'string' } + data: { + tool: 'model', + params: { + model: 'gpt-4o', + systemPrompt: 'You are helpful', + temperature: 0.7 + }, + interface: { + inputs: { + prompt: 'string' + }, + outputs: { + response: 'string', + tokens: 'number' + } + }, + title: 'GPT-4o Agent', + description: 'Language model block', + category: 'AI', + icon: 'brain', + color: '#7F2FFF' } }, { id: 'http-1', - type: 'api', - position: { x: 300, y: 100 }, - config: { - url: 'https://api.example.com', - method: 'GET', - inputs: { headers: 'string', body: 'string' }, - outputs: { response: 'string', status: 'string' } - } - }, - { - id: 'condition-1', - type: 'conditional', - position: { x: 200, y: 300 }, - config: { - condition: 'value > 0', - operator: 'greater than', - value: '', - inputs: { value: 'string' }, - outputs: { result: 'string' } + type: 'custom', + position: { x: 400, y: 100 }, + data: { + tool: 'http', + params: { + url: 'https://api.example.com', + method: 'GET' + }, + interface: { + inputs: { + body: 'object' + }, + outputs: { + data: 'object', + status: 'number' + } + }, + title: 'API Call', + description: 'HTTP request block', + category: 'Web', + icon: 'globe', + color: '#00FF00' } } - ], - connections: [ + ]; + + const connections: Edge[] = [ { - source: 'agent-1', + id: 'conn-1', + source: 'model-1', target: 'http-1', sourceHandle: 'response', - targetHandle: 'headers' + targetHandle: 'body' + } + ]; + + const serialized = serializer.serializeWorkflow(blocks, connections); + + // Test workflow structure + expect(serialized.version).toBe('1.0'); + expect(serialized.blocks).toHaveLength(2); + expect(serialized.connections).toHaveLength(1); + + // Test model block serialization + const modelBlock = serialized.blocks.find(b => b.id === 'model-1'); + expect(modelBlock).toBeDefined(); + expect(modelBlock?.config.tool).toBe('model'); + expect(modelBlock?.config.params).toEqual({ + model: 'gpt-4o', + systemPrompt: 'You are helpful', + temperature: 0.7 + }); + expect(modelBlock?.metadata).toEqual({ + title: 'GPT-4o Agent', + description: 'Language model block', + category: 'AI', + icon: 'brain', + color: '#7F2FFF' + }); + + // Test http block serialization + const httpBlock = serialized.blocks.find(b => b.id === 'http-1'); + expect(httpBlock).toBeDefined(); + expect(httpBlock?.config.tool).toBe('http'); + expect(httpBlock?.config.params).toEqual({ + url: 'https://api.example.com', + method: 'GET' + }); + + // Test connection serialization + const connection = serialized.connections[0]; + expect(connection).toEqual({ + source: 'model-1', + target: 'http-1', + sourceHandle: 'response', + targetHandle: 'body' + }); + }); + + it('should handle blocks with minimal required configuration', () => { + const blocks: Node[] = [{ + id: 'minimal-1', + type: 'custom', + position: { x: 0, y: 0 }, + data: { + tool: 'model', + params: { + model: 'gpt-4' + }, + interface: { + inputs: {}, + outputs: {} + } + } + }]; + + const serialized = serializer.serializeWorkflow(blocks, []); + const block = serialized.blocks[0]; + + expect(block.id).toBe('minimal-1'); + expect(block.config.tool).toBe('model'); + expect(block.config.params).toEqual({ model: 'gpt-4' }); + expect(block.metadata).toBeUndefined(); + }); + + it('should handle complex workflow with multiple interconnected blocks', () => { + const blocks: Node[] = [ + { + id: 'input-1', + type: 'custom', + position: { x: 100, y: 100 }, + data: { + tool: 'http', + params: { + url: 'https://api.data.com', + method: 'GET' + }, + interface: { + inputs: {}, + outputs: { + data: 'object' + } + } + } }, { - source: 'http-1', - target: 'condition-1', - sourceHandle: 'response', - targetHandle: 'value' + id: 'process-1', + type: 'custom', + position: { x: 300, y: 100 }, + data: { + tool: 'model', + params: { + model: 'gpt-4', + systemPrompt: 'Process this data' + }, + interface: { + inputs: { + data: 'object', + config: 'object' + }, + outputs: { + result: 'string' + } + } + } + }, + { + id: 'output-1', + type: 'custom', + position: { x: 500, y: 100 }, + data: { + tool: 'http', + params: { + url: 'https://api.output.com', + method: 'POST' + }, + interface: { + inputs: { + body: 'string' + }, + outputs: { + status: 'number' + } + } + } } - ] - }; + ]; - const deserialized = serializer.deserializeWorkflow(mockWorkflow); - - // Check blocks - expect(deserialized.blocks).toHaveLength(3); - expect(deserialized.blocks.map(b => b.data.type)).toEqual(['agent', 'api', 'conditional']); - - // Check connections maintain workflow logic - expect(deserialized.connections).toHaveLength(2); - expect(deserialized.connections[0].source).toBe('agent-1'); - expect(deserialized.connections[0].target).toBe('http-1'); - expect(deserialized.connections[1].source).toBe('http-1'); - expect(deserialized.connections[1].target).toBe('condition-1'); + const connections: Edge[] = [ + { + id: 'conn-1', + source: 'input-1', + target: 'process-1', + sourceHandle: 'data', + targetHandle: 'data' + }, + { + id: 'conn-2', + source: 'process-1', + target: 'output-1', + sourceHandle: 'result', + targetHandle: 'body' + } + ]; + + const serialized = serializer.serializeWorkflow(blocks, connections); + + // Verify workflow structure + expect(serialized.blocks).toHaveLength(3); + expect(serialized.connections).toHaveLength(2); + + // Verify data flow chain + const conn1 = serialized.connections[0]; + const conn2 = serialized.connections[1]; + expect(conn1.source).toBe('input-1'); + expect(conn1.target).toBe('process-1'); + expect(conn2.source).toBe('process-1'); + expect(conn2.target).toBe('output-1'); + + // Verify interface matching + const process = serialized.blocks.find(b => b.id === 'process-1'); + expect(process?.config.interface.inputs).toHaveProperty('data'); + expect(process?.config.interface.outputs).toHaveProperty('result'); + }); + + it('should preserve tool-specific parameters', () => { + const blocks: Node[] = [{ + id: 'model-1', + type: 'custom', + position: { x: 0, y: 0 }, + data: { + tool: 'model', + params: { + model: 'gpt-4', + temperature: 0.7, + maxTokens: 1000, + topP: 0.9, + frequencyPenalty: 0.5, + presencePenalty: 0.5, + stop: ['###'] + }, + interface: { + inputs: { prompt: 'string' }, + outputs: { response: 'string' } + } + } + }]; + + const serialized = serializer.serializeWorkflow(blocks, []); + const block = serialized.blocks[0]; + + expect(block.config.params).toEqual({ + model: 'gpt-4', + temperature: 0.7, + maxTokens: 1000, + topP: 0.9, + frequencyPenalty: 0.5, + presencePenalty: 0.5, + stop: ['###'] + }); + }); }); -}); \ No newline at end of file + + describe('deserializeWorkflow', () => { + it('should deserialize a workflow back to ReactFlow format', () => { + const serialized: SerializedWorkflow = { + version: '1.0', + blocks: [ + { + id: 'model-1', + position: { x: 100, y: 100 }, + config: { + tool: 'model', + params: { + model: 'gpt-4o', + systemPrompt: 'You are helpful' + }, + interface: { + inputs: { prompt: 'string' }, + outputs: { response: 'string' } + } + }, + metadata: { + title: 'GPT-4o Agent', + category: 'AI' + } + } + ], + connections: [ + { + source: 'model-1', + target: 'http-1', + sourceHandle: 'response', + targetHandle: 'body' + } + ] + }; + + const deserialized = serializer.deserializeWorkflow(serialized); + + // Test blocks deserialization + expect(deserialized.blocks).toHaveLength(1); + const block = deserialized.blocks[0]; + expect(block.id).toBe('model-1'); + expect(block.type).toBe('custom'); + expect(block.position).toEqual({ x: 100, y: 100 }); + expect(block.data).toEqual({ + tool: 'model', + params: { + model: 'gpt-4o', + systemPrompt: 'You are helpful' + }, + interface: { + inputs: { prompt: 'string' }, + outputs: { response: 'string' } + }, + title: 'GPT-4o Agent', + category: 'AI' + }); + + // Test connections deserialization + expect(deserialized.connections).toHaveLength(1); + const connection = deserialized.connections[0]; + expect(connection).toEqual({ + id: 'model-1-http-1', + source: 'model-1', + target: 'http-1', + sourceHandle: 'response', + targetHandle: 'body' + }); + }); + + it('should handle empty workflow', () => { + const serialized: SerializedWorkflow = { + version: '1.0', + blocks: [], + connections: [] + }; + + const deserialized = serializer.deserializeWorkflow(serialized); + expect(deserialized.blocks).toHaveLength(0); + expect(deserialized.connections).toHaveLength(0); + }); + + it('should handle blocks with complex interface types', () => { + const serialized: SerializedWorkflow = { + version: '1.0', + blocks: [{ + id: 'complex-1', + position: { x: 0, y: 0 }, + config: { + tool: 'model', + params: { + model: 'gpt-4' + }, + interface: { + inputs: { + context: 'object', + options: 'array', + metadata: 'Record', + callback: 'function' + }, + outputs: { + result: 'object', + errors: 'array', + logs: 'string[]' + } + } + } + }], + connections: [] + }; + + const deserialized = serializer.deserializeWorkflow(serialized); + const block = deserialized.blocks[0]; + + expect(block.data.interface.inputs).toEqual({ + context: 'object', + options: 'array', + metadata: 'Record', + callback: 'function' + }); + expect(block.data.interface.outputs).toEqual({ + result: 'object', + errors: 'array', + logs: 'string[]' + }); + }); + + it('should handle circular connections', () => { + const serialized: SerializedWorkflow = { + version: '1.0', + blocks: [ + { + id: 'loop-1', + position: { x: 0, y: 0 }, + config: { + tool: 'model', + params: {}, + interface: { + inputs: { input: 'string' }, + outputs: { output: 'string' } + } + } + }, + { + id: 'loop-2', + position: { x: 200, y: 0 }, + config: { + tool: 'model', + params: {}, + interface: { + inputs: { input: 'string' }, + outputs: { output: 'string' } + } + } + } + ], + connections: [ + { + source: 'loop-1', + target: 'loop-2', + sourceHandle: 'output', + targetHandle: 'input' + }, + { + source: 'loop-2', + target: 'loop-1', + sourceHandle: 'output', + targetHandle: 'input' + } + ] + }; + + const deserialized = serializer.deserializeWorkflow(serialized); + + expect(deserialized.connections).toHaveLength(2); + expect(deserialized.connections[0].source).toBe('loop-1'); + expect(deserialized.connections[0].target).toBe('loop-2'); + expect(deserialized.connections[1].source).toBe('loop-2'); + expect(deserialized.connections[1].target).toBe('loop-1'); + }); + }); +}); \ No newline at end of file diff --git a/serializer/index.ts b/serializer/index.ts index f49594229b..37cdb82f9e 100644 --- a/serializer/index.ts +++ b/serializer/index.ts @@ -1,72 +1,56 @@ -import { BlockConfig, SubBlockConfig, BlockType } from "@/blocks/types/block"; -import { Connection, Node } from "reactflow"; +import { Node, Edge } from "reactflow"; import { SerializedBlock, SerializedConnection, SerializedWorkflow } from "./types"; export class Serializer { - serializeWorkflow(blocks: Node[], connections: Connection[]): SerializedWorkflow { + serializeWorkflow(blocks: Node[], connections: Edge[]): SerializedWorkflow { return { version: '1.0', blocks: blocks.map(block => this.serializeBlock(block)), connections: connections.map(conn => ({ - source: conn.source || '', - target: conn.target || '', + source: conn.source, + target: conn.target, sourceHandle: conn.sourceHandle || undefined, targetHandle: conn.targetHandle || undefined })) }; } - private serializeBlock(block: Node): SerializedBlock { - const values = this.extractSubBlockValues(block.data); - - return { + private serializeBlock(block: Node): SerializedBlock { + const { data } = block; + const serialized: SerializedBlock = { id: block.id, - type: block.data.type, position: { x: block.position.x, y: block.position.y }, config: { - ...values, - inputs: block.data.workflow.inputs, - outputs: block.data.workflow.outputs + tool: data.tool, + params: data.params || {}, + interface: { + inputs: data.interface?.inputs || {}, + outputs: data.interface?.outputs || {} + } } }; - } - private extractSubBlockValues(block: BlockConfig): Record { - return block.workflow.subBlocks.reduce((acc, subBlock: SubBlockConfig) => { - const key = subBlock.title.toLowerCase().replace(/\s/g, '_'); - let value: any; + const metadata = { + title: data.title, + description: data.description, + category: data.category, + icon: data.icon, + color: data.color + }; - switch (subBlock.type) { - case 'long-input': - case 'short-input': - value = ''; - break; - case 'dropdown': - value = subBlock.options?.[0] || ''; - break; - case 'slider': - value = subBlock.min || 0; - break; - case 'code': - value = ''; - break; - default: - value = null; - } + if (Object.values(metadata).some(value => value !== undefined)) { + serialized.metadata = metadata; + } - return { - ...acc, - [key]: value - }; - }, {}); + return serialized; } deserializeWorkflow(serialized: SerializedWorkflow): { - blocks: Node>[]; - connections: Partial[]; + blocks: Node[]; + connections: Edge[]; } { return { blocks: serialized.blocks.map(block => this.deserializeBlock(block)), @@ -80,18 +64,16 @@ export class Serializer { }; } - private deserializeBlock(serialized: SerializedBlock): Node> { + private deserializeBlock(serialized: SerializedBlock): Node { return { id: serialized.id, - type: 'custom', // or whatever node type you use in ReactFlow + type: 'custom', position: serialized.position, data: { - type: serialized.type as BlockType, - workflow: { - inputs: serialized.config.inputs, - outputs: serialized.config.outputs, - subBlocks: [] - } + tool: serialized.config.tool, + params: serialized.config.params, + interface: serialized.config.interface, + ...(serialized.metadata || {}) } }; } diff --git a/serializer/types.ts b/serializer/types.ts index 38718dcfa9..09f2de4e24 100644 --- a/serializer/types.ts +++ b/serializer/types.ts @@ -20,12 +20,25 @@ export interface Position { export interface SerializedBlock { id: string; - type: string; position: Position; config: { - inputs: Record; - outputs: Record; - [key: string]: any; + // The tool this block uses + tool: string; + // Tool-specific parameters + params: Record; + // Block's input/output interface + interface: { + inputs: Record; + outputs: Record; + }; + }; + // UI-specific metadata (optional) + metadata?: { + title?: string; + description?: string; + category?: string; + icon?: string; + color?: string; }; } diff --git a/tools/http-service/index.ts b/tools/http-service/index.ts index 683c80b88e..245e3adbde 100644 --- a/tools/http-service/index.ts +++ b/tools/http-service/index.ts @@ -3,7 +3,7 @@ import { HttpRequestConfig, HttpResponse, HttpError } from './types/http'; export class HttpService { private static instance: HttpService; - private constructor() {} + constructor() {} public static getInstance(): HttpService { if (!HttpService.instance) { diff --git a/tools/model-service/index.ts b/tools/model-service/index.ts index 723ddf566c..839bdf40c6 100644 --- a/tools/model-service/index.ts +++ b/tools/model-service/index.ts @@ -1,15 +1,15 @@ -import { AgentConfig } from '@/app/w/core/types/runner'; import { ModelProvider, ModelRequestOptions, ModelResponse } from './types/model'; import { OpenAIProvider } from './providers/openai'; import { AnthropicProvider } from './providers/anthropic'; import { GoogleProvider } from './providers/google'; import { XAIProvider } from './providers/xai'; +import { AgentConfig } from './types/agent'; export class ModelService { private static instance: ModelService; private providers: Map; - private constructor() { + constructor() { this.providers = new Map(); this.initializeProviders(); } diff --git a/tools/registry.ts b/tools/registry.ts new file mode 100644 index 0000000000..8ee66ca82e --- /dev/null +++ b/tools/registry.ts @@ -0,0 +1,76 @@ +import { Tool, ToolRegistry } from '@/executor/types'; +import { ModelService } from './model-service'; +import { HttpService } from './http-service'; +import { AgentConfig } from './model-service/types/agent'; + +class ModelTool implements Tool { + name = 'model'; + private service: ModelService; + + constructor() { + this.service = ModelService.getInstance(); + } + + validateParams(params: Record): boolean | string { + const required = ['model', 'prompt']; + const missing = required.filter(param => !params[param]); + if (missing.length > 0) { + return `Missing required parameters: ${missing.join(', ')}`; + } + return true; + } + + async execute(params: Record): Promise> { + const config: AgentConfig = { + model: params.model, + systemPrompt: params.systemPrompt || 'You are a helpful assistant.', + prompt: params.prompt, + temperature: params.temperature || 0.7, + apiKey: params.apiKey || process.env.OPENAI_API_KEY || '' + }; + + const response = await this.service.callModel(config); + return { + response: response.response, + tokens: response.tokens, + model: response.model + }; + } +} + +class HttpTool implements Tool { + name = 'http'; + private service: HttpService; + + constructor() { + this.service = HttpService.getInstance(); + } + + validateParams(params: Record): boolean | string { + if (!params.url) { + return 'Missing required parameter: url'; + } + return true; + } + + async execute(params: Record): Promise> { + const response = await this.service.request({ + url: params.url, + method: params.method || 'GET', + headers: params.headers || {}, + body: params.body, + timeout: params.timeout + }); + + return { + data: response.data, + status: response.status, + headers: response.headers + }; + } +} + +export const toolRegistry: ToolRegistry = { + model: new ModelTool(), + http: new HttpTool() +}; \ No newline at end of file