@tangle-network/agent-eval 0.149.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 (131) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +5 -1
  3. package/dist/analyst/index.d.ts +13 -13
  4. package/dist/analyst/index.d.ts.map +1 -1
  5. package/dist/analyst/index.js +4 -4
  6. package/dist/{backend-integrity-Bz8nSrRE.d.ts → backend-integrity-DOCa_QrR.d.ts} +2 -2
  7. package/dist/{backend-integrity-Bz8nSrRE.d.ts.map → backend-integrity-DOCa_QrR.d.ts.map} +1 -1
  8. package/dist/{benchmark-Cn7ZMFNW.d.ts → benchmark-BtAWA8nT.d.ts} +3 -3
  9. package/dist/{benchmark-Cn7ZMFNW.d.ts.map → benchmark-BtAWA8nT.d.ts.map} +1 -1
  10. package/dist/{benchmark-command-gufocQ-N.js → benchmark-command-CAFwbH0L.js} +8 -7
  11. package/dist/benchmark-command-CAFwbH0L.js.map +1 -0
  12. package/dist/benchmarks/index.d.ts +4 -4
  13. package/dist/benchmarks/index.js +3 -3
  14. package/dist/campaign/index.d.ts +9 -9
  15. package/dist/campaign/index.js +6 -6
  16. package/dist/{campaign-BcfXzmPM.js → campaign-CN_7xJdV.js} +90 -8
  17. package/dist/campaign-CN_7xJdV.js.map +1 -0
  18. package/dist/{capture-fetch-BAx3Gntt.d.ts → capture-fetch-BBVFzhkk.d.ts} +2 -2
  19. package/dist/{capture-fetch-BAx3Gntt.d.ts.map → capture-fetch-BBVFzhkk.d.ts.map} +1 -1
  20. package/dist/{chat-client-BX8Wh3fn.js → chat-client-2bVfrzhN.js} +2 -2
  21. package/dist/{chat-client-BX8Wh3fn.js.map → chat-client-2bVfrzhN.js.map} +1 -1
  22. package/dist/cli.js +2 -2
  23. package/dist/{client-BV7icIfy.d.ts → client-kPQYT_56.d.ts} +2 -2
  24. package/dist/{client-BV7icIfy.d.ts.map → client-kPQYT_56.d.ts.map} +1 -1
  25. package/dist/contract/index.d.ts +10 -10
  26. package/dist/contract/index.js +5 -5
  27. package/dist/{default-registry-BUtRMRKk.d.ts → default-registry-Cw0Ohdoj.d.ts} +6 -6
  28. package/dist/{default-registry-BUtRMRKk.d.ts.map → default-registry-Cw0Ohdoj.d.ts.map} +1 -1
  29. package/dist/{define-agent-eval-BWuk2O_N.d.ts → define-agent-eval-CEQWL9Hy.d.ts} +5 -5
  30. package/dist/{define-agent-eval-BWuk2O_N.d.ts.map → define-agent-eval-CEQWL9Hy.d.ts.map} +1 -1
  31. package/dist/{define-agent-eval-CvZQW4u9.js → define-agent-eval-rqNyVhVV.js} +4 -4
  32. package/dist/{define-agent-eval-CvZQW4u9.js.map → define-agent-eval-rqNyVhVV.js.map} +1 -1
  33. package/dist/{dspy-rlm-engine-BmtOl_kP.js → dspy-rlm-engine-DHI0WrUU.js} +2 -2
  34. package/dist/{dspy-rlm-engine-BmtOl_kP.js.map → dspy-rlm-engine-DHI0WrUU.js.map} +1 -1
  35. package/dist/{engine-CIy18RTX.d.ts → engine-BLzhNzoY.d.ts} +4 -4
  36. package/dist/{engine-CIy18RTX.d.ts.map → engine-BLzhNzoY.d.ts.map} +1 -1
  37. package/dist/{eval-campaign-bmZ6NIIP.js → eval-campaign-C4jmuM-b.js} +2 -2
  38. package/dist/{eval-campaign-bmZ6NIIP.js.map → eval-campaign-C4jmuM-b.js.map} +1 -1
  39. package/dist/{exact-types-rdKFzEnK.d.ts → exact-types-ccQAyut1.d.ts} +2 -2
  40. package/dist/{exact-types-rdKFzEnK.d.ts.map → exact-types-ccQAyut1.d.ts.map} +1 -1
  41. package/dist/experiment/index.d.ts +73 -4
  42. package/dist/experiment/index.d.ts.map +1 -1
  43. package/dist/experiment/index.js +175 -1
  44. package/dist/experiment/index.js.map +1 -1
  45. package/dist/{experiment-tracker-D9VHwRm-.d.ts → experiment-tracker-0MhuPArU.d.ts} +2 -2
  46. package/dist/{experiment-tracker-D9VHwRm-.d.ts.map → experiment-tracker-0MhuPArU.d.ts.map} +1 -1
  47. package/dist/{external-optimizer-contracts-CSjDLLmr.d.ts → external-optimizer-contracts-DbLsm4Po.d.ts} +13 -6
  48. package/dist/{external-optimizer-contracts-CSjDLLmr.d.ts.map → external-optimizer-contracts-DbLsm4Po.d.ts.map} +1 -1
  49. package/dist/{external-optimizer-process-C4AyCcQd.js → external-optimizer-process-Bhmzngf-.js} +4 -4
  50. package/dist/{external-optimizer-process-C4AyCcQd.js.map → external-optimizer-process-Bhmzngf-.js.map} +1 -1
  51. package/dist/{external-optimizer-subprocess-DU1KM8yV.js → external-optimizer-subprocess-Dn90UqN2.js} +572 -25
  52. package/dist/external-optimizer-subprocess-Dn90UqN2.js.map +1 -0
  53. package/dist/{feedback-trajectory-niGeR6uJ.d.ts → feedback-trajectory-DpTTjo0q.d.ts} +3 -3
  54. package/dist/{feedback-trajectory-niGeR6uJ.d.ts.map → feedback-trajectory-DpTTjo0q.d.ts.map} +1 -1
  55. package/dist/hosted/index.d.ts +2 -2
  56. package/dist/index-C1ravkGA.d.ts +1 -0
  57. package/dist/{index-aHDnhBC7.d.ts → index-CTKpu9ry.d.ts} +7 -7
  58. package/dist/{index-aHDnhBC7.d.ts.map → index-CTKpu9ry.d.ts.map} +1 -1
  59. package/dist/{index-BCDyf_kT.d.ts → index-IQccV3Ou.d.ts} +47 -11
  60. package/dist/index-IQccV3Ou.d.ts.map +1 -0
  61. package/dist/index.d.ts +20 -20
  62. package/dist/index.js +8 -8
  63. package/dist/{integrity-CGfpTE5-.d.ts → integrity-B0dZ96EO.d.ts} +2 -2
  64. package/dist/{integrity-CGfpTE5-.d.ts.map → integrity-B0dZ96EO.d.ts.map} +1 -1
  65. package/dist/{llm-client-BMuxYoZy.js → llm-client-Bg32RW0j.js} +61 -7
  66. package/dist/llm-client-Bg32RW0j.js.map +1 -0
  67. package/dist/{llm-judge-DWq1Ptco.js → llm-judge-CVq33oz1.js} +5 -4
  68. package/dist/llm-judge-CVq33oz1.js.map +1 -0
  69. package/dist/{matrix-Bc111FKv.d.ts → matrix-DrVnRp4G.d.ts} +2 -2
  70. package/dist/{matrix-Bc111FKv.d.ts.map → matrix-DrVnRp4G.d.ts.map} +1 -1
  71. package/dist/meta-eval/index.d.ts +1 -1
  72. package/dist/multishot/golden/index.d.ts +1 -1
  73. package/dist/multishot/index.d.ts +2 -2
  74. package/dist/openapi.json +1 -1
  75. package/dist/{produced-state-C0oJ4vr-.js → produced-state-jfk8Du3b.js} +3 -3
  76. package/dist/{produced-state-C0oJ4vr-.js.map → produced-state-jfk8Du3b.js.map} +1 -1
  77. package/dist/{promotion-policy-CewdjfCJ.d.ts → promotion-policy-DLOUkYhI.d.ts} +2 -2
  78. package/dist/{promotion-policy-CewdjfCJ.d.ts.map → promotion-policy-DLOUkYhI.d.ts.map} +1 -1
  79. package/dist/{provenance-DA-Pmyfv.d.ts → provenance-oA4-zUqm.d.ts} +17 -6
  80. package/dist/{provenance-DA-Pmyfv.d.ts.map → provenance-oA4-zUqm.d.ts.map} +1 -1
  81. package/dist/{registry-CaqJIDdN.d.ts → registry-BQwrSYpC.d.ts} +4 -4
  82. package/dist/{registry-CaqJIDdN.d.ts.map → registry-BQwrSYpC.d.ts.map} +1 -1
  83. package/dist/{researcher-BySlpla_.d.ts → researcher-DJnoUE8c.d.ts} +3 -3
  84. package/dist/{researcher-BySlpla_.d.ts.map → researcher-DJnoUE8c.d.ts.map} +1 -1
  85. package/dist/{reward-hacking-DFo2FU5J.js → reward-hacking-DKI9T52l.js} +19 -1
  86. package/dist/reward-hacking-DKI9T52l.js.map +1 -0
  87. package/dist/rl.d.ts +2 -2
  88. package/dist/rl.js +2 -2
  89. package/dist/{semantic-concept-judge-BI7Rrl5-.js → semantic-concept-judge-laMCnTLn.js} +2 -2
  90. package/dist/{semantic-concept-judge-BI7Rrl5-.js.map → semantic-concept-judge-laMCnTLn.js.map} +1 -1
  91. package/dist/{series-convergence-D2fsoJ2w.d.ts → series-convergence-BxKEgBwA.d.ts} +2 -2
  92. package/dist/{series-convergence-D2fsoJ2w.d.ts.map → series-convergence-BxKEgBwA.d.ts.map} +1 -1
  93. package/dist/{server-D9wQclzG.js → server-dIWwF3j_.js} +2 -2
  94. package/dist/{server-D9wQclzG.js.map → server-dIWwF3j_.js.map} +1 -1
  95. package/dist/{skillopt-optimization-method-ChCTN4FD.d.ts → skillopt-optimization-method-BDD_o1xE.d.ts} +19 -13
  96. package/dist/{skillopt-optimization-method-ChCTN4FD.d.ts.map → skillopt-optimization-method-BDD_o1xE.d.ts.map} +1 -1
  97. package/dist/{skillopt-optimization-method-Donmq4sq.js → skillopt-optimization-method-DLeUcK-K.js} +92 -14
  98. package/dist/skillopt-optimization-method-DLeUcK-K.js.map +1 -0
  99. package/dist/{statistical-heldout-uxSpFEjm.d.ts → statistical-heldout-_woZ9q9j.d.ts} +2 -2
  100. package/dist/{statistical-heldout-uxSpFEjm.d.ts.map → statistical-heldout-_woZ9q9j.d.ts.map} +1 -1
  101. package/dist/{store-tool-spans-D5FhM0_A.d.ts → store-tool-spans-Br2_IUhm.d.ts} +4 -4
  102. package/dist/{store-tool-spans-D5FhM0_A.d.ts.map → store-tool-spans-Br2_IUhm.d.ts.map} +1 -1
  103. package/dist/{tool-groups-zpufabP8.d.ts → tool-groups-B4tqh8jB.d.ts} +3 -3
  104. package/dist/tool-groups-B4tqh8jB.d.ts.map +1 -0
  105. package/dist/trace-repair/index.d.ts +1 -1
  106. package/dist/traces.d.ts +7 -7
  107. package/dist/{types-BohHewKK.d.ts → types-B2NsbrNy.d.ts} +2 -2
  108. package/dist/{types-BohHewKK.d.ts.map → types-B2NsbrNy.d.ts.map} +1 -1
  109. package/dist/{types-ColZlZtc.d.ts → types-CLAwnY-L.d.ts} +2 -2
  110. package/dist/{types-ColZlZtc.d.ts.map → types-CLAwnY-L.d.ts.map} +1 -1
  111. package/dist/{types-C1Bmeb8X.d.ts → types-DdFNuyxQ.d.ts} +3 -3
  112. package/dist/{types-C1Bmeb8X.d.ts.map → types-DdFNuyxQ.d.ts.map} +1 -1
  113. package/dist/{types-DOhGq4S1.d.ts → types-jUBXJ7Iz.d.ts} +50 -5
  114. package/dist/types-jUBXJ7Iz.d.ts.map +1 -0
  115. package/dist/wire/index.d.ts +2 -2
  116. package/dist/wire/index.js +1 -1
  117. package/docs/campaign-proposers.md +67 -3
  118. package/docs/experiment.md +2 -0
  119. package/docs/multishot-golden-records.md +3 -0
  120. package/package.json +4 -2
  121. package/dist/benchmark-command-gufocQ-N.js.map +0 -1
  122. package/dist/campaign-BcfXzmPM.js.map +0 -1
  123. package/dist/external-optimizer-subprocess-DU1KM8yV.js.map +0 -1
  124. package/dist/index-BCDyf_kT.d.ts.map +0 -1
  125. package/dist/index-CKblBxtr.d.ts +0 -1
  126. package/dist/llm-client-BMuxYoZy.js.map +0 -1
  127. package/dist/llm-judge-DWq1Ptco.js.map +0 -1
  128. package/dist/reward-hacking-DFo2FU5J.js.map +0 -1
  129. package/dist/skillopt-optimization-method-Donmq4sq.js.map +0 -1
  130. package/dist/tool-groups-zpufabP8.d.ts.map +0 -1
  131. package/dist/types-DOhGq4S1.d.ts.map +0 -1
