@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.
- package/CHANGELOG.md +14 -0
- package/dist/analyst/index.d.ts +13 -13
- package/dist/analyst/index.js +4 -4
- package/dist/{backend-integrity-DaeroaEs.d.ts → backend-integrity-DOCa_QrR.d.ts} +2 -2
- package/dist/{backend-integrity-DaeroaEs.d.ts.map → backend-integrity-DOCa_QrR.d.ts.map} +1 -1
- package/dist/{benchmark-A0E-JZ1H.d.ts → benchmark-BtAWA8nT.d.ts} +3 -3
- package/dist/{benchmark-A0E-JZ1H.d.ts.map → benchmark-BtAWA8nT.d.ts.map} +1 -1
- package/dist/{benchmark-command-CqGA0SJQ.js → benchmark-command-CAFwbH0L.js} +7 -7
- package/dist/{benchmark-command-CqGA0SJQ.js.map → benchmark-command-CAFwbH0L.js.map} +1 -1
- package/dist/benchmarks/index.d.ts +4 -4
- package/dist/benchmarks/index.js +3 -3
- package/dist/campaign/index.d.ts +8 -8
- package/dist/campaign/index.js +5 -5
- package/dist/{campaign-CR6Yt5ZS.js → campaign-CN_7xJdV.js} +18 -9
- package/dist/campaign-CN_7xJdV.js.map +1 -0
- package/dist/{capture-fetch-Im0UN1i6.d.ts → capture-fetch-BBVFzhkk.d.ts} +2 -2
- package/dist/{capture-fetch-Im0UN1i6.d.ts.map → capture-fetch-BBVFzhkk.d.ts.map} +1 -1
- package/dist/{chat-client-sFiClniQ.js → chat-client-2bVfrzhN.js} +2 -2
- package/dist/{chat-client-sFiClniQ.js.map → chat-client-2bVfrzhN.js.map} +1 -1
- package/dist/cli.js +2 -2
- package/dist/{client-w6vVJhSp.d.ts → client-kPQYT_56.d.ts} +2 -2
- package/dist/{client-w6vVJhSp.d.ts.map → client-kPQYT_56.d.ts.map} +1 -1
- package/dist/contract/index.d.ts +10 -10
- package/dist/contract/index.js +5 -5
- package/dist/{default-registry-BhRNsP-D.d.ts → default-registry-Cw0Ohdoj.d.ts} +6 -6
- package/dist/{default-registry-BhRNsP-D.d.ts.map → default-registry-Cw0Ohdoj.d.ts.map} +1 -1
- package/dist/{define-agent-eval-Cw3mkB4-.d.ts → define-agent-eval-CEQWL9Hy.d.ts} +5 -5
- package/dist/{define-agent-eval-Cw3mkB4-.d.ts.map → define-agent-eval-CEQWL9Hy.d.ts.map} +1 -1
- package/dist/{define-agent-eval-ChrtErmg.js → define-agent-eval-rqNyVhVV.js} +4 -4
- package/dist/{define-agent-eval-ChrtErmg.js.map → define-agent-eval-rqNyVhVV.js.map} +1 -1
- package/dist/{dspy-rlm-engine-Csi3CUAT.js → dspy-rlm-engine-DHI0WrUU.js} +2 -2
- package/dist/{dspy-rlm-engine-Csi3CUAT.js.map → dspy-rlm-engine-DHI0WrUU.js.map} +1 -1
- package/dist/{engine-CgQdUh-r.d.ts → engine-BLzhNzoY.d.ts} +4 -4
- package/dist/{engine-CgQdUh-r.d.ts.map → engine-BLzhNzoY.d.ts.map} +1 -1
- package/dist/{eval-campaign-Czc2TcW5.js → eval-campaign-C4jmuM-b.js} +2 -2
- package/dist/{eval-campaign-Czc2TcW5.js.map → eval-campaign-C4jmuM-b.js.map} +1 -1
- package/dist/{exact-types-D9_T83Ol.d.ts → exact-types-ccQAyut1.d.ts} +2 -2
- package/dist/{exact-types-D9_T83Ol.d.ts.map → exact-types-ccQAyut1.d.ts.map} +1 -1
- package/dist/experiment/index.d.ts +3 -3
- package/dist/{experiment-tracker-Cp9ougHs.d.ts → experiment-tracker-0MhuPArU.d.ts} +2 -2
- package/dist/{experiment-tracker-Cp9ougHs.d.ts.map → experiment-tracker-0MhuPArU.d.ts.map} +1 -1
- package/dist/{external-optimizer-contracts-mbhR9Ldu.d.ts → external-optimizer-contracts-DbLsm4Po.d.ts} +2 -2
- package/dist/{external-optimizer-contracts-mbhR9Ldu.d.ts.map → external-optimizer-contracts-DbLsm4Po.d.ts.map} +1 -1
- package/dist/{external-optimizer-process-tDe72VRO.js → external-optimizer-process-Bhmzngf-.js} +2 -2
- package/dist/{external-optimizer-process-tDe72VRO.js.map → external-optimizer-process-Bhmzngf-.js.map} +1 -1
- package/dist/{external-optimizer-subprocess-BepyNwtj.js → external-optimizer-subprocess-Dn90UqN2.js} +273 -42
- package/dist/external-optimizer-subprocess-Dn90UqN2.js.map +1 -0
- package/dist/{feedback-trajectory-Cc9M2tNF.d.ts → feedback-trajectory-DpTTjo0q.d.ts} +3 -3
- package/dist/{feedback-trajectory-Cc9M2tNF.d.ts.map → feedback-trajectory-DpTTjo0q.d.ts.map} +1 -1
- package/dist/hosted/index.d.ts +2 -2
- package/dist/index-C1ravkGA.d.ts +1 -0
- package/dist/{index-DL4MJuJL.d.ts → index-CTKpu9ry.d.ts} +7 -7
- package/dist/{index-DL4MJuJL.d.ts.map → index-CTKpu9ry.d.ts.map} +1 -1
- package/dist/{index-dh0kp2GM.d.ts → index-IQccV3Ou.d.ts} +15 -10
- package/dist/index-IQccV3Ou.d.ts.map +1 -0
- package/dist/index.d.ts +19 -19
- package/dist/index.js +8 -8
- package/dist/{integrity-D2MAlC_K.d.ts → integrity-B0dZ96EO.d.ts} +2 -2
- package/dist/{integrity-D2MAlC_K.d.ts.map → integrity-B0dZ96EO.d.ts.map} +1 -1
- package/dist/{llm-client-CxyJ8Oft.js → llm-client-Bg32RW0j.js} +47 -2
- package/dist/llm-client-Bg32RW0j.js.map +1 -0
- package/dist/{llm-judge-D7UuMI2t.js → llm-judge-CVq33oz1.js} +4 -4
- package/dist/{llm-judge-D7UuMI2t.js.map → llm-judge-CVq33oz1.js.map} +1 -1
- package/dist/{matrix-D3hL68uV.d.ts → matrix-DrVnRp4G.d.ts} +2 -2
- package/dist/{matrix-D3hL68uV.d.ts.map → matrix-DrVnRp4G.d.ts.map} +1 -1
- package/dist/meta-eval/index.d.ts +1 -1
- package/dist/multishot/golden/index.d.ts +1 -1
- package/dist/multishot/index.d.ts +2 -2
- package/dist/openapi.json +1 -1
- package/dist/{produced-state-WJs_enbZ.js → produced-state-jfk8Du3b.js} +3 -3
- package/dist/{produced-state-WJs_enbZ.js.map → produced-state-jfk8Du3b.js.map} +1 -1
- package/dist/{promotion-policy-DYRzXdTV.d.ts → promotion-policy-DLOUkYhI.d.ts} +2 -2
- package/dist/{promotion-policy-DYRzXdTV.d.ts.map → promotion-policy-DLOUkYhI.d.ts.map} +1 -1
- package/dist/{provenance-Bh9k3sjl.d.ts → provenance-oA4-zUqm.d.ts} +6 -6
- package/dist/{provenance-Bh9k3sjl.d.ts.map → provenance-oA4-zUqm.d.ts.map} +1 -1
- package/dist/{registry-DhpX9Bsg.d.ts → registry-BQwrSYpC.d.ts} +4 -4
- package/dist/{registry-DhpX9Bsg.d.ts.map → registry-BQwrSYpC.d.ts.map} +1 -1
- package/dist/{researcher-CbCK-ppF.d.ts → researcher-DJnoUE8c.d.ts} +3 -3
- package/dist/{researcher-CbCK-ppF.d.ts.map → researcher-DJnoUE8c.d.ts.map} +1 -1
- package/dist/{reward-hacking-DFo2FU5J.js → reward-hacking-DKI9T52l.js} +19 -1
- package/dist/reward-hacking-DKI9T52l.js.map +1 -0
- package/dist/rl.d.ts +2 -2
- package/dist/rl.js +2 -2
- package/dist/{semantic-concept-judge-B7KPD57W.js → semantic-concept-judge-laMCnTLn.js} +2 -2
- package/dist/{semantic-concept-judge-B7KPD57W.js.map → semantic-concept-judge-laMCnTLn.js.map} +1 -1
- package/dist/{series-convergence-VhTad5TM.d.ts → series-convergence-BxKEgBwA.d.ts} +2 -2
- package/dist/{series-convergence-VhTad5TM.d.ts.map → series-convergence-BxKEgBwA.d.ts.map} +1 -1
- package/dist/{server-DNiUVirV.js → server-dIWwF3j_.js} +2 -2
- package/dist/{server-DNiUVirV.js.map → server-dIWwF3j_.js.map} +1 -1
- package/dist/{skillopt-optimization-method-BtgL6PMH.d.ts → skillopt-optimization-method-BDD_o1xE.d.ts} +5 -5
- package/dist/{skillopt-optimization-method-BtgL6PMH.d.ts.map → skillopt-optimization-method-BDD_o1xE.d.ts.map} +1 -1
- package/dist/{skillopt-optimization-method-L8vi4h4m.js → skillopt-optimization-method-DLeUcK-K.js} +5 -5
- package/dist/{skillopt-optimization-method-L8vi4h4m.js.map → skillopt-optimization-method-DLeUcK-K.js.map} +1 -1
- package/dist/{statistical-heldout-Caxd30ZF.d.ts → statistical-heldout-_woZ9q9j.d.ts} +2 -2
- package/dist/{statistical-heldout-Caxd30ZF.d.ts.map → statistical-heldout-_woZ9q9j.d.ts.map} +1 -1
- package/dist/{store-tool-spans-BGg2QFk1.d.ts → store-tool-spans-Br2_IUhm.d.ts} +4 -4
- package/dist/{store-tool-spans-BGg2QFk1.d.ts.map → store-tool-spans-Br2_IUhm.d.ts.map} +1 -1
- package/dist/{tool-groups-BuhFx38z.d.ts → tool-groups-B4tqh8jB.d.ts} +3 -3
- package/dist/tool-groups-B4tqh8jB.d.ts.map +1 -0
- package/dist/trace-repair/index.d.ts +1 -1
- package/dist/traces.d.ts +7 -7
- package/dist/{types-BNbhD_Vj.d.ts → types-B2NsbrNy.d.ts} +2 -2
- package/dist/{types-BNbhD_Vj.d.ts.map → types-B2NsbrNy.d.ts.map} +1 -1
- package/dist/{types-B61iI2ZG.d.ts → types-CLAwnY-L.d.ts} +2 -2
- package/dist/{types-B61iI2ZG.d.ts.map → types-CLAwnY-L.d.ts.map} +1 -1
- package/dist/{types-COEjZMLY.d.ts → types-DdFNuyxQ.d.ts} +3 -3
- package/dist/{types-COEjZMLY.d.ts.map → types-DdFNuyxQ.d.ts.map} +1 -1
- package/dist/{types-COAJy8Ku.d.ts → types-jUBXJ7Iz.d.ts} +36 -3
- package/dist/types-jUBXJ7Iz.d.ts.map +1 -0
- package/dist/wire/index.d.ts +2 -2
- package/dist/wire/index.js +1 -1
- package/docs/campaign-proposers.md +8 -1
- package/package.json +1 -1
- package/dist/campaign-CR6Yt5ZS.js.map +0 -1
- package/dist/external-optimizer-subprocess-BepyNwtj.js.map +0 -1
- package/dist/index-DjpxROrT.d.ts +0 -1
- package/dist/index-dh0kp2GM.d.ts.map +0 -1
- package/dist/llm-client-CxyJ8Oft.js.map +0 -1
- package/dist/reward-hacking-DFo2FU5J.js.map +0 -1
- package/dist/tool-groups-BuhFx38z.d.ts.map +0 -1
- 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":"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"}
|
|
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":";;;;;;;;;;;;;;;;;;;;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"}
|
package/dist/{external-optimizer-subprocess-BepyNwtj.js → external-optimizer-subprocess-Dn90UqN2.js}
RENAMED
|
@@ -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-
|
|
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.
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
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
|
-
*
|
|
264
|
-
*
|
|
265
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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
|
|
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)
|
|
430
|
-
|
|
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: "
|
|
437
|
-
|
|
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: "
|
|
447
|
-
|
|
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-
|
|
1900
|
+
//# sourceMappingURL=external-optimizer-subprocess-Dn90UqN2.js.map
|