import type { ChatCompletionRequest, ChatCompletionResult, ChatMessage, LlmClient } from "./types"; type OpenAiMessage = { role: string; content: ChatMessage["content"]; tool_calls?: Array<{ id: string; type: "function"; function: { name: string; arguments: string }; }>; tool_call_id?: string; name?: string; }; export function createOpenAiCompatibleClient(options: { baseUrl: string; apiKey: string | null; model: string; }): LlmClient { const completionsUrl = `${options.baseUrl.replace(/\/$/, "")}/chat/completions`; return { async chatCompletion(request: ChatCompletionRequest): Promise { const headers: Record = { "Content-Type": "application/json", }; if (options.apiKey) { headers.Authorization = `Bearer ${options.apiKey}`; } const body = { model: options.model, messages: request.messages as OpenAiMessage[], tools: request.tools, tool_choice: request.tools?.length ? "auto" : undefined, }; const response = await fetch(completionsUrl, { method: "POST", headers, body: JSON.stringify(body), }); if (!response.ok) { const detail = await response.text(); throw new Error(`LLM request failed (${response.status}): ${detail.slice(0, 400)}`); } const payload = (await response.json()) as { choices?: Array<{ finish_reason?: string | null; message?: OpenAiMessage; }>; }; const choice = payload.choices?.[0]; const message = choice?.message; if (!message) { throw new Error("LLM response missing message"); } return { message: { role: message.role as ChatCompletionResult["message"]["role"], content: message.content, tool_calls: message.tool_calls, tool_call_id: message.tool_call_id, name: message.name, }, finishReason: choice.finish_reason ?? null, }; }, }; }