@tangle-network/agent-eval 0.144.0 → 0.144.3

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.
Files changed (136) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/analyst/index.d.ts +88 -23
  3. package/dist/analyst/index.d.ts.map +1 -1
  4. package/dist/analyst/index.js +4 -4
  5. package/dist/{analyze-runs-BScZvqMV.js → analyze-runs-DWIvOAGk.js} +2 -2
  6. package/dist/{analyze-runs-BScZvqMV.js.map → analyze-runs-DWIvOAGk.js.map} +1 -1
  7. package/dist/{benchmark-DxaZfy0w.d.ts → benchmark-CP6kWfj8.d.ts} +3 -3
  8. package/dist/{benchmark-DxaZfy0w.d.ts.map → benchmark-CP6kWfj8.d.ts.map} +1 -1
  9. package/dist/{benchmark-command-4c7N_rlw.js → benchmark-command-CQPKRUr-.js} +332 -111
  10. package/dist/benchmark-command-CQPKRUr-.js.map +1 -0
  11. package/dist/benchmarks/index.d.ts +1 -1
  12. package/dist/benchmarks/index.js +1 -1
  13. package/dist/{benchmarks-05UGZ8sZ.js → benchmarks-CkG1bWFa.js} +4 -4
  14. package/dist/{benchmarks-05UGZ8sZ.js.map → benchmarks-CkG1bWFa.js.map} +1 -1
  15. package/dist/campaign/index.d.ts +6 -6
  16. package/dist/campaign/index.js +4 -4
  17. package/dist/{campaign-BKOtvRAB.js → campaign-DjGFyPxH.js} +74 -40
  18. package/dist/campaign-DjGFyPxH.js.map +1 -0
  19. package/dist/cli.js +2 -2
  20. package/dist/{client-Cgl6KasJ.d.ts → client-Bbht4xxl.d.ts} +4 -4
  21. package/dist/{client-Cgl6KasJ.d.ts.map → client-Bbht4xxl.d.ts.map} +1 -1
  22. package/dist/{completion-verifier-D15NHYSk.d.ts → completion-verifier-EJERfFwF.d.ts} +3 -3
  23. package/dist/{completion-verifier-D15NHYSk.d.ts.map → completion-verifier-EJERfFwF.d.ts.map} +1 -1
  24. package/dist/contract/index.d.ts +8 -8
  25. package/dist/contract/index.js +10 -8
  26. package/dist/contract/index.js.map +1 -1
  27. package/dist/control.d.ts +2 -2
  28. package/dist/control.js +1 -1
  29. package/dist/{default-registry-DfHJEwYh.js → default-registry-SOyHB6qG.js} +2 -2
  30. package/dist/{default-registry-DfHJEwYh.js.map → default-registry-SOyHB6qG.js.map} +1 -1
  31. package/dist/{default-registry-D3uqKbo6.d.ts → default-registry-iXfu2trt.d.ts} +5 -5
  32. package/dist/{default-registry-D3uqKbo6.d.ts.map → default-registry-iXfu2trt.d.ts.map} +1 -1
  33. package/dist/{dspy-rlm-engine-CBFwlyaY.js → dspy-rlm-engine-IRCG8kdi.js} +61 -105
  34. package/dist/dspy-rlm-engine-IRCG8kdi.js.map +1 -0
  35. package/dist/{eval-campaign-YdkpWWoT.js → eval-campaign-lZcDIwQM.js} +3 -3
  36. package/dist/{eval-campaign-YdkpWWoT.js.map → eval-campaign-lZcDIwQM.js.map} +1 -1
  37. package/dist/{exact-types-B0lJV3tu.d.ts → exact-types-BygCBR4L.d.ts} +2 -2
  38. package/dist/{exact-types-B0lJV3tu.d.ts.map → exact-types-BygCBR4L.d.ts.map} +1 -1
  39. package/dist/{external-optimizer-contracts-iK0yu4AR.d.ts → external-optimizer-contracts-CdmX2K2S.d.ts} +47 -10
  40. package/dist/external-optimizer-contracts-CdmX2K2S.d.ts.map +1 -0
  41. package/dist/{feedback-trajectory-BCHqzLh3.d.ts → feedback-trajectory-CSIkRLQX.d.ts} +3 -3
  42. package/dist/{feedback-trajectory-BCHqzLh3.d.ts.map → feedback-trajectory-CSIkRLQX.d.ts.map} +1 -1
  43. package/dist/hosted/index.d.ts +3 -3
  44. package/dist/{index-DgbFl4cv.d.ts → index-BuHs_OnD.d.ts} +17 -12
  45. package/dist/{index-DgbFl4cv.d.ts.map → index-BuHs_OnD.d.ts.map} +1 -1
  46. package/dist/{index-D2enbqA0.d.ts → index-CNOCxBLh.d.ts} +2 -2
  47. package/dist/{index-D2enbqA0.d.ts.map → index-CNOCxBLh.d.ts.map} +1 -1
  48. package/dist/{index-DFLVtPZ9.d.ts → index-DGIzNtRv.d.ts} +2 -2
  49. package/dist/{index-DFLVtPZ9.d.ts.map → index-DGIzNtRv.d.ts.map} +1 -1
  50. package/dist/{index-DtMpBKVF.d.ts → index-D_qTihaQ.d.ts} +5 -5
  51. package/dist/{index-DtMpBKVF.d.ts.map → index-D_qTihaQ.d.ts.map} +1 -1
  52. package/dist/index.d.ts +20 -20
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +17 -17
  55. package/dist/{insight-report-Bh_8ksel.d.ts → insight-report-D5m1z0_n.d.ts} +3 -3
  56. package/dist/{insight-report-Bh_8ksel.d.ts.map → insight-report-D5m1z0_n.d.ts.map} +1 -1
  57. package/dist/ledger-core/index.js +1 -1
  58. package/dist/{ledger-core-Dxz0Rkwa.js → ledger-core-DXZIqu17.js} +3 -3
  59. package/dist/ledger-core-DXZIqu17.js.map +1 -0
  60. package/dist/{llm-client-B3WXSH5Y.js → llm-client-D3EoChAU.js} +7 -7
  61. package/dist/llm-client-D3EoChAU.js.map +1 -0
  62. package/dist/meta-eval/index.d.ts +1 -1
  63. package/dist/{mint-Ctwk079K.js → mint-DD-0oQTA.js} +2 -2
  64. package/dist/{mint-Ctwk079K.js.map → mint-DD-0oQTA.js.map} +1 -1
  65. package/dist/multishot/index.d.ts +1 -1
  66. package/dist/openapi.json +1 -1
  67. package/dist/{propose-review-control-DLXz4FCX.js → propose-review-control-BciUCZoh.js} +2 -2
  68. package/dist/{propose-review-control-DLXz4FCX.js.map → propose-review-control-BciUCZoh.js.map} +1 -1
  69. package/dist/{release-report-B_bQOHM-.d.ts → release-report-BZRWdq_t.d.ts} +3 -3
  70. package/dist/{release-report-B_bQOHM-.d.ts.map → release-report-BZRWdq_t.d.ts.map} +1 -1
  71. package/dist/{release-report-B5XPBvAU.js → release-report-Sl0xfkFv.js} +2 -2
  72. package/dist/{release-report-B5XPBvAU.js.map → release-report-Sl0xfkFv.js.map} +1 -1
  73. package/dist/{replay-DjUfTrHD.js → replay-CqOsGjzU.js} +2 -9
  74. package/dist/replay-CqOsGjzU.js.map +1 -0
  75. package/dist/{replay-BuJM6kLh.d.ts → replay-DQ-55DC_.d.ts} +4 -4
  76. package/dist/{replay-BuJM6kLh.d.ts.map → replay-DQ-55DC_.d.ts.map} +1 -1
  77. package/dist/reporting.d.ts +3 -3
  78. package/dist/reporting.js +1 -1
  79. package/dist/{researcher-C6lzl-rP.d.ts → researcher-BSiCoM1s.d.ts} +3 -3
  80. package/dist/{researcher-C6lzl-rP.d.ts.map → researcher-BSiCoM1s.d.ts.map} +1 -1
  81. package/dist/{reward-hacking-DjTi9HLb.js → reward-hacking-CyuzxKly.js} +2 -2
  82. package/dist/{reward-hacking-DjTi9HLb.js.map → reward-hacking-CyuzxKly.js.map} +1 -1
  83. package/dist/{reward-hacking-CEVVmy3h.d.ts → reward-hacking-DFgkEY4p.d.ts} +2 -2
  84. package/dist/{reward-hacking-CEVVmy3h.d.ts.map → reward-hacking-DFgkEY4p.d.ts.map} +1 -1
  85. package/dist/rl.d.ts +5 -5
  86. package/dist/rl.js +4 -4
  87. package/dist/rollout/index.d.ts +1 -1
  88. package/dist/rollout/index.js +2 -2
  89. package/dist/{rollout-8nj3mYvx.js → rollout-C2fD1cf4.js} +2 -2
  90. package/dist/{rollout-8nj3mYvx.js.map → rollout-C2fD1cf4.js.map} +1 -1
  91. package/dist/{rubric-predictive-validity-C2CthIfY.d.ts → rubric-predictive-validity-C4r4Y-q8.d.ts} +2 -2
  92. package/dist/{rubric-predictive-validity-C2CthIfY.d.ts.map → rubric-predictive-validity-C4r4Y-q8.d.ts.map} +1 -1
  93. package/dist/{run-evidence-8Ou28QSa.d.ts → run-evidence-H1vRpIdT.d.ts} +3 -3
  94. package/dist/{run-evidence-8Ou28QSa.d.ts.map → run-evidence-H1vRpIdT.d.ts.map} +1 -1
  95. package/dist/{run-record-vRgqWmJw.js → run-record-CWN8-VsV.js} +40 -14
  96. package/dist/{run-record-vRgqWmJw.js.map → run-record-CWN8-VsV.js.map} +1 -1
  97. package/dist/{run-record-Tb3TTtUn.d.ts → run-record-ooo9FWns.d.ts} +5 -9
  98. package/dist/{run-record-Tb3TTtUn.d.ts.map → run-record-ooo9FWns.d.ts.map} +1 -1
  99. package/dist/{semantic-concept-judge-DJQtFr95.js → semantic-concept-judge-Do5aM9wP.js} +3 -3
  100. package/dist/{semantic-concept-judge-DJQtFr95.js.map → semantic-concept-judge-Do5aM9wP.js.map} +1 -1
  101. package/dist/{server-Cu4M3NSO.js → server-Df00sdwz.js} +3 -3
  102. package/dist/{server-Cu4M3NSO.js.map → server-Df00sdwz.js.map} +1 -1
  103. package/dist/{single-run-lock-t1si1ob7.js → single-run-lock-D5iN0Xzb.js} +719 -187
  104. package/dist/single-run-lock-D5iN0Xzb.js.map +1 -0
  105. package/dist/{skill-usage-BiVEU0QY.d.ts → skill-usage-DtpLou9L.d.ts} +28 -9
  106. package/dist/skill-usage-DtpLou9L.d.ts.map +1 -0
  107. package/dist/{skillopt-optimization-method-Ds8J1_K8.js → skillopt-optimization-method-C4FX42dy.js} +42 -210
  108. package/dist/skillopt-optimization-method-C4FX42dy.js.map +1 -0
  109. package/dist/{skillopt-optimization-method-B7o01OdX.d.ts → skillopt-optimization-method-CvSJGdm3.d.ts} +12 -8
  110. package/dist/{skillopt-optimization-method-B7o01OdX.d.ts.map → skillopt-optimization-method-CvSJGdm3.d.ts.map} +1 -1
  111. package/dist/{summary-report-o3eJ3gxG.d.ts → summary-report-BOM6dfP7.d.ts} +2 -2
  112. package/dist/{summary-report-o3eJ3gxG.d.ts.map → summary-report-BOM6dfP7.d.ts.map} +1 -1
  113. package/dist/{tool-groups-Bqy4A3QB.d.ts → tool-groups-CMmsgTzj.d.ts} +7 -7
  114. package/dist/{tool-groups-Bqy4A3QB.d.ts.map → tool-groups-CMmsgTzj.d.ts.map} +1 -1
  115. package/dist/traces.d.ts +4 -4
  116. package/dist/traces.js +1 -1
  117. package/dist/types-BjMFz88h.d.ts.map +1 -1
  118. package/dist/{types-KEqL1pZc.d.ts → types-DcJxgsLy.d.ts} +8 -6
  119. package/dist/{types-KEqL1pZc.d.ts.map → types-DcJxgsLy.d.ts.map} +1 -1
  120. package/dist/{types-D3jh6F98.d.ts → types-y8jrxXWd.d.ts} +2 -2
  121. package/dist/{types-D3jh6F98.d.ts.map → types-y8jrxXWd.d.ts.map} +1 -1
  122. package/dist/wire/index.d.ts +1 -1
  123. package/dist/wire/index.js +1 -1
  124. package/docs/eval-surface-map.md +4 -0
  125. package/docs/trace-analysis.md +43 -32
  126. package/package.json +1 -1
  127. package/dist/benchmark-command-4c7N_rlw.js.map +0 -1
  128. package/dist/campaign-BKOtvRAB.js.map +0 -1
  129. package/dist/dspy-rlm-engine-CBFwlyaY.js.map +0 -1
  130. package/dist/external-optimizer-contracts-iK0yu4AR.d.ts.map +0 -1
  131. package/dist/ledger-core-Dxz0Rkwa.js.map +0 -1
  132. package/dist/llm-client-B3WXSH5Y.js.map +0 -1
  133. package/dist/replay-DjUfTrHD.js.map +0 -1
  134. package/dist/single-run-lock-t1si1ob7.js.map +0 -1
  135. package/dist/skill-usage-BiVEU0QY.d.ts.map +0 -1
  136. package/dist/skillopt-optimization-method-Ds8J1_K8.js.map +0 -1
