UNPKG

@just-every/ensemble

Version:

LLM provider abstraction layer with unified streaming interface

132 lines 5.45 kB
import { v4 as uuidv4 } from 'uuid'; import { runningToolTracker } from './running_tool_tracker.js'; import { runSequential } from './sequential_queue.js'; import { coerceValue } from './tool_parameter_utils.js'; import { FUNCTION_TIMEOUT_MS, EXCLUDED_FROM_TIMEOUT_FUNCTIONS, STATUS_TRACKING_TOOLS, } from '../config/tool_execution.js'; export function timeoutPromise(ms) { return new Promise(resolve => { setTimeout(() => resolve('TIMEOUT'), ms); }); } export function agentHasStatusTracking(agent) { if (!agent.tools) return false; return agent.tools.some(tool => STATUS_TRACKING_TOOLS.has(tool.definition.function.name)); } export async function executeToolWithLifecycle(toolCall, tool, agent, signal) { const fnId = toolCall.id || uuidv4(); const toolName = toolCall.function.name; const argsString = toolCall.function.arguments || '{}'; let args; try { args = prepareToolArguments(argsString, tool); } catch (error) { throw new Error(`Invalid JSON in tool arguments: ${error}`); } const runningTool = runningToolTracker.addRunningTool(fnId, toolName, agent.agent_id || 'unknown', argsString); const abortSignal = signal || runningTool.abortController?.signal; try { if (tool.injectAgentId) { args.unshift(agent.agent_id || 'ensemble'); } if (tool.injectAbortSignal && abortSignal) { args.push(abortSignal); } const result = await tool.function(...args); await runningToolTracker.completeRunningTool(fnId, String(result), agent); return String(result); } catch (error) { await runningToolTracker.failRunningTool(fnId, String(error), agent); throw error; } } export async function handleToolCall(toolCall, tool, agent) { const fnId = toolCall.id || uuidv4(); const toolName = toolCall.function.name; const executeFunction = async () => { if (toolName === 'wait_for_running_tool') { return executeToolWithLifecycle(toolCall, tool, agent); } return Promise.race([ executeToolWithLifecycle(toolCall, tool, agent), new Promise((_, reject) => { const runningTool = runningToolTracker.getRunningTool(fnId); if (runningTool?.abortController?.signal) { runningTool.abortController.signal.addEventListener('abort', () => reject(new Error('Operation was aborted')), { once: true }); } }), ]); }; const sequential = !!agent.modelSettings?.sequential_tools; const hasStatusTools = agentHasStatusTracking(agent); const excludedFromTimeout = EXCLUDED_FROM_TIMEOUT_FUNCTIONS.has(toolName); const shouldTimeout = !excludedFromTimeout && hasStatusTools && !sequential; const execute = sequential ? () => runSequential(agent.agent_id || 'unknown', executeFunction) : executeFunction; if (!shouldTimeout) { return execute(); } const result = await Promise.race([ execute().catch(error => { throw error; }), timeoutPromise(FUNCTION_TIMEOUT_MS), ]); if (result === 'TIMEOUT') { runningToolTracker.markTimedOut(fnId); return `Tool ${toolName} is running in the background (RunningTool: ${fnId}).`; } return result; } export function prepareToolArguments(argsString, tool) { let args; try { if (!argsString || argsString.trim() === '') { args = {}; } else { args = JSON.parse(argsString); } } catch (error) { throw new Error(`Invalid JSON in tool arguments: ${error}`); } if (typeof args === 'object' && args !== null && !Array.isArray(args)) { const paramNames = Object.keys(tool.definition.function.parameters.properties); Object.keys(args).forEach(key => { if (!paramNames.includes(key)) { console.warn(`Removing unknown parameter "${key}" for tool "${tool.definition.function.name}"`); delete args[key]; } }); if (paramNames.length > 0) { return paramNames.map(param => { const value = args[param]; const paramSpec = tool.definition.function.parameters.properties[param]; if ((value === undefined || value === '') && !tool.definition.function.parameters.required?.includes(param)) { return undefined; } const [coercedValue, error] = coerceValue(value, paramSpec, param); if (error && tool.definition.function.parameters.required?.includes(param)) { throw new Error(JSON.stringify({ error: { param, expected: paramSpec.type + (paramSpec.items?.type ? `<${paramSpec.items.type}>` : ''), received: String(value), message: error, }, })); } else if (error) { console.warn(`Parameter coercion warning for ${param}: ${error}`); } return coercedValue; }); } return Object.values(args); } return [args]; } //# sourceMappingURL=tool_execution_manager.js.map