Parent directory

config.ts

2915 bytes
 1import type { PiReasoningEffort, ReviewerConfig } from "./types";
 2
 3function objectValue(value: unknown): Record<string, unknown> | undefined {
 4  return value && typeof value === "object" && !Array.isArray(value) ? (value as Record<string, unknown>) : undefined;
 5}
 6
 7function stringValue(value: unknown): string | undefined {
 8  return typeof value === "string" && value.trim() ? value.trim() : undefined;
 9}
10
11function booleanValue(value: unknown, field: string, fallback: boolean): boolean {
12  if (value === undefined) return fallback;
13  if (typeof value === "boolean") return value;
14  throw new Error(`${field} must be a boolean`);
15}
16
17function reasoningEffort(value: unknown): PiReasoningEffort | undefined {
18  if (value === undefined) return undefined;
19  if (
20    value === "none" ||
21    value === "minimal" ||
22    value === "low" ||
23    value === "medium" ||
24    value === "high" ||
25    value === "xhigh" ||
26    value === "max"
27  ) {
28    return value;
29  }
30  throw new Error('reviewer.reasoningEffort must be "none", "minimal", "low", "medium", "high", "xhigh", or "max"');
31}
32
33export function parseReviewerConfig(value: unknown): ReviewerConfig {
34  const data = objectValue(value);
35  const reviewer = data && objectValue(data.reviewer);
36  if (!reviewer) {
37    return {
38      kind: "pi",
39      provider: "openai-codex",
40      model: "gpt-5.6-luna",
41      reasoningEffort: "minimal",
42    };
43  }
44  if (reviewer.kind === "none") return { kind: "none" };
45
46  const model = stringValue(reviewer.model);
47  if (!model) throw new Error("reviewer.model must be a non-empty string");
48  if (reviewer.kind === "llama.cpp") {
49    const baseUrl = stringValue(reviewer.baseUrl);
50    if (!baseUrl) throw new Error("reviewer.baseUrl must be a non-empty string");
51    return {
52      kind: "llama.cpp",
53      baseUrl,
54      model,
55      enableThinking: booleanValue(reviewer.enableThinking, "reviewer.enableThinking", false),
56    };
57  }
58  if (reviewer.kind !== "pi" && reviewer.kind !== "classifier")
59    throw new Error('reviewer.kind must be "none", "pi", "classifier", or "llama.cpp"');
60
61  const provider = stringValue(reviewer.provider);
62  if (!provider) throw new Error("reviewer.provider must be a non-empty string");
63  if (reviewer.kind === "classifier") {
64    const askThreshold = reviewer.askThreshold;
65    if (typeof askThreshold !== "number" || !Number.isFinite(askThreshold) || askThreshold < 0 || askThreshold > 1)
66      throw new Error("reviewer.askThreshold must be a finite number between 0 and 1");
67    return { kind: "classifier", provider, model, askThreshold };
68  }
69  const promptCacheKey = stringValue(reviewer.promptCacheKey);
70  if (reviewer.promptCacheKey !== undefined && !promptCacheKey) {
71    throw new Error("reviewer.promptCacheKey must be a non-empty string");
72  }
73  return {
74    kind: "pi",
75    provider,
76    model,
77    reasoningEffort: reasoningEffort(reviewer.reasoningEffort),
78    promptCacheKey,
79  };
80}