import { XllmError } from "../core/errors"; import type { XChatWithToolsOptions, XChatWithToolsResult, XClientOptions, XMessage, XRequest, XToolExecutorMap, } from "../core/types"; import { generate } from "./generate"; import { runToolCall } from "./tool-loop-shared"; export const chatWithTools = async ( options: XClientOptions, input: XChatWithToolsOptions, executors: XToolExecutorMap, ): Promise => { 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: input.provider ?? options.provider ?? "openai-compatible", }); } let rounds = 0; let toolCallsExecuted = 0; let currentMessages = [...input.messages]; const requestBase: Omit = { tools: input.tools, toolChoice: input.toolChoice, stream: false, temperature: input.temperature, topP: input.topP, maxTokens: input.maxTokens, metadata: input.metadata, provider: input.provider, model: input.model, apiKey: input.apiKey, baseURL: input.baseURL, }; while (rounds < maxRounds) { rounds += 1; const response = await generate(options, { ...requestBase, messages: currentMessages, }); if (response.toolCalls.length === 0) { return { response, rounds, toolCallsExecuted }; } const assistantMessage: XMessage = { role: "assistant", content: [{ type: "text", text: response.text }], toolCalls: response.toolCalls, }; const toolMessages: XMessage[] = []; for (const toolCall of response.toolCalls) { const toolRunResult = await runToolCall(response.provider, toolCall, executors, strategy); if (!toolRunResult.handled || !toolRunResult.message) { continue; } toolMessages.push(toolRunResult.message); toolCallsExecuted += 1; } currentMessages = [...currentMessages, assistantMessage, ...toolMessages]; } throw new XllmError({ code: "PROVIDER_ERROR", message: `Tool loop exceeded maxRounds=${maxRounds}`, provider: input.provider ?? options.provider ?? "openai-compatible", }); };