@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.
- package/CHANGELOG.md +42 -0
- package/README.md +5 -1
- package/dist/analyst/index.d.ts +13 -13
- package/dist/analyst/index.d.ts.map +1 -1
- package/dist/analyst/index.js +4 -4
- package/dist/{backend-integrity-Bz8nSrRE.d.ts → backend-integrity-DOCa_QrR.d.ts} +2 -2
- package/dist/{backend-integrity-Bz8nSrRE.d.ts.map → backend-integrity-DOCa_QrR.d.ts.map} +1 -1
- package/dist/{benchmark-Cn7ZMFNW.d.ts → benchmark-BtAWA8nT.d.ts} +3 -3
- package/dist/{benchmark-Cn7ZMFNW.d.ts.map → benchmark-BtAWA8nT.d.ts.map} +1 -1
- package/dist/{benchmark-command-gufocQ-N.js → benchmark-command-CAFwbH0L.js} +8 -7
- package/dist/benchmark-command-CAFwbH0L.js.map +1 -0
- package/dist/benchmarks/index.d.ts +4 -4
- package/dist/benchmarks/index.js +3 -3
- package/dist/campaign/index.d.ts +9 -9
- package/dist/campaign/index.js +6 -6
- package/dist/{campaign-BcfXzmPM.js → campaign-CN_7xJdV.js} +90 -8
- package/dist/campaign-CN_7xJdV.js.map +1 -0
- package/dist/{capture-fetch-BAx3Gntt.d.ts → capture-fetch-BBVFzhkk.d.ts} +2 -2
- package/dist/{capture-fetch-BAx3Gntt.d.ts.map → capture-fetch-BBVFzhkk.d.ts.map} +1 -1
- package/dist/{chat-client-BX8Wh3fn.js → chat-client-2bVfrzhN.js} +2 -2
- package/dist/{chat-client-BX8Wh3fn.js.map → chat-client-2bVfrzhN.js.map} +1 -1
- package/dist/cli.js +2 -2
- package/dist/{client-BV7icIfy.d.ts → client-kPQYT_56.d.ts} +2 -2
- package/dist/{client-BV7icIfy.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-BUtRMRKk.d.ts → default-registry-Cw0Ohdoj.d.ts} +6 -6
- package/dist/{default-registry-BUtRMRKk.d.ts.map → default-registry-Cw0Ohdoj.d.ts.map} +1 -1
- package/dist/{define-agent-eval-BWuk2O_N.d.ts → define-agent-eval-CEQWL9Hy.d.ts} +5 -5
- package/dist/{define-agent-eval-BWuk2O_N.d.ts.map → define-agent-eval-CEQWL9Hy.d.ts.map} +1 -1
- package/dist/{define-agent-eval-CvZQW4u9.js → define-agent-eval-rqNyVhVV.js} +4 -4
- package/dist/{define-agent-eval-CvZQW4u9.js.map → define-agent-eval-rqNyVhVV.js.map} +1 -1
- package/dist/{dspy-rlm-engine-BmtOl_kP.js → dspy-rlm-engine-DHI0WrUU.js} +2 -2
- package/dist/{dspy-rlm-engine-BmtOl_kP.js.map → dspy-rlm-engine-DHI0WrUU.js.map} +1 -1
- package/dist/{engine-CIy18RTX.d.ts → engine-BLzhNzoY.d.ts} +4 -4
- package/dist/{engine-CIy18RTX.d.ts.map → engine-BLzhNzoY.d.ts.map} +1 -1
- package/dist/{eval-campaign-bmZ6NIIP.js → eval-campaign-C4jmuM-b.js} +2 -2
- package/dist/{eval-campaign-bmZ6NIIP.js.map → eval-campaign-C4jmuM-b.js.map} +1 -1
- package/dist/{exact-types-rdKFzEnK.d.ts → exact-types-ccQAyut1.d.ts} +2 -2
- package/dist/{exact-types-rdKFzEnK.d.ts.map → exact-types-ccQAyut1.d.ts.map} +1 -1
- package/dist/experiment/index.d.ts +73 -4
- package/dist/experiment/index.d.ts.map +1 -1
- package/dist/experiment/index.js +175 -1
- package/dist/experiment/index.js.map +1 -1
- package/dist/{experiment-tracker-D9VHwRm-.d.ts → experiment-tracker-0MhuPArU.d.ts} +2 -2
- package/dist/{experiment-tracker-D9VHwRm-.d.ts.map → experiment-tracker-0MhuPArU.d.ts.map} +1 -1
- package/dist/{external-optimizer-contracts-CSjDLLmr.d.ts → external-optimizer-contracts-DbLsm4Po.d.ts} +13 -6
- package/dist/{external-optimizer-contracts-CSjDLLmr.d.ts.map → external-optimizer-contracts-DbLsm4Po.d.ts.map} +1 -1
- package/dist/{external-optimizer-process-C4AyCcQd.js → external-optimizer-process-Bhmzngf-.js} +4 -4
- package/dist/{external-optimizer-process-C4AyCcQd.js.map → external-optimizer-process-Bhmzngf-.js.map} +1 -1
- package/dist/{external-optimizer-subprocess-DU1KM8yV.js → external-optimizer-subprocess-Dn90UqN2.js} +572 -25
- package/dist/external-optimizer-subprocess-Dn90UqN2.js.map +1 -0
- package/dist/{feedback-trajectory-niGeR6uJ.d.ts → feedback-trajectory-DpTTjo0q.d.ts} +3 -3
- package/dist/{feedback-trajectory-niGeR6uJ.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-aHDnhBC7.d.ts → index-CTKpu9ry.d.ts} +7 -7
- package/dist/{index-aHDnhBC7.d.ts.map → index-CTKpu9ry.d.ts.map} +1 -1
- package/dist/{index-BCDyf_kT.d.ts → index-IQccV3Ou.d.ts} +47 -11
- package/dist/index-IQccV3Ou.d.ts.map +1 -0
- package/dist/index.d.ts +20 -20
- package/dist/index.js +8 -8
- package/dist/{integrity-CGfpTE5-.d.ts → integrity-B0dZ96EO.d.ts} +2 -2
- package/dist/{integrity-CGfpTE5-.d.ts.map → integrity-B0dZ96EO.d.ts.map} +1 -1
- package/dist/{llm-client-BMuxYoZy.js → llm-client-Bg32RW0j.js} +61 -7
- package/dist/llm-client-Bg32RW0j.js.map +1 -0
- package/dist/{llm-judge-DWq1Ptco.js → llm-judge-CVq33oz1.js} +5 -4
- package/dist/llm-judge-CVq33oz1.js.map +1 -0
- package/dist/{matrix-Bc111FKv.d.ts → matrix-DrVnRp4G.d.ts} +2 -2
- package/dist/{matrix-Bc111FKv.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-C0oJ4vr-.js → produced-state-jfk8Du3b.js} +3 -3
- package/dist/{produced-state-C0oJ4vr-.js.map → produced-state-jfk8Du3b.js.map} +1 -1
- package/dist/{promotion-policy-CewdjfCJ.d.ts → promotion-policy-DLOUkYhI.d.ts} +2 -2
- package/dist/{promotion-policy-CewdjfCJ.d.ts.map → promotion-policy-DLOUkYhI.d.ts.map} +1 -1
- package/dist/{provenance-DA-Pmyfv.d.ts → provenance-oA4-zUqm.d.ts} +17 -6
- package/dist/{provenance-DA-Pmyfv.d.ts.map → provenance-oA4-zUqm.d.ts.map} +1 -1
- package/dist/{registry-CaqJIDdN.d.ts → registry-BQwrSYpC.d.ts} +4 -4
- package/dist/{registry-CaqJIDdN.d.ts.map → registry-BQwrSYpC.d.ts.map} +1 -1
- package/dist/{researcher-BySlpla_.d.ts → researcher-DJnoUE8c.d.ts} +3 -3
- package/dist/{researcher-BySlpla_.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-BI7Rrl5-.js → semantic-concept-judge-laMCnTLn.js} +2 -2
- package/dist/{semantic-concept-judge-BI7Rrl5-.js.map → semantic-concept-judge-laMCnTLn.js.map} +1 -1
- package/dist/{series-convergence-D2fsoJ2w.d.ts → series-convergence-BxKEgBwA.d.ts} +2 -2
- package/dist/{series-convergence-D2fsoJ2w.d.ts.map → series-convergence-BxKEgBwA.d.ts.map} +1 -1
- package/dist/{server-D9wQclzG.js → server-dIWwF3j_.js} +2 -2
- package/dist/{server-D9wQclzG.js.map → server-dIWwF3j_.js.map} +1 -1
- package/dist/{skillopt-optimization-method-ChCTN4FD.d.ts → skillopt-optimization-method-BDD_o1xE.d.ts} +19 -13
- package/dist/{skillopt-optimization-method-ChCTN4FD.d.ts.map → skillopt-optimization-method-BDD_o1xE.d.ts.map} +1 -1
- package/dist/{skillopt-optimization-method-Donmq4sq.js → skillopt-optimization-method-DLeUcK-K.js} +92 -14
- package/dist/skillopt-optimization-method-DLeUcK-K.js.map +1 -0
- package/dist/{statistical-heldout-uxSpFEjm.d.ts → statistical-heldout-_woZ9q9j.d.ts} +2 -2
- package/dist/{statistical-heldout-uxSpFEjm.d.ts.map → statistical-heldout-_woZ9q9j.d.ts.map} +1 -1
- package/dist/{store-tool-spans-D5FhM0_A.d.ts → store-tool-spans-Br2_IUhm.d.ts} +4 -4
- package/dist/{store-tool-spans-D5FhM0_A.d.ts.map → store-tool-spans-Br2_IUhm.d.ts.map} +1 -1
- package/dist/{tool-groups-zpufabP8.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-BohHewKK.d.ts → types-B2NsbrNy.d.ts} +2 -2
- package/dist/{types-BohHewKK.d.ts.map → types-B2NsbrNy.d.ts.map} +1 -1
- package/dist/{types-ColZlZtc.d.ts → types-CLAwnY-L.d.ts} +2 -2
- package/dist/{types-ColZlZtc.d.ts.map → types-CLAwnY-L.d.ts.map} +1 -1
- package/dist/{types-C1Bmeb8X.d.ts → types-DdFNuyxQ.d.ts} +3 -3
- package/dist/{types-C1Bmeb8X.d.ts.map → types-DdFNuyxQ.d.ts.map} +1 -1
- package/dist/{types-DOhGq4S1.d.ts → types-jUBXJ7Iz.d.ts} +50 -5
- 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 +67 -3
- package/docs/experiment.md +2 -0
- package/docs/multishot-golden-records.md +3 -0
- package/package.json +4 -2
- package/dist/benchmark-command-gufocQ-N.js.map +0 -1
- package/dist/campaign-BcfXzmPM.js.map +0 -1
- package/dist/external-optimizer-subprocess-DU1KM8yV.js.map +0 -1
- package/dist/index-BCDyf_kT.d.ts.map +0 -1
- package/dist/index-CKblBxtr.d.ts +0 -1
- package/dist/llm-client-BMuxYoZy.js.map +0 -1
- package/dist/llm-judge-DWq1Ptco.js.map +0 -1
- package/dist/reward-hacking-DFo2FU5J.js.map +0 -1
- package/dist/skillopt-optimization-method-Donmq4sq.js.map +0 -1
- package/dist/tool-groups-zpufabP8.d.ts.map +0 -1
- package/dist/types-DOhGq4S1.d.ts.map +0 -1
package/dist/{external-optimizer-subprocess-DU1KM8yV.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 } 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";
|
|
@@ -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
|
-
|
|
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
|
|
420
|
-
|
|
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
|
-
|
|
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
|
-
...
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
560
|
-
|
|
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, {
|
|
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, {
|
|
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-
|
|
1900
|
+
//# sourceMappingURL=external-optimizer-subprocess-Dn90UqN2.js.map
|