@tangle-network/agent-eval 0.144.0 → 0.144.3
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 +36 -0
- package/dist/analyst/index.d.ts +88 -23
- package/dist/analyst/index.d.ts.map +1 -1
- package/dist/analyst/index.js +4 -4
- package/dist/{analyze-runs-BScZvqMV.js → analyze-runs-DWIvOAGk.js} +2 -2
- package/dist/{analyze-runs-BScZvqMV.js.map → analyze-runs-DWIvOAGk.js.map} +1 -1
- package/dist/{benchmark-DxaZfy0w.d.ts → benchmark-CP6kWfj8.d.ts} +3 -3
- package/dist/{benchmark-DxaZfy0w.d.ts.map → benchmark-CP6kWfj8.d.ts.map} +1 -1
- package/dist/{benchmark-command-4c7N_rlw.js → benchmark-command-CQPKRUr-.js} +332 -111
- package/dist/benchmark-command-CQPKRUr-.js.map +1 -0
- package/dist/benchmarks/index.d.ts +1 -1
- package/dist/benchmarks/index.js +1 -1
- package/dist/{benchmarks-05UGZ8sZ.js → benchmarks-CkG1bWFa.js} +4 -4
- package/dist/{benchmarks-05UGZ8sZ.js.map → benchmarks-CkG1bWFa.js.map} +1 -1
- package/dist/campaign/index.d.ts +6 -6
- package/dist/campaign/index.js +4 -4
- package/dist/{campaign-BKOtvRAB.js → campaign-DjGFyPxH.js} +74 -40
- package/dist/campaign-DjGFyPxH.js.map +1 -0
- package/dist/cli.js +2 -2
- package/dist/{client-Cgl6KasJ.d.ts → client-Bbht4xxl.d.ts} +4 -4
- package/dist/{client-Cgl6KasJ.d.ts.map → client-Bbht4xxl.d.ts.map} +1 -1
- package/dist/{completion-verifier-D15NHYSk.d.ts → completion-verifier-EJERfFwF.d.ts} +3 -3
- package/dist/{completion-verifier-D15NHYSk.d.ts.map → completion-verifier-EJERfFwF.d.ts.map} +1 -1
- package/dist/contract/index.d.ts +8 -8
- package/dist/contract/index.js +10 -8
- package/dist/contract/index.js.map +1 -1
- package/dist/control.d.ts +2 -2
- package/dist/control.js +1 -1
- package/dist/{default-registry-DfHJEwYh.js → default-registry-SOyHB6qG.js} +2 -2
- package/dist/{default-registry-DfHJEwYh.js.map → default-registry-SOyHB6qG.js.map} +1 -1
- package/dist/{default-registry-D3uqKbo6.d.ts → default-registry-iXfu2trt.d.ts} +5 -5
- package/dist/{default-registry-D3uqKbo6.d.ts.map → default-registry-iXfu2trt.d.ts.map} +1 -1
- package/dist/{dspy-rlm-engine-CBFwlyaY.js → dspy-rlm-engine-IRCG8kdi.js} +61 -105
- package/dist/dspy-rlm-engine-IRCG8kdi.js.map +1 -0
- package/dist/{eval-campaign-YdkpWWoT.js → eval-campaign-lZcDIwQM.js} +3 -3
- package/dist/{eval-campaign-YdkpWWoT.js.map → eval-campaign-lZcDIwQM.js.map} +1 -1
- package/dist/{exact-types-B0lJV3tu.d.ts → exact-types-BygCBR4L.d.ts} +2 -2
- package/dist/{exact-types-B0lJV3tu.d.ts.map → exact-types-BygCBR4L.d.ts.map} +1 -1
- package/dist/{external-optimizer-contracts-iK0yu4AR.d.ts → external-optimizer-contracts-CdmX2K2S.d.ts} +47 -10
- package/dist/external-optimizer-contracts-CdmX2K2S.d.ts.map +1 -0
- package/dist/{feedback-trajectory-BCHqzLh3.d.ts → feedback-trajectory-CSIkRLQX.d.ts} +3 -3
- package/dist/{feedback-trajectory-BCHqzLh3.d.ts.map → feedback-trajectory-CSIkRLQX.d.ts.map} +1 -1
- package/dist/hosted/index.d.ts +3 -3
- package/dist/{index-DgbFl4cv.d.ts → index-BuHs_OnD.d.ts} +17 -12
- package/dist/{index-DgbFl4cv.d.ts.map → index-BuHs_OnD.d.ts.map} +1 -1
- package/dist/{index-D2enbqA0.d.ts → index-CNOCxBLh.d.ts} +2 -2
- package/dist/{index-D2enbqA0.d.ts.map → index-CNOCxBLh.d.ts.map} +1 -1
- package/dist/{index-DFLVtPZ9.d.ts → index-DGIzNtRv.d.ts} +2 -2
- package/dist/{index-DFLVtPZ9.d.ts.map → index-DGIzNtRv.d.ts.map} +1 -1
- package/dist/{index-DtMpBKVF.d.ts → index-D_qTihaQ.d.ts} +5 -5
- package/dist/{index-DtMpBKVF.d.ts.map → index-D_qTihaQ.d.ts.map} +1 -1
- package/dist/index.d.ts +20 -20
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -17
- package/dist/{insight-report-Bh_8ksel.d.ts → insight-report-D5m1z0_n.d.ts} +3 -3
- package/dist/{insight-report-Bh_8ksel.d.ts.map → insight-report-D5m1z0_n.d.ts.map} +1 -1
- package/dist/ledger-core/index.js +1 -1
- package/dist/{ledger-core-Dxz0Rkwa.js → ledger-core-DXZIqu17.js} +3 -3
- package/dist/ledger-core-DXZIqu17.js.map +1 -0
- package/dist/{llm-client-B3WXSH5Y.js → llm-client-D3EoChAU.js} +7 -7
- package/dist/llm-client-D3EoChAU.js.map +1 -0
- package/dist/meta-eval/index.d.ts +1 -1
- package/dist/{mint-Ctwk079K.js → mint-DD-0oQTA.js} +2 -2
- package/dist/{mint-Ctwk079K.js.map → mint-DD-0oQTA.js.map} +1 -1
- package/dist/multishot/index.d.ts +1 -1
- package/dist/openapi.json +1 -1
- package/dist/{propose-review-control-DLXz4FCX.js → propose-review-control-BciUCZoh.js} +2 -2
- package/dist/{propose-review-control-DLXz4FCX.js.map → propose-review-control-BciUCZoh.js.map} +1 -1
- package/dist/{release-report-B_bQOHM-.d.ts → release-report-BZRWdq_t.d.ts} +3 -3
- package/dist/{release-report-B_bQOHM-.d.ts.map → release-report-BZRWdq_t.d.ts.map} +1 -1
- package/dist/{release-report-B5XPBvAU.js → release-report-Sl0xfkFv.js} +2 -2
- package/dist/{release-report-B5XPBvAU.js.map → release-report-Sl0xfkFv.js.map} +1 -1
- package/dist/{replay-DjUfTrHD.js → replay-CqOsGjzU.js} +2 -9
- package/dist/replay-CqOsGjzU.js.map +1 -0
- package/dist/{replay-BuJM6kLh.d.ts → replay-DQ-55DC_.d.ts} +4 -4
- package/dist/{replay-BuJM6kLh.d.ts.map → replay-DQ-55DC_.d.ts.map} +1 -1
- package/dist/reporting.d.ts +3 -3
- package/dist/reporting.js +1 -1
- package/dist/{researcher-C6lzl-rP.d.ts → researcher-BSiCoM1s.d.ts} +3 -3
- package/dist/{researcher-C6lzl-rP.d.ts.map → researcher-BSiCoM1s.d.ts.map} +1 -1
- package/dist/{reward-hacking-DjTi9HLb.js → reward-hacking-CyuzxKly.js} +2 -2
- package/dist/{reward-hacking-DjTi9HLb.js.map → reward-hacking-CyuzxKly.js.map} +1 -1
- package/dist/{reward-hacking-CEVVmy3h.d.ts → reward-hacking-DFgkEY4p.d.ts} +2 -2
- package/dist/{reward-hacking-CEVVmy3h.d.ts.map → reward-hacking-DFgkEY4p.d.ts.map} +1 -1
- package/dist/rl.d.ts +5 -5
- package/dist/rl.js +4 -4
- package/dist/rollout/index.d.ts +1 -1
- package/dist/rollout/index.js +2 -2
- package/dist/{rollout-8nj3mYvx.js → rollout-C2fD1cf4.js} +2 -2
- package/dist/{rollout-8nj3mYvx.js.map → rollout-C2fD1cf4.js.map} +1 -1
- package/dist/{rubric-predictive-validity-C2CthIfY.d.ts → rubric-predictive-validity-C4r4Y-q8.d.ts} +2 -2
- package/dist/{rubric-predictive-validity-C2CthIfY.d.ts.map → rubric-predictive-validity-C4r4Y-q8.d.ts.map} +1 -1
- package/dist/{run-evidence-8Ou28QSa.d.ts → run-evidence-H1vRpIdT.d.ts} +3 -3
- package/dist/{run-evidence-8Ou28QSa.d.ts.map → run-evidence-H1vRpIdT.d.ts.map} +1 -1
- package/dist/{run-record-vRgqWmJw.js → run-record-CWN8-VsV.js} +40 -14
- package/dist/{run-record-vRgqWmJw.js.map → run-record-CWN8-VsV.js.map} +1 -1
- package/dist/{run-record-Tb3TTtUn.d.ts → run-record-ooo9FWns.d.ts} +5 -9
- package/dist/{run-record-Tb3TTtUn.d.ts.map → run-record-ooo9FWns.d.ts.map} +1 -1
- package/dist/{semantic-concept-judge-DJQtFr95.js → semantic-concept-judge-Do5aM9wP.js} +3 -3
- package/dist/{semantic-concept-judge-DJQtFr95.js.map → semantic-concept-judge-Do5aM9wP.js.map} +1 -1
- package/dist/{server-Cu4M3NSO.js → server-Df00sdwz.js} +3 -3
- package/dist/{server-Cu4M3NSO.js.map → server-Df00sdwz.js.map} +1 -1
- package/dist/{single-run-lock-t1si1ob7.js → single-run-lock-D5iN0Xzb.js} +719 -187
- package/dist/single-run-lock-D5iN0Xzb.js.map +1 -0
- package/dist/{skill-usage-BiVEU0QY.d.ts → skill-usage-DtpLou9L.d.ts} +28 -9
- package/dist/skill-usage-DtpLou9L.d.ts.map +1 -0
- package/dist/{skillopt-optimization-method-Ds8J1_K8.js → skillopt-optimization-method-C4FX42dy.js} +42 -210
- package/dist/skillopt-optimization-method-C4FX42dy.js.map +1 -0
- package/dist/{skillopt-optimization-method-B7o01OdX.d.ts → skillopt-optimization-method-CvSJGdm3.d.ts} +12 -8
- package/dist/{skillopt-optimization-method-B7o01OdX.d.ts.map → skillopt-optimization-method-CvSJGdm3.d.ts.map} +1 -1
- package/dist/{summary-report-o3eJ3gxG.d.ts → summary-report-BOM6dfP7.d.ts} +2 -2
- package/dist/{summary-report-o3eJ3gxG.d.ts.map → summary-report-BOM6dfP7.d.ts.map} +1 -1
- package/dist/{tool-groups-Bqy4A3QB.d.ts → tool-groups-CMmsgTzj.d.ts} +7 -7
- package/dist/{tool-groups-Bqy4A3QB.d.ts.map → tool-groups-CMmsgTzj.d.ts.map} +1 -1
- package/dist/traces.d.ts +4 -4
- package/dist/traces.js +1 -1
- package/dist/types-BjMFz88h.d.ts.map +1 -1
- package/dist/{types-KEqL1pZc.d.ts → types-DcJxgsLy.d.ts} +8 -6
- package/dist/{types-KEqL1pZc.d.ts.map → types-DcJxgsLy.d.ts.map} +1 -1
- package/dist/{types-D3jh6F98.d.ts → types-y8jrxXWd.d.ts} +2 -2
- package/dist/{types-D3jh6F98.d.ts.map → types-y8jrxXWd.d.ts.map} +1 -1
- package/dist/wire/index.d.ts +1 -1
- package/dist/wire/index.js +1 -1
- package/docs/eval-surface-map.md +4 -0
- package/docs/trace-analysis.md +43 -32
- package/package.json +1 -1
- package/dist/benchmark-command-4c7N_rlw.js.map +0 -1
- package/dist/campaign-BKOtvRAB.js.map +0 -1
- package/dist/dspy-rlm-engine-CBFwlyaY.js.map +0 -1
- package/dist/external-optimizer-contracts-iK0yu4AR.d.ts.map +0 -1
- package/dist/ledger-core-Dxz0Rkwa.js.map +0 -1
- package/dist/llm-client-B3WXSH5Y.js.map +0 -1
- package/dist/replay-DjUfTrHD.js.map +0 -1
- package/dist/single-run-lock-t1si1ob7.js.map +0 -1
- package/dist/skill-usage-BiVEU0QY.d.ts.map +0 -1
- package/dist/skillopt-optimization-method-Ds8J1_K8.js.map +0 -1
|
@@ -1,16 +1,46 @@
|
|
|
1
1
|
import { c as ValidationError } from "./errors-D-LKuDhb.js";
|
|
2
2
|
import { c as costForTokenPricing, i as CostLedger } from "./cost-ledger-DMFxsLKr.js";
|
|
3
|
-
import { a as appendLedgerLine, d as tryAcquireAtomicFileLock, o as tryWithLedgerFileLock, u as probeAtomicFileLock } from "./ledger-core-
|
|
3
|
+
import { a as appendLedgerLine, d as tryAcquireAtomicFileLock, o as tryWithLedgerFileLock, u as probeAtomicFileLock } from "./ledger-core-DXZIqu17.js";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { appendFileSync, existsSync, readFileSync } from "node:fs";
|
|
6
6
|
import { isAbsolute, join, resolve } from "node:path";
|
|
7
7
|
import { createHash, randomBytes } from "node:crypto";
|
|
8
|
+
import { constants as constants$1 } from "node:buffer";
|
|
8
9
|
import { createServer } from "node:http";
|
|
9
10
|
import { spawn } from "node:child_process";
|
|
10
11
|
import { lstat, mkdtemp, open, rm, writeFile } from "node:fs/promises";
|
|
11
12
|
import { tmpdir } from "node:os";
|
|
12
13
|
//#region src/campaign/external-optimizer-contracts.ts
|
|
13
14
|
const MAX_TIMER_DELAY_MS$1 = 2147483647;
|
|
15
|
+
const MAXIMUM_JSON_BYTES = Math.min(constants$1.MAX_LENGTH - 1, constants$1.MAX_STRING_LENGTH);
|
|
16
|
+
const DEFAULT_EXTERNAL_OPTIMIZER_PROCESS_LIMITS = Object.freeze({
|
|
17
|
+
maxInputBytes: 64 * 1024 * 1024,
|
|
18
|
+
maxResultBytes: 4 * 1024 * 1024,
|
|
19
|
+
maxOutputChars: 64e3
|
|
20
|
+
});
|
|
21
|
+
const DEFAULT_EXTERNAL_OPTIMIZER_CALLBACK_LIMITS = Object.freeze({
|
|
22
|
+
maxRequestBytes: 1e6,
|
|
23
|
+
maxResponseBytes: 4e6
|
|
24
|
+
});
|
|
25
|
+
function resolveExternalOptimizerProcessLimits(value, label = "external optimizer process limits") {
|
|
26
|
+
const resolved = {
|
|
27
|
+
...DEFAULT_EXTERNAL_OPTIMIZER_PROCESS_LIMITS,
|
|
28
|
+
...value
|
|
29
|
+
};
|
|
30
|
+
assertBoundedPositiveInteger(resolved.maxInputBytes, `${label}.maxInputBytes`, MAXIMUM_JSON_BYTES);
|
|
31
|
+
assertBoundedPositiveInteger(resolved.maxResultBytes, `${label}.maxResultBytes`, MAXIMUM_JSON_BYTES);
|
|
32
|
+
assertBoundedPositiveInteger(resolved.maxOutputChars, `${label}.maxOutputChars`, constants$1.MAX_STRING_LENGTH);
|
|
33
|
+
return resolved;
|
|
34
|
+
}
|
|
35
|
+
function resolveExternalOptimizerCallbackLimits(value, label = "external optimizer callback limits") {
|
|
36
|
+
const resolved = {
|
|
37
|
+
...DEFAULT_EXTERNAL_OPTIMIZER_CALLBACK_LIMITS,
|
|
38
|
+
...value
|
|
39
|
+
};
|
|
40
|
+
assertBoundedPositiveInteger(resolved.maxRequestBytes, `${label}.maxRequestBytes`, MAXIMUM_JSON_BYTES);
|
|
41
|
+
assertBoundedPositiveInteger(resolved.maxResponseBytes, `${label}.maxResponseBytes`, MAXIMUM_JSON_BYTES);
|
|
42
|
+
return resolved;
|
|
43
|
+
}
|
|
14
44
|
function isCandidateText(value, maxChars) {
|
|
15
45
|
return typeof value === "string" && value.trim().length > 0 && value.length <= maxChars;
|
|
16
46
|
}
|
|
@@ -59,12 +89,9 @@ function removeCredentialEnvironment(environment) {
|
|
|
59
89
|
}
|
|
60
90
|
function assertExternalOptimizerModelBudget(value, label) {
|
|
61
91
|
if (!value || typeof value !== "object") throw new Error(`${label} is required`);
|
|
62
|
-
for (const [field, entry] of [
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
["maxResponseBytes", value.maxResponseBytes],
|
|
66
|
-
["maxOutputTokensPerRequest", value.maxOutputTokensPerRequest]
|
|
67
|
-
]) if (!Number.isSafeInteger(entry) || entry <= 0) throw new Error(`${label}.${field} must be a positive safe integer`);
|
|
92
|
+
for (const [field, entry] of [["maxRequests", value.maxRequests], ["maxOutputTokensPerRequest", value.maxOutputTokensPerRequest]]) if (!Number.isSafeInteger(entry) || entry <= 0) throw new Error(`${label}.${field} must be a positive safe integer`);
|
|
93
|
+
assertBoundedPositiveInteger(value.maxRequestBytes, `${label}.maxRequestBytes`, MAXIMUM_JSON_BYTES);
|
|
94
|
+
assertBoundedPositiveInteger(value.maxResponseBytes, `${label}.maxResponseBytes`, MAXIMUM_JSON_BYTES);
|
|
68
95
|
if (value.maxReasoningTokensPerRequest !== void 0 && (!Number.isSafeInteger(value.maxReasoningTokensPerRequest) || value.maxReasoningTokensPerRequest < 0)) throw new Error(`${label}.maxReasoningTokensPerRequest must be a non-negative safe integer`);
|
|
69
96
|
if (value.maxCostUsd !== void 0 && (!Number.isFinite(value.maxCostUsd) || value.maxCostUsd <= 0)) throw new Error(`${label}.maxCostUsd must be positive and finite when supplied`);
|
|
70
97
|
if (value.requestTimeoutMs !== void 0 && (!Number.isSafeInteger(value.requestTimeoutMs) || value.requestTimeoutMs <= 0 || value.requestTimeoutMs > MAX_TIMER_DELAY_MS$1)) throw new Error(`${label}.requestTimeoutMs must be between 1 and ${MAX_TIMER_DELAY_MS$1}`);
|
|
@@ -77,6 +104,9 @@ function assertExternalOptimizerModelBudget(value, label) {
|
|
|
77
104
|
function safePathComponent(value) {
|
|
78
105
|
return value.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
79
106
|
}
|
|
107
|
+
function assertBoundedPositiveInteger(value, label, maximum) {
|
|
108
|
+
if (!Number.isSafeInteger(value) || value <= 0 || value > maximum) throw new Error(`${label} must be an integer between 1 and ${maximum}`);
|
|
109
|
+
}
|
|
80
110
|
function isCredentialKey(key) {
|
|
81
111
|
return key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/-/g, "_").toLowerCase().split("_").some((segment) => [
|
|
82
112
|
"auth",
|
|
@@ -294,6 +324,7 @@ async function startExternalOptimizerModelProxy(args) {
|
|
|
294
324
|
let modelCallInvocations = 0;
|
|
295
325
|
let executionRecordCount = 0;
|
|
296
326
|
let executionSequence = 0;
|
|
327
|
+
const failures = [];
|
|
297
328
|
let totalRequestCount = args.initialUsage?.requests ?? 0;
|
|
298
329
|
let committedForBudget = args.initialUsage?.costUsd ?? 0;
|
|
299
330
|
let reservedForBudget = 0;
|
|
@@ -303,7 +334,7 @@ async function startExternalOptimizerModelProxy(args) {
|
|
|
303
334
|
const activeHandlers = /* @__PURE__ */ new Set();
|
|
304
335
|
const server = createServer((request, response) => {
|
|
305
336
|
if (!accepting) {
|
|
306
|
-
sendJsonIfOpen(response, 503, { error: "external optimizer model proxy is closing" });
|
|
337
|
+
sendJsonIfOpen$1(response, 503, { error: "external optimizer model proxy is closing" });
|
|
307
338
|
return;
|
|
308
339
|
}
|
|
309
340
|
const controller = new AbortController();
|
|
@@ -355,6 +386,9 @@ async function startExternalOptimizerModelProxy(args) {
|
|
|
355
386
|
},
|
|
356
387
|
recordModelCallInvocation: () => {
|
|
357
388
|
modelCallInvocations += 1;
|
|
389
|
+
},
|
|
390
|
+
recordFailure: (error) => {
|
|
391
|
+
failures.push(error);
|
|
358
392
|
}
|
|
359
393
|
}).finally(() => {
|
|
360
394
|
controller.signal.removeEventListener("abort", abortRequest);
|
|
@@ -370,6 +404,7 @@ async function startExternalOptimizerModelProxy(args) {
|
|
|
370
404
|
return closePromise;
|
|
371
405
|
};
|
|
372
406
|
const onAbort = () => {
|
|
407
|
+
for (const controller of activeControllers) controller.abort(args.signal?.reason);
|
|
373
408
|
close().catch(() => void 0);
|
|
374
409
|
};
|
|
375
410
|
args.signal?.addEventListener("abort", onAbort, { once: true });
|
|
@@ -383,6 +418,7 @@ async function startExternalOptimizerModelProxy(args) {
|
|
|
383
418
|
requestAttempts: () => requestCount,
|
|
384
419
|
successfulCompletions: () => successfulCompletionCount,
|
|
385
420
|
assertExecutionComplete,
|
|
421
|
+
failures: () => [...failures],
|
|
386
422
|
close
|
|
387
423
|
};
|
|
388
424
|
async function closeModelProxy() {
|
|
@@ -390,8 +426,7 @@ async function startExternalOptimizerModelProxy(args) {
|
|
|
390
426
|
accepting = false;
|
|
391
427
|
const closingServer = closeServer(server);
|
|
392
428
|
server.closeIdleConnections?.();
|
|
393
|
-
|
|
394
|
-
const [serverResult] = await Promise.allSettled([closingServer, waitForActiveHandlers(activeHandlers)]);
|
|
429
|
+
const [serverResult] = await Promise.allSettled([closingServer, waitForActiveHandlers$1(activeHandlers)]);
|
|
395
430
|
if (activeControllers.size !== 0 || activeHandlers.size !== 0) throw new Error("external optimizer model proxy closed with active request work");
|
|
396
431
|
let executionError;
|
|
397
432
|
try {
|
|
@@ -409,26 +444,28 @@ async function handleModelProxyRequest(args) {
|
|
|
409
444
|
try {
|
|
410
445
|
const path = request.url ? new URL(request.url, "http://127.0.0.1").pathname : "";
|
|
411
446
|
if (request.method !== "POST" || !MODEL_PROXY_PATHS.has(path)) {
|
|
412
|
-
sendJsonIfOpen(response, 404, { error: "not found" });
|
|
447
|
+
sendJsonIfOpen$1(response, 404, { error: "not found" });
|
|
413
448
|
return;
|
|
414
449
|
}
|
|
450
|
+
const modelPath = path;
|
|
415
451
|
if (request.headers.authorization !== `Bearer ${args.token}`) {
|
|
416
|
-
sendJsonIfOpen(response, 401, { error: "unauthorized" });
|
|
452
|
+
sendJsonIfOpen$1(response, 401, { error: "unauthorized" });
|
|
417
453
|
return;
|
|
418
454
|
}
|
|
419
455
|
const body = await readBody(request, args.args.budget.maxRequestBytes);
|
|
420
|
-
const parsed = parseModelProxyRequest(body, args.args.model, args.args.budget);
|
|
456
|
+
const parsed = parseModelProxyRequest(body, modelPath, args.args.model, args.args.budget);
|
|
421
457
|
const maximumUsage = conservativeMaximumUsage(body.byteLength, parsed.maxOutputTokens + (args.args.budget.maxReasoningTokensPerRequest ?? 0), args.args.budget.pricing);
|
|
422
458
|
const maximumCostUsd = args.args.budget.pricing ? costForTokenPricing(args.args.budget.pricing, maximumUsage) : void 0;
|
|
423
459
|
const reservation = args.nextReservation(maximumCostUsd);
|
|
424
460
|
if (!reservation.accepted) {
|
|
425
|
-
sendJsonIfOpen(response, 429, { error: reservation.reason });
|
|
461
|
+
sendJsonIfOpen$1(response, 429, { error: reservation.reason });
|
|
426
462
|
return;
|
|
427
463
|
}
|
|
428
464
|
const timeout = setTimeout(() => controller.abort(), args.args.budget.requestTimeoutMs ?? 3e5);
|
|
429
465
|
let chargedForBudget = maximumCostUsd;
|
|
430
466
|
try {
|
|
431
467
|
const paid = await args.args.costLedger.runPaidCall({
|
|
468
|
+
...args.args.callId ? { callId: args.args.callId } : {},
|
|
432
469
|
channel: args.args.channel ?? "optimizer",
|
|
433
470
|
phase: args.args.phase,
|
|
434
471
|
actor: args.args.actor,
|
|
@@ -438,18 +475,20 @@ async function handleModelProxyRequest(args) {
|
|
|
438
475
|
customTokenPricing: args.args.budget.pricing,
|
|
439
476
|
...maximumUsage
|
|
440
477
|
} } : {},
|
|
441
|
-
|
|
478
|
+
signal: controller.signal,
|
|
479
|
+
execute: async (paidCallSignal, paidCallId) => forwardModelProxyRequest({
|
|
442
480
|
call: args.args.call,
|
|
481
|
+
callId: paidCallId,
|
|
443
482
|
callRef: args.args.callRef,
|
|
444
483
|
recordExecutionReceipt: args.recordExecutionReceipt,
|
|
445
484
|
recordModelCallInvocation: args.recordModelCallInvocation,
|
|
446
|
-
path,
|
|
447
|
-
|
|
485
|
+
path: modelPath,
|
|
486
|
+
request: parsed.request,
|
|
448
487
|
model: args.args.model,
|
|
449
488
|
maxOutputTokens: parsed.maxOutputTokens,
|
|
450
489
|
...args.args.budget.maxReasoningTokensPerRequest === void 0 ? {} : { maxReasoningTokens: args.args.budget.maxReasoningTokensPerRequest },
|
|
451
490
|
maxResponseBytes: args.args.budget.maxResponseBytes,
|
|
452
|
-
signal:
|
|
491
|
+
signal: paidCallSignal
|
|
453
492
|
}),
|
|
454
493
|
receipt: (result) => result.receipt,
|
|
455
494
|
receiptFromError: () => ({
|
|
@@ -461,17 +500,18 @@ async function handleModelProxyRequest(args) {
|
|
|
461
500
|
})
|
|
462
501
|
});
|
|
463
502
|
if (!paid.succeeded) {
|
|
503
|
+
args.recordFailure(paid.error);
|
|
464
504
|
chargedForBudget = paid.receipt ? paid.receipt.usageUnknown || paid.receipt.costUnknown ? maximumCostUsd : paid.receipt.costUsd : void 0;
|
|
465
|
-
sendJsonIfOpen(response, isAbortError(paid.error) ? 504 : paid.error instanceof ProviderResponseTooLargeError || paid.error instanceof MissingModelExecutionError || paid.error instanceof ModelExecutionPersistenceError || paid.error instanceof OwnerModelContractError ? 502 : 429, { error: paid.error.message });
|
|
505
|
+
sendJsonIfOpen$1(response, isAbortError(paid.error) ? 504 : paid.error instanceof ProviderResponseTooLargeError || paid.error instanceof MissingModelExecutionError || paid.error instanceof ModelExecutionPersistenceError || paid.error instanceof OwnerModelContractError ? 502 : 429, { error: paid.error.message });
|
|
466
506
|
return;
|
|
467
507
|
}
|
|
468
508
|
if (paid.value.modelCallFailed) {
|
|
469
|
-
sendJsonIfOpen(response, 502, { error: paid.value.modelCallFailed });
|
|
509
|
+
sendJsonIfOpen$1(response, 502, { error: paid.value.modelCallFailed });
|
|
470
510
|
return;
|
|
471
511
|
}
|
|
472
512
|
chargedForBudget = paid.value.usageComplete ? paid.receipt.costUsd : maximumCostUsd;
|
|
473
513
|
if (paid.value.usageRejected) {
|
|
474
|
-
sendJsonIfOpen(response, 502, { error: paid.value.usageRejected });
|
|
514
|
+
sendJsonIfOpen$1(response, 502, { error: paid.value.usageRejected });
|
|
475
515
|
return;
|
|
476
516
|
}
|
|
477
517
|
if (paid.value.status >= 200 && paid.value.status < 300) args.recordSuccessfulCompletion();
|
|
@@ -486,13 +526,13 @@ async function handleModelProxyRequest(args) {
|
|
|
486
526
|
args.settleReservation(maximumCostUsd, chargedForBudget);
|
|
487
527
|
}
|
|
488
528
|
} catch (error) {
|
|
489
|
-
sendJsonIfOpen(response, error instanceof RequestBodyTooLargeError ? 413 : error instanceof Error && isAbortError(error) ? 503 : 400, { error: toErrorMessage(error) });
|
|
529
|
+
sendJsonIfOpen$1(response, error instanceof RequestBodyTooLargeError ? 413 : error instanceof Error && isAbortError(error) ? 503 : 400, { error: toErrorMessage(error) });
|
|
490
530
|
}
|
|
491
531
|
}
|
|
492
|
-
async function waitForActiveHandlers(activeHandlers) {
|
|
532
|
+
async function waitForActiveHandlers$1(activeHandlers) {
|
|
493
533
|
while (activeHandlers.size > 0) await Promise.allSettled([...activeHandlers]);
|
|
494
534
|
}
|
|
495
|
-
function sendJsonIfOpen(response, status, body) {
|
|
535
|
+
function sendJsonIfOpen$1(response, status, body) {
|
|
496
536
|
if (response.destroyed || response.writableEnded) return;
|
|
497
537
|
sendJson(response, status, body);
|
|
498
538
|
}
|
|
@@ -501,9 +541,9 @@ async function forwardModelProxyRequest(args) {
|
|
|
501
541
|
let called;
|
|
502
542
|
try {
|
|
503
543
|
called = await args.call({
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
544
|
+
callId: args.callId,
|
|
545
|
+
request: freezeJsonSnapshot(args.request, "optimizer model canonical request"),
|
|
546
|
+
endpointFormat: endpointFormat(args.path),
|
|
507
547
|
signal: args.signal
|
|
508
548
|
});
|
|
509
549
|
} catch (error) {
|
|
@@ -517,18 +557,55 @@ async function forwardModelProxyRequest(args) {
|
|
|
517
557
|
throw new MissingModelExecutionError(`optimizer model callback returned invalid execution evidence: ${toErrorMessage(error)}`);
|
|
518
558
|
}
|
|
519
559
|
if (called.succeeded) {
|
|
520
|
-
|
|
560
|
+
let authoritativeReceipt;
|
|
561
|
+
let canonicalResponse;
|
|
562
|
+
try {
|
|
563
|
+
authoritativeReceipt = snapshotModelReceipt(called.receipt, args.model);
|
|
564
|
+
canonicalResponse = snapshotChatResponse(called.response, args.model);
|
|
565
|
+
assertResponseUsageMatchesReceipt(canonicalResponse, authoritativeReceipt);
|
|
566
|
+
} catch (error) {
|
|
567
|
+
args.recordExecutionReceipt({
|
|
568
|
+
callId: args.callId,
|
|
569
|
+
callRef: args.callRef,
|
|
570
|
+
path: args.path,
|
|
571
|
+
model: args.model,
|
|
572
|
+
succeeded: false,
|
|
573
|
+
error: toErrorMessage(error),
|
|
574
|
+
execution
|
|
575
|
+
});
|
|
576
|
+
throw error;
|
|
577
|
+
}
|
|
521
578
|
args.recordExecutionReceipt({
|
|
579
|
+
callId: args.callId,
|
|
522
580
|
callRef: args.callRef,
|
|
523
581
|
path: args.path,
|
|
524
582
|
model: args.model,
|
|
525
583
|
succeeded: true,
|
|
526
|
-
responseStatus:
|
|
584
|
+
responseStatus: 200,
|
|
527
585
|
execution
|
|
528
586
|
});
|
|
587
|
+
const usageKnown = canonicalResponse.usage.captured !== false;
|
|
588
|
+
const reasoningTokens = canonicalResponse.usage.reasoningTokens ?? 0;
|
|
589
|
+
const completionTokens = canonicalResponse.usage.completionTokens - reasoningTokens;
|
|
590
|
+
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;
|
|
591
|
+
return {
|
|
592
|
+
status: 200,
|
|
593
|
+
contentType: "application/json",
|
|
594
|
+
body: encodeCanonicalModelResponse({
|
|
595
|
+
path: args.path,
|
|
596
|
+
callId: args.callId,
|
|
597
|
+
response: canonicalResponse,
|
|
598
|
+
receipt: authoritativeReceipt,
|
|
599
|
+
maxBytes: args.maxResponseBytes
|
|
600
|
+
}),
|
|
601
|
+
receipt: authoritativeReceipt,
|
|
602
|
+
usageComplete: authoritativeReceipt.usageUnknown !== true,
|
|
603
|
+
...usageRejected ? { usageRejected } : {}
|
|
604
|
+
};
|
|
529
605
|
} else {
|
|
530
606
|
if (typeof called.error !== "string" || !called.error.trim()) throw new MissingModelExecutionError("optimizer model callback failure did not return a public error");
|
|
531
607
|
args.recordExecutionReceipt({
|
|
608
|
+
callId: args.callId,
|
|
532
609
|
callRef: args.callRef,
|
|
533
610
|
path: args.path,
|
|
534
611
|
model: args.model,
|
|
@@ -546,23 +623,6 @@ async function forwardModelProxyRequest(args) {
|
|
|
546
623
|
modelCallFailed: called.error
|
|
547
624
|
};
|
|
548
625
|
}
|
|
549
|
-
const response = called.response;
|
|
550
|
-
const authoritativeReceipt = snapshotModelReceipt(called.receipt, args.model);
|
|
551
|
-
const body = await readProviderResponseBody(response, args.maxResponseBytes);
|
|
552
|
-
const usage = parseProviderUsage(body);
|
|
553
|
-
const successful = response.status >= 200 && response.status < 300;
|
|
554
|
-
const completionTokens = usage === void 0 ? 0 : usage.outputTokens - (usage.reasoningTokens ?? 0);
|
|
555
|
-
const reasoningTokens = usage?.reasoningTokens ?? 0;
|
|
556
|
-
const usageRejected = successful && usage !== void 0 && completionTokens > args.maxOutputTokens ? `optimizer model provider reported ${completionTokens} completion tokens, exceeding requested limit ${args.maxOutputTokens}` : successful && args.maxReasoningTokens !== void 0 && reasoningTokens > args.maxReasoningTokens ? `optimizer model provider reported ${reasoningTokens} reasoning tokens, exceeding the declared budget ${args.maxReasoningTokens}` : void 0;
|
|
557
|
-
if (usage !== void 0) assertResponseUsageMatchesReceipt(usage, authoritativeReceipt);
|
|
558
|
-
return {
|
|
559
|
-
status: response.status,
|
|
560
|
-
contentType: response.headers.get("content-type") ?? "application/json",
|
|
561
|
-
body,
|
|
562
|
-
receipt: authoritativeReceipt,
|
|
563
|
-
usageComplete: authoritativeReceipt.usageUnknown !== true,
|
|
564
|
-
...usageRejected ? { usageRejected } : {}
|
|
565
|
-
};
|
|
566
626
|
}
|
|
567
627
|
function snapshotModelReceipt(value, expectedModel) {
|
|
568
628
|
let snapshot;
|
|
@@ -586,17 +646,20 @@ function deepFreezeJson(value) {
|
|
|
586
646
|
}
|
|
587
647
|
return value;
|
|
588
648
|
}
|
|
589
|
-
function assertResponseUsageMatchesReceipt(
|
|
590
|
-
|
|
591
|
-
"
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
"
|
|
595
|
-
"
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
}
|
|
599
|
-
|
|
649
|
+
function assertResponseUsageMatchesReceipt(response, receipt) {
|
|
650
|
+
if (response.usage.captured === false || receipt.usageUnknown === true) {
|
|
651
|
+
if (response.usage.captured !== false || receipt.usageUnknown !== true) throw new OwnerModelContractError("optimizer model response and execution receipt disagree about whether usage was captured");
|
|
652
|
+
} else {
|
|
653
|
+
const cachedTokens = response.usage.cachedPromptTokens ?? 0;
|
|
654
|
+
if (receipt.inputTokens + (receipt.cachedTokens ?? 0) !== response.usage.promptTokens) throw new OwnerModelContractError("optimizer model response promptTokens must equal receipt inputTokens plus cachedTokens");
|
|
655
|
+
if (cachedTokens !== (receipt.cachedTokens ?? 0)) throw new OwnerModelContractError("optimizer model response cachedPromptTokens disagrees with the execution receipt");
|
|
656
|
+
if (response.usage.completionTokens !== receipt.outputTokens) throw new OwnerModelContractError("optimizer model response completionTokens disagrees with the execution receipt");
|
|
657
|
+
if ((response.usage.reasoningTokens ?? 0) !== (receipt.reasoningTokens ?? 0)) throw new OwnerModelContractError("optimizer model response reasoningTokens disagrees with the execution receipt");
|
|
658
|
+
}
|
|
659
|
+
const receiptCostUsd = receipt.actualCostUsd ?? receipt.estimatedCostUsd ?? (receipt.customTokenPricing && receipt.usageUnknown !== true ? costForTokenPricing(receipt.customTokenPricing, receipt) : null);
|
|
660
|
+
if (response.costUsd !== receiptCostUsd) throw new OwnerModelContractError("optimizer model response billed cost disagrees with the execution receipt");
|
|
661
|
+
}
|
|
662
|
+
function parseModelProxyRequest(body, path, expectedModel, budget) {
|
|
600
663
|
let value;
|
|
601
664
|
try {
|
|
602
665
|
value = JSON.parse(Buffer.from(body).toString("utf8"));
|
|
@@ -612,148 +675,376 @@ function parseModelProxyRequest(body, expectedModel, budget) {
|
|
|
612
675
|
"candidate_count",
|
|
613
676
|
"num_candidates"
|
|
614
677
|
]) if (value[field] !== void 0 && value[field] !== 1) throw new Error(`optimizer model request ${field} must be 1 when supplied`);
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
const maxOutputTokens = Math.max(...suppliedMaximums);
|
|
678
|
+
if (value.reasoning !== void 0 || value.reasoning_effort !== void 0) throw new Error("optimizer model reasoning settings belong to the execution AgentProfile");
|
|
679
|
+
const maxOutputTokens = parseOutputMaximum(value, path === "/v1/responses" ? ["max_output_tokens"] : [
|
|
680
|
+
"max_tokens",
|
|
681
|
+
"max_completion_tokens",
|
|
682
|
+
"max_output_tokens"
|
|
683
|
+
]);
|
|
622
684
|
if (maxOutputTokens > budget.maxOutputTokensPerRequest) throw new Error("optimizer model request exceeds maxOutputTokensPerRequest");
|
|
623
685
|
return {
|
|
624
686
|
maxOutputTokens,
|
|
625
|
-
|
|
687
|
+
request: freezeJsonSnapshot(path === "/v1/chat/completions" ? parseChatCompletionsRequest(value, expectedModel, maxOutputTokens) : parseResponsesRequest(value, expectedModel, maxOutputTokens), "optimizer model canonical request")
|
|
626
688
|
};
|
|
627
689
|
}
|
|
628
|
-
function
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
const nestedCacheWriteTokens = optionalTokenCount(inputDetails, ["cache_write_tokens", "cache_creation_tokens"]);
|
|
645
|
-
const separateCacheWriteTokens = optionalTokenCount(usage, ["cache_creation_input_tokens"]);
|
|
646
|
-
const nestedReasoningTokens = optionalTokenCount(outputDetails, ["reasoning_tokens"]);
|
|
647
|
-
const separateReasoningTokens = optionalTokenCount(usage, ["reasoning_tokens"]);
|
|
648
|
-
const actualCostUsd = typeof usage.cost === "number" && Number.isFinite(usage.cost) && usage.cost >= 0 ? usage.cost : void 0;
|
|
649
|
-
if (nestedCachedTokens === INVALID_TOKEN_COUNT || separateCachedTokens === INVALID_TOKEN_COUNT || nestedCacheWriteTokens === INVALID_TOKEN_COUNT || separateCacheWriteTokens === INVALID_TOKEN_COUNT || nestedReasoningTokens === INVALID_TOKEN_COUNT || separateReasoningTokens === INVALID_TOKEN_COUNT) return;
|
|
650
|
-
const cached = selectProviderTokenClass(nestedCachedTokens, separateCachedTokens);
|
|
651
|
-
const cacheWrite = selectProviderTokenClass(nestedCacheWriteTokens, separateCacheWriteTokens);
|
|
652
|
-
const reasoning = selectProviderTokenClass(nestedReasoningTokens, separateReasoningTokens);
|
|
653
|
-
if (cached === void 0 || cacheWrite === void 0 || reasoning === void 0) return void 0;
|
|
654
|
-
const classifiedInputTokens = (cached.includedInTotal ? cached.count : 0) + (cacheWrite.includedInTotal ? cacheWrite.count : 0);
|
|
655
|
-
if (classifiedInputTokens > totalInputTokens || reasoning.count > outputTokens) return;
|
|
690
|
+
function parseChatCompletionsRequest(body, model, maxTokens) {
|
|
691
|
+
assertAllowedKeys(body, [
|
|
692
|
+
"model",
|
|
693
|
+
"messages",
|
|
694
|
+
"max_tokens",
|
|
695
|
+
"max_completion_tokens",
|
|
696
|
+
"max_output_tokens",
|
|
697
|
+
"temperature",
|
|
698
|
+
"thinking",
|
|
699
|
+
"response_format",
|
|
700
|
+
"stream",
|
|
701
|
+
"n",
|
|
702
|
+
"best_of",
|
|
703
|
+
"candidate_count",
|
|
704
|
+
"num_candidates"
|
|
705
|
+
]);
|
|
656
706
|
return {
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
...
|
|
661
|
-
...
|
|
662
|
-
...
|
|
707
|
+
model,
|
|
708
|
+
messages: parseChatMessages(body.messages),
|
|
709
|
+
maxTokens,
|
|
710
|
+
...parseTemperature(body.temperature),
|
|
711
|
+
...parseThinking(body.thinking),
|
|
712
|
+
...parseChatResponseFormat(body.response_format)
|
|
663
713
|
};
|
|
664
714
|
}
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
715
|
+
function parseResponsesRequest(body, model, maxTokens) {
|
|
716
|
+
assertAllowedKeys(body, [
|
|
717
|
+
"model",
|
|
718
|
+
"input",
|
|
719
|
+
"instructions",
|
|
720
|
+
"max_output_tokens",
|
|
721
|
+
"temperature",
|
|
722
|
+
"thinking",
|
|
723
|
+
"text",
|
|
724
|
+
"stream",
|
|
725
|
+
"n",
|
|
726
|
+
"best_of",
|
|
727
|
+
"candidate_count",
|
|
728
|
+
"num_candidates"
|
|
729
|
+
]);
|
|
730
|
+
const instructions = body.instructions;
|
|
731
|
+
if (instructions !== void 0 && typeof instructions !== "string") throw new Error("optimizer Responses instructions must be a string");
|
|
732
|
+
return {
|
|
733
|
+
model,
|
|
734
|
+
messages: [...instructions === void 0 ? [] : [{
|
|
735
|
+
role: "system",
|
|
736
|
+
content: instructions
|
|
737
|
+
}], ...parseResponsesInput(body.input)],
|
|
738
|
+
maxTokens,
|
|
739
|
+
...parseTemperature(body.temperature),
|
|
740
|
+
...parseThinking(body.thinking),
|
|
741
|
+
...parseResponsesTextFormat(body.text)
|
|
683
742
|
};
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
743
|
+
}
|
|
744
|
+
function parseOutputMaximum(body, fields) {
|
|
745
|
+
const supplied = fields.flatMap((field) => body[field] === void 0 ? [] : [{
|
|
746
|
+
field,
|
|
747
|
+
value: body[field]
|
|
748
|
+
}]);
|
|
749
|
+
if (supplied.length === 0 || supplied.some(({ value }) => !Number.isSafeInteger(value) || value <= 0)) throw new Error("optimizer model request requires a positive output-token limit");
|
|
750
|
+
if (new Set(supplied.map(({ value }) => value)).size !== 1) throw new Error("optimizer model request supplied conflicting output-token limits");
|
|
751
|
+
return supplied[0].value;
|
|
752
|
+
}
|
|
753
|
+
function parseTemperature(value) {
|
|
754
|
+
if (value === void 0) return {};
|
|
755
|
+
if (typeof value !== "number" || !Number.isFinite(value)) throw new Error("optimizer model request temperature must be finite");
|
|
756
|
+
return { temperature: value };
|
|
757
|
+
}
|
|
758
|
+
function parseThinking(value) {
|
|
759
|
+
if (value === void 0) return {};
|
|
760
|
+
if (!isRecord(value)) throw new Error("optimizer model request thinking must be an object");
|
|
761
|
+
assertAllowedKeys(value, ["type"], "optimizer model request thinking");
|
|
762
|
+
if (value.type !== "enabled" && value.type !== "disabled") throw new Error("optimizer model request thinking.type must be enabled or disabled");
|
|
763
|
+
return { thinking: value.type };
|
|
764
|
+
}
|
|
765
|
+
function parseChatMessages(value) {
|
|
766
|
+
if (!Array.isArray(value)) throw new Error("optimizer chat request messages must be an array");
|
|
767
|
+
return value.map((message, index) => parseMessage(message, `optimizer chat message ${index}`));
|
|
768
|
+
}
|
|
769
|
+
function parseResponsesInput(value) {
|
|
770
|
+
if (typeof value === "string") return [{
|
|
771
|
+
role: "user",
|
|
772
|
+
content: value
|
|
773
|
+
}];
|
|
774
|
+
if (!Array.isArray(value) || value.length === 0) throw new Error("optimizer Responses request requires non-empty input");
|
|
775
|
+
return value.map((message, index) => {
|
|
776
|
+
if (!isRecord(message)) throw new Error(`optimizer Responses input ${index} must be a message object`);
|
|
777
|
+
assertAllowedKeys(message, [
|
|
778
|
+
"type",
|
|
779
|
+
"role",
|
|
780
|
+
"content"
|
|
781
|
+
], `optimizer Responses input ${index}`);
|
|
782
|
+
if (message.type !== void 0 && message.type !== "message") throw new Error(`optimizer Responses input ${index} type must be message`);
|
|
783
|
+
return parseMessage(message, `optimizer Responses input ${index}`, true);
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
function parseMessage(value, label, responsesContent = false) {
|
|
787
|
+
if (!isRecord(value)) throw new Error(`${label} must be an object`);
|
|
788
|
+
assertAllowedKeys(value, responsesContent ? [
|
|
789
|
+
"type",
|
|
790
|
+
"role",
|
|
791
|
+
"content"
|
|
792
|
+
] : ["role", "content"], label);
|
|
793
|
+
if (value.role !== "system" && value.role !== "user" && value.role !== "assistant") throw new Error(`${label} role must be system, user, or assistant`);
|
|
794
|
+
return {
|
|
795
|
+
role: value.role,
|
|
796
|
+
content: parseMessageContent(value.content, label, responsesContent)
|
|
688
797
|
};
|
|
798
|
+
}
|
|
799
|
+
function parseMessageContent(value, label, responsesContent) {
|
|
800
|
+
if (typeof value === "string") return value;
|
|
801
|
+
if (!Array.isArray(value) || value.length === 0) throw new Error(`${label} content must be text or a non-empty content array`);
|
|
802
|
+
return value.map((part, partIndex) => {
|
|
803
|
+
if (!isRecord(part)) throw new Error(`${label} content ${partIndex} must be an object`);
|
|
804
|
+
const partLabel = `${label} content ${partIndex}`;
|
|
805
|
+
if (part.type === "text" || responsesContent && part.type === "input_text") {
|
|
806
|
+
assertAllowedKeys(part, ["type", "text"], partLabel);
|
|
807
|
+
if (typeof part.text !== "string") throw new Error(`${partLabel} text must be a string`);
|
|
808
|
+
return {
|
|
809
|
+
type: "text",
|
|
810
|
+
text: part.text
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
if (responsesContent && part.type === "output_text") {
|
|
814
|
+
assertAllowedKeys(part, ["type", "text"], partLabel);
|
|
815
|
+
if (typeof part.text !== "string") throw new Error(`${partLabel} text must be a string`);
|
|
816
|
+
return {
|
|
817
|
+
type: "text",
|
|
818
|
+
text: part.text
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
if (part.type === "image_url") {
|
|
822
|
+
assertAllowedKeys(part, ["type", "image_url"], partLabel);
|
|
823
|
+
if (!isRecord(part.image_url) || typeof part.image_url.url !== "string") throw new Error(`${partLabel} image_url must contain a URL string`);
|
|
824
|
+
assertAllowedKeys(part.image_url, ["url", "detail"], `${partLabel} image_url`);
|
|
825
|
+
const detail = parseImageDetail(part.image_url.detail, partLabel);
|
|
826
|
+
return {
|
|
827
|
+
type: "image_url",
|
|
828
|
+
image_url: {
|
|
829
|
+
url: part.image_url.url,
|
|
830
|
+
...detail
|
|
831
|
+
}
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
if (responsesContent && part.type === "input_image") {
|
|
835
|
+
assertAllowedKeys(part, [
|
|
836
|
+
"type",
|
|
837
|
+
"image_url",
|
|
838
|
+
"detail"
|
|
839
|
+
], partLabel);
|
|
840
|
+
if (typeof part.image_url !== "string") throw new Error(`${partLabel} image_url must be a URL string`);
|
|
841
|
+
const detail = parseImageDetail(part.detail, partLabel);
|
|
842
|
+
return {
|
|
843
|
+
type: "image_url",
|
|
844
|
+
image_url: {
|
|
845
|
+
url: part.image_url,
|
|
846
|
+
...detail
|
|
847
|
+
}
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
throw new Error(`${partLabel} uses an unsupported content type`);
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
function parseImageDetail(value, label) {
|
|
854
|
+
if (value === void 0) return {};
|
|
855
|
+
if (value !== "auto" && value !== "low" && value !== "high") throw new Error(`${label} image detail must be auto, low, or high`);
|
|
856
|
+
return { detail: value };
|
|
857
|
+
}
|
|
858
|
+
function parseChatResponseFormat(value) {
|
|
859
|
+
if (value === void 0) return {};
|
|
860
|
+
if (!isRecord(value) || typeof value.type !== "string") throw new Error("optimizer chat response_format must be an object with a type");
|
|
861
|
+
if (value.type === "text") {
|
|
862
|
+
assertAllowedKeys(value, ["type"], "optimizer chat response_format");
|
|
863
|
+
return {};
|
|
864
|
+
}
|
|
865
|
+
if (value.type === "json_object") {
|
|
866
|
+
assertAllowedKeys(value, ["type"], "optimizer chat response_format");
|
|
867
|
+
return { jsonMode: true };
|
|
868
|
+
}
|
|
869
|
+
if (value.type !== "json_schema") throw new Error("optimizer chat response_format type is unsupported");
|
|
870
|
+
assertAllowedKeys(value, ["type", "json_schema"], "optimizer chat response_format");
|
|
871
|
+
return { jsonSchema: parseJsonSchema(value.json_schema, "optimizer chat response_format") };
|
|
872
|
+
}
|
|
873
|
+
function parseResponsesTextFormat(value) {
|
|
874
|
+
if (value === void 0) return {};
|
|
875
|
+
if (!isRecord(value)) throw new Error("optimizer Responses text must be an object");
|
|
876
|
+
assertAllowedKeys(value, ["format"], "optimizer Responses text");
|
|
877
|
+
if (value.format === void 0) return {};
|
|
878
|
+
if (!isRecord(value.format) || typeof value.format.type !== "string") throw new Error("optimizer Responses text.format must be an object with a type");
|
|
879
|
+
const format = value.format;
|
|
880
|
+
if (format.type === "text") {
|
|
881
|
+
assertAllowedKeys(format, ["type"], "optimizer Responses text.format");
|
|
882
|
+
return {};
|
|
883
|
+
}
|
|
884
|
+
if (format.type === "json_object") {
|
|
885
|
+
assertAllowedKeys(format, ["type"], "optimizer Responses text.format");
|
|
886
|
+
return { jsonMode: true };
|
|
887
|
+
}
|
|
888
|
+
if (format.type !== "json_schema") throw new Error("optimizer Responses text.format type is unsupported");
|
|
889
|
+
return { jsonSchema: parseJsonSchema(format, "optimizer Responses text.format", true) };
|
|
890
|
+
}
|
|
891
|
+
function parseJsonSchema(value, label, flat = false) {
|
|
892
|
+
if (!isRecord(value)) throw new Error(`${label} JSON schema must be an object`);
|
|
893
|
+
assertAllowedKeys(value, [
|
|
894
|
+
"name",
|
|
895
|
+
"schema",
|
|
896
|
+
"strict",
|
|
897
|
+
...flat ? ["type"] : []
|
|
898
|
+
], label);
|
|
899
|
+
if (typeof value.name !== "string" || !value.name.trim()) throw new Error(`${label} JSON schema name must be non-empty`);
|
|
900
|
+
if (!isRecord(value.schema)) throw new Error(`${label} JSON schema body must be an object`);
|
|
901
|
+
if (value.strict !== void 0 && value.strict !== true) throw new Error(`${label} JSON schema strict must be true when supplied`);
|
|
689
902
|
return {
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
includedInTotal: nested !== void 0
|
|
903
|
+
name: value.name,
|
|
904
|
+
schema: freezeJsonSnapshot(value.schema, `${label} schema`)
|
|
693
905
|
};
|
|
694
906
|
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
907
|
+
function assertAllowedKeys(value, allowed, label = "optimizer model request") {
|
|
908
|
+
const allowedSet = new Set(allowed);
|
|
909
|
+
const unknown = Object.keys(value).filter((key) => !allowedSet.has(key));
|
|
910
|
+
if (unknown.length > 0) throw new Error(`${label} contains unsupported fields: ${unknown.join(", ")}`);
|
|
911
|
+
}
|
|
912
|
+
function snapshotChatResponse(value, expectedModel) {
|
|
913
|
+
if (!isRecord(value)) throw new OwnerModelContractError("optimizer model callback response must be an object");
|
|
914
|
+
if (typeof value.content !== "string") throw new OwnerModelContractError("optimizer model callback response content must be a string");
|
|
915
|
+
if (value.model !== expectedModel) throw new OwnerModelContractError(`optimizer model callback response used '${String(value.model)}' instead of '${expectedModel}'`);
|
|
916
|
+
if (!isNonnegativeFiniteNumber(value.durationMs)) throw new OwnerModelContractError("optimizer model callback response durationMs must be finite and non-negative");
|
|
917
|
+
if (value.costUsd !== null && !isNonnegativeFiniteNumber(value.costUsd)) throw new OwnerModelContractError("optimizer model callback response costUsd must be null or finite and non-negative");
|
|
918
|
+
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");
|
|
919
|
+
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");
|
|
920
|
+
if (!isRecord(value.raw)) throw new OwnerModelContractError("optimizer model callback response raw must be an object");
|
|
921
|
+
if (!isRecord(value.usage)) throw new OwnerModelContractError("optimizer model callback response usage must be an object");
|
|
922
|
+
const usage = value.usage;
|
|
923
|
+
for (const field of [
|
|
924
|
+
"promptTokens",
|
|
925
|
+
"completionTokens",
|
|
926
|
+
"totalTokens"
|
|
927
|
+
]) if (!isNonnegativeSafeInteger(usage[field])) throw new OwnerModelContractError(`optimizer model callback response usage.${field} must be a non-negative safe integer`);
|
|
928
|
+
const promptTokens = usage.promptTokens;
|
|
929
|
+
const completionTokens = usage.completionTokens;
|
|
930
|
+
const totalTokens = usage.totalTokens;
|
|
931
|
+
if (totalTokens !== promptTokens + completionTokens) throw new OwnerModelContractError("optimizer model callback response totalTokens must equal promptTokens plus completionTokens");
|
|
932
|
+
if (usage.captured !== void 0 && typeof usage.captured !== "boolean") throw new OwnerModelContractError("optimizer model callback response usage.captured must be a boolean");
|
|
933
|
+
if (usage.cachedPromptTokens !== void 0 && (!isNonnegativeSafeInteger(usage.cachedPromptTokens) || usage.cachedPromptTokens > promptTokens)) throw new OwnerModelContractError("optimizer model callback response cachedPromptTokens must be a subset of promptTokens");
|
|
934
|
+
if (usage.reasoningTokens !== void 0 && (!isNonnegativeSafeInteger(usage.reasoningTokens) || usage.reasoningTokens > completionTokens)) throw new OwnerModelContractError("optimizer model callback response reasoningTokens must be a subset of completionTokens");
|
|
935
|
+
let raw;
|
|
936
|
+
try {
|
|
937
|
+
raw = freezeJsonSnapshot(value.raw, "optimizer model callback canonical response.raw");
|
|
938
|
+
} catch (error) {
|
|
939
|
+
throw new OwnerModelContractError(`optimizer model callback returned invalid raw response evidence: ${toErrorMessage(error)}`);
|
|
704
940
|
}
|
|
705
|
-
return
|
|
941
|
+
return deepFreezeJson({
|
|
942
|
+
content: value.content,
|
|
943
|
+
usage: {
|
|
944
|
+
promptTokens,
|
|
945
|
+
completionTokens,
|
|
946
|
+
totalTokens,
|
|
947
|
+
...usage.captured === void 0 ? {} : { captured: usage.captured },
|
|
948
|
+
...usage.reasoningTokens === void 0 ? {} : { reasoningTokens: usage.reasoningTokens },
|
|
949
|
+
...usage.cachedPromptTokens === void 0 ? {} : { cachedPromptTokens: usage.cachedPromptTokens }
|
|
950
|
+
},
|
|
951
|
+
costUsd: value.costUsd,
|
|
952
|
+
model: value.model,
|
|
953
|
+
durationMs: value.durationMs,
|
|
954
|
+
...value.finishReason === void 0 ? {} : { finishReason: value.finishReason },
|
|
955
|
+
...value.contentEmpty === void 0 ? {} : { contentEmpty: value.contentEmpty },
|
|
956
|
+
raw
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
function encodeCanonicalModelResponse(args) {
|
|
960
|
+
const usage = encodeProtocolUsage(args.path, args.response, args.receipt);
|
|
961
|
+
const finishReason = args.response.finishReason === void 0 ? "stop" : args.response.finishReason;
|
|
962
|
+
const body = args.path === "/v1/chat/completions" ? {
|
|
963
|
+
id: `chatcmpl-${args.callId}`,
|
|
964
|
+
object: "chat.completion",
|
|
965
|
+
created: 0,
|
|
966
|
+
model: args.response.model,
|
|
967
|
+
choices: [{
|
|
968
|
+
index: 0,
|
|
969
|
+
message: {
|
|
970
|
+
role: "assistant",
|
|
971
|
+
content: args.response.content
|
|
972
|
+
},
|
|
973
|
+
finish_reason: finishReason
|
|
974
|
+
}],
|
|
975
|
+
...usage ? { usage } : {}
|
|
976
|
+
} : {
|
|
977
|
+
id: `resp-${args.callId}`,
|
|
978
|
+
object: "response",
|
|
979
|
+
created_at: 0,
|
|
980
|
+
model: args.response.model,
|
|
981
|
+
status: finishReason === "length" ? "incomplete" : "completed",
|
|
982
|
+
error: null,
|
|
983
|
+
incomplete_details: finishReason === "length" ? { reason: "max_output_tokens" } : null,
|
|
984
|
+
output: [{
|
|
985
|
+
id: `msg-${args.callId}`,
|
|
986
|
+
type: "message",
|
|
987
|
+
status: finishReason === "length" ? "incomplete" : "completed",
|
|
988
|
+
role: "assistant",
|
|
989
|
+
content: [{
|
|
990
|
+
type: "output_text",
|
|
991
|
+
text: args.response.content,
|
|
992
|
+
annotations: []
|
|
993
|
+
}]
|
|
994
|
+
}],
|
|
995
|
+
output_text: args.response.content,
|
|
996
|
+
...usage ? { usage } : {}
|
|
997
|
+
};
|
|
998
|
+
const encoded = new TextEncoder().encode(JSON.stringify(body));
|
|
999
|
+
if (encoded.byteLength > args.maxBytes) throw new ProviderResponseTooLargeError();
|
|
1000
|
+
return encoded;
|
|
1001
|
+
}
|
|
1002
|
+
function endpointFormat(path) {
|
|
1003
|
+
return path === "/v1/responses" ? "responses" : "chat-completions";
|
|
1004
|
+
}
|
|
1005
|
+
function encodeProtocolUsage(path, response, receipt) {
|
|
1006
|
+
if (response.usage.captured === false) return void 0;
|
|
1007
|
+
const inputDetails = {
|
|
1008
|
+
...receipt.cachedTokens === void 0 ? {} : { cached_tokens: receipt.cachedTokens },
|
|
1009
|
+
...receipt.cacheWriteTokens === void 0 ? {} : path === "/v1/chat/completions" ? { cache_creation_tokens: receipt.cacheWriteTokens } : { cache_write_tokens: receipt.cacheWriteTokens }
|
|
1010
|
+
};
|
|
1011
|
+
const outputDetails = { ...receipt.reasoningTokens === void 0 ? {} : { reasoning_tokens: receipt.reasoningTokens } };
|
|
1012
|
+
return path === "/v1/chat/completions" ? {
|
|
1013
|
+
prompt_tokens: response.usage.promptTokens,
|
|
1014
|
+
completion_tokens: response.usage.completionTokens,
|
|
1015
|
+
total_tokens: response.usage.totalTokens,
|
|
1016
|
+
...Object.keys(inputDetails).length > 0 ? { prompt_tokens_details: inputDetails } : {},
|
|
1017
|
+
...Object.keys(outputDetails).length > 0 ? { completion_tokens_details: outputDetails } : {},
|
|
1018
|
+
...receipt.actualCostUsd === void 0 ? {} : { cost: receipt.actualCostUsd }
|
|
1019
|
+
} : {
|
|
1020
|
+
input_tokens: response.usage.promptTokens,
|
|
1021
|
+
output_tokens: response.usage.completionTokens,
|
|
1022
|
+
total_tokens: response.usage.totalTokens,
|
|
1023
|
+
...Object.keys(inputDetails).length > 0 ? { input_tokens_details: inputDetails } : {},
|
|
1024
|
+
...Object.keys(outputDetails).length > 0 ? { output_tokens_details: outputDetails } : {},
|
|
1025
|
+
...receipt.actualCostUsd === void 0 ? {} : { cost: receipt.actualCostUsd }
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
1028
|
+
function isNonnegativeFiniteNumber(value) {
|
|
1029
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
1030
|
+
}
|
|
1031
|
+
function isNonnegativeSafeInteger(value) {
|
|
1032
|
+
return Number.isSafeInteger(value) && value >= 0;
|
|
706
1033
|
}
|
|
707
1034
|
function conservativeMaximumUsage(inputTokenUpperBound, outputTokenUpperBound, pricing) {
|
|
708
1035
|
if (pricing === void 0) return {
|
|
709
1036
|
inputTokens: inputTokenUpperBound,
|
|
710
1037
|
outputTokens: outputTokenUpperBound
|
|
711
1038
|
};
|
|
712
|
-
const inputRates = [
|
|
713
|
-
pricing.inputUsdPerMillion,
|
|
714
|
-
pricing.cachedInputUsdPerMillion ?? pricing.inputUsdPerMillion,
|
|
715
|
-
pricing.cacheWriteUsdPerMillion ?? pricing.inputUsdPerMillion
|
|
716
|
-
];
|
|
1039
|
+
const inputRates = [pricing.inputUsdPerMillion, pricing.cachedInputUsdPerMillion ?? pricing.inputUsdPerMillion];
|
|
717
1040
|
const mostExpensiveInputClass = inputRates.indexOf(Math.max(...inputRates));
|
|
718
1041
|
return {
|
|
719
1042
|
inputTokens: mostExpensiveInputClass === 0 ? inputTokenUpperBound : 0,
|
|
720
1043
|
...mostExpensiveInputClass === 1 ? { cachedTokens: inputTokenUpperBound } : {},
|
|
721
|
-
|
|
1044
|
+
cacheWriteTokens: inputTokenUpperBound,
|
|
722
1045
|
outputTokens: outputTokenUpperBound
|
|
723
1046
|
};
|
|
724
1047
|
}
|
|
725
|
-
async function readProviderResponseBody(response, maxBytes) {
|
|
726
|
-
const declaredLength = response.headers.get("content-length");
|
|
727
|
-
if (declaredLength !== null) {
|
|
728
|
-
const parsedLength = Number(declaredLength);
|
|
729
|
-
if (Number.isFinite(parsedLength) && parsedLength > maxBytes) throw new ProviderResponseTooLargeError();
|
|
730
|
-
}
|
|
731
|
-
if (!response.body) return /* @__PURE__ */ new Uint8Array();
|
|
732
|
-
const reader = response.body.getReader();
|
|
733
|
-
const chunks = [];
|
|
734
|
-
let totalBytes = 0;
|
|
735
|
-
try {
|
|
736
|
-
while (true) {
|
|
737
|
-
const next = await reader.read();
|
|
738
|
-
if (next.done) break;
|
|
739
|
-
totalBytes += next.value.byteLength;
|
|
740
|
-
if (totalBytes > maxBytes) {
|
|
741
|
-
await reader.cancel();
|
|
742
|
-
throw new ProviderResponseTooLargeError();
|
|
743
|
-
}
|
|
744
|
-
chunks.push(next.value);
|
|
745
|
-
}
|
|
746
|
-
} finally {
|
|
747
|
-
reader.releaseLock();
|
|
748
|
-
}
|
|
749
|
-
const body = new Uint8Array(totalBytes);
|
|
750
|
-
let offset = 0;
|
|
751
|
-
for (const chunk of chunks) {
|
|
752
|
-
body.set(chunk, offset);
|
|
753
|
-
offset += chunk.byteLength;
|
|
754
|
-
}
|
|
755
|
-
return body;
|
|
756
|
-
}
|
|
757
1048
|
function assertModelProxyConfig(args) {
|
|
758
1049
|
for (const [label, value] of [
|
|
759
1050
|
["model", args.model],
|
|
@@ -763,6 +1054,10 @@ function assertModelProxyConfig(args) {
|
|
|
763
1054
|
if (typeof args.call !== "function") throw new Error("external optimizer model proxy: call must be a function");
|
|
764
1055
|
if (typeof args.callRef !== "string" || !args.callRef.trim() || args.callRef.trim() !== args.callRef) throw new Error("external optimizer model proxy: callRef must be trimmed and non-empty");
|
|
765
1056
|
if (typeof args.recordExecution !== "function") throw new Error("external optimizer model proxy: recordExecution must be a function");
|
|
1057
|
+
if (args.callId !== void 0) {
|
|
1058
|
+
if (typeof args.callId !== "string" || !args.callId.trim() || args.callId !== args.callId.trim()) throw new Error("external optimizer model proxy: callId must be trimmed and non-empty");
|
|
1059
|
+
if (args.budget.maxRequests !== 1) throw new Error("external optimizer model proxy: callId requires budget.maxRequests to be 1");
|
|
1060
|
+
}
|
|
766
1061
|
assertExternalOptimizerModelBudget(args.budget, "external optimizer model proxy: budget");
|
|
767
1062
|
if (args.tags !== void 0) {
|
|
768
1063
|
for (const [key, value] of Object.entries(args.tags)) if (!key.trim() || key.trim() !== key || !value.trim() || value.trim() !== value) throw new Error("external optimizer model proxy: tags must be trimmed and non-empty");
|
|
@@ -845,16 +1140,13 @@ function toError(cause) {
|
|
|
845
1140
|
}
|
|
846
1141
|
//#endregion
|
|
847
1142
|
//#region src/campaign/external-optimizer-subprocess.ts
|
|
848
|
-
const MAX_PROCESS_OUTPUT_CHARS = 64e3;
|
|
849
|
-
const MAX_PROCESS_INPUT_BYTES = 64 * 1024 * 1024;
|
|
850
|
-
const MAX_PROCESS_RESULT_BYTES = 4 * 1024 * 1024;
|
|
851
1143
|
const PROCESS_TERMINATION_GRACE_MS = 5e3;
|
|
852
1144
|
const PROCESS_TERMINATION_POLL_MS = 25;
|
|
853
1145
|
const PROCESS_KILL_WAIT_MS = 1e3;
|
|
854
1146
|
const MAX_TIMER_DELAY_MS = 2147483647;
|
|
855
|
-
const PROCESS_OUTPUT_HEAD_CHARS = MAX_PROCESS_OUTPUT_CHARS / 2;
|
|
856
1147
|
async function runExternalOptimizerProcess(args) {
|
|
857
1148
|
if (!Number.isSafeInteger(args.timeoutMs) || args.timeoutMs <= 0 || args.timeoutMs > MAX_TIMER_DELAY_MS) throw new Error(`${args.label} timeoutMs must be between 1 and ${MAX_TIMER_DELAY_MS}`);
|
|
1149
|
+
const limits = resolveExternalOptimizerProcessLimits(args.runner?.limits);
|
|
858
1150
|
if (process.platform === "win32") throw new Error(`${args.label} requires POSIX process-group cleanup; run the optimizer through WSL or Linux`);
|
|
859
1151
|
throwIfAborted(args.signal, args.label);
|
|
860
1152
|
const dir = await mkdtemp(join(tmpdir(), args.tempPrefix));
|
|
@@ -867,11 +1159,12 @@ async function runExternalOptimizerProcess(args) {
|
|
|
867
1159
|
if (serializedInput === void 0) throw new Error(`${args.label} input must be JSON-serializable`);
|
|
868
1160
|
const inputJson = `${serializedInput}\n`;
|
|
869
1161
|
const inputBytes = Buffer.byteLength(inputJson);
|
|
870
|
-
if (inputBytes >
|
|
1162
|
+
if (inputBytes > limits.maxInputBytes) throw new Error(`${args.label} input exceeds ${limits.maxInputBytes} bytes (${inputBytes})`);
|
|
871
1163
|
await writeFile(inputPath, inputJson);
|
|
872
1164
|
const command = resolveRunnerCommand(args.runner?.command ?? "python");
|
|
873
1165
|
const commandArgs = [
|
|
874
1166
|
...args.runner?.args ?? ["-m", args.module],
|
|
1167
|
+
...args.additionalArgs ?? [],
|
|
875
1168
|
"--input",
|
|
876
1169
|
inputPath,
|
|
877
1170
|
"--output",
|
|
@@ -884,10 +1177,11 @@ async function runExternalOptimizerProcess(args) {
|
|
|
884
1177
|
cwd: dir,
|
|
885
1178
|
env: args.runner?.env,
|
|
886
1179
|
timeoutMs: args.timeoutMs,
|
|
1180
|
+
maxOutputChars: limits.maxOutputChars,
|
|
887
1181
|
signal: args.signal
|
|
888
1182
|
});
|
|
889
1183
|
throwIfAborted(args.signal, args.label);
|
|
890
|
-
const raw = JSON.parse(await readBoundedTextFile(outputPath,
|
|
1184
|
+
const raw = JSON.parse(await readBoundedTextFile(outputPath, limits.maxResultBytes, `${args.label} output`));
|
|
891
1185
|
throwIfAborted(args.signal, args.label);
|
|
892
1186
|
if (!isRecord(raw)) throw new Error(`${args.label} output must be a JSON object`);
|
|
893
1187
|
return raw;
|
|
@@ -964,10 +1258,10 @@ function runProcess(args) {
|
|
|
964
1258
|
args.signal?.addEventListener("abort", onAbort, { once: true });
|
|
965
1259
|
if (args.signal?.aborted) onAbort();
|
|
966
1260
|
child.stdout.on("data", (chunk) => {
|
|
967
|
-
appendProcessOutput(stdout, chunk);
|
|
1261
|
+
appendProcessOutput(stdout, chunk, args.maxOutputChars);
|
|
968
1262
|
});
|
|
969
1263
|
child.stderr.on("data", (chunk) => {
|
|
970
|
-
appendProcessOutput(stderr, chunk);
|
|
1264
|
+
appendProcessOutput(stderr, chunk, args.maxOutputChars);
|
|
971
1265
|
});
|
|
972
1266
|
child.on("error", (error) => {
|
|
973
1267
|
finish(/* @__PURE__ */ new Error(`${args.label} could not start: ${error.message}`));
|
|
@@ -1059,21 +1353,23 @@ function createProcessOutputCapture() {
|
|
|
1059
1353
|
totalChars: 0
|
|
1060
1354
|
};
|
|
1061
1355
|
}
|
|
1062
|
-
function appendProcessOutput(capture, chunk) {
|
|
1356
|
+
function appendProcessOutput(capture, chunk, maxOutputChars) {
|
|
1063
1357
|
const text = chunk.toString();
|
|
1064
1358
|
capture.totalChars += text.length;
|
|
1359
|
+
const headChars = Math.floor(maxOutputChars / 2);
|
|
1360
|
+
const tailChars = maxOutputChars - headChars;
|
|
1065
1361
|
if (capture.full !== void 0) {
|
|
1066
1362
|
const combined = `${capture.full}${text}`;
|
|
1067
|
-
if (combined.length <=
|
|
1363
|
+
if (combined.length <= maxOutputChars) {
|
|
1068
1364
|
capture.full = combined;
|
|
1069
1365
|
return;
|
|
1070
1366
|
}
|
|
1071
|
-
capture.head = combined.slice(0,
|
|
1072
|
-
capture.tail = combined.slice(-
|
|
1367
|
+
capture.head = combined.slice(0, headChars);
|
|
1368
|
+
capture.tail = combined.slice(-tailChars);
|
|
1073
1369
|
capture.full = void 0;
|
|
1074
1370
|
return;
|
|
1075
1371
|
}
|
|
1076
|
-
capture.tail = `${capture.tail}${text}`.slice(-
|
|
1372
|
+
capture.tail = `${capture.tail}${text}`.slice(-tailChars);
|
|
1077
1373
|
}
|
|
1078
1374
|
function summarizeProcessOutput(capture, max = 4e3) {
|
|
1079
1375
|
const compact = (capture.full ?? `${capture.head}\n...[${capture.totalChars - capture.head.length - capture.tail.length} chars omitted]...\n${capture.tail}`).trim().replace(/\s+/g, " ");
|
|
@@ -1083,6 +1379,242 @@ function summarizeProcessOutput(capture, max = 4e3) {
|
|
|
1083
1379
|
return `${compact.slice(0, headChars)}...[${compact.length - max} chars omitted]...${compact.slice(-tailChars)}`;
|
|
1084
1380
|
}
|
|
1085
1381
|
//#endregion
|
|
1382
|
+
//#region src/campaign/external-optimizer-callback.ts
|
|
1383
|
+
async function startExternalOptimizerCallback(args) {
|
|
1384
|
+
assertCallbackConfig(args);
|
|
1385
|
+
const limits = resolveExternalOptimizerCallbackLimits(args.limits);
|
|
1386
|
+
args.signal?.throwIfAborted();
|
|
1387
|
+
let evaluations = 0;
|
|
1388
|
+
let accepting = true;
|
|
1389
|
+
let closePromise;
|
|
1390
|
+
const activeControllers = /* @__PURE__ */ new Set();
|
|
1391
|
+
const activeHandlers = /* @__PURE__ */ new Set();
|
|
1392
|
+
const proposedCandidateHashes = /* @__PURE__ */ new Set();
|
|
1393
|
+
let observationSequence = 0;
|
|
1394
|
+
const server = createServer((request, response) => {
|
|
1395
|
+
if (!accepting) {
|
|
1396
|
+
sendJsonIfOpen(response, 503, { error: "external optimizer callback is closing" });
|
|
1397
|
+
return;
|
|
1398
|
+
}
|
|
1399
|
+
const controller = new AbortController();
|
|
1400
|
+
const abortRequest = () => {
|
|
1401
|
+
request.destroy();
|
|
1402
|
+
response.destroy();
|
|
1403
|
+
};
|
|
1404
|
+
activeControllers.add(controller);
|
|
1405
|
+
controller.signal.addEventListener("abort", abortRequest, { once: true });
|
|
1406
|
+
let handler;
|
|
1407
|
+
handler = handleCallback(request, response, controller.signal, args, limits, () => {
|
|
1408
|
+
const accepted = args.acceptEvaluation ? args.acceptEvaluation() : evaluations + 1;
|
|
1409
|
+
if (accepted === void 0) return void 0;
|
|
1410
|
+
if (!Number.isSafeInteger(accepted) || accepted <= 0) throw new Error("external optimizer callback: invalid accepted evaluation count");
|
|
1411
|
+
if (accepted > args.maxEvaluations) return void 0;
|
|
1412
|
+
evaluations += 1;
|
|
1413
|
+
return accepted;
|
|
1414
|
+
}, (observation) => {
|
|
1415
|
+
if (!args.observe) return;
|
|
1416
|
+
args.observe({
|
|
1417
|
+
...observation,
|
|
1418
|
+
sequence: ++observationSequence
|
|
1419
|
+
});
|
|
1420
|
+
}, proposedCandidateHashes).finally(() => {
|
|
1421
|
+
controller.signal.removeEventListener("abort", abortRequest);
|
|
1422
|
+
activeControllers.delete(controller);
|
|
1423
|
+
activeHandlers.delete(handler);
|
|
1424
|
+
});
|
|
1425
|
+
activeHandlers.add(handler);
|
|
1426
|
+
handler.catch(() => void 0);
|
|
1427
|
+
});
|
|
1428
|
+
const port = await listenLocal(server);
|
|
1429
|
+
const close = () => {
|
|
1430
|
+
closePromise ??= closeCallbackServer();
|
|
1431
|
+
return closePromise;
|
|
1432
|
+
};
|
|
1433
|
+
const onAbort = () => {
|
|
1434
|
+
close().catch(() => void 0);
|
|
1435
|
+
};
|
|
1436
|
+
args.signal?.addEventListener("abort", onAbort, { once: true });
|
|
1437
|
+
if (args.signal?.aborted) onAbort();
|
|
1438
|
+
return {
|
|
1439
|
+
url: `http://127.0.0.1:${port}/evaluate`,
|
|
1440
|
+
token: args.token,
|
|
1441
|
+
evaluations: () => evaluations,
|
|
1442
|
+
close
|
|
1443
|
+
};
|
|
1444
|
+
async function closeCallbackServer() {
|
|
1445
|
+
args.signal?.removeEventListener("abort", onAbort);
|
|
1446
|
+
accepting = false;
|
|
1447
|
+
const closingServer = closeServer(server);
|
|
1448
|
+
server.closeIdleConnections?.();
|
|
1449
|
+
for (const controller of activeControllers) controller.abort();
|
|
1450
|
+
const [serverResult] = await Promise.allSettled([closingServer, waitForActiveHandlers(activeHandlers)]);
|
|
1451
|
+
if (activeControllers.size !== 0 || activeHandlers.size !== 0) throw new Error("external optimizer callback closed with active request work");
|
|
1452
|
+
if (serverResult?.status === "rejected") throw serverResult.reason;
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
async function handleCallback(request, response, signal, args, limits, nextEvaluation, observe, proposedCandidateHashes) {
|
|
1456
|
+
try {
|
|
1457
|
+
if (request.method !== "POST" || request.url !== "/evaluate") {
|
|
1458
|
+
sendJsonIfOpen(response, 404, { error: "not found" });
|
|
1459
|
+
return;
|
|
1460
|
+
}
|
|
1461
|
+
if (request.headers.authorization !== `Bearer ${args.token}`) {
|
|
1462
|
+
sendJsonIfOpen(response, 401, { error: "unauthorized" });
|
|
1463
|
+
return;
|
|
1464
|
+
}
|
|
1465
|
+
let body;
|
|
1466
|
+
try {
|
|
1467
|
+
body = await readJson(request, limits.maxRequestBytes);
|
|
1468
|
+
} catch {
|
|
1469
|
+
observe({
|
|
1470
|
+
kind: "refusal",
|
|
1471
|
+
reason: "invalid-request"
|
|
1472
|
+
});
|
|
1473
|
+
sendJsonIfOpen(response, 400, { error: "request body must be bounded valid JSON" });
|
|
1474
|
+
return;
|
|
1475
|
+
}
|
|
1476
|
+
if (!isRecord(body) || !isExternalTextCandidate(body.candidate) || typeof body.exampleId !== "string") {
|
|
1477
|
+
observe({
|
|
1478
|
+
kind: "refusal",
|
|
1479
|
+
reason: "invalid-request"
|
|
1480
|
+
});
|
|
1481
|
+
sendJsonIfOpen(response, 400, { error: "candidate and exampleId are required strings" });
|
|
1482
|
+
return;
|
|
1483
|
+
}
|
|
1484
|
+
const candidate = cloneCandidate(body.candidate);
|
|
1485
|
+
const candidateHash = contentHash({
|
|
1486
|
+
kind: "external-text-candidate",
|
|
1487
|
+
candidate
|
|
1488
|
+
});
|
|
1489
|
+
if (!proposedCandidateHashes.has(candidateHash)) {
|
|
1490
|
+
observe({
|
|
1491
|
+
kind: "proposal",
|
|
1492
|
+
candidate,
|
|
1493
|
+
candidateHash
|
|
1494
|
+
});
|
|
1495
|
+
proposedCandidateHashes.add(candidateHash);
|
|
1496
|
+
}
|
|
1497
|
+
const count = nextEvaluation();
|
|
1498
|
+
if (count === void 0) {
|
|
1499
|
+
observe({
|
|
1500
|
+
kind: "refusal",
|
|
1501
|
+
reason: "evaluation-limit",
|
|
1502
|
+
candidate,
|
|
1503
|
+
candidateHash,
|
|
1504
|
+
exampleId: body.exampleId
|
|
1505
|
+
});
|
|
1506
|
+
sendJsonIfOpen(response, 429, { error: "evaluation limit reached" });
|
|
1507
|
+
return;
|
|
1508
|
+
}
|
|
1509
|
+
let result;
|
|
1510
|
+
try {
|
|
1511
|
+
result = await args.evaluate({
|
|
1512
|
+
candidate,
|
|
1513
|
+
exampleId: body.exampleId
|
|
1514
|
+
}, signal);
|
|
1515
|
+
} catch {
|
|
1516
|
+
observe({
|
|
1517
|
+
kind: "refusal",
|
|
1518
|
+
reason: "evaluation-failed",
|
|
1519
|
+
candidate,
|
|
1520
|
+
candidateHash,
|
|
1521
|
+
exampleId: body.exampleId
|
|
1522
|
+
});
|
|
1523
|
+
sendJsonIfOpen(response, 500, { error: "evaluation failed" });
|
|
1524
|
+
return;
|
|
1525
|
+
}
|
|
1526
|
+
let encoded;
|
|
1527
|
+
try {
|
|
1528
|
+
encoded = encodeJsonResponse(result);
|
|
1529
|
+
} catch {
|
|
1530
|
+
observe({
|
|
1531
|
+
kind: "refusal",
|
|
1532
|
+
reason: "evaluation-failed",
|
|
1533
|
+
candidate,
|
|
1534
|
+
candidateHash,
|
|
1535
|
+
exampleId: body.exampleId
|
|
1536
|
+
});
|
|
1537
|
+
sendJsonIfOpen(response, 500, { error: "evaluation response must be valid JSON" });
|
|
1538
|
+
return;
|
|
1539
|
+
}
|
|
1540
|
+
if (Buffer.byteLength(encoded) > limits.maxResponseBytes) {
|
|
1541
|
+
observe({
|
|
1542
|
+
kind: "refusal",
|
|
1543
|
+
reason: "evaluation-failed",
|
|
1544
|
+
candidate,
|
|
1545
|
+
candidateHash,
|
|
1546
|
+
exampleId: body.exampleId
|
|
1547
|
+
});
|
|
1548
|
+
sendJsonIfOpen(response, 413, { error: "evaluation response too large" });
|
|
1549
|
+
return;
|
|
1550
|
+
}
|
|
1551
|
+
observe({
|
|
1552
|
+
kind: "evaluation",
|
|
1553
|
+
candidate,
|
|
1554
|
+
candidateHash,
|
|
1555
|
+
exampleId: body.exampleId,
|
|
1556
|
+
evaluationNumber: count,
|
|
1557
|
+
response: structuredClone(result)
|
|
1558
|
+
});
|
|
1559
|
+
sendEncodedJsonIfOpen(response, 200, encoded);
|
|
1560
|
+
} catch {
|
|
1561
|
+
sendJsonIfOpen(response, 500, { error: "evaluation failed" });
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
async function waitForActiveHandlers(activeHandlers) {
|
|
1565
|
+
while (activeHandlers.size > 0) await Promise.allSettled([...activeHandlers]);
|
|
1566
|
+
}
|
|
1567
|
+
function sendJsonIfOpen(response, status, body) {
|
|
1568
|
+
if (response.destroyed || response.writableEnded) return;
|
|
1569
|
+
sendJson(response, status, body);
|
|
1570
|
+
}
|
|
1571
|
+
function readJson(request, maxRequestBytes) {
|
|
1572
|
+
return new Promise((resolvePromise, reject) => {
|
|
1573
|
+
let size = 0;
|
|
1574
|
+
const chunks = [];
|
|
1575
|
+
request.on("data", (chunk) => {
|
|
1576
|
+
size += chunk.length;
|
|
1577
|
+
if (size > maxRequestBytes) {
|
|
1578
|
+
reject(/* @__PURE__ */ new Error("callback body too large"));
|
|
1579
|
+
request.destroy();
|
|
1580
|
+
return;
|
|
1581
|
+
}
|
|
1582
|
+
chunks.push(chunk);
|
|
1583
|
+
});
|
|
1584
|
+
request.on("error", reject);
|
|
1585
|
+
request.on("end", () => {
|
|
1586
|
+
try {
|
|
1587
|
+
resolvePromise(JSON.parse(Buffer.concat(chunks).toString("utf8")));
|
|
1588
|
+
} catch (error) {
|
|
1589
|
+
reject(error);
|
|
1590
|
+
}
|
|
1591
|
+
});
|
|
1592
|
+
});
|
|
1593
|
+
}
|
|
1594
|
+
function encodeJsonResponse(value) {
|
|
1595
|
+
const encoded = JSON.stringify(value);
|
|
1596
|
+
if (encoded === void 0) throw new Error("evaluation response must be JSON-serializable");
|
|
1597
|
+
return encoded;
|
|
1598
|
+
}
|
|
1599
|
+
function sendEncodedJsonIfOpen(response, status, encoded) {
|
|
1600
|
+
if (response.destroyed || response.writableEnded) return;
|
|
1601
|
+
response.writeHead(status, {
|
|
1602
|
+
"content-type": "application/json; charset=utf-8",
|
|
1603
|
+
"content-length": String(Buffer.byteLength(encoded))
|
|
1604
|
+
});
|
|
1605
|
+
response.end(encoded);
|
|
1606
|
+
}
|
|
1607
|
+
function assertCallbackConfig(args) {
|
|
1608
|
+
if (typeof args.token !== "string" || !args.token.trim()) throw new Error("external optimizer callback: token must be non-empty");
|
|
1609
|
+
if (!Number.isSafeInteger(args.maxEvaluations) || args.maxEvaluations <= 0) throw new Error("external optimizer callback: maxEvaluations must be a positive safe integer");
|
|
1610
|
+
if (args.acceptEvaluation !== void 0 && typeof args.acceptEvaluation !== "function") throw new Error("external optimizer callback: acceptEvaluation must be a function");
|
|
1611
|
+
if (typeof args.evaluate !== "function") throw new Error("external optimizer callback: evaluate must be a function");
|
|
1612
|
+
if (args.observe !== void 0 && typeof args.observe !== "function") throw new Error("external optimizer callback: observe must be a function");
|
|
1613
|
+
}
|
|
1614
|
+
function cloneCandidate(candidate) {
|
|
1615
|
+
return typeof candidate === "string" ? candidate : { ...candidate };
|
|
1616
|
+
}
|
|
1617
|
+
//#endregion
|
|
1086
1618
|
//#region src/campaign/search-ledger-errors.ts
|
|
1087
1619
|
/** Base error for invalid search-ledger input or operations. */
|
|
1088
1620
|
var SearchLedgerError = class extends ValidationError {};
|
|
@@ -1262,6 +1794,6 @@ function acquireSingleRunLock(opts) {
|
|
|
1262
1794
|
return { release };
|
|
1263
1795
|
}
|
|
1264
1796
|
//#endregion
|
|
1265
|
-
export {
|
|
1797
|
+
export { removeCredentialEnvironment as A, DEFAULT_EXTERNAL_OPTIMIZER_PROCESS_LIMITS as C, isCandidateText as D, assertNoCredentialValues as E, resolveExternalOptimizerProcessLimits as M, safePathComponent as N, isExternalTextCandidate as O, DEFAULT_EXTERNAL_OPTIMIZER_CALLBACK_LIMITS as S, assertJsonValue as T, cachedJudge as _, SEARCH_LEDGER_FILE_CONTEXT as a, fileVerdictCache as b, SearchLedgerIntegrityError as c, closeExternalOptimizerResources as d, runWithCleanup as f, sendJson as g, listenLocal as h, inMemoryCampaignStorage as i, resolveExternalOptimizerCallbackLimits as j, isRecord as k, startExternalOptimizerCallback as l, closeServer as m, createRunCostLedger as n, SearchLedgerConflictError as o, startExternalOptimizerModelProxy as p, fsCampaignStorage as r, SearchLedgerError as s, acquireSingleRunLock as t, runExternalOptimizerProcess as u, canonicalJson as v, assertExternalOptimizerModelBudget as w, inMemoryVerdictCache as x, contentHash as y };
|
|
1266
1798
|
|
|
1267
|
-
//# sourceMappingURL=single-run-lock-
|
|
1799
|
+
//# sourceMappingURL=single-run-lock-D5iN0Xzb.js.map
|