diff --git a/blocks/blocks/agent.ts b/blocks/blocks/agent.ts index c175fac450..7325756324 100644 --- a/blocks/blocks/agent.ts +++ b/blocks/blocks/agent.ts @@ -11,7 +11,7 @@ const MODEL_TOOLS = { 'claude-3-5-sonnet-20241022': 'anthropic.chat', 'gemini-pro': 'google.chat', 'grok-2-latest': 'xai.chat' -} as const; +} as const export const AgentBlock: BlockConfig = { type: 'agent', @@ -26,19 +26,19 @@ export const AgentBlock: BlockConfig = { access: ['openai.chat', 'anthropic.chat', 'google.chat', 'xai.chat', 'deepseek.chat', 'deepseek.reasoner'], config: { tool: (params: Record) => { - const model = params.model || 'gpt-4o'; + const model = params.model || 'gpt-4o' if (!model) { - throw new Error('No model selected'); + throw new Error('No model selected') } - const tool = MODEL_TOOLS[model as keyof typeof MODEL_TOOLS]; + const tool = MODEL_TOOLS[model as keyof typeof MODEL_TOOLS] if (!tool) { - throw new Error(`Invalid model selected: ${model}`); + throw new Error(`Invalid model selected: ${model}`) } - return tool; + return tool } } }, diff --git a/executor/__tests__/executor.test.ts b/executor/__tests__/executor.test.ts index 6d76887e48..c77ac0dfe8 100644 --- a/executor/__tests__/executor.test.ts +++ b/executor/__tests__/executor.test.ts @@ -1,7 +1,7 @@ -import { Executor } from '../index'; -import { SerializedWorkflow } from '@/serializer/types'; -import { Tool } from '../types'; -import { tools } from '@/tools'; +import { Executor } from '../index' +import { SerializedWorkflow } from '@/serializer/types' +import { Tool } from '../types' +import { tools } from '@/tools' // Mock tools const createMockTool = ( @@ -39,17 +39,17 @@ const createMockTool = ( }, transformResponse: () => mockResponse, transformError: () => mockError || 'Mock error' -}); +}) jest.mock('@/tools', () => ({ tools: {} -})); +})) describe('Executor', () => { beforeEach(() => { // Reset tools mock - (tools as any) = {}; - }); + (tools as any) = {} + }) describe('Tool Execution', () => { it('should execute a simple workflow with one tool', async () => { @@ -58,7 +58,7 @@ describe('Executor', () => { 'Test Tool', { result: 'test processed' } ); - (tools as any)['test-tool'] = mockTool; + (tools as any)['test-tool'] = mockTool const workflow: SerializedWorkflow = { version: '1.0', @@ -75,7 +75,7 @@ describe('Executor', () => { } }], connections: [] - }; + } // Mock fetch global.fetch = jest.fn().mockImplementation(() => @@ -83,13 +83,13 @@ describe('Executor', () => { ok: true, json: () => Promise.resolve({ result: 'test processed' }) }) - ); + ) - const executor = new Executor(workflow); - const result = await executor.execute('workflow-1'); + const executor = new Executor(workflow) + const result = await executor.execute('workflow-1') - expect(result.success).toBe(true); - expect(result.data).toEqual({ result: 'test processed' }); + expect(result.success).toBe(true) + expect(result.data).toEqual({ result: 'test processed' }) expect(global.fetch).toHaveBeenCalledWith( 'https://api.test.com/endpoint', expect.objectContaining({ @@ -100,8 +100,8 @@ describe('Executor', () => { }, body: JSON.stringify({ input: 'test' }) }) - ); - }); + ) + }) it('should validate required parameters', async () => { const mockTool = createMockTool( @@ -109,7 +109,7 @@ describe('Executor', () => { 'Test Tool', { result: 'test processed' } ); - (tools as any)['test-tool'] = mockTool; + (tools as any)['test-tool'] = mockTool const workflow: SerializedWorkflow = { version: '1.0', @@ -126,14 +126,14 @@ describe('Executor', () => { } }], connections: [] - }; + } - const executor = new Executor(workflow); - const result = await executor.execute('workflow-1'); + const executor = new Executor(workflow) + const result = await executor.execute('workflow-1') - expect(result.success).toBe(false); - expect(result.error).toContain('Missing required parameter'); - }); + expect(result.success).toBe(false) + expect(result.error).toContain('Missing required parameter') + }) it('should handle tool execution errors', async () => { const mockTool = createMockTool( @@ -142,7 +142,7 @@ describe('Executor', () => { {}, 'API Error' ); - (tools as any)['test-tool'] = mockTool; + (tools as any)['test-tool'] = mockTool const workflow: SerializedWorkflow = { version: '1.0', @@ -159,7 +159,7 @@ describe('Executor', () => { } }], connections: [] - }; + } // Mock fetch to fail global.fetch = jest.fn().mockImplementation(() => @@ -167,15 +167,15 @@ describe('Executor', () => { ok: false, json: () => Promise.resolve({ error: 'API Error' }) }) - ); + ) - const executor = new Executor(workflow); - const result = await executor.execute('workflow-1'); + const executor = new Executor(workflow) + const result = await executor.execute('workflow-1') - expect(result.success).toBe(false); - expect(result.error).toContain('API Error'); - }); - }); + expect(result.success).toBe(false) + expect(result.error).toContain('API Error') + }) + }) describe('Interface Validation', () => { it('should validate input types', async () => { @@ -184,7 +184,7 @@ describe('Executor', () => { 'Test Tool', { result: 123 } ); - (tools as any)['test-tool'] = mockTool; + (tools as any)['test-tool'] = mockTool const workflow: SerializedWorkflow = { version: '1.0', @@ -201,14 +201,14 @@ describe('Executor', () => { } }], connections: [] - }; + } - const executor = new Executor(workflow); - const result = await executor.execute('workflow-1'); + const executor = new Executor(workflow) + const result = await executor.execute('workflow-1') - expect(result.success).toBe(false); - expect(result.error).toContain('Invalid type for input'); - }); + expect(result.success).toBe(false) + expect(result.error).toContain('Invalid type for input') + }) it('should validate tool output against interface', async () => { const mockTool = createMockTool( @@ -216,7 +216,7 @@ describe('Executor', () => { 'Test Tool', { wrongField: 'wrong type' } ); - (tools as any)['test-tool'] = mockTool; + (tools as any)['test-tool'] = mockTool const workflow: SerializedWorkflow = { version: '1.0', @@ -233,7 +233,7 @@ describe('Executor', () => { } }], connections: [] - }; + } // Mock fetch global.fetch = jest.fn().mockImplementation(() => @@ -241,15 +241,15 @@ describe('Executor', () => { ok: true, json: () => Promise.resolve({ wrongField: 'wrong type' }) }) - ); + ) - const executor = new Executor(workflow); - const result = await executor.execute('workflow-1'); + const executor = new Executor(workflow) + const result = await executor.execute('workflow-1') - expect(result.success).toBe(false); - expect(result.error).toContain('Tool output missing required field'); - }); - }); + expect(result.success).toBe(false) + expect(result.error).toContain('Tool output missing required field') + }) + }) describe('Complex Workflows', () => { it('should execute blocks in correct order and pass data between them', async () => { @@ -257,14 +257,14 @@ describe('Executor', () => { 'tool-1', 'Tool 1', { output: 'test data' } - ); + ) const mockTool2 = createMockTool( 'tool-2', 'Tool 2', { result: 'processed data' } ); (tools as any)['tool-1'] = mockTool1; - (tools as any)['tool-2'] = mockTool2; + (tools as any)['tool-2'] = mockTool2 const workflow: SerializedWorkflow = { version: '1.0', @@ -302,7 +302,7 @@ describe('Executor', () => { targetHandle: 'input' } ] - }; + } // Mock fetch for both tools global.fetch = jest.fn() @@ -317,15 +317,15 @@ describe('Executor', () => { ok: true, json: () => Promise.resolve({ result: 'processed data' }) }) - ); + ) - const executor = new Executor(workflow); - const result = await executor.execute('workflow-1'); + const executor = new Executor(workflow) + const result = await executor.execute('workflow-1') - expect(result.success).toBe(true); - expect(result.data).toEqual({ result: 'processed data' }); - expect(global.fetch).toHaveBeenCalledTimes(2); - }); + expect(result.success).toBe(true) + expect(result.data).toEqual({ result: 'processed data' }) + expect(global.fetch).toHaveBeenCalledTimes(2) + }) it('should handle cycles in workflow', async () => { const workflow: SerializedWorkflow = { @@ -370,13 +370,13 @@ describe('Executor', () => { targetHandle: 'input' } ] - }; + } - const executor = new Executor(workflow); - const result = await executor.execute('workflow-1'); + const executor = new Executor(workflow) + const result = await executor.execute('workflow-1') - expect(result.success).toBe(false); - expect(result.error).toContain('Workflow contains cycles'); - }); - }); -}); + expect(result.success).toBe(false) + expect(result.error).toContain('Workflow contains cycles') + }) + }) +}) diff --git a/executor/index.ts b/executor/index.ts index 2c07e52922..f4524d322e 100644 --- a/executor/index.ts +++ b/executor/index.ts @@ -1,13 +1,13 @@ -import { SerializedWorkflow, SerializedBlock } from '@/serializer/types'; -import { ExecutionContext, ExecutionResult, Tool } from './types'; -import { tools } from '@/tools'; -import { BlockState } from '@/stores/workflow/types'; +import { SerializedWorkflow, SerializedBlock } from '@/serializer/types' +import { ExecutionContext, ExecutionResult, Tool } from './types' +import { tools } from '@/tools' +import { BlockState } from '@/stores/workflow/types' export class Executor { - private workflow: SerializedWorkflow; + private workflow: SerializedWorkflow constructor(workflow: SerializedWorkflow) { - this.workflow = workflow; + this.workflow = workflow } private async executeBlock( @@ -15,54 +15,54 @@ export class Executor { inputs: Record, context: ExecutionContext ): Promise> { - const config = block.config; - const toolId = config.tool; + const config = block.config + const toolId = config.tool if (!toolId) { - throw new Error(`Block ${block.id} does not specify a tool`); + throw new Error(`Block ${block.id} does not specify a tool`) } - const tool = tools[toolId]; + const tool = tools[toolId] if (!tool) { - throw new Error(`Tool not found: ${toolId}`); + throw new Error(`Tool not found: ${toolId}`) } // Validate interface compatibility - this.validateInterface(block, inputs); + this.validateInterface(block, inputs) // Merge block parameters with runtime inputs const params = { ...config.params, ...inputs - }; + } // Validate tool parameters - this.validateToolParams(tool, params); + this.validateToolParams(tool, params) try { // Make the HTTP request const url = typeof tool.request.url === 'function' ? tool.request.url(params) - : tool.request.url; + : tool.request.url const response = await fetch(url, { method: tool.request.method, headers: tool.request.headers(params), body: tool.request.body ? JSON.stringify(tool.request.body(params)) : undefined - }); + }) if (!response.ok) { - const error = await response.json().catch(() => ({ message: response.statusText })); - throw new Error(tool.transformError(error)); + const error = await response.json().catch(() => ({ message: response.statusText })) + throw new Error(tool.transformError(error)) } - const result = await tool.transformResponse(response); + const result = await tool.transformResponse(response) // Validate the output matches the interface - this.validateToolOutput(block, result); - return result; + this.validateToolOutput(block, result) + return result } catch (error) { - throw new Error(`Tool ${toolId} execution failed: ${error instanceof Error ? error.message : 'Unknown error'}`); + throw new Error(`Tool ${toolId} execution failed: ${error instanceof Error ? error.message : 'Unknown error'}`) } } @@ -70,37 +70,37 @@ export class Executor { // Check required parameters for (const [paramName, paramConfig] of Object.entries(tool.params)) { if (paramConfig.required && !(paramName in params)) { - throw new Error(`Missing required parameter '${paramName}' for tool ${tool.id}`); + throw new Error(`Missing required parameter '${paramName}' for tool ${tool.id}`) } } } private validateInterface(block: SerializedBlock, inputs: Record): void { - const { interface: blockInterface } = block.config; + const { interface: blockInterface } = block.config // Check if all required inputs are provided for (const [inputName, inputType] of Object.entries(blockInterface.inputs)) { if (!(inputName in inputs)) { - throw new Error(`Missing required input '${inputName}' of type '${inputType}' for block ${block.id}`); + throw new Error(`Missing required input '${inputName}' of type '${inputType}' for block ${block.id}`) } // Basic type validation (can be enhanced for more complex types) if (!this.validateType(inputs[inputName], inputType)) { - throw new Error(`Invalid type for input '${inputName}' in block ${block.id}. Expected ${inputType}`); + throw new Error(`Invalid type for input '${inputName}' in block ${block.id}. Expected ${inputType}`) } } } private validateToolOutput(block: SerializedBlock, output: Record): void { - const { interface: blockInterface } = block.config; + const { interface: blockInterface } = block.config // Check if all promised outputs are present for (const [outputName, outputType] of Object.entries(blockInterface.outputs)) { if (!(outputName in output)) { - throw new Error(`Tool output missing required field '${outputName}' of type '${outputType}' for block ${block.id}`); + throw new Error(`Tool output missing required field '${outputName}' of type '${outputType}' for block ${block.id}`) } // Basic type validation (can be enhanced for more complex types) if (!this.validateType(output[outputName], outputType)) { - throw new Error(`Invalid type for output '${outputName}' in block ${block.id}. Expected ${outputType}`); + throw new Error(`Invalid type for output '${outputName}' in block ${block.id}. Expected ${outputType}`) } } } @@ -108,126 +108,126 @@ export class Executor { private validateType(value: any, expectedType: string): boolean { switch (expectedType.toLowerCase()) { case 'string': - return typeof value === 'string'; + return typeof value === 'string' case 'number': - return typeof value === 'number'; + return typeof value === 'number' case 'boolean': - return typeof value === 'boolean'; + return typeof value === 'boolean' case 'json': try { if (typeof value === 'string') { - JSON.parse(value); + JSON.parse(value) } - return true; + return true } catch { - return false; + return false } default: // For complex types, we just do basic object/array validation - return true; + return true } } private determineExecutionOrder(): string[] { - const { blocks, connections } = this.workflow; - const order: string[] = []; - const visited = new Set(); - const inDegree = new Map(); + const { blocks, connections } = this.workflow + const order: string[] = [] + const visited = new Set() + const inDegree = new Map() - blocks.forEach(block => inDegree.set(block.id, 0)); + blocks.forEach(block => inDegree.set(block.id, 0)) connections.forEach(conn => { - const target = conn.target; - inDegree.set(target, (inDegree.get(target) || 0) + 1); - }); + const target = conn.target + inDegree.set(target, (inDegree.get(target) || 0) + 1) + }) const queue = blocks .filter(block => (inDegree.get(block.id) || 0) === 0) - .map(block => block.id); + .map(block => block.id) while (queue.length > 0) { - const blockId = queue.shift()!; - if (visited.has(blockId)) continue; + const blockId = queue.shift()! + if (visited.has(blockId)) continue - visited.add(blockId); - order.push(blockId); + visited.add(blockId) + order.push(blockId) connections .filter(conn => conn.source === blockId) .forEach(conn => { - const targetId = conn.target; - inDegree.set(targetId, (inDegree.get(targetId) || 0) - 1); + const targetId = conn.target + inDegree.set(targetId, (inDegree.get(targetId) || 0) - 1) if (inDegree.get(targetId) === 0) { - queue.push(targetId); + queue.push(targetId) } - }); + }) } if (order.length !== blocks.length) { - throw new Error('Workflow contains cycles'); + throw new Error('Workflow contains cycles') } - return order; + return order } private resolveInputs( block: SerializedBlock, context: ExecutionContext ): Record { - const inputs: Record = {}; + const inputs: Record = {} // Get all incoming connections for this block const incomingConnections = this.workflow.connections.filter( conn => conn.target === block.id - ); + ) // Map outputs from previous blocks to inputs for this block incomingConnections.forEach(conn => { - const sourceOutput = context.blockStates.get(conn.source); + const sourceOutput = context.blockStates.get(conn.source) if (sourceOutput && conn.sourceHandle && conn.targetHandle) { - inputs[conn.targetHandle] = sourceOutput[conn.sourceHandle]; + inputs[conn.targetHandle] = sourceOutput[conn.sourceHandle] } - }); + }) // If this is a start block with no inputs, use the block's params if (Object.keys(inputs).length === 0) { - const targetBlock = this.workflow.blocks.find(b => b.id === block.id); + const targetBlock = this.workflow.blocks.find(b => b.id === block.id) if (targetBlock) { - return targetBlock.config.params; + return targetBlock.config.params } } - return inputs; + return inputs } async execute(workflowId: string): Promise { - const startTime = new Date(); + const startTime = new Date() const context: ExecutionContext = { workflowId, blockStates: new Map(), metadata: { startTime: startTime.toISOString() } - }; + } try { - const executionOrder = this.determineExecutionOrder(); + const executionOrder = this.determineExecutionOrder() for (const blockId of executionOrder) { - const block = this.workflow.blocks.find(b => b.id === blockId); + const block = this.workflow.blocks.find(b => b.id === blockId) if (!block) { - throw new Error(`Block ${blockId} not found in workflow`); + throw new Error(`Block ${blockId} not found in workflow`) } - const blockInputs = this.resolveInputs(block, context); - const result = await this.executeBlock(block, blockInputs, context); - context.blockStates.set(blockId, result); + const blockInputs = this.resolveInputs(block, context) + const result = await this.executeBlock(block, blockInputs, context) + context.blockStates.set(blockId, result) } - const lastBlockId = executionOrder[executionOrder.length - 1]; - const finalOutput = context.blockStates.get(lastBlockId); + const lastBlockId = executionOrder[executionOrder.length - 1] + const finalOutput = context.blockStates.get(lastBlockId) - const endTime = new Date(); + const endTime = new Date() return { success: true, data: finalOutput, @@ -236,9 +236,9 @@ export class Executor { startTime: startTime.toISOString(), endTime: endTime.toISOString() } - }; + } } catch (error) { - const endTime = new Date(); + const endTime = new Date() return { success: false, data: {}, @@ -248,7 +248,7 @@ export class Executor { startTime: startTime.toISOString(), endTime: endTime.toISOString() } - }; + } } } } diff --git a/executor/types.ts b/executor/types.ts index 34c23b5b0b..c6acb6343a 100644 --- a/executor/types.ts +++ b/executor/types.ts @@ -1,44 +1,44 @@ export interface Tool

{ - id: string; - name: string; - description: string; - version: string; + id: string + name: string + description: string + version: string params: { [key: string]: { - type: string; - required?: boolean; - description?: string; - default?: any; - }; - }; + type: string + required?: boolean + description?: string + default?: any + } + } request: { - url: string | ((params: P) => string); - method: string; - headers: (params: P) => Record; - body?: (params: P) => Record; - }; - transformResponse: (response: any) => R; - transformError: (error: any) => string; + url: string | ((params: P) => string) + method: string + headers: (params: P) => Record + body?: (params: P) => Record + } + transformResponse: (response: any) => R + transformError: (error: any) => string } export interface ToolRegistry { - [key: string]: Tool; + [key: string]: Tool } export interface ExecutionContext { - workflowId: string; - blockStates: Map; - input?: Record; - metadata?: Record; + workflowId: string + blockStates: Map + input?: Record + metadata?: Record } export interface ExecutionResult { - success: boolean; - data: Record; - error?: string; + success: boolean + data: Record + error?: string metadata?: { - duration: number; - startTime: string; - endTime: string; - }; + duration: number + startTime: string + endTime: string + } } \ No newline at end of file diff --git a/serializer/__tests__/serializer.test.ts b/serializer/__tests__/serializer.test.ts index 72ef39242d..bfb0d8c8f0 100644 --- a/serializer/__tests__/serializer.test.ts +++ b/serializer/__tests__/serializer.test.ts @@ -1,15 +1,15 @@ -import { Edge } from 'reactflow'; -import { Serializer } from '../index'; -import { SerializedWorkflow } from '../types'; -import { BlockState } from '@/stores/workflow/types'; -import { OutputType } from '@/blocks/types'; +import { Edge } from 'reactflow' +import { Serializer } from '../index' +import { SerializedWorkflow } from '../types' +import { BlockState } from '@/stores/workflow/types' +import { OutputType } from '@/blocks/types' // Mock icons jest.mock('@/components/icons', () => ({ AgentIcon: () => 'AgentIcon', ApiIcon: () => 'ApiIcon', CodeIcon: () => 'CodeIcon', -})); +})) // Mock blocks jest.mock('@/blocks', () => ({ @@ -35,7 +35,7 @@ jest.mock('@/blocks', () => ({ }, subBlocks: [] } - }; + } } // Default agent block config return { @@ -60,24 +60,24 @@ jest.mock('@/blocks', () => ({ }, subBlocks: [] } - }; + } }, getBlockTypeForTool: (toolId: string) => { const toolToType: Record = { 'openai.chat': 'agent', 'http.request': 'api', 'function': 'function' - }; - return toolToType[toolId]; + } + return toolToType[toolId] } -})); +})) describe('Serializer', () => { - let serializer: Serializer; + let serializer: Serializer beforeEach(() => { - serializer = new Serializer(); - }); + serializer = new Serializer() + }) describe('serializeWorkflow', () => { it('should serialize a workflow with agent and http blocks', () => { @@ -125,7 +125,7 @@ describe('Serializer', () => { }, outputType: 'json' } - }; + } const connections: Edge[] = [ { @@ -135,34 +135,34 @@ describe('Serializer', () => { sourceHandle: 'response', targetHandle: 'body' } - ]; + ] - const serialized = serializer.serializeWorkflow(blocks, connections); + 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); + expect(serialized.version).toBe('1.0') + expect(serialized.blocks).toHaveLength(2) + expect(serialized.connections).toHaveLength(1) // Test agent block serialization - const agentBlock = serialized.blocks.find(b => b.id === 'agent-1'); - expect(agentBlock).toBeDefined(); - expect(agentBlock?.config.tool).toBe('openai.chat'); + const agentBlock = serialized.blocks.find(b => b.id === 'agent-1') + expect(agentBlock).toBeDefined() + expect(agentBlock?.config.tool).toBe('openai.chat') expect(agentBlock?.config.params).toEqual({ model: 'gpt-4o', systemPrompt: 'You are helpful', temperature: 0.7 - }); + }) // Test http block serialization - const httpBlock = serialized.blocks.find(b => b.id === 'http-1'); - expect(httpBlock).toBeDefined(); - expect(httpBlock?.config.tool).toBe('http.request'); + const httpBlock = serialized.blocks.find(b => b.id === 'http-1') + expect(httpBlock).toBeDefined() + expect(httpBlock?.config.tool).toBe('http.request') expect(httpBlock?.config.params).toEqual({ url: 'https://api.example.com', method: 'GET' - }); - }); + }) + }) it('should handle blocks with minimal required configuration', () => { const blocks: Record = { @@ -180,15 +180,15 @@ describe('Serializer', () => { }, outputType: 'string' } - }; + } - const serialized = serializer.serializeWorkflow(blocks, []); - const block = serialized.blocks[0]; + const serialized = serializer.serializeWorkflow(blocks, []) + const block = serialized.blocks[0] - expect(block.id).toBe('minimal-1'); - expect(block.config.tool).toBe('openai.chat'); - expect(block.config.params).toEqual({ model: 'gpt-4o' }); - }); + expect(block.id).toBe('minimal-1') + expect(block.config.tool).toBe('openai.chat') + expect(block.config.params).toEqual({ model: 'gpt-4o' }) + }) it('should handle complex workflow with multiple interconnected blocks', () => { const blocks: Record = { @@ -249,7 +249,7 @@ describe('Serializer', () => { }, outputType: 'json' } - }; + } const connections: Edge[] = [ { @@ -266,22 +266,22 @@ describe('Serializer', () => { sourceHandle: 'result', targetHandle: 'body' } - ]; + ] - const serialized = serializer.serializeWorkflow(blocks, connections); + const serialized = serializer.serializeWorkflow(blocks, connections) // Verify workflow structure - expect(serialized.blocks).toHaveLength(3); - expect(serialized.connections).toHaveLength(2); + 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'); - }); + 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') + }) it('should preserve tool-specific parameters', () => { const blocks: Record = { @@ -309,19 +309,19 @@ describe('Serializer', () => { }, outputType: 'string' } - }; + } - const serialized = serializer.serializeWorkflow(blocks, []); - const block = serialized.blocks[0]; + const serialized = serializer.serializeWorkflow(blocks, []) + const block = serialized.blocks[0] - expect(block.config.tool).toBe('openai.chat'); + expect(block.config.tool).toBe('openai.chat') expect(block.config.params).toEqual({ model: 'gpt-4o', temperature: 0.7, maxTokens: 1000 - }); - }); - }); + }) + }) + }) describe('deserializeWorkflow', () => { it('should deserialize a workflow back to blocks and connections', () => { @@ -345,15 +345,15 @@ describe('Serializer', () => { } ], connections: [] - }; + } - const { blocks } = serializer.deserializeWorkflow(workflow); - const block = blocks['agent-1']; + const { blocks } = serializer.deserializeWorkflow(workflow) + const block = blocks['agent-1'] - expect(block.type).toBe('agent'); - expect(block.subBlocks.model.value).toBe('gpt-4o'); - expect(block.subBlocks.systemPrompt.value).toBe('You are helpful'); - expect(block.outputType).toBe('string'); - }); - }); -}); \ No newline at end of file + expect(block.type).toBe('agent') + expect(block.subBlocks.model.value).toBe('gpt-4o') + expect(block.subBlocks.systemPrompt.value).toBe('You are helpful') + expect(block.outputType).toBe('string') + }) + }) +}) \ No newline at end of file diff --git a/serializer/index.ts b/serializer/index.ts index d5792f3ffd..f1db28af86 100644 --- a/serializer/index.ts +++ b/serializer/index.ts @@ -1,8 +1,8 @@ -import { BlockState, SubBlockState } from '@/stores/workflow/types'; -import { Edge } from 'reactflow'; -import { SerializedBlock, SerializedConnection, SerializedWorkflow } from './types'; -import { getBlock, getBlockTypeForTool } from '@/blocks'; -import { OutputType, SubBlockType } from '@/blocks/types'; +import { BlockState, SubBlockState } from '@/stores/workflow/types' +import { Edge } from 'reactflow' +import { SerializedBlock, SerializedConnection, SerializedWorkflow } from './types' +import { getBlock, getBlockTypeForTool } from '@/blocks' +import { OutputType, SubBlockType } from '@/blocks/types' export class Serializer { serializeWorkflow(blocks: Record, connections: Edge[]): SerializedWorkflow { @@ -15,33 +15,33 @@ export class Serializer { sourceHandle: conn.sourceHandle || undefined, targetHandle: conn.targetHandle || undefined })) - }; + } } private serializeBlock(block: BlockState): SerializedBlock { - const blockConfig = getBlock(block.type); + const blockConfig = getBlock(block.type) if (!blockConfig) { - throw new Error(`Block configuration not found for type: ${block.type}`); + throw new Error(`Block configuration not found for type: ${block.type}`) } // Get the tool ID from the block's configuration - const tools = blockConfig.tools; + const tools = blockConfig.tools if (!tools?.access || tools.access.length === 0) { - throw new Error(`No tools specified for block type: ${block.type}`); + throw new Error(`No tools specified for block type: ${block.type}`) } // Get all values from subBlocks - const params: Record = {}; + const params: Record = {} Object.entries(block.subBlocks || {}).forEach(([id, subBlock]) => { if (subBlock?.value !== undefined) { - params[id] = subBlock.value; + params[id] = subBlock.value } - }); + }) // Get the tool ID from the block's configuration - const toolId = tools.config?.tool?.(params) || params.tool || tools.access[0]; + const toolId = tools.config?.tool?.(params) || params.tool || tools.access[0] if (!toolId || !tools.access.includes(toolId)) { - throw new Error(`Invalid or unauthorized tool: ${toolId}`); + throw new Error(`Invalid or unauthorized tool: ${toolId}`) } return { @@ -57,18 +57,18 @@ export class Serializer { } } } - }; + } } deserializeWorkflow(serialized: SerializedWorkflow): { - blocks: Record; - connections: Edge[]; + blocks: Record + connections: Edge[] } { - const blocks: Record = {}; + const blocks: Record = {} serialized.blocks.forEach(block => { - const deserialized = this.deserializeBlock(block); - blocks[deserialized.id] = deserialized; - }); + const deserialized = this.deserializeBlock(block) + blocks[deserialized.id] = deserialized + }) return { blocks, @@ -79,20 +79,20 @@ export class Serializer { sourceHandle: conn.sourceHandle || null, targetHandle: conn.targetHandle || null })) - }; + } } private deserializeBlock(serialized: SerializedBlock): BlockState { - const toolId = serialized.config.tool; - const blockType = getBlockTypeForTool(toolId); + const toolId = serialized.config.tool + const blockType = getBlockTypeForTool(toolId) if (!blockType) { - throw new Error(`Could not determine block type for tool: ${toolId}`); + throw new Error(`Could not determine block type for tool: ${toolId}`) } - const blockConfig = getBlock(blockType); + const blockConfig = getBlock(blockType) if (!blockConfig) { - throw new Error(`Block configuration not found for type: ${blockType}`); + throw new Error(`Block configuration not found for type: ${blockType}`) } return { @@ -101,15 +101,15 @@ export class Serializer { name: `${blockType} Block`, position: serialized.position, subBlocks: Object.entries(serialized.config.params).reduce((acc, [key, value]) => { - const subBlock = blockConfig.workflow.subBlocks?.find(sb => sb.id === key); + const subBlock = blockConfig.workflow.subBlocks?.find(sb => sb.id === key) acc[key] = { id: key, type: subBlock?.type || 'short-input', value: value - }; - return acc; + } + return acc }, {} as Record), outputType: serialized.config.interface.outputs.output as OutputType - }; + } } } \ No newline at end of file diff --git a/serializer/types.ts b/serializer/types.ts index b6ba51b83e..8499126b9b 100644 --- a/serializer/types.ts +++ b/serializer/types.ts @@ -1,39 +1,39 @@ export interface SerializedWorkflow { - version: string; - blocks: SerializedBlock[]; - connections: SerializedConnection[]; + version: string + blocks: SerializedBlock[] + connections: SerializedConnection[] } export interface SerializedConnection { - source: string; - target: string; - sourceHandle?: string; - targetHandle?: string; + source: string + target: string + sourceHandle?: string + targetHandle?: string } export interface Position { - x: number; - y: number; + x: number + y: number } export interface BlockConfig { - tool: string; - params: Record; + tool: string + params: Record interface: { - inputs: Record; - outputs: Record; - }; + inputs: Record + outputs: Record + } } export interface SerializedBlock { - id: string; - position: Position; - config: BlockConfig; + id: string + position: Position + config: BlockConfig metadata?: { - title?: string; - description?: string; - category?: string; - icon?: string; - color?: string; - }; + title?: string + description?: string + category?: string + icon?: string + color?: string + } } diff --git a/tools/anthropic/chat.ts b/tools/anthropic/chat.ts index 6ca18fef86..0e1fd5f9ac 100644 --- a/tools/anthropic/chat.ts +++ b/tools/anthropic/chat.ts @@ -1,19 +1,19 @@ -import { ToolConfig, ToolResponse } from '../types'; +import { ToolConfig, ToolResponse } from '../types' interface ChatParams { - apiKey: string; - systemPrompt: string; - context?: string; - model?: string; - temperature?: number; - maxTokens?: number; - topP?: number; - stream?: boolean; + apiKey: string + systemPrompt: string + context?: string + model?: string + temperature?: number + maxTokens?: number + topP?: number + stream?: boolean } interface ChatResponse extends ToolResponse { - tokens?: number; - model: string; + tokens?: number + model: string } export const chatTool: ToolConfig = { @@ -60,10 +60,10 @@ export const chatTool: ToolConfig = { body: (params) => { const messages = [ { role: 'user', content: params.systemPrompt } - ]; + ] if (params.context) { - messages.push({ role: 'user', content: params.context }); + messages.push({ role: 'user', content: params.context }) } const body = { @@ -73,23 +73,23 @@ export const chatTool: ToolConfig = { max_tokens: params.maxTokens, top_p: params.topP, stream: params.stream - }; - return body; + } + return body } }, transformResponse: async (response: Response) => { - const data = await response.json(); + const data = await response.json() return { output: data.completion, tokens: data.usage?.total_tokens, model: data.model - }; + } }, transformError: (error) => { - const message = error.error?.message || error.message; - const code = error.error?.type || error.code; - return `${message} (${code})`; + const message = error.error?.message || error.message + const code = error.error?.type || error.code + return `${message} (${code})` } -}; \ No newline at end of file +} \ No newline at end of file diff --git a/tools/function/execute.ts b/tools/function/execute.ts index 022cc9eff3..37054f92c9 100644 --- a/tools/function/execute.ts +++ b/tools/function/execute.ts @@ -33,7 +33,7 @@ export const functionExecuteTool: ToolConfig { const codeContent = Array.isArray(params.code) ? params.code.map(c => c.content).join('\n') - : params.code; + : params.code return { language: 'js', @@ -48,38 +48,38 @@ export const functionExecuteTool: ToolConfig { - const result = await response.json(); + const result = await response.json() if (!response.ok) { - throw new Error(result.message || 'Execution failed'); + throw new Error(result.message || 'Execution failed') } if (result.run?.stderr) { - throw new Error(result.run.stderr); + throw new Error(result.run.stderr) } - const stdout = result.run?.stdout || ''; + const stdout = result.run?.stdout || '' try { // Try parsing the output as JSON - const parsed = JSON.parse(stdout); - return { output: parsed }; + const parsed = JSON.parse(stdout) + return { output: parsed } } catch { // If not JSON, wrap it in a JSON object return { output: { result: stdout } - }; + } } }, transformError: (error: any) => { - return error.message || 'Code execution failed'; + return error.message || 'Code execution failed' }, } \ No newline at end of file diff --git a/tools/google/chat.ts b/tools/google/chat.ts index d3637d0d2e..80502a0a7a 100644 --- a/tools/google/chat.ts +++ b/tools/google/chat.ts @@ -1,20 +1,20 @@ -import { ToolConfig, ToolResponse } from '../types'; +import { ToolConfig, ToolResponse } from '../types' interface ChatParams { - apiKey: string; - systemPrompt: string; - context?: string; - model?: string; - temperature?: number; - maxTokens?: number; - topP?: number; - topK?: number; + apiKey: string + systemPrompt: string + context?: string + model?: string + temperature?: number + maxTokens?: number + topP?: number + topK?: number } interface ChatResponse extends ToolResponse { - tokens?: number; - model: string; - safetyRatings?: any[]; + tokens?: number + model: string + safetyRatings?: any[] } export const chatTool: ToolConfig = { @@ -63,13 +63,13 @@ export const chatTool: ToolConfig = { role: 'model', parts: [{ text: params.systemPrompt }] } - ]; + ] if (params.context) { contents.push({ role: 'user', parts: [{ text: params.context }] - }); + }) } const body = { @@ -80,24 +80,24 @@ export const chatTool: ToolConfig = { topP: params.topP, topK: params.topK } - }; - return body; + } + return body } }, transformResponse: async (response: Response) => { - const data = await response.json(); + const data = await response.json() return { output: data.candidates[0].content.parts[0].text, tokens: data.usage?.totalTokens, model: data.model, safetyRatings: data.candidates[0].safetyRatings - }; + } }, transformError: (error) => { - const message = error.error?.message || error.message; - const code = error.error?.status || error.code; - return `${message} (${code})`; + const message = error.error?.message || error.message + const code = error.error?.status || error.code + return `${message} (${code})` } -}; \ No newline at end of file +} \ No newline at end of file diff --git a/tools/http/request.ts b/tools/http/request.ts index ebff615bcd..07d33491a6 100644 --- a/tools/http/request.ts +++ b/tools/http/request.ts @@ -1,20 +1,20 @@ -import { ToolConfig, HttpMethod, ToolResponse } from '../types'; +import { ToolConfig, HttpMethod, ToolResponse } from '../types' interface RequestParams { - url: string; - method?: HttpMethod; - headers?: Record; - body?: any; - queryParams?: Record; - pathParams?: Record; - formData?: Record; - timeout?: number; - validateStatus?: (status: number) => boolean; + url: string + method?: HttpMethod + headers?: Record + body?: any + queryParams?: Record + pathParams?: Record + formData?: Record + timeout?: number + validateStatus?: (status: number) => boolean } interface RequestResponse extends ToolResponse { - status: number; - headers: Record; + status: number + headers: Record } export const requestTool: ToolConfig = { @@ -67,83 +67,83 @@ export const requestTool: ToolConfig = { request: { url: (params: RequestParams) => { - let url = params.url; + let url = params.url // Replace path parameters if (params.pathParams) { Object.entries(params.pathParams).forEach(([key, value]) => { - url = url.replace(`:${key}`, encodeURIComponent(value)); - }); + url = url.replace(`:${key}`, encodeURIComponent(value)) + }) } // Append query parameters if (params.queryParams) { const queryString = Object.entries(params.queryParams) .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`) - .join('&'); - url += (url.includes('?') ? '&' : '?') + queryString; + .join('&') + url += (url.includes('?') ? '&' : '?') + queryString } - return url; + return url }, method: 'POST' as HttpMethod, headers: (params: RequestParams) => { const headers: Record = { ...params.headers - }; + } // Set appropriate Content-Type if (params.formData) { // Don't set Content-Type for FormData, browser will set it with boundary - return headers; + return headers } else if (params.body) { - headers['Content-Type'] = 'application/json'; + headers['Content-Type'] = 'application/json' } - return headers; + return headers }, body: (params: RequestParams) => { if (params.formData) { - const formData = new FormData(); + const formData = new FormData() Object.entries(params.formData).forEach(([key, value]) => { - formData.append(key, value); - }); - return formData; + formData.append(key, value) + }) + return formData } if (params.body) { - return params.body; + return params.body } - return undefined; + return undefined } }, transformResponse: async (response: Response) => { // Convert Headers to a plain object - const headers: Record = {}; + const headers: Record = {} response.headers.forEach((value, key) => { - headers[key] = value; - }); + headers[key] = value + }) // Parse response based on content type const data = await (response.headers.get('content-type')?.includes('application/json') ? response.json() - : response.text()); + : response.text()) return { output: data, status: response.status, headers - }; + } }, transformError: (error) => { - const message = error.message || error.error?.message; - const code = error.status || error.error?.status; + const message = error.message || error.error?.message + const code = error.status || error.error?.status const details = error.response?.data ? `\nDetails: ${JSON.stringify(error.response.data)}` - : ''; - return `${message} (${code})${details}`; + : '' + return `${message} (${code})${details}` } -}; \ No newline at end of file +} \ No newline at end of file diff --git a/tools/hubspot/contacts.ts b/tools/hubspot/contacts.ts index d75f8ce23e..6897c91032 100644 --- a/tools/hubspot/contacts.ts +++ b/tools/hubspot/contacts.ts @@ -1,26 +1,26 @@ -import { ToolConfig, ToolResponse } from '../types'; +import { ToolConfig, ToolResponse } from '../types' interface ContactsParams { - apiKey: string; - action: 'create' | 'update' | 'search' | 'delete'; - id?: string; - email?: string; - firstName?: string; - lastName?: string; - phone?: string; - company?: string; - properties?: Record; - limit?: number; - after?: string; - data: Record; + apiKey: string + action: 'create' | 'update' | 'search' | 'delete' + id?: string + email?: string + firstName?: string + lastName?: string + phone?: string + company?: string + properties?: Record + limit?: number + after?: string + data: Record } interface ContactsResponse extends ToolResponse { - totalResults?: number; + totalResults?: number pagination?: { - hasMore: boolean; - offset: number; - }; + hasMore: boolean + offset: number + } } export const contactsTool: ToolConfig = { @@ -77,11 +77,11 @@ export const contactsTool: ToolConfig = { request: { url: (params) => { - const baseUrl = 'https://api.hubapi.com/crm/v3/objects/contacts'; + const baseUrl = 'https://api.hubapi.com/crm/v3/objects/contacts' if (params.id) { - return `${baseUrl}/${params.id}`; + return `${baseUrl}/${params.id}` } - return baseUrl; + return baseUrl }, method: 'POST', headers: (params) => ({ @@ -96,11 +96,11 @@ export const contactsTool: ToolConfig = { ...(params.phone && { phone: params.phone }), ...(params.company && { company: params.company }), ...params.properties - }; + } if (params.id) { // Update existing contact - return { properties }; + return { properties } } // Create new contact or search @@ -108,22 +108,22 @@ export const contactsTool: ToolConfig = { properties, ...(params.limit && { limit: params.limit }), ...(params.after && { after: params.after }) - }; + } } }, transformResponse: async (response: Response) => { - const data = await response.json(); + const data = await response.json() return { output: data.results || data, totalResults: data.total, pagination: data.paging - }; + } }, transformError: (error) => { - const message = error.message || error.error?.message; - const code = error.status || error.error?.status; - return `${message} (${code})`; + const message = error.message || error.error?.message + const code = error.status || error.error?.status + return `${message} (${code})` } -}; \ No newline at end of file +} \ No newline at end of file diff --git a/tools/index.ts b/tools/index.ts index 7456420ea4..944c3ad5f8 100644 --- a/tools/index.ts +++ b/tools/index.ts @@ -1,14 +1,14 @@ -import { ToolConfig } from './types'; -import { chatTool as openaiChat } from './openai/chat'; -import { chatTool as anthropicChat } from './anthropic/chat'; -import { chatTool as googleChat } from './google/chat'; -import { chatTool as xaiChat } from './xai/chat'; -import { chatTool as deepseekChat } from './deepseek/chat'; -import { reasonerTool as deepseekReasoner } from './deepseek/reasoner'; -import { requestTool as httpRequest } from './http/request'; -import { contactsTool as hubspotContacts } from './hubspot/contacts'; -import { opportunitiesTool as salesforceOpportunities } from './salesforce/opportunities'; -import { functionExecuteTool as functionExecute } from './function/execute'; +import { ToolConfig } from './types' +import { chatTool as openaiChat } from './openai/chat' +import { chatTool as anthropicChat } from './anthropic/chat' +import { chatTool as googleChat } from './google/chat' +import { chatTool as xaiChat } from './xai/chat' +import { chatTool as deepseekChat } from './deepseek/chat' +import { reasonerTool as deepseekReasoner } from './deepseek/reasoner' +import { requestTool as httpRequest } from './http/request' +import { contactsTool as hubspotContacts } from './hubspot/contacts' +import { opportunitiesTool as salesforceOpportunities } from './salesforce/opportunities' +import { functionExecuteTool as functionExecute } from './function/execute' // Registry of all available tools export const tools: Record = { @@ -26,11 +26,11 @@ export const tools: Record = { 'salesforce.opportunities': salesforceOpportunities, // Function Tools 'function.execute': functionExecute -}; +} // Get a tool by its ID export function getTool(toolId: string): ToolConfig | undefined { - return tools[toolId]; + return tools[toolId] } // Execute a tool with parameters @@ -38,33 +38,33 @@ export async function executeTool( toolId: string, params: Record ): Promise { - const tool = getTool(toolId); + const tool = getTool(toolId) if (!tool) { - throw new Error(`Tool not found: ${toolId}`); + throw new Error(`Tool not found: ${toolId}`) } try { // Get the URL (which might be a function or string) const url = typeof tool.request.url === 'function' ? tool.request.url(params) - : tool.request.url; + : tool.request.url // Make the HTTP request const response = await fetch(url, { method: tool.request.method, headers: tool.request.headers(params), body: tool.request.body ? JSON.stringify(tool.request.body(params)) : undefined - }); + }) if (!response.ok) { - const error = await response.json(); - throw new Error(tool.transformError(error)); + const error = await response.json() + throw new Error(tool.transformError(error)) } - const data = await response.json(); - return tool.transformResponse(data); + const data = await response.json() + return tool.transformResponse(data) } catch (error) { - throw new Error(tool.transformError(error)); + throw new Error(tool.transformError(error)) } } \ No newline at end of file diff --git a/tools/openai/chat.ts b/tools/openai/chat.ts index 75a7bc3b1d..ac5a08b1f6 100644 --- a/tools/openai/chat.ts +++ b/tools/openai/chat.ts @@ -1,23 +1,23 @@ -import { ToolConfig, ToolResponse } from '../types'; +import { ToolConfig, ToolResponse } from '../types' interface ChatParams { - apiKey: string; - systemPrompt: string; - context?: string; - model?: string; - temperature?: number; - maxTokens?: number; - maxCompletionTokens?: number; - topP?: number; - frequencyPenalty?: number; - presencePenalty?: number; - stream?: boolean; + apiKey: string + systemPrompt: string + context?: string + model?: string + temperature?: number + maxTokens?: number + maxCompletionTokens?: number + topP?: number + frequencyPenalty?: number + presencePenalty?: number + stream?: boolean } interface ChatResponse extends ToolResponse { - tokens?: number; - model: string; - reasoning_tokens?: number; + tokens?: number + model: string + reasoning_tokens?: number } export const chatTool: ToolConfig = { @@ -65,60 +65,60 @@ export const chatTool: ToolConfig = { 'Authorization': `Bearer ${params.apiKey}` }), body: (params) => { - const isO1Model = params.model?.startsWith('o1'); - const messages = []; + const isO1Model = params.model?.startsWith('o1') + const messages = [] // For o1-mini, we need to use 'user' role instead of 'system' if (params.model === 'o1-mini') { - messages.push({ role: 'user', content: params.systemPrompt }); + messages.push({ role: 'user', content: params.systemPrompt }) } else { - messages.push({ role: 'system', content: params.systemPrompt }); + messages.push({ role: 'system', content: params.systemPrompt }) } if (params.context) { - messages.push({ role: 'user', content: params.context }); + messages.push({ role: 'user', content: params.context }) } const body: any = { model: params.model || 'gpt-4o', messages - }; + } // Only add parameters supported by the model type if (!isO1Model) { - body.temperature = params.temperature; - body.max_tokens = params.maxTokens; - body.top_p = params.topP; - body.frequency_penalty = params.frequencyPenalty; - body.presence_penalty = params.presencePenalty; + body.temperature = params.temperature + body.max_tokens = params.maxTokens + body.top_p = params.topP + body.frequency_penalty = params.frequencyPenalty + body.presence_penalty = params.presencePenalty } else if (params.maxCompletionTokens) { - body.max_completion_tokens = params.maxCompletionTokens; + body.max_completion_tokens = params.maxCompletionTokens } - body.stream = params.stream; - return body; + body.stream = params.stream + return body } }, transformResponse: async (response: Response) => { - const data = await response.json(); + const data = await response.json() if (data.choices?.[0]?.delta?.content) { return { output: data.choices[0].delta.content, model: data.model - }; + } } return { output: data.choices[0].message.content, tokens: data.usage?.total_tokens, model: data.model, reasoning_tokens: data.usage?.completion_tokens_details?.reasoning_tokens - }; + } }, transformError: (error) => { - const message = error.error?.message || error.message; - const code = error.error?.type || error.code; - return `${message} (${code})`; + const message = error.error?.message || error.message + const code = error.error?.type || error.code + return `${message} (${code})` } -}; \ No newline at end of file +} \ No newline at end of file diff --git a/tools/salesforce/opportunities.ts b/tools/salesforce/opportunities.ts index d47d0dc23f..c43cab0335 100644 --- a/tools/salesforce/opportunities.ts +++ b/tools/salesforce/opportunities.ts @@ -1,27 +1,27 @@ -import { ToolConfig, ToolResponse } from '../types'; +import { ToolConfig, ToolResponse } from '../types' interface OpportunityParams { - apiKey: string; - action: 'create' | 'update' | 'search' | 'delete'; - id?: string; - name?: string; - accountId?: string; - stage?: string; - amount?: number; - closeDate?: string; - probability?: number; - properties?: Record; - limit?: number; - offset?: number; - data: Record; + apiKey: string + action: 'create' | 'update' | 'search' | 'delete' + id?: string + name?: string + accountId?: string + stage?: string + amount?: number + closeDate?: string + probability?: number + properties?: Record + limit?: number + offset?: number + data: Record } interface OpportunityResponse extends ToolResponse { - totalResults?: number; + totalResults?: number pagination?: { - hasMore: boolean; - offset: number; - }; + hasMore: boolean + offset: number + } } export const opportunitiesTool: ToolConfig = { @@ -90,11 +90,11 @@ export const opportunitiesTool: ToolConfig { - const baseUrl = `${params.apiKey}@salesforce.com/services/data/v58.0/sobjects/Opportunity`; + const baseUrl = `${params.apiKey}@salesforce.com/services/data/v58.0/sobjects/Opportunity` if (params.id) { - return `${baseUrl}/${params.id}`; + return `${baseUrl}/${params.id}` } - return baseUrl; + return baseUrl }, method: 'POST', headers: (params) => ({ @@ -110,14 +110,14 @@ export const opportunitiesTool: ToolConfig { - const data = await response.json(); + const data = await response.json() return { output: data.records || data, totalResults: data.totalSize, @@ -125,12 +125,12 @@ export const opportunitiesTool: ToolConfig { - const message = error.message || error.error?.message; - const code = error.errorCode || error.error?.errorCode; - return `${message} (${code})`; + const message = error.message || error.error?.message + const code = error.errorCode || error.error?.errorCode + return `${message} (${code})` } -}; \ No newline at end of file +} \ No newline at end of file diff --git a/tools/types.ts b/tools/types.ts index 7d7e0c1ac5..a0ec2d3b2e 100644 --- a/tools/types.ts +++ b/tools/types.ts @@ -1,34 +1,34 @@ -export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'; +export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' export interface ToolResponse { - output: any; // All tools must provide an output field - [key: string]: any; // Tools can include additional metadata + output: any // All tools must provide an output field + [key: string]: any // Tools can include additional metadata } export interface ToolConfig

