@tangle-network/agent-eval 0.150.0 → 0.150.2

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 (150) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/dist/analyst/index.d.ts +13 -13
  3. package/dist/analyst/index.js +4 -4
  4. package/dist/{backend-integrity-DaeroaEs.d.ts → backend-integrity-DOCa_QrR.d.ts} +2 -2
  5. package/dist/{backend-integrity-DaeroaEs.d.ts.map → backend-integrity-DOCa_QrR.d.ts.map} +1 -1
  6. package/dist/{benchmark-A0E-JZ1H.d.ts → benchmark-BtAWA8nT.d.ts} +3 -3
  7. package/dist/{benchmark-A0E-JZ1H.d.ts.map → benchmark-BtAWA8nT.d.ts.map} +1 -1
  8. package/dist/{benchmark-command-CqGA0SJQ.js → benchmark-command-BU1Las59.js} +7 -7
  9. package/dist/{benchmark-command-CqGA0SJQ.js.map → benchmark-command-BU1Las59.js.map} +1 -1
  10. package/dist/benchmarks/index.d.ts +4 -4
  11. package/dist/benchmarks/index.js +3 -3
  12. package/dist/builder-eval/index.js +1 -1
  13. package/dist/campaign/index.d.ts +8 -8
  14. package/dist/campaign/index.js +5 -5
  15. package/dist/{campaign-CR6Yt5ZS.js → campaign-la-gEYNz.js} +18 -9
  16. package/dist/campaign-la-gEYNz.js.map +1 -0
  17. package/dist/{capture-fetch-Im0UN1i6.d.ts → capture-fetch-BBVFzhkk.d.ts} +2 -2
  18. package/dist/{capture-fetch-Im0UN1i6.d.ts.map → capture-fetch-BBVFzhkk.d.ts.map} +1 -1
  19. package/dist/{chat-client-sFiClniQ.js → chat-client-Bvmxedyv.js} +3 -3
  20. package/dist/{chat-client-sFiClniQ.js.map → chat-client-Bvmxedyv.js.map} +1 -1
  21. package/dist/cli.js +2 -2
  22. package/dist/{client-w6vVJhSp.d.ts → client-kPQYT_56.d.ts} +2 -2
  23. package/dist/{client-w6vVJhSp.d.ts.map → client-kPQYT_56.d.ts.map} +1 -1
  24. package/dist/contract/index.d.ts +10 -10
  25. package/dist/contract/index.js +5 -5
  26. package/dist/{default-registry-BhRNsP-D.d.ts → default-registry-Cw0Ohdoj.d.ts} +6 -6
  27. package/dist/{default-registry-BhRNsP-D.d.ts.map → default-registry-Cw0Ohdoj.d.ts.map} +1 -1
  28. package/dist/{define-agent-eval-ChrtErmg.js → define-agent-eval-C8V8sMqP.js} +7 -7
  29. package/dist/{define-agent-eval-ChrtErmg.js.map → define-agent-eval-C8V8sMqP.js.map} +1 -1
  30. package/dist/{define-agent-eval-Cw3mkB4-.d.ts → define-agent-eval-CEQWL9Hy.d.ts} +5 -5
  31. package/dist/{define-agent-eval-Cw3mkB4-.d.ts.map → define-agent-eval-CEQWL9Hy.d.ts.map} +1 -1
  32. package/dist/{descriptive-B5MwKfbf.js → descriptive-jDOuI6mz.js} +22 -2
  33. package/dist/descriptive-jDOuI6mz.js.map +1 -0
  34. package/dist/{dspy-rlm-engine-Csi3CUAT.js → dspy-rlm-engine-CBYlPvNy.js} +2 -2
  35. package/dist/{dspy-rlm-engine-Csi3CUAT.js.map → dspy-rlm-engine-CBYlPvNy.js.map} +1 -1
  36. package/dist/{engine-CgQdUh-r.d.ts → engine-BLzhNzoY.d.ts} +4 -4
  37. package/dist/{engine-CgQdUh-r.d.ts.map → engine-BLzhNzoY.d.ts.map} +1 -1
  38. package/dist/{eval-campaign-Czc2TcW5.js → eval-campaign-CQuZrLR_.js} +3 -3
  39. package/dist/{eval-campaign-Czc2TcW5.js.map → eval-campaign-CQuZrLR_.js.map} +1 -1
  40. package/dist/{exact-types-D9_T83Ol.d.ts → exact-types-ccQAyut1.d.ts} +2 -2
  41. package/dist/{exact-types-D9_T83Ol.d.ts.map → exact-types-ccQAyut1.d.ts.map} +1 -1
  42. package/dist/experiment/index.d.ts +3 -3
  43. package/dist/{experiment-tracker-Cp9ougHs.d.ts → experiment-tracker-0MhuPArU.d.ts} +2 -2
  44. package/dist/{experiment-tracker-Cp9ougHs.d.ts.map → experiment-tracker-0MhuPArU.d.ts.map} +1 -1
  45. package/dist/{external-optimizer-contracts-mbhR9Ldu.d.ts → external-optimizer-contracts-DbLsm4Po.d.ts} +2 -2
  46. package/dist/{external-optimizer-contracts-mbhR9Ldu.d.ts.map → external-optimizer-contracts-DbLsm4Po.d.ts.map} +1 -1
  47. package/dist/{external-optimizer-process-tDe72VRO.js → external-optimizer-process-x9oEXKsU.js} +2 -2
  48. package/dist/{external-optimizer-process-tDe72VRO.js.map → external-optimizer-process-x9oEXKsU.js.map} +1 -1
  49. package/dist/{external-optimizer-subprocess-BepyNwtj.js → external-optimizer-subprocess-CKNb42oM.js} +278 -43
  50. package/dist/external-optimizer-subprocess-CKNb42oM.js.map +1 -0
  51. package/dist/{feedback-trajectory-Cc9M2tNF.d.ts → feedback-trajectory-DpTTjo0q.d.ts} +3 -3
  52. package/dist/{feedback-trajectory-Cc9M2tNF.d.ts.map → feedback-trajectory-DpTTjo0q.d.ts.map} +1 -1
  53. package/dist/hosted/index.d.ts +2 -2
  54. package/dist/{index-DL4MJuJL.d.ts → index-Aj3WO3_a.d.ts} +8 -8
  55. package/dist/{index-DL4MJuJL.d.ts.map → index-Aj3WO3_a.d.ts.map} +1 -1
  56. package/dist/index-C1ravkGA.d.ts +1 -0
  57. package/dist/{index-dh0kp2GM.d.ts → index-IQccV3Ou.d.ts} +15 -10
  58. package/dist/index-IQccV3Ou.d.ts.map +1 -0
  59. package/dist/index.d.ts +21 -90
  60. package/dist/index.d.ts.map +1 -1
  61. package/dist/index.js +13 -13
  62. package/dist/{integrity-D2MAlC_K.d.ts → integrity-B0dZ96EO.d.ts} +2 -2
  63. package/dist/{integrity-D2MAlC_K.d.ts.map → integrity-B0dZ96EO.d.ts.map} +1 -1
  64. package/dist/{integrity-DysDBWDu.js → integrity-DL91tucI.js} +16 -3
  65. package/dist/integrity-DL91tucI.js.map +1 -0
  66. package/dist/{judge-calibration-DZkWrm5H.js → judge-calibration-zZjLz8hr.js} +2 -2
  67. package/dist/{judge-calibration-DZkWrm5H.js.map → judge-calibration-zZjLz8hr.js.map} +1 -1
  68. package/dist/{llm-client-CxyJ8Oft.js → llm-client-Bg32RW0j.js} +47 -2
  69. package/dist/llm-client-Bg32RW0j.js.map +1 -0
  70. package/dist/{llm-judge-D7UuMI2t.js → llm-judge-BqqMS8t7.js} +5 -5
  71. package/dist/{llm-judge-D7UuMI2t.js.map → llm-judge-BqqMS8t7.js.map} +1 -1
  72. package/dist/{matrix-D3hL68uV.d.ts → matrix-DrVnRp4G.d.ts} +2 -2
  73. package/dist/{matrix-D3hL68uV.d.ts.map → matrix-DrVnRp4G.d.ts.map} +1 -1
  74. package/dist/meta-eval/index.d.ts +1 -1
  75. package/dist/meta-eval/index.js +2 -2
  76. package/dist/multishot/golden/index.d.ts +1 -1
  77. package/dist/multishot/index.d.ts +2 -2
  78. package/dist/openapi.json +1 -1
  79. package/dist/pipelines/index.js +1 -1
  80. package/dist/{produced-state-WJs_enbZ.js → produced-state-Bnq4FaDO.js} +3 -3
  81. package/dist/{produced-state-WJs_enbZ.js.map → produced-state-Bnq4FaDO.js.map} +1 -1
  82. package/dist/{promotion-policy-DYRzXdTV.d.ts → promotion-policy-DLOUkYhI.d.ts} +2 -2
  83. package/dist/{promotion-policy-DYRzXdTV.d.ts.map → promotion-policy-DLOUkYhI.d.ts.map} +1 -1
  84. package/dist/{provenance-Bh9k3sjl.d.ts → provenance-oA4-zUqm.d.ts} +6 -6
  85. package/dist/{provenance-Bh9k3sjl.d.ts.map → provenance-oA4-zUqm.d.ts.map} +1 -1
  86. package/dist/{registry-DhpX9Bsg.d.ts → registry-BQwrSYpC.d.ts} +4 -4
  87. package/dist/{registry-DhpX9Bsg.d.ts.map → registry-BQwrSYpC.d.ts.map} +1 -1
  88. package/dist/reporting.js +2 -2
  89. package/dist/{researcher-CbCK-ppF.d.ts → researcher-DJnoUE8c.d.ts} +3 -3
  90. package/dist/{researcher-CbCK-ppF.d.ts.map → researcher-DJnoUE8c.d.ts.map} +1 -1
  91. package/dist/{reward-hacking-DFo2FU5J.js → reward-hacking-C0x0xihA.js} +20 -2
  92. package/dist/reward-hacking-C0x0xihA.js.map +1 -0
  93. package/dist/rl.d.ts +2 -2
  94. package/dist/rl.js +3 -3
  95. package/dist/{rubric-predictive-validity-Cwwyd7ah.js → rubric-predictive-validity-CzxLoZge.js} +2 -2
  96. package/dist/{rubric-predictive-validity-Cwwyd7ah.js.map → rubric-predictive-validity-CzxLoZge.js.map} +1 -1
  97. package/dist/{semantic-concept-judge-B7KPD57W.js → semantic-concept-judge-laMCnTLn.js} +2 -2
  98. package/dist/{semantic-concept-judge-B7KPD57W.js.map → semantic-concept-judge-laMCnTLn.js.map} +1 -1
  99. package/dist/{series-convergence-VhTad5TM.d.ts → series-convergence-BxKEgBwA.d.ts} +2 -2
  100. package/dist/{series-convergence-VhTad5TM.d.ts.map → series-convergence-BxKEgBwA.d.ts.map} +1 -1
  101. package/dist/{server-DNiUVirV.js → server-dIWwF3j_.js} +2 -2
  102. package/dist/{server-DNiUVirV.js.map → server-dIWwF3j_.js.map} +1 -1
  103. package/dist/{skillopt-optimization-method-BtgL6PMH.d.ts → skillopt-optimization-method-BDD_o1xE.d.ts} +5 -5
  104. package/dist/{skillopt-optimization-method-BtgL6PMH.d.ts.map → skillopt-optimization-method-BDD_o1xE.d.ts.map} +1 -1
  105. package/dist/{skillopt-optimization-method-L8vi4h4m.js → skillopt-optimization-method-CPBlTcj5.js} +5 -5
  106. package/dist/{skillopt-optimization-method-L8vi4h4m.js.map → skillopt-optimization-method-CPBlTcj5.js.map} +1 -1
  107. package/dist/{statistical-heldout-Caxd30ZF.d.ts → statistical-heldout-_woZ9q9j.d.ts} +2 -2
  108. package/dist/{statistical-heldout-Caxd30ZF.d.ts.map → statistical-heldout-_woZ9q9j.d.ts.map} +1 -1
  109. package/dist/{store-tool-spans-BGg2QFk1.d.ts → store-tool-spans-Br2_IUhm.d.ts} +4 -4
  110. package/dist/{store-tool-spans-BGg2QFk1.d.ts.map → store-tool-spans-Br2_IUhm.d.ts.map} +1 -1
  111. package/dist/{summary-report-Blysd6Z2.js → summary-report-DW2bEpdB.js} +2 -2
  112. package/dist/{summary-report-Blysd6Z2.js.map → summary-report-DW2bEpdB.js.map} +1 -1
  113. package/dist/supervisor-run/index.d.ts +26 -4
  114. package/dist/supervisor-run/index.d.ts.map +1 -1
  115. package/dist/supervisor-run/index.js +102 -24
  116. package/dist/supervisor-run/index.js.map +1 -1
  117. package/dist/{tool-groups-BuhFx38z.d.ts → tool-groups-B4tqh8jB.d.ts} +3 -3
  118. package/dist/tool-groups-B4tqh8jB.d.ts.map +1 -0
  119. package/dist/{tool-waste-BDdBZG1F.js → tool-waste-C-VHSRwF.js} +3 -3
  120. package/dist/{tool-waste-BDdBZG1F.js.map → tool-waste-C-VHSRwF.js.map} +1 -1
  121. package/dist/trace-repair/index.d.ts +1 -1
  122. package/dist/traces.d.ts +7 -7
  123. package/dist/{types-BNbhD_Vj.d.ts → types-B2NsbrNy.d.ts} +2 -2
  124. package/dist/{types-BNbhD_Vj.d.ts.map → types-B2NsbrNy.d.ts.map} +1 -1
  125. package/dist/{types-B61iI2ZG.d.ts → types-CLAwnY-L.d.ts} +2 -2
  126. package/dist/{types-B61iI2ZG.d.ts.map → types-CLAwnY-L.d.ts.map} +1 -1
  127. package/dist/{types-COEjZMLY.d.ts → types-DdFNuyxQ.d.ts} +3 -3
  128. package/dist/{types-COEjZMLY.d.ts.map → types-DdFNuyxQ.d.ts.map} +1 -1
  129. package/dist/{types-yLK8gXE9.d.ts → types-I5WwVzQ7.d.ts} +159 -9
  130. package/dist/types-I5WwVzQ7.d.ts.map +1 -0
  131. package/dist/{types-COAJy8Ku.d.ts → types-jUBXJ7Iz.d.ts} +36 -3
  132. package/dist/types-jUBXJ7Iz.d.ts.map +1 -0
  133. package/dist/wire/index.d.ts +2 -2
  134. package/dist/wire/index.js +1 -1
  135. package/docs/adapters-observability.md +9 -23
  136. package/docs/campaign-proposers.md +18 -3
  137. package/docs/concepts.md +3 -4
  138. package/docs/wire-protocol.md +1 -1
  139. package/package.json +1 -1
  140. package/dist/campaign-CR6Yt5ZS.js.map +0 -1
  141. package/dist/descriptive-B5MwKfbf.js.map +0 -1
  142. package/dist/external-optimizer-subprocess-BepyNwtj.js.map +0 -1
  143. package/dist/index-DjpxROrT.d.ts +0 -1
  144. package/dist/index-dh0kp2GM.d.ts.map +0 -1
  145. package/dist/integrity-DysDBWDu.js.map +0 -1
  146. package/dist/llm-client-CxyJ8Oft.js.map +0 -1
  147. package/dist/reward-hacking-DFo2FU5J.js.map +0 -1
  148. package/dist/tool-groups-BuhFx38z.d.ts.map +0 -1
  149. package/dist/types-COAJy8Ku.d.ts.map +0 -1
  150. package/dist/types-yLK8gXE9.d.ts.map +0 -1
