llm-relay 0.80.0 → 0.82.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/backend.d.ts +43 -3
- package/dist/backend.js +0 -0
- package/dist/backend.js.map +1 -1
- package/dist/candidate-runner.d.ts +45 -9
- package/dist/candidate-runner.js +55 -22
- package/dist/candidate-runner.js.map +1 -1
- package/dist/catalog.d.ts +49 -0
- package/dist/catalog.js +18 -0
- package/dist/catalog.js.map +1 -1
- package/dist/circuit-breaker.d.ts +118 -1
- package/dist/circuit-breaker.js +96 -1
- package/dist/circuit-breaker.js.map +1 -1
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +23 -3
- package/dist/cli.js.map +1 -1
- package/dist/config/routing-parser.d.ts +1 -1
- package/dist/config/routing-parser.js +27 -1
- package/dist/config/routing-parser.js.map +1 -1
- package/dist/config-edit.js +70 -16
- package/dist/config-edit.js.map +1 -1
- package/dist/config-types.d.ts +62 -6
- package/dist/config-types.js +1 -1
- package/dist/config-types.js.map +1 -1
- package/dist/dashboard/.vite/manifest.json +2 -2
- package/dist/dashboard/assets/index-B2UQHoym.js +74 -0
- package/dist/dashboard/assets/index-DFEP3XPr.css +1 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/dispatch-lane-stats.d.ts +59 -5
- package/dist/dispatch-lane-stats.js +96 -29
- package/dist/dispatch-lane-stats.js.map +1 -1
- package/dist/dispatch.d.ts +143 -1
- package/dist/dispatch.js +260 -98
- package/dist/dispatch.js.map +1 -1
- package/dist/lane-affinity.d.ts +8 -0
- package/dist/lane-affinity.js +10 -0
- package/dist/lane-affinity.js.map +1 -1
- package/dist/log.d.ts +16 -0
- package/dist/log.js +1 -0
- package/dist/log.js.map +1 -1
- package/dist/mcp/agy-quota-log.d.ts +37 -0
- package/dist/mcp/agy-quota-log.js +68 -0
- package/dist/mcp/agy-quota-log.js.map +1 -0
- package/dist/mcp/job-archive.d.ts +55 -0
- package/dist/mcp/job-archive.js +151 -0
- package/dist/mcp/job-archive.js.map +1 -0
- package/dist/mcp/lane-runner.d.ts +108 -5
- package/dist/mcp/lane-runner.js +93 -4
- package/dist/mcp/lane-runner.js.map +1 -1
- package/dist/mcp/readonly-boundary.d.ts +46 -0
- package/dist/mcp/readonly-boundary.js +119 -0
- package/dist/mcp/readonly-boundary.js.map +1 -1
- package/dist/mcp/server.d.ts +132 -8
- package/dist/mcp/server.js +267 -68
- package/dist/mcp/server.js.map +1 -1
- package/dist/openai-request.d.ts +8 -0
- package/dist/openai-request.js +33 -9
- package/dist/openai-request.js.map +1 -1
- package/dist/pacing.d.ts +125 -0
- package/dist/pacing.js +83 -0
- package/dist/pacing.js.map +1 -0
- package/dist/ping/cadence.d.ts +57 -0
- package/dist/ping/cadence.js +58 -0
- package/dist/ping/cadence.js.map +1 -1
- package/dist/refusal-interpretation.d.ts +24 -0
- package/dist/refusal-interpretation.js +10 -1
- package/dist/refusal-interpretation.js.map +1 -1
- package/dist/responses-request.d.ts +14 -1
- package/dist/responses-request.js +21 -4
- package/dist/responses-request.js.map +1 -1
- package/dist/routes/admin.d.ts +8 -0
- package/dist/routes/admin.js +122 -35
- package/dist/routes/admin.js.map +1 -1
- package/dist/routes/messages.d.ts +2 -0
- package/dist/routes/messages.js +7 -3
- package/dist/routes/messages.js.map +1 -1
- package/dist/routes/openai-front.d.ts +1 -0
- package/dist/routes/openai-front.js +7 -3
- package/dist/routes/openai-front.js.map +1 -1
- package/dist/server.d.ts +4 -0
- package/dist/server.js +28 -4
- package/dist/server.js.map +1 -1
- package/dist/setup-claude.d.ts +2 -2
- package/dist/setup-claude.js +3 -3
- package/dist/stream-commit.d.ts +65 -0
- package/dist/stream-commit.js +150 -8
- package/dist/stream-commit.js.map +1 -1
- package/dist/target-facts.js +3 -0
- package/dist/target-facts.js.map +1 -1
- package/dist/telemetry.d.ts +9 -1
- package/dist/telemetry.js +1 -1
- package/dist/telemetry.js.map +1 -1
- package/package.json +1 -1
- package/scripts/install-skill.mjs +2 -2
- package/skills/llm-relay/SKILL.md +5 -3
- package/skills/llm-relay/references/dispatch-lanes.md +8 -2
- package/dist/dashboard/assets/index-Bqtx14ES.css +0 -1
- package/dist/dashboard/assets/index-CetZdLCA.js +0 -74
|
@@ -40,6 +40,7 @@ import { type QuotaObservation } from "./quota-observation.js";
|
|
|
40
40
|
import { type FactResetBasis } from "./target-facts.js";
|
|
41
41
|
import { type QuotaDemotionFn } from "./quota-demotion.js";
|
|
42
42
|
import { type LatencyDemotionFn } from "./latency-demotion.js";
|
|
43
|
+
import { type PacingFn } from "./pacing.js";
|
|
43
44
|
import type { HedgeDelayDecision } from "./hedge-trigger.js";
|
|
44
45
|
import { type Settled } from "./hedge-race.js";
|
|
45
46
|
import { type HardCapVerdict } from "./hard-cap.js";
|
|
@@ -62,7 +63,13 @@ export interface StickyRequestContext {
|
|
|
62
63
|
/** The previously stored pin's routing evaluation; null means a new pin may be created. */
|
|
63
64
|
provenance: string | null;
|
|
64
65
|
}
|
|
65
|
-
|
|
66
|
+
/**
|
|
67
|
+
* The walk's health bands, best first: `probation` → `live` → `slow` → `paced` →
|
|
68
|
+
* `credential-fault` → `cooling`. `paced` (2026-09-15, `pacing.ts`) sits behind `slow` because a
|
|
69
|
+
* slow member most likely answers while one at its stated ceiling most likely 429s — and ahead of
|
|
70
|
+
* the failure bands because it is healthy, merely full for the moment.
|
|
71
|
+
*/
|
|
72
|
+
export type TargetUsability = "live" | "slow" | "paced" | "credential-fault" | "cooling" | "probation";
|
|
66
73
|
type OutcomeClass = "ok" | "retriable" | "credential" | "client";
|
|
67
74
|
export declare const DEFAULT_WALK_BUDGET_MS = 45000;
|
|
68
75
|
export declare const DEFAULT_STALL_TIMEOUT_MS = 90000;
|
|
@@ -80,6 +87,20 @@ export interface CandidateRunnerHandlers {
|
|
|
80
87
|
* site that omits it silently disables the band there.
|
|
81
88
|
*/
|
|
82
89
|
probation?: ProbationFn | null;
|
|
90
|
+
/**
|
|
91
|
+
* Evidence that a provider's own roster has moved: a 404 stating that a model THIS RELAY LISTS
|
|
92
|
+
* does not exist. The request path wires it to `ModelCatalog.noteProviderStale`, which re-fetches
|
|
93
|
+
* that provider's `/models` behind a per-provider cooldown.
|
|
94
|
+
*
|
|
95
|
+
* Optional, like `probation` and for the same reason — hand-built handler literals in tests keep
|
|
96
|
+
* compiling — and absent reads as "no trigger", which is the pre-existing behaviour: the catalog
|
|
97
|
+
* waits for its TTL. `createProxy` always sets it.
|
|
98
|
+
*
|
|
99
|
+
* ⚠ The handler is handed the attempt, not a provider name, so the containment the trigger needs
|
|
100
|
+
* — "on a model the catalog currently lists" — is decided where the catalog is, and not by a
|
|
101
|
+
* caller that would have to re-derive it.
|
|
102
|
+
*/
|
|
103
|
+
catalogStale?: ((attempt: ResolvedAttempt) => void) | null;
|
|
83
104
|
}
|
|
84
105
|
interface ServedAnnouncementContext {
|
|
85
106
|
readonly target: ResolvedTarget;
|
|
@@ -91,6 +112,7 @@ interface ServedAnnouncementContext {
|
|
|
91
112
|
readonly degraded?: string | null | undefined;
|
|
92
113
|
readonly quotaDemoted?: string | null | undefined;
|
|
93
114
|
readonly latencyDemoted?: string | null | undefined;
|
|
115
|
+
readonly paced?: string | null | undefined;
|
|
94
116
|
readonly probation?: string | null | undefined;
|
|
95
117
|
readonly hedged?: string | null | undefined;
|
|
96
118
|
readonly paid?: string | null | undefined;
|
|
@@ -174,7 +196,7 @@ export declare function probationLabel(spec: string, verdict: ProbationVerdict):
|
|
|
174
196
|
export declare function probationLabelForAttempt(h: {
|
|
175
197
|
probation?: ProbationFn | null;
|
|
176
198
|
}, attempt: ResolvedAttempt, now: number): string | null;
|
|
177
|
-
export declare function targetUsability(attempt: ResolvedAttempt, breaker: CircuitBreaker, now: number, quotaDemotion?: QuotaDemotionFn | null, costClassOf?: CostClassFn | null, latencyDemotion?: LatencyDemotionFn | null, probation?: ProbationFn | null): TargetUsability;
|
|
199
|
+
export declare function targetUsability(attempt: ResolvedAttempt, breaker: CircuitBreaker, now: number, quotaDemotion?: QuotaDemotionFn | null, costClassOf?: CostClassFn | null, latencyDemotion?: LatencyDemotionFn | null, probation?: ProbationFn | null, pacing?: PacingFn | null): TargetUsability;
|
|
178
200
|
export declare function expandCredentialAttempts(targets: readonly ResolvedTarget[]): ResolvedAttempt[];
|
|
179
201
|
export declare function credentialEvidence(attempt: ResolvedAttempt, cfg: Config, breaker: CircuitBreaker, now: number): {
|
|
180
202
|
facts: {
|
|
@@ -191,10 +213,11 @@ export declare function credentialEvidence(attempt: ResolvedAttempt, cfg: Config
|
|
|
191
213
|
quota: QuotaObservation[];
|
|
192
214
|
cost: "free" | "unknown" | "paid";
|
|
193
215
|
};
|
|
194
|
-
export declare function orderDeploymentGroupsByUsability(attempts: readonly ResolvedAttempt[], breaker: CircuitBreaker, now: number, quotaDemotion?: QuotaDemotionFn | null, costClassOf?: CostClassFn | null, latencyDemotion?: LatencyDemotionFn | null, probation?: ProbationFn | null): {
|
|
216
|
+
export declare function orderDeploymentGroupsByUsability(attempts: readonly ResolvedAttempt[], breaker: CircuitBreaker, now: number, quotaDemotion?: QuotaDemotionFn | null, costClassOf?: CostClassFn | null, latencyDemotion?: LatencyDemotionFn | null, probation?: ProbationFn | null, pacing?: PacingFn | null): {
|
|
195
217
|
ordered: ResolvedAttempt[];
|
|
196
218
|
quotaDemotedFirst: string | null;
|
|
197
219
|
latencyDemotedFirst: string | null;
|
|
220
|
+
pacedFirst: string | null;
|
|
198
221
|
};
|
|
199
222
|
export declare class CredentialAttemptTrace {
|
|
200
223
|
private readonly cfg;
|
|
@@ -320,6 +343,12 @@ interface WalkExitData {
|
|
|
320
343
|
errorOrigin?: ErrorOrigin;
|
|
321
344
|
servedBy?: string;
|
|
322
345
|
shouldTryNext?: boolean;
|
|
346
|
+
/**
|
|
347
|
+
* The dead stream's classified cause, when the backend stated one — see `stream-commit.ts`
|
|
348
|
+
* `stopCauseToken`. The WIRE half is already carried by `message`; this is the half that reaches
|
|
349
|
+
* the metadata log, and it is an enum-like token rather than any part of the backend's words.
|
|
350
|
+
*/
|
|
351
|
+
streamStopCause?: string | undefined;
|
|
323
352
|
}
|
|
324
353
|
export declare function walkExitHeaders(tracker: Pool429Tracker, sticky: StickyRequestContext | null | undefined, credentialTrace: CredentialAttemptTrace, { errorOrigin, errorType, servedBy, }?: Pick<WalkExitData, "errorOrigin" | "errorType" | "servedBy">): Record<string, string>;
|
|
325
354
|
export declare function endWalk(h: {
|
|
@@ -334,7 +363,7 @@ export declare function respondAllCapped(res: ServerResponse, h: {
|
|
|
334
363
|
hadTools: boolean;
|
|
335
364
|
streamed: boolean;
|
|
336
365
|
}, front: "anthropic" | "openai", tracker: Pool429Tracker, attempts: readonly RequestAttemptLog[]): void;
|
|
337
|
-
export declare function applyStickyOrdering(ordered: ResolvedAttempt[], pinnedSpec: string, breaker: CircuitBreaker, degraded: Set<string> | null, now: number, quotaDemotion?: QuotaDemotionFn | null, costClassOf?: CostClassFn | null, latencyDemotion?: LatencyDemotionFn | null, probation?: ProbationFn | null): {
|
|
366
|
+
export declare function applyStickyOrdering(ordered: ResolvedAttempt[], pinnedSpec: string, breaker: CircuitBreaker, degraded: Set<string> | null, now: number, quotaDemotion?: QuotaDemotionFn | null, costClassOf?: CostClassFn | null, latencyDemotion?: LatencyDemotionFn | null, probation?: ProbationFn | null, pacing?: PacingFn | null): {
|
|
338
367
|
targets: ResolvedAttempt[];
|
|
339
368
|
status: string;
|
|
340
369
|
};
|
|
@@ -374,11 +403,12 @@ export declare function recordStickySuccess(h: {
|
|
|
374
403
|
* hedged (`hedge-trigger.ts`: unmeasured IS hedged), so a probation member that hangs costs one
|
|
375
404
|
* hedge, not a timeout — no second mechanism is added here.
|
|
376
405
|
*/
|
|
377
|
-
export declare function orderByUsability(attempts: ResolvedAttempt[], breaker?: CircuitBreaker, now?: number, quotaDemotion?: QuotaDemotionFn | null, costClassOf?: CostClassFn | null, latencyDemotion?: LatencyDemotionFn | null, probation?: ProbationFn | null): ResolvedAttempt[];
|
|
378
|
-
export declare function orderByUsabilityTracked(attempts: ResolvedAttempt[], breaker: CircuitBreaker, now: number, quotaDemotion?: QuotaDemotionFn | null, costClassOf?: CostClassFn | null, latencyDemotion?: LatencyDemotionFn | null, probation?: ProbationFn | null): {
|
|
406
|
+
export declare function orderByUsability(attempts: ResolvedAttempt[], breaker?: CircuitBreaker, now?: number, quotaDemotion?: QuotaDemotionFn | null, costClassOf?: CostClassFn | null, latencyDemotion?: LatencyDemotionFn | null, probation?: ProbationFn | null, pacing?: PacingFn | null): ResolvedAttempt[];
|
|
407
|
+
export declare function orderByUsabilityTracked(attempts: ResolvedAttempt[], breaker: CircuitBreaker, now: number, quotaDemotion?: QuotaDemotionFn | null, costClassOf?: CostClassFn | null, latencyDemotion?: LatencyDemotionFn | null, probation?: ProbationFn | null, pacing?: PacingFn | null): {
|
|
379
408
|
ordered: ResolvedAttempt[];
|
|
380
409
|
quotaDemotedFirst: string | null;
|
|
381
410
|
latencyDemotedFirst: string | null;
|
|
411
|
+
pacedFirst: string | null;
|
|
382
412
|
};
|
|
383
413
|
/** What one HTTP status means to the walk: how to classify the outcome, and whether the body may
|
|
384
414
|
* carry an eligibility fact worth interpreting. */
|
|
@@ -464,14 +494,20 @@ export interface HealthAttempt {
|
|
|
464
494
|
terminal?: "succeeded" | "failed" | "cancelled";
|
|
465
495
|
}
|
|
466
496
|
export declare function targetIdentity(attempt: ResolvedAttempt): ProviderTargetIdentity;
|
|
497
|
+
/**
|
|
498
|
+
* Begin the breaker attempt for one egress. `estimatedInputTokens` is REQUIRED, not optional, so
|
|
499
|
+
* the compiler enumerates both fronts' call sites: it feeds the cell's attempt-start log that
|
|
500
|
+
* `pacing.ts` counts token windows over, and an optional parameter would let one front silently
|
|
501
|
+
* pace on requests alone — the one-front gap this repo's history warns about most.
|
|
502
|
+
*/
|
|
467
503
|
export declare function beginHealthAttempt(h: {
|
|
468
504
|
breaker: CircuitBreaker;
|
|
469
|
-
}, resolvedAttempt: ResolvedAttempt, started: number, trace: RequestAttemptTrace, usage: UsageAccumulator, accounting: RequestAccountingState | null): HealthAttempt | null;
|
|
505
|
+
}, resolvedAttempt: ResolvedAttempt, started: number, trace: RequestAttemptTrace, usage: UsageAccumulator, accounting: RequestAccountingState | null, estimatedInputTokens: number): HealthAttempt | null;
|
|
470
506
|
type EligibilityObservation = {
|
|
471
507
|
readonly unknown: boolean;
|
|
472
508
|
readonly scope?: ReturnType<typeof materializeScope>;
|
|
473
509
|
};
|
|
474
|
-
export declare function observeEligibility(attempt: ResolvedAttempt, status: number, retryAfterMs: number | null, body: string): EligibilityObservation;
|
|
510
|
+
export declare function observeEligibility(attempt: ResolvedAttempt, status: number, retryAfterMs: number | null, body: string, h?: Pick<CandidateRunnerHandlers, "catalogStale">): EligibilityObservation;
|
|
475
511
|
export declare function freeOnlyApplies(rule: {
|
|
476
512
|
freeOnly?: boolean;
|
|
477
513
|
}, rerouted: boolean): boolean;
|
|
@@ -485,7 +521,7 @@ type InspectedCandidateResponse = {
|
|
|
485
521
|
response: Response;
|
|
486
522
|
eligibility: EligibilityObservation;
|
|
487
523
|
} | PostHeaderBodyFailure;
|
|
488
|
-
export declare function inspectCandidateResponse(res: Response, attempt: ResolvedAttempt, retryAfterMs: number | null): Promise<InspectedCandidateResponse>;
|
|
524
|
+
export declare function inspectCandidateResponse(res: Response, attempt: ResolvedAttempt, retryAfterMs: number | null, h?: Pick<CandidateRunnerHandlers, "catalogStale">): Promise<InspectedCandidateResponse>;
|
|
489
525
|
export declare function walkOutcomeForResponse(status: number, localFailure: boolean, scope?: EligibilityObservation["scope"]): CredentialWalkOutcome;
|
|
490
526
|
export declare function observeAttemptHeaders(h: {
|
|
491
527
|
breaker: CircuitBreaker;
|
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, PROBATION_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, PACED_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";
|
|
@@ -18,9 +18,10 @@ import { looksLikeRateLimitError, parseStatedRateLimit, recordObservedRateLimit
|
|
|
18
18
|
import { clearFacts, cooldownUntil, factsFor, recordFact } from "./target-facts.js";
|
|
19
19
|
import { quotaDemotionLabel } from "./quota-demotion.js";
|
|
20
20
|
import { latencyDemotionLabel } from "./latency-demotion.js";
|
|
21
|
+
import { pacingLabel } from "./pacing.js";
|
|
21
22
|
import { raceWithHedge } from "./hedge-race.js";
|
|
22
23
|
import { hardCapLabel } from "./hard-cap.js";
|
|
23
|
-
import { applyResetRule, interpretRefusal, materializeScope, parseStatedResetMs, recordUnknownRefusal, } from "./refusal-interpretation.js";
|
|
24
|
+
import { applyResetRule, interpretRefusal, materializeScope, parseStatedResetMs, recordUnknownRefusal, statesModelDoesNotExist, } from "./refusal-interpretation.js";
|
|
24
25
|
import { DEFAULT_ANTHROPIC_VERSION } from "./config.js";
|
|
25
26
|
import { buildAuthHeaders } from "./authEnv.js";
|
|
26
27
|
import { CONTROL_AUTHORIZATION_HEADER } from "./control-authorization.js";
|
|
@@ -124,6 +125,8 @@ export function responseHeadersForTarget(backendRes, ctx) {
|
|
|
124
125
|
responseHeaders[QUOTA_DEMOTED_HEADER] = ctx.quotaDemoted;
|
|
125
126
|
if (ctx.latencyDemoted)
|
|
126
127
|
responseHeaders[LATENCY_DEMOTED_HEADER] = ctx.latencyDemoted;
|
|
128
|
+
if (ctx.paced)
|
|
129
|
+
responseHeaders[PACED_HEADER] = ctx.paced;
|
|
127
130
|
if (ctx.probation)
|
|
128
131
|
responseHeaders[PROBATION_HEADER] = ctx.probation;
|
|
129
132
|
if (ctx.hedged)
|
|
@@ -273,7 +276,7 @@ function cooledByQuota(attempt, breaker, quotaDemotion, now) {
|
|
|
273
276
|
breaker.recordQuotaCooldown(targetIdentity(attempt), spent.resetsAt, now);
|
|
274
277
|
return true;
|
|
275
278
|
}
|
|
276
|
-
export function targetUsability(attempt, breaker, now, quotaDemotion, costClassOf, latencyDemotion, probation) {
|
|
279
|
+
export function targetUsability(attempt, breaker, now, quotaDemotion, costClassOf, latencyDemotion, probation, pacing) {
|
|
277
280
|
const identity = targetIdentity(attempt);
|
|
278
281
|
if (!breaker.isHealthy(identity, now))
|
|
279
282
|
return "cooling";
|
|
@@ -283,6 +286,8 @@ export function targetUsability(attempt, breaker, now, quotaDemotion, costClassO
|
|
|
283
286
|
return "cooling";
|
|
284
287
|
if (breaker.hasCredentialFault(identity, now))
|
|
285
288
|
return "credential-fault";
|
|
289
|
+
if (pacing?.(attempt, now))
|
|
290
|
+
return "paced";
|
|
286
291
|
if (latencyDemotion?.(attempt, now))
|
|
287
292
|
return "slow";
|
|
288
293
|
if (probation?.(attempt, now))
|
|
@@ -343,15 +348,16 @@ function coolingLiftTime(attempt, breaker, now, costClassOf) {
|
|
|
343
348
|
}
|
|
344
349
|
return null;
|
|
345
350
|
}
|
|
346
|
-
export function orderDeploymentGroupsByUsability(attempts, breaker, now, quotaDemotion, costClassOf, latencyDemotion, probation) {
|
|
351
|
+
export function orderDeploymentGroupsByUsability(attempts, breaker, now, quotaDemotion, costClassOf, latencyDemotion, probation, pacing) {
|
|
347
352
|
const preferred = groupCredentialAttempts(attempts)[0]?.attempts[0];
|
|
348
353
|
const probationGroups = [];
|
|
349
354
|
const live = [];
|
|
350
355
|
const slow = [];
|
|
356
|
+
const paced = [];
|
|
351
357
|
const faulted = [];
|
|
352
358
|
const cooling = [];
|
|
353
359
|
for (const group of groupCredentialAttempts(attempts)) {
|
|
354
|
-
const usability = targetUsability(group.attempts[0], breaker, now, quotaDemotion, costClassOf, latencyDemotion, probation);
|
|
360
|
+
const usability = targetUsability(group.attempts[0], breaker, now, quotaDemotion, costClassOf, latencyDemotion, probation, pacing);
|
|
355
361
|
switch (usability) {
|
|
356
362
|
case "probation":
|
|
357
363
|
probationGroups.push(group);
|
|
@@ -362,6 +368,9 @@ export function orderDeploymentGroupsByUsability(attempts, breaker, now, quotaDe
|
|
|
362
368
|
case "slow":
|
|
363
369
|
slow.push(group);
|
|
364
370
|
break;
|
|
371
|
+
case "paced":
|
|
372
|
+
paced.push(group);
|
|
373
|
+
break;
|
|
365
374
|
case "credential-fault":
|
|
366
375
|
faulted.push(group);
|
|
367
376
|
break;
|
|
@@ -385,7 +394,17 @@ export function orderDeploymentGroupsByUsability(attempts, breaker, now, quotaDe
|
|
|
385
394
|
return -1;
|
|
386
395
|
return liftA - liftB;
|
|
387
396
|
});
|
|
388
|
-
const ordered = [...probationGroups, ...live, ...slow, ...faulted, ...cooling].flatMap((group) => group.attempts);
|
|
397
|
+
const ordered = [...probationGroups, ...live, ...slow, ...paced, ...faulted, ...cooling].flatMap((group) => group.attempts);
|
|
398
|
+
let pacedFirst = null;
|
|
399
|
+
if (preferred !== undefined &&
|
|
400
|
+
pacing !== undefined &&
|
|
401
|
+
pacing !== null &&
|
|
402
|
+
ordered[0] !== undefined &&
|
|
403
|
+
ordered[0] !== preferred) {
|
|
404
|
+
const pacedVerdict = pacing(preferred, now);
|
|
405
|
+
if (pacedVerdict)
|
|
406
|
+
pacedFirst = pacingLabel(specOfTarget(preferred.target), pacedVerdict);
|
|
407
|
+
}
|
|
389
408
|
let latencyDemotedFirst = null;
|
|
390
409
|
if (preferred !== undefined &&
|
|
391
410
|
latencyDemotion !== undefined &&
|
|
@@ -405,7 +424,7 @@ export function orderDeploymentGroupsByUsability(attempts, breaker, now, quotaDe
|
|
|
405
424
|
quotaDemotion(preferred, now) !== null) {
|
|
406
425
|
quotaDemotedFirst = quotaDemotionLabel(specOfTarget(preferred.target), quotaDemotion(preferred, now));
|
|
407
426
|
}
|
|
408
|
-
return { ordered, quotaDemotedFirst, latencyDemotedFirst };
|
|
427
|
+
return { ordered, quotaDemotedFirst, latencyDemotedFirst, pacedFirst };
|
|
409
428
|
}
|
|
410
429
|
export class CredentialAttemptTrace {
|
|
411
430
|
cfg;
|
|
@@ -705,7 +724,8 @@ export function endWalk(h, res, front, walk, attemptTrace, tracker, status, stic
|
|
|
705
724
|
error: { message: exit.message, type: exit.errorType ?? "api_error" },
|
|
706
725
|
}));
|
|
707
726
|
}
|
|
708
|
-
|
|
727
|
+
const record = log();
|
|
728
|
+
h.logger.write(exit.streamStopCause ? { ...record, streamStopCause: exit.streamStopCause } : record);
|
|
709
729
|
return false;
|
|
710
730
|
}
|
|
711
731
|
export function respondAllCapped(res, h, ctx, front, tracker, attempts) {
|
|
@@ -735,21 +755,21 @@ export function respondAllCapped(res, h, ctx, front, tracker, attempts) {
|
|
|
735
755
|
res.end(JSON.stringify(body));
|
|
736
756
|
h.logger.write(baseLog(ctx.started, ctx.path, ctx.hadTools, ctx.streamed, 429, "skipped", null, attempts));
|
|
737
757
|
}
|
|
738
|
-
export function applyStickyOrdering(ordered, pinnedSpec, breaker, degraded, now, quotaDemotion, costClassOf, latencyDemotion, probation) {
|
|
758
|
+
export function applyStickyOrdering(ordered, pinnedSpec, breaker, degraded, now, quotaDemotion, costClassOf, latencyDemotion, probation, pacing) {
|
|
739
759
|
const groups = groupCredentialAttempts(ordered);
|
|
740
760
|
const pinnedIndex = groups.findIndex((group) => specOfTarget(group.attempts[0].target) === pinnedSpec);
|
|
741
761
|
const pinnedGroup = pinnedIndex < 0 ? undefined : groups[pinnedIndex];
|
|
742
762
|
const pinned = pinnedGroup?.attempts[0];
|
|
743
763
|
if (!pinned || !pinnedGroup)
|
|
744
764
|
return { targets: ordered, status: "bypassed: not-in-pool" };
|
|
745
|
-
const usability = targetUsability(pinned, breaker, now, quotaDemotion, costClassOf, latencyDemotion, probation);
|
|
765
|
+
const usability = targetUsability(pinned, breaker, now, quotaDemotion, costClassOf, latencyDemotion, probation, pacing);
|
|
746
766
|
if (usability !== "live")
|
|
747
767
|
return { targets: ordered, status: `bypassed: ${usability}` };
|
|
748
768
|
if (degraded?.has(pinnedSpec)) {
|
|
749
769
|
const hasLiveInBand = groups.some((group) => {
|
|
750
770
|
const candidate = group.attempts[0];
|
|
751
771
|
return !degraded.has(specOfTarget(candidate.target)) &&
|
|
752
|
-
targetUsability(candidate, breaker, now, quotaDemotion, costClassOf, latencyDemotion, probation) === "live";
|
|
772
|
+
targetUsability(candidate, breaker, now, quotaDemotion, costClassOf, latencyDemotion, probation, pacing) === "live";
|
|
753
773
|
});
|
|
754
774
|
if (hasLiveInBand)
|
|
755
775
|
return { targets: ordered, status: "bypassed: degraded" };
|
|
@@ -777,18 +797,19 @@ export function recordStickySuccess(h, sticky, target, status) {
|
|
|
777
797
|
return;
|
|
778
798
|
h.stickySessions?.setPin(sticky.key, specOfTarget(target));
|
|
779
799
|
}
|
|
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);
|
|
800
|
+
export function orderByUsability(attempts, breaker = globalCircuitBreaker, now = Date.now(), quotaDemotion, costClassOf, latencyDemotion, probation, pacing) {
|
|
801
|
+
const { ordered } = orderByUsabilityTracked(attempts, breaker, now, quotaDemotion, costClassOf, latencyDemotion, probation, pacing);
|
|
782
802
|
return ordered;
|
|
783
803
|
}
|
|
784
|
-
export function orderByUsabilityTracked(attempts, breaker, now, quotaDemotion, costClassOf, latencyDemotion, probation) {
|
|
804
|
+
export function orderByUsabilityTracked(attempts, breaker, now, quotaDemotion, costClassOf, latencyDemotion, probation, pacing) {
|
|
785
805
|
const probationMembers = [];
|
|
786
806
|
const live = [];
|
|
787
807
|
const slow = [];
|
|
808
|
+
const paced = [];
|
|
788
809
|
const faulted = [];
|
|
789
810
|
const cooling = [];
|
|
790
811
|
for (const attempt of attempts) {
|
|
791
|
-
const usability = targetUsability(attempt, breaker, now, quotaDemotion, costClassOf, latencyDemotion, probation);
|
|
812
|
+
const usability = targetUsability(attempt, breaker, now, quotaDemotion, costClassOf, latencyDemotion, probation, pacing);
|
|
792
813
|
switch (usability) {
|
|
793
814
|
case "probation":
|
|
794
815
|
probationMembers.push(attempt);
|
|
@@ -799,6 +820,9 @@ export function orderByUsabilityTracked(attempts, breaker, now, quotaDemotion, c
|
|
|
799
820
|
case "slow":
|
|
800
821
|
slow.push(attempt);
|
|
801
822
|
break;
|
|
823
|
+
case "paced":
|
|
824
|
+
paced.push(attempt);
|
|
825
|
+
break;
|
|
802
826
|
case "credential-fault":
|
|
803
827
|
faulted.push(attempt);
|
|
804
828
|
break;
|
|
@@ -822,8 +846,8 @@ export function orderByUsabilityTracked(attempts, breaker, now, quotaDemotion, c
|
|
|
822
846
|
return -1;
|
|
823
847
|
return liftA - liftB;
|
|
824
848
|
});
|
|
825
|
-
const ordered = [...probationMembers, ...live, ...slow, ...faulted, ...cooling];
|
|
826
|
-
return { ordered, quotaDemotedFirst: null, latencyDemotedFirst: null };
|
|
849
|
+
const ordered = [...probationMembers, ...live, ...slow, ...paced, ...faulted, ...cooling];
|
|
850
|
+
return { ordered, quotaDemotedFirst: null, latencyDemotedFirst: null, pacedFirst: null };
|
|
827
851
|
}
|
|
828
852
|
export const STATUS_VERDICT_TABLE = Object.freeze({
|
|
829
853
|
400: { outcome: "retriable", carriesEligibilityFact: true },
|
|
@@ -1000,9 +1024,9 @@ export function targetIdentity(attempt) {
|
|
|
1000
1024
|
base: target.base,
|
|
1001
1025
|
});
|
|
1002
1026
|
}
|
|
1003
|
-
export function beginHealthAttempt(h, resolvedAttempt, started, trace, usage, accounting) {
|
|
1027
|
+
export function beginHealthAttempt(h, resolvedAttempt, started, trace, usage, accounting, estimatedInputTokens) {
|
|
1004
1028
|
const identity = targetIdentity(resolvedAttempt);
|
|
1005
|
-
const begun = h.breaker.beginAttempt(identity);
|
|
1029
|
+
const begun = h.breaker.beginAttempt(identity, { at: started, estimatedInputTokens });
|
|
1006
1030
|
if (!begun.ok)
|
|
1007
1031
|
return null;
|
|
1008
1032
|
return {
|
|
@@ -1147,11 +1171,20 @@ function refusalBodyCandidates(body) {
|
|
|
1147
1171
|
}
|
|
1148
1172
|
return candidates;
|
|
1149
1173
|
}
|
|
1150
|
-
|
|
1174
|
+
function statesModelDoesNotExistInAny(status, body) {
|
|
1175
|
+
for (const candidate of refusalBodyCandidates(body)) {
|
|
1176
|
+
if (statesModelDoesNotExist(status, candidate))
|
|
1177
|
+
return true;
|
|
1178
|
+
}
|
|
1179
|
+
return false;
|
|
1180
|
+
}
|
|
1181
|
+
export function observeEligibility(attempt, status, retryAfterMs, body, h) {
|
|
1151
1182
|
const { target } = attempt;
|
|
1152
1183
|
if (target.model === undefined)
|
|
1153
1184
|
return { unknown: false };
|
|
1154
1185
|
try {
|
|
1186
|
+
if (statesModelDoesNotExistInAny(status, body))
|
|
1187
|
+
h?.catalogStale?.(attempt);
|
|
1155
1188
|
let matchedBody = body;
|
|
1156
1189
|
let verdict = null;
|
|
1157
1190
|
for (const candidate of refusalBodyCandidates(body)) {
|
|
@@ -1202,7 +1235,7 @@ export function resolveReset(interpretation, headerMs, body) {
|
|
|
1202
1235
|
export function carriesEligibilityFact(status) {
|
|
1203
1236
|
return statusVerdict(status).carriesEligibilityFact;
|
|
1204
1237
|
}
|
|
1205
|
-
export async function inspectCandidateResponse(res, attempt, retryAfterMs) {
|
|
1238
|
+
export async function inspectCandidateResponse(res, attempt, retryAfterMs, h) {
|
|
1206
1239
|
const propagatedFailure = postHeaderBodyFailure(res);
|
|
1207
1240
|
if (propagatedFailure)
|
|
1208
1241
|
return propagatedFailure;
|
|
@@ -1222,7 +1255,7 @@ export async function inspectCandidateResponse(res, attempt, retryAfterMs) {
|
|
|
1222
1255
|
observeMaxOutput(status, attempt.target, body);
|
|
1223
1256
|
observeRateLimit(attempt, status, body);
|
|
1224
1257
|
const eligibility = carriesEligibilityFact(status) && body
|
|
1225
|
-
? observeEligibility(attempt, status, retryAfterMs, body)
|
|
1258
|
+
? observeEligibility(attempt, status, retryAfterMs, body, h)
|
|
1226
1259
|
: { unknown: false };
|
|
1227
1260
|
return {
|
|
1228
1261
|
kind: "response",
|