@tangle-network/agent-eval 0.143.0 → 0.144.0
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 +22 -0
- package/README.md +21 -8
- package/dist/analyst/index.d.ts +4 -4
- package/dist/analyst/index.d.ts.map +1 -1
- package/dist/analyst/index.js +3 -3
- package/dist/{benchmark-command-CSddvlq7.js → benchmark-command-4c7N_rlw.js} +7 -6
- package/dist/benchmark-command-4c7N_rlw.js.map +1 -0
- package/dist/benchmarks/index.d.ts +1 -1
- package/dist/benchmarks/index.js +1 -1
- package/dist/{benchmarks-Dg0B56tK.js → benchmarks-05UGZ8sZ.js} +4 -4
- package/dist/{benchmarks-Dg0B56tK.js.map → benchmarks-05UGZ8sZ.js.map} +1 -1
- package/dist/campaign/index.d.ts +4 -3
- package/dist/campaign/index.js +4 -4
- package/dist/{campaign-C7ZGiZYG.js → campaign-BKOtvRAB.js} +11 -9
- package/dist/campaign-BKOtvRAB.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/contract/index.d.ts +1 -1
- package/dist/contract/index.js +3 -3
- package/dist/{default-registry-BupFSXSN.js → default-registry-DfHJEwYh.js} +2 -2
- package/dist/{default-registry-BupFSXSN.js.map → default-registry-DfHJEwYh.js.map} +1 -1
- package/dist/{dspy-rlm-engine-CJ4sN8Wx.js → dspy-rlm-engine-CBFwlyaY.js} +89 -7
- package/dist/dspy-rlm-engine-CBFwlyaY.js.map +1 -0
- package/dist/external-optimizer-contracts-iK0yu4AR.d.ts +128 -0
- package/dist/external-optimizer-contracts-iK0yu4AR.d.ts.map +1 -0
- package/dist/{index-BKjcUH13.d.ts → index-BZUe-ODI.d.ts} +41 -5
- package/dist/index-BZUe-ODI.d.ts.map +1 -0
- package/dist/{index-DY83M1WL.d.ts → index-DgbFl4cv.d.ts} +9 -8
- package/dist/{index-DY83M1WL.d.ts.map → index-DgbFl4cv.d.ts.map} +1 -1
- package/dist/{index-CaOjqCli.d.ts → index-DtMpBKVF.d.ts} +3 -3
- package/dist/{index-CaOjqCli.d.ts.map → index-DtMpBKVF.d.ts.map} +1 -1
- package/dist/index.d.ts +7 -7
- package/dist/index.js +9 -8
- package/dist/index.js.map +1 -1
- package/dist/{integrity-hruKe6oe.js → integrity-DY6tIbl0.js} +48 -18
- package/dist/integrity-DY6tIbl0.js.map +1 -0
- package/dist/openapi.json +1 -1
- package/dist/{single-run-lock-Do-8vBIJ.js → single-run-lock-t1si1ob7.js} +370 -152
- package/dist/single-run-lock-t1si1ob7.js.map +1 -0
- package/dist/{skill-usage-CRqaHi0R.d.ts → skill-usage-BiVEU0QY.d.ts} +3 -3
- package/dist/{skill-usage-CRqaHi0R.d.ts.map → skill-usage-BiVEU0QY.d.ts.map} +1 -1
- package/dist/{skillopt-optimization-method-CwRvi-rL.d.ts → skillopt-optimization-method-B7o01OdX.d.ts} +43 -9
- package/dist/skillopt-optimization-method-B7o01OdX.d.ts.map +1 -0
- package/dist/{skillopt-optimization-method-Di8Xkalt.js → skillopt-optimization-method-Ds8J1_K8.js} +289 -222
- package/dist/skillopt-optimization-method-Ds8J1_K8.js.map +1 -0
- package/dist/supervisor-run/index.d.ts +3 -3
- package/dist/supervisor-run/index.js +3 -3
- package/dist/{supervisor-run-BEns49Nx.js → supervisor-run-DiyQVczd.js} +301 -25
- package/dist/supervisor-run-DiyQVczd.js.map +1 -0
- package/dist/{types-Dea6tiVI.d.ts → types-yLK8gXE9.d.ts} +15 -2
- package/dist/types-yLK8gXE9.d.ts.map +1 -0
- package/docs/campaign-proposers.md +11 -7
- package/package.json +1 -1
- package/dist/benchmark-command-CSddvlq7.js.map +0 -1
- package/dist/campaign-C7ZGiZYG.js.map +0 -1
- package/dist/dspy-rlm-engine-CJ4sN8Wx.js.map +0 -1
- package/dist/external-optimizer-contracts-nb7c_WAR.d.ts +0 -42
- package/dist/external-optimizer-contracts-nb7c_WAR.d.ts.map +0 -1
- package/dist/index-BKjcUH13.d.ts.map +0 -1
- package/dist/integrity-hruKe6oe.js.map +0 -1
- package/dist/single-run-lock-Do-8vBIJ.js.map +0 -1
- package/dist/skillopt-optimization-method-CwRvi-rL.d.ts.map +0 -1
- package/dist/skillopt-optimization-method-Di8Xkalt.js.map +0 -1
- package/dist/supervisor-run-BEns49Nx.js.map +0 -1
- package/dist/types-Dea6tiVI.d.ts.map +0 -1
|
@@ -2,8 +2,9 @@ import { c as ValidationError } from "./errors-D-LKuDhb.js";
|
|
|
2
2
|
import { c as costForTokenPricing, i as CostLedger } from "./cost-ledger-DMFxsLKr.js";
|
|
3
3
|
import { a as appendLedgerLine, d as tryAcquireAtomicFileLock, o as tryWithLedgerFileLock, u as probeAtomicFileLock } from "./ledger-core-Dxz0Rkwa.js";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
|
+
import { appendFileSync, existsSync, readFileSync } from "node:fs";
|
|
5
6
|
import { isAbsolute, join, resolve } from "node:path";
|
|
6
|
-
import { randomBytes } from "node:crypto";
|
|
7
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
7
8
|
import { createServer } from "node:http";
|
|
8
9
|
import { spawn } from "node:child_process";
|
|
9
10
|
import { lstat, mkdtemp, open, rm, writeFile } from "node:fs/promises";
|
|
@@ -64,12 +65,14 @@ function assertExternalOptimizerModelBudget(value, label) {
|
|
|
64
65
|
["maxResponseBytes", value.maxResponseBytes],
|
|
65
66
|
["maxOutputTokensPerRequest", value.maxOutputTokensPerRequest]
|
|
66
67
|
]) if (!Number.isSafeInteger(entry) || entry <= 0) throw new Error(`${label}.${field} must be a positive safe integer`);
|
|
67
|
-
if (!Number.
|
|
68
|
+
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
|
+
if (value.maxCostUsd !== void 0 && (!Number.isFinite(value.maxCostUsd) || value.maxCostUsd <= 0)) throw new Error(`${label}.maxCostUsd must be positive and finite when supplied`);
|
|
68
70
|
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}`);
|
|
69
|
-
costForTokenPricing(value.pricing, {
|
|
71
|
+
if (value.pricing !== void 0) costForTokenPricing(value.pricing, {
|
|
70
72
|
inputTokens: 1,
|
|
71
73
|
outputTokens: 1
|
|
72
74
|
});
|
|
75
|
+
if (value.maxCostUsd !== void 0 && value.pricing === void 0) throw new Error(`${label}.pricing is required when maxCostUsd is supplied`);
|
|
73
76
|
}
|
|
74
77
|
function safePathComponent(value) {
|
|
75
78
|
return value.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
@@ -92,6 +95,160 @@ function isRecord(value) {
|
|
|
92
95
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
93
96
|
}
|
|
94
97
|
//#endregion
|
|
98
|
+
//#region src/verdict-cache.ts
|
|
99
|
+
/**
|
|
100
|
+
* Content-addressed judge-verdict caching.
|
|
101
|
+
*
|
|
102
|
+
* LAW: cache JUDGE VERDICTS only — judging the same artifact with the same
|
|
103
|
+
* judge+rubric is pure. NEVER cache agent rollouts. (A router that cached
|
|
104
|
+
* identical fanout prompts silently destroyed best-of-N diversity; rollout
|
|
105
|
+
* caching reintroduces that failure class. Judging has no diversity to
|
|
106
|
+
* destroy — same artifact + same rubric ⇒ same verdict is the desired
|
|
107
|
+
* property, not a bug.)
|
|
108
|
+
*
|
|
109
|
+
* The cache key is a sha-256 over the canonical JSON of everything that can
|
|
110
|
+
* change a verdict: the artifact content, the scenario id, the judge name,
|
|
111
|
+
* the full dimension list (key + description — the description IS the rubric
|
|
112
|
+
* text shown to the judge), and a caller-supplied `judgeVersion`.
|
|
113
|
+
* `judgeVersion` is REQUIRED: a judge whose prompt/model/ensemble changes
|
|
114
|
+
* without a version bump would otherwise silently serve stale verdicts.
|
|
115
|
+
*
|
|
116
|
+
* Strict canonicalization (`canonicalJson`) throws on undefined / function /
|
|
117
|
+
* symbol / non-finite numbers — an artifact that cannot be unambiguously
|
|
118
|
+
* serialized cannot be content-addressed, and coercing it would let two
|
|
119
|
+
* different artifacts collide on one key.
|
|
120
|
+
*/
|
|
121
|
+
function canonicalizeAt(value, path) {
|
|
122
|
+
if (value === null) return "null";
|
|
123
|
+
switch (typeof value) {
|
|
124
|
+
case "boolean": return value ? "true" : "false";
|
|
125
|
+
case "number":
|
|
126
|
+
if (!Number.isFinite(value)) throw new Error(`canonicalJson: non-finite number (${value}) at ${path} — ambiguity is an error, not a coercion`);
|
|
127
|
+
return JSON.stringify(value);
|
|
128
|
+
case "string": return JSON.stringify(value);
|
|
129
|
+
case "undefined":
|
|
130
|
+
case "function":
|
|
131
|
+
case "symbol": throw new Error(`canonicalJson: ${typeof value} at ${path} — ambiguity is an error, not a coercion`);
|
|
132
|
+
case "bigint": throw new Error(`canonicalJson: bigint at ${path} — not representable in JSON`);
|
|
133
|
+
case "object": break;
|
|
134
|
+
}
|
|
135
|
+
const obj = value;
|
|
136
|
+
if (typeof obj.toJSON === "function") return canonicalizeAt(obj.toJSON(), path);
|
|
137
|
+
if (Array.isArray(obj)) return `[${obj.map((item, i) => canonicalizeAt(item, `${path}[${i}]`)).join(",")}]`;
|
|
138
|
+
if (obj instanceof Map || obj instanceof Set) throw new Error(`canonicalJson: ${obj instanceof Map ? "Map" : "Set"} at ${path} — would serialize as '{}'; convert to a plain object/array first`);
|
|
139
|
+
return `{${Object.keys(obj).sort().map((k) => `${JSON.stringify(k)}:${canonicalizeAt(obj[k], `${path}.${k}`)}`).join(",")}}`;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Stable JSON stringify: object keys sorted recursively, so two semantically
|
|
143
|
+
* equal values produce byte-identical output regardless of key insertion
|
|
144
|
+
* order. Throws on undefined / function / symbol / NaN / ±Infinity / bigint /
|
|
145
|
+
* Map / Set — anything JSON.stringify would coerce or drop silently.
|
|
146
|
+
*
|
|
147
|
+
* Distinct from `pre-registration.ts`'s `canonicalize`/`hashJson`, which are
|
|
148
|
+
* permissive (coercion allowed) and async (web-crypto). Use THIS pair when a
|
|
149
|
+
* hash collision or silent coercion would corrupt a cache key or attestation.
|
|
150
|
+
*/
|
|
151
|
+
function canonicalJson(value) {
|
|
152
|
+
return canonicalizeAt(value, "$");
|
|
153
|
+
}
|
|
154
|
+
/** Hex sha-256 over `canonicalJson(value)`. The content address used by the
|
|
155
|
+
* verdict cache and report attestation. */
|
|
156
|
+
function contentHash(value) {
|
|
157
|
+
return createHash("sha256").update(canonicalJson(value)).digest("hex");
|
|
158
|
+
}
|
|
159
|
+
/** Process-local Map-backed store. */
|
|
160
|
+
function inMemoryVerdictCache() {
|
|
161
|
+
const entries = /* @__PURE__ */ new Map();
|
|
162
|
+
return {
|
|
163
|
+
get: (key) => entries.get(key),
|
|
164
|
+
set: (key, score) => {
|
|
165
|
+
entries.set(key, score);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function parseCacheLine(line, path, lineNo) {
|
|
170
|
+
let parsed;
|
|
171
|
+
try {
|
|
172
|
+
parsed = JSON.parse(line);
|
|
173
|
+
} catch (err) {
|
|
174
|
+
throw new Error(`fileVerdictCache: corrupt JSONL at ${path}:${lineNo} — ${err instanceof Error ? err.message : String(err)}`);
|
|
175
|
+
}
|
|
176
|
+
const rec = parsed;
|
|
177
|
+
if (typeof rec !== "object" || rec === null || typeof rec.key !== "string" || typeof rec.score !== "object" || rec.score === null || typeof rec.score.composite !== "number" || typeof rec.score.dimensions !== "object") throw new Error(`fileVerdictCache: invalid record shape at ${path}:${lineNo} — expected {key, score:{dimensions, composite, notes}}`);
|
|
178
|
+
return rec;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* JSONL-file-backed store: the full file is loaded into an in-memory index at
|
|
182
|
+
* construction; every `set` appends one line synchronously (durable before
|
|
183
|
+
* the verdict is returned). A corrupt or malformed line throws at load with
|
|
184
|
+
* file:line — a skipped line would silently re-judge (cost) or, worse, mask
|
|
185
|
+
* a half-written file that needs operator attention.
|
|
186
|
+
*/
|
|
187
|
+
function fileVerdictCache(path) {
|
|
188
|
+
const entries = /* @__PURE__ */ new Map();
|
|
189
|
+
if (existsSync(path)) {
|
|
190
|
+
const lines = readFileSync(path, "utf8").split("\n");
|
|
191
|
+
for (let i = 0; i < lines.length; i++) {
|
|
192
|
+
const line = lines[i];
|
|
193
|
+
if (line === void 0 || line.trim() === "") continue;
|
|
194
|
+
const rec = parseCacheLine(line, path, i + 1);
|
|
195
|
+
entries.set(rec.key, rec.score);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
get: (key) => entries.get(key),
|
|
200
|
+
set: (key, score) => {
|
|
201
|
+
appendFileSync(path, `${JSON.stringify({
|
|
202
|
+
key,
|
|
203
|
+
score
|
|
204
|
+
})}\n`, "utf8");
|
|
205
|
+
entries.set(key, score);
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Wrap a `JudgeConfig` so repeat judgments of the same artifact are served
|
|
211
|
+
* from the store instead of re-invoking `score()`. The wrapper is generic
|
|
212
|
+
* over the judge's own type parameters and preserves `appliesTo` — it is a
|
|
213
|
+
* drop-in replacement anywhere a `JudgeConfig` is accepted.
|
|
214
|
+
*
|
|
215
|
+
* A judge that throws is NOT cached: the error propagates and the next
|
|
216
|
+
* attempt re-judges (caching a failure would pin a transient outage forever).
|
|
217
|
+
*/
|
|
218
|
+
function cachedJudge(judge, store, options) {
|
|
219
|
+
if (typeof options.judgeVersion !== "string" || options.judgeVersion.trim() === "") throw new Error("cachedJudge: judgeVersion is required and must be a non-empty string");
|
|
220
|
+
const stats = {
|
|
221
|
+
hits: 0,
|
|
222
|
+
misses: 0
|
|
223
|
+
};
|
|
224
|
+
const wrapped = {
|
|
225
|
+
name: judge.name,
|
|
226
|
+
dimensions: judge.dimensions,
|
|
227
|
+
judgeVersion: options.judgeVersion,
|
|
228
|
+
async score(input) {
|
|
229
|
+
const key = contentHash({
|
|
230
|
+
artifact: canonicalJson(input.artifact),
|
|
231
|
+
scenarioId: input.scenario.id,
|
|
232
|
+
judgeName: judge.name,
|
|
233
|
+
dimensions: judge.dimensions,
|
|
234
|
+
judgeVersion: options.judgeVersion
|
|
235
|
+
});
|
|
236
|
+
const cached = await store.get(key);
|
|
237
|
+
if (cached !== void 0) {
|
|
238
|
+
stats.hits += 1;
|
|
239
|
+
return cached;
|
|
240
|
+
}
|
|
241
|
+
const score = await judge.score(input);
|
|
242
|
+
await store.set(key, score);
|
|
243
|
+
stats.misses += 1;
|
|
244
|
+
return score;
|
|
245
|
+
},
|
|
246
|
+
stats: () => ({ ...stats })
|
|
247
|
+
};
|
|
248
|
+
if (judge.appliesTo) wrapped.appliesTo = judge.appliesTo;
|
|
249
|
+
return wrapped;
|
|
250
|
+
}
|
|
251
|
+
//#endregion
|
|
95
252
|
//#region src/campaign/external-optimizer-http.ts
|
|
96
253
|
function listenLocal(server) {
|
|
97
254
|
return new Promise((resolvePromise, reject) => {
|
|
@@ -120,36 +277,23 @@ function sendJson(response, status, body) {
|
|
|
120
277
|
//#region src/campaign/external-optimizer-model-proxy.ts
|
|
121
278
|
const MODEL_PROXY_PATHS = /* @__PURE__ */ new Set(["/v1/chat/completions", "/v1/responses"]);
|
|
122
279
|
/**
|
|
123
|
-
* Backoff before repeating an upstream fetch the provider answered with 429.
|
|
124
|
-
* One admitted request retries at most this many times, each retry consuming
|
|
125
|
-
* one budgeted request slot, before the final 429 is forwarded to the child.
|
|
126
|
-
* Only HTTP 429 is retried: any other status or a transport error keeps
|
|
127
|
-
* failing immediately.
|
|
128
|
-
*/
|
|
129
|
-
const RATE_LIMIT_RETRY_DELAYS_MS = [
|
|
130
|
-
2e3,
|
|
131
|
-
8e3,
|
|
132
|
-
3e4
|
|
133
|
-
];
|
|
134
|
-
/** Uniform 0..25% added to each delay so concurrent workers do not resynchronize. */
|
|
135
|
-
const RATE_LIMIT_RETRY_JITTER_RATIO = .25;
|
|
136
|
-
/**
|
|
137
280
|
* Put an OpenAI-compatible optimizer behind the shared cost ledger.
|
|
138
281
|
*
|
|
139
|
-
* The child process receives only a loopback URL and an ephemeral token.
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
282
|
+
* The child process receives only a loopback URL and an ephemeral token. The
|
|
283
|
+
* package that owns execution receives a validated immutable request through
|
|
284
|
+
* `call`; Eval receives no provider credential. Every request reserves its
|
|
285
|
+
* conservative byte-count input bound plus the declared output cap before the
|
|
286
|
+
* owner is invoked exactly once.
|
|
143
287
|
*/
|
|
144
288
|
async function startExternalOptimizerModelProxy(args) {
|
|
145
289
|
assertModelProxyConfig(args);
|
|
146
290
|
args.signal?.throwIfAborted();
|
|
147
291
|
const token = randomLocalToken();
|
|
148
|
-
const fetchImpl = args.fetchImpl ?? fetch;
|
|
149
|
-
const sleepImpl = args.sleepImpl ?? abortableDelay;
|
|
150
292
|
let requestCount = 0;
|
|
151
293
|
let successfulCompletionCount = 0;
|
|
152
|
-
let
|
|
294
|
+
let modelCallInvocations = 0;
|
|
295
|
+
let executionRecordCount = 0;
|
|
296
|
+
let executionSequence = 0;
|
|
153
297
|
let totalRequestCount = args.initialUsage?.requests ?? 0;
|
|
154
298
|
let committedForBudget = args.initialUsage?.costUsd ?? 0;
|
|
155
299
|
let reservedForBudget = 0;
|
|
@@ -176,36 +320,42 @@ async function startExternalOptimizerModelProxy(args) {
|
|
|
176
320
|
controller,
|
|
177
321
|
token,
|
|
178
322
|
args,
|
|
179
|
-
fetchImpl,
|
|
180
323
|
nextReservation: (maximumCostUsd) => {
|
|
181
324
|
if (totalRequestCount >= args.budget.maxRequests) return {
|
|
182
325
|
accepted: false,
|
|
183
326
|
reason: "optimizer model request limit reached"
|
|
184
327
|
};
|
|
185
|
-
if (committedForBudget + reservedForBudget + maximumCostUsd > args.budget.maxCostUsd + Number.EPSILON) return {
|
|
328
|
+
if (args.budget.maxCostUsd !== void 0 && maximumCostUsd !== void 0 && committedForBudget + reservedForBudget + maximumCostUsd > args.budget.maxCostUsd + Number.EPSILON) return {
|
|
186
329
|
accepted: false,
|
|
187
330
|
reason: "optimizer model cost limit reached"
|
|
188
331
|
};
|
|
189
332
|
requestCount += 1;
|
|
190
333
|
totalRequestCount += 1;
|
|
191
|
-
reservedForBudget += maximumCostUsd;
|
|
334
|
+
reservedForBudget += maximumCostUsd ?? 0;
|
|
192
335
|
return { accepted: true };
|
|
193
336
|
},
|
|
194
337
|
settleReservation: (maximumCostUsd, chargedCostUsd) => {
|
|
195
|
-
reservedForBudget = Math.max(0, reservedForBudget - maximumCostUsd);
|
|
196
|
-
committedForBudget += chargedCostUsd;
|
|
338
|
+
reservedForBudget = Math.max(0, reservedForBudget - (maximumCostUsd ?? 0));
|
|
339
|
+
committedForBudget += chargedCostUsd ?? 0;
|
|
197
340
|
},
|
|
198
341
|
recordSuccessfulCompletion: () => {
|
|
199
342
|
successfulCompletionCount += 1;
|
|
200
343
|
},
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
344
|
+
recordExecutionReceipt: (observation) => {
|
|
345
|
+
try {
|
|
346
|
+
args.recordExecution({
|
|
347
|
+
...observation,
|
|
348
|
+
sequence: executionSequence + 1
|
|
349
|
+
});
|
|
350
|
+
} catch (error) {
|
|
351
|
+
throw new ModelExecutionPersistenceError(`optimizer model execution evidence was not persisted: ${toErrorMessage(error)}`);
|
|
352
|
+
}
|
|
353
|
+
executionSequence += 1;
|
|
354
|
+
executionRecordCount += 1;
|
|
207
355
|
},
|
|
208
|
-
|
|
356
|
+
recordModelCallInvocation: () => {
|
|
357
|
+
modelCallInvocations += 1;
|
|
358
|
+
}
|
|
209
359
|
}).finally(() => {
|
|
210
360
|
controller.signal.removeEventListener("abort", abortRequest);
|
|
211
361
|
activeControllers.delete(controller);
|
|
@@ -224,12 +374,15 @@ async function startExternalOptimizerModelProxy(args) {
|
|
|
224
374
|
};
|
|
225
375
|
args.signal?.addEventListener("abort", onAbort, { once: true });
|
|
226
376
|
if (args.signal?.aborted) onAbort();
|
|
377
|
+
const assertExecutionComplete = () => {
|
|
378
|
+
if (executionRecordCount !== modelCallInvocations) throw new Error(`external optimizer model callback returned ${executionRecordCount} execution records for ${modelCallInvocations} invoked calls`);
|
|
379
|
+
};
|
|
227
380
|
return {
|
|
228
381
|
baseUrl: `http://127.0.0.1:${port}/v1`,
|
|
229
382
|
apiKey: token,
|
|
230
383
|
requestAttempts: () => requestCount,
|
|
231
384
|
successfulCompletions: () => successfulCompletionCount,
|
|
232
|
-
|
|
385
|
+
assertExecutionComplete,
|
|
233
386
|
close
|
|
234
387
|
};
|
|
235
388
|
async function closeModelProxy() {
|
|
@@ -240,7 +393,15 @@ async function startExternalOptimizerModelProxy(args) {
|
|
|
240
393
|
for (const controller of activeControllers) controller.abort();
|
|
241
394
|
const [serverResult] = await Promise.allSettled([closingServer, waitForActiveHandlers(activeHandlers)]);
|
|
242
395
|
if (activeControllers.size !== 0 || activeHandlers.size !== 0) throw new Error("external optimizer model proxy closed with active request work");
|
|
396
|
+
let executionError;
|
|
397
|
+
try {
|
|
398
|
+
assertExecutionComplete();
|
|
399
|
+
} catch (error) {
|
|
400
|
+
executionError = error;
|
|
401
|
+
}
|
|
402
|
+
if (serverResult?.status === "rejected" && executionError !== void 0) throw new AggregateError([serverResult.reason, executionError], "external optimizer model proxy close and execution evidence both failed");
|
|
243
403
|
if (serverResult?.status === "rejected") throw serverResult.reason;
|
|
404
|
+
if (executionError !== void 0) throw executionError;
|
|
244
405
|
}
|
|
245
406
|
}
|
|
246
407
|
async function handleModelProxyRequest(args) {
|
|
@@ -258,7 +419,7 @@ async function handleModelProxyRequest(args) {
|
|
|
258
419
|
const body = await readBody(request, args.args.budget.maxRequestBytes);
|
|
259
420
|
const parsed = parseModelProxyRequest(body, args.args.model, args.args.budget);
|
|
260
421
|
const maximumUsage = conservativeMaximumUsage(body.byteLength, parsed.maxOutputTokens + (args.args.budget.maxReasoningTokensPerRequest ?? 0), args.args.budget.pricing);
|
|
261
|
-
const maximumCostUsd = costForTokenPricing(args.args.budget.pricing, maximumUsage);
|
|
422
|
+
const maximumCostUsd = args.args.budget.pricing ? costForTokenPricing(args.args.budget.pricing, maximumUsage) : void 0;
|
|
262
423
|
const reservation = args.nextReservation(maximumCostUsd);
|
|
263
424
|
if (!reservation.accepted) {
|
|
264
425
|
sendJsonIfOpen(response, 429, { error: reservation.reason });
|
|
@@ -273,30 +434,23 @@ async function handleModelProxyRequest(args) {
|
|
|
273
434
|
actor: args.args.actor,
|
|
274
435
|
...args.args.tags ? { tags: args.args.tags } : {},
|
|
275
436
|
model: args.args.model,
|
|
276
|
-
maximumCharge: {
|
|
437
|
+
...args.args.budget.pricing ? { maximumCharge: {
|
|
277
438
|
customTokenPricing: args.args.budget.pricing,
|
|
278
439
|
...maximumUsage
|
|
279
|
-
},
|
|
280
|
-
execute: async () => {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
signal: controller.signal
|
|
294
|
-
});
|
|
295
|
-
if (forwarded.status !== 429 || retries >= RATE_LIMIT_RETRY_DELAYS_MS.length || !args.tryConsumeRateLimitRetry()) return forwarded;
|
|
296
|
-
const baseDelayMs = RATE_LIMIT_RETRY_DELAYS_MS[retries];
|
|
297
|
-
await args.sleepImpl(Math.round(baseDelayMs * (1 + Math.random() * RATE_LIMIT_RETRY_JITTER_RATIO)), controller.signal);
|
|
298
|
-
}
|
|
299
|
-
},
|
|
440
|
+
} } : {},
|
|
441
|
+
execute: async () => forwardModelProxyRequest({
|
|
442
|
+
call: args.args.call,
|
|
443
|
+
callRef: args.args.callRef,
|
|
444
|
+
recordExecutionReceipt: args.recordExecutionReceipt,
|
|
445
|
+
recordModelCallInvocation: args.recordModelCallInvocation,
|
|
446
|
+
path,
|
|
447
|
+
requestBody: parsed.body,
|
|
448
|
+
model: args.args.model,
|
|
449
|
+
maxOutputTokens: parsed.maxOutputTokens,
|
|
450
|
+
...args.args.budget.maxReasoningTokensPerRequest === void 0 ? {} : { maxReasoningTokens: args.args.budget.maxReasoningTokensPerRequest },
|
|
451
|
+
maxResponseBytes: args.args.budget.maxResponseBytes,
|
|
452
|
+
signal: controller.signal
|
|
453
|
+
}),
|
|
300
454
|
receipt: (result) => result.receipt,
|
|
301
455
|
receiptFromError: () => ({
|
|
302
456
|
model: args.args.model,
|
|
@@ -307,8 +461,12 @@ async function handleModelProxyRequest(args) {
|
|
|
307
461
|
})
|
|
308
462
|
});
|
|
309
463
|
if (!paid.succeeded) {
|
|
310
|
-
chargedForBudget = paid.receipt ? paid.receipt.usageUnknown || paid.receipt.costUnknown ? maximumCostUsd : paid.receipt.costUsd : 0;
|
|
311
|
-
sendJsonIfOpen(response, isAbortError(paid.error) ? 504 : paid.error instanceof ProviderResponseTooLargeError ? 502 : 429, { error: paid.error.message });
|
|
464
|
+
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 });
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
if (paid.value.modelCallFailed) {
|
|
469
|
+
sendJsonIfOpen(response, 502, { error: paid.value.modelCallFailed });
|
|
312
470
|
return;
|
|
313
471
|
}
|
|
314
472
|
chargedForBudget = paid.value.usageComplete ? paid.receipt.costUsd : maximumCostUsd;
|
|
@@ -339,43 +497,105 @@ function sendJsonIfOpen(response, status, body) {
|
|
|
339
497
|
sendJson(response, status, body);
|
|
340
498
|
}
|
|
341
499
|
async function forwardModelProxyRequest(args) {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
500
|
+
args.recordModelCallInvocation();
|
|
501
|
+
let called;
|
|
502
|
+
try {
|
|
503
|
+
called = await args.call({
|
|
504
|
+
path: args.path,
|
|
505
|
+
model: args.model,
|
|
506
|
+
body: freezeJsonSnapshot(args.requestBody, "optimizer model call body"),
|
|
507
|
+
signal: args.signal
|
|
508
|
+
});
|
|
509
|
+
} catch (error) {
|
|
510
|
+
throw new MissingModelExecutionError(`optimizer model callback rejected without execution evidence: ${toErrorMessage(error)}`);
|
|
511
|
+
}
|
|
512
|
+
if (!called || typeof called !== "object" || typeof called.succeeded !== "boolean") throw new MissingModelExecutionError("optimizer model callback returned no typed success/failure outcome");
|
|
513
|
+
let execution;
|
|
514
|
+
try {
|
|
515
|
+
execution = freezeJsonSnapshot(called.execution, "optimizer model callback execution evidence");
|
|
516
|
+
} catch (error) {
|
|
517
|
+
throw new MissingModelExecutionError(`optimizer model callback returned invalid execution evidence: ${toErrorMessage(error)}`);
|
|
518
|
+
}
|
|
519
|
+
if (called.succeeded) {
|
|
520
|
+
if (!(called.response instanceof Response)) throw new MissingModelExecutionError("optimizer model callback success did not return a Response");
|
|
521
|
+
args.recordExecutionReceipt({
|
|
522
|
+
callRef: args.callRef,
|
|
523
|
+
path: args.path,
|
|
524
|
+
model: args.model,
|
|
525
|
+
succeeded: true,
|
|
526
|
+
responseStatus: called.response.status,
|
|
527
|
+
execution
|
|
528
|
+
});
|
|
529
|
+
} else {
|
|
530
|
+
if (typeof called.error !== "string" || !called.error.trim()) throw new MissingModelExecutionError("optimizer model callback failure did not return a public error");
|
|
531
|
+
args.recordExecutionReceipt({
|
|
532
|
+
callRef: args.callRef,
|
|
533
|
+
path: args.path,
|
|
534
|
+
model: args.model,
|
|
535
|
+
succeeded: false,
|
|
536
|
+
error: called.error,
|
|
537
|
+
execution
|
|
538
|
+
});
|
|
539
|
+
const failedReceipt = snapshotModelReceipt(called.receipt, args.model);
|
|
540
|
+
return {
|
|
541
|
+
status: 502,
|
|
542
|
+
contentType: "application/json",
|
|
543
|
+
body: new TextEncoder().encode(JSON.stringify({ error: called.error })),
|
|
544
|
+
receipt: failedReceipt,
|
|
545
|
+
usageComplete: failedReceipt.usageUnknown !== true,
|
|
546
|
+
modelCallFailed: called.error
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
const response = called.response;
|
|
550
|
+
const authoritativeReceipt = snapshotModelReceipt(called.receipt, args.model);
|
|
353
551
|
const body = await readProviderResponseBody(response, args.maxResponseBytes);
|
|
354
552
|
const usage = parseProviderUsage(body);
|
|
355
553
|
const successful = response.status >= 200 && response.status < 300;
|
|
356
|
-
const zeroUsage = successful && usage !== void 0 && usage.inputTokens + usage.outputTokens + (usage.cachedTokens ?? 0) + (usage.cacheWriteTokens ?? 0) === 0;
|
|
357
554
|
const completionTokens = usage === void 0 ? 0 : usage.outputTokens - (usage.reasoningTokens ?? 0);
|
|
358
555
|
const reasoningTokens = usage?.reasoningTokens ?? 0;
|
|
359
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);
|
|
360
558
|
return {
|
|
361
559
|
status: response.status,
|
|
362
560
|
contentType: response.headers.get("content-type") ?? "application/json",
|
|
363
561
|
body,
|
|
364
|
-
receipt:
|
|
365
|
-
|
|
366
|
-
...usage,
|
|
367
|
-
...usage.actualCostUsd === void 0 ? { customTokenPricing: args.pricing } : {}
|
|
368
|
-
} : {
|
|
369
|
-
model: args.model,
|
|
370
|
-
inputTokens: 0,
|
|
371
|
-
outputTokens: 0,
|
|
372
|
-
costUnknown: true,
|
|
373
|
-
usageUnknown: true
|
|
374
|
-
},
|
|
375
|
-
usageComplete: usage !== void 0 && !zeroUsage,
|
|
562
|
+
receipt: authoritativeReceipt,
|
|
563
|
+
usageComplete: authoritativeReceipt.usageUnknown !== true,
|
|
376
564
|
...usageRejected ? { usageRejected } : {}
|
|
377
565
|
};
|
|
378
566
|
}
|
|
567
|
+
function snapshotModelReceipt(value, expectedModel) {
|
|
568
|
+
let snapshot;
|
|
569
|
+
try {
|
|
570
|
+
assertJsonValue(value, "optimizer model callback receipt");
|
|
571
|
+
snapshot = JSON.parse(canonicalJson(value));
|
|
572
|
+
} catch (error) {
|
|
573
|
+
throw new OwnerModelContractError(`optimizer model callback returned an invalid receipt: ${toErrorMessage(error)}`);
|
|
574
|
+
}
|
|
575
|
+
if (snapshot.model !== expectedModel) throw new OwnerModelContractError(`optimizer model callback receipt used '${snapshot.model}' instead of '${expectedModel}'`);
|
|
576
|
+
return snapshot;
|
|
577
|
+
}
|
|
578
|
+
function freezeJsonSnapshot(value, label) {
|
|
579
|
+
assertJsonValue(value, label);
|
|
580
|
+
return deepFreezeJson(JSON.parse(canonicalJson(value)));
|
|
581
|
+
}
|
|
582
|
+
function deepFreezeJson(value) {
|
|
583
|
+
if (value !== null && typeof value === "object") {
|
|
584
|
+
for (const child of Object.values(value)) deepFreezeJson(child);
|
|
585
|
+
Object.freeze(value);
|
|
586
|
+
}
|
|
587
|
+
return value;
|
|
588
|
+
}
|
|
589
|
+
function assertResponseUsageMatchesReceipt(usage, receipt) {
|
|
590
|
+
for (const field of [
|
|
591
|
+
"inputTokens",
|
|
592
|
+
"outputTokens",
|
|
593
|
+
"cachedTokens",
|
|
594
|
+
"cacheWriteTokens",
|
|
595
|
+
"reasoningTokens"
|
|
596
|
+
]) if ((usage[field] ?? 0) !== (receipt[field] ?? 0)) throw new OwnerModelContractError(`optimizer model response usage disagrees with Runtime receipt at ${field}`);
|
|
597
|
+
if (usage.actualCostUsd !== void 0 && usage.actualCostUsd !== receipt.actualCostUsd) throw new OwnerModelContractError("optimizer model response cost disagrees with Runtime receipt");
|
|
598
|
+
}
|
|
379
599
|
function parseModelProxyRequest(body, expectedModel, budget) {
|
|
380
600
|
let value;
|
|
381
601
|
try {
|
|
@@ -400,24 +620,10 @@ function parseModelProxyRequest(body, expectedModel, budget) {
|
|
|
400
620
|
if (suppliedMaximums.length === 0 || suppliedMaximums.some((maximum) => !Number.isSafeInteger(maximum) || maximum <= 0)) throw new Error("optimizer model request requires a positive output-token limit");
|
|
401
621
|
const maxOutputTokens = Math.max(...suppliedMaximums);
|
|
402
622
|
if (maxOutputTokens > budget.maxOutputTokensPerRequest) throw new Error("optimizer model request exceeds maxOutputTokensPerRequest");
|
|
403
|
-
return {
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
*
|
|
408
|
-
* This proxy exposes `/v1/...` to its child for client compatibility. Whether
|
|
409
|
-
* that prefix belongs upstream depends on the caller's base URL: a base that
|
|
410
|
-
* already names a version segment (`/v1`, `/api/coding/paas/v4`) receives only
|
|
411
|
-
* the endpoint, and a base without one receives the whole versioned path.
|
|
412
|
-
* Forwarding the prefix unconditionally produced `/v4/v1/chat/completions`,
|
|
413
|
-
* which providers answer with 404.
|
|
414
|
-
*/
|
|
415
|
-
function modelProxyUpstreamUrl(baseUrl, requestPath) {
|
|
416
|
-
const upstream = new URL(baseUrl);
|
|
417
|
-
const basePath = upstream.pathname.replace(/\/+$/, "");
|
|
418
|
-
const lastSegment = basePath.split("/").at(-1) ?? "";
|
|
419
|
-
upstream.pathname = `${basePath}${/^v\d+/.test(lastSegment) ? requestPath.replace(/^\/v1(?=\/)/, "") : requestPath}`;
|
|
420
|
-
return upstream.toString();
|
|
623
|
+
return {
|
|
624
|
+
maxOutputTokens,
|
|
625
|
+
body: value
|
|
626
|
+
};
|
|
421
627
|
}
|
|
422
628
|
function parseProviderUsage(body) {
|
|
423
629
|
let value;
|
|
@@ -433,30 +639,59 @@ function parseProviderUsage(body) {
|
|
|
433
639
|
if (!Number.isSafeInteger(totalInputTokens) || totalInputTokens < 0 || !Number.isSafeInteger(outputTokens) || outputTokens < 0) return;
|
|
434
640
|
const inputDetails = isRecord(usage.input_tokens_details) ? usage.input_tokens_details : isRecord(usage.prompt_tokens_details) ? usage.prompt_tokens_details : {};
|
|
435
641
|
const outputDetails = isRecord(usage.output_tokens_details) ? usage.output_tokens_details : isRecord(usage.completion_tokens_details) ? usage.completion_tokens_details : {};
|
|
436
|
-
const
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
]);
|
|
441
|
-
const
|
|
442
|
-
"cache_write_tokens",
|
|
443
|
-
"cache_creation_tokens",
|
|
444
|
-
"cache_creation_input_tokens"
|
|
445
|
-
]);
|
|
446
|
-
const reasoningTokens = optionalTokenCount(outputDetails, ["reasoning_tokens"]);
|
|
642
|
+
const nestedCachedTokens = optionalTokenCount(inputDetails, ["cached_tokens", "cache_read_tokens"]);
|
|
643
|
+
const separateCachedTokens = optionalTokenCount(usage, ["cache_read_input_tokens"]);
|
|
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"]);
|
|
447
648
|
const actualCostUsd = typeof usage.cost === "number" && Number.isFinite(usage.cost) && usage.cost >= 0 ? usage.cost : void 0;
|
|
448
|
-
if (
|
|
449
|
-
const
|
|
450
|
-
|
|
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;
|
|
451
656
|
return {
|
|
452
657
|
inputTokens: totalInputTokens - classifiedInputTokens,
|
|
453
658
|
outputTokens,
|
|
454
|
-
...
|
|
455
|
-
...
|
|
456
|
-
...
|
|
659
|
+
...cached.supplied ? { cachedTokens: cached.count } : {},
|
|
660
|
+
...cacheWrite.supplied ? { cacheWriteTokens: cacheWrite.count } : {},
|
|
661
|
+
...reasoning.supplied ? { reasoningTokens: reasoning.count } : {},
|
|
457
662
|
...actualCostUsd === void 0 ? {} : { actualCostUsd }
|
|
458
663
|
};
|
|
459
664
|
}
|
|
665
|
+
/**
|
|
666
|
+
* OpenAI reports a cache class inside the input total; Anthropic reports it
|
|
667
|
+
* beside the input total. When both equivalent forms are present they must
|
|
668
|
+
* agree, and the nested normalized form wins.
|
|
669
|
+
*/
|
|
670
|
+
function selectProviderTokenClass(nested, separate) {
|
|
671
|
+
if (nested !== void 0 && separate !== void 0 && nested > 0 && separate > 0) {
|
|
672
|
+
if (nested !== separate) return void 0;
|
|
673
|
+
return {
|
|
674
|
+
count: nested,
|
|
675
|
+
supplied: true,
|
|
676
|
+
includedInTotal: true
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
if (nested !== void 0 && nested > 0) return {
|
|
680
|
+
count: nested,
|
|
681
|
+
supplied: true,
|
|
682
|
+
includedInTotal: true
|
|
683
|
+
};
|
|
684
|
+
if (separate !== void 0 && separate > 0) return {
|
|
685
|
+
count: separate,
|
|
686
|
+
supplied: true,
|
|
687
|
+
includedInTotal: false
|
|
688
|
+
};
|
|
689
|
+
return {
|
|
690
|
+
count: 0,
|
|
691
|
+
supplied: nested !== void 0 || separate !== void 0,
|
|
692
|
+
includedInTotal: nested !== void 0
|
|
693
|
+
};
|
|
694
|
+
}
|
|
460
695
|
const INVALID_TOKEN_COUNT = Symbol("invalid-token-count");
|
|
461
696
|
function optionalTokenCount(details, fields) {
|
|
462
697
|
let found;
|
|
@@ -470,6 +705,10 @@ function optionalTokenCount(details, fields) {
|
|
|
470
705
|
return found;
|
|
471
706
|
}
|
|
472
707
|
function conservativeMaximumUsage(inputTokenUpperBound, outputTokenUpperBound, pricing) {
|
|
708
|
+
if (pricing === void 0) return {
|
|
709
|
+
inputTokens: inputTokenUpperBound,
|
|
710
|
+
outputTokens: outputTokenUpperBound
|
|
711
|
+
};
|
|
473
712
|
const inputRates = [
|
|
474
713
|
pricing.inputUsdPerMillion,
|
|
475
714
|
pricing.cachedInputUsdPerMillion ?? pricing.inputUsdPerMillion,
|
|
@@ -517,26 +756,20 @@ async function readProviderResponseBody(response, maxBytes) {
|
|
|
517
756
|
}
|
|
518
757
|
function assertModelProxyConfig(args) {
|
|
519
758
|
for (const [label, value] of [
|
|
520
|
-
["upstreamBaseUrl", args.upstreamBaseUrl],
|
|
521
|
-
["upstreamApiKey", args.upstreamApiKey],
|
|
522
759
|
["model", args.model],
|
|
523
760
|
["phase", args.phase],
|
|
524
761
|
["actor", args.actor]
|
|
525
762
|
]) if (typeof value !== "string" || !value.trim() || value.trim() !== value) throw new Error(`external optimizer model proxy: ${label} must be trimmed and non-empty`);
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
} catch {
|
|
530
|
-
throw new Error("external optimizer model proxy: upstreamBaseUrl must be an HTTP(S) URL");
|
|
531
|
-
}
|
|
532
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:" || parsed.username || parsed.password || parsed.search || parsed.hash) throw new Error("external optimizer model proxy: upstreamBaseUrl must be an HTTP(S) URL without credentials, query, or fragment");
|
|
763
|
+
if (typeof args.call !== "function") throw new Error("external optimizer model proxy: call must be a function");
|
|
764
|
+
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
|
+
if (typeof args.recordExecution !== "function") throw new Error("external optimizer model proxy: recordExecution must be a function");
|
|
533
766
|
assertExternalOptimizerModelBudget(args.budget, "external optimizer model proxy: budget");
|
|
534
767
|
if (args.tags !== void 0) {
|
|
535
768
|
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");
|
|
536
769
|
}
|
|
537
770
|
if (args.initialUsage !== void 0) {
|
|
538
|
-
if (!Number.isSafeInteger(args.initialUsage.requests) || args.initialUsage.requests < 0 || !Number.isFinite(args.initialUsage.costUsd) || args.initialUsage.costUsd < 0) throw new Error("external optimizer model proxy: initialUsage must contain non-negative requests and cost");
|
|
539
|
-
if (args.initialUsage.requests > args.budget.maxRequests || args.initialUsage.costUsd > args.budget.maxCostUsd + Number.EPSILON) throw new Error("external optimizer model proxy: initialUsage exceeds the configured budget");
|
|
771
|
+
if (!Number.isSafeInteger(args.initialUsage.requests) || args.initialUsage.requests < 0 || args.initialUsage.costUsd !== void 0 && (!Number.isFinite(args.initialUsage.costUsd) || args.initialUsage.costUsd < 0)) throw new Error("external optimizer model proxy: initialUsage must contain non-negative requests and cost");
|
|
772
|
+
if (args.initialUsage.requests > args.budget.maxRequests || args.budget.maxCostUsd !== void 0 && args.initialUsage.costUsd !== void 0 && args.initialUsage.costUsd > args.budget.maxCostUsd + Number.EPSILON) throw new Error("external optimizer model proxy: initialUsage exceeds the configured budget");
|
|
540
773
|
}
|
|
541
774
|
}
|
|
542
775
|
var RequestBodyTooLargeError = class extends Error {};
|
|
@@ -545,6 +778,9 @@ var ProviderResponseTooLargeError = class extends Error {
|
|
|
545
778
|
super("optimizer model response exceeds maxResponseBytes");
|
|
546
779
|
}
|
|
547
780
|
};
|
|
781
|
+
var MissingModelExecutionError = class extends Error {};
|
|
782
|
+
var ModelExecutionPersistenceError = class extends Error {};
|
|
783
|
+
var OwnerModelContractError = class extends Error {};
|
|
548
784
|
function readBody(request, maximumBytes) {
|
|
549
785
|
return new Promise((resolvePromise, reject) => {
|
|
550
786
|
let size = 0;
|
|
@@ -573,24 +809,6 @@ function readBody(request, maximumBytes) {
|
|
|
573
809
|
function randomLocalToken() {
|
|
574
810
|
return randomBytes(32).toString("hex");
|
|
575
811
|
}
|
|
576
|
-
function abortableDelay(ms, signal) {
|
|
577
|
-
return new Promise((resolve, reject) => {
|
|
578
|
-
const abortError = () => signal.reason instanceof Error ? signal.reason : /* @__PURE__ */ new Error("optimizer model retry backoff aborted");
|
|
579
|
-
if (signal.aborted) {
|
|
580
|
-
reject(abortError());
|
|
581
|
-
return;
|
|
582
|
-
}
|
|
583
|
-
const onAbort = () => {
|
|
584
|
-
clearTimeout(timer);
|
|
585
|
-
reject(abortError());
|
|
586
|
-
};
|
|
587
|
-
const timer = setTimeout(() => {
|
|
588
|
-
signal.removeEventListener("abort", onAbort);
|
|
589
|
-
resolve();
|
|
590
|
-
}, ms);
|
|
591
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
592
|
-
});
|
|
593
|
-
}
|
|
594
812
|
function isAbortError(error) {
|
|
595
813
|
return error.name === "AbortError" || error.message.toLowerCase().includes("abort");
|
|
596
814
|
}
|
|
@@ -1044,6 +1262,6 @@ function acquireSingleRunLock(opts) {
|
|
|
1044
1262
|
return { release };
|
|
1045
1263
|
}
|
|
1046
1264
|
//#endregion
|
|
1047
|
-
export {
|
|
1265
|
+
export { assertNoCredentialValues as C, removeCredentialEnvironment as D, isRecord as E, safePathComponent as O, assertJsonValue as S, isExternalTextCandidate as T, canonicalJson as _, SEARCH_LEDGER_FILE_CONTEXT as a, inMemoryVerdictCache as b, SearchLedgerIntegrityError as c, runWithCleanup as d, startExternalOptimizerModelProxy as f, cachedJudge as g, sendJson as h, inMemoryCampaignStorage as i, runExternalOptimizerProcess as l, listenLocal as m, createRunCostLedger as n, SearchLedgerConflictError as o, closeServer as p, fsCampaignStorage as r, SearchLedgerError as s, acquireSingleRunLock as t, closeExternalOptimizerResources as u, contentHash as v, isCandidateText as w, assertExternalOptimizerModelBudget as x, fileVerdictCache as y };
|
|
1048
1266
|
|
|
1049
|
-
//# sourceMappingURL=single-run-lock-
|
|
1267
|
+
//# sourceMappingURL=single-run-lock-t1si1ob7.js.map
|