{ // Basic tool identification - id: string; - name: string; - description: string; - version: string; + id: string + name: string + description: string + version: string // Parameter schema - what this tool accepts params: Record; + type: string + required?: boolean + default?: any + description?: string + }> // Request configuration request: { - url: string | ((params: P) => string); - method: string; - headers: (params: P) => Record; - body?: (params: P) => Record; - }; + url: string | ((params: P) => string) + method: string + headers: (params: P) => Record + body?: (params: P) => Record + } // Response handling - transformResponse: (response: Response) => Promise; - transformError: (error: any) => string; + transformResponse: (response: Response) => Promise + transformError: (error: any) => string } \ No newline at end of file diff --git a/tools/xai/chat.ts b/tools/xai/chat.ts index cb6927668a..de8da0d2ff 100644 --- a/tools/xai/chat.ts +++ b/tools/xai/chat.ts @@ -1,21 +1,21 @@ -import { ToolConfig, ToolResponse } from '../types'; +import { ToolConfig, ToolResponse } from '../types' interface ChatParams { - apiKey: string; - systemPrompt: string; - context?: string; - model?: string; - temperature?: number; - maxTokens?: number; - topP?: number; - frequencyPenalty?: number; - presencePenalty?: number; + apiKey: string + systemPrompt: string + context?: string + model?: string + temperature?: number + maxTokens?: number + topP?: number + frequencyPenalty?: number + presencePenalty?: number } interface ChatResponse extends ToolResponse { - tokens?: number; - model: string; - reasoning?: string; + tokens?: number + model: string + reasoning?: string } export const chatTool: ToolConfig = { @@ -61,10 +61,10 @@ export const chatTool: ToolConfig = { body: (params) => { const messages = [ { role: 'system', content: params.systemPrompt } - ]; + ] if (params.context) { - messages.push({ role: 'user', content: params.context }); + messages.push({ role: 'user', content: params.context }) } const body = { @@ -75,24 +75,24 @@ export const chatTool: ToolConfig = { top_p: params.topP, frequency_penalty: params.frequencyPenalty, presence_penalty: params.presencePenalty - }; - return body; + } + return body } }, transformResponse: async (response: Response) => { - const data = await response.json(); + const data = await response.json() return { output: data.choices[0].message.content, tokens: data.usage?.total_tokens, model: data.model, reasoning: data.choices[0]?.reasoning - }; + } }, transformError: (error) => { - const message = error.error?.message || error.message; - const code = error.error?.type || error.code; - return `${message} (${code})`; + const message = error.error?.message || error.message + const code = error.error?.type || error.code + return `${message} (${code})` } -}; \ No newline at end of file +} \ No newline at end of file