@@ -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 } from "./llm-client-BMuxYoZy.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";
@@ -247,6 +247,469 @@ function isRecord(value) {
247
247
  return typeof value === "object" && value !== null && !Array.isArray(value);
248
248
  }
249
249
  //#endregion
250
+ //#region src/campaign/external-optimizer-anthropic.ts
251
+ /**
252
+ * Translate between the Anthropic Messages API and the canonical owner-call
253
+ * contract used by the metered optimizer model proxy.
254
+ *
255
+ * The `claude` CLI driven by GEPA agent engines speaks `POST /v1/messages`.
256
+ * Each request becomes one canonical `ExternalOptimizerChatRequest`, so the
257
+ * shared reservation, receipt, and execution-evidence pipeline applies
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
+ *
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.
274
+ */
275
+ var AnthropicRequestRefusal = class extends Error {
276
+ status;
277
+ errorType;
278
+ constructor(status, errorType, message) {
279
+ super(message);
280
+ this.status = status;
281
+ this.errorType = errorType;
282
+ }
283
+ };
284
+ const REQUEST_FIELDS = /* @__PURE__ */ new Set([
285
+ "model",
286
+ "max_tokens",
287
+ "messages",
288
+ "system",
289
+ "temperature",
290
+ "stream",
291
+ "metadata",
292
+ "tools",
293
+ "tool_choice"
294
+ ]);
295
+ const STRIPPED_REQUEST_FIELDS = /* @__PURE__ */ new Set([
296
+ "thinking",
297
+ "context_management",
298
+ "output_config"
299
+ ]);
300
+ const REFUSED_REQUEST_FIELDS = {
301
+ top_p: "top_p has no canonical execution-owner slot",
302
+ top_k: "top_k has no canonical execution-owner slot",
303
+ stop_sequences: "stop_sequences has no canonical execution-owner slot"
304
+ };
305
+ function translateAnthropicMessagesRequest(raw, expectedModel, maxOutputTokensPerRequest) {
306
+ let parsed;
307
+ try {
308
+ parsed = JSON.parse(Buffer.from(raw).toString("utf8"));
309
+ } catch {
310
+ throw refuse("request must be valid JSON");
311
+ }
312
+ if (!isRecord(parsed)) throw refuse("request must be a JSON object");
313
+ const stripped = /* @__PURE__ */ new Set();
314
+ for (const key of Object.keys(parsed)) {
315
+ if (REQUEST_FIELDS.has(key)) continue;
316
+ if (STRIPPED_REQUEST_FIELDS.has(key)) {
317
+ stripped.add(key);
318
+ continue;
319
+ }
320
+ const reason = REFUSED_REQUEST_FIELDS[key];
321
+ throw refuse(reason ?? `request field '${key}' has no metered translation and is not supported`);
322
+ }
323
+ if (parsed.model !== expectedModel) throw refuse(`request must use configured model '${expectedModel}'`);
324
+ const maxTokens = parsed.max_tokens;
325
+ if (!Number.isSafeInteger(maxTokens) || maxTokens <= 0) throw refuse("request requires a positive integer max_tokens");
326
+ if (maxTokens > maxOutputTokensPerRequest) throw refuse("request max_tokens exceeds maxOutputTokensPerRequest");
327
+ if (parsed.stream !== void 0 && typeof parsed.stream !== "boolean") throw refuse("request stream must be a boolean");
328
+ if (parsed.temperature !== void 0 && (typeof parsed.temperature !== "number" || !Number.isFinite(parsed.temperature))) throw refuse("request temperature must be a finite number");
329
+ const messages = [];
330
+ const systemText = translateSystem(parsed.system);
331
+ if (systemText !== void 0) messages.push({
332
+ role: "system",
333
+ content: systemText
334
+ });
335
+ messages.push(...translateConversation(parsed.messages, stripped));
336
+ const tools = translateTools(parsed.tools, stripped);
337
+ const toolChoice = translateToolChoice(parsed.tool_choice, stripped);
338
+ return {
339
+ request: {
340
+ model: expectedModel,
341
+ messages,
342
+ maxTokens,
343
+ ...parsed.temperature !== void 0 ? { temperature: parsed.temperature } : {},
344
+ ...tools === void 0 ? {} : { tools },
345
+ ...toolChoice === void 0 ? {} : { toolChoice }
346
+ },
347
+ maxOutputTokens: maxTokens,
348
+ stream: parsed.stream === true,
349
+ strippedFields: [...stripped].sort()
350
+ };
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
+ }
406
+ function translateSystem(value) {
407
+ if (value === void 0) return void 0;
408
+ if (typeof value === "string") return value;
409
+ if (!Array.isArray(value)) throw refuse("system must be a string or an array of text blocks");
410
+ return joinTextBlocks(value, "system");
411
+ }
412
+ function translateConversation(value, stripped) {
413
+ if (!Array.isArray(value) || value.length === 0) throw refuse("request requires a non-empty messages array");
414
+ const out = [];
415
+ for (const entry of value) {
416
+ if (!isRecord(entry)) throw refuse("each message must be an object");
417
+ if (entry.role !== "user" && entry.role !== "assistant") throw refuse(`message role must be 'user' or 'assistant'`);
418
+ if (typeof entry.content === "string") {
419
+ out.push({
420
+ role: entry.role,
421
+ content: entry.content
422
+ });
423
+ continue;
424
+ }
425
+ if (!Array.isArray(entry.content) || entry.content.length === 0) throw refuse(`${entry.role} message content must be a string or a non-empty block array`);
426
+ if (entry.role === "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");
508
+ }
509
+ function joinTextBlocks(blocks, label) {
510
+ const parts = [];
511
+ for (const block of blocks) {
512
+ if (!isRecord(block)) throw refuse(`${label} content blocks must be objects`);
513
+ if (block.type === "text") {
514
+ if (typeof block.text !== "string") throw refuse(`${label} text blocks require string text`);
515
+ parts.push(block.text);
516
+ continue;
517
+ }
518
+ throw refuse(`${label} content block type '${String(block.type)}' has no metered translation`);
519
+ }
520
+ return parts.join("\n\n");
521
+ }
522
+ const STOP_REASON_BY_FINISH_REASON = {
523
+ stop: "end_turn",
524
+ length: "max_tokens",
525
+ content_filter: "refusal",
526
+ tool_calls: "tool_use",
527
+ tool_use: "tool_use"
528
+ };
529
+ /**
530
+ * Encode one canonical owner response as an Anthropic message.
531
+ *
532
+ * Usage fields are display-level for the CLI; the ledger receipt reads the
533
+ * same canonical response through the proxy's strict receipt checks. An
534
+ * uncaptured usage renders as zeros here while the receipt keeps the honest
535
+ * unknown-cost maximum. Canonical tool calls become `tool_use` content blocks
536
+ * after the text block, matching Anthropic block ordering.
537
+ */
538
+ function encodeAnthropicMessage(response, callId) {
539
+ const finishReason = response.finishReason ?? "stop";
540
+ const stopReason = finishReason === null ? "end_turn" : STOP_REASON_BY_FINISH_REASON[finishReason];
541
+ if (stopReason === void 0) throw new AnthropicRequestRefusal(502, "api_error", `optimizer model finish reason '${String(finishReason)}' has no Anthropic translation`);
542
+ const captured = response.usage.captured !== false;
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
+ });
555
+ return {
556
+ id: `msg_${callId}`,
557
+ type: "message",
558
+ role: "assistant",
559
+ model: response.model,
560
+ content,
561
+ stop_reason: stopReason,
562
+ stop_sequence: null,
563
+ usage: captured ? {
564
+ input_tokens: response.usage.promptTokens - cachedTokens,
565
+ cache_read_input_tokens: cachedTokens,
566
+ cache_creation_input_tokens: 0,
567
+ output_tokens: response.usage.completionTokens
568
+ } : {
569
+ input_tokens: 0,
570
+ output_tokens: 0
571
+ }
572
+ };
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
+ }
586
+ /**
587
+ * Render one completed Anthropic message as the SSE stream the CLI expects.
588
+ *
589
+ * The whole stream is synthesized after the owner call completes, so the
590
+ * event burst is exact: usage is final and a cap-violating response is
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.
594
+ */
595
+ function renderAnthropicSseStream(message) {
596
+ const content = Array.isArray(message.content) ? message.content : [];
597
+ const usage = isRecord(message.usage) ? message.usage : {
598
+ input_tokens: 0,
599
+ output_tokens: 0
600
+ };
601
+ const events = [{
602
+ event: "message_start",
603
+ data: {
604
+ type: "message_start",
605
+ message: {
606
+ ...message,
607
+ content: [],
608
+ stop_reason: null,
609
+ stop_sequence: null
610
+ }
611
+ }
612
+ }];
613
+ for (const [index, block] of content.entries()) {
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({
646
+ event: "content_block_start",
647
+ data: {
648
+ type: "content_block_start",
649
+ index,
650
+ content_block: {
651
+ type: "tool_use",
652
+ id: block.id,
653
+ name: block.name,
654
+ input: {}
655
+ }
656
+ }
657
+ }, {
658
+ event: "content_block_delta",
659
+ data: {
660
+ type: "content_block_delta",
661
+ index,
662
+ delta: {
663
+ type: "input_json_delta",
664
+ partial_json: JSON.stringify(block.input)
665
+ }
666
+ }
667
+ }, {
668
+ event: "content_block_stop",
669
+ data: {
670
+ type: "content_block_stop",
671
+ index
672
+ }
673
+ });
674
+ }
675
+ events.push({
676
+ event: "message_delta",
677
+ data: {
678
+ type: "message_delta",
679
+ delta: {
680
+ stop_reason: message.stop_reason,
681
+ stop_sequence: null
682
+ },
683
+ usage: { output_tokens: typeof usage.output_tokens === "number" ? usage.output_tokens : 0 }
684
+ }
685
+ }, {
686
+ event: "message_stop",
687
+ data: { type: "message_stop" }
688
+ });
689
+ return events.map((entry) => `event: ${entry.event}\ndata: ${JSON.stringify(entry.data)}\n\n`).join("");
690
+ }
691
+ function anthropicErrorEnvelope(errorType, message) {
692
+ return {
693
+ type: "error",
694
+ error: {
695
+ type: errorType,
696
+ message
697
+ }
698
+ };
699
+ }
700
+ function anthropicErrorTypeForStatus(status) {
701
+ if (status === 401) return "authentication_error";
702
+ if (status === 403) return "permission_error";
703
+ if (status === 404) return "not_found_error";
704
+ if (status === 413) return "request_too_large";
705
+ if (status === 429) return "rate_limit_error";
706
+ if (status >= 500) return "api_error";
707
+ return "invalid_request_error";
708
+ }
709
+ function refuse(message) {
710
+ return new AnthropicRequestRefusal(400, "invalid_request_error", message);
711
+ }
712
+ //#endregion
250
713
  //#region src/campaign/external-optimizer-http.ts