@@ -1,16 +1,46 @@
1
1
  import { c as ValidationError } from "./errors-D-LKuDhb.js";
2
2
  import { c as costForTokenPricing, i as CostLedger } from "./cost-ledger-DMFxsLKr.js";
3
- import { a as appendLedgerLine, d as tryAcquireAtomicFileLock, o as tryWithLedgerFileLock, u as probeAtomicFileLock } from "./ledger-core-Dxz0Rkwa.js";
3
+ import { a as appendLedgerLine, d as tryAcquireAtomicFileLock, o as tryWithLedgerFileLock, u as probeAtomicFileLock } from "./ledger-core-DXZIqu17.js";
4
4
  import { createRequire } from "node:module";
5
5
  import { appendFileSync, existsSync, readFileSync } from "node:fs";
6
6
  import { isAbsolute, join, resolve } from "node:path";
7
7
  import { createHash, randomBytes } from "node:crypto";
8
+ import { constants as constants$1 } from "node:buffer";
8
9
  import { createServer } from "node:http";
9
10
  import { spawn } from "node:child_process";
10
11
  import { lstat, mkdtemp, open, rm, writeFile } from "node:fs/promises";
11
12
  import { tmpdir } from "node:os";
12
13
  //#region src/campaign/external-optimizer-contracts.ts
13
14
  const MAX_TIMER_DELAY_MS$1 = 2147483647;
15
+ const MAXIMUM_JSON_BYTES = Math.min(constants$1.MAX_LENGTH - 1, constants$1.MAX_STRING_LENGTH);
16
+ const DEFAULT_EXTERNAL_OPTIMIZER_PROCESS_LIMITS = Object.freeze({
17
+ maxInputBytes: 64 * 1024 * 1024,
18
+ maxResultBytes: 4 * 1024 * 1024,
19
+ maxOutputChars: 64e3
20
+ });
21
+ const DEFAULT_EXTERNAL_OPTIMIZER_CALLBACK_LIMITS = Object.freeze({
22
+ maxRequestBytes: 1e6,
23
+ maxResponseBytes: 4e6
24
+ });
25
+ function resolveExternalOptimizerProcessLimits(value, label = "external optimizer process limits") {
26
+ const resolved = {
27
+ ...DEFAULT_EXTERNAL_OPTIMIZER_PROCESS_LIMITS,
28
+ ...value
29
+ };
30
+ assertBoundedPositiveInteger(resolved.maxInputBytes, `${label}.maxInputBytes`, MAXIMUM_JSON_BYTES);
31
+ assertBoundedPositiveInteger(resolved.maxResultBytes, `${label}.maxResultBytes`, MAXIMUM_JSON_BYTES);
32
+ assertBoundedPositiveInteger(resolved.maxOutputChars, `${label}.maxOutputChars`, constants$1.MAX_STRING_LENGTH);
33
+ return resolved;
34
+ }
35
+ function resolveExternalOptimizerCallbackLimits(value, label = "external optimizer callback limits") {
36
+ const resolved = {
37
+ ...DEFAULT_EXTERNAL_OPTIMIZER_CALLBACK_LIMITS,
38
+ ...value
39
+ };
40
+ assertBoundedPositiveInteger(resolved.maxRequestBytes, `${label}.maxRequestBytes`, MAXIMUM_JSON_BYTES);
41
+ assertBoundedPositiveInteger(resolved.maxResponseBytes, `${label}.maxResponseBytes`, MAXIMUM_JSON_BYTES);
42
+ return resolved;
43
+ }
14
44
  function isCandidateText(value, maxChars) {
15
45
  return typeof value === "string" && value.trim().length > 0 && value.length <= maxChars;
16
46
  }
@@ -59,12 +89,9 @@ function removeCredentialEnvironment(environment) {
59
89
  }
