@tangle-network/agent-eval 0.150.0 → 0.150.1

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 (121) hide show
  1. package/CHANGELOG.md +14 -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-CAFwbH0L.js} +7 -7
  9. package/dist/{benchmark-command-CqGA0SJQ.js.map → benchmark-command-CAFwbH0L.js.map} +1 -1
  10. package/dist/benchmarks/index.d.ts +4 -4
  11. package/dist/benchmarks/index.js +3 -3
  12. package/dist/campaign/index.d.ts +8 -8
  13. package/dist/campaign/index.js +5 -5
  14. package/dist/{campaign-CR6Yt5ZS.js → campaign-CN_7xJdV.js} +18 -9
  15. package/dist/campaign-CN_7xJdV.js.map +1 -0
  16. package/dist/{capture-fetch-Im0UN1i6.d.ts → capture-fetch-BBVFzhkk.d.ts} +2 -2
  17. package/dist/{capture-fetch-Im0UN1i6.d.ts.map → capture-fetch-BBVFzhkk.d.ts.map} +1 -1
  18. package/dist/{chat-client-sFiClniQ.js → chat-client-2bVfrzhN.js} +2 -2
  19. package/dist/{chat-client-sFiClniQ.js.map → chat-client-2bVfrzhN.js.map} +1 -1
  20. package/dist/cli.js +2 -2
  21. package/dist/{client-w6vVJhSp.d.ts → client-kPQYT_56.d.ts} +2 -2
  22. package/dist/{client-w6vVJhSp.d.ts.map → client-kPQYT_56.d.ts.map} +1 -1
  23. package/dist/contract/index.d.ts +10 -10
  24. package/dist/contract/index.js +5 -5
  25. package/dist/{default-registry-BhRNsP-D.d.ts → default-registry-Cw0Ohdoj.d.ts} +6 -6
  26. package/dist/{default-registry-BhRNsP-D.d.ts.map → default-registry-Cw0Ohdoj.d.ts.map} +1 -1
  27. package/dist/{define-agent-eval-Cw3mkB4-.d.ts → define-agent-eval-CEQWL9Hy.d.ts} +5 -5
  28. package/dist/{define-agent-eval-Cw3mkB4-.d.ts.map → define-agent-eval-CEQWL9Hy.d.ts.map} +1 -1
  29. package/dist/{define-agent-eval-ChrtErmg.js → define-agent-eval-rqNyVhVV.js} +4 -4
  30. package/dist/{define-agent-eval-ChrtErmg.js.map → define-agent-eval-rqNyVhVV.js.map} +1 -1
  31. package/dist/{dspy-rlm-engine-Csi3CUAT.js → dspy-rlm-engine-DHI0WrUU.js} +2 -2
  32. package/dist/{dspy-rlm-engine-Csi3CUAT.js.map → dspy-rlm-engine-DHI0WrUU.js.map} +1 -1
  33. package/dist/{engine-CgQdUh-r.d.ts → engine-BLzhNzoY.d.ts} +4 -4
  34. package/dist/{engine-CgQdUh-r.d.ts.map → engine-BLzhNzoY.d.ts.map} +1 -1
  35. package/dist/{eval-campaign-Czc2TcW5.js → eval-campaign-C4jmuM-b.js} +2 -2
  36. package/dist/{eval-campaign-Czc2TcW5.js.map → eval-campaign-C4jmuM-b.js.map} +1 -1
  37. package/dist/{exact-types-D9_T83Ol.d.ts → exact-types-ccQAyut1.d.ts} +2 -2
  38. package/dist/{exact-types-D9_T83Ol.d.ts.map → exact-types-ccQAyut1.d.ts.map} +1 -1
  39. package/dist/experiment/index.d.ts +3 -3
  40. package/dist/{experiment-tracker-Cp9ougHs.d.ts → experiment-tracker-0MhuPArU.d.ts} +2 -2
  41. package/dist/{experiment-tracker-Cp9ougHs.d.ts.map → experiment-tracker-0MhuPArU.d.ts.map} +1 -1
  42. package/dist/{external-optimizer-contracts-mbhR9Ldu.d.ts → external-optimizer-contracts-DbLsm4Po.d.ts} +2 -2
  43. package/dist/{external-optimizer-contracts-mbhR9Ldu.d.ts.map → external-optimizer-contracts-DbLsm4Po.d.ts.map} +1 -1
  44. package/dist/{external-optimizer-process-tDe72VRO.js → external-optimizer-process-Bhmzngf-.js} +2 -2
  45. package/dist/{external-optimizer-process-tDe72VRO.js.map → external-optimizer-process-Bhmzngf-.js.map} +1 -1
  46. package/dist/{external-optimizer-subprocess-BepyNwtj.js → external-optimizer-subprocess-Dn90UqN2.js} +273 -42
  47. package/dist/external-optimizer-subprocess-Dn90UqN2.js.map +1 -0
  48. package/dist/{feedback-trajectory-Cc9M2tNF.d.ts → feedback-trajectory-DpTTjo0q.d.ts} +3 -3
  49. package/dist/{feedback-trajectory-Cc9M2tNF.d.ts.map → feedback-trajectory-DpTTjo0q.d.ts.map} +1 -1
  50. package/dist/hosted/index.d.ts +2 -2
  51. package/dist/index-C1ravkGA.d.ts +1 -0
  52. package/dist/{index-DL4MJuJL.d.ts → index-CTKpu9ry.d.ts} +7 -7
  53. package/dist/{index-DL4MJuJL.d.ts.map → index-CTKpu9ry.d.ts.map} +1 -1
  54. package/dist/{index-dh0kp2GM.d.ts → index-IQccV3Ou.d.ts} +15 -10
  55. package/dist/index-IQccV3Ou.d.ts.map +1 -0
  56. package/dist/index.d.ts +19 -19
  57. package/dist/index.js +8 -8
  58. package/dist/{integrity-D2MAlC_K.d.ts → integrity-B0dZ96EO.d.ts} +2 -2
  59. package/dist/{integrity-D2MAlC_K.d.ts.map → integrity-B0dZ96EO.d.ts.map} +1 -1
  60. package/dist/{llm-client-CxyJ8Oft.js → llm-client-Bg32RW0j.js} +47 -2
  61. package/dist/llm-client-Bg32RW0j.js.map +1 -0
  62. package/dist/{llm-judge-D7UuMI2t.js → llm-judge-CVq33oz1.js} +4 -4
  63. package/dist/{llm-judge-D7UuMI2t.js.map → llm-judge-CVq33oz1.js.map} +1 -1
  64. package/dist/{matrix-D3hL68uV.d.ts → matrix-DrVnRp4G.d.ts} +2 -2
  65. package/dist/{matrix-D3hL68uV.d.ts.map → matrix-DrVnRp4G.d.ts.map} +1 -1
  66. package/dist/meta-eval/index.d.ts +1 -1
  67. package/dist/multishot/golden/index.d.ts +1 -1
  68. package/dist/multishot/index.d.ts +2 -2
  69. package/dist/openapi.json +1 -1
  70. package/dist/{produced-state-WJs_enbZ.js → produced-state-jfk8Du3b.js} +3 -3
  71. package/dist/{produced-state-WJs_enbZ.js.map → produced-state-jfk8Du3b.js.map} +1 -1
  72. package/dist/{promotion-policy-DYRzXdTV.d.ts → promotion-policy-DLOUkYhI.d.ts} +2 -2
  73. package/dist/{promotion-policy-DYRzXdTV.d.ts.map → promotion-policy-DLOUkYhI.d.ts.map} +1 -1
  74. package/dist/{provenance-Bh9k3sjl.d.ts → provenance-oA4-zUqm.d.ts} +6 -6
  75. package/dist/{provenance-Bh9k3sjl.d.ts.map → provenance-oA4-zUqm.d.ts.map} +1 -1
  76. package/dist/{registry-DhpX9Bsg.d.ts → registry-BQwrSYpC.d.ts} +4 -4
  77. package/dist/{registry-DhpX9Bsg.d.ts.map → registry-BQwrSYpC.d.ts.map} +1 -1
  78. package/dist/{researcher-CbCK-ppF.d.ts → researcher-DJnoUE8c.d.ts} +3 -3
  79. package/dist/{researcher-CbCK-ppF.d.ts.map → researcher-DJnoUE8c.d.ts.map} +1 -1
  80. package/dist/{reward-hacking-DFo2FU5J.js → reward-hacking-DKI9T52l.js} +19 -1
  81. package/dist/reward-hacking-DKI9T52l.js.map +1 -0
  82. package/dist/rl.d.ts +2 -2
  83. package/dist/rl.js +2 -2
  84. package/dist/{semantic-concept-judge-B7KPD57W.js → semantic-concept-judge-laMCnTLn.js} +2 -2
  85. package/dist/{semantic-concept-judge-B7KPD57W.js.map → semantic-concept-judge-laMCnTLn.js.map} +1 -1
  86. package/dist/{series-convergence-VhTad5TM.d.ts → series-convergence-BxKEgBwA.d.ts} +2 -2
  87. package/dist/{series-convergence-VhTad5TM.d.ts.map → series-convergence-BxKEgBwA.d.ts.map} +1 -1
  88. package/dist/{server-DNiUVirV.js → server-dIWwF3j_.js} +2 -2
  89. package/dist/{server-DNiUVirV.js.map → server-dIWwF3j_.js.map} +1 -1
  90. package/dist/{skillopt-optimization-method-BtgL6PMH.d.ts → skillopt-optimization-method-BDD_o1xE.d.ts} +5 -5
  91. package/dist/{skillopt-optimization-method-BtgL6PMH.d.ts.map → skillopt-optimization-method-BDD_o1xE.d.ts.map} +1 -1
  92. package/dist/{skillopt-optimization-method-L8vi4h4m.js → skillopt-optimization-method-DLeUcK-K.js} +5 -5
  93. package/dist/{skillopt-optimization-method-L8vi4h4m.js.map → skillopt-optimization-method-DLeUcK-K.js.map} +1 -1
  94. package/dist/{statistical-heldout-Caxd30ZF.d.ts → statistical-heldout-_woZ9q9j.d.ts} +2 -2
  95. package/dist/{statistical-heldout-Caxd30ZF.d.ts.map → statistical-heldout-_woZ9q9j.d.ts.map} +1 -1
  96. package/dist/{store-tool-spans-BGg2QFk1.d.ts → store-tool-spans-Br2_IUhm.d.ts} +4 -4
  97. package/dist/{store-tool-spans-BGg2QFk1.d.ts.map → store-tool-spans-Br2_IUhm.d.ts.map} +1 -1
  98. package/dist/{tool-groups-BuhFx38z.d.ts → tool-groups-B4tqh8jB.d.ts} +3 -3
  99. package/dist/tool-groups-B4tqh8jB.d.ts.map +1 -0
  100. package/dist/trace-repair/index.d.ts +1 -1
  101. package/dist/traces.d.ts +7 -7
  102. package/dist/{types-BNbhD_Vj.d.ts → types-B2NsbrNy.d.ts} +2 -2
  103. package/dist/{types-BNbhD_Vj.d.ts.map → types-B2NsbrNy.d.ts.map} +1 -1
  104. package/dist/{types-B61iI2ZG.d.ts → types-CLAwnY-L.d.ts} +2 -2
  105. package/dist/{types-B61iI2ZG.d.ts.map → types-CLAwnY-L.d.ts.map} +1 -1
  106. package/dist/{types-COEjZMLY.d.ts → types-DdFNuyxQ.d.ts} +3 -3
  107. package/dist/{types-COEjZMLY.d.ts.map → types-DdFNuyxQ.d.ts.map} +1 -1
  108. package/dist/{types-COAJy8Ku.d.ts → types-jUBXJ7Iz.d.ts} +36 -3
  109. package/dist/types-jUBXJ7Iz.d.ts.map +1 -0
  110. package/dist/wire/index.d.ts +2 -2
  111. package/dist/wire/index.js +1 -1
  112. package/docs/campaign-proposers.md +8 -1
  113. package/package.json +1 -1
  114. package/dist/campaign-CR6Yt5ZS.js.map +0 -1
  115. package/dist/external-optimizer-subprocess-BepyNwtj.js.map +0 -1
  116. package/dist/index-DjpxROrT.d.ts +0 -1
  117. package/dist/index-dh0kp2GM.d.ts.map +0 -1
  118. package/dist/llm-client-CxyJ8Oft.js.map +0 -1
  119. package/dist/reward-hacking-DFo2FU5J.js.map +0 -1
  120. package/dist/tool-groups-BuhFx38z.d.ts.map +0 -1
  121. package/dist/types-COAJy8Ku.d.ts.map +0 -1
