llm-relay 0.77.0 → 0.78.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/dist/accounting-store-schema.js +5 -6
- package/dist/accounting-store-schema.js.map +1 -1
- package/dist/accounting-store.d.ts +10 -0
- package/dist/accounting-store.js +60 -6
- package/dist/accounting-store.js.map +1 -1
- package/dist/backend/envelope-validator.d.ts +1 -1
- package/dist/backend/envelope-validator.js +89 -52
- package/dist/backend/envelope-validator.js.map +1 -1
- package/dist/backend/health-prober.js +3 -1
- package/dist/backend/health-prober.js.map +1 -1
- package/dist/backend.d.ts +48 -0
- package/dist/backend.js +831 -7
- package/dist/backend.js.map +1 -1
- package/dist/candidate-runner.d.ts +135 -50
- package/dist/candidate-runner.js +160 -55
- package/dist/candidate-runner.js.map +1 -1
- package/dist/cli.d.ts +24 -2
- package/dist/cli.js +122 -20
- package/dist/cli.js.map +1 -1
- package/dist/config/routing-parser.d.ts +13 -8
- package/dist/config/routing-parser.js +369 -183
- package/dist/config/routing-parser.js.map +1 -1
- package/dist/config-types.d.ts +202 -0
- package/dist/config-types.js +2 -0
- package/dist/config-types.js.map +1 -1
- package/dist/config.d.ts +45 -1
- package/dist/config.js +87 -22
- package/dist/config.js.map +1 -1
- package/dist/dashboard-contract.d.ts +38 -0
- package/dist/dashboard-contract.js +19 -3
- package/dist/dashboard-contract.js.map +1 -1
- package/dist/dispatch-lane-stats.d.ts +33 -4
- package/dist/dispatch-lane-stats.js +65 -16
- package/dist/dispatch-lane-stats.js.map +1 -1
- package/dist/dispatch.d.ts +9 -0
- package/dist/dispatch.js +36 -4
- package/dist/dispatch.js.map +1 -1
- package/dist/key-checker.d.ts +21 -1
- package/dist/key-checker.js +38 -12
- package/dist/key-checker.js.map +1 -1
- package/dist/lane-affinity.d.ts +94 -1
- package/dist/lane-affinity.js +52 -4
- package/dist/lane-affinity.js.map +1 -1
- package/dist/mcp/lane-runner.d.ts +69 -16
- package/dist/mcp/lane-runner.js +30 -1
- package/dist/mcp/lane-runner.js.map +1 -1
- package/dist/mcp/server.d.ts +39 -4
- package/dist/mcp/server.js +70 -13
- package/dist/mcp/server.js.map +1 -1
- package/dist/openai-request.d.ts +21 -0
- package/dist/openai-request.js +1 -1
- package/dist/openai-request.js.map +1 -1
- package/dist/ping/cadence.js +9 -1
- package/dist/ping/cadence.js.map +1 -1
- package/dist/ping/probe-cache.d.ts +13 -0
- package/dist/ping/probe-cache.js +12 -0
- package/dist/ping/probe-cache.js.map +1 -1
- package/dist/responses-request.d.ts +20 -0
- package/dist/responses-request.js +7 -7
- package/dist/responses-request.js.map +1 -1
- package/dist/routes/admin.d.ts +8 -1
- package/dist/routes/admin.js +42 -6
- package/dist/routes/admin.js.map +1 -1
- package/dist/routes/messages.d.ts +1 -0
- package/dist/routes/messages.js +13 -8
- package/dist/routes/messages.js.map +1 -1
- package/dist/routes/openai-front.js +20 -7
- package/dist/routes/openai-front.js.map +1 -1
- package/dist/server.d.ts +9 -1
- package/dist/server.js +21 -3
- package/dist/server.js.map +1 -1
- package/dist/setup-claude.d.ts +2 -2
- package/dist/setup-claude.js +9 -7
- package/dist/setup-claude.js.map +1 -1
- package/dist/stream-pipeline.d.ts +116 -0
- package/dist/stream-pipeline.js +151 -0
- package/dist/stream-pipeline.js.map +1 -1
- package/dist/target-facts.d.ts +5 -4
- package/dist/target-facts.js +5 -0
- package/dist/target-facts.js.map +1 -1
- package/dist/telemetry.d.ts +15 -2
- package/dist/telemetry.js +4 -1
- package/dist/telemetry.js.map +1 -1
- package/dist/tier-data.d.ts +22 -0
- package/dist/tier-data.js +12 -2
- package/dist/tier-data.js.map +1 -1
- package/package.json +2 -1
- package/skills/llm-relay/SKILL.md +8 -3
- package/skills/llm-relay/references/dispatch-lanes.md +2 -1
- package/skills/llm-relay/references/operations.md +1 -1
package/dist/candidate-runner.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { specOfTarget } from "./benchmarks.js";
|
|
2
|
-
import { CREDENTIAL_HEADER, CREDENTIAL_ATTEMPTS_HEADER, POOL_ATTEMPTS_HEADER, HARD_CAP_HEADER, ERROR_ORIGIN_HEADER, SERVED_BY_HEADER, TOOL_DIALECT_HEADER, UNKNOWN_REFUSAL_HEADER, DEGRADED_HEADER, PAID_HEADER, QUOTA_DEMOTED_HEADER, LATENCY_DEMOTED_HEADER, HEDGED_HEADER, dialectRefusalSignalOf, errorOrigin, postHeaderBodyFailure, toolUseIdRewrites, toolCallIdRewrites, thoughtSignatureSentinels, upstreamReportedModel, } from "./backend.js";
|
|
2
|
+
import { CREDENTIAL_HEADER, CREDENTIAL_ATTEMPTS_HEADER, POOL_ATTEMPTS_HEADER, HARD_CAP_HEADER, ERROR_ORIGIN_HEADER, SERVED_BY_HEADER, TOOL_DIALECT_HEADER, UNKNOWN_REFUSAL_HEADER, DEGRADED_HEADER, PAID_HEADER, QUOTA_DEMOTED_HEADER, LATENCY_DEMOTED_HEADER, PROBATION_HEADER, HEDGED_HEADER, dialectRefusalSignalOf, errorOrigin, postHeaderBodyFailure, toolUseIdRewrites, toolCallIdRewrites, thoughtSignatureSentinels, upstreamReportedModel, } from "./backend.js";
|
|
3
3
|
import { STICKY_PROVENANCE_HEADER } from "./session-pin.js";
|
|
4
4
|
import { probeStreamForCommit } from "./stream-commit.js";
|
|
5
5
|
import { DIALECT_REFUSED_DESTRUCTIVE_CODE } from "./tool-dialects.js";
|
|
@@ -25,15 +25,21 @@ import { DEFAULT_ANTHROPIC_VERSION } from "./config.js";
|
|
|
25
25
|
import { buildAuthHeaders } from "./authEnv.js";
|
|
26
26
|
import { CONTROL_AUTHORIZATION_HEADER } from "./control-authorization.js";
|
|
27
27
|
import { STICKY_SESSION_HEADER } from "./session-pin.js";
|
|
28
|
-
import { failClosed, HOP_BY_HOP } from "./stream-pipeline.js";
|
|
28
|
+
import { CrawlAbortedError, failClosed, HOP_BY_HOP } from "./stream-pipeline.js";
|
|
29
29
|
const INTERNAL_REQUEST_HEADERS = new Set([
|
|
30
30
|
"x-codex-turn-metadata",
|
|
31
31
|
"x-llm-relay-dashboard-session",
|
|
32
32
|
STICKY_SESSION_HEADER,
|
|
33
33
|
CONTROL_AUTHORIZATION_HEADER,
|
|
34
34
|
]);
|
|
35
|
-
const
|
|
36
|
-
|
|
35
|
+
const ALLOWED_FORWARD_HEADERS = new Set([
|
|
36
|
+
"content-type",
|
|
37
|
+
"accept",
|
|
38
|
+
"anthropic-version",
|
|
39
|
+
"anthropic-beta",
|
|
40
|
+
]);
|
|
41
|
+
const MID_STREAM_ERROR_KIND = "backend_stream_failed";
|
|
42
|
+
const CRAWL_ERROR_KIND = "backend_stream_crawl";
|
|
37
43
|
export function toolUseIdRewriteField(source) {
|
|
38
44
|
const rewrites = toolUseIdRewrites(source);
|
|
39
45
|
const outbound = toolCallIdRewrites(source);
|
|
@@ -52,7 +58,7 @@ export class CredentialConfigError extends Error {
|
|
|
52
58
|
}
|
|
53
59
|
export function buildForwardHeaders(inbound, attempt) {
|
|
54
60
|
const { target, credential } = attempt;
|
|
55
|
-
const
|
|
61
|
+
const isContained = credential.state !== "not-declared" || target.credentialMode === "contained";
|
|
56
62
|
const out = {};
|
|
57
63
|
for (const [k, v] of Object.entries(inbound)) {
|
|
58
64
|
const key = k.toLowerCase();
|
|
@@ -60,7 +66,7 @@ export function buildForwardHeaders(inbound, attempt) {
|
|
|
60
66
|
continue;
|
|
61
67
|
if (INTERNAL_REQUEST_HEADERS.has(key))
|
|
62
68
|
continue;
|
|
63
|
-
if (
|
|
69
|
+
if (isContained && !ALLOWED_FORWARD_HEADERS.has(key))
|
|
64
70
|
continue;
|
|
65
71
|
if (v === undefined)
|
|
66
72
|
continue;
|
|
@@ -77,7 +83,7 @@ export function buildForwardHeaders(inbound, attempt) {
|
|
|
77
83
|
export const DEFAULT_WALK_BUDGET_MS = 45_000;
|
|
78
84
|
export const DEFAULT_STALL_TIMEOUT_MS = 90_000;
|
|
79
85
|
const MAX_CAPPED_HEADER_CELLS = 5;
|
|
80
|
-
|
|
86
|
+
function filterResponseHeaders(hh) {
|
|
81
87
|
const out = {};
|
|
82
88
|
for (const [k, v] of hh.entries()) {
|
|
83
89
|
const lower = k.toLowerCase();
|
|
@@ -118,6 +124,8 @@ export function responseHeadersForTarget(backendRes, ctx) {
|
|
|
118
124
|
responseHeaders[QUOTA_DEMOTED_HEADER] = ctx.quotaDemoted;
|
|
119
125
|
if (ctx.latencyDemoted)
|
|
120
126
|
responseHeaders[LATENCY_DEMOTED_HEADER] = ctx.latencyDemoted;
|
|
127
|
+
if (ctx.probation)
|
|
128
|
+
responseHeaders[PROBATION_HEADER] = ctx.probation;
|
|
121
129
|
if (ctx.hedged)
|
|
122
130
|
responseHeaders[HEDGED_HEADER] = ctx.hedged;
|
|
123
131
|
if (ctx.paid)
|
|
@@ -131,7 +139,7 @@ export function responseHeadersForTarget(backendRes, ctx) {
|
|
|
131
139
|
}
|
|
132
140
|
return responseHeaders;
|
|
133
141
|
}
|
|
134
|
-
|
|
142
|
+
function endMidStreamFailure(res, errorFrame, message) {
|
|
135
143
|
if (res.writableEnded || res.destroyed)
|
|
136
144
|
return;
|
|
137
145
|
if (res.headersSent) {
|
|
@@ -143,32 +151,38 @@ export function endMidStreamFailure(res, errorFrame, message) {
|
|
|
143
151
|
failClosed(res, 502, message);
|
|
144
152
|
}
|
|
145
153
|
}
|
|
146
|
-
|
|
154
|
+
function midStreamMessage(e) {
|
|
147
155
|
const errStr = e && typeof e === "object" && "message" in e && typeof e.message === "string"
|
|
148
156
|
? e.message
|
|
149
157
|
: String(e);
|
|
150
158
|
return `llm-relay: backend stream failed mid-response: ${errStr}`;
|
|
151
159
|
}
|
|
152
|
-
|
|
153
|
-
|
|
160
|
+
function crawlAbortReason(signal) {
|
|
161
|
+
return signal?.reason instanceof CrawlAbortedError ? signal.reason : undefined;
|
|
162
|
+
}
|
|
163
|
+
function completeMidStreamAttempt(h, res, attempt, deadlineAborted, committed) {
|
|
164
|
+
if (!attempt)
|
|
165
|
+
return;
|
|
166
|
+
if (res.destroyed) {
|
|
167
|
+
completeAttemptCancelled(h, attempt, "client disconnected");
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
completeAttemptFailure(h, attempt, {
|
|
171
|
+
failure: deadlineAborted ? "transport" : "protocol",
|
|
172
|
+
provenance: deadlineAborted ? "deadline" : "upstream",
|
|
173
|
+
status: deadlineAborted ? 504 : 502,
|
|
174
|
+
...(committed ? { logStatus: "committed" } : {}),
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
export function handleMidStreamError(res, e, started, path, hadTools, streamed, backendStatus, target, attempt, h, errorFrameBuilder, reportedModelSource, deadlineAborted = false, committed = false, signal) {
|
|
178
|
+
const crawl = crawlAbortReason(signal);
|
|
179
|
+
const message = crawl ? crawl.message : midStreamMessage(e);
|
|
154
180
|
const errorFrame = errorFrameBuilder(message);
|
|
155
181
|
endMidStreamFailure(res, errorFrame, message);
|
|
156
|
-
|
|
157
|
-
if (res.destroyed) {
|
|
158
|
-
completeAttemptCancelled(h, attempt, "client disconnected");
|
|
159
|
-
}
|
|
160
|
-
else {
|
|
161
|
-
completeAttemptFailure(h, attempt, {
|
|
162
|
-
failure: deadlineAborted ? "transport" : "protocol",
|
|
163
|
-
provenance: deadlineAborted ? "deadline" : "upstream",
|
|
164
|
-
status: deadlineAborted ? 504 : 502,
|
|
165
|
-
...(committed ? { logStatus: "committed" } : {}),
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
}
|
|
182
|
+
completeMidStreamAttempt(h, res, attempt, deadlineAborted, committed);
|
|
169
183
|
h.logger.write({
|
|
170
184
|
...baseLog(started, path, hadTools, streamed, backendStatus, "skipped", target, attempt?.trace.snapshot(), reportedModelSource ? upstreamReportedModel(reportedModelSource) : undefined),
|
|
171
|
-
errorKinds: [MID_STREAM_ERROR_KIND],
|
|
185
|
+
errorKinds: [crawl ? CRAWL_ERROR_KIND : MID_STREAM_ERROR_KIND],
|
|
172
186
|
...(reportedModelSource ? toolUseIdRewriteField(reportedModelSource) : {}),
|
|
173
187
|
});
|
|
174
188
|
}
|
|
@@ -186,7 +200,58 @@ export function degradedLabel(pool, degraded, target) {
|
|
|
186
200
|
const spec = specOfTarget(target);
|
|
187
201
|
return degraded.has(spec) ? `${spec} (below ${pool})` : null;
|
|
188
202
|
}
|
|
189
|
-
export
|
|
203
|
+
export const DEFAULT_PROBATION_MIN_SAMPLES = 5;
|
|
204
|
+
export function resolveProbationSettings(settings) {
|
|
205
|
+
return {
|
|
206
|
+
enabled: settings?.enabled ?? true,
|
|
207
|
+
minSamples: settings?.minSamples ?? DEFAULT_PROBATION_MIN_SAMPLES,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
export function resolveProbation(deps, attempt) {
|
|
211
|
+
const { enabled, minSamples } = resolveProbationSettings(deps.settings);
|
|
212
|
+
if (!enabled)
|
|
213
|
+
return null;
|
|
214
|
+
const model = attempt.target.model;
|
|
215
|
+
if (typeof model !== "string" || model.length === 0)
|
|
216
|
+
return null;
|
|
217
|
+
let cost;
|
|
218
|
+
try {
|
|
219
|
+
cost = deps.costClassOf?.(attempt);
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
if (cost !== "free")
|
|
225
|
+
return null;
|
|
226
|
+
let samples;
|
|
227
|
+
try {
|
|
228
|
+
samples = deps.readRequestSamples(attempt.target.provider, model);
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
if (!Number.isFinite(samples) || samples >= minSamples)
|
|
234
|
+
return null;
|
|
235
|
+
return { samples: Math.max(0, Math.floor(samples)), minSamples };
|
|
236
|
+
}
|
|
237
|
+
export function createProbationFn(deps) {
|
|
238
|
+
return (attempt) => {
|
|
239
|
+
try {
|
|
240
|
+
return resolveProbation(deps, attempt);
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
export function probationLabel(spec, verdict) {
|
|
248
|
+
return `${spec} (${verdict.samples} of ${verdict.minSamples} request samples)`;
|
|
249
|
+
}
|
|
250
|
+
export function probationLabelForAttempt(h, attempt, now) {
|
|
251
|
+
const verdict = h.probation?.(attempt, now) ?? null;
|
|
252
|
+
return verdict ? probationLabel(specOfTarget(attempt.target), verdict) : null;
|
|
253
|
+
}
|
|
254
|
+
function cooledByAllowance(attempt, now, costClass) {
|
|
190
255
|
try {
|
|
191
256
|
const { target } = attempt;
|
|
192
257
|
const until = cooldownUntil(target.provider, attempt.credentialId, target.model ?? null, {
|
|
@@ -199,7 +264,7 @@ export function cooledByAllowance(attempt, now, costClass) {
|
|
|
199
264
|
return false;
|
|
200
265
|
}
|
|
201
266
|
}
|
|
202
|
-
|
|
267
|
+
function cooledByQuota(attempt, breaker, quotaDemotion, now) {
|
|
203
268
|
if (!quotaDemotion)
|
|
204
269
|
return false;
|
|
205
270
|
const spent = quotaDemotion(attempt, now);
|
|
@@ -208,7 +273,7 @@ export function cooledByQuota(attempt, breaker, quotaDemotion, now) {
|
|
|
208
273
|
breaker.recordQuotaCooldown(targetIdentity(attempt), spent.resetsAt, now);
|
|
209
274
|
return true;
|
|
210
275
|
}
|
|
211
|
-
export function targetUsability(attempt, breaker, now, quotaDemotion, costClassOf, latencyDemotion) {
|
|
276
|
+
export function targetUsability(attempt, breaker, now, quotaDemotion, costClassOf, latencyDemotion, probation) {
|
|
212
277
|
const identity = targetIdentity(attempt);
|
|
213
278
|
if (!breaker.isHealthy(identity, now))
|
|
214
279
|
return "cooling";
|
|
@@ -220,6 +285,8 @@ export function targetUsability(attempt, breaker, now, quotaDemotion, costClassO
|
|
|
220
285
|
return "credential-fault";
|
|
221
286
|
if (latencyDemotion?.(attempt, now))
|
|
222
287
|
return "slow";
|
|
288
|
+
if (probation?.(attempt, now))
|
|
289
|
+
return "probation";
|
|
223
290
|
return "live";
|
|
224
291
|
}
|
|
225
292
|
export function expandCredentialAttempts(targets) {
|
|
@@ -260,7 +327,7 @@ export function credentialEvidence(attempt, cfg, breaker, now) {
|
|
|
260
327
|
cost,
|
|
261
328
|
};
|
|
262
329
|
}
|
|
263
|
-
|
|
330
|
+
function coolingLiftTime(attempt, breaker, now, costClassOf) {
|
|
264
331
|
const identity = targetIdentity(attempt);
|
|
265
332
|
const breakerState = breaker.getState(identity);
|
|
266
333
|
if (breakerState && breakerState.cooldownUntil > now) {
|
|
@@ -276,15 +343,19 @@ export function coolingLiftTime(attempt, breaker, now, costClassOf) {
|
|
|
276
343
|
}
|
|
277
344
|
return null;
|
|
278
345
|
}
|
|
279
|
-
export function orderDeploymentGroupsByUsability(attempts, breaker, now, quotaDemotion, costClassOf, latencyDemotion) {
|
|
346
|
+
export function orderDeploymentGroupsByUsability(attempts, breaker, now, quotaDemotion, costClassOf, latencyDemotion, probation) {
|
|
280
347
|
const preferred = groupCredentialAttempts(attempts)[0]?.attempts[0];
|
|
348
|
+
const probationGroups = [];
|
|
281
349
|
const live = [];
|
|
282
350
|
const slow = [];
|
|
283
351
|
const faulted = [];
|
|
284
352
|
const cooling = [];
|
|
285
353
|
for (const group of groupCredentialAttempts(attempts)) {
|
|
286
|
-
const usability = targetUsability(group.attempts[0], breaker, now, quotaDemotion, costClassOf, latencyDemotion);
|
|
354
|
+
const usability = targetUsability(group.attempts[0], breaker, now, quotaDemotion, costClassOf, latencyDemotion, probation);
|
|
287
355
|
switch (usability) {
|
|
356
|
+
case "probation":
|
|
357
|
+
probationGroups.push(group);
|
|
358
|
+
break;
|
|
288
359
|
case "live":
|
|
289
360
|
live.push(group);
|
|
290
361
|
break;
|
|
@@ -314,7 +385,7 @@ export function orderDeploymentGroupsByUsability(attempts, breaker, now, quotaDe
|
|
|
314
385
|
return -1;
|
|
315
386
|
return liftA - liftB;
|
|
316
387
|
});
|
|
317
|
-
const ordered = [...live, ...slow, ...faulted, ...cooling].flatMap((group) => group.attempts);
|
|
388
|
+
const ordered = [...probationGroups, ...live, ...slow, ...faulted, ...cooling].flatMap((group) => group.attempts);
|
|
318
389
|
let latencyDemotedFirst = null;
|
|
319
390
|
if (preferred !== undefined &&
|
|
320
391
|
latencyDemotion !== undefined &&
|
|
@@ -394,7 +465,7 @@ export class CredentialAttemptTrace {
|
|
|
394
465
|
return headers;
|
|
395
466
|
}
|
|
396
467
|
}
|
|
397
|
-
|
|
468
|
+
function credentialAttemptLabel(outcome) {
|
|
398
469
|
if (outcome?.status !== undefined)
|
|
399
470
|
return outcome.status;
|
|
400
471
|
if (outcome?.kind === "provider-transport")
|
|
@@ -433,7 +504,18 @@ export function nextUncappedAttempt(h, walk, attemptTrace, tracker) {
|
|
|
433
504
|
walk.recordRejected(candidate);
|
|
434
505
|
}
|
|
435
506
|
}
|
|
436
|
-
|
|
507
|
+
function firstByteFetch(fetchFn, onFirstByte) {
|
|
508
|
+
let notified = false;
|
|
509
|
+
return (input, init) => {
|
|
510
|
+
const p = fetchFn(input, init);
|
|
511
|
+
if (!notified) {
|
|
512
|
+
notified = true;
|
|
513
|
+
void p.then(onFirstByte, onFirstByte);
|
|
514
|
+
}
|
|
515
|
+
return p;
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
export function beginAttemptRun(res, offer, wantsStream) {
|
|
437
519
|
const target = offer.target;
|
|
438
520
|
const controller = new AbortController();
|
|
439
521
|
const callerController = new AbortController();
|
|
@@ -447,18 +529,35 @@ export function beginAttemptRun(res, offer) {
|
|
|
447
529
|
usage: createUsageAccumulator(),
|
|
448
530
|
attempt: undefined,
|
|
449
531
|
egressCallbackCalled: false,
|
|
532
|
+
fetchFn: fetch,
|
|
450
533
|
};
|
|
534
|
+
const firstByteTimeoutMs = wantsStream ? undefined : target.firstByteTimeoutMs;
|
|
535
|
+
if (firstByteTimeoutMs !== undefined) {
|
|
536
|
+
run.firstByteTimeoutMs = firstByteTimeoutMs;
|
|
537
|
+
run.firstByteTimer = setTimeout(() => {
|
|
538
|
+
run.firstByteTimedOut = true;
|
|
539
|
+
controller.abort();
|
|
540
|
+
}, firstByteTimeoutMs);
|
|
541
|
+
run.fetchFn = firstByteFetch(fetch, () => {
|
|
542
|
+
if (run.firstByteTimer !== undefined) {
|
|
543
|
+
clearTimeout(run.firstByteTimer);
|
|
544
|
+
run.firstByteTimer = undefined;
|
|
545
|
+
}
|
|
546
|
+
});
|
|
547
|
+
}
|
|
451
548
|
res.on("close", run.onResClose);
|
|
452
549
|
return run;
|
|
453
550
|
}
|
|
454
551
|
export function releaseAttemptRun(res, run) {
|
|
455
552
|
clearTimeout(run.timer);
|
|
553
|
+
if (run.firstByteTimer !== undefined)
|
|
554
|
+
clearTimeout(run.firstByteTimer);
|
|
456
555
|
res.off("close", run.onResClose);
|
|
457
556
|
}
|
|
458
|
-
|
|
557
|
+
function settleResponse(promise) {
|
|
459
558
|
return promise.then((value) => ({ ok: true, value }), (error) => ({ ok: false, error }));
|
|
460
559
|
}
|
|
461
|
-
|
|
560
|
+
function walkWouldFailOver(response) {
|
|
462
561
|
return errorOrigin(response) !== "local" && shouldTryNext(classifyStatus(response.status));
|
|
463
562
|
}
|
|
464
563
|
const COMMIT_PROBES = new WeakMap();
|
|
@@ -636,21 +735,21 @@ export function respondAllCapped(res, h, ctx, front, tracker, attempts) {
|
|
|
636
735
|
res.end(JSON.stringify(body));
|
|
637
736
|
h.logger.write(baseLog(ctx.started, ctx.path, ctx.hadTools, ctx.streamed, 429, "skipped", null, attempts));
|
|
638
737
|
}
|
|
639
|
-
export function applyStickyOrdering(ordered, pinnedSpec, breaker, degraded, now, quotaDemotion, costClassOf, latencyDemotion) {
|
|
738
|
+
export function applyStickyOrdering(ordered, pinnedSpec, breaker, degraded, now, quotaDemotion, costClassOf, latencyDemotion, probation) {
|
|
640
739
|
const groups = groupCredentialAttempts(ordered);
|
|
641
740
|
const pinnedIndex = groups.findIndex((group) => specOfTarget(group.attempts[0].target) === pinnedSpec);
|
|
642
741
|
const pinnedGroup = pinnedIndex < 0 ? undefined : groups[pinnedIndex];
|
|
643
742
|
const pinned = pinnedGroup?.attempts[0];
|
|
644
743
|
if (!pinned || !pinnedGroup)
|
|
645
744
|
return { targets: ordered, status: "bypassed: not-in-pool" };
|
|
646
|
-
const usability = targetUsability(pinned, breaker, now, quotaDemotion, costClassOf, latencyDemotion);
|
|
745
|
+
const usability = targetUsability(pinned, breaker, now, quotaDemotion, costClassOf, latencyDemotion, probation);
|
|
647
746
|
if (usability !== "live")
|
|
648
747
|
return { targets: ordered, status: `bypassed: ${usability}` };
|
|
649
748
|
if (degraded?.has(pinnedSpec)) {
|
|
650
749
|
const hasLiveInBand = groups.some((group) => {
|
|
651
750
|
const candidate = group.attempts[0];
|
|
652
751
|
return !degraded.has(specOfTarget(candidate.target)) &&
|
|
653
|
-
targetUsability(candidate, breaker, now, quotaDemotion, costClassOf, latencyDemotion) === "live";
|
|
752
|
+
targetUsability(candidate, breaker, now, quotaDemotion, costClassOf, latencyDemotion, probation) === "live";
|
|
654
753
|
});
|
|
655
754
|
if (hasLiveInBand)
|
|
656
755
|
return { targets: ordered, status: "bypassed: degraded" };
|
|
@@ -661,7 +760,7 @@ export function applyStickyOrdering(ordered, pinnedSpec, breaker, degraded, now,
|
|
|
661
760
|
.flatMap((group) => group.attempts);
|
|
662
761
|
return { targets: reordered, status: "pinned, reordered" };
|
|
663
762
|
}
|
|
664
|
-
|
|
763
|
+
function stickyHeaderValue(sticky, target, status) {
|
|
665
764
|
if (!sticky)
|
|
666
765
|
return null;
|
|
667
766
|
if (sticky.provenance)
|
|
@@ -678,18 +777,22 @@ export function recordStickySuccess(h, sticky, target, status) {
|
|
|
678
777
|
return;
|
|
679
778
|
h.stickySessions?.setPin(sticky.key, specOfTarget(target));
|
|
680
779
|
}
|
|
681
|
-
export function orderByUsability(attempts, breaker = globalCircuitBreaker, now = Date.now(), quotaDemotion, costClassOf, latencyDemotion) {
|
|
682
|
-
const { ordered } = orderByUsabilityTracked(attempts, breaker, now, quotaDemotion, costClassOf, latencyDemotion);
|
|
780
|
+
export function orderByUsability(attempts, breaker = globalCircuitBreaker, now = Date.now(), quotaDemotion, costClassOf, latencyDemotion, probation) {
|
|
781
|
+
const { ordered } = orderByUsabilityTracked(attempts, breaker, now, quotaDemotion, costClassOf, latencyDemotion, probation);
|
|
683
782
|
return ordered;
|
|
684
783
|
}
|
|
685
|
-
export function orderByUsabilityTracked(attempts, breaker, now, quotaDemotion, costClassOf, latencyDemotion) {
|
|
784
|
+
export function orderByUsabilityTracked(attempts, breaker, now, quotaDemotion, costClassOf, latencyDemotion, probation) {
|
|
785
|
+
const probationMembers = [];
|
|
686
786
|
const live = [];
|
|
687
787
|
const slow = [];
|
|
688
788
|
const faulted = [];
|
|
689
789
|
const cooling = [];
|
|
690
790
|
for (const attempt of attempts) {
|
|
691
|
-
const usability = targetUsability(attempt, breaker, now, quotaDemotion, costClassOf, latencyDemotion);
|
|
791
|
+
const usability = targetUsability(attempt, breaker, now, quotaDemotion, costClassOf, latencyDemotion, probation);
|
|
692
792
|
switch (usability) {
|
|
793
|
+
case "probation":
|
|
794
|
+
probationMembers.push(attempt);
|
|
795
|
+
break;
|
|
693
796
|
case "live":
|
|
694
797
|
live.push(attempt);
|
|
695
798
|
break;
|
|
@@ -719,7 +822,8 @@ export function orderByUsabilityTracked(attempts, breaker, now, quotaDemotion, c
|
|
|
719
822
|
return -1;
|
|
720
823
|
return liftA - liftB;
|
|
721
824
|
});
|
|
722
|
-
|
|
825
|
+
const ordered = [...probationMembers, ...live, ...slow, ...faulted, ...cooling];
|
|
826
|
+
return { ordered, quotaDemotedFirst: null, latencyDemotedFirst: null };
|
|
723
827
|
}
|
|
724
828
|
export const STATUS_VERDICT_TABLE = Object.freeze({
|
|
725
829
|
400: { outcome: "retriable", carriesEligibilityFact: true },
|
|
@@ -853,7 +957,7 @@ export class Pool429Tracker {
|
|
|
853
957
|
return Math.min(this.minRetryAfterMs, finalRetryAfterMs ?? Infinity);
|
|
854
958
|
}
|
|
855
959
|
}
|
|
856
|
-
|
|
960
|
+
function recordCall(h, attempt, ok, completedAt) {
|
|
857
961
|
const { target, usage } = attempt;
|
|
858
962
|
if (!target.model || !h.modelCallRecorder)
|
|
859
963
|
return;
|
|
@@ -915,7 +1019,7 @@ export function beginHealthAttempt(h, resolvedAttempt, started, trace, usage, ac
|
|
|
915
1019
|
completed: false,
|
|
916
1020
|
};
|
|
917
1021
|
}
|
|
918
|
-
|
|
1022
|
+
function abortOnClientClose(res, callerController, controller) {
|
|
919
1023
|
return () => {
|
|
920
1024
|
if (!res.writableEnded) {
|
|
921
1025
|
callerController.abort();
|
|
@@ -923,7 +1027,7 @@ export function abortOnClientClose(res, callerController, controller) {
|
|
|
923
1027
|
}
|
|
924
1028
|
};
|
|
925
1029
|
}
|
|
926
|
-
|
|
1030
|
+
function observeContextLimit(status, target, body) {
|
|
927
1031
|
if (status !== 400 && status !== 413)
|
|
928
1032
|
return;
|
|
929
1033
|
if (target.model === undefined)
|
|
@@ -939,7 +1043,7 @@ export function observeContextLimit(status, target, body) {
|
|
|
939
1043
|
catch {
|
|
940
1044
|
}
|
|
941
1045
|
}
|
|
942
|
-
|
|
1046
|
+
function observeMaxOutput(status, target, body) {
|
|
943
1047
|
if (status !== 400 && status !== 413)
|
|
944
1048
|
return;
|
|
945
1049
|
if (target.model === undefined)
|
|
@@ -955,7 +1059,7 @@ export function observeMaxOutput(status, target, body) {
|
|
|
955
1059
|
catch {
|
|
956
1060
|
}
|
|
957
1061
|
}
|
|
958
|
-
|
|
1062
|
+
function observeStatedRateLimits(attempt, observations) {
|
|
959
1063
|
if (observations.length === 0)
|
|
960
1064
|
return;
|
|
961
1065
|
const { target } = attempt;
|
|
@@ -975,7 +1079,7 @@ export function observeStatedRateLimits(attempt, observations) {
|
|
|
975
1079
|
catch {
|
|
976
1080
|
}
|
|
977
1081
|
}
|
|
978
|
-
|
|
1082
|
+
function observeRateLimit(attempt, status, body) {
|
|
979
1083
|
if (status !== 429)
|
|
980
1084
|
return;
|
|
981
1085
|
if (attempt.target.model === undefined)
|
|
@@ -991,7 +1095,7 @@ export function observeRateLimit(attempt, status, body) {
|
|
|
991
1095
|
catch {
|
|
992
1096
|
}
|
|
993
1097
|
}
|
|
994
|
-
|
|
1098
|
+
function refusalBodyCandidates(body) {
|
|
995
1099
|
const candidates = [];
|
|
996
1100
|
const queued = [body];
|
|
997
1101
|
const seen = new Set();
|
|
@@ -1196,6 +1300,7 @@ export function markAttemptCommitted(attempt) {
|
|
|
1196
1300
|
export function completeAttemptSuccess(h, attempt, status) {
|
|
1197
1301
|
if (attempt.completed)
|
|
1198
1302
|
return;
|
|
1303
|
+
const costClass = h.costClassOf?.(attempt.resolvedAttempt);
|
|
1199
1304
|
const completedAt = Date.now();
|
|
1200
1305
|
const result = h.breaker.completeAttempt(attempt.handle, {
|
|
1201
1306
|
terminal: "succeeded",
|
|
@@ -1213,7 +1318,7 @@ export function completeAttemptSuccess(h, attempt, status) {
|
|
|
1213
1318
|
recordCall(h, attempt, true, completedAt);
|
|
1214
1319
|
attempt.accounting?.complete(attempt.accountingAttempt, "success", null, attempt.usage, completedAt);
|
|
1215
1320
|
try {
|
|
1216
|
-
const cleared = clearFacts(attempt.target.provider, attempt.resolvedAttempt.credentialId, attempt.target.model ?? null);
|
|
1321
|
+
const cleared = clearFacts(attempt.target.provider, attempt.resolvedAttempt.credentialId, attempt.target.model ?? null, { costClass });
|
|
1217
1322
|
if (cleared.includes("credential-invalid")) {
|
|
1218
1323
|
h.breaker.clearCredentialFaults(attempt.resolvedAttempt.credentialId);
|
|
1219
1324
|
}
|
|
@@ -1246,10 +1351,10 @@ export function completeAttemptFailure(h, attempt, options) {
|
|
|
1246
1351
|
export function completeAttemptCancelled(h, attempt, reason) {
|
|
1247
1352
|
completeCancellation(h, attempt, reason, attempt.committed ? "client-gone-mid-response" : "client-gone-before-response");
|
|
1248
1353
|
}
|
|
1249
|
-
|
|
1354
|
+
function completeAttemptAbandoned(h, attempt, reason) {
|
|
1250
1355
|
completeCancellation(h, attempt, reason, "relay-abandoned");
|
|
1251
1356
|
}
|
|
1252
|
-
|
|
1357
|
+
function completeCancellation(h, attempt, reason, cause) {
|
|
1253
1358
|
if (attempt.completed)
|
|
1254
1359
|
return;
|
|
1255
1360
|
const completedAt = Date.now();
|