Parent directory

providers.ts

1643 bytes
 1import type { ChatMessage } from "./review";
 2import type { ReviewerConfig } from "./types";
 3
 4type LlamaResponse = {
 5  choices?: {
 6    message?: {
 7      content?: unknown;
 8    };
 9  }[];
10};
11
12function textValue(value: unknown): string | undefined {
13  return typeof value === "string" && value.trim() ? value : undefined;
14}
15
16function completionUrl(baseUrl: string): string {
17  const normalized = baseUrl.replace(/\/+$/, "");
18  const root = normalized.endsWith("/v1") ? normalized : `${normalized}/v1`;
19  return `${root}/chat/completions`;
20}
21
22async function responseError(response: Response): Promise<never> {
23  const detail = (await response.text()).trim().slice(0, 1000);
24  throw new Error(`llama-server API ${response.status}${detail ? `: ${detail}` : ""}`);
25}
26
27export async function callLlamaReviewer(
28  config: Extract<ReviewerConfig, { kind: "llama.cpp" }>,
29  messages: ChatMessage[],
30  maxTokens: number,
31): Promise<string> {
32  const response = await fetch(completionUrl(config.baseUrl), {
33    method: "POST",
34    headers: { "content-type": "application/json" },
35    body: JSON.stringify({
36      model: config.model,
37      max_tokens: maxTokens,
38      messages,
39      stream: false,
40      temperature: 0,
41      response_format: { type: "json_object" },
42      chat_template_kwargs: { enable_thinking: config.enableThinking },
43      reasoning_format: "deepseek",
44    }),
45  });
46  if (!response.ok) return responseError(response);
47  const data = (await response.json()) as LlamaResponse;
48  const text = textValue(data.choices?.[0]?.message?.content);
49  if (!text) throw new Error("llama-server response had no generated text");
50  return text;
51}