@@ -1,6 +1,6 @@
1
1
  import { n as contentHash } from "./verdict-cache-mZf5FEiY.js";
2
2
  import { m as tryAcquireAtomicFileLock, p as probeAtomicFileLock } from "./ledger-core-DTae9rv_.js";
3
- import { _ as resolveExternalOptimizerCallbackLimits, a as closeServer, h as isRecord, m as isExternalTextCandidate, o as listenLocal, s as sendJson } from "./external-optimizer-subprocess-BepyNwtj.js";
3
+ import { _ as resolveExternalOptimizerCallbackLimits, a as closeServer, h as isRecord, m as isExternalTextCandidate, o as listenLocal, s as sendJson } from "./external-optimizer-subprocess-CKNb42oM.js";
4
4
  import { createServer } from "node:http";
5
5
  //#region src/campaign/single-run-lock.ts
6
6
  /**
@@ -298,4 +298,4 @@ function cloneCandidate(candidate) {
298
298
  //#endregion
299
299
  export { acquireSingleRunLock as n, startExternalOptimizerCallback as t };
300
300
 
301
- //# sourceMappingURL=external-optimizer-process-tDe72VRO.js.map
301
+ //# sourceMappingURL=external-optimizer-process-x9oEXKsU.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"external-optimizer-process-tDe72VRO.js","names":[],"sources":["../src/campaign/single-run-lock.ts","../src/campaign/external-optimizer-callback.ts"],"sourcesContent":["/**\n * Single-run lock for evaluations that share one mutable environment.\n *\n * Two concurrent runs against a shared stateful gym silently corrupt each\n * other: each resets/mutates environment state mid-cell of the other, and\n * every score from both becomes garbage that LOOKS like worker variance\n * (agent-lab R357 burned hours on flip-flopping scores before tracing them\n * to exactly this). The fix is a pid lockfile: refuse to start while a live\n * holder exists, reclaim stale locks whose pid is gone, release only if the\n * lock is still ours.\n *\n * `alsoCheck` exists because independent runners can guard the same shared\n * resource with differently named lockfiles; a runner must respect all of\n * them even though it writes only its own.\n */\n\nimport {\n type AtomicFileLockUnavailable,\n probeAtomicFileLock,\n tryAcquireAtomicFileLock,\n} from '../ledger-core/atomic-file-lock'\n\nexport interface SingleRunLockOptions {\n /** Lockfile this runner writes (and checks). */\n readonly lockPath: string\n /** Other runners' lockfiles guarding the same resource; checked, never written. */\n readonly alsoCheck?: readonly string[]\n /** Install a process 'exit' hook that releases the lock. Default true. */\n readonly releaseOnExit?: boolean\n /** Owner pid recorded in the lockfile metadata. Default process.pid. */\n readonly pid?: number\n}\n\nexport interface SingleRunLock {\n /** Remove the lockfile if this process still owns it. Idempotent. */\n release(): void\n}\n\nfunction assertAvailable(path: string): void {\n const unavailable = probeAtomicFileLock({ lockPath: path })\n if (unavailable) throw unavailableError(path, unavailable)\n}\n\nfunction unavailableError(path: string, unavailable: AtomicFileLockUnavailable): Error {\n if (unavailable.reason === 'recovery') {\n return new Error(\n `single-run lock recovery is already in progress (${path}); refusing a concurrent run`,\n )\n }\n return new Error(\n `single-run lock held by live pid ${unavailable.holder.pid} (${path}); refusing a concurrent run on the shared resource`,\n )\n}\n\n/**\n * Acquire the lock or throw naming the live holder. A stale lock (holder pid\n * no longer running) is reclaimed by one contender. An interrupted reclaim\n * leaves a marker that fails closed instead of admitting overlapping runs.\n */\nexport function acquireSingleRunLock(opts: SingleRunLockOptions): SingleRunLock {\n const pid = opts.pid ?? process.pid\n if (!Number.isSafeInteger(pid) || pid <= 0)\n throw new Error('single-run lock pid must be a positive integer')\n for (const path of opts.alsoCheck ?? []) assertAvailable(path)\n const acquisition = tryAcquireAtomicFileLock({\n lockPath: opts.lockPath,\n pid,\n })\n if (!acquisition.acquired) throw unavailableError(opts.lockPath, acquisition)\n const releaseOnExit = opts.releaseOnExit ?? true\n let released = false\n const release = (): void => {\n if (released) return\n released = true\n if (releaseOnExit) process.off('exit', release)\n try {\n acquisition.lock.release()\n } catch {\n // release is best-effort; a leftover stale lock is reclaimed on next acquire\n }\n }\n try {\n for (const path of opts.alsoCheck ?? []) assertAvailable(path)\n } catch (error) {\n release()\n throw error\n }\n if (releaseOnExit) process.on('exit', release)\n return { release }\n}\n","import { createServer, type IncomingMessage, type ServerResponse } from 'node:http'\nimport { contentHash } from '../verdict-cache'\nimport {\n type ExternalOptimizerCallback,\n type ExternalOptimizerCallbackLimits,\n type ExternalOptimizerEvaluationObservation,\n type ExternalTextCandidate,\n type ExternalTextEvaluationRequest,\n isExternalTextCandidate,\n isRecord,\n resolveExternalOptimizerCallbackLimits,\n} from './external-optimizer-contracts'\nimport { closeServer, listenLocal, sendJson } from './external-optimizer-http'\n\ntype UnsequencedObservation = ExternalOptimizerEvaluationObservation extends infer T\n ? T extends ExternalOptimizerEvaluationObservation\n ? Omit<T, 'sequence'>\n : never\n : never\n\nexport async function startExternalOptimizerCallback<TResponse>(args: {\n token: string\n maxEvaluations: number\n acceptEvaluation?: () => number | undefined\n evaluate: (request: ExternalTextEvaluationRequest, signal: AbortSignal) => Promise<TResponse>\n observe?: (observation: ExternalOptimizerEvaluationObservation) => void\n /** Loopback JSON byte limits. Omitted fields use finite defaults. */\n limits?: Partial<ExternalOptimizerCallbackLimits>\n signal?: AbortSignal\n}): Promise<ExternalOptimizerCallback> {\n assertCallbackConfig(args)\n const limits = resolveExternalOptimizerCallbackLimits(args.limits)\n args.signal?.throwIfAborted()\n let evaluations = 0\n let accepting = true\n let closePromise: Promise<void> | undefined\n const activeControllers = new Set<AbortController>()\n const activeHandlers = new Set<Promise<void>>()\n const proposedCandidateHashes = new Set<string>()\n let observationSequence = 0\n const server = createServer((request, response) => {\n if (!accepting) {\n sendJsonIfOpen(response, 503, { error: 'external optimizer callback is closing' })\n return\n }\n\n const controller = new AbortController()\n const abortRequest = (): void => {\n request.destroy()\n response.destroy()\n }\n activeControllers.add(controller)\n controller.signal.addEventListener('abort', abortRequest, { once: true })\n\n let handler!: Promise<void>\n handler = handleCallback(\n request,\n response,\n controller.signal,\n args,\n limits,\n () => {\n const accepted = args.acceptEvaluation ? args.acceptEvaluation() : evaluations + 1\n if (accepted === undefined) return undefined\n if (!Number.isSafeInteger(accepted) || accepted <= 0) {\n throw new Error('external optimizer callback: invalid accepted evaluation count')\n }\n if (accepted > args.maxEvaluations) return undefined\n evaluations += 1\n return accepted\n },\n (observation) => {\n if (!args.observe) return\n args.observe({ ...observation, sequence: ++observationSequence })\n },\n proposedCandidateHashes,\n ).finally(() => {\n controller.signal.removeEventListener('abort', abortRequest)\n activeControllers.delete(controller)\n activeHandlers.delete(handler)\n })\n activeHandlers.add(handler)\n void handler.catch(() => undefined)\n })\n const port = await listenLocal(server)\n const close = (): Promise<void> => {\n closePromise ??= closeCallbackServer()\n return closePromise\n }\n const onAbort = (): void => {\n void close().catch(() => undefined)\n }\n args.signal?.addEventListener('abort', onAbort, { once: true })\n if (args.signal?.aborted) onAbort()\n return {\n url: `http://127.0.0.1:${port}/evaluate`,\n token: args.token,\n evaluations: () => evaluations,\n close,\n }\n\n async function closeCallbackServer(): Promise<void> {\n args.signal?.removeEventListener('abort', onAbort)\n accepting = false\n const closingServer = closeServer(server)\n server.closeIdleConnections?.()\n for (const controller of activeControllers) controller.abort()\n const [serverResult] = await Promise.allSettled([\n closingServer,\n waitForActiveHandlers(activeHandlers),\n ])\n if (activeControllers.size !== 0 || activeHandlers.size !== 0) {\n throw new Error('external optimizer callback closed with active request work')\n }\n if (serverResult?.status === 'rejected') throw serverResult.reason\n }\n}\n\nasync function handleCallback<TResponse>(\n request: IncomingMessage,\n response: ServerResponse,\n signal: AbortSignal,\n args: {\n token: string\n maxEvaluations: number\n acceptEvaluation?: () => number | undefined\n evaluate: (request: ExternalTextEvaluationRequest, signal: AbortSignal) => Promise<TResponse>\n observe?: (observation: ExternalOptimizerEvaluationObservation) => void\n },\n limits: ExternalOptimizerCallbackLimits,\n nextEvaluation: () => number | undefined,\n observe: (observation: UnsequencedObservation) => void,\n proposedCandidateHashes: Set<string>,\n): Promise<void> {\n try {\n if (request.method !== 'POST' || request.url !== '/evaluate') {\n sendJsonIfOpen(response, 404, { error: 'not found' })\n return\n }\n if (request.headers.authorization !== `Bearer ${args.token}`) {\n sendJsonIfOpen(response, 401, { error: 'unauthorized' })\n return\n }\n let body: unknown\n try {\n body = await readJson(request, limits.maxRequestBytes)\n } catch {\n observe({ kind: 'refusal', reason: 'invalid-request' })\n sendJsonIfOpen(response, 400, { error: 'request body must be bounded valid JSON' })\n return\n }\n if (\n !isRecord(body) ||\n !isExternalTextCandidate(body.candidate) ||\n typeof body.exampleId !== 'string'\n ) {\n observe({ kind: 'refusal', reason: 'invalid-request' })\n sendJsonIfOpen(response, 400, { error: 'candidate and exampleId are required strings' })\n return\n }\n const candidate = cloneCandidate(body.candidate)\n const candidateHash = contentHash({ kind: 'external-text-candidate', candidate })\n if (!proposedCandidateHashes.has(candidateHash)) {\n observe({ kind: 'proposal', candidate, candidateHash })\n proposedCandidateHashes.add(candidateHash)\n }\n const count = nextEvaluation()\n if (count === undefined) {\n observe({\n kind: 'refusal',\n reason: 'evaluation-limit',\n candidate,\n candidateHash,\n exampleId: body.exampleId,\n })\n sendJsonIfOpen(response, 429, { error: 'evaluation limit reached' })\n return\n }\n let result: TResponse\n try {\n result = await args.evaluate({ candidate, exampleId: body.exampleId }, signal)\n } catch (error) {\n observe({\n kind: 'refusal',\n reason: 'evaluation-failed',\n candidate,\n candidateHash,\n exampleId: body.exampleId,\n })\n // The thrown detail is the only diagnostic for a failed evaluation —\n // an opaque 500 costs the caller a blind debugging round.\n sendJsonIfOpen(response, 500, {\n error: `evaluation failed: ${String(error).slice(0, 400)}`,\n })\n return\n }\n let encoded: string\n try {\n encoded = encodeJsonResponse(result)\n } catch {\n observe({\n kind: 'refusal',\n reason: 'evaluation-failed',\n candidate,\n candidateHash,\n exampleId: body.exampleId,\n })\n sendJsonIfOpen(response, 500, { error: 'evaluation response must be valid JSON' })\n return\n }\n if (Buffer.byteLength(encoded) > limits.maxResponseBytes) {\n observe({\n kind: 'refusal',\n reason: 'evaluation-failed',\n candidate,\n candidateHash,\n exampleId: body.exampleId,\n })\n sendJsonIfOpen(response, 413, { error: 'evaluation response too large' })\n return\n }\n observe({\n kind: 'evaluation',\n candidate,\n candidateHash,\n exampleId: body.exampleId,\n evaluationNumber: count,\n response: structuredClone(result),\n })\n sendEncodedJsonIfOpen(response, 200, encoded)\n } catch {\n sendJsonIfOpen(response, 500, { error: 'evaluation failed' })\n }\n}\n\nasync function waitForActiveHandlers(activeHandlers: Set<Promise<void>>): Promise<void> {\n while (activeHandlers.size > 0) {\n await Promise.allSettled([...activeHandlers])\n }\n}\n\nfunction sendJsonIfOpen(response: ServerResponse, status: number, body: unknown): void {\n if (response.destroyed || response.writableEnded) return\n sendJson(response, status, body)\n}\n\nfunction readJson(request: IncomingMessage, maxRequestBytes: number): Promise<unknown> {\n return new Promise((resolvePromise, reject) => {\n let size = 0\n const chunks: Buffer[] = []\n request.on('data', (chunk: Buffer) => {\n size += chunk.length\n if (size > maxRequestBytes) {\n reject(new Error('callback body too large'))\n request.destroy()\n return\n }\n chunks.push(chunk)\n })\n request.on('error', reject)\n request.on('end', () => {\n try {\n resolvePromise(JSON.parse(Buffer.concat(chunks).toString('utf8')))\n } catch (error) {\n reject(error)\n }\n })\n })\n}\n\nfunction encodeJsonResponse(value: unknown): string {\n const encoded = JSON.stringify(value)\n if (encoded === undefined) throw new Error('evaluation response must be JSON-serializable')\n return encoded\n}\n\nfunction sendEncodedJsonIfOpen(response: ServerResponse, status: number, encoded: string): void {\n if (response.destroyed || response.writableEnded) return\n response.writeHead(status, {\n 'content-type': 'application/json; charset=utf-8',\n 'content-length': String(Buffer.byteLength(encoded)),\n })\n response.end(encoded)\n}\n\nfunction assertCallbackConfig(args: {\n token: string\n maxEvaluations: number\n acceptEvaluation?: () => number | undefined\n evaluate: (request: ExternalTextEvaluationRequest, signal: AbortSignal) => Promise<unknown>\n observe?: (observation: ExternalOptimizerEvaluationObservation) => void\n limits?: Partial<ExternalOptimizerCallbackLimits>\n signal?: AbortSignal\n}): void {\n if (typeof args.token !== 'string' || !args.token.trim()) {\n throw new Error('external optimizer callback: token must be non-empty')\n }\n if (!Number.isSafeInteger(args.maxEvaluations) || args.maxEvaluations <= 0) {\n throw new Error('external optimizer callback: maxEvaluations must be a positive safe integer')\n }\n if (args.acceptEvaluation !== undefined && typeof args.acceptEvaluation !== 'function') {\n throw new Error('external optimizer callback: acceptEvaluation must be a function')\n }\n if (typeof args.evaluate !== 'function') {\n throw new Error('external optimizer callback: evaluate must be a function')\n }\n if (args.observe !== undefined && typeof args.observe !== 'function') {\n throw new Error('external optimizer callback: observe must be a function')\n }\n}\n\nfunction cloneCandidate(candidate: ExternalTextCandidate): ExternalTextCandidate {\n return typeof candidate === 'string' ? candidate : { ...candidate }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"external-optimizer-process-x9oEXKsU.js","names":[],"sources":["../src/campaign/single-run-lock.ts","../src/campaign/external-optimizer-callback.ts"],"sourcesContent":["/**\n * Single-run lock for evaluations that share one mutable environment.\n *\n * Two concurrent runs against a shared stateful gym silently corrupt each\n * other: each resets/mutates environment state mid-cell of the other, and\n * every score from both becomes garbage that LOOKS like worker variance\n * (agent-lab R357 burned hours on flip-flopping scores before tracing them\n * to exactly this). The fix is a pid lockfile: refuse to start while a live\n * holder exists, reclaim stale locks whose pid is gone, release only if the\n * lock is still ours.\n *\n * `alsoCheck` exists because independent runners can guard the same shared\n * resource with differently named lockfiles; a runner must respect all of\n * them even though it writes only its own.\n */\n\nimport {\n type AtomicFileLockUnavailable,\n probeAtomicFileLock,\n tryAcquireAtomicFileLock,\n} from '../ledger-core/atomic-file-lock'\n\nexport interface SingleRunLockOptions {\n /** Lockfile this runner writes (and checks). */\n readonly lockPath: string\n /** Other runners' lockfiles guarding the same resource; checked, never written. */\n readonly alsoCheck?: readonly string[]\n /** Install a process 'exit' hook that releases the lock. Default true. */\n readonly releaseOnExit?: boolean\n /** Owner pid recorded in the lockfile metadata. Default process.pid. */\n readonly pid?: number\n}\n\nexport interface SingleRunLock {\n /** Remove the lockfile if this process still owns it. Idempotent. */\n release(): void\n}\n\nfunction assertAvailable(path: string): void {\n const unavailable = probeAtomicFileLock({ lockPath: path })\n if (unavailable) throw unavailableError(path, unavailable)\n}\n\nfunction unavailableError(path: string, unavailable: AtomicFileLockUnavailable): Error {\n if (unavailable.reason === 'recovery') {\n return new Error(\n `single-run lock recovery is already in progress (${path}); refusing a concurrent run`,\n )\n }\n return new Error(\n `single-run lock held by live pid ${unavailable.holder.pid} (${path}); refusing a concurrent run on the shared resource`,\n )\n}\n\n/**\n * Acquire the lock or throw naming the live holder. A stale lock (holder pid\n * no longer running) is reclaimed by one contender. An interrupted reclaim\n * leaves a marker that fails closed instead of admitting overlapping runs.\n */\nexport function acquireSingleRunLock(opts: SingleRunLockOptions): SingleRunLock {\n const pid = opts.pid ?? process.pid\n if (!Number.isSafeInteger(pid) || pid <= 0)\n throw new Error('single-run lock pid must be a positive integer')\n for (const path of opts.alsoCheck ?? []) assertAvailable(path)\n const acquisition = tryAcquireAtomicFileLock({\n lockPath: opts.lockPath,\n pid,\n })\n if (!acquisition.acquired) throw unavailableError(opts.lockPath, acquisition)\n const releaseOnExit = opts.releaseOnExit ?? true\n let released = false\n const release = (): void => {\n if (released) return\n released = true\n if (releaseOnExit) process.off('exit', release)\n try {\n acquisition.lock.release()\n } catch {\n // release is best-effort; a leftover stale lock is reclaimed on next acquire\n }\n }\n try {\n for (const path of opts.alsoCheck ?? []) assertAvailable(path)\n } catch (error) {\n release()\n throw error\n }\n if (releaseOnExit) process.on('exit', release)\n return { release }\n}\n","import { createServer, type IncomingMessage, type ServerResponse } from 'node:http'\nimport { contentHash } from '../verdict-cache'\nimport {\n type ExternalOptimizerCallback,\n type ExternalOptimizerCallbackLimits,\n type ExternalOptimizerEvaluationObservation,\n type ExternalTextCandidate,\n type ExternalTextEvaluationRequest,\n isExternalTextCandidate,\n isRecord,\n resolveExternalOptimizerCallbackLimits,\n} from './external-optimizer-contracts'\nimport { closeServer, listenLocal, sendJson } from './external-optimizer-http'\n\ntype UnsequencedObservation = ExternalOptimizerEvaluationObservation extends infer T\n ? T extends ExternalOptimizerEvaluationObservation\n ? Omit<T, 'sequence'>\n : never\n : never\n\nexport async function startExternalOptimizerCallback<TResponse>(args: {\n token: string\n maxEvaluations: number\n acceptEvaluation?: () => number | undefined\n evaluate: (request: ExternalTextEvaluationRequest, signal: AbortSignal) => Promise<TResponse>\n observe?: (observation: ExternalOptimizerEvaluationObservation) => void\n /** Loopback JSON byte limits. Omitted fields use finite defaults. */\n limits?: Partial<ExternalOptimizerCallbackLimits>\n signal?: AbortSignal\n}): Promise<ExternalOptimizerCallback> {\n assertCallbackConfig(args)\n const limits = resolveExternalOptimizerCallbackLimits(args.limits)\n args.signal?.throwIfAborted()\n let evaluations = 0\n let accepting = true\n let closePromise: Promise<void> | undefined\n const activeControllers = new Set<AbortController>()\n const activeHandlers = new Set<Promise<void>>()\n const proposedCandidateHashes = new Set<string>()\n let observationSequence = 0\n const server = createServer((request, response) => {\n if (!accepting) {\n sendJsonIfOpen(response, 503, { error: 'external optimizer callback is closing' })\n return\n }\n\n const controller = new AbortController()\n const abortRequest = (): void => {\n request.destroy()\n response.destroy()\n }\n activeControllers.add(controller)\n controller.signal.addEventListener('abort', abortRequest, { once: true })\n\n let handler!: Promise<void>\n handler = handleCallback(\n request,\n response,\n controller.signal,\n args,\n limits,\n () => {\n const accepted = args.acceptEvaluation ? args.acceptEvaluation() : evaluations + 1\n if (accepted === undefined) return undefined\n if (!Number.isSafeInteger(accepted) || accepted <= 0) {\n throw new Error('external optimizer callback: invalid accepted evaluation count')\n }\n if (accepted > args.maxEvaluations) return undefined\n evaluations += 1\n return accepted\n },\n (observation) => {\n if (!args.observe) return\n args.observe({ ...observation, sequence: ++observationSequence })\n },\n proposedCandidateHashes,\n ).finally(() => {\n controller.signal.removeEventListener('abort', abortRequest)\n activeControllers.delete(controller)\n activeHandlers.delete(handler)\n })\n activeHandlers.add(handler)\n void handler.catch(() => undefined)\n })\n const port = await listenLocal(server)\n const close = (): Promise<void> => {\n closePromise ??= closeCallbackServer()\n return closePromise\n }\n const onAbort = (): void => {\n void close().catch(() => undefined)\n }\n args.signal?.addEventListener('abort', onAbort, { once: true })\n if (args.signal?.aborted) onAbort()\n return {\n url: `http://127.0.0.1:${port}/evaluate`,\n token: args.token,\n evaluations: () => evaluations,\n close,\n }\n\n async function closeCallbackServer(): Promise<void> {\n args.signal?.removeEventListener('abort', onAbort)\n accepting = false\n const closingServer = closeServer(server)\n server.closeIdleConnections?.()\n for (const controller of activeControllers) controller.abort()\n const [serverResult] = await Promise.allSettled([\n closingServer,\n waitForActiveHandlers(activeHandlers),\n ])\n if (activeControllers.size !== 0 || activeHandlers.size !== 0) {\n throw new Error('external optimizer callback closed with active request work')\n }\n if (serverResult?.status === 'rejected') throw serverResult.reason\n }\n}\n\nasync function handleCallback<TResponse>(\n request: IncomingMessage,\n response: ServerResponse,\n signal: AbortSignal,\n args: {\n token: string\n maxEvaluations: number\n acceptEvaluation?: () => number | undefined\n evaluate: (request: ExternalTextEvaluationRequest, signal: AbortSignal) => Promise<TResponse>\n observe?: (observation: ExternalOptimizerEvaluationObservation) => void\n },\n limits: ExternalOptimizerCallbackLimits,\n nextEvaluation: () => number | undefined,\n observe: (observation: UnsequencedObservation) => void,\n proposedCandidateHashes: Set<string>,\n): Promise<void> {\n try {\n if (request.method !== 'POST' || request.url !== '/evaluate') {\n sendJsonIfOpen(response, 404, { error: 'not found' })\n return\n }\n if (request.headers.authorization !== `Bearer ${args.token}`) {\n sendJsonIfOpen(response, 401, { error: 'unauthorized' })\n return\n }\n let body: unknown\n try {\n body = await readJson(request, limits.maxRequestBytes)\n } catch {\n observe({ kind: 'refusal', reason: 'invalid-request' })\n sendJsonIfOpen(response, 400, { error: 'request body must be bounded valid JSON' })\n return\n }\n if (\n !isRecord(body) ||\n !isExternalTextCandidate(body.candidate) ||\n typeof body.exampleId !== 'string'\n ) {\n observe({ kind: 'refusal', reason: 'invalid-request' })\n sendJsonIfOpen(response, 400, { error: 'candidate and exampleId are required strings' })\n return\n }\n const candidate = cloneCandidate(body.candidate)\n const candidateHash = contentHash({ kind: 'external-text-candidate', candidate })\n if (!proposedCandidateHashes.has(candidateHash)) {\n observe({ kind: 'proposal', candidate, candidateHash })\n proposedCandidateHashes.add(candidateHash)\n }\n const count = nextEvaluation()\n if (count === undefined) {\n observe({\n kind: 'refusal',\n reason: 'evaluation-limit',\n candidate,\n candidateHash,\n exampleId: body.exampleId,\n })\n sendJsonIfOpen(response, 429, { error: 'evaluation limit reached' })\n return\n }\n let result: TResponse\n try {\n result = await args.evaluate({ candidate, exampleId: body.exampleId }, signal)\n } catch (error) {\n observe({\n kind: 'refusal',\n reason: 'evaluation-failed',\n candidate,\n candidateHash,\n exampleId: body.exampleId,\n })\n // The thrown detail is the only diagnostic for a failed evaluation —\n // an opaque 500 costs the caller a blind debugging round.\n sendJsonIfOpen(response, 500, {\n error: `evaluation failed: ${String(error).slice(0, 400)}`,\n })\n return\n }\n let encoded: string\n try {\n encoded = encodeJsonResponse(result)\n } catch {\n observe({\n kind: 'refusal',\n reason: 'evaluation-failed',\n candidate,\n candidateHash,\n exampleId: body.exampleId,\n })\n sendJsonIfOpen(response, 500, { error: 'evaluation response must be valid JSON' })\n return\n }\n if (Buffer.byteLength(encoded) > limits.maxResponseBytes) {\n observe({\n kind: 'refusal',\n reason: 'evaluation-failed',\n candidate,\n candidateHash,\n exampleId: body.exampleId,\n })\n sendJsonIfOpen(response, 413, { error: 'evaluation response too large' })\n return\n }\n observe({\n kind: 'evaluation',\n candidate,\n candidateHash,\n exampleId: body.exampleId,\n evaluationNumber: count,\n response: structuredClone(result),\n })\n sendEncodedJsonIfOpen(response, 200, encoded)\n } catch {\n sendJsonIfOpen(response, 500, { error: 'evaluation failed' })\n }\n}\n\nasync function waitForActiveHandlers(activeHandlers: Set<Promise<void>>): Promise<void> {\n while (activeHandlers.size > 0) {\n await Promise.allSettled([...activeHandlers])\n }\n}\n\nfunction sendJsonIfOpen(response: ServerResponse, status: number, body: unknown): void {\n if (response.destroyed || response.writableEnded) return\n sendJson(response, status, body)\n}\n\nfunction readJson(request: IncomingMessage, maxRequestBytes: number): Promise<unknown> {\n return new Promise((resolvePromise, reject) => {\n let size = 0\n const chunks: Buffer[] = []\n request.on('data', (chunk: Buffer) => {\n size += chunk.length\n if (size > maxRequestBytes) {\n reject(new Error('callback body too large'))\n request.destroy()\n return\n }\n chunks.push(chunk)\n })\n request.on('error', reject)\n request.on('end', () => {\n try {\n resolvePromise(JSON.parse(Buffer.concat(chunks).toString('utf8')))\n } catch (error) {\n reject(error)\n }\n })\n })\n}\n\nfunction encodeJsonResponse(value: unknown): string {\n const encoded = JSON.stringify(value)\n if (encoded === undefined) throw new Error('evaluation response must be JSON-serializable')\n return encoded\n}\n\nfunction sendEncodedJsonIfOpen(response: ServerResponse, status: number, encoded: string): void {\n if (response.destroyed || response.writableEnded) return\n response.writeHead(status, {\n 'content-type': 'application/json; charset=utf-8',\n 'content-length': String(Buffer.byteLength(encoded)),\n })\n response.end(encoded)\n}\n\nfunction assertCallbackConfig(args: {\n token: string\n maxEvaluations: number\n acceptEvaluation?: () => number | undefined\n evaluate: (request: ExternalTextEvaluationRequest, signal: AbortSignal) => Promise<unknown>\n observe?: (observation: ExternalOptimizerEvaluationObservation) => void\n limits?: Partial<ExternalOptimizerCallbackLimits>\n signal?: AbortSignal\n}): void {\n if (typeof args.token !== 'string' || !args.token.trim()) {\n throw new Error('external optimizer callback: token must be non-empty')\n }\n if (!Number.isSafeInteger(args.maxEvaluations) || args.maxEvaluations <= 0) {\n throw new Error('external optimizer callback: maxEvaluations must be a positive safe integer')\n }\n if (args.acceptEvaluation !== undefined && typeof args.acceptEvaluation !== 'function') {\n throw new Error('external optimizer callback: acceptEvaluation must be a function')\n }\n if (typeof args.evaluate !== 'function') {\n throw new Error('external optimizer callback: evaluate must be a function')\n }\n if (args.observe !== undefined && typeof args.observe !== 'function') {\n throw new Error('external optimizer callback: observe must be a function')\n }\n}\n\nfunction cloneCandidate(candidate: ExternalTextCandidate): ExternalTextCandidate {\n return typeof candidate === 'string' ? candidate : { ...candidate }\n}\n"],"mappings":"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"}
@@ -2,7 +2,7 @@ import { s as ValidationError } from "./errors-Dngq5h35.js";
2
2
  import { t as canonicalJson } from "./verdict-cache-mZf5FEiY.js";
3
3
  import { c as costForTokenPricing, i as CostLedger } from "./cost-ledger-B1qx30B4.js";
4
4
  import { a as appendLedgerLine, o as tryWithLedgerFileLock } from "./ledger-core-DTae9rv_.js";
5
- import { h as ModelSubstitutionError, v as assertServedModel, y as assertServedModelPolicy } from "./llm-client-CxyJ8Oft.js";
5
+ import { h as ModelSubstitutionError, v as assertServedModel, y as assertServedModelPolicy } from "./llm-client-Bg32RW0j.js";
6
6
  import { createRequire } from "node:module";
7
7
  import { randomBytes } from "node:crypto";
8
8
  import { isAbsolute, join, resolve } from "node:path";
@@ -255,14 +255,22 @@ function isRecord(value) {
255
255
  * The `claude` CLI driven by GEPA agent engines speaks `POST /v1/messages`.
256
256
  * Each request becomes one canonical `ExternalOptimizerChatRequest`, so the
257
257
  * shared reservation, receipt, and execution-evidence pipeline applies
258
- * unchanged. Anything without a full-fidelity mapping onto that contract is
259
- * refused with a loud Anthropic error envelope. The only silent strips are
260
- * `metadata` and `cache_control`: the CLI always sends both, neither has a
261
- * canonical slot, and dropping them changes no message content.
258
+ * unchanged. Tool use translates in both directions: Anthropic `tools`,
259
+ * `tool_choice`, `tool_use`, and `tool_result` map onto the canonical
260
+ * function-tool contract, and a tool-calling response is synthesized back as
261
+ * the Anthropic stream shape the CLI expects.
262
262
  *
263
- * Tool use is refused because the canonical contract carries text-only
264
- * messages. Metering CLI tool calls requires a tool extension of
265
- * `ExternalOptimizerChatRequest` and the execution-owner result.
263
+ * Anything without a full-fidelity mapping onto the canonical contract is
264
+ * refused with a loud Anthropic error envelope, with two exceptions:
265
+ *
266
+ * - `metadata` and `cache_control` are dropped silently: the CLI always sends
267
+ * both, neither has a canonical slot, and dropping them changes no content.
268
+ * - Claude-specific control fields (`thinking`, `context_management`,
269
+ * `output_config`, replayed reasoning blocks, and unknown tool-entry keys)
270
+ * are STRIPPED AND RECORDED. They carry no token-billing semantics on the
271
+ * owner wire, and the real CLI sends them on every request — refusing them
272
+ * makes every real CLI call impossible. The dropped names are returned in
273
+ * `strippedFields` so accounting discloses exactly what the shim removed.
266
274
  */
267
275
  var AnthropicRequestRefusal = class extends Error {
268
276
  status;
@@ -280,12 +288,16 @@ const REQUEST_FIELDS = /* @__PURE__ */ new Set([
280
288
  "system",
281
289
  "temperature",
282
290
  "stream",
283
- "metadata"
291
+ "metadata",
292
+ "tools",
293
+ "tool_choice"
294
+ ]);
295
+ const STRIPPED_REQUEST_FIELDS = /* @__PURE__ */ new Set([
296
+ "thinking",
297
+ "context_management",
298
+ "output_config"
284
299
  ]);
285
300
  const REFUSED_REQUEST_FIELDS = {
286
- tools: "tools require a tool-call extension of the canonical execution-owner contract",
287
- tool_choice: "tool_choice requires a tool-call extension of the canonical execution-owner contract",
288
- thinking: "thinking has no canonical mapping; the injected CLI environment disables it",
289
301
  top_p: "top_p has no canonical execution-owner slot",
290
302
  top_k: "top_k has no canonical execution-owner slot",
291
303
  stop_sequences: "stop_sequences has no canonical execution-owner slot"
@@ -298,8 +310,13 @@ function translateAnthropicMessagesRequest(raw, expectedModel, maxOutputTokensPe
298
310
  throw refuse("request must be valid JSON");
299
311
  }
300
312
  if (!isRecord(parsed)) throw refuse("request must be a JSON object");
313
+ const stripped = /* @__PURE__ */ new Set();
301
314
  for (const key of Object.keys(parsed)) {
302
315
  if (REQUEST_FIELDS.has(key)) continue;
316
+ if (STRIPPED_REQUEST_FIELDS.has(key)) {
317
+ stripped.add(key);
318
+ continue;
319
+ }
303
320
  const reason = REFUSED_REQUEST_FIELDS[key];
304
321
  throw refuse(reason ?? `request field '${key}' has no metered translation and is not supported`);
305
322
  }
@@ -315,35 +332,183 @@ function translateAnthropicMessagesRequest(raw, expectedModel, maxOutputTokensPe
315
332
  role: "system",
316
333
  content: systemText
317
334
  });
318
- messages.push(...translateConversation(parsed.messages));
335
+ messages.push(...translateConversation(parsed.messages, stripped));
336
+ const tools = translateTools(parsed.tools, stripped);
337
+ const toolChoice = translateToolChoice(parsed.tool_choice, stripped);
319
338
  return {
320
339
  request: {
321
340
  model: expectedModel,
322
341
  messages,
323
342
  maxTokens,
324
- ...parsed.temperature !== void 0 ? { temperature: parsed.temperature } : {}
343
+ ...parsed.temperature !== void 0 ? { temperature: parsed.temperature } : {},
344
+ ...tools === void 0 ? {} : { tools },
345
+ ...toolChoice === void 0 ? {} : { toolChoice }
325
346
  },
326
347
  maxOutputTokens: maxTokens,
327
- stream: parsed.stream === true
348
+ stream: parsed.stream === true,
349
+ strippedFields: [...stripped].sort()
328
350
  };
329
351
  }
352
+ const TOOL_ENTRY_FIELDS = /* @__PURE__ */ new Set([
353
+ "name",
354
+ "description",
355
+ "input_schema",
356
+ "type",
357
+ "cache_control"
358
+ ]);
359
+ function translateTools(value, stripped) {
360
+ if (value === void 0) return void 0;
361
+ if (!Array.isArray(value) || value.length === 0) throw refuse("tools must be a non-empty array of tool definitions");
362
+ return value.map((entry, index) => {
363
+ if (!isRecord(entry)) throw refuse(`tools[${index}] must be an object`);
364
+ if (entry.type !== void 0 && entry.type !== "custom") throw refuse(`tools[${index}] type '${String(entry.type)}' has no metered translation; only client function tools are served`);
365
+ if (typeof entry.name !== "string" || !entry.name.trim()) throw refuse(`tools[${index}] requires a non-empty name`);
366
+ if (entry.description !== void 0 && typeof entry.description !== "string") throw refuse(`tools[${index}] description must be a string`);
367
+ if (!isRecord(entry.input_schema)) throw refuse(`tools[${index}] requires an input_schema object`);
368
+ for (const key of Object.keys(entry)) {
369
+ if (TOOL_ENTRY_FIELDS.has(key)) continue;
370
+ stripped.add(`tools.${key}`);
371
+ }
372
+ return {
373
+ type: "function",
374
+ function: {
375
+ name: entry.name,
376
+ ...entry.description === void 0 ? {} : { description: entry.description },
377
+ parameters: entry.input_schema
378
+ }
379
+ };
380
+ });
381
+ }
382
+ function translateToolChoice(value, stripped) {
383
+ if (value === void 0) return void 0;
384
+ if (!isRecord(value) || typeof value.type !== "string") throw refuse("tool_choice must be an object with a type");
385
+ for (const key of Object.keys(value)) {
386
+ if (key === "type") continue;
387
+ if (key === "name" && value.type === "tool") continue;
388
+ if (key === "disable_parallel_tool_use") {
389
+ stripped.add("tool_choice.disable_parallel_tool_use");
390
+ continue;
391
+ }
392
+ throw refuse(`tool_choice field '${key}' has no metered translation`);
393
+ }
394
+ if (value.type === "auto") return "auto";
395
+ if (value.type === "none") return "none";
396
+ if (value.type === "any") return "required";
397
+ if (value.type === "tool") {
398
+ if (typeof value.name !== "string" || !value.name.trim()) throw refuse("tool_choice type tool requires a tool name");
399
+ return {
400
+ type: "function",
401
+ function: { name: value.name }
402
+ };
403
+ }
404
+ throw refuse(`tool_choice type '${String(value.type)}' has no metered translation`);
405
+ }
330
406
  function translateSystem(value) {
331
407
  if (value === void 0) return void 0;
332
408
  if (typeof value === "string") return value;
333
409
  if (!Array.isArray(value)) throw refuse("system must be a string or an array of text blocks");
334
410
  return joinTextBlocks(value, "system");
335
411
  }
336
- function translateConversation(value) {
412
+ function translateConversation(value, stripped) {
337
413
  if (!Array.isArray(value) || value.length === 0) throw refuse("request requires a non-empty messages array");
338
- return value.map((entry) => {
414
+ const out = [];
415
+ for (const entry of value) {
339
416
  if (!isRecord(entry)) throw refuse("each message must be an object");
340
- if (entry.role !== "user" && entry.role !== "assistant") throw refuse(`message role must be 'user' or 'assistant'`);
341
- const content = typeof entry.content === "string" ? entry.content : Array.isArray(entry.content) && entry.content.length > 0 ? joinTextBlocks(entry.content, `${entry.role} message`) : refuseValue(`${entry.role} message content must be a string or a non-empty block array`);
342
- return {
343
- role: entry.role,
344
- content
345
- };
346
- });
417
+ if (entry.role !== "system" && entry.role !== "user" && entry.role !== "assistant") throw refuse(`message role must be 'system', 'user', or 'assistant'`);
418
+ if (typeof entry.content === "string") {
419
+ out.push({
420
+ role: entry.role,
421
+ content: entry.content
422
+ });
423
+ continue;
424
+ }
425
+ if (!Array.isArray(entry.content) || entry.content.length === 0) throw refuse(`${entry.role} message content must be a string or a non-empty block array`);
426
+ if (entry.role === "system") out.push({
427
+ role: "system",
428
+ content: joinTextBlocks(entry.content, "system")
429
+ });
430
+ else if (entry.role === "assistant") out.push(translateAssistantBlocks(entry.content, stripped));
431
+ else out.push(...translateUserBlocks(entry.content, stripped));
432
+ }
433
+ return out;
434
+ }
435
+ function translateAssistantBlocks(blocks, stripped) {
436
+ const parts = [];
437
+ const toolCalls = [];
438
+ for (const block of blocks) {
439
+ if (!isRecord(block)) throw refuse("assistant message content blocks must be objects");
440
+ if (block.type === "text") {
441
+ if (typeof block.text !== "string") throw refuse("assistant message text blocks require string text");
442
+ parts.push(block.text);
443
+ continue;
444
+ }
445
+ if (block.type === "tool_use") {
446
+ if (typeof block.id !== "string" || !block.id.trim()) throw refuse("assistant tool_use blocks require a string id");
447
+ if (typeof block.name !== "string" || !block.name.trim()) throw refuse("assistant tool_use blocks require a string name");
448
+ if (!isRecord(block.input)) throw refuse("assistant tool_use blocks require an input object");
449
+ toolCalls.push({
450
+ id: block.id,
451
+ name: block.name,
452
+ argumentsJson: JSON.stringify(block.input)
453
+ });
454
+ continue;
455
+ }
456
+ if (block.type === "thinking" || block.type === "redacted_thinking") {
457
+ stripped.add(`messages.${String(block.type)}`);
458
+ continue;
459
+ }
460
+ throw refuse(`assistant message content block type '${String(block.type)}' has no metered translation`);
461
+ }
462
+ return {
463
+ role: "assistant",
464
+ content: parts.join("\n\n"),
465
+ ...toolCalls.length > 0 ? { toolCalls } : {}
466
+ };
467
+ }
468
+ function translateUserBlocks(blocks, stripped) {
469
+ const out = [];
470
+ const textParts = [];
471
+ const flushText = () => {
472
+ if (textParts.length === 0) return;
473
+ out.push({
474
+ role: "user",
475
+ content: textParts.join("\n\n")
476
+ });
477
+ textParts.length = 0;
478
+ };
479
+ for (const block of blocks) {
480
+ if (!isRecord(block)) throw refuse("user message content blocks must be objects");
481
+ if (block.type === "text") {
482
+ if (typeof block.text !== "string") throw refuse("user message text blocks require string text");
483
+ textParts.push(block.text);
484
+ continue;
485
+ }
486
+ if (block.type === "tool_result") {
487
+ if (typeof block.tool_use_id !== "string" || !block.tool_use_id.trim()) throw refuse("user tool_result blocks require a string tool_use_id");
488
+ if (block.is_error === true) stripped.add("messages.tool_result.is_error");
489
+ flushText();
490
+ out.push({
491
+ role: "tool",
492
+ content: toolResultText(block.content),
493
+ toolCallId: block.tool_use_id
494
+ });
495
+ continue;
496
+ }
497
+ throw refuse(`user message content block type '${String(block.type)}' has no metered translation`);
498
+ }
499
+ flushText();
500
+ return out;
501
+ }
502
+ function toolResultText(value) {
503
+ if (value === void 0) return "";
504
+ if (typeof value === "string") return value;
505
+ if (!Array.isArray(value)) throw refuse("user tool_result content must be a string or a block array");
506
+ const parts = [];
507
+ for (const block of value) {
508
+ if (!isRecord(block) || block.type !== "text" || typeof block.text !== "string") throw refuse("user tool_result content blocks must be text blocks");
509
+ parts.push(block.text);
510
+ }
511
+ return parts.join("\n\n");
347
512
  }
348
513
  function joinTextBlocks(blocks, label) {
349
514
  const parts = [];
@@ -354,7 +519,6 @@ function joinTextBlocks(blocks, label) {
354
519
  parts.push(block.text);
355
520
  continue;
356
521
  }
357
- if (block.type === "tool_use" || block.type === "tool_result") throw refuse(`${label} ${String(block.type)} blocks require a tool-call extension of the canonical execution-owner contract`);
358
522
  throw refuse(`${label} content block type '${String(block.type)}' has no metered translation`);
359
523
  }
360
524
  return parts.join("\n\n");
@@ -362,7 +526,9 @@ function joinTextBlocks(blocks, label) {
362
526
  const STOP_REASON_BY_FINISH_REASON = {
363
527
  stop: "end_turn",
364
528
  length: "max_tokens",
365
- content_filter: "refusal"
529
+ content_filter: "refusal",
530
+ tool_calls: "tool_use",
531
+ tool_use: "tool_use"
366
532
  };
367
533
  /**
368
534
  * Encode one canonical owner response as an Anthropic message.
@@ -370,7 +536,8 @@ const STOP_REASON_BY_FINISH_REASON = {
370
536
  * Usage fields are display-level for the CLI; the ledger receipt reads the
371
537
  * same canonical response through the proxy's strict receipt checks. An
372
538
  * uncaptured usage renders as zeros here while the receipt keeps the honest
373
- * unknown-cost maximum.
539
+ * unknown-cost maximum. Canonical tool calls become `tool_use` content blocks
540
+ * after the text block, matching Anthropic block ordering.
374
541
  */
375
542
  function encodeAnthropicMessage(response, callId) {
376
543
  const finishReason = response.finishReason ?? "stop";
@@ -378,15 +545,23 @@ function encodeAnthropicMessage(response, callId) {
378
545
  if (stopReason === void 0) throw new AnthropicRequestRefusal(502, "api_error", `optimizer model finish reason '${String(finishReason)}' has no Anthropic translation`);
379
546
  const captured = response.usage.captured !== false;
380
547
  const cachedTokens = response.usage.cachedPromptTokens ?? 0;
548
+ const content = [];
549
+ if (response.content.length > 0) content.push({
550
+ type: "text",
551
+ text: response.content
552
+ });
553
+ for (const call of response.toolCalls ?? []) content.push({
554
+ type: "tool_use",
555
+ id: call.id,
556
+ name: call.name,
557
+ input: parseToolInput(call)
558
+ });
381
559
  return {
382
560
  id: `msg_${callId}`,
383
561
  type: "message",
384
562
  role: "assistant",
385
563
  model: response.model,
386
- content: response.content.length > 0 ? [{
387
- type: "text",
388
- text: response.content
389
- }] : [],
564
+ content,
390
565
  stop_reason: stopReason,
391
566
  stop_sequence: null,
392
567
  usage: captured ? {
@@ -400,12 +575,26 @@ function encodeAnthropicMessage(response, callId) {
400
575
  }
401
576
  };
402
577
  }
578
+ function parseToolInput(call) {
579
+ const text = call.argumentsJson.trim();
580
+ if (text === "") return {};
581
+ let parsed;
582
+ try {
583
+ parsed = JSON.parse(text);
584
+ } catch {
585
+ throw new AnthropicRequestRefusal(502, "api_error", `optimizer model tool call '${call.name}' returned arguments that are not valid JSON`);
586
+ }
587
+ if (!isRecord(parsed)) throw new AnthropicRequestRefusal(502, "api_error", `optimizer model tool call '${call.name}' arguments must encode a JSON object`);
588
+ return parsed;
589
+ }
403
590
  /**
404
591
  * Render one completed Anthropic message as the SSE stream the CLI expects.
405
592
  *
406
593
  * The whole stream is synthesized after the owner call completes, so the
407
594
  * event burst is exact: usage is final and a cap-violating response is
408
- * rejected with a real status instead of a half-delivered stream.
595
+ * rejected with a real status instead of a half-delivered stream. A
596
+ * `tool_use` block streams as an empty-input `content_block_start` plus one
597
+ * `input_json_delta` carrying the complete arguments JSON.
409
598
  */
410
599
  function renderAnthropicSseStream(message) {
411
600
  const content = Array.isArray(message.content) ? message.content : [];
@@ -426,15 +615,47 @@ function renderAnthropicSseStream(message) {
426
615
  }
427
616
  }];
428
617
  for (const [index, block] of content.entries()) {
429
- if (!isRecord(block) || block.type !== "text") continue;
430
- events.push({
618
+ if (!isRecord(block)) continue;
619
+ if (block.type === "text") {
620
+ events.push({
621
+ event: "content_block_start",
622
+ data: {
623
+ type: "content_block_start",
624
+ index,
625
+ content_block: {
626
+ type: "text",
627
+ text: ""
628
+ }
629
+ }
630
+ }, {
631
+ event: "content_block_delta",
632
+ data: {
633
+ type: "content_block_delta",
634
+ index,
635
+ delta: {
636
+ type: "text_delta",
637
+ text: block.text
638
+ }
639
+ }
640
+ }, {
641
+ event: "content_block_stop",
642
+ data: {
643
+ type: "content_block_stop",
644
+ index
645
+ }
646
+ });
647
+ continue;
648
+ }
649
+ if (block.type === "tool_use") events.push({
431
650
  event: "content_block_start",
432
651
  data: {
433
652
  type: "content_block_start",
434
653
  index,
435
654
  content_block: {
436
- type: "text",
437
- text: ""
655
+ type: "tool_use",
656
+ id: block.id,
657
+ name: block.name,
658
+ input: {}
438
659
  }
439
660
  }
440
661
  }, {
@@ -443,8 +664,8 @@ function renderAnthropicSseStream(message) {
443
664
  type: "content_block_delta",
444
665
  index,
445
666
  delta: {
446
- type: "text_delta",
447
- text: block.text
667
+ type: "input_json_delta",
668
+ partial_json: JSON.stringify(block.input)
448
669
  }
449
670
  }
450
671
  }, {
@@ -492,9 +713,6 @@ function anthropicErrorTypeForStatus(status) {
492
713
  function refuse(message) {
493
714
  return new AnthropicRequestRefusal(400, "invalid_request_error", message);
494
715
  }
495
- function refuseValue(message) {
496
- throw refuse(message);
497
- }
498
716
  //#endregion
499
717
  //#region src/campaign/external-optimizer-http.ts
500
718
  function listenLocal(server) {
@@ -710,7 +928,8 @@ async function handleModelProxyRequest(args) {
710
928
  try {
711
929
  const requestTags = wire === "anthropic" ? {
712
930
  ...args.args.tags ?? {},
713
- wire: "anthropic"
931
+ wire: "anthropic",
932
+ ...parsed.strippedFields === void 0 ? {} : { strippedFields: parsed.strippedFields.join(",") }
714
933
  } : args.args.tags;
715
934
  const paid = await args.args.costLedger.runPaidCall({
716
935
  ...args.args.callId ? { callId: args.args.callId } : {},
@@ -928,6 +1147,7 @@ function parseModelProxyRequest(body, path, expectedModel, budget) {
928
1147
  return {
929
1148
  maxOutputTokens: translated.maxOutputTokens,
930
1149
  stream: translated.stream,
1150
+ ...translated.strippedFields.length > 0 ? { strippedFields: translated.strippedFields } : {},
931
1151
  request: freezeJsonSnapshot(translated.request, "optimizer model canonical request")
932
1152
  };
933
1153
  }
@@ -1190,6 +1410,7 @@ function snapshotChatResponse(value, expectedModel, allowWithinFamily) {
1190
1410
  if (!isNonnegativeFiniteNumber(value.durationMs)) throw new OwnerModelContractError("optimizer model callback response durationMs must be finite and non-negative");
1191
1411
  if (value.costUsd !== null && !isNonnegativeFiniteNumber(value.costUsd)) throw new OwnerModelContractError("optimizer model callback response costUsd must be null or finite and non-negative");
1192
1412
  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");
1413
+ const toolCalls = snapshotResponseToolCalls(value.toolCalls);
1193
1414
  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");
1194
1415
  if (!isRecord(value.raw)) throw new OwnerModelContractError("optimizer model callback response raw must be an object");
1195
1416
  if (!isRecord(value.usage)) throw new OwnerModelContractError("optimizer model callback response usage must be an object");
@@ -1214,6 +1435,7 @@ function snapshotChatResponse(value, expectedModel, allowWithinFamily) {
1214
1435
  }
1215
1436
  return deepFreezeJson({
1216
1437
  content: value.content,
1438
+ ...toolCalls === void 0 ? {} : { toolCalls },
1217
1439
  usage: {
1218
1440
  promptTokens,
1219
1441
  completionTokens,
@@ -1230,6 +1452,19 @@ function snapshotChatResponse(value, expectedModel, allowWithinFamily) {
1230
1452
  raw
1231
1453
  });
1232
1454
  }
1455
+ /** Snapshot the owner's canonical tool calls onto the allowlisted response. */
1456
+ function snapshotResponseToolCalls(value) {
1457
+ if (value === void 0) return void 0;
1458
+ if (!Array.isArray(value) || value.length === 0) throw new OwnerModelContractError("optimizer model callback response toolCalls must be a non-empty array when present");
1459
+ return value.map((entry) => {
1460
+ if (!isRecord(entry) || typeof entry.id !== "string" || !entry.id || typeof entry.name !== "string" || !entry.name || typeof entry.argumentsJson !== "string") throw new OwnerModelContractError("optimizer model callback response toolCalls entries require string id, name, and argumentsJson");
1461
+ return {
1462
+ id: entry.id,
1463
+ name: entry.name,
1464
+ argumentsJson: entry.argumentsJson
1465
+ };
1466
+ });
1467
+ }
1233
1468
  function encodeAnthropicResponseBody(args) {
1234
1469
  const message = encodeAnthropicMessage(args.response, args.callId);
1235
1470
  const body = new TextEncoder().encode(args.stream ? renderAnthropicSseStream(message) : JSON.stringify(message));
@@ -1666,4 +1901,4 @@ function summarizeProcessOutput(capture, max = 4e3) {
1666
1901
  //#endregion
1667
1902
  export { SEARCH_LEDGER_FILE_CONTEXT as C, SearchLedgerIntegrityError as E, inMemoryCampaignStorage as S, SearchLedgerError as T, resolveExternalOptimizerCallbackLimits as _, closeServer as a, createRunCostLedger as b, DEFAULT_EXTERNAL_OPTIMIZER_CALLBACK_LIMITS as c, assertJsonValue as d, assertNoCredentialValues as f, removeCredentialEnvironment as g, isRecord as h, startExternalOptimizerModelProxy as i, DEFAULT_EXTERNAL_OPTIMIZER_PROCESS_LIMITS as l, isExternalTextCandidate as m, closeExternalOptimizerResources as n, listenLocal as o, isCandidateText as p, runWithCleanup as r, sendJson as s, runExternalOptimizerProcess as t, assertExternalOptimizerModelBudget as u, resolveExternalOptimizerProcessLimits as v, SearchLedgerConflictError as w, fsCampaignStorage as x, safePathComponent as y };
1668
1903
 
1669
- //# sourceMappingURL=external-optimizer-subprocess-BepyNwtj.js.map
1904
+ //# sourceMappingURL=external-optimizer-subprocess-CKNb42oM.js.map