@@ -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":";;;;;;;;;;;;;;;;;;;;AAsCA,SAAS,gBAAgB,MAAoB;CAC3C,MAAM,cAAc,oBAAoB,EAAE,UAAU,KAAK,CAAC;CAC1D,IAAI,aAAa,MAAM,iBAAiB,MAAM,WAAW;AAC3D;AAEA,SAAS,iBAAiB,MAAc,aAA+C;CACrF,IAAI,YAAY,WAAW,YACzB,uBAAO,IAAI,MACT,oDAAoD,KAAK,6BAC3D;CAEF,uBAAO,IAAI,MACT,oCAAoC,YAAY,OAAO,IAAI,IAAI,KAAK,oDACtE;AACF;;;;;;AAOA,SAAgB,qBAAqB,MAA2C;CAC9E,MAAM,MAAM,KAAK,OAAO,QAAQ;CAChC,IAAI,CAAC,OAAO,cAAc,GAAG,KAAK,OAAO,GACvC,MAAM,IAAI,MAAM,gDAAgD;CAClE,KAAK,MAAM,QAAQ,KAAK,aAAa,CAAC,GAAG,gBAAgB,IAAI;CAC7D,MAAM,cAAc,yBAAyB;EAC3C,UAAU,KAAK;EACf;CACF,CAAC;CACD,IAAI,CAAC,YAAY,UAAU,MAAM,iBAAiB,KAAK,UAAU,WAAW;CAC5E,MAAM,gBAAgB,KAAK,iBAAiB;CAC5C,IAAI,WAAW;CACf,MAAM,gBAAsB;EAC1B,IAAI,UAAU;EACd,WAAW;EACX,IAAI,eAAe,QAAQ,IAAI,QAAQ,OAAO;EAC9C,IAAI;GACF,YAAY,KAAK,QAAQ;EAC3B,QAAQ,CAER;CACF;CACA,IAAI;EACF,KAAK,MAAM,QAAQ,KAAK,aAAa,CAAC,GAAG,gBAAgB,IAAI;CAC/D,SAAS,OAAO;EACd,QAAQ;EACR,MAAM;CACR;CACA,IAAI,eAAe,QAAQ,GAAG,QAAQ,OAAO;CAC7C,OAAO,EAAE,QAAQ;AACnB;;;ACrEA,eAAsB,+BAA0C,MASzB;CACrC,qBAAqB,IAAI;CACzB,MAAM,SAAS,uCAAuC,KAAK,MAAM;CACjE,KAAK,QAAQ,eAAe;CAC5B,IAAI,cAAc;CAClB,IAAI,YAAY;CAChB,IAAI;CACJ,MAAM,oCAAoB,IAAI,IAAqB;CACnD,MAAM,iCAAiB,IAAI,IAAmB;CAC9C,MAAM,0CAA0B,IAAI,IAAY;CAChD,IAAI,sBAAsB;CAC1B,MAAM,SAAS,cAAc,SAAS,aAAa;EACjD,IAAI,CAAC,WAAW;GACd,eAAe,UAAU,KAAK,EAAE,OAAO,yCAAyC,CAAC;GACjF;EACF;EAEA,MAAM,aAAa,IAAI,gBAAgB;EACvC,MAAM,qBAA2B;GAC/B,QAAQ,QAAQ;GAChB,SAAS,QAAQ;EACnB;EACA,kBAAkB,IAAI,UAAU;EAChC,WAAW,OAAO,iBAAiB,SAAS,cAAc,EAAE,MAAM,KAAK,CAAC;EAExE,IAAI;EACJ,UAAU,eACR,SACA,UACA,WAAW,QACX,MACA,cACM;GACJ,MAAM,WAAW,KAAK,mBAAmB,KAAK,iBAAiB,IAAI,cAAc;GACjF,IAAI,aAAa,KAAA,GAAW,OAAO,KAAA;GACnC,IAAI,CAAC,OAAO,cAAc,QAAQ,KAAK,YAAY,GACjD,MAAM,IAAI,MAAM,gEAAgE;GAElF,IAAI,WAAW,KAAK,gBAAgB,OAAO,KAAA;GAC3C,eAAe;GACf,OAAO;EACT,IACC,gBAAgB;GACf,IAAI,CAAC,KAAK,SAAS;GACnB,KAAK,QAAQ;IAAE,GAAG;IAAa,UAAU,EAAE;GAAoB,CAAC;EAClE,GACA,uBACF,CAAC,CAAC,cAAc;GACd,WAAW,OAAO,oBAAoB,SAAS,YAAY;GAC3D,kBAAkB,OAAO,UAAU;GACnC,eAAe,OAAO,OAAO;EAC/B,CAAC;EACD,eAAe,IAAI,OAAO;EAC1B,QAAa,YAAY,KAAA,CAAS;CACpC,CAAC;CACD,MAAM,OAAO,MAAM,YAAY,MAAM;CACrC,MAAM,cAA6B;EACjC,iBAAiB,oBAAoB;EACrC,OAAO;CACT;CACA,MAAM,gBAAsB;EAC1B,MAAW,CAAC,CAAC,YAAY,KAAA,CAAS;CACpC;CACA,KAAK,QAAQ,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;CAC9D,IAAI,KAAK,QAAQ,SAAS,QAAQ;CAClC,OAAO;EACL,KAAK,oBAAoB,KAAK;EAC9B,OAAO,KAAK;EACZ,mBAAmB;EACnB;CACF;CAEA,eAAe,sBAAqC;EAClD,KAAK,QAAQ,oBAAoB,SAAS,OAAO;EACjD,YAAY;EACZ,MAAM,gBAAgB,YAAY,MAAM;EACxC,OAAO,uBAAuB;EAC9B,KAAK,MAAM,cAAc,mBAAmB,WAAW,MAAM;EAC7D,MAAM,CAAC,gBAAgB,MAAM,QAAQ,WAAW,CAC9C,eACA,sBAAsB,cAAc,CACtC,CAAC;EACD,IAAI,kBAAkB,SAAS,KAAK,eAAe,SAAS,GAC1D,MAAM,IAAI,MAAM,6DAA6D;EAE/E,IAAI,cAAc,WAAW,YAAY,MAAM,aAAa;CAC9D;AACF;AAEA,eAAe,eACb,SACA,UACA,QACA,MAOA,QACA,gBACA,SACA,yBACe;CACf,IAAI;EACF,IAAI,QAAQ,WAAW,UAAU,QAAQ,QAAQ,aAAa;GAC5D,eAAe,UAAU,KAAK,EAAE,OAAO,YAAY,CAAC;GACpD;EACF;EACA,IAAI,QAAQ,QAAQ,kBAAkB,UAAU,KAAK,SAAS;GAC5D,eAAe,UAAU,KAAK,EAAE,OAAO,eAAe,CAAC;GACvD;EACF;EACA,IAAI;EACJ,IAAI;GACF,OAAO,MAAM,SAAS,SAAS,OAAO,eAAe;EACvD,QAAQ;GACN,QAAQ;IAAE,MAAM;IAAW,QAAQ;GAAkB,CAAC;GACtD,eAAe,UAAU,KAAK,EAAE,OAAO,0CAA0C,CAAC;GAClF;EACF;EACA,IACE,CAAC,SAAS,IAAI,KACd,CAAC,wBAAwB,KAAK,SAAS,KACvC,OAAO,KAAK,cAAc,UAC1B;GACA,QAAQ;IAAE,MAAM;IAAW,QAAQ;GAAkB,CAAC;GACtD,eAAe,UAAU,KAAK,EAAE,OAAO,+CAA+C,CAAC;GACvF;EACF;EACA,MAAM,YAAY,eAAe,KAAK,SAAS;EAC/C,MAAM,gBAAgB,YAAY;GAAE,MAAM;GAA2B;EAAU,CAAC;EAChF,IAAI,CAAC,wBAAwB,IAAI,aAAa,GAAG;GAC/C,QAAQ;IAAE,MAAM;IAAY;IAAW;GAAc,CAAC;GACtD,wBAAwB,IAAI,aAAa;EAC3C;EACA,MAAM,QAAQ,eAAe;EAC7B,IAAI,UAAU,KAAA,GAAW;GACvB,QAAQ;IACN,MAAM;IACN,QAAQ;IACR;IACA;IACA,WAAW,KAAK;GAClB,CAAC;GACD,eAAe,UAAU,KAAK,EAAE,OAAO,2BAA2B,CAAC;GACnE;EACF;EACA,IAAI;EACJ,IAAI;GACF,SAAS,MAAM,KAAK,SAAS;IAAE;IAAW,WAAW,KAAK;GAAU,GAAG,MAAM;EAC/E,SAAS,OAAO;GACd,QAAQ;IACN,MAAM;IACN,QAAQ;IACR;IACA;IACA,WAAW,KAAK;GAClB,CAAC;GAGD,eAAe,UAAU,KAAK,EAC5B,OAAO,sBAAsB,OAAO,KAAK,CAAC,CAAC,MAAM,GAAG,GAAG,IACzD,CAAC;GACD;EACF;EACA,IAAI;EACJ,IAAI;GACF,UAAU,mBAAmB,MAAM;EACrC,QAAQ;GACN,QAAQ;IACN,MAAM;IACN,QAAQ;IACR;IACA;IACA,WAAW,KAAK;GAClB,CAAC;GACD,eAAe,UAAU,KAAK,EAAE,OAAO,yCAAyC,CAAC;GACjF;EACF;EACA,IAAI,OAAO,WAAW,OAAO,IAAI,OAAO,kBAAkB;GACxD,QAAQ;IACN,MAAM;IACN,QAAQ;IACR;IACA;IACA,WAAW,KAAK;GAClB,CAAC;GACD,eAAe,UAAU,KAAK,EAAE,OAAO,gCAAgC,CAAC;GACxE;EACF;EACA,QAAQ;GACN,MAAM;GACN;GACA;GACA,WAAW,KAAK;GAChB,kBAAkB;GAClB,UAAU,gBAAgB,MAAM;EAClC,CAAC;EACD,sBAAsB,UAAU,KAAK,OAAO;CAC9C,QAAQ;EACN,eAAe,UAAU,KAAK,EAAE,OAAO,oBAAoB,CAAC;CAC9D;AACF;AAEA,eAAe,sBAAsB,gBAAmD;CACtF,OAAO,eAAe,OAAO,GAC3B,MAAM,QAAQ,WAAW,CAAC,GAAG,cAAc,CAAC;AAEhD;AAEA,SAAS,eAAe,UAA0B,QAAgB,MAAqB;CACrF,IAAI,SAAS,aAAa,SAAS,eAAe;CAClD,SAAS,UAAU,QAAQ,IAAI;AACjC;AAEA,SAAS,SAAS,SAA0B,iBAA2C;CACrF,OAAO,IAAI,SAAS,gBAAgB,WAAW;EAC7C,IAAI,OAAO;EACX,MAAM,SAAmB,CAAC;EAC1B,QAAQ,GAAG,SAAS,UAAkB;GACpC,QAAQ,MAAM;GACd,IAAI,OAAO,iBAAiB;IAC1B,uBAAO,IAAI,MAAM,yBAAyB,CAAC;IAC3C,QAAQ,QAAQ;IAChB;GACF;GACA,OAAO,KAAK,KAAK;EACnB,CAAC;EACD,QAAQ,GAAG,SAAS,MAAM;EAC1B,QAAQ,GAAG,aAAa;GACtB,IAAI;IACF,eAAe,KAAK,MAAM,OAAO,OAAO,MAAM,CAAC,CAAC,SAAS,MAAM,CAAC,CAAC;GACnE,SAAS,OAAO;IACd,OAAO,KAAK;GACd;EACF,CAAC;CACH,CAAC;AACH;AAEA,SAAS,mBAAmB,OAAwB;CAClD,MAAM,UAAU,KAAK,UAAU,KAAK;CACpC,IAAI,YAAY,KAAA,GAAW,MAAM,IAAI,MAAM,+CAA+C;CAC1F,OAAO;AACT;AAEA,SAAS,sBAAsB,UAA0B,QAAgB,SAAuB;CAC9F,IAAI,SAAS,aAAa,SAAS,eAAe;CAClD,SAAS,UAAU,QAAQ;EACzB,gBAAgB;EAChB,kBAAkB,OAAO,OAAO,WAAW,OAAO,CAAC;CACrD,CAAC;CACD,SAAS,IAAI,OAAO;AACtB;AAEA,SAAS,qBAAqB,MAQrB;CACP,IAAI,OAAO,KAAK,UAAU,YAAY,CAAC,KAAK,MAAM,KAAK,GACrD,MAAM,IAAI,MAAM,sDAAsD;CAExE,IAAI,CAAC,OAAO,cAAc,KAAK,cAAc,KAAK,KAAK,kBAAkB,GACvE,MAAM,IAAI,MAAM,6EAA6E;CAE/F,IAAI,KAAK,qBAAqB,KAAA,KAAa,OAAO,KAAK,qBAAqB,YAC1E,MAAM,IAAI,MAAM,kEAAkE;CAEpF,IAAI,OAAO,KAAK,aAAa,YAC3B,MAAM,IAAI,MAAM,0DAA0D;CAE5E,IAAI,KAAK,YAAY,KAAA,KAAa,OAAO,KAAK,YAAY,YACxD,MAAM,IAAI,MAAM,yDAAyD;AAE7E;AAEA,SAAS,eAAe,WAAyD;CAC/E,OAAO,OAAO,cAAc,WAAW,YAAY,EAAE,GAAG,UAAU;AACpE"}
1
+ {"version":3,"file":"external-optimizer-process-Bhmzngf-.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,179 @@ 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
417
  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
- });
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 === "assistant") out.push(translateAssistantBlocks(entry.content, stripped));
427
+ else out.push(...translateUserBlocks(entry.content, stripped));
428
+ }
429
+ return out;
430
+ }
431
+ function translateAssistantBlocks(blocks, stripped) {
432
+ const parts = [];
433
+ const toolCalls = [];
434
+ for (const block of blocks) {
435
+ if (!isRecord(block)) throw refuse("assistant message content blocks must be objects");
436
+ if (block.type === "text") {
437
+ if (typeof block.text !== "string") throw refuse("assistant message text blocks require string text");
438
+ parts.push(block.text);
439
+ continue;
440
+ }
441
+ if (block.type === "tool_use") {
442
+ if (typeof block.id !== "string" || !block.id.trim()) throw refuse("assistant tool_use blocks require a string id");
443
+ if (typeof block.name !== "string" || !block.name.trim()) throw refuse("assistant tool_use blocks require a string name");
444
+ if (!isRecord(block.input)) throw refuse("assistant tool_use blocks require an input object");
445
+ toolCalls.push({
446
+ id: block.id,
447
+ name: block.name,
448
+ argumentsJson: JSON.stringify(block.input)
449
+ });
450
+ continue;
451
+ }
452
+ if (block.type === "thinking" || block.type === "redacted_thinking") {
453
+ stripped.add(`messages.${String(block.type)}`);
454
+ continue;
455
+ }
456
+ throw refuse(`assistant message content block type '${String(block.type)}' has no metered translation`);
457
+ }
458
+ return {
459
+ role: "assistant",
460
+ content: parts.join("\n\n"),
461
+ ...toolCalls.length > 0 ? { toolCalls } : {}
462
+ };
463
+ }
464
+ function translateUserBlocks(blocks, stripped) {
465
+ const out = [];
466
+ const textParts = [];
467
+ const flushText = () => {
468
+ if (textParts.length === 0) return;
469
+ out.push({
470
+ role: "user",
471
+ content: textParts.join("\n\n")
472
+ });
473
+ textParts.length = 0;
474
+ };
475
+ for (const block of blocks) {
476
+ if (!isRecord(block)) throw refuse("user message content blocks must be objects");
477
+ if (block.type === "text") {
478
+ if (typeof block.text !== "string") throw refuse("user message text blocks require string text");
479
+ textParts.push(block.text);
480
+ continue;
481
+ }
482
+ if (block.type === "tool_result") {
483
+ if (typeof block.tool_use_id !== "string" || !block.tool_use_id.trim()) throw refuse("user tool_result blocks require a string tool_use_id");
484
+ if (block.is_error === true) stripped.add("messages.tool_result.is_error");
485
+ flushText();
486
+ out.push({
487
+ role: "tool",
488
+ content: toolResultText(block.content),
489
+ toolCallId: block.tool_use_id
490
+ });
491
+ continue;
492
+ }
493
+ throw refuse(`user message content block type '${String(block.type)}' has no metered translation`);
494
+ }
495
+ flushText();
496
+ return out;
497
+ }
498
+ function toolResultText(value) {
499
+ if (value === void 0) return "";
500
+ if (typeof value === "string") return value;
501
+ if (!Array.isArray(value)) throw refuse("user tool_result content must be a string or a block array");
502
+ const parts = [];
503
+ for (const block of value) {
504
+ if (!isRecord(block) || block.type !== "text" || typeof block.text !== "string") throw refuse("user tool_result content blocks must be text blocks");
505
+ parts.push(block.text);
506
+ }
507
+ return parts.join("\n\n");
347
508
  }