251
714
  function listenLocal(server) {
252
715
  return new Promise((resolvePromise, reject) => {
@@ -289,6 +752,16 @@ async function startExternalOptimizerModelProxy(args) {
289
752
  const token = randomLocalToken();
290
753
  let requestCount = 0;
291
754
  let successfulCompletionCount = 0;
755
+ const wireCounts = {
756
+ openai: {
757
+ requestAttempts: 0,
758
+ successfulCompletions: 0
759
+ },
760
+ anthropic: {
761
+ requestAttempts: 0,
762
+ successfulCompletions: 0
763
+ }
764
+ };
292
765
  let modelCallInvocations = 0;
293
766
  let executionRecordCount = 0;
294
767
  let executionSequence = 0;
@@ -319,7 +792,7 @@ async function startExternalOptimizerModelProxy(args) {
319
792
  controller,
320
793
  token,
321
794
  args,
322
- nextReservation: (maximumCostUsd) => {
795
+ nextReservation: (maximumCostUsd, wire) => {
323
796
  if (totalRequestCount >= args.budget.maxRequests) return {
324
797
  accepted: false,
325
798
  reason: "optimizer model request limit reached"
@@ -329,6 +802,7 @@ async function startExternalOptimizerModelProxy(args) {
329
802
  reason: "optimizer model cost limit reached"
330
803
  };
331
804
  requestCount += 1;
805
+ wireCounts[wire].requestAttempts += 1;
332
806
  totalRequestCount += 1;
333
807
  reservedForBudget += maximumCostUsd ?? 0;
334
808
  return { accepted: true };
@@ -337,8 +811,9 @@ async function startExternalOptimizerModelProxy(args) {
337
811
  reservedForBudget = Math.max(0, reservedForBudget - (maximumCostUsd ?? 0));
338
812
  committedForBudget += chargedCostUsd ?? 0;
339
813
  },
340
- recordSuccessfulCompletion: () => {
814
+ recordSuccessfulCompletion: (wire) => {
341
815
  successfulCompletionCount += 1;
816
+ wireCounts[wire].successfulCompletions += 1;
342
817
  },
343
818
  recordExecutionReceipt: (observation) => {
344
819
  try {
@@ -385,6 +860,10 @@ async function startExternalOptimizerModelProxy(args) {
385
860
  apiKey: token,
386
861
  requestAttempts: () => requestCount,
387
862
  successfulCompletions: () => successfulCompletionCount,
863
+ wireUsage: () => ({
864
+ openai: { ...wireCounts.openai },
865
+ anthropic: { ...wireCounts.anthropic }
866
+ }),
388
867
  assertExecutionComplete,
389
868
  failures: () => [...failures],
390
869
  close
@@ -409,35 +888,51 @@ async function startExternalOptimizerModelProxy(args) {
409
888
  }
410
889
  async function handleModelProxyRequest(args) {
411
890
  const { controller, request, response } = args;
891
+ let wire = "openai";
892
+ const respondError = (status, message, errorType) => {
893
+ if (wire === "anthropic") {
894
+ const mapped = status === 429 ? 402 : status;
895
+ sendJsonIfOpen(response, mapped, anthropicErrorEnvelope(errorType ?? anthropicErrorTypeForStatus(mapped), message));
896
+ return;
897
+ }
898
+ sendJsonIfOpen(response, status, { error: message });
899
+ };
412
900
  try {
413
901
  const path = request.url ? new URL(request.url, "http://127.0.0.1").pathname : "";
414
- if (request.method !== "POST" || !MODEL_PROXY_PATHS.has(path)) {
902
+ const anthropicPath = path === "/v1/messages" && args.args.anthropicEndpoint === true;
903
+ if (request.method !== "POST" || !(MODEL_PROXY_PATHS.has(path) || anthropicPath)) {
415
904
  sendJsonIfOpen(response, 404, { error: "not found" });
416
905
  return;
417
906
  }
907
+ if (anthropicPath) wire = "anthropic";
418
908
  const modelPath = path;
419
- if (request.headers.authorization !== `Bearer ${args.token}`) {
420
- sendJsonIfOpen(response, 401, { error: "unauthorized" });
909
+ if (!(request.headers.authorization === `Bearer ${args.token}` || wire === "anthropic" && request.headers["x-api-key"] === args.token)) {
910
+ respondError(401, "unauthorized");
421
911
  return;
422
912
  }
423
913
  const body = await readBody(request, args.args.budget.maxRequestBytes);
424
914
  const parsed = parseModelProxyRequest(body, modelPath, args.args.model, args.args.budget);
425
915
  const maximumUsage = conservativeMaximumUsage(body.byteLength, parsed.maxOutputTokens + (args.args.budget.maxReasoningTokensPerRequest ?? 0), args.args.budget.pricing);
426
916
  const maximumCostUsd = args.args.budget.pricing ? costForTokenPricing(args.args.budget.pricing, maximumUsage) : void 0;
427
- const reservation = args.nextReservation(maximumCostUsd);
917
+ const reservation = args.nextReservation(maximumCostUsd, wire);
428
918
  if (!reservation.accepted) {
429
- sendJsonIfOpen(response, 429, { error: reservation.reason });
919
+ respondError(429, reservation.reason);
430
920
  return;
431
921
  }
432
922
  const timeout = setTimeout(() => controller.abort(), args.args.budget.requestTimeoutMs ?? 3e5);
433
923
  let chargedForBudget = maximumCostUsd;
434
924
  try {
925
+ const requestTags = wire === "anthropic" ? {
926
+ ...args.args.tags ?? {},
927
+ wire: "anthropic",
928
+ ...parsed.strippedFields === void 0 ? {} : { strippedFields: parsed.strippedFields.join(",") }
929
+ } : args.args.tags;
435
930
  const paid = await args.args.costLedger.runPaidCall({
436
931
  ...args.args.callId ? { callId: args.args.callId } : {},
437
932
  channel: args.args.channel ?? "optimizer",
438
933
  phase: args.args.phase,
439
934
  actor: args.args.actor,
440
- ...args.args.tags ? { tags: args.args.tags } : {},
935
+ ...requestTags ? { tags: requestTags } : {},
441
936
  model: args.args.model,
442
937
  ...args.args.budget.pricing ? { maximumCharge: {
443
938
  customTokenPricing: args.args.budget.pricing,
@@ -452,7 +947,9 @@ async function handleModelProxyRequest(args) {
452
947
  recordModelCallInvocation: args.recordModelCallInvocation,
453
948
  path: modelPath,
454
949
  request: parsed.request,
950
+ ...parsed.stream === void 0 ? {} : { anthropicStream: parsed.stream },
455
951
  model: args.args.model,
952
+ allowWithinFamily: args.args.servedModelPolicy === "allow-within-family",
456
953
  maxOutputTokens: parsed.maxOutputTokens,
457
954
  ...args.args.budget.maxReasoningTokensPerRequest === void 0 ? {} : { maxReasoningTokens: args.args.budget.maxReasoningTokensPerRequest },
458
955
  maxResponseBytes: args.args.budget.maxResponseBytes,
@@ -470,19 +967,19 @@ async function handleModelProxyRequest(args) {
470
967
  if (!paid.succeeded) {
471
968
  args.recordFailure(paid.error);
472
969
  chargedForBudget = paid.receipt ? paid.receipt.usageUnknown || paid.receipt.costUnknown ? maximumCostUsd : paid.receipt.costUsd : void 0;
473
- sendJsonIfOpen(response, isAbortError(paid.error) ? 504 : paid.error instanceof ProviderResponseTooLargeError || paid.error instanceof MissingModelExecutionError || paid.error instanceof ModelExecutionPersistenceError || paid.error instanceof OwnerModelContractError || paid.error instanceof ModelSubstitutionError ? 502 : 429, { error: paid.error.message });
970
+ respondError(isAbortError(paid.error) ? 504 : paid.error instanceof AnthropicRequestRefusal ? paid.error.status : paid.error instanceof ProviderResponseTooLargeError || paid.error instanceof MissingModelExecutionError || paid.error instanceof ModelExecutionPersistenceError || paid.error instanceof OwnerModelContractError || paid.error instanceof ModelSubstitutionError ? 502 : 429, paid.error.message, paid.error instanceof AnthropicRequestRefusal ? paid.error.errorType : void 0);
474
971
  return;
475
972
  }
476
973
  if (paid.value.modelCallFailed) {
477
- sendJsonIfOpen(response, 502, { error: paid.value.modelCallFailed });
974
+ respondError(502, paid.value.modelCallFailed);
478
975
  return;
479
976
  }
480
977
  chargedForBudget = paid.value.usageComplete ? paid.receipt.costUsd : maximumCostUsd;
481
978
  if (paid.value.usageRejected) {
482
- sendJsonIfOpen(response, 502, { error: paid.value.usageRejected });
979
+ respondError(502, paid.value.usageRejected);
483
980
  return;
484
981
  }
485
- if (paid.value.status >= 200 && paid.value.status < 300) args.recordSuccessfulCompletion();
982
+ if (paid.value.status >= 200 && paid.value.status < 300) args.recordSuccessfulCompletion(wire);
486
983
  if (response.destroyed || response.writableEnded) return;
487
984
  response.writeHead(paid.value.status, {
488
985
  "content-type": paid.value.contentType,
@@ -494,7 +991,7 @@ async function handleModelProxyRequest(args) {
494
991
  args.settleReservation(maximumCostUsd, chargedForBudget);
495
992
  }
496
993
  } catch (error) {
497
- sendJsonIfOpen(response, error instanceof RequestBodyTooLargeError ? 413 : error instanceof Error && isAbortError(error) ? 503 : 400, { error: toErrorMessage(error) });
994
+ respondError(error instanceof AnthropicRequestRefusal ? error.status : error instanceof RequestBodyTooLargeError ? 413 : error instanceof Error && isAbortError(error) ? 503 : 400, toErrorMessage(error), error instanceof AnthropicRequestRefusal ? error.errorType : void 0);
498
995
  }
499
996
  }
500
997
  async function waitForActiveHandlers(activeHandlers) {
@@ -528,8 +1025,8 @@ async function forwardModelProxyRequest(args) {
528
1025
  let authoritativeReceipt;
529
1026
  let canonicalResponse;
530
1027
  try {
531
- authoritativeReceipt = snapshotModelReceipt(called.receipt, args.model);
532
- canonicalResponse = snapshotChatResponse(called.response, args.model);
1028
+ authoritativeReceipt = snapshotModelReceipt(called.receipt, args.model, args.allowWithinFamily);
1029
+ canonicalResponse = snapshotChatResponse(called.response, args.model, args.allowWithinFamily);
533
1030
  assertResponseUsageMatchesReceipt(canonicalResponse, authoritativeReceipt);
534
1031
  } catch (error) {
535
1032
  args.recordExecutionReceipt({
@@ -556,8 +1053,12 @@ async function forwardModelProxyRequest(args) {
556
1053
  const reasoningTokens = canonicalResponse.usage.reasoningTokens ?? 0;
557
1054
  const completionTokens = canonicalResponse.usage.completionTokens - reasoningTokens;
558
1055
  const usageRejected = usageKnown && completionTokens > args.maxOutputTokens ? `optimizer model execution reported ${completionTokens} completion tokens, exceeding requested limit ${args.maxOutputTokens}` : usageKnown && args.maxReasoningTokens !== void 0 && reasoningTokens > args.maxReasoningTokens ? `optimizer model execution reported ${reasoningTokens} reasoning tokens, exceeding the declared budget ${args.maxReasoningTokens}` : void 0;
559
- return {
560
- status: 200,
1056
+ const encoded = args.path === "/v1/messages" ? encodeAnthropicResponseBody({
1057
+ callId: args.callId,
1058
+ response: canonicalResponse,
1059
+ stream: args.anthropicStream === true,
1060
+ maxBytes: args.maxResponseBytes
1061
+ }) : {
561
1062
  contentType: "application/json",
562
1063
  body: encodeCanonicalModelResponse({
563
1064
  path: args.path,
@@ -565,7 +1066,12 @@ async function forwardModelProxyRequest(args) {
565
1066
  response: canonicalResponse,
566
1067
  receipt: authoritativeReceipt,
567
1068
  maxBytes: args.maxResponseBytes
568
- }),
1069
+ })
1070
+ };
1071
+ return {
1072
+ status: 200,
1073
+ contentType: encoded.contentType,
1074
+ body: encoded.body,
569
1075
  receipt: authoritativeReceipt,
570
1076
  usageComplete: authoritativeReceipt.usageUnknown !== true,
571
1077
  ...usageRejected ? { usageRejected } : {}
@@ -581,7 +1087,7 @@ async function forwardModelProxyRequest(args) {
581
1087
  error: called.error,
582
1088
  execution
583
1089
  });
584
- const failedReceipt = snapshotModelReceipt(called.receipt, args.model);
1090
+ const failedReceipt = snapshotModelReceipt(called.receipt, args.model, args.allowWithinFamily);
585
1091
  return {
586
1092
  status: 502,
587
1093
  contentType: "application/json",
@@ -592,7 +1098,7 @@ async function forwardModelProxyRequest(args) {
592
1098
  };
593
1099
  }
594
1100
  }
595
- function snapshotModelReceipt(value, expectedModel) {
1101
+ function snapshotModelReceipt(value, expectedModel, allowWithinFamily) {
596
1102
  let snapshot;
597
1103
  try {
598
1104
  assertJsonValue(value, "optimizer model callback receipt");
@@ -600,7 +1106,10 @@ function snapshotModelReceipt(value, expectedModel) {
600
1106
  } catch (error) {
601
1107
  throw new OwnerModelContractError(`optimizer model callback returned an invalid receipt: ${toErrorMessage(error)}`);
602
1108
  }
603
- assertServedModel(expectedModel, snapshot.model, { context: "optimizer model callback receipt" });
1109
+ assertServedModel(expectedModel, snapshot.model, {
1110
+ context: "optimizer model callback receipt",
1111
+ allowWithinFamily
1112
+ });
604
1113
  return snapshot;
605
1114
  }
606
1115
  function freezeJsonSnapshot(value, label) {
@@ -629,6 +1138,15 @@ function assertResponseUsageMatchesReceipt(response, receipt) {
629
1138
  if (response.costUsd !== receiptCostUsd) throw new OwnerModelContractError("optimizer model response billed cost disagrees with the execution receipt");
630
1139
  }
631
1140
  function parseModelProxyRequest(body, path, expectedModel, budget) {
1141
+ if (path === "/v1/messages") {
1142
+ const translated = translateAnthropicMessagesRequest(body, expectedModel, budget.maxOutputTokensPerRequest);
1143
+ return {
1144
+ maxOutputTokens: translated.maxOutputTokens,
1145
+ stream: translated.stream,
1146
+ ...translated.strippedFields.length > 0 ? { strippedFields: translated.strippedFields } : {},
1147
+ request: freezeJsonSnapshot(translated.request, "optimizer model canonical request")
1148
+ };
1149
+ }
632
1150
  let value;
633
1151
  try {
634
1152
  value = JSON.parse(Buffer.from(body).toString("utf8"));
@@ -878,13 +1396,17 @@ function assertAllowedKeys(value, allowed, label = "optimizer model request") {
878
1396
  const unknown = Object.keys(value).filter((key) => !allowedSet.has(key));
879
1397
  if (unknown.length > 0) throw new Error(`${label} contains unsupported fields: ${unknown.join(", ")}`);
880
1398
  }
881
- function snapshotChatResponse(value, expectedModel) {
1399
+ function snapshotChatResponse(value, expectedModel, allowWithinFamily) {
882
1400
  if (!isRecord(value)) throw new OwnerModelContractError("optimizer model callback response must be an object");
883
1401
  if (typeof value.content !== "string") throw new OwnerModelContractError("optimizer model callback response content must be a string");
884
- assertServedModel(expectedModel, typeof value.model === "string" ? value.model : null, { context: "optimizer model callback response" });
1402
+ assertServedModel(expectedModel, typeof value.model === "string" ? value.model : null, {
1403
+ context: "optimizer model callback response",
1404
+ allowWithinFamily
1405
+ });
885
1406
  if (!isNonnegativeFiniteNumber(value.durationMs)) throw new OwnerModelContractError("optimizer model callback response durationMs must be finite and non-negative");
886
1407
  if (value.costUsd !== null && !isNonnegativeFiniteNumber(value.costUsd)) throw new OwnerModelContractError("optimizer model callback response costUsd must be null or finite and non-negative");
887
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);
888
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");
889
1411
  if (!isRecord(value.raw)) throw new OwnerModelContractError("optimizer model callback response raw must be an object");
890
1412
  if (!isRecord(value.usage)) throw new OwnerModelContractError("optimizer model callback response usage must be an object");
@@ -909,6 +1431,7 @@ function snapshotChatResponse(value, expectedModel) {
909
1431
  }
910
1432
  return deepFreezeJson({
911
1433
  content: value.content,
1434
+ ...toolCalls === void 0 ? {} : { toolCalls },
912
1435
  usage: {
913
1436
  promptTokens,
914
1437
  completionTokens,
@@ -925,6 +1448,28 @@ function snapshotChatResponse(value, expectedModel) {
925
1448
  raw
926
1449
  });
927
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
+ }
1464
+ function encodeAnthropicResponseBody(args) {
1465
+ const message = encodeAnthropicMessage(args.response, args.callId);
1466
+ const body = new TextEncoder().encode(args.stream ? renderAnthropicSseStream(message) : JSON.stringify(message));
1467
+ if (body.byteLength > args.maxBytes) throw new ProviderResponseTooLargeError();
1468
+ return {
1469
+ contentType: args.stream ? "text/event-stream; charset=utf-8" : "application/json",
1470
+ body
1471
+ };
1472
+ }
928
1473
  function encodeCanonicalModelResponse(args) {
929
1474
  const usage = encodeProtocolUsage(args.path, args.response, args.receipt);
930
1475
  const finishReason = args.response.finishReason === void 0 ? "stop" : args.response.finishReason;
@@ -969,6 +1514,7 @@ function encodeCanonicalModelResponse(args) {
969
1514
  return encoded;
970
1515
  }
971
1516
  function endpointFormat(path) {
1517
+ if (path === "/v1/messages") return "anthropic-messages";
972
1518
  return path === "/v1/responses" ? "responses" : "chat-completions";
973
1519
  }
974
1520
  function encodeProtocolUsage(path, response, receipt) {
@@ -1015,6 +1561,7 @@ function conservativeMaximumUsage(inputTokenUpperBound, outputTokenUpperBound, p
1015
1561
  };
1016
1562
  }
1017
1563
  function assertModelProxyConfig(args) {
1564
+ assertServedModelPolicy(args.servedModelPolicy, "external optimizer model proxy: servedModelPolicy");
1018
1565
  for (const [label, value] of [
1019
1566
  ["model", args.model],
1020
1567
  ["phase", args.phase],
@@ -1350,4 +1897,4 @@ function summarizeProcessOutput(capture, max = 4e3) {
1350
1897
  //#endregion
1351
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 };
1352
1899
 
1353
- //# sourceMappingURL=external-optimizer-subprocess-DU1KM8yV.js.map
1900
+ //# sourceMappingURL=external-optimizer-subprocess-Dn90UqN2.js.map