mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
Removed extraneous semicolons
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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})`
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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})`
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user