Removed extraneous semicolons

This commit is contained in:
Waleed Latif
2025-01-28 10:40:38 -08:00
parent c71b6aa18f
commit f0f88dec5d
17 changed files with 548 additions and 548 deletions
+6 -6
View File
@@ -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<string, any>) => {
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
}
}
},
+67 -67
View File
@@ -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')
})
})
})
+77 -77
View File
@@ -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<string, any>,
context: ExecutionContext
): Promise<Record<string, any>> {
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<string, any>): 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<string, any>): 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<string>();
const inDegree = new Map<string, number>();
const { blocks, connections } = this.workflow
const order: string[] = []
const visited = new Set<string>()
const inDegree = new Map<string, number>()
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<string, any> {
const inputs: Record<string, any> = {};
const inputs: Record<string, any> = {}
// 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<ExecutionResult> {
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()
}
};
}
}
}
}
+29 -29
View File
@@ -1,44 +1,44 @@
export interface Tool<P = any, R = any> {
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<string, string>;
body?: (params: P) => Record<string, any>;
};
transformResponse: (response: any) => R;
transformError: (error: any) => string;
url: string | ((params: P) => string)
method: string
headers: (params: P) => Record<string, string>
body?: (params: P) => Record<string, any>
}
transformResponse: (response: any) => R
transformError: (error: any) => string
}
export interface ToolRegistry {
[key: string]: Tool;
[key: string]: Tool
}
export interface ExecutionContext {
workflowId: string;
blockStates: Map<string, any>;
input?: Record<string, any>;
metadata?: Record<string, any>;
workflowId: string
blockStates: Map<string, any>
input?: Record<string, any>
metadata?: Record<string, any>
}
export interface ExecutionResult {
success: boolean;
data: Record<string, any>;
error?: string;
success: boolean
data: Record<string, any>
error?: string
metadata?: {
duration: number;
startTime: string;
endTime: string;
};
duration: number
startTime: string
endTime: string
}
}
+65 -65
View File
@@ -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<string, string> = {
'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<string, BlockState> = {
@@ -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<string, BlockState> = {
@@ -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<string, BlockState> = {
@@ -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');
});
});
});
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')
})
})
})
+32 -32
View File
@@ -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<string, BlockState>, 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<string, any> = {};
const params: Record<string, any> = {}
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<string, BlockState>;
connections: Edge[];
blocks: Record<string, BlockState>
connections: Edge[]
} {
const blocks: Record<string, BlockState> = {};
const blocks: Record<string, BlockState> = {}
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<string, SubBlockState>),
outputType: serialized.config.interface.outputs.output as OutputType
};
}
}
}
+23 -23
View File
@@ -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<string, any>;
tool: string
params: Record<string, any>
interface: {
inputs: Record<string, string>;
outputs: Record<string, string>;
};
inputs: Record<string, string>
outputs: Record<string, string>
}
}
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
}
}
+21 -21
View File
@@ -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<ChatParams, ChatResponse> = {
@@ -60,10 +60,10 @@ export const chatTool: ToolConfig<ChatParams, ChatResponse> = {
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<ChatParams, ChatResponse> = {
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})`
}
};
}
+10 -10
View File
@@ -33,7 +33,7 @@ export const functionExecuteTool: ToolConfig<CodeExecutionInput, CodeExecutionOu
body: (params) => {
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<CodeExecutionInput, CodeExecutionOu
run_timeout: 3000,
compile_memory_limit: -1,
run_memory_limit: -1
};
}
},
},
transformResponse: async (response) => {
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'
},
}
+22 -22
View File
@@ -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<ChatParams, ChatResponse> = {
@@ -63,13 +63,13 @@ export const chatTool: ToolConfig<ChatParams, ChatResponse> = {
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<ChatParams, ChatResponse> = {
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})`
}
};
}
+38 -38
View File
@@ -1,20 +1,20 @@
import { ToolConfig, HttpMethod, ToolResponse } from '../types';
import { ToolConfig, HttpMethod, ToolResponse } from '../types'
interface RequestParams {
url: string;
method?: HttpMethod;
headers?: Record<string, string>;
body?: any;
queryParams?: Record<string, string>;
pathParams?: Record<string, string>;
formData?: Record<string, string | Blob>;
timeout?: number;
validateStatus?: (status: number) => boolean;
url: string
method?: HttpMethod
headers?: Record<string, string>
body?: any
queryParams?: Record<string, string>
pathParams?: Record<string, string>
formData?: Record<string, string | Blob>
timeout?: number
validateStatus?: (status: number) => boolean
}
interface RequestResponse extends ToolResponse {
status: number;
headers: Record<string, string>;
status: number
headers: Record<string, string>
}
export const requestTool: ToolConfig<RequestParams, RequestResponse> = {
@@ -67,83 +67,83 @@ export const requestTool: ToolConfig<RequestParams, RequestResponse> = {
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<string, string> = {
...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<string, string> = {};
const headers: Record<string, string> = {}
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}`
}
};
}
+29 -29
View File
@@ -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<string, any>;
limit?: number;
after?: string;
data: Record<string, any>;
apiKey: string
action: 'create' | 'update' | 'search' | 'delete'
id?: string
email?: string
firstName?: string
lastName?: string
phone?: string
company?: string
properties?: Record<string, any>
limit?: number
after?: string
data: Record<string, any>
}
interface ContactsResponse extends ToolResponse {
totalResults?: number;
totalResults?: number
pagination?: {
hasMore: boolean;
offset: number;
};
hasMore: boolean
offset: number
}
}
export const contactsTool: ToolConfig<ContactsParams, ContactsResponse> = {
@@ -77,11 +77,11 @@ export const contactsTool: ToolConfig<ContactsParams, ContactsResponse> = {
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<ContactsParams, ContactsResponse> = {
...(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<ContactsParams, ContactsResponse> = {
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})`
}
};
}
+22 -22
View File
@@ -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<string, ToolConfig> = {
@@ -26,11 +26,11 @@ export const tools: Record<string, ToolConfig> = {
'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<string, any>
): Promise<any> {
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))
}
}
+36 -36
View File
@@ -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<ChatParams, ChatResponse> = {
@@ -65,60 +65,60 @@ export const chatTool: ToolConfig<ChatParams, ChatResponse> = {
'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})`
}
};
}
+29 -29
View File
@@ -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<string, any>;
limit?: number;
offset?: number;
data: Record<string, any>;
apiKey: string
action: 'create' | 'update' | 'search' | 'delete'
id?: string
name?: string
accountId?: string
stage?: string
amount?: number
closeDate?: string
probability?: number
properties?: Record<string, any>
limit?: number
offset?: number
data: Record<string, any>
}
interface OpportunityResponse extends ToolResponse {
totalResults?: number;
totalResults?: number
pagination?: {
hasMore: boolean;
offset: number;
};
hasMore: boolean
offset: number
}
}
export const opportunitiesTool: ToolConfig<OpportunityParams, OpportunityResponse> = {
@@ -90,11 +90,11 @@ export const opportunitiesTool: ToolConfig<OpportunityParams, OpportunityRespons
request: {
url: (params) => {
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<OpportunityParams, OpportunityRespons
...(params.closeDate && { CloseDate: params.closeDate }),
...(params.probability && { Probability: params.probability }),
...params.properties
};
}
return fields;
return fields
}
},
transformResponse: async (response: Response) => {
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<OpportunityParams, OpportunityRespons
hasMore: !data.done,
offset: data.nextRecordsUrl ? parseInt(data.nextRecordsUrl.split('-')[1]) : 0
}
};
}
},
transformError: (error) => {
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})`
}
};
}
+19 -19
View File
@@ -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<P = any, R extends ToolResponse = ToolResponse> {
// 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<string, {
type: string;
required?: boolean;
default?: any;
description?: string;
}>;
type: string
required?: boolean
default?: any
description?: string
}>
// Request configuration
request: {
url: string | ((params: P) => string);
method: string;
headers: (params: P) => Record<string, string>;
body?: (params: P) => Record<string, any>;
};
url: string | ((params: P) => string)
method: string
headers: (params: P) => Record<string, string>
body?: (params: P) => Record<string, any>
}
// Response handling
transformResponse: (response: Response) => Promise<R>;
transformError: (error: any) => string;
transformResponse: (response: Response) => Promise<R>
transformError: (error: any) => string
}
+23 -23
View File
@@ -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<ChatParams, ChatResponse> = {
@@ -61,10 +61,10 @@ export const chatTool: ToolConfig<ChatParams, ChatResponse> = {
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<ChatParams, ChatResponse> = {
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})`
}
};
}