import { XllmError } from "../core/errors"; import type { XChatWithToolsOptions, XClientOptions, XMessage, XRequest, XToolCall, XToolErrorStrategy, XToolExecutorMap, } from "../core/types"; import { resolveConfig } from "../runtime/config"; import { runToolCall } from "./tool-loop-shared"; export interface ToolLoopConfig { provider: string; maxRounds: number; strategy: XToolErrorStrategy; } export interface ToolLoopAccumulator { rounds: number; toolCallsExecuted: number; currentMessages: XMessage[]; } export const createToolLoopConfig = ( input: XChatWithToolsOptions, options: XClientOptions, ): ToolLoopConfig => { const resolved = resolveConfig(input, options); const maxRounds = input.maxRounds ?? 5; const strategy = input.toolErrorStrategy ?? "throw"; if (maxRounds < 1) { throw new XllmError({ code: "INVALID_REQUEST", message: "maxRounds must be >= 1", provider: resolved.provider, }); } return { provider: resolved.provider, maxRounds, strategy }; }; export const buildRequestBase = ( input: XChatWithToolsOptions, stream: boolean, ): Omit => ({ tools: input.tools, toolChoice: input.toolChoice, stream, temperature: input.temperature, topP: input.topP, maxTokens: input.maxTokens, metadata: input.metadata, provider: input.provider, model: input.model, apiKey: input.apiKey, baseURL: input.baseURL, providerExtras: input.providerExtras, }); export const buildAssistantMessage = ( text: string, toolCalls: XToolCall[], reasoning?: string, ): XMessage => ({ role: "assistant", content: text, toolCalls, ...(reasoning ? { reasoningContent: reasoning } : {}), }); export const executeToolCalls = async ( provider: string, toolCalls: XToolCall[], executors: XToolExecutorMap, strategy: XToolErrorStrategy, ): Promise<{ toolMessages: XMessage[]; toolCallsExecuted: number }> => { const toolMessages: XMessage[] = []; let toolCallsExecuted = 0; for (const toolCall of toolCalls) { const toolRunResult = await runToolCall(provider, toolCall, executors, strategy); if (!toolRunResult.handled || !toolRunResult.message) { continue; } toolMessages.push(toolRunResult.message); toolCallsExecuted += 1; } return { toolMessages, toolCallsExecuted }; }; export function throwIfMaxRoundsExceeded(config: ToolLoopConfig): never { throw new XllmError({ code: "PROVIDER_ERROR", message: `Tool loop exceeded maxRounds=${config.maxRounds}`, provider: config.provider, }); }