60
90
  function assertExternalOptimizerModelBudget(value, label) {
61
91
  if (!value || typeof value !== "object") throw new Error(`${label} is required`);
62
- for (const [field, entry] of [
63
- ["maxRequests", value.maxRequests],
64
- ["maxRequestBytes", value.maxRequestBytes],
65
- ["maxResponseBytes", value.maxResponseBytes],
66
- ["maxOutputTokensPerRequest", value.maxOutputTokensPerRequest]
67
- ]) if (!Number.isSafeInteger(entry) || entry <= 0) throw new Error(`${label}.${field} must be a positive safe integer`);
92
+ for (const [field, entry] of [["maxRequests", value.maxRequests], ["maxOutputTokensPerRequest", value.maxOutputTokensPerRequest]]) if (!Number.isSafeInteger(entry) || entry <= 0) throw new Error(`${label}.${field} must be a positive safe integer`);
93
+ assertBoundedPositiveInteger(value.maxRequestBytes, `${label}.maxRequestBytes`, MAXIMUM_JSON_BYTES);
94
+ assertBoundedPositiveInteger(value.maxResponseBytes, `${label}.maxResponseBytes`, MAXIMUM_JSON_BYTES);
68
95
  if (value.maxReasoningTokensPerRequest !== void 0 && (!Number.isSafeInteger(value.maxReasoningTokensPerRequest) || value.maxReasoningTokensPerRequest < 0)) throw new Error(`${label}.maxReasoningTokensPerRequest must be a non-negative safe integer`);
69
96
  if (value.maxCostUsd !== void 0 && (!Number.isFinite(value.maxCostUsd) || value.maxCostUsd <= 0)) throw new Error(`${label}.maxCostUsd must be positive and finite when supplied`);
70
97
  if (value.requestTimeoutMs !== void 0 && (!Number.isSafeInteger(value.requestTimeoutMs) || value.requestTimeoutMs <= 0 || value.requestTimeoutMs > MAX_TIMER_DELAY_MS$1)) throw new Error(`${label}.requestTimeoutMs must be between 1 and ${MAX_TIMER_DELAY_MS$1}`);
@@ -77,6 +104,9 @@ function assertExternalOptimizerModelBudget(value, label) {
77
104
  function safePathComponent(value) {
78
105
  return value.replace(/[^a-zA-Z0-9._-]/g, "_");
79
106
  }
107
+ function assertBoundedPositiveInteger(value, label, maximum) {
108
+ if (!Number.isSafeInteger(value) || value <= 0 || value > maximum) throw new Error(`${label} must be an integer between 1 and ${maximum}`);
109
+ }
80
110
  function isCredentialKey(key) {
81
111
  return key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/-/g, "_").toLowerCase().split("_").some((segment) => [
82
112
  "auth",
@@ -294,6 +324,7 @@ async function startExternalOptimizerModelProxy(args) {
294
324
  let modelCallInvocations = 0;
295
325
  let executionRecordCount = 0;
296
326
  let executionSequence = 0;
327
+ const failures = [];
297
328
  let totalRequestCount = args.initialUsage?.requests ?? 0;
298
329
  let committedForBudget = args.initialUsage?.costUsd ?? 0;
299
330
  let reservedForBudget = 0;
@@ -303,7 +334,7 @@ async function startExternalOptimizerModelProxy(args) {
303
334
  const activeHandlers = /* @__PURE__ */ new Set();
304
335
  const server = createServer((request, response) => {
305
336
  if (!accepting) {
306
- sendJsonIfOpen(response, 503, { error: "external optimizer model proxy is closing" });
337
+ sendJsonIfOpen$1(response, 503, { error: "external optimizer model proxy is closing" });
307
338
  return;
308
339
  }
309
340
  const controller = new AbortController();
@@ -355,6 +386,9 @@ async function startExternalOptimizerModelProxy(args) {
355
386
  },
356
387
  recordModelCallInvocation: () => {
357
388
  modelCallInvocations += 1;
389
+ },
390
+ recordFailure: (error) => {
391
+ failures.push(error);
358
392
  }
359
393
  }).finally(() => {
360
394
  controller.signal.removeEventListener("abort", abortRequest);
@@ -370,6 +404,7 @@ async function startExternalOptimizerModelProxy(args) {
370
404
  return closePromise;
371
405
  };
372
406
  const onAbort = () => {
407
+ for (const controller of activeControllers) controller.abort(args.signal?.reason);
373
408
  close().catch(() => void 0);
374
409
  };
375
410
  args.signal?.addEventListener("abort", onAbort, { once: true });
@@ -383,6 +418,7 @@ async function startExternalOptimizerModelProxy(args) {
383
418
  requestAttempts: () => requestCount,
384
419
  successfulCompletions: () => successfulCompletionCount,
385
420
  assertExecutionComplete,
421
+ failures: () => [...failures],
386
422
  close
387
423
  };
388
424
  async function closeModelProxy() {
@@ -390,8 +426,7 @@ async function startExternalOptimizerModelProxy(args) {
390
426
  accepting = false;
391
427
  const closingServer = closeServer(server);
392
428
  server.closeIdleConnections?.();
393
- for (const controller of activeControllers) controller.abort();
394
- const [serverResult] = await Promise.allSettled([closingServer, waitForActiveHandlers(activeHandlers)]);
429
+ const [serverResult] = await Promise.allSettled([closingServer, waitForActiveHandlers$1(activeHandlers)]);
395
430
  if (activeControllers.size !== 0 || activeHandlers.size !== 0) throw new Error("external optimizer model proxy closed with active request work");
396
431
  let executionError;
397
432
  try {
@@ -409,26 +444,28 @@ async function handleModelProxyRequest(args) {
409
444
  try {
410
445
  const path = request.url ? new URL(request.url, "http://127.0.0.1").pathname : "";
411
446
  if (request.method !== "POST" || !MODEL_PROXY_PATHS.has(path)) {
412
- sendJsonIfOpen(response, 404, { error: "not found" });
447
+ sendJsonIfOpen$1(response, 404, { error: "not found" });
413
448
  return;
414
449
  }
450
+ const modelPath = path;
415
451
  if (request.headers.authorization !== `Bearer ${args.token}`) {
416
- sendJsonIfOpen(response, 401, { error: "unauthorized" });
452
+ sendJsonIfOpen$1(response, 401, { error: "unauthorized" });
417
453
  return;
418
454
  }
419
455
  const body = await readBody(request, args.args.budget.maxRequestBytes);
420
- const parsed = parseModelProxyRequest(body, args.args.model, args.args.budget);
456
+ const parsed = parseModelProxyRequest(body, modelPath, args.args.model, args.args.budget);
421
457
  const maximumUsage = conservativeMaximumUsage(body.byteLength, parsed.maxOutputTokens + (args.args.budget.maxReasoningTokensPerRequest ?? 0), args.args.budget.pricing);
422
458
  const maximumCostUsd = args.args.budget.pricing ? costForTokenPricing(args.args.budget.pricing, maximumUsage) : void 0;
423
459
  const reservation = args.nextReservation(maximumCostUsd);
424
460
  if (!reservation.accepted) {
425
- sendJsonIfOpen(response, 429, { error: reservation.reason });
461
+ sendJsonIfOpen$1(response, 429, { error: reservation.reason });
426
462
  return;
427
463
  }
428
464
  const timeout = setTimeout(() => controller.abort(), args.args.budget.requestTimeoutMs ?? 3e5);
429
465
  let chargedForBudget = maximumCostUsd;
430
466
  try {
431
467
  const paid = await args.args.costLedger.runPaidCall({
468
+ ...args.args.callId ? { callId: args.args.callId } : {},
432
469
  channel: args.args.channel ?? "optimizer",
433
470
  phase: args.args.phase,
434
471
  actor: args.args.actor,
@@ -438,18 +475,20 @@ async function handleModelProxyRequest(args) {
438
475
  customTokenPricing: args.args.budget.pricing,
439
476
  ...maximumUsage
440
477
  } } : {},
441
- execute: async () => forwardModelProxyRequest({
478
+ signal: controller.signal,
479
+ execute: async (paidCallSignal, paidCallId) => forwardModelProxyRequest({
442
480
  call: args.args.call,
481
+ callId: paidCallId,
443
482
  callRef: args.args.callRef,
444
483
  recordExecutionReceipt: args.recordExecutionReceipt,
445
484
  recordModelCallInvocation: args.recordModelCallInvocation,
446
- path,
447
- requestBody: parsed.body,
485
+ path: modelPath,
486
+ request: parsed.request,
448
487
  model: args.args.model,
449
488
  maxOutputTokens: parsed.maxOutputTokens,
450
489
  ...args.args.budget.maxReasoningTokensPerRequest === void 0 ? {} : { maxReasoningTokens: args.args.budget.maxReasoningTokensPerRequest },
451
490
  maxResponseBytes: args.args.budget.maxResponseBytes,
452
- signal: controller.signal
491
+ signal: paidCallSignal
453
492
  }),
454
493
  receipt: (result) => result.receipt,
455
494
  receiptFromError: () => ({
@@ -461,17 +500,18 @@ async function handleModelProxyRequest(args) {
461
500
  })
462
501
  });
463
502
  if (!paid.succeeded) {
503
+ args.recordFailure(paid.error);
464
504
  chargedForBudget = paid.receipt ? paid.receipt.usageUnknown || paid.receipt.costUnknown ? maximumCostUsd : paid.receipt.costUsd : void 0;
465
- sendJsonIfOpen(response, isAbortError(paid.error) ? 504 : paid.error instanceof ProviderResponseTooLargeError || paid.error instanceof MissingModelExecutionError || paid.error instanceof ModelExecutionPersistenceError || paid.error instanceof OwnerModelContractError ? 502 : 429, { error: paid.error.message });
505
+ sendJsonIfOpen$1(response, isAbortError(paid.error) ? 504 : paid.error instanceof ProviderResponseTooLargeError || paid.error instanceof MissingModelExecutionError || paid.error instanceof ModelExecutionPersistenceError || paid.error instanceof OwnerModelContractError ? 502 : 429, { error: paid.error.message });
466
506
  return;
467
507
  }
468
508
  if (paid.value.modelCallFailed) {
469
- sendJsonIfOpen(response, 502, { error: paid.value.modelCallFailed });
509
+ sendJsonIfOpen$1(response, 502, { error: paid.value.modelCallFailed });
470
510
  return;
471
511
  }
472
512
  chargedForBudget = paid.value.usageComplete ? paid.receipt.costUsd : maximumCostUsd;
473
513
  if (paid.value.usageRejected) {
474
- sendJsonIfOpen(response, 502, { error: paid.value.usageRejected });
514
+ sendJsonIfOpen$1(response, 502, { error: paid.value.usageRejected });
475
515
  return;
476
516
  }
477
517
  if (paid.value.status >= 200 && paid.value.status < 300) args.recordSuccessfulCompletion();
@@ -486,13 +526,13 @@ async function handleModelProxyRequest(args) {
486
526
  args.settleReservation(maximumCostUsd, chargedForBudget);
487
527
  }
488
528
  } catch (error) {
489
- sendJsonIfOpen(response, error instanceof RequestBodyTooLargeError ? 413 : error instanceof Error && isAbortError(error) ? 503 : 400, { error: toErrorMessage(error) });
529
+ sendJsonIfOpen$1(response, error instanceof RequestBodyTooLargeError ? 413 : error instanceof Error && isAbortError(error) ? 503 : 400, { error: toErrorMessage(error) });
490
530
  }
491
531
  }
492
- async function waitForActiveHandlers(activeHandlers) {
532
+ async function waitForActiveHandlers$1(activeHandlers) {
493
533
  while (activeHandlers.size > 0) await Promise.allSettled([...activeHandlers]);
494
534
  }
495
- function sendJsonIfOpen(response, status, body) {
535
+ function sendJsonIfOpen$1(response, status, body) {
496
536
  if (response.destroyed || response.writableEnded) return;
497
537
  sendJson(response, status, body);
498
538
  }
@@ -501,9 +541,9 @@ async function forwardModelProxyRequest(args) {
501
541
  let called;
502
542
  try {
503
543
  called = await args.call({
504
- path: args.path,
505
- model: args.model,
506
- body: freezeJsonSnapshot(args.requestBody, "optimizer model call body"),
544
+ callId: args.callId,
545
+ request: freezeJsonSnapshot(args.request, "optimizer model canonical request"),
546
+ endpointFormat: endpointFormat(args.path),
507
547
  signal: args.signal
508
548
  });
509
549
  } catch (error) {
@@ -517,18 +557,55 @@ async function forwardModelProxyRequest(args) {
517
557
  throw new MissingModelExecutionError(`optimizer model callback returned invalid execution evidence: ${toErrorMessage(error)}`);
518
558
  }
519
559
  if (called.succeeded) {
520
- if (!(called.response instanceof Response)) throw new MissingModelExecutionError("optimizer model callback success did not return a Response");
560
+ let authoritativeReceipt;
561
+ let canonicalResponse;
562
+ try {
563
+ authoritativeReceipt = snapshotModelReceipt(called.receipt, args.model);
564
+ canonicalResponse = snapshotChatResponse(called.response, args.model);
565
+ assertResponseUsageMatchesReceipt(canonicalResponse, authoritativeReceipt);
566
+ } catch (error) {
567
+ args.recordExecutionReceipt({
568
+ callId: args.callId,
569
+ callRef: args.callRef,
570
+ path: args.path,
571
+ model: args.model,
572
+ succeeded: false,
573
+ error: toErrorMessage(error),
574
+ execution
575
+ });
576
+ throw error;
577
+ }
521
578
  args.recordExecutionReceipt({
579
+ callId: args.callId,
522
580
  callRef: args.callRef,
523
581
  path: args.path,
524
582
  model: args.model,
525
583
  succeeded: true,
526
- responseStatus: called.response.status,
584
+ responseStatus: 200,
527
585
  execution
528
586
  });
587
+ const usageKnown = canonicalResponse.usage.captured !== false;
588
+ const reasoningTokens = canonicalResponse.usage.reasoningTokens ?? 0;
589
+ const completionTokens = canonicalResponse.usage.completionTokens - reasoningTokens;
590
+ const usageRejected = usageKnown && completionTokens > args.maxOutputTokens ? `optimizer model execution reported ${completionTokens} completion tokens, exceeding requested limit ${args.maxOutputTokens}` : usageKnown && args.maxReasoningTokens !== void 0 && reasoningTokens > args.maxReasoningTokens ? `optimizer model execution reported ${reasoningTokens} reasoning tokens, exceeding the declared budget ${args.maxReasoningTokens}` : void 0;
591
+ return {
592
+ status: 200,
593
+ contentType: "application/json",
594
+ body: encodeCanonicalModelResponse({
595
+ path: args.path,
596
+ callId: args.callId,
597
+ response: canonicalResponse,
598
+ receipt: authoritativeReceipt,
599
+ maxBytes: args.maxResponseBytes
600
+ }),
601
+ receipt: authoritativeReceipt,
602
+ usageComplete: authoritativeReceipt.usageUnknown !== true,
603
+ ...usageRejected ? { usageRejected } : {}
604
+ };
529
605
  } else {
530
606
  if (typeof called.error !== "string" || !called.error.trim()) throw new MissingModelExecutionError("optimizer model callback failure did not return a public error");
531
607
  args.recordExecutionReceipt({
608
+ callId: args.callId,
532
609
  callRef: args.callRef,
533
610
  path: args.path,
534
611
  model: args.model,
@@ -546,23 +623,6 @@ async function forwardModelProxyRequest(args) {
546
623
  modelCallFailed: called.error
547
624
  };
548
625
  }
549
- const response = called.response;
550
- const authoritativeReceipt = snapshotModelReceipt(called.receipt, args.model);
551
- const body = await readProviderResponseBody(response, args.maxResponseBytes);
552
- const usage = parseProviderUsage(body);
553
- const successful = response.status >= 200 && response.status < 300;
554
- const completionTokens = usage === void 0 ? 0 : usage.outputTokens - (usage.reasoningTokens ?? 0);
555
- const reasoningTokens = usage?.reasoningTokens ?? 0;
556
- const usageRejected = successful && usage !== void 0 && completionTokens > args.maxOutputTokens ? `optimizer model provider reported ${completionTokens} completion tokens, exceeding requested limit ${args.maxOutputTokens}` : successful && args.maxReasoningTokens !== void 0 && reasoningTokens > args.maxReasoningTokens ? `optimizer model provider reported ${reasoningTokens} reasoning tokens, exceeding the declared budget ${args.maxReasoningTokens}` : void 0;
557
- if (usage !== void 0) assertResponseUsageMatchesReceipt(usage, authoritativeReceipt);
558
- return {
559
- status: response.status,
560
- contentType: response.headers.get("content-type") ?? "application/json",
561
- body,
562
- receipt: authoritativeReceipt,
563
- usageComplete: authoritativeReceipt.usageUnknown !== true,
564
- ...usageRejected ? { usageRejected } : {}
565
- };
566
626
  }
567
627
  function snapshotModelReceipt(value, expectedModel) {
568
628
  let snapshot;
@@ -586,17 +646,20 @@ function deepFreezeJson(value) {
586
646
  }
587
647
  return value;
588
648
  }
589
- function assertResponseUsageMatchesReceipt(usage, receipt) {
590
- for (const field of [
591
- "inputTokens",
592
- "outputTokens",
593
- "cachedTokens",
594
- "cacheWriteTokens",
595
- "reasoningTokens"
596
- ]) if ((usage[field] ?? 0) !== (receipt[field] ?? 0)) throw new OwnerModelContractError(`optimizer model response usage disagrees with Runtime receipt at ${field}`);
597
- if (usage.actualCostUsd !== void 0 && usage.actualCostUsd !== receipt.actualCostUsd) throw new OwnerModelContractError("optimizer model response cost disagrees with Runtime receipt");
598
- }
599
- function parseModelProxyRequest(body, expectedModel, budget) {
649
+ function assertResponseUsageMatchesReceipt(response, receipt) {
650
+ if (response.usage.captured === false || receipt.usageUnknown === true) {
651
+ if (response.usage.captured !== false || receipt.usageUnknown !== true) throw new OwnerModelContractError("optimizer model response and execution receipt disagree about whether usage was captured");
652
+ } else {
653
+ const cachedTokens = response.usage.cachedPromptTokens ?? 0;
654
+ if (receipt.inputTokens + (receipt.cachedTokens ?? 0) !== response.usage.promptTokens) throw new OwnerModelContractError("optimizer model response promptTokens must equal receipt inputTokens plus cachedTokens");
655
+ if (cachedTokens !== (receipt.cachedTokens ?? 0)) throw new OwnerModelContractError("optimizer model response cachedPromptTokens disagrees with the execution receipt");
656
+ if (response.usage.completionTokens !== receipt.outputTokens) throw new OwnerModelContractError("optimizer model response completionTokens disagrees with the execution receipt");
657
+ if ((response.usage.reasoningTokens ?? 0) !== (receipt.reasoningTokens ?? 0)) throw new OwnerModelContractError("optimizer model response reasoningTokens disagrees with the execution receipt");
658
+ }
659
+ const receiptCostUsd = receipt.actualCostUsd ?? receipt.estimatedCostUsd ?? (receipt.customTokenPricing && receipt.usageUnknown !== true ? costForTokenPricing(receipt.customTokenPricing, receipt) : null);
660
+ if (response.costUsd !== receiptCostUsd) throw new OwnerModelContractError("optimizer model response billed cost disagrees with the execution receipt");
661
+ }
662
+ function parseModelProxyRequest(body, path, expectedModel, budget) {
600
663
  let value;
601
664
  try {
602
665
  value = JSON.parse(Buffer.from(body).toString("utf8"));
@@ -612,148 +675,376 @@ function parseModelProxyRequest(body, expectedModel, budget) {
612
675
  "candidate_count",
613
676
  "num_candidates"
614
677
  ]) if (value[field] !== void 0 && value[field] !== 1) throw new Error(`optimizer model request ${field} must be 1 when supplied`);
615
- const suppliedMaximums = [
616
- value.max_output_tokens,
617
- value.max_completion_tokens,
618
- value.max_tokens
619
- ].filter((maximum) => maximum !== void 0);
620
- if (suppliedMaximums.length === 0 || suppliedMaximums.some((maximum) => !Number.isSafeInteger(maximum) || maximum <= 0)) throw new Error("optimizer model request requires a positive output-token limit");
621
- const maxOutputTokens = Math.max(...suppliedMaximums);
678
+ if (value.reasoning !== void 0 || value.reasoning_effort !== void 0) throw new Error("optimizer model reasoning settings belong to the execution AgentProfile");
679
+ const maxOutputTokens = parseOutputMaximum(value, path === "/v1/responses" ? ["max_output_tokens"] : [
680
+ "max_tokens",
681
+ "max_completion_tokens",
682
+ "max_output_tokens"
683
+ ]);
622
684
  if (maxOutputTokens > budget.maxOutputTokensPerRequest) throw new Error("optimizer model request exceeds maxOutputTokensPerRequest");
623
685
  return {
624
686
  maxOutputTokens,
625
- body: value
687
+ request: freezeJsonSnapshot(path === "/v1/chat/completions" ? parseChatCompletionsRequest(value, expectedModel, maxOutputTokens) : parseResponsesRequest(value, expectedModel, maxOutputTokens), "optimizer model canonical request")
626
688
  };
627
689
  }
628
- function parseProviderUsage(body) {
629
- let value;
630
- try {
631
- value = JSON.parse(Buffer.from(body).toString("utf8"));
632
- } catch {
633
- return;
634
- }
635
- if (!isRecord(value) || !isRecord(value.usage)) return void 0;
636
- const usage = value.usage;
637
- const totalInputTokens = usage.input_tokens ?? usage.prompt_tokens;
638
- const outputTokens = usage.output_tokens ?? usage.completion_tokens;
639
- if (!Number.isSafeInteger(totalInputTokens) || totalInputTokens < 0 || !Number.isSafeInteger(outputTokens) || outputTokens < 0) return;
640
- const inputDetails = isRecord(usage.input_tokens_details) ? usage.input_tokens_details : isRecord(usage.prompt_tokens_details) ? usage.prompt_tokens_details : {};
641
- const outputDetails = isRecord(usage.output_tokens_details) ? usage.output_tokens_details : isRecord(usage.completion_tokens_details) ? usage.completion_tokens_details : {};
642
- const nestedCachedTokens = optionalTokenCount(inputDetails, ["cached_tokens", "cache_read_tokens"]);
643
- const separateCachedTokens = optionalTokenCount(usage, ["cache_read_input_tokens"]);
644
- const nestedCacheWriteTokens = optionalTokenCount(inputDetails, ["cache_write_tokens", "cache_creation_tokens"]);
645
- const separateCacheWriteTokens = optionalTokenCount(usage, ["cache_creation_input_tokens"]);
646
- const nestedReasoningTokens = optionalTokenCount(outputDetails, ["reasoning_tokens"]);
647
- const separateReasoningTokens = optionalTokenCount(usage, ["reasoning_tokens"]);
648
- const actualCostUsd = typeof usage.cost === "number" && Number.isFinite(usage.cost) && usage.cost >= 0 ? usage.cost : void 0;
649
- if (nestedCachedTokens === INVALID_TOKEN_COUNT || separateCachedTokens === INVALID_TOKEN_COUNT || nestedCacheWriteTokens === INVALID_TOKEN_COUNT || separateCacheWriteTokens === INVALID_TOKEN_COUNT || nestedReasoningTokens === INVALID_TOKEN_COUNT || separateReasoningTokens === INVALID_TOKEN_COUNT) return;
650
- const cached = selectProviderTokenClass(nestedCachedTokens, separateCachedTokens);
651
- const cacheWrite = selectProviderTokenClass(nestedCacheWriteTokens, separateCacheWriteTokens);
652
- const reasoning = selectProviderTokenClass(nestedReasoningTokens, separateReasoningTokens);
653
- if (cached === void 0 || cacheWrite === void 0 || reasoning === void 0) return void 0;
654
- const classifiedInputTokens = (cached.includedInTotal ? cached.count : 0) + (cacheWrite.includedInTotal ? cacheWrite.count : 0);
655
- if (classifiedInputTokens > totalInputTokens || reasoning.count > outputTokens) return;
690
+ function parseChatCompletionsRequest(body, model, maxTokens) {
691
+ assertAllowedKeys(body, [
692
+ "model",
693
+ "messages",
694
+ "max_tokens",
695
+ "max_completion_tokens",
696
+ "max_output_tokens",
697
+ "temperature",
698
+ "thinking",
699
+ "response_format",
700
+ "stream",
701
+ "n",
702
+ "best_of",
703
+ "candidate_count",
704
+ "num_candidates"
705
+ ]);
656
706
  return {
657
- inputTokens: totalInputTokens - classifiedInputTokens,
658
- outputTokens,
659
- ...cached.supplied ? { cachedTokens: cached.count } : {},
660
- ...cacheWrite.supplied ? { cacheWriteTokens: cacheWrite.count } : {},
661
- ...reasoning.supplied ? { reasoningTokens: reasoning.count } : {},
662
- ...actualCostUsd === void 0 ? {} : { actualCostUsd }
707
+ model,
708
+ messages: parseChatMessages(body.messages),
709
+ maxTokens,
710
+ ...parseTemperature(body.temperature),
711
+ ...parseThinking(body.thinking),
712
+ ...parseChatResponseFormat(body.response_format)
663
713
  };
664
714
  }
665
- /**
666
- * OpenAI reports a cache class inside the input total; Anthropic reports it
667
- * beside the input total. When both equivalent forms are present they must
668
- * agree, and the nested normalized form wins.
669
- */
670
- function selectProviderTokenClass(nested, separate) {
671
- if (nested !== void 0 && separate !== void 0 && nested > 0 && separate > 0) {
672
- if (nested !== separate) return void 0;
673
- return {
674
- count: nested,
675
- supplied: true,
676
- includedInTotal: true
677
- };
678
- }
679
- if (nested !== void 0 && nested > 0) return {
680
- count: nested,
681
- supplied: true,
682
- includedInTotal: true
715
+ function parseResponsesRequest(body, model, maxTokens) {
716
+ assertAllowedKeys(body, [
717
+ "model",
718
+ "input",
719
+ "instructions",
720
+ "max_output_tokens",
721
+ "temperature",
722
+ "thinking",
723
+ "text",
724
+ "stream",
725
+ "n",
726
+ "best_of",
727
+ "candidate_count",
728
+ "num_candidates"
729
+ ]);
730
+ const instructions = body.instructions;
731
+ if (instructions !== void 0 && typeof instructions !== "string") throw new Error("optimizer Responses instructions must be a string");
732
+ return {
733
+ model,
734
+ messages: [...instructions === void 0 ? [] : [{
735
+ role: "system",
736
+ content: instructions
737
+ }], ...parseResponsesInput(body.input)],
738
+ maxTokens,
739
+ ...parseTemperature(body.temperature),
740
+ ...parseThinking(body.thinking),
741
+ ...parseResponsesTextFormat(body.text)
683
742
  };
684
- if (separate !== void 0 && separate > 0) return {
685
- count: separate,
686
- supplied: true,
687
- includedInTotal: false
743
+ }
744
+ function parseOutputMaximum(body, fields) {
745
+ const supplied = fields.flatMap((field) => body[field] === void 0 ? [] : [{
746
+ field,
747
+ value: body[field]
748
+ }]);
749
+ if (supplied.length === 0 || supplied.some(({ value }) => !Number.isSafeInteger(value) || value <= 0)) throw new Error("optimizer model request requires a positive output-token limit");
750
+ if (new Set(supplied.map(({ value }) => value)).size !== 1) throw new Error("optimizer model request supplied conflicting output-token limits");
751
+ return supplied[0].value;
752
+ }
753
+ function parseTemperature(value) {
754
+ if (value === void 0) return {};
755
+ if (typeof value !== "number" || !Number.isFinite(value)) throw new Error("optimizer model request temperature must be finite");
756
+ return { temperature: value };
757
+ }
758
+ function parseThinking(value) {
759
+ if (value === void 0) return {};
760
+ if (!isRecord(value)) throw new Error("optimizer model request thinking must be an object");
761
+ assertAllowedKeys(value, ["type"], "optimizer model request thinking");
762
+ if (value.type !== "enabled" && value.type !== "disabled") throw new Error("optimizer model request thinking.type must be enabled or disabled");
763
+ return { thinking: value.type };
764
+ }
765
+ function parseChatMessages(value) {
766
+ if (!Array.isArray(value)) throw new Error("optimizer chat request messages must be an array");
767
+ return value.map((message, index) => parseMessage(message, `optimizer chat message ${index}`));
768
+ }
769
+ function parseResponsesInput(value) {
770
+ if (typeof value === "string") return [{
771
+ role: "user",
772
+ content: value
773
+ }];
774
+ if (!Array.isArray(value) || value.length === 0) throw new Error("optimizer Responses request requires non-empty input");
775
+ return value.map((message, index) => {
776
+ if (!isRecord(message)) throw new Error(`optimizer Responses input ${index} must be a message object`);
777
+ assertAllowedKeys(message, [
778
+ "type",
779
+ "role",
780
+ "content"
781
+ ], `optimizer Responses input ${index}`);
782
+ if (message.type !== void 0 && message.type !== "message") throw new Error(`optimizer Responses input ${index} type must be message`);
783
+ return parseMessage(message, `optimizer Responses input ${index}`, true);
784
+ });
785
+ }
786
+ function parseMessage(value, label, responsesContent = false) {
787
+ if (!isRecord(value)) throw new Error(`${label} must be an object`);
788
+ assertAllowedKeys(value, responsesContent ? [
789
+ "type",
790
+ "role",
791
+ "content"
792
+ ] : ["role", "content"], label);
793
+ if (value.role !== "system" && value.role !== "user" && value.role !== "assistant") throw new Error(`${label} role must be system, user, or assistant`);
794
+ return {
795
+ role: value.role,
796
+ content: parseMessageContent(value.content, label, responsesContent)
688
797
  };
798
+ }
799
+ function parseMessageContent(value, label, responsesContent) {
800
+ if (typeof value === "string") return value;
801
+ if (!Array.isArray(value) || value.length === 0) throw new Error(`${label} content must be text or a non-empty content array`);
802
+ return value.map((part, partIndex) => {
803
+ if (!isRecord(part)) throw new Error(`${label} content ${partIndex} must be an object`);
804
+ const partLabel = `${label} content ${partIndex}`;
805
+ if (part.type === "text" || responsesContent && part.type === "input_text") {
806
+ assertAllowedKeys(part, ["type", "text"], partLabel);
807
+ if (typeof part.text !== "string") throw new Error(`${partLabel} text must be a string`);
808
+ return {
809
+ type: "text",
810
+ text: part.text
811
+ };
812
+ }
813
+ if (responsesContent && part.type === "output_text") {
814
+ assertAllowedKeys(part, ["type", "text"], partLabel);
815
+ if (typeof part.text !== "string") throw new Error(`${partLabel} text must be a string`);
816
+ return {
817
+ type: "text",
818
+ text: part.text
819
+ };
820
+ }
821
+ if (part.type === "image_url") {
822
+ assertAllowedKeys(part, ["type", "image_url"], partLabel);
823
+ if (!isRecord(part.image_url) || typeof part.image_url.url !== "string") throw new Error(`${partLabel} image_url must contain a URL string`);
824
+ assertAllowedKeys(part.image_url, ["url", "detail"], `${partLabel} image_url`);
825
+ const detail = parseImageDetail(part.image_url.detail, partLabel);
826
+ return {
827
+ type: "image_url",
828
+ image_url: {
829
+ url: part.image_url.url,
830
+ ...detail
831
+ }
832
+ };
833
+ }
834
+ if (responsesContent && part.type === "input_image") {
835
+ assertAllowedKeys(part, [
836
+ "type",
837
+ "image_url",
838
+ "detail"
839
+ ], partLabel);
840
+ if (typeof part.image_url !== "string") throw new Error(`${partLabel} image_url must be a URL string`);
841
+ const detail = parseImageDetail(part.detail, partLabel);
842
+ return {
843
+ type: "image_url",
844
+ image_url: {
845
+ url: part.image_url,
846
+ ...detail
847
+ }
848
+ };
849
+ }
850
+ throw new Error(`${partLabel} uses an unsupported content type`);
851
+ });
852
+ }
853
+ function parseImageDetail(value, label) {
854
+ if (value === void 0) return {};
855
+ if (value !== "auto" && value !== "low" && value !== "high") throw new Error(`${label} image detail must be auto, low, or high`);
856
+ return { detail: value };
857
+ }
858
+ function parseChatResponseFormat(value) {
859
+ if (value === void 0) return {};
860
+ if (!isRecord(value) || typeof value.type !== "string") throw new Error("optimizer chat response_format must be an object with a type");
861
+ if (value.type === "text") {
862
+ assertAllowedKeys(value, ["type"], "optimizer chat response_format");
863
+ return {};
864
+ }
865
+ if (value.type === "json_object") {
866
+ assertAllowedKeys(value, ["type"], "optimizer chat response_format");
867
+ return { jsonMode: true };
868
+ }
869
+ if (value.type !== "json_schema") throw new Error("optimizer chat response_format type is unsupported");
870
+ assertAllowedKeys(value, ["type", "json_schema"], "optimizer chat response_format");
871
+ return { jsonSchema: parseJsonSchema(value.json_schema, "optimizer chat response_format") };
872
+ }
873
+ function parseResponsesTextFormat(value) {
874
+ if (value === void 0) return {};
875
+ if (!isRecord(value)) throw new Error("optimizer Responses text must be an object");
876
+ assertAllowedKeys(value, ["format"], "optimizer Responses text");
877
+ if (value.format === void 0) return {};
878
+ if (!isRecord(value.format) || typeof value.format.type !== "string") throw new Error("optimizer Responses text.format must be an object with a type");
879
+ const format = value.format;
880
+ if (format.type === "text") {
881
+ assertAllowedKeys(format, ["type"], "optimizer Responses text.format");
882
+ return {};
883
+ }
884
+ if (format.type === "json_object") {
885
+ assertAllowedKeys(format, ["type"], "optimizer Responses text.format");
886
+ return { jsonMode: true };
887
+ }
888
+ if (format.type !== "json_schema") throw new Error("optimizer Responses text.format type is unsupported");
889
+ return { jsonSchema: parseJsonSchema(format, "optimizer Responses text.format", true) };
890
+ }
891
+ function parseJsonSchema(value, label, flat = false) {
892
+ if (!isRecord(value)) throw new Error(`${label} JSON schema must be an object`);
893
+ assertAllowedKeys(value, [
894
+ "name",
895
+ "schema",
896
+ "strict",
897
+ ...flat ? ["type"] : []
898
+ ], label);
899
+ if (typeof value.name !== "string" || !value.name.trim()) throw new Error(`${label} JSON schema name must be non-empty`);
900
+ if (!isRecord(value.schema)) throw new Error(`${label} JSON schema body must be an object`);
901
+ if (value.strict !== void 0 && value.strict !== true) throw new Error(`${label} JSON schema strict must be true when supplied`);
689
902
  return {
690
- count: 0,
691
- supplied: nested !== void 0 || separate !== void 0,
692
- includedInTotal: nested !== void 0
903
+ name: value.name,
904
+ schema: freezeJsonSnapshot(value.schema, `${label} schema`)
693
905
  };
694
906
  }
695
- const INVALID_TOKEN_COUNT = Symbol("invalid-token-count");
696
- function optionalTokenCount(details, fields) {
697
- let found;
698
- for (const field of fields) {
699
- const value = details[field];
700
- if (value === void 0 || value === null) continue;
701
- if (!Number.isSafeInteger(value) || value < 0) return INVALID_TOKEN_COUNT;
702
- if (found !== void 0 && found !== value) return INVALID_TOKEN_COUNT;
703
- found = value;
907
+ function assertAllowedKeys(value, allowed, label = "optimizer model request") {
908
+ const allowedSet = new Set(allowed);
909
+ const unknown = Object.keys(value).filter((key) => !allowedSet.has(key));
910
+ if (unknown.length > 0) throw new Error(`${label} contains unsupported fields: ${unknown.join(", ")}`);
911
+ }
912
+ function snapshotChatResponse(value, expectedModel) {
913
+ if (!isRecord(value)) throw new OwnerModelContractError("optimizer model callback response must be an object");
914
+ if (typeof value.content !== "string") throw new OwnerModelContractError("optimizer model callback response content must be a string");
915
+ if (value.model !== expectedModel) throw new OwnerModelContractError(`optimizer model callback response used '${String(value.model)}' instead of '${expectedModel}'`);
916
+ if (!isNonnegativeFiniteNumber(value.durationMs)) throw new OwnerModelContractError("optimizer model callback response durationMs must be finite and non-negative");
917
+ if (value.costUsd !== null && !isNonnegativeFiniteNumber(value.costUsd)) throw new OwnerModelContractError("optimizer model callback response costUsd must be null or finite and non-negative");
918
+ if (value.finishReason !== void 0 && value.finishReason !== null && typeof value.finishReason !== "string") throw new OwnerModelContractError("optimizer model callback response finishReason must be a string or null");
919
+ if (value.contentEmpty !== void 0 && (typeof value.contentEmpty !== "boolean" || value.contentEmpty !== (value.content.trim().length === 0))) throw new OwnerModelContractError("optimizer model callback response contentEmpty must match content");
920
+ if (!isRecord(value.raw)) throw new OwnerModelContractError("optimizer model callback response raw must be an object");
921
+ if (!isRecord(value.usage)) throw new OwnerModelContractError("optimizer model callback response usage must be an object");
922
+ const usage = value.usage;
923
+ for (const field of [
924
+ "promptTokens",
925
+ "completionTokens",
926
+ "totalTokens"
927
+ ]) if (!isNonnegativeSafeInteger(usage[field])) throw new OwnerModelContractError(`optimizer model callback response usage.${field} must be a non-negative safe integer`);
928
+ const promptTokens = usage.promptTokens;
929
+ const completionTokens = usage.completionTokens;
930
+ const totalTokens = usage.totalTokens;
931
+ if (totalTokens !== promptTokens + completionTokens) throw new OwnerModelContractError("optimizer model callback response totalTokens must equal promptTokens plus completionTokens");
932
+ if (usage.captured !== void 0 && typeof usage.captured !== "boolean") throw new OwnerModelContractError("optimizer model callback response usage.captured must be a boolean");
933
+ if (usage.cachedPromptTokens !== void 0 && (!isNonnegativeSafeInteger(usage.cachedPromptTokens) || usage.cachedPromptTokens > promptTokens)) throw new OwnerModelContractError("optimizer model callback response cachedPromptTokens must be a subset of promptTokens");
934
+ if (usage.reasoningTokens !== void 0 && (!isNonnegativeSafeInteger(usage.reasoningTokens) || usage.reasoningTokens > completionTokens)) throw new OwnerModelContractError("optimizer model callback response reasoningTokens must be a subset of completionTokens");
935
+ let raw;
936
+ try {
937
+ raw = freezeJsonSnapshot(value.raw, "optimizer model callback canonical response.raw");
938
+ } catch (error) {
939
+ throw new OwnerModelContractError(`optimizer model callback returned invalid raw response evidence: ${toErrorMessage(error)}`);
704
940
  }
705
- return found;
941
+ return deepFreezeJson({
942
+ content: value.content,
943
+ usage: {
944
+ promptTokens,
945
+ completionTokens,
946
+ totalTokens,
947
+ ...usage.captured === void 0 ? {} : { captured: usage.captured },
948
+ ...usage.reasoningTokens === void 0 ? {} : { reasoningTokens: usage.reasoningTokens },
949
+ ...usage.cachedPromptTokens === void 0 ? {} : { cachedPromptTokens: usage.cachedPromptTokens }
950
+ },
951
+ costUsd: value.costUsd,
952
+ model: value.model,
953
+ durationMs: value.durationMs,
954
+ ...value.finishReason === void 0 ? {} : { finishReason: value.finishReason },
955
+ ...value.contentEmpty === void 0 ? {} : { contentEmpty: value.contentEmpty },
956
+ raw
957
+ });
958
+ }
959
+ function encodeCanonicalModelResponse(args) {
960
+ const usage = encodeProtocolUsage(args.path, args.response, args.receipt);
961
+ const finishReason = args.response.finishReason === void 0 ? "stop" : args.response.finishReason;
962
+ const body = args.path === "/v1/chat/completions" ? {
963
+ id: `chatcmpl-${args.callId}`,
964
+ object: "chat.completion",
965
+ created: 0,
966
+ model: args.response.model,
967
+ choices: [{
968
+ index: 0,
969
+ message: {
970
+ role: "assistant",
971
+ content: args.response.content
972
+ },
973
+ finish_reason: finishReason
974
+ }],
975
+ ...usage ? { usage } : {}
976
+ } : {
977
+ id: `resp-${args.callId}`,
978
+ object: "response",
979
+ created_at: 0,
980
+ model: args.response.model,
981
+ status: finishReason === "length" ? "incomplete" : "completed",
982
+ error: null,
983
+ incomplete_details: finishReason === "length" ? { reason: "max_output_tokens" } : null,
984
+ output: [{
985
+ id: `msg-${args.callId}`,
986
+ type: "message",
987
+ status: finishReason === "length" ? "incomplete" : "completed",
988
+ role: "assistant",
989
+ content: [{
990
+ type: "output_text",
991
+ text: args.response.content,
992
+ annotations: []
993
+ }]
994
+ }],
995
+ output_text: args.response.content,
996
+ ...usage ? { usage } : {}
997
+ };
998
+ const encoded = new TextEncoder().encode(JSON.stringify(body));
999
+ if (encoded.byteLength > args.maxBytes) throw new ProviderResponseTooLargeError();
1000
+ return encoded;
1001
+ }
1002
+ function endpointFormat(path) {
1003
+ return path === "/v1/responses" ? "responses" : "chat-completions";
1004
+ }
1005
+ function encodeProtocolUsage(path, response, receipt) {
1006
+ if (response.usage.captured === false) return void 0;
1007
+ const inputDetails = {
1008
+ ...receipt.cachedTokens === void 0 ? {} : { cached_tokens: receipt.cachedTokens },
1009
+ ...receipt.cacheWriteTokens === void 0 ? {} : path === "/v1/chat/completions" ? { cache_creation_tokens: receipt.cacheWriteTokens } : { cache_write_tokens: receipt.cacheWriteTokens }
1010
+ };
1011
+ const outputDetails = { ...receipt.reasoningTokens === void 0 ? {} : { reasoning_tokens: receipt.reasoningTokens } };
1012
+ return path === "/v1/chat/completions" ? {
1013
+ prompt_tokens: response.usage.promptTokens,
1014
+ completion_tokens: response.usage.completionTokens,
1015
+ total_tokens: response.usage.totalTokens,
1016
+ ...Object.keys(inputDetails).length > 0 ? { prompt_tokens_details: inputDetails } : {},
1017
+ ...Object.keys(outputDetails).length > 0 ? { completion_tokens_details: outputDetails } : {},
1018
+ ...receipt.actualCostUsd === void 0 ? {} : { cost: receipt.actualCostUsd }
1019
+ } : {
1020
+ input_tokens: response.usage.promptTokens,
1021
+ output_tokens: response.usage.completionTokens,
1022
+ total_tokens: response.usage.totalTokens,
1023
+ ...Object.keys(inputDetails).length > 0 ? { input_tokens_details: inputDetails } : {},
1024
+ ...Object.keys(outputDetails).length > 0 ? { output_tokens_details: outputDetails } : {},
1025
+ ...receipt.actualCostUsd === void 0 ? {} : { cost: receipt.actualCostUsd }
1026
+ };
1027
+ }
1028
+ function isNonnegativeFiniteNumber(value) {
1029
+ return typeof value === "number" && Number.isFinite(value) && value >= 0;
1030
+ }
1031
+ function isNonnegativeSafeInteger(value) {
1032
+ return Number.isSafeInteger(value) && value >= 0;
706
1033
  }
707
1034
  function conservativeMaximumUsage(inputTokenUpperBound, outputTokenUpperBound, pricing) {
708
1035
  if (pricing === void 0) return {
709
1036
  inputTokens: inputTokenUpperBound,
710
1037
  outputTokens: outputTokenUpperBound
711
1038
  };
712
- const inputRates = [
713
- pricing.inputUsdPerMillion,
714
- pricing.cachedInputUsdPerMillion ?? pricing.inputUsdPerMillion,
715
- pricing.cacheWriteUsdPerMillion ?? pricing.inputUsdPerMillion
716
- ];
1039
+ const inputRates = [pricing.inputUsdPerMillion, pricing.cachedInputUsdPerMillion ?? pricing.inputUsdPerMillion];
717
1040
  const mostExpensiveInputClass = inputRates.indexOf(Math.max(...inputRates));
718
1041
  return {
719
1042
  inputTokens: mostExpensiveInputClass === 0 ? inputTokenUpperBound : 0,
720
1043
  ...mostExpensiveInputClass === 1 ? { cachedTokens: inputTokenUpperBound } : {},
721
- ...mostExpensiveInputClass === 2 ? { cacheWriteTokens: inputTokenUpperBound } : {},
1044
+ cacheWriteTokens: inputTokenUpperBound,
722
1045
  outputTokens: outputTokenUpperBound
723
1046
  };
724
1047
  }
725
- async function readProviderResponseBody(response, maxBytes) {
726
- const declaredLength = response.headers.get("content-length");
727
- if (declaredLength !== null) {
728
- const parsedLength = Number(declaredLength);
729
- if (Number.isFinite(parsedLength) && parsedLength > maxBytes) throw new ProviderResponseTooLargeError();
730
- }
731
- if (!response.body) return /* @__PURE__ */ new Uint8Array();
732
- const reader = response.body.getReader();
733
- const chunks = [];
734
- let totalBytes = 0;
735
- try {
736
- while (true) {
737
- const next = await reader.read();
738
- if (next.done) break;
739
- totalBytes += next.value.byteLength;
740
- if (totalBytes > maxBytes) {
741
- await reader.cancel();
742
- throw new ProviderResponseTooLargeError();
743
- }
744
- chunks.push(next.value);
745
- }
746
- } finally {
747
- reader.releaseLock();
748
- }
749
- const body = new Uint8Array(totalBytes);
750
- let offset = 0;
751
- for (const chunk of chunks) {
752
- body.set(chunk, offset);
753
- offset += chunk.byteLength;
754
- }
755
- return body;
756
- }
757
1048
  function assertModelProxyConfig(args) {
758
1049
  for (const [label, value] of [
759
1050
  ["model", args.model],
@@ -763,6 +1054,10 @@ function assertModelProxyConfig(args) {
763
1054
  if (typeof args.call !== "function") throw new Error("external optimizer model proxy: call must be a function");
764
1055
  if (typeof args.callRef !== "string" || !args.callRef.trim() || args.callRef.trim() !== args.callRef) throw new Error("external optimizer model proxy: callRef must be trimmed and non-empty");
765
1056
  if (typeof args.recordExecution !== "function") throw new Error("external optimizer model proxy: recordExecution must be a function");
1057
+ if (args.callId !== void 0) {
1058
+ if (typeof args.callId !== "string" || !args.callId.trim() || args.callId !== args.callId.trim()) throw new Error("external optimizer model proxy: callId must be trimmed and non-empty");
1059
+ if (args.budget.maxRequests !== 1) throw new Error("external optimizer model proxy: callId requires budget.maxRequests to be 1");
1060
+ }
766
1061
  assertExternalOptimizerModelBudget(args.budget, "external optimizer model proxy: budget");
767
1062
  if (args.tags !== void 0) {
768
1063
  for (const [key, value] of Object.entries(args.tags)) if (!key.trim() || key.trim() !== key || !value.trim() || value.trim() !== value) throw new Error("external optimizer model proxy: tags must be trimmed and non-empty");
@@ -845,16 +1140,13 @@ function toError(cause) {
845
1140
  }
846
1141
  //#endregion
847
1142
  //#region src/campaign/external-optimizer-subprocess.ts
848
- const MAX_PROCESS_OUTPUT_CHARS = 64e3;
849
- const MAX_PROCESS_INPUT_BYTES = 64 * 1024 * 1024;
850
- const MAX_PROCESS_RESULT_BYTES = 4 * 1024 * 1024;
851
1143
  const PROCESS_TERMINATION_GRACE_MS = 5e3;
852
1144
  const PROCESS_TERMINATION_POLL_MS = 25;
853
1145
  const PROCESS_KILL_WAIT_MS = 1e3;
854
1146
  const MAX_TIMER_DELAY_MS = 2147483647;
855
- const PROCESS_OUTPUT_HEAD_CHARS = MAX_PROCESS_OUTPUT_CHARS / 2;
856
1147
  async function runExternalOptimizerProcess(args) {
857
1148
  if (!Number.isSafeInteger(args.timeoutMs) || args.timeoutMs <= 0 || args.timeoutMs > MAX_TIMER_DELAY_MS) throw new Error(`${args.label} timeoutMs must be between 1 and ${MAX_TIMER_DELAY_MS}`);
1149
+ const limits = resolveExternalOptimizerProcessLimits(args.runner?.limits);
858
1150
  if (process.platform === "win32") throw new Error(`${args.label} requires POSIX process-group cleanup; run the optimizer through WSL or Linux`);
859
1151
  throwIfAborted(args.signal, args.label);
860
1152
  const dir = await mkdtemp(join(tmpdir(), args.tempPrefix));
@@ -867,11 +1159,12 @@ async function runExternalOptimizerProcess(args) {
867
1159
  if (serializedInput === void 0) throw new Error(`${args.label} input must be JSON-serializable`);
868
1160
  const inputJson = `${serializedInput}\n`;
869
1161
  const inputBytes = Buffer.byteLength(inputJson);
870
- if (inputBytes > MAX_PROCESS_INPUT_BYTES) throw new Error(`${args.label} input exceeds ${MAX_PROCESS_INPUT_BYTES} bytes (${inputBytes})`);
1162
+ if (inputBytes > limits.maxInputBytes) throw new Error(`${args.label} input exceeds ${limits.maxInputBytes} bytes (${inputBytes})`);
871
1163
  await writeFile(inputPath, inputJson);
872
1164
  const command = resolveRunnerCommand(args.runner?.command ?? "python");
873
1165
  const commandArgs = [
874
1166
  ...args.runner?.args ?? ["-m", args.module],
1167
+ ...args.additionalArgs ?? [],
875
1168
  "--input",
876
1169
  inputPath,
877
1170
  "--output",
@@ -884,10 +1177,11 @@ async function runExternalOptimizerProcess(args) {
884
1177
  cwd: dir,
885
1178
  env: args.runner?.env,
886
1179
  timeoutMs: args.timeoutMs,
1180
+ maxOutputChars: limits.maxOutputChars,
887
1181
  signal: args.signal
888
1182
  });
889
1183
  throwIfAborted(args.signal, args.label);
890
- const raw = JSON.parse(await readBoundedTextFile(outputPath, MAX_PROCESS_RESULT_BYTES, `${args.label} output`));
1184
+ const raw = JSON.parse(await readBoundedTextFile(outputPath, limits.maxResultBytes, `${args.label} output`));
891
1185
  throwIfAborted(args.signal, args.label);
892
1186
  if (!isRecord(raw)) throw new Error(`${args.label} output must be a JSON object`);
893
1187
  return raw;
@@ -964,10 +1258,10 @@ function runProcess(args) {
964
1258
  args.signal?.addEventListener("abort", onAbort, { once: true });
965
1259
  if (args.signal?.aborted) onAbort();
966
1260
  child.stdout.on("data", (chunk) => {
967
- appendProcessOutput(stdout, chunk);
1261
+ appendProcessOutput(stdout, chunk, args.maxOutputChars);
968
1262
  });
969
1263
  child.stderr.on("data", (chunk) => {
970
- appendProcessOutput(stderr, chunk);
1264
+ appendProcessOutput(stderr, chunk, args.maxOutputChars);
971
1265
  });
972
1266
  child.on("error", (error) => {
973
1267
  finish(/* @__PURE__ */ new Error(`${args.label} could not start: ${error.message}`));
@@ -1059,21 +1353,23 @@ function createProcessOutputCapture() {
1059
1353
  totalChars: 0
1060
1354
  };
1061
1355
  }
1062
- function appendProcessOutput(capture, chunk) {
1356
+ function appendProcessOutput(capture, chunk, maxOutputChars) {
1063
1357
  const text = chunk.toString();
1064
1358
  capture.totalChars += text.length;
1359
+ const headChars = Math.floor(maxOutputChars / 2);
1360
+ const tailChars = maxOutputChars - headChars;
1065
1361
  if (capture.full !== void 0) {
1066
1362
  const combined = `${capture.full}${text}`;
1067
- if (combined.length <= MAX_PROCESS_OUTPUT_CHARS) {
1363
+ if (combined.length <= maxOutputChars) {
1068
1364
  capture.full = combined;
1069
1365
  return;
1070
1366
  }
1071
- capture.head = combined.slice(0, PROCESS_OUTPUT_HEAD_CHARS);
1072
- capture.tail = combined.slice(-32e3);
1367
+ capture.head = combined.slice(0, headChars);
1368
+ capture.tail = combined.slice(-tailChars);
1073
1369
  capture.full = void 0;
1074
1370
  return;
1075
1371
  }
1076
- capture.tail = `${capture.tail}${text}`.slice(-32e3);
1372
+ capture.tail = `${capture.tail}${text}`.slice(-tailChars);
1077
1373
  }
1078
1374
  function summarizeProcessOutput(capture, max = 4e3) {
1079
1375
  const compact = (capture.full ?? `${capture.head}\n...[${capture.totalChars - capture.head.length - capture.tail.length} chars omitted]...\n${capture.tail}`).trim().replace(/\s+/g, " ");
@@ -1083,6 +1379,242 @@ function summarizeProcessOutput(capture, max = 4e3) {
1083
1379
  return `${compact.slice(0, headChars)}...[${compact.length - max} chars omitted]...${compact.slice(-tailChars)}`;
1084
1380
  }
1085
1381
  //#endregion
1382
+ //#region src/campaign/external-optimizer-callback.ts
1383
+ async function startExternalOptimizerCallback(args) {
1384
+ assertCallbackConfig(args);
1385
+ const limits = resolveExternalOptimizerCallbackLimits(args.limits);
1386
+ args.signal?.throwIfAborted();
1387
+ let evaluations = 0;
1388
+ let accepting = true;
1389
+ let closePromise;
1390
+ const activeControllers = /* @__PURE__ */ new Set();
1391
+ const activeHandlers = /* @__PURE__ */ new Set();
1392
+ const proposedCandidateHashes = /* @__PURE__ */ new Set();
1393
+ let observationSequence = 0;
1394
+ const server = createServer((request, response) => {
1395
+ if (!accepting) {
1396
+ sendJsonIfOpen(response, 503, { error: "external optimizer callback is closing" });
1397
+ return;
1398
+ }
1399
+ const controller = new AbortController();
1400
+ const abortRequest = () => {
1401
+ request.destroy();
1402
+ response.destroy();
1403
+ };
1404
+ activeControllers.add(controller);
1405
+ controller.signal.addEventListener("abort", abortRequest, { once: true });
1406
+ let handler;
1407
+ handler = handleCallback(request, response, controller.signal, args, limits, () => {
1408
+ const accepted = args.acceptEvaluation ? args.acceptEvaluation() : evaluations + 1;
1409
+ if (accepted === void 0) return void 0;
1410
+ if (!Number.isSafeInteger(accepted) || accepted <= 0) throw new Error("external optimizer callback: invalid accepted evaluation count");
1411
+ if (accepted > args.maxEvaluations) return void 0;
1412
+ evaluations += 1;
1413
+ return accepted;
1414
+ }, (observation) => {
1415
+ if (!args.observe) return;
1416
+ args.observe({
1417
+ ...observation,
1418
+ sequence: ++observationSequence
1419
+ });
1420
+ }, proposedCandidateHashes).finally(() => {
1421
+ controller.signal.removeEventListener("abort", abortRequest);
1422
+ activeControllers.delete(controller);
1423
+ activeHandlers.delete(handler);
1424
+ });
1425
+ activeHandlers.add(handler);
1426
+ handler.catch(() => void 0);
1427
+ });
1428
+ const port = await listenLocal(server);
1429
+ const close = () => {
1430
+ closePromise ??= closeCallbackServer();
1431
+ return closePromise;
1432
+ };
1433
+ const onAbort = () => {
1434
+ close().catch(() => void 0);
1435
+ };
1436
+ args.signal?.addEventListener("abort", onAbort, { once: true });
1437
+ if (args.signal?.aborted) onAbort();
1438
+ return {
1439
+ url: `http://127.0.0.1:${port}/evaluate`,
1440
+ token: args.token,
1441
+ evaluations: () => evaluations,
1442
+ close
1443
+ };
1444
+ async function closeCallbackServer() {
1445
+ args.signal?.removeEventListener("abort", onAbort);
1446
+ accepting = false;
1447
+ const closingServer = closeServer(server);
1448
+ server.closeIdleConnections?.();
1449
+ for (const controller of activeControllers) controller.abort();
1450
+ const [serverResult] = await Promise.allSettled([closingServer, waitForActiveHandlers(activeHandlers)]);
1451
+ if (activeControllers.size !== 0 || activeHandlers.size !== 0) throw new Error("external optimizer callback closed with active request work");
1452
+ if (serverResult?.status === "rejected") throw serverResult.reason;
1453
+ }
1454
+ }
1455
+ async function handleCallback(request, response, signal, args, limits, nextEvaluation, observe, proposedCandidateHashes) {
1456
+ try {
1457
+ if (request.method !== "POST" || request.url !== "/evaluate") {
1458
+ sendJsonIfOpen(response, 404, { error: "not found" });
1459
+ return;
1460
+ }
1461
+ if (request.headers.authorization !== `Bearer ${args.token}`) {
1462
+ sendJsonIfOpen(response, 401, { error: "unauthorized" });
1463
+ return;
1464
+ }
1465
+ let body;
1466
+ try {
1467
+ body = await readJson(request, limits.maxRequestBytes);
1468
+ } catch {
1469
+ observe({
1470
+ kind: "refusal",
1471
+ reason: "invalid-request"
1472
+ });
1473
+ sendJsonIfOpen(response, 400, { error: "request body must be bounded valid JSON" });
1474
+ return;
1475
+ }
1476
+ if (!isRecord(body) || !isExternalTextCandidate(body.candidate) || typeof body.exampleId !== "string") {
1477
+ observe({
1478
+ kind: "refusal",
1479
+ reason: "invalid-request"
1480
+ });
1481
+ sendJsonIfOpen(response, 400, { error: "candidate and exampleId are required strings" });
1482
+ return;
1483
+ }
1484
+ const candidate = cloneCandidate(body.candidate);
1485
+ const candidateHash = contentHash({
1486
+ kind: "external-text-candidate",
1487
+ candidate
1488
+ });
1489
+ if (!proposedCandidateHashes.has(candidateHash)) {
1490
+ observe({
1491
+ kind: "proposal",
1492
+ candidate,
1493
+ candidateHash
1494
+ });
1495
+ proposedCandidateHashes.add(candidateHash);
1496
+ }
1497
+ const count = nextEvaluation();
1498
+ if (count === void 0) {
1499
+ observe({
1500
+ kind: "refusal",
1501
+ reason: "evaluation-limit",
1502
+ candidate,
1503
+ candidateHash,
1504
+ exampleId: body.exampleId
1505
+ });
1506
+ sendJsonIfOpen(response, 429, { error: "evaluation limit reached" });
1507
+ return;
1508
+ }
1509
+ let result;
1510
+ try {
1511
+ result = await args.evaluate({
1512
+ candidate,
1513
+ exampleId: body.exampleId
1514
+ }, signal);
1515
+ } catch {
1516
+ observe({
1517
+ kind: "refusal",
1518
+ reason: "evaluation-failed",
1519
+ candidate,
1520
+ candidateHash,
1521
+ exampleId: body.exampleId
1522
+ });
1523
+ sendJsonIfOpen(response, 500, { error: "evaluation failed" });
1524
+ return;
1525
+ }
1526
+ let encoded;
1527
+ try {
1528
+ encoded = encodeJsonResponse(result);
1529
+ } catch {
1530
+ observe({
1531
+ kind: "refusal",
1532
+ reason: "evaluation-failed",
1533
+ candidate,
1534
+ candidateHash,
1535
+ exampleId: body.exampleId
1536
+ });
1537
+ sendJsonIfOpen(response, 500, { error: "evaluation response must be valid JSON" });
1538
+ return;
1539
+ }
1540
+ if (Buffer.byteLength(encoded) > limits.maxResponseBytes) {
1541
+ observe({
1542
+ kind: "refusal",
1543
+ reason: "evaluation-failed",
1544
+ candidate,
1545
+ candidateHash,
1546
+ exampleId: body.exampleId
1547
+ });
1548
+ sendJsonIfOpen(response, 413, { error: "evaluation response too large" });
1549
+ return;
1550
+ }
1551
+ observe({
1552
+ kind: "evaluation",
1553
+ candidate,
1554
+ candidateHash,
1555
+ exampleId: body.exampleId,
1556
+ evaluationNumber: count,
1557
+ response: structuredClone(result)
1558
+ });
1559
+ sendEncodedJsonIfOpen(response, 200, encoded);
1560
+ } catch {
1561
+ sendJsonIfOpen(response, 500, { error: "evaluation failed" });
1562
+ }
1563
+ }
1564
+ async function waitForActiveHandlers(activeHandlers) {
1565
+ while (activeHandlers.size > 0) await Promise.allSettled([...activeHandlers]);
1566
+ }
1567
+ function sendJsonIfOpen(response, status, body) {
1568
+ if (response.destroyed || response.writableEnded) return;
1569
+ sendJson(response, status, body);
1570
+ }
1571
+ function readJson(request, maxRequestBytes) {
1572
+ return new Promise((resolvePromise, reject) => {
1573
+ let size = 0;
1574
+ const chunks = [];
1575
+ request.on("data", (chunk) => {
1576
+ size += chunk.length;
1577
+ if (size > maxRequestBytes) {
1578
+ reject(/* @__PURE__ */ new Error("callback body too large"));
1579
+ request.destroy();
1580
+ return;
1581
+ }
1582
+ chunks.push(chunk);
1583
+ });
1584
+ request.on("error", reject);
1585
+ request.on("end", () => {
1586
+ try {
1587
+ resolvePromise(JSON.parse(Buffer.concat(chunks).toString("utf8")));
1588
+ } catch (error) {
1589
+ reject(error);
1590
+ }
1591
+ });
1592
+ });
1593
+ }
1594
+ function encodeJsonResponse(value) {
1595
+ const encoded = JSON.stringify(value);
1596
+ if (encoded === void 0) throw new Error("evaluation response must be JSON-serializable");
1597
+ return encoded;
1598
+ }
1599
+ function sendEncodedJsonIfOpen(response, status, encoded) {
1600
+ if (response.destroyed || response.writableEnded) return;
1601
+ response.writeHead(status, {
1602
+ "content-type": "application/json; charset=utf-8",
1603
+ "content-length": String(Buffer.byteLength(encoded))
1604
+ });
1605
+ response.end(encoded);
1606
+ }
1607
+ function assertCallbackConfig(args) {
1608
+ if (typeof args.token !== "string" || !args.token.trim()) throw new Error("external optimizer callback: token must be non-empty");
1609
+ if (!Number.isSafeInteger(args.maxEvaluations) || args.maxEvaluations <= 0) throw new Error("external optimizer callback: maxEvaluations must be a positive safe integer");
1610
+ if (args.acceptEvaluation !== void 0 && typeof args.acceptEvaluation !== "function") throw new Error("external optimizer callback: acceptEvaluation must be a function");
1611
+ if (typeof args.evaluate !== "function") throw new Error("external optimizer callback: evaluate must be a function");
1612
+ if (args.observe !== void 0 && typeof args.observe !== "function") throw new Error("external optimizer callback: observe must be a function");
1613
+ }
1614
+ function cloneCandidate(candidate) {
1615
+ return typeof candidate === "string" ? candidate : { ...candidate };
1616
+ }
1617
+ //#endregion
1086
1618
  //#region src/campaign/search-ledger-errors.ts
1087
1619
  /** Base error for invalid search-ledger input or operations. */
1088
1620
  var SearchLedgerError = class extends ValidationError {};
@@ -1262,6 +1794,6 @@ function acquireSingleRunLock(opts) {
1262
1794
  return { release };
1263
1795
  }
1264
1796
  //#endregion
1265
- export { assertNoCredentialValues as C, removeCredentialEnvironment as D, isRecord as E, safePathComponent as O, assertJsonValue as S, isExternalTextCandidate as T, canonicalJson as _, SEARCH_LEDGER_FILE_CONTEXT as a, inMemoryVerdictCache as b, SearchLedgerIntegrityError as c, runWithCleanup as d, startExternalOptimizerModelProxy as f, cachedJudge as g, sendJson as h, inMemoryCampaignStorage as i, runExternalOptimizerProcess as l, listenLocal as m, createRunCostLedger as n, SearchLedgerConflictError as o, closeServer as p, fsCampaignStorage as r, SearchLedgerError as s, acquireSingleRunLock as t, closeExternalOptimizerResources as u, contentHash as v, isCandidateText as w, assertExternalOptimizerModelBudget as x, fileVerdictCache as y };
1797
+ export { removeCredentialEnvironment as A, DEFAULT_EXTERNAL_OPTIMIZER_PROCESS_LIMITS as C, isCandidateText as D, assertNoCredentialValues as E, resolveExternalOptimizerProcessLimits as M, safePathComponent as N, isExternalTextCandidate as O, DEFAULT_EXTERNAL_OPTIMIZER_CALLBACK_LIMITS as S, assertJsonValue as T, cachedJudge as _, SEARCH_LEDGER_FILE_CONTEXT as a, fileVerdictCache as b, SearchLedgerIntegrityError as c, closeExternalOptimizerResources as d, runWithCleanup as f, sendJson as g, listenLocal as h, inMemoryCampaignStorage as i, resolveExternalOptimizerCallbackLimits as j, isRecord as k, startExternalOptimizerCallback as l, closeServer as m, createRunCostLedger as n, SearchLedgerConflictError as o, startExternalOptimizerModelProxy as p, fsCampaignStorage as r, SearchLedgerError as s, acquireSingleRunLock as t, runExternalOptimizerProcess as u, canonicalJson as v, assertExternalOptimizerModelBudget as w, inMemoryVerdictCache as x, contentHash as y };
1266
1798
 
1267
- //# sourceMappingURL=single-run-lock-t1si1ob7.js.map
1799
+ //# sourceMappingURL=single-run-lock-D5iN0Xzb.js.map