@just-every/ensemble
Version:
LLM provider abstraction layer with unified streaming interface
132 lines • 5.45 kB
JavaScript
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