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}