348
509
  function joinTextBlocks(blocks, label) {
349
510
  const parts = [];
@@ -354,7 +515,6 @@ function joinTextBlocks(blocks, label) {
354
515
  parts.push(block.text);
355
516
  continue;
356
517
  }
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
518
  throw refuse(`${label} content block type '${String(block.type)}' has no metered translation`);
359
519
  }
360
520
  return parts.join("\n\n");
@@ -362,7 +522,9 @@ function joinTextBlocks(blocks, label) {
362
522
  const STOP_REASON_BY_FINISH_REASON = {
363
523
  stop: "end_turn",
364
524
  length: "max_tokens",
365
- content_filter: "refusal"
525
+ content_filter: "refusal",
526
+ tool_calls: "tool_use",
527
+ tool_use: "tool_use"
366
528
  };
367
529
  /**
368
530
  * Encode one canonical owner response as an Anthropic message.
@@ -370,7 +532,8 @@ const STOP_REASON_BY_FINISH_REASON = {
370
532
  * Usage fields are display-level for the CLI; the ledger receipt reads the
371
533
  * same canonical response through the proxy's strict receipt checks. An
372
534
  * uncaptured usage renders as zeros here while the receipt keeps the honest
373
- * unknown-cost maximum.
535
+ * unknown-cost maximum. Canonical tool calls become `tool_use` content blocks
536
+ * after the text block, matching Anthropic block ordering.
374
537
  */
375
538
  function encodeAnthropicMessage(response, callId) {
376
539
  const finishReason = response.finishReason ?? "stop";
@@ -378,15 +541,23 @@ function encodeAnthropicMessage(response, callId) {
378
541
  if (stopReason === void 0) throw new AnthropicRequestRefusal(502, "api_error", `optimizer model finish reason '${String(finishReason)}' has no Anthropic translation`);
379
542
  const captured = response.usage.captured !== false;
380
543
  const cachedTokens = response.usage.cachedPromptTokens ?? 0;
544
+ const content = [];
545
+ if (response.content.length > 0) content.push({
546
+ type: "text",
547
+ text: response.content
548
+ });
549
+ for (const call of response.toolCalls ?? []) content.push({
550
+ type: "tool_use",
551
+ id: call.id,
552
+ name: call.name,
553
+ input: parseToolInput(call)
554
+ });
381
555
  return {
382
556
  id: `msg_${callId}`,
383
557
  type: "message",
384
558
  role: "assistant",
385
559
  model: response.model,
386
- content: response.content.length > 0 ? [{
387
- type: "text",
388
- text: response.content
389
- }] : [],
560
+ content,
390
561
  stop_reason: stopReason,
391
562
  stop_sequence: null,
392
563
  usage: captured ? {
@@ -400,12 +571,26 @@ function encodeAnthropicMessage(response, callId) {
400
571
  }
401
572
  };
402
573
  }
574
+ function parseToolInput(call) {
575
+ const text = call.argumentsJson.trim();
576
+ if (text === "") return {};
577
+ let parsed;
578
+ try {
579
+ parsed = JSON.parse(text);
580
+ } catch {
581
+ throw new AnthropicRequestRefusal(502, "api_error", `optimizer model tool call '${call.name}' returned arguments that are not valid JSON`);
582
+ }
583
+ if (!isRecord(parsed)) throw new AnthropicRequestRefusal(502, "api_error", `optimizer model tool call '${call.name}' arguments must encode a JSON object`);
584
+ return parsed;
585
+ }
403
586
  /**
404
587
  * Render one completed Anthropic message as the SSE stream the CLI expects.
405
588
  *
406
589
  * The whole stream is synthesized after the owner call completes, so the
407
590
  * 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.
591
+ * rejected with a real status instead of a half-delivered stream. A
592
+ * `tool_use` block streams as an empty-input `content_block_start` plus one
593
+ * `input_json_delta` carrying the complete arguments JSON.
409
594
  */
410
595
  function renderAnthropicSseStream(message) {
411
596
  const content = Array.isArray(message.content) ? message.content : [];
@@ -426,15 +611,47 @@ function renderAnthropicSseStream(message) {
426
611
  }
427
612
  }];
428
613
  for (const [index, block] of content.entries()) {
429
- if (!isRecord(block) || block.type !== "text") continue;
430
- events.push({
614
+ if (!isRecord(block)) continue;
615
+ if (block.type === "text") {
616
+ events.push({
617
+ event: "content_block_start",
618
+ data: {
619
+ type: "content_block_start",
620
+ index,
621
+ content_block: {
622
+ type: "text",
623
+ text: ""
624
+ }
625
+ }
626
+ }, {
627
+ event: "content_block_delta",
628
+ data: {
629
+ type: "content_block_delta",
630
+ index,
631
+ delta: {
632
+ type: "text_delta",
633
+ text: block.text
634
+ }
635
+ }
636
+ }, {
637
+ event: "content_block_stop",
638
+ data: {
639
+ type: "content_block_stop",
640
+ index
641
+ }
642
+ });
643
+ continue;
644
+ }
645
+ if (block.type === "tool_use") events.push({
431
646
  event: "content_block_start",
432
647
  data: {
433
648
  type: "content_block_start",
434
649
  index,
435
650
  content_block: {
436
- type: "text",
437
- text: ""
651
+ type: "tool_use",
652
+ id: block.id,
653
+ name: block.name,
654
+ input: {}
438
655
  }
439
656
  }
440
657
  }, {
@@ -443,8 +660,8 @@ function renderAnthropicSseStream(message) {
443
660
  type: "content_block_delta",
444
661
  index,
445
662
  delta: {
446
- type: "text_delta",
447
- text: block.text
663
+ type: "input_json_delta",
664
+ partial_json: JSON.stringify(block.input)
448
665
  }
449
666
  }
450
667
  }, {
@@ -492,9 +709,6 @@ function anthropicErrorTypeForStatus(status) {
492
709
  function refuse(message) {
493
710
  return new AnthropicRequestRefusal(400, "invalid_request_error", message);
494
711
  }
495
- function refuseValue(message) {
496
- throw refuse(message);
497
- }
498
712
  //#endregion
499
713
  //#region src/campaign/external-optimizer-http.ts
500
714
  function listenLocal(server) {
@@ -710,7 +924,8 @@ async function handleModelProxyRequest(args) {
710
924
  try {
711
925
  const requestTags = wire === "anthropic" ? {
712
926
  ...args.args.tags ?? {},
713
- wire: "anthropic"
927
+ wire: "anthropic",
928
+ ...parsed.strippedFields === void 0 ? {} : { strippedFields: parsed.strippedFields.join(",") }
714
929
  } : args.args.tags;
715
930
  const paid = await args.args.costLedger.runPaidCall({
716
931
  ...args.args.callId ? { callId: args.args.callId } : {},
@@ -928,6 +1143,7 @@ function parseModelProxyRequest(body, path, expectedModel, budget) {
928
1143
  return {
929
1144
  maxOutputTokens: translated.maxOutputTokens,
930
1145
  stream: translated.stream,
1146
+ ...translated.strippedFields.length > 0 ? { strippedFields: translated.strippedFields } : {},
931
1147
  request: freezeJsonSnapshot(translated.request, "optimizer model canonical request")
932
1148
  };
933
1149
  }
@@ -1190,6 +1406,7 @@ function snapshotChatResponse(value, expectedModel, allowWithinFamily) {
1190
1406
  if (!isNonnegativeFiniteNumber(value.durationMs)) throw new OwnerModelContractError("optimizer model callback response durationMs must be finite and non-negative");
1191
1407
  if (value.costUsd !== null && !isNonnegativeFiniteNumber(value.costUsd)) throw new OwnerModelContractError("optimizer model callback response costUsd must be null or finite and non-negative");
1192
1408
  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");
1409
+ const toolCalls = snapshotResponseToolCalls(value.toolCalls);
1193
1410
  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
1411
  if (!isRecord(value.raw)) throw new OwnerModelContractError("optimizer model callback response raw must be an object");
1195
1412
  if (!isRecord(value.usage)) throw new OwnerModelContractError("optimizer model callback response usage must be an object");
@@ -1214,6 +1431,7 @@ function snapshotChatResponse(value, expectedModel, allowWithinFamily) {
1214
1431
  }
1215
1432
  return deepFreezeJson({
1216
1433
  content: value.content,
1434
+ ...toolCalls === void 0 ? {} : { toolCalls },
1217
1435
  usage: {
1218
1436
  promptTokens,
1219
1437
  completionTokens,
@@ -1230,6 +1448,19 @@ function snapshotChatResponse(value, expectedModel, allowWithinFamily) {
1230
1448
  raw
1231
1449
  });
1232
1450
  }
1451
+ /** Snapshot the owner's canonical tool calls onto the allowlisted response. */
1452
+ function snapshotResponseToolCalls(value) {
1453
+ if (value === void 0) return void 0;
1454
+ if (!Array.isArray(value) || value.length === 0) throw new OwnerModelContractError("optimizer model callback response toolCalls must be a non-empty array when present");
1455
+ return value.map((entry) => {
1456
+ 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");
1457
+ return {
1458
+ id: entry.id,
1459
+ name: entry.name,
1460
+ argumentsJson: entry.argumentsJson
1461
+ };
1462
+ });
1463
+ }
1233
1464
  function encodeAnthropicResponseBody(args) {
1234
1465
  const message = encodeAnthropicMessage(args.response, args.callId);
1235
1466
  const body = new TextEncoder().encode(args.stream ? renderAnthropicSseStream(message) : JSON.stringify(message));
@@ -1666,4 +1897,4 @@ function summarizeProcessOutput(capture, max = 4e3) {
1666
1897
  //#endregion
1667
1898
  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
1899
 
1669
- //# sourceMappingURL=external-optimizer-subprocess-BepyNwtj.js.map
1900
+ //# sourceMappingURL=external-optimizer-subprocess-Dn90UqN2.js.map