@unotest/judge 0.24.0 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1157 @@
1
+ // src/errors.ts
2
+ var JudgeError = class extends Error {
3
+ constructor(message, context = {}) {
4
+ super(message);
5
+ this.context = context;
6
+ this.name = new.target.name;
7
+ }
8
+ context;
9
+ };
10
+ var JudgeConfigError = class extends JudgeError {
11
+ };
12
+ var JudgeProviderError = class extends JudgeError {
13
+ };
14
+ var JudgeTransientError = class extends JudgeProviderError {
15
+ };
16
+ function judgeErrorCode(e) {
17
+ if (e instanceof JudgeConfigError) return "config";
18
+ if (e instanceof JudgeProviderError) return "provider-error";
19
+ return "internal";
20
+ }
21
+ function errorMessage(e) {
22
+ return e instanceof Error ? e.message : String(e);
23
+ }
24
+
25
+ // src/providers/fake.ts
26
+ var FAKE_MODEL_ID = "fake";
27
+ var CONSTRAINT_RE = /^must(?<not>\s+not)?\s+contain:\s*(?<needle>.+)$/i;
28
+ function parseFakeRubric(rubric) {
29
+ const constraints = [];
30
+ for (const line of rubric.split(/\r?\n/)) {
31
+ const m = CONSTRAINT_RE.exec(line.trim());
32
+ if (!m?.groups?.needle) continue;
33
+ constraints.push({
34
+ kind: m.groups.not ? "not-contains" : "contains",
35
+ needle: m.groups.needle.trim()
36
+ });
37
+ }
38
+ return constraints;
39
+ }
40
+ var FakeJudgeProvider = class {
41
+ async judgeOnce(request) {
42
+ const constraints = parseFakeRubric(request.rubric);
43
+ if (constraints.length === 0) {
44
+ return {
45
+ pass: false,
46
+ reasoning: 'fake provider: rubric has no parseable constraints \u2014 use lines like "must contain: <substring>" / "must not contain: <substring>", or switch to a real provider (UNOTEST_JUDGE_PROVIDER=vertex|claude|gemini|openai|anthropic)',
47
+ model: FAKE_MODEL_ID
48
+ };
49
+ }
50
+ const text = request.text.toLowerCase();
51
+ const violations = [];
52
+ for (const c of constraints) {
53
+ const hit = text.includes(c.needle.toLowerCase());
54
+ if (c.kind === "contains" && !hit) violations.push(`missing required "${c.needle}"`);
55
+ if (c.kind === "not-contains" && hit) violations.push(`contains forbidden "${c.needle}"`);
56
+ }
57
+ if (violations.length > 0) {
58
+ return { pass: false, reasoning: violations.join("; "), model: FAKE_MODEL_ID };
59
+ }
60
+ return {
61
+ pass: true,
62
+ reasoning: `all ${constraints.length} constraint(s) satisfied`,
63
+ model: FAKE_MODEL_ID
64
+ };
65
+ }
66
+ };
67
+
68
+ // src/verdict.ts
69
+ var VERDICT_PROMPT = (rubric, text) => `You are a strict test judge. Evaluate the TEXT against the RUBRIC.
70
+ Reply with ONLY a raw JSON object, no markdown code fences: {"verdict": "pass" | "fail", "reasoning": "<one short sentence>"}.
71
+ RUBRIC:
72
+ ${rubric}
73
+
74
+ TEXT:
75
+ ${text}`;
76
+ var FENCED_JSON = /^```(?:json)?\s*\n([\s\S]*?)\n\s*```$/;
77
+ function parseVerdictReply(text, model) {
78
+ const trimmed = text.trim();
79
+ const parsed = parseJsonish(FENCED_JSON.exec(trimmed)?.[1] ?? trimmed);
80
+ if (parsed === void 0) {
81
+ throw new JudgeProviderError(
82
+ `model "${model}" did not reply with the requested JSON verdict: ${text.slice(0, 200)}`,
83
+ { model, raw: text }
84
+ );
85
+ }
86
+ const { verdict, reasoning } = parsed;
87
+ const normalized = typeof verdict === "string" ? verdict.trim().toLowerCase() : verdict;
88
+ if (normalized !== "pass" && normalized !== "fail") {
89
+ throw new JudgeProviderError(
90
+ `model "${model}" replied with an unknown verdict ${JSON.stringify(verdict)} (expected "pass"/"fail")`,
91
+ { model, raw: text }
92
+ );
93
+ }
94
+ return {
95
+ pass: normalized === "pass",
96
+ reasoning: typeof reasoning === "string" && reasoning.length > 0 ? reasoning : "(no reasoning)",
97
+ raw: text
98
+ };
99
+ }
100
+ function parseJsonish(candidate) {
101
+ const spans = [candidate];
102
+ const start = candidate.indexOf("{");
103
+ const end = candidate.lastIndexOf("}");
104
+ if (start >= 0 && end > start) spans.push(candidate.slice(start, end + 1));
105
+ for (const span of spans) {
106
+ try {
107
+ const parsed = JSON.parse(span);
108
+ if (typeof parsed === "object" && parsed !== null) return parsed;
109
+ } catch {
110
+ }
111
+ }
112
+ return void 0;
113
+ }
114
+
115
+ // src/providers/vertex.ts
116
+ import { JUDGE_ENV } from "@unotest/protocol";
117
+
118
+ // src/providers/adc-auth.ts
119
+ async function buildAdcTokenSource() {
120
+ const specifier = "google-auth-library";
121
+ let mod;
122
+ try {
123
+ mod = await import(specifier);
124
+ } catch (e) {
125
+ throw new JudgeConfigError(
126
+ `Vertex provider could not load "google-auth-library" (a dependency of this package \u2014 a broken install?): ${e instanceof Error ? e.message : String(e)}. Workaround: pass a token via UNOTEST_JUDGE_ACCESS_TOKEN (\`gcloud auth print-access-token\`)`
127
+ );
128
+ }
129
+ const auth = new mod.GoogleAuth({ scopes: ["https://www.googleapis.com/auth/cloud-platform"] });
130
+ return async () => {
131
+ let token;
132
+ try {
133
+ token = await auth.getAccessToken();
134
+ } catch (e) {
135
+ throw describeAdcFailure(e);
136
+ }
137
+ if (!token) {
138
+ throw new JudgeConfigError(
139
+ "ADC produced no access token \u2014 run `gcloud auth application-default login` or set GOOGLE_APPLICATION_CREDENTIALS"
140
+ );
141
+ }
142
+ return token;
143
+ };
144
+ }
145
+ var REAUTH_CODES = /* @__PURE__ */ new Set(["invalid_grant", "invalid_rapt"]);
146
+ function describeAdcFailure(e) {
147
+ const body = oauthErrorBody(e);
148
+ if (body && (REAUTH_CODES.has(body.error) || REAUTH_CODES.has(body.error_subtype ?? ""))) {
149
+ return new JudgeConfigError(
150
+ `Application Default Credentials need re-authentication (${body.error}${body.error_subtype ? `/${body.error_subtype}` : ""}) \u2014 run \`gcloud auth application-default login\``,
151
+ { oauthError: body.error }
152
+ );
153
+ }
154
+ const detail = body?.error_description ?? body?.error ?? (e instanceof Error ? e.message : String(e));
155
+ return new JudgeConfigError(
156
+ `Application Default Credentials failed: ${detail} \u2014 check GOOGLE_CLOUD_PROJECT / GOOGLE_CLOUD_LOCATION and \`gcloud auth application-default login\``
157
+ );
158
+ }
159
+ function oauthErrorBody(e) {
160
+ if (typeof e !== "object" || e === null) return void 0;
161
+ const data = e.response?.data;
162
+ if (typeof data !== "object" || data === null) return void 0;
163
+ const body = data;
164
+ return typeof body.error === "string" ? body : void 0;
165
+ }
166
+
167
+ // src/providers/generate-content.ts
168
+ function generateContentBody(request) {
169
+ return {
170
+ contents: [
171
+ { role: "user", parts: [{ text: VERDICT_PROMPT(request.rubric, request.text) }] }
172
+ ],
173
+ generationConfig: {
174
+ temperature: 0,
175
+ responseMimeType: "application/json"
176
+ }
177
+ };
178
+ }
179
+ function extractCandidateText(body, model, backend) {
180
+ let parsed;
181
+ try {
182
+ parsed = JSON.parse(body);
183
+ } catch {
184
+ throw new JudgeProviderError(`${backend} reply is not JSON: ${body.slice(0, 200)}`, { model });
185
+ }
186
+ const text = parsed.candidates?.[0]?.content?.parts?.[0]?.text;
187
+ if (typeof text !== "string" || text.length === 0) {
188
+ throw new JudgeProviderError(
189
+ `${backend} reply carries no candidate text (blocked or empty): ${body.slice(0, 300)}`,
190
+ { model }
191
+ );
192
+ }
193
+ return text;
194
+ }
195
+
196
+ // src/providers/http-json.ts
197
+ var MAX_RETRY_AFTER_MS = 1e4;
198
+ function postJson(call) {
199
+ return send(call, {
200
+ method: "POST",
201
+ headers: { "content-type": "application/json", ...call.headers },
202
+ body: JSON.stringify(call.body),
203
+ signal: AbortSignal.timeout(call.timeoutMs)
204
+ });
205
+ }
206
+ async function getOk(call) {
207
+ await send(call, {
208
+ method: "GET",
209
+ headers: call.headers,
210
+ signal: AbortSignal.timeout(call.timeoutMs)
211
+ });
212
+ }
213
+ var HttpBackend = class {
214
+ constructor(spec) {
215
+ this.spec = spec;
216
+ }
217
+ spec;
218
+ post(url, body, model, headers = {}) {
219
+ return postJson({ ...this.spec, headers: { ...this.spec.headers, ...headers }, url, body, model });
220
+ }
221
+ /** Ask a free endpoint whether the credentials are still good. */
222
+ async probe(url, headers = {}) {
223
+ await getOk({ ...this.spec, headers: { ...this.spec.headers, ...headers }, url });
224
+ }
225
+ };
226
+ function apiBackend(backend, authHint2, headers, opts) {
227
+ return new HttpBackend({
228
+ backend,
229
+ authHint: authHint2,
230
+ headers,
231
+ timeoutMs: opts.timeoutMs,
232
+ fetchImpl: opts.fetchImpl ?? fetch
233
+ });
234
+ }
235
+ async function send(call, init) {
236
+ const { url, backend, fetchImpl } = call;
237
+ let res;
238
+ try {
239
+ res = await fetchImpl(url, init);
240
+ } catch (e) {
241
+ throw new JudgeTransientError(
242
+ `cannot reach ${backend} at ${url} (${e instanceof Error ? e.message : String(e)})`,
243
+ { url }
244
+ );
245
+ }
246
+ const text = await res.text();
247
+ if (res.ok) return text;
248
+ throw describeFailure(call, res, text);
249
+ }
250
+ function describeFailure(call, res, body) {
251
+ const { url, backend, authHint: authHint2 } = call;
252
+ if (res.status === 401 || res.status === 403) {
253
+ return new JudgeConfigError(
254
+ `${backend} rejected the credentials (HTTP ${res.status}) \u2014 ${authHint2}`,
255
+ { status: res.status, url }
256
+ );
257
+ }
258
+ if (res.status === 429 || res.status >= 500) {
259
+ const retryAfterMs = retryAfter(res);
260
+ return new JudgeTransientError(
261
+ `${backend} replied ${res.status} (${res.status === 429 ? "rate limited" : "backend error"})`,
262
+ { status: res.status, url, ...retryAfterMs === void 0 ? {} : { retryAfterMs } }
263
+ );
264
+ }
265
+ if (!looksLikeJson(body)) {
266
+ return new JudgeProviderError(
267
+ `${backend} replied ${res.status} with a non-JSON body \u2014 the endpoint is wrong, not the model: ${url} (UNOTEST_JUDGE_LOG_LEVEL=debug logs the body)`,
268
+ { status: res.status, url, body: body.slice(0, 2e3) }
269
+ );
270
+ }
271
+ return new JudgeProviderError(
272
+ `${backend} replied ${res.status}: ${body.slice(0, 500)}`,
273
+ { status: res.status, url }
274
+ );
275
+ }
276
+ function looksLikeJson(body) {
277
+ const head = body.trimStart()[0];
278
+ return head === "{" || head === "[";
279
+ }
280
+ function retryAfter(res) {
281
+ const raw = res.headers.get("retry-after");
282
+ if (!raw) return void 0;
283
+ const seconds = Number.parseFloat(raw);
284
+ const ms = Number.isFinite(seconds) ? seconds * 1e3 : Date.parse(raw) - Date.now();
285
+ if (!Number.isFinite(ms) || ms <= 0) return void 0;
286
+ return Math.min(ms, MAX_RETRY_AFTER_MS);
287
+ }
288
+
289
+ // src/providers/vertex.ts
290
+ function vertexHost(location) {
291
+ return location === "global" ? "https://aiplatform.googleapis.com" : `https://${location}-aiplatform.googleapis.com`;
292
+ }
293
+ function modelPath(model) {
294
+ return model.includes("/") ? model : `publishers/google/models/${model}`;
295
+ }
296
+ function vertexEndpoint(project, location, model) {
297
+ return `${vertexHost(location)}/v1/projects/${project}/locations/${location}/${modelPath(model)}:generateContent`;
298
+ }
299
+ var TOKENINFO_URL = "https://oauth2.googleapis.com/tokeninfo";
300
+ var TOKEN_ENV = JUDGE_ENV.accessToken;
301
+ function authHint(opts) {
302
+ return opts.accessToken ? "UNOTEST_JUDGE_ACCESS_TOKEN has expired (these tokens live about an hour) \u2014 refresh it with `gcloud auth print-access-token`, or unset it to use ADC, which refreshes itself" : "run `gcloud auth application-default login`, and check that GOOGLE_CLOUD_PROJECT has Vertex AI enabled";
303
+ }
304
+ var VertexJudgeProvider = class {
305
+ constructor(opts) {
306
+ this.opts = opts;
307
+ this.api = apiBackend("Vertex AI", authHint(opts), {}, opts);
308
+ }
309
+ opts;
310
+ api;
311
+ tokenSource;
312
+ async judgeOnce(request) {
313
+ const { project, location, model } = this.opts;
314
+ const token = await this.accessToken();
315
+ const body = await this.api.post(
316
+ vertexEndpoint(project, location, model),
317
+ generateContentBody(request),
318
+ model,
319
+ { authorization: `Bearer ${token}` }
320
+ );
321
+ return { ...parseVerdictReply(extractCandidateText(body, model, "Vertex AI"), model), model };
322
+ }
323
+ /** Two credentials, two free probes. ADC: fetching a token exercises the
324
+ * exact thing that dies on an org's daily reauth policy. A static token:
325
+ * Google's own `tokeninfo` says whether it is still alive — and a static
326
+ * token lives about an hour, so "valid when the service started" is not
327
+ * the same question as "valid now". */
328
+ async checkAuth() {
329
+ const { accessToken } = this.opts;
330
+ if (!accessToken) {
331
+ await this.accessToken();
332
+ return;
333
+ }
334
+ try {
335
+ await this.api.probe(`${TOKENINFO_URL}?access_token=${encodeURIComponent(accessToken)}`);
336
+ } catch (e) {
337
+ if (e instanceof JudgeTransientError) return;
338
+ throw new JudgeConfigError(`Google rejected ${TOKEN_ENV} \u2014 ${authHint(this.opts)}`);
339
+ }
340
+ }
341
+ async accessToken() {
342
+ if (this.opts.accessToken) return this.opts.accessToken;
343
+ this.tokenSource ??= await buildAdcTokenSource();
344
+ return this.tokenSource();
345
+ }
346
+ };
347
+
348
+ // src/providers/gemini.ts
349
+ var BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
350
+ var AUTH_HINT = "check GEMINI_API_KEY (https://aistudio.google.com/apikey)";
351
+ function modelId(model) {
352
+ return model.replace(/^models\//, "");
353
+ }
354
+ var GeminiJudgeProvider = class {
355
+ constructor(opts) {
356
+ this.opts = opts;
357
+ this.api = apiBackend("Gemini API", AUTH_HINT, { "x-goog-api-key": opts.apiKey }, opts);
358
+ }
359
+ opts;
360
+ api;
361
+ async judgeOnce(request) {
362
+ const { model } = this.opts;
363
+ const body = await this.api.post(
364
+ `${BASE_URL}/models/${modelId(model)}:generateContent`,
365
+ generateContentBody(request),
366
+ model
367
+ );
368
+ return { ...parseVerdictReply(extractCandidateText(body, model, "Gemini API"), model), model };
369
+ }
370
+ checkAuth() {
371
+ return this.api.probe(`${BASE_URL}/models?pageSize=1`);
372
+ }
373
+ };
374
+
375
+ // src/providers/openai.ts
376
+ var BASE_URL2 = "https://api.openai.com/v1";
377
+ var AUTH_HINT2 = "check OPENAI_API_KEY (https://platform.openai.com/api-keys)";
378
+ var OpenAiJudgeProvider = class {
379
+ constructor(opts) {
380
+ this.opts = opts;
381
+ this.api = apiBackend("OpenAI", AUTH_HINT2, { authorization: `Bearer ${opts.apiKey}` }, opts);
382
+ }
383
+ opts;
384
+ api;
385
+ async judgeOnce(request) {
386
+ const { model } = this.opts;
387
+ const body = await this.api.post(
388
+ `${BASE_URL2}/chat/completions`,
389
+ {
390
+ model,
391
+ messages: [{ role: "user", content: VERDICT_PROMPT(request.rubric, request.text) }],
392
+ response_format: { type: "json_object" }
393
+ },
394
+ model
395
+ );
396
+ return { ...parseVerdictReply(extractMessageContent(body, model), model), model };
397
+ }
398
+ /** `GET /models` is free and needs the same key the verdict call uses. */
399
+ checkAuth() {
400
+ return this.api.probe(`${BASE_URL2}/models`);
401
+ }
402
+ };
403
+ function extractMessageContent(body, model) {
404
+ let parsed;
405
+ try {
406
+ parsed = JSON.parse(body);
407
+ } catch {
408
+ throw new JudgeProviderError(`OpenAI reply is not JSON: ${body.slice(0, 200)}`, { model });
409
+ }
410
+ const choice = parsed.choices?.[0];
411
+ if (choice?.finish_reason === "length") {
412
+ throw new JudgeProviderError(
413
+ `OpenAI model "${model}" hit its token limit before finishing the verdict \u2014 the verdict itself is short, so this usually means a reasoning model spent the budget thinking: pick a non-reasoning model via UNOTEST_JUDGE_MODEL`,
414
+ { model, finishReason: "length" }
415
+ );
416
+ }
417
+ const content = choice?.message?.content;
418
+ if (typeof content !== "string" || content.length === 0) {
419
+ throw new JudgeProviderError(
420
+ `OpenAI reply carries no message content: ${body.slice(0, 300)}`,
421
+ { model }
422
+ );
423
+ }
424
+ return content;
425
+ }
426
+
427
+ // src/providers/anthropic.ts
428
+ var BASE_URL3 = "https://api.anthropic.com/v1";
429
+ var ANTHROPIC_VERSION = "2023-06-01";
430
+ var MAX_TOKENS = 1024;
431
+ var AUTH_HINT3 = "check ANTHROPIC_API_KEY (https://console.anthropic.com/settings/keys)";
432
+ var AnthropicJudgeProvider = class {
433
+ constructor(opts) {
434
+ this.opts = opts;
435
+ this.api = apiBackend(
436
+ "Anthropic API",
437
+ AUTH_HINT3,
438
+ { "x-api-key": opts.apiKey, "anthropic-version": ANTHROPIC_VERSION },
439
+ opts
440
+ );
441
+ }
442
+ opts;
443
+ api;
444
+ async judgeOnce(request) {
445
+ const { model } = this.opts;
446
+ const body = await this.api.post(
447
+ `${BASE_URL3}/messages`,
448
+ {
449
+ model,
450
+ max_tokens: MAX_TOKENS,
451
+ messages: [{ role: "user", content: VERDICT_PROMPT(request.rubric, request.text) }]
452
+ },
453
+ model
454
+ );
455
+ return { ...parseVerdictReply(extractTextBlock(body, model), model), model };
456
+ }
457
+ /** `GET /models` is free and needs the same key the verdict call uses. */
458
+ checkAuth() {
459
+ return this.api.probe(`${BASE_URL3}/models?limit=1`);
460
+ }
461
+ };
462
+ function extractTextBlock(body, model) {
463
+ let parsed;
464
+ try {
465
+ parsed = JSON.parse(body);
466
+ } catch {
467
+ throw new JudgeProviderError(`Anthropic API reply is not JSON: ${body.slice(0, 200)}`, {
468
+ model
469
+ });
470
+ }
471
+ const reply = parsed;
472
+ if (reply.stop_reason === "max_tokens") {
473
+ throw new JudgeProviderError(
474
+ `Anthropic model "${model}" hit the ${MAX_TOKENS}-token limit before finishing the verdict \u2014 a reasoning model spends the budget thinking: pick a non-reasoning model via UNOTEST_JUDGE_MODEL`,
475
+ { model, stopReason: "max_tokens" }
476
+ );
477
+ }
478
+ const text = reply.content?.find((b) => b.type === "text")?.text;
479
+ if (typeof text === "string" && text.length > 0) return text;
480
+ throw new JudgeProviderError(
481
+ `Anthropic API reply carries no text block (refusal or empty): ${body.slice(0, 300)}`,
482
+ { model }
483
+ );
484
+ }
485
+
486
+ // src/providers/run-process.ts
487
+ import { spawn } from "child_process";
488
+ var runProcess = (file, args, opts) => new Promise((resolve, reject) => {
489
+ const child = spawn(file, args);
490
+ let stdout = "";
491
+ let stderr = "";
492
+ let overflowed = false;
493
+ let timedOut = false;
494
+ const timer = setTimeout(() => {
495
+ timedOut = true;
496
+ child.kill();
497
+ }, opts.timeoutMs);
498
+ const settle = (finish) => {
499
+ clearTimeout(timer);
500
+ finish();
501
+ };
502
+ child.stdout.on("data", (chunk) => {
503
+ if (stdout.length + chunk.length > opts.maxBuffer) {
504
+ overflowed = true;
505
+ child.kill();
506
+ return;
507
+ }
508
+ stdout += chunk.toString("utf8");
509
+ });
510
+ child.stderr.on("data", (chunk) => {
511
+ stderr += chunk.toString("utf8");
512
+ });
513
+ child.on("error", (e) => settle(() => reject(Object.assign(e, { stderr }))));
514
+ child.on(
515
+ "close",
516
+ (code, signal) => settle(() => {
517
+ if (overflowed) {
518
+ reject(
519
+ Object.assign(
520
+ new Error(`${file} produced more than ${opts.maxBuffer} bytes of output`),
521
+ { code: "ERR_MAXBUFFER", stderr }
522
+ )
523
+ );
524
+ return;
525
+ }
526
+ if (timedOut || signal !== null) {
527
+ reject(
528
+ Object.assign(new Error(`${file} was killed (${signal ?? "timeout"})`), {
529
+ killed: true,
530
+ stderr
531
+ })
532
+ );
533
+ return;
534
+ }
535
+ if (code !== 0) {
536
+ reject(Object.assign(new Error(`${file} exited with code ${code}`), { code, stderr }));
537
+ return;
538
+ }
539
+ resolve({ stdout, stderr });
540
+ })
541
+ );
542
+ child.stdin.on("error", () => {
543
+ });
544
+ if (opts.input !== void 0) child.stdin.end(opts.input);
545
+ else child.stdin.end();
546
+ });
547
+
548
+ // src/providers/claude-cli.ts
549
+ var MAX_STDOUT_BYTES = 10 * 1024 * 1024;
550
+ var VERSION_TIMEOUT_MS = 15e3;
551
+ var ClaudeCliJudgeProvider = class {
552
+ constructor(opts) {
553
+ this.opts = opts;
554
+ this.run = opts.runImpl ?? runProcess;
555
+ }
556
+ opts;
557
+ run;
558
+ async judgeOnce(request) {
559
+ const { bin, model, timeoutMs } = this.opts;
560
+ const args = [
561
+ "-p",
562
+ "--output-format",
563
+ "json",
564
+ "--strict-mcp-config",
565
+ ...model ? ["--model", model] : []
566
+ ];
567
+ let stdout;
568
+ try {
569
+ ({ stdout } = await this.run(bin, args, {
570
+ timeoutMs,
571
+ maxBuffer: MAX_STDOUT_BYTES,
572
+ input: VERDICT_PROMPT(request.rubric, request.text)
573
+ }));
574
+ } catch (e) {
575
+ throw toTypedError(e, bin, timeoutMs);
576
+ }
577
+ const envelope = parseEnvelope(stdout, bin);
578
+ const modelLabel = firstModelId(envelope) ?? model ?? "claude";
579
+ if (envelope.is_error === true || typeof envelope.result !== "string" || !envelope.result) {
580
+ throw new JudgeProviderError(
581
+ `${bin} -p returned an error result: ${String(envelope.result ?? stdout).slice(0, 300)}`,
582
+ { model: modelLabel }
583
+ );
584
+ }
585
+ return { ...parseVerdictReply(envelope.result, modelLabel), model: modelLabel };
586
+ }
587
+ /** The binary exists and runs. It cannot prove the session is still
588
+ * authenticated without spending a model call, so that failure stays a
589
+ * first-verdict one — but "claude is not installed on this box" is the
590
+ * common case and it is caught here. */
591
+ async checkAuth() {
592
+ const { bin } = this.opts;
593
+ try {
594
+ await this.run(bin, ["--version"], {
595
+ timeoutMs: VERSION_TIMEOUT_MS,
596
+ maxBuffer: MAX_STDOUT_BYTES
597
+ });
598
+ } catch (e) {
599
+ throw toTypedError(e, bin, VERSION_TIMEOUT_MS);
600
+ }
601
+ }
602
+ };
603
+ var TRANSIENT_SPAWN_CODES = /* @__PURE__ */ new Set(["EAGAIN", "ENOMEM", "EMFILE", "ENFILE"]);
604
+ function toTypedError(e, bin, timeoutMs) {
605
+ const err = e;
606
+ if (typeof err.code === "string" && TRANSIENT_SPAWN_CODES.has(err.code)) {
607
+ return new JudgeTransientError(`${bin} could not be spawned (${err.code})`, { bin });
608
+ }
609
+ if (err.code === "ENOENT") {
610
+ return new JudgeConfigError(
611
+ `Claude Code CLI not found ("${bin}") \u2014 install it (https://claude.com/claude-code) or point UNOTEST_JUDGE_CLAUDE_BIN at the binary`,
612
+ { bin }
613
+ );
614
+ }
615
+ if (err.code === "EACCES") {
616
+ return new JudgeConfigError(`Claude Code CLI is not executable ("${bin}")`, { bin });
617
+ }
618
+ if (err.killed === true) {
619
+ return new JudgeProviderError(`${bin} -p timed out after ${timeoutMs}ms`, { bin, timeoutMs });
620
+ }
621
+ const stderr = typeof err.stderr === "string" && err.stderr ? err.stderr : String(err.message ?? e);
622
+ return new JudgeProviderError(`${bin} -p failed: ${stderr.slice(0, 500)}`, { bin });
623
+ }
624
+ function parseEnvelope(stdout, bin) {
625
+ try {
626
+ return JSON.parse(stdout);
627
+ } catch {
628
+ throw new JudgeProviderError(
629
+ `${bin} -p did not return the JSON envelope: ${stdout.slice(0, 300)}`,
630
+ { bin }
631
+ );
632
+ }
633
+ }
634
+ function firstModelId(envelope) {
635
+ const keys = envelope.modelUsage ? Object.keys(envelope.modelUsage) : [];
636
+ return keys.length > 0 ? keys[0] : void 0;
637
+ }
638
+
639
+ // src/policy.ts
640
+ async function judgeWithRetries(provider, request, retries) {
641
+ const maxAttempts = Math.max(0, retries) + 1;
642
+ let last = null;
643
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
644
+ const single = await provider.judgeOnce(request);
645
+ last = {
646
+ verdict: single.pass ? "pass" : "fail",
647
+ reasoning: single.reasoning,
648
+ model: single.model,
649
+ attempts: attempt
650
+ };
651
+ if (single.pass) return last;
652
+ }
653
+ return last;
654
+ }
655
+ async function judgeWithVote(provider, request, votes) {
656
+ const settled = await Promise.allSettled(
657
+ Array.from({ length: votes }, () => provider.judgeOnce(request))
658
+ );
659
+ const ballots = settled.flatMap((s) => s.status === "fulfilled" ? [s.value] : []);
660
+ const failure = settled.find((s) => s.status === "rejected")?.reason;
661
+ const quorum = Math.floor(votes / 2) + 1;
662
+ if (ballots.length < quorum) throw failure;
663
+ const passes = ballots.filter((b) => b.pass);
664
+ const fails = ballots.filter((b) => !b.pass);
665
+ const winners = passes.length > fails.length ? passes : fails;
666
+ if (winners.length * 2 <= ballots.length) throw failure;
667
+ const winner = winners[0];
668
+ const lost = votes - ballots.length;
669
+ return {
670
+ verdict: winner.pass ? "pass" : "fail",
671
+ reasoning: `majority ${winners.length}/${votes}` + (lost > 0 ? ` (${lost} ballot(s) errored)` : "") + `: ${winner.reasoning}`,
672
+ model: winner.model,
673
+ attempts: votes
674
+ };
675
+ }
676
+
677
+ // src/logger.ts
678
+ var LOG_LEVELS = ["silent", "error", "warn", "info", "debug"];
679
+ var ConsoleLogger = class {
680
+ constructor(level, out = (l) => process.stdout.write(`${l}
681
+ `), err = (l) => process.stderr.write(`${l}
682
+ `)) {
683
+ this.level = level;
684
+ this.out = out;
685
+ this.err = err;
686
+ }
687
+ level;
688
+ out;
689
+ err;
690
+ error(message) {
691
+ if (this.enabled("error")) this.err(message);
692
+ }
693
+ warn(message) {
694
+ if (this.enabled("warn")) this.err(message);
695
+ }
696
+ info(message) {
697
+ if (this.enabled("info")) this.out(message);
698
+ }
699
+ debug(message) {
700
+ if (this.enabled("debug")) this.out(message);
701
+ }
702
+ enabled(of) {
703
+ return LOG_LEVELS.indexOf(this.level) >= LOG_LEVELS.indexOf(of);
704
+ }
705
+ };
706
+ var SILENT_LOGGER = {
707
+ error: () => {
708
+ },
709
+ warn: () => {
710
+ },
711
+ info: () => {
712
+ },
713
+ debug: () => {
714
+ }
715
+ };
716
+
717
+ // src/logging-provider.ts
718
+ var LoggingJudgeProvider = class {
719
+ constructor(inner, logger, nowFn = Date.now) {
720
+ this.inner = inner;
721
+ this.logger = logger;
722
+ this.nowFn = nowFn;
723
+ if (inner.checkAuth) this.checkAuth = () => inner.checkAuth();
724
+ }
725
+ inner;
726
+ logger;
727
+ nowFn;
728
+ /** Mirrors the inner provider's capability instead of faking a probe the
729
+ * wrapped one does not have. */
730
+ checkAuth;
731
+ async judgeOnce(request) {
732
+ this.logger.debug(`judge: effective prompt
733
+ ${VERDICT_PROMPT(request.rubric, request.text)}`);
734
+ const started = this.nowFn();
735
+ try {
736
+ const verdict = await this.inner.judgeOnce(request);
737
+ this.logger.debug(
738
+ `judge: ballot ${verdict.pass ? "pass" : "fail"} ${verdict.model} ${this.nowFn() - started}ms \u2014 ${verdict.reasoning}`
739
+ );
740
+ if (verdict.raw !== void 0) this.logger.debug(`judge: raw reply
741
+ ${verdict.raw}`);
742
+ return verdict;
743
+ } catch (e) {
744
+ this.logger.debug(
745
+ `judge: ballot ERROR after ${this.nowFn() - started}ms \u2014 ${e instanceof Error ? e.message : String(e)}` + (e instanceof JudgeError ? `
746
+ context: ${JSON.stringify(e.context)}` : "")
747
+ );
748
+ throw e;
749
+ }
750
+ }
751
+ };
752
+ function withLogging(provider, logger) {
753
+ return logger ? new LoggingJudgeProvider(provider, logger) : provider;
754
+ }
755
+
756
+ // src/transient-retry.ts
757
+ var ATTEMPTS = 3;
758
+ var BACKOFF_MS = [500, 1500];
759
+ var realSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
760
+ var TransientRetryProvider = class {
761
+ constructor(inner, opts = {}) {
762
+ this.inner = inner;
763
+ this.opts = opts;
764
+ if (inner.checkAuth) this.checkAuth = () => this.retry(() => inner.checkAuth());
765
+ }
766
+ inner;
767
+ opts;
768
+ /** Mirrors the inner provider's capability rather than inventing one. */
769
+ checkAuth;
770
+ judgeOnce(request) {
771
+ return this.retry(() => this.inner.judgeOnce(request));
772
+ }
773
+ async retry(attempt) {
774
+ const sleep = this.opts.sleepImpl ?? realSleep;
775
+ for (let i = 1; ; i++) {
776
+ try {
777
+ return await attempt();
778
+ } catch (e) {
779
+ if (!(e instanceof JudgeTransientError) || i >= ATTEMPTS) throw e;
780
+ const after = e.context.retryAfterMs;
781
+ await sleep(typeof after === "number" ? after : BACKOFF_MS[i - 1]);
782
+ }
783
+ }
784
+ }
785
+ };
786
+ function withTransientRetry(provider, opts) {
787
+ return new TransientRetryProvider(provider, opts);
788
+ }
789
+
790
+ // src/auth-probe.ts
791
+ var DEFAULT_TTL_MS = 15e3;
792
+ function createAuthProbe(service) {
793
+ return new AuthProbe(service.checkAuth ? { checkAuth: () => service.checkAuth() } : {});
794
+ }
795
+ var AuthProbe = class {
796
+ constructor(opts) {
797
+ this.opts = opts;
798
+ }
799
+ opts;
800
+ cached;
801
+ /** @param fresh bypass the cache (startup preflight, `--check`). */
802
+ async status(fresh = false) {
803
+ const now = (this.opts.nowFn ?? Date.now)();
804
+ const ttl = this.opts.ttlMs ?? DEFAULT_TTL_MS;
805
+ if (!fresh && this.cached && now - this.cached.at < ttl) return this.cached.result;
806
+ const result = await this.run();
807
+ this.cached = { at: now, result };
808
+ return result;
809
+ }
810
+ async run() {
811
+ if (!this.opts.checkAuth) return { ok: true };
812
+ try {
813
+ await this.opts.checkAuth();
814
+ return { ok: true };
815
+ } catch (e) {
816
+ return { ok: false, code: judgeErrorCode(e), error: errorMessage(e) };
817
+ }
818
+ }
819
+ };
820
+
821
+ // src/env.ts
822
+ import { JUDGE_ENV as JUDGE_ENV2 } from "@unotest/protocol";
823
+ var PROVIDER_KINDS = [
824
+ "fake",
825
+ "vertex",
826
+ "claude",
827
+ "gemini",
828
+ "openai",
829
+ "anthropic"
830
+ ];
831
+ var DEFAULT_MODEL = {
832
+ fake: void 0,
833
+ vertex: "gemini-2.5-flash",
834
+ gemini: "gemini-2.5-flash",
835
+ openai: "gpt-5-mini",
836
+ anthropic: "claude-haiku-4-5",
837
+ claude: void 0
838
+ };
839
+ var API_KEY_ENV = {
840
+ gemini: "GEMINI_API_KEY",
841
+ openai: "OPENAI_API_KEY",
842
+ anthropic: "ANTHROPIC_API_KEY"
843
+ };
844
+ var DEFAULT_RETRIES = 1;
845
+ var DEFAULT_TIMEOUT_MS = 3e4;
846
+ var DEFAULT_CLAUDE_TIMEOUT_MS = 12e4;
847
+ function intEnv(env, name, fallback, min, max) {
848
+ const raw = env[name];
849
+ if (raw === void 0 || raw === "") return fallback;
850
+ const n = Number.parseInt(raw, 10);
851
+ if (Number.isNaN(n) || n < min || n > max) {
852
+ throw new JudgeConfigError(`${name} must be an integer in [${min}, ${max}], got "${raw}"`);
853
+ }
854
+ return n;
855
+ }
856
+ function resolveJudgeServiceEnv(env) {
857
+ const provider = env[JUDGE_ENV2.provider];
858
+ if (!provider || !PROVIDER_KINDS.includes(provider)) {
859
+ const list = PROVIDER_KINDS.map((k) => `"${k}"`).join(" | ");
860
+ throw new JudgeConfigError(
861
+ provider === void 0 || provider === "" ? `${JUDGE_ENV2.provider} is not set \u2014 one of ${list} ("fake" is deterministic and CI-safe)` : `${JUDGE_ENV2.provider} must be one of ${list}, got "${provider}"`
862
+ );
863
+ }
864
+ const votes = intEnv(env, JUDGE_ENV2.vote, 1, 1, 9);
865
+ if (votes % 2 === 0) {
866
+ throw new JudgeConfigError(
867
+ `${JUDGE_ENV2.vote} must be odd (1, 3, 5, 7, 9) \u2014 an even vote can tie, and a tie has no verdict; got "${votes}"`
868
+ );
869
+ }
870
+ const resolved = {
871
+ provider,
872
+ votes,
873
+ retries: intEnv(env, JUDGE_ENV2.retries, DEFAULT_RETRIES, 0, 10),
874
+ timeoutMs: intEnv(
875
+ env,
876
+ JUDGE_ENV2.callTimeoutMs,
877
+ provider === "claude" ? DEFAULT_CLAUDE_TIMEOUT_MS : DEFAULT_TIMEOUT_MS,
878
+ 1e3,
879
+ 6e5
880
+ )
881
+ };
882
+ const model = env[JUDGE_ENV2.model] || DEFAULT_MODEL[provider];
883
+ if (model) resolved.model = model;
884
+ if (provider === "vertex") {
885
+ const project = env.GOOGLE_CLOUD_PROJECT;
886
+ const location = env.GOOGLE_CLOUD_LOCATION;
887
+ if (!project || !location) {
888
+ throw new JudgeConfigError(
889
+ `${JUDGE_ENV2.provider}=vertex requires GOOGLE_CLOUD_PROJECT and GOOGLE_CLOUD_LOCATION (ADC auth, no API keys). Location is "global" or a region such as "us-central1" \u2014 pick it together with the model: model availability differs per location`
890
+ );
891
+ }
892
+ resolved.project = project;
893
+ resolved.location = location;
894
+ const accessToken = env[JUDGE_ENV2.accessToken];
895
+ if (accessToken) resolved.accessToken = accessToken;
896
+ }
897
+ const keyEnvName = API_KEY_ENV[provider];
898
+ if (keyEnvName) {
899
+ const apiKey = env[keyEnvName];
900
+ if (!apiKey) {
901
+ throw new JudgeConfigError(`${JUDGE_ENV2.provider}=${provider} requires ${keyEnvName}`);
902
+ }
903
+ resolved.apiKey = apiKey;
904
+ }
905
+ if (provider === "claude") {
906
+ resolved.claudeBin = env[JUDGE_ENV2.claudeBin] || "claude";
907
+ }
908
+ return resolved;
909
+ }
910
+ function resolveJudgeServerEnv(env) {
911
+ const resolved = {
912
+ host: env[JUDGE_ENV2.host] || "127.0.0.1",
913
+ port: intEnv(env, JUDGE_ENV2.port, 8790, 1, 65535),
914
+ skipPreflight: boolEnv(env, JUDGE_ENV2.skipPreflight)
915
+ };
916
+ const token = env[JUDGE_ENV2.token];
917
+ if (token) resolved.token = token;
918
+ return resolved;
919
+ }
920
+ function resolveLogLevel(env) {
921
+ const raw = (env[JUDGE_ENV2.logLevel] || env.UNOTEST_LOG_LEVEL || "").trim().toLowerCase();
922
+ if (raw === "") return void 0;
923
+ if (!LOG_LEVELS.includes(raw)) {
924
+ throw new JudgeConfigError(
925
+ `${JUDGE_ENV2.logLevel} must be one of ${LOG_LEVELS.join(" | ")}, got "${raw}"`
926
+ );
927
+ }
928
+ return raw;
929
+ }
930
+ function boolEnv(env, name) {
931
+ const raw = (env[name] || "").trim().toLowerCase();
932
+ return raw === "1" || raw === "true" || raw === "yes";
933
+ }
934
+
935
+ // src/judge-service.ts
936
+ function buildProvider(env) {
937
+ switch (env.provider) {
938
+ case "fake":
939
+ return new FakeJudgeProvider();
940
+ case "vertex":
941
+ return new VertexJudgeProvider({
942
+ project: env.project,
943
+ location: env.location,
944
+ model: env.model,
945
+ timeoutMs: env.timeoutMs,
946
+ ...env.accessToken ? { accessToken: env.accessToken } : {}
947
+ });
948
+ case "gemini":
949
+ return new GeminiJudgeProvider({
950
+ apiKey: env.apiKey,
951
+ model: env.model,
952
+ timeoutMs: env.timeoutMs
953
+ });
954
+ case "openai":
955
+ return new OpenAiJudgeProvider({
956
+ apiKey: env.apiKey,
957
+ model: env.model,
958
+ timeoutMs: env.timeoutMs
959
+ });
960
+ case "anthropic":
961
+ return new AnthropicJudgeProvider({
962
+ apiKey: env.apiKey,
963
+ model: env.model,
964
+ timeoutMs: env.timeoutMs
965
+ });
966
+ case "claude":
967
+ return new ClaudeCliJudgeProvider({
968
+ bin: env.claudeBin,
969
+ timeoutMs: env.timeoutMs,
970
+ ...env.model ? { model: env.model } : {}
971
+ });
972
+ }
973
+ }
974
+ function createJudgeService(env, provider, logger) {
975
+ const p = withTransientRetry(withLogging(provider ?? buildProvider(env), logger));
976
+ const service = {
977
+ judge: (request) => env.votes > 1 ? judgeWithVote(p, request, env.votes) : judgeWithRetries(p, request, env.retries)
978
+ };
979
+ if (p.checkAuth) service.checkAuth = () => p.checkAuth();
980
+ return service;
981
+ }
982
+ function createJudgeServiceFromEnv(env) {
983
+ const level = resolveLogLevel(env);
984
+ return createJudgeService(
985
+ resolveJudgeServiceEnv(env),
986
+ void 0,
987
+ level === void 0 ? void 0 : new ConsoleLogger(level)
988
+ );
989
+ }
990
+
991
+ // src/server.ts
992
+ import { createServer } from "http";
993
+ import {
994
+ JUDGE_ENV as JUDGE_ENV3,
995
+ JUDGE_ROUTES
996
+ } from "@unotest/protocol";
997
+ var MAX_BODY_BYTES = 1024 * 1024;
998
+ var DRAIN_TIMEOUT_MS = 5e3;
999
+ function startJudgeServer(service, env, deps = {}) {
1000
+ const ctx = {
1001
+ service,
1002
+ env,
1003
+ logger: deps.logger ?? SILENT_LOGGER,
1004
+ probe: deps.probe ?? createAuthProbe(service),
1005
+ now: deps.nowFn ?? Date.now
1006
+ };
1007
+ const server = createServer((req, res) => {
1008
+ void route(ctx, req, res);
1009
+ });
1010
+ return new Promise((resolve, reject) => {
1011
+ server.once("error", reject);
1012
+ server.listen(env.port, env.host, () => {
1013
+ const addr = server.address();
1014
+ const port = typeof addr === "object" && addr !== null ? addr.port : env.port;
1015
+ resolve({
1016
+ server,
1017
+ port,
1018
+ close: () => new Promise((res2, rej2) => server.close((e) => e ? rej2(e) : res2()))
1019
+ });
1020
+ });
1021
+ });
1022
+ }
1023
+ async function route(ctx, req, res) {
1024
+ const url = req.url ?? "/";
1025
+ if (req.method === "GET" && url === JUDGE_ROUTES.health) {
1026
+ const health = await ctx.probe.status();
1027
+ sendJson(res, health.ok ? 200 : 503, health);
1028
+ return;
1029
+ }
1030
+ if (req.method !== "POST" || url !== JUDGE_ROUTES.judge) {
1031
+ sendError(res, 404, `unknown route ${req.method} ${url}`, "bad-request");
1032
+ return;
1033
+ }
1034
+ if (ctx.env.token && req.headers.authorization !== `Bearer ${ctx.env.token}`) {
1035
+ sendError(res, 401, `missing or wrong bearer token (${JUDGE_ENV3.token})`, "unauthorized");
1036
+ return;
1037
+ }
1038
+ let body;
1039
+ try {
1040
+ body = parseRequest(await readBody(req, res));
1041
+ } catch (e) {
1042
+ if (res.writableEnded) return;
1043
+ const message = errorMessage(e);
1044
+ ctx.logger.error(`POST /judge ERROR bad-request ${message}`);
1045
+ sendError(res, 400, message, "bad-request");
1046
+ return;
1047
+ }
1048
+ const started = ctx.now();
1049
+ try {
1050
+ const verdict = await ctx.service.judge(body);
1051
+ ctx.logger.info(verdictLine(verdict, ctx.now() - started));
1052
+ sendJson(res, 200, verdict);
1053
+ } catch (e) {
1054
+ const code = judgeErrorCode(e);
1055
+ ctx.logger.error(`POST /judge ERROR ${code} ${errorMessage(e)}`);
1056
+ sendError(res, code === "provider-error" ? 502 : 500, errorMessage(e), code);
1057
+ }
1058
+ }
1059
+ function verdictLine(verdict, ms) {
1060
+ return `POST /judge ${verdict.verdict} attempts ${verdict.attempts} ${verdict.model} ${ms}ms`;
1061
+ }
1062
+ function parseRequest(raw) {
1063
+ let parsed;
1064
+ try {
1065
+ parsed = JSON.parse(raw);
1066
+ } catch {
1067
+ throw new Error("request body is not JSON");
1068
+ }
1069
+ const { rubric, text } = parsed;
1070
+ if (typeof rubric !== "string" || rubric.trim() === "") {
1071
+ throw new Error('request body needs a non-empty string "rubric"');
1072
+ }
1073
+ if (typeof text !== "string") {
1074
+ throw new Error('request body needs a string "text"');
1075
+ }
1076
+ return { rubric, text };
1077
+ }
1078
+ function readBody(req, res) {
1079
+ return new Promise((resolve, reject) => {
1080
+ const chunks = [];
1081
+ let size = 0;
1082
+ let overflowed = false;
1083
+ req.on("data", (chunk) => {
1084
+ if (overflowed) return;
1085
+ size += chunk.length;
1086
+ if (size > MAX_BODY_BYTES) {
1087
+ overflowed = true;
1088
+ sendError(
1089
+ res,
1090
+ 413,
1091
+ `request body exceeds ${MAX_BODY_BYTES} bytes \u2014 judge a narrower locator, not the whole page`,
1092
+ "bad-request"
1093
+ );
1094
+ drain(req);
1095
+ reject(new Error("request body too large"));
1096
+ return;
1097
+ }
1098
+ chunks.push(chunk);
1099
+ });
1100
+ req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
1101
+ req.on("error", reject);
1102
+ });
1103
+ }
1104
+ function drain(req) {
1105
+ const timer = setTimeout(() => req.destroy(), DRAIN_TIMEOUT_MS);
1106
+ timer.unref();
1107
+ req.on("end", () => clearTimeout(timer));
1108
+ req.on("error", () => clearTimeout(timer));
1109
+ req.resume();
1110
+ }
1111
+ function sendJson(res, status, body) {
1112
+ const payload = JSON.stringify(body);
1113
+ res.writeHead(status, { "content-type": "application/json" });
1114
+ res.end(payload);
1115
+ }
1116
+ function sendError(res, status, error, code) {
1117
+ sendJson(res, status, { error, code });
1118
+ }
1119
+
1120
+ export {
1121
+ JudgeError,
1122
+ JudgeConfigError,
1123
+ JudgeProviderError,
1124
+ JudgeTransientError,
1125
+ judgeErrorCode,
1126
+ FAKE_MODEL_ID,
1127
+ parseFakeRubric,
1128
+ FakeJudgeProvider,
1129
+ VERDICT_PROMPT,
1130
+ parseVerdictReply,
1131
+ vertexEndpoint,
1132
+ VertexJudgeProvider,
1133
+ GeminiJudgeProvider,
1134
+ OpenAiJudgeProvider,
1135
+ AnthropicJudgeProvider,
1136
+ runProcess,
1137
+ ClaudeCliJudgeProvider,
1138
+ judgeWithRetries,
1139
+ judgeWithVote,
1140
+ LOG_LEVELS,
1141
+ ConsoleLogger,
1142
+ SILENT_LOGGER,
1143
+ LoggingJudgeProvider,
1144
+ withLogging,
1145
+ TransientRetryProvider,
1146
+ withTransientRetry,
1147
+ createAuthProbe,
1148
+ AuthProbe,
1149
+ resolveJudgeServiceEnv,
1150
+ resolveJudgeServerEnv,
1151
+ resolveLogLevel,
1152
+ buildProvider,
1153
+ createJudgeService,
1154
+ createJudgeServiceFromEnv,
1155
+ startJudgeServer
1156
+ };
1157
+ //# sourceMappingURL=chunk-ADU4QPCJ.js.map