llm-relay 0.65.3 → 0.67.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.
Files changed (40) hide show
  1. package/dist/accounting-store-schema.d.ts +18 -0
  2. package/dist/accounting-store-schema.js +9 -2
  3. package/dist/accounting-store-schema.js.map +1 -1
  4. package/dist/accounting-store.js +4 -0
  5. package/dist/accounting-store.js.map +1 -1
  6. package/dist/accounting.d.ts +22 -0
  7. package/dist/accounting.js +6 -0
  8. package/dist/accounting.js.map +1 -1
  9. package/dist/backend.d.ts +23 -0
  10. package/dist/backend.js +1 -0
  11. package/dist/backend.js.map +1 -1
  12. package/dist/circuit-breaker.d.ts +14 -0
  13. package/dist/circuit-breaker.js +20 -1
  14. package/dist/circuit-breaker.js.map +1 -1
  15. package/dist/cli.js +63 -48
  16. package/dist/cli.js.map +1 -1
  17. package/dist/config.d.ts +48 -0
  18. package/dist/config.js +33 -0
  19. package/dist/config.js.map +1 -1
  20. package/dist/credential-select.d.ts +34 -0
  21. package/dist/credential-select.js +48 -26
  22. package/dist/credential-select.js.map +1 -1
  23. package/dist/dashboard-contract.d.ts +31 -0
  24. package/dist/dashboard-contract.js +14 -5
  25. package/dist/dashboard-contract.js.map +1 -1
  26. package/dist/dashboard-snapshot.js +3 -0
  27. package/dist/dashboard-snapshot.js.map +1 -1
  28. package/dist/hedge-race.d.ts +90 -0
  29. package/dist/hedge-race.js +48 -0
  30. package/dist/hedge-race.js.map +1 -0
  31. package/dist/hedge-trigger.d.ts +143 -0
  32. package/dist/hedge-trigger.js +55 -0
  33. package/dist/hedge-trigger.js.map +1 -0
  34. package/dist/kernel/contracts.d.ts +18 -0
  35. package/dist/ping/metrics.d.ts +22 -4
  36. package/dist/ping/metrics.js +20 -19
  37. package/dist/ping/metrics.js.map +1 -1
  38. package/dist/server.js +361 -179
  39. package/dist/server.js.map +1 -1
  40. package/package.json +1 -1
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Racing a slow attempt against the candidate the walk would have tried next.
3
+ *
4
+ * ⚠ **This module is deliberately ignorant of HTTP, of `server.ts`, and of what it is racing.** It
5
+ * is the concurrency, and nothing else. That isolation is the whole point: the walk lives in the
6
+ * least forgiving code in this repo, and a subtle mistake in "which promise won and who gets
7
+ * aborted" is not something an end-to-end proxy test reliably catches. Everything here is pinned by
8
+ * direct unit tests with injected timers, so the dangerous part is provable on its own.
9
+ *
10
+ * ⚠ **THE RACE IS DECIDED AT RESPONSE RESOLUTION, not at first content**, and that bound is
11
+ * deliberate. The measured failure this exists for —
12
+ * `nim/deepseek-ai/deepseek-v4-flash-0731`, 43 consecutive attempts at the full 120000 ms provider
13
+ * timeout — never resolved at all: no headers, no body, no tokens. Resolution is therefore exactly
14
+ * the seam where it can be beaten, and racing there is a far smaller change than racing two live
15
+ * response bodies. ⚠ The case NOT covered is "headers arrive quickly, then the body stalls": that
16
+ * needs the per-token rung and a race between two committed streams, and it is a later stage. Say
17
+ * so rather than implying this covers every slow shape.
18
+ *
19
+ * ⚠ **A loser is ABORTED, and the breaker therefore learns nothing from it** — cancellation returns
20
+ * before the provenance table in `circuit-breaker.ts`. That is a real, stated cost: hedging HIDES
21
+ * the slowness it routes around. It is acceptable only because v0.65.3 makes the requests that do
22
+ * NOT hedge cool a slow deployment for as long as it wasted, so the slowness is still learned.
23
+ */
24
+ /** One side of the race. `abort` must be idempotent and must never throw. */
25
+ export interface RaceEntrant<T> {
26
+ readonly promise: Promise<T>;
27
+ abort(): void;
28
+ }
29
+ /** A settled outcome, kept as data so a rejection never escapes the race itself. */
30
+ export type Settled<T> = {
31
+ readonly ok: true;
32
+ readonly value: T;
33
+ } | {
34
+ readonly ok: false;
35
+ readonly error: unknown;
36
+ };
37
+ export interface HedgeRaceResult<T> {
38
+ /** Whose settlement the caller must now handle. */
39
+ readonly winner: "primary" | "hedge";
40
+ readonly settled: Settled<T>;
41
+ /** Did a hedge actually start? False when the primary settled inside the delay. */
42
+ readonly hedgeStarted: boolean;
43
+ /** The loser, already aborted, or undefined when only one side ran. */
44
+ readonly loser?: RaceEntrant<T>;
45
+ }
46
+ export interface HedgeRaceOptions<T> {
47
+ readonly primary: RaceEntrant<T>;
48
+ /**
49
+ * Produce the second entrant, at the moment the delay expires — never before.
50
+ *
51
+ * ⚠ Called LATE on purpose. The walk's next candidate must be resolved against the state that
52
+ * exists when the hedge is actually needed, not against a snapshot taken before the primary ran:
53
+ * a hard cap, a quota demotion or a breaker trip may have landed in between. Returning
54
+ * `undefined` means "no candidate left", and the race degrades to awaiting the primary.
55
+ */
56
+ startHedge(): RaceEntrant<T> | undefined;
57
+ /** Milliseconds to wait for the primary before starting the hedge. */
58
+ readonly delayMs: number;
59
+ /**
60
+ * Is this settlement good enough to stop the race?
61
+ *
62
+ * ⚠ Supplied by the caller because "won" is a policy question, not a concurrency one. The walk
63
+ * passes "resolved with a status the walk would not fail over from", so a primary that answers
64
+ * 429 does NOT beat a hedge that is still running — it would have been failed over anyway.
65
+ */
66
+ isWin(settled: Settled<T>): boolean;
67
+ /** Injected for tests. Must behave like `setTimeout`/`clearTimeout`. */
68
+ readonly setTimer?: (fn: () => void, ms: number) => unknown;
69
+ readonly clearTimer?: (handle: unknown) => void;
70
+ }
71
+ /**
72
+ * Run the race.
73
+ *
74
+ * The rules, in order:
75
+ *
76
+ * 1. **Wait `delayMs` for the primary.** If it settles in that window — win or lose — return it and
77
+ * start no hedge at all. A pool whose members answer promptly therefore never duplicates, which
78
+ * is what keeps the duplicate rate tied to actual slowness rather than to traffic.
79
+ * 2. **Start the hedge.** If there is no candidate, await the primary and return it.
80
+ * 3. **The first side that settles as a WIN takes it.** The loser is aborted.
81
+ * 4. **A losing settlement does not end the race** — keep waiting for the other side. This is what
82
+ * makes a hedge useful against a primary that fails slowly.
83
+ * 5. **If both settle without winning, return the PRIMARY's settlement.** It is the ranked-first
84
+ * candidate, so its error is the one the walk has always reported, and preserving that keeps
85
+ * every existing error path and test meaningful.
86
+ *
87
+ * ⚠ `delayMs <= 0` still runs rule 1 with a zero wait rather than skipping it, so a
88
+ * misconfigured 0 cannot turn every request into an unconditional duplicate.
89
+ */
90
+ export declare function raceWithHedge<T>(opts: HedgeRaceOptions<T>): Promise<HedgeRaceResult<T>>;
@@ -0,0 +1,48 @@
1
+ function settle(p) {
2
+ return p.then((value) => ({ ok: true, value }), (error) => ({ ok: false, error }));
3
+ }
4
+ export async function raceWithHedge(opts) {
5
+ const setTimer = opts.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
6
+ const clearTimer = opts.clearTimer ?? ((h) => clearTimeout(h));
7
+ const primarySettled = settle(opts.primary.promise);
8
+ const DELAY = Symbol("hedge-delay");
9
+ let handle;
10
+ const delay = new Promise((resolve) => {
11
+ handle = setTimer(() => resolve(DELAY), Math.max(0, opts.delayMs));
12
+ });
13
+ const firstOrDelay = await Promise.race([primarySettled, delay]);
14
+ if (firstOrDelay !== DELAY) {
15
+ clearTimer(handle);
16
+ return { winner: "primary", settled: firstOrDelay, hedgeStarted: false };
17
+ }
18
+ const hedgeEntrant = opts.startHedge();
19
+ if (!hedgeEntrant) {
20
+ return { winner: "primary", settled: await primarySettled, hedgeStarted: false };
21
+ }
22
+ const hedgeSettled = settle(hedgeEntrant.promise);
23
+ const tagged = [
24
+ primarySettled.then((settled) => ({ side: "primary", settled })),
25
+ hedgeSettled.then((settled) => ({ side: "hedge", settled })),
26
+ ];
27
+ const first = await Promise.race(tagged);
28
+ if (opts.isWin(first.settled)) {
29
+ const loser = first.side === "primary" ? hedgeEntrant : opts.primary;
30
+ abortQuietly(loser);
31
+ return { winner: first.side, settled: first.settled, hedgeStarted: true, loser };
32
+ }
33
+ const other = first.side === "primary" ? await hedgeSettled : await primarySettled;
34
+ const otherSide = first.side === "primary" ? "hedge" : "primary";
35
+ if (opts.isWin(other)) {
36
+ return { winner: otherSide, settled: other, hedgeStarted: true, loser: first.side === "primary" ? opts.primary : hedgeEntrant };
37
+ }
38
+ const primaryFinal = await primarySettled;
39
+ return { winner: "primary", settled: primaryFinal, hedgeStarted: true, loser: hedgeEntrant };
40
+ }
41
+ function abortQuietly(entrant) {
42
+ try {
43
+ entrant.abort();
44
+ }
45
+ catch {
46
+ }
47
+ }
48
+ //# sourceMappingURL=hedge-race.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hedge-race.js","sourceRoot":"","sources":["../src/hedge-race.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,143 @@
1
+ /**
2
+ * When is an in-flight attempt slow enough to start the NEXT candidate beside it?
3
+ *
4
+ * ⚠ **This is the decision layer only. Nothing in this file duplicates a request, and nothing in
5
+ * `src/` calls it yet — that is a deliberate intermediate state, declared here so it is not read
6
+ * later as dead code.** The walk integration is stage 2; both request paths in `server.ts` are
7
+ * serial `while` loops built around a single `await`, and turning that into a race is a separate,
8
+ * larger change. Landing the decision first means its constants can be calibrated and its edge
9
+ * cases pinned before any request behaviour moves.
10
+ *
11
+ * ⚠⚠ **STAGE 2 IS BLOCKED ON `CredentialWalk`, and the blocker is structural — read this before
12
+ * attempting the wiring (found 2026-08-30, during that attempt).** The walk holds exactly ONE
13
+ * `#pending` slot, and that is its documented contract: *"`next()` only offers a candidate. The
14
+ * caller must call `recordStarted()` immediately before fetch/egress; that is the sole budget/LRU
15
+ * mutation boundary."* Concretely, in `credential-select.ts`:
16
+ * - `next()` opens with `if (this.#pending) return this.#pending.attempt;` — while an attempt is
17
+ * in flight it re-offers **that same attempt**, so asking for a hedge candidate hands back the
18
+ * primary and the relay would fetch one deployment twice;
19
+ * - `recordStarted()` throws `"credential attempt already marked started"` on the second call;
20
+ * - `recordOutcome()` throws when the outcome does not match the single pending attempt.
21
+ *
22
+ * So hedging is not a restructuring of the loop — it first needs `CredentialWalk` to carry N
23
+ * in-flight attempts, with its start budget, LRU touch and breadth-first ordering all still
24
+ * correct. That is a change to a component whose whole job is being the one mutation boundary, and
25
+ * it must be designed rather than patched around. Attempting the loop first would produce requests
26
+ * that THROW, which is why this was worth finding before any of `server.ts` was touched.
27
+ *
28
+ * ⚠ **Hedging DUPLICATES, it does not reorder** — the first behaviour in this relay that does. The
29
+ * `CLAUDE.md` invariant reads *"Acting on counts is optional, always announced, and may only
30
+ * reorder"*, so the duplication is bounded three ways, all of them owner decisions recorded in
31
+ * `docs/hedged-attempts-design-2026-08-30.md` §7: it is confined to deployments `assessCost()`
32
+ * calls FREE (D1), the loser is aborted the moment a winner commits, and the response announces it.
33
+ *
34
+ * WHY IT EXISTS. Measured 2026-08-30: `nim/deepseek-ai/deepseek-v4-flash-0731` hung on 43
35
+ * consecutive attempts, each costing the full 120000 ms provider timeout. The obvious remedy — a
36
+ * shorter timeout — was measured and REJECTED by the data: successful `nim` requests run from
37
+ * 559 ms to 96959 ms, so the working band reaches almost to the timeout, and a 25000 ms cap would
38
+ * have cut 22.5% of real successes. **A timeout must choose between abandoning a slow success and
39
+ * waiting out a hang; here the two are indistinguishable by duration.** A hedge does not choose.
40
+ */
41
+ import { type PingRecord } from "./ping/metrics.js";
42
+ /**
43
+ * Tunables. ⚠ **These are PLACEHOLDERS awaiting calibration, and that is stated rather than
44
+ * hidden.** `DEFAULT_LATENCY_MS_PER_TOKEN` earned its 250 from 68 real requests, and these have no
45
+ * such backing yet — the population they need is "how long did an attempt run before it produced
46
+ * its first token", which this relay does not record today. Do not quote them as measurements, and
47
+ * calibrate them against real traffic before the walk integration ships.
48
+ */
49
+ export declare const DEFAULT_HEDGE_FLOOR_MS = 20000;
50
+ export declare const DEFAULT_HEDGE_MARGIN = 2;
51
+ export declare const DEFAULT_HEDGE_MIN_SAMPLES = 5;
52
+ /** One hedge decision, carrying the evidence that produced it so the header can state it. */
53
+ export interface HedgeVerdict {
54
+ /** Which statistic decided. `floor` means no per-deployment evidence applied — see below. */
55
+ readonly basis: "per-token" | "absolute" | "floor";
56
+ /** The elapsed time that crossed the bar, in ms. */
57
+ readonly elapsedMs: number;
58
+ /** The bar it crossed, in ms. Always finite. */
59
+ readonly thresholdMs: number;
60
+ /** Output tokens seen so far. 0 for a buffered attempt, or a stream that has emitted nothing. */
61
+ readonly tokensSeen: number;
62
+ }
63
+ export interface HedgeSettings {
64
+ readonly enabled: boolean;
65
+ readonly floorMs: number;
66
+ readonly margin: number;
67
+ readonly minSamples: number;
68
+ }
69
+ export declare function resolveHedgeSettings(settings?: Partial<HedgeSettings>): HedgeSettings;
70
+ export interface HedgeInput {
71
+ /** How long this attempt has been running. */
72
+ readonly elapsedMs: number;
73
+ /** Output tokens the attempt has produced so far. 0 when it has produced nothing. */
74
+ readonly tokensSeen: number;
75
+ /** This deployment's sample window, exactly as `latency-demotion.ts` reads it. */
76
+ readonly pings: readonly PingRecord[];
77
+ /**
78
+ * Is this deployment FREE, per `assessCost()`?
79
+ *
80
+ * ⚠ Owner decision D1: hedging is on by default but confined to free deployments. `assessCost()`
81
+ * treats UNKNOWN as paid, so this is fail-safe — a duplicate can never land on a deployment whose
82
+ * price we cannot establish. The stated cost is that it will silently not fire on many members
83
+ * whose prices are simply unpublished, and that reach grows for free as catalog coverage improves.
84
+ */
85
+ readonly isFree: boolean;
86
+ }
87
+ /**
88
+ * Should a hedge start now?
89
+ *
90
+ * The ladder, in order, and each rung's reason:
91
+ *
92
+ * 1. **Disabled, or not free ⇒ never.** D1's containment, checked before anything is measured.
93
+ * 2. **Per-token, when the attempt has produced tokens AND the deployment has `minSamples` real
94
+ * request samples.** This is the owner's rule implemented literally:
95
+ * `expected = p90_ms_per_token × tokens_seen`, hedge when elapsed exceeds it by `margin`. It is
96
+ * self-correcting — a deployment producing a long answer earns more time as it produces it.
97
+ * 3. **Absolute p90 over PROBE samples**, when there is no per-token evidence but there is probe
98
+ * evidence. ⚠ Probe-only is not an optimisation: v0.65.2 exists because feeding request latency
99
+ * into a probe-calibrated statistic demotes the busiest healthy deployment first, and the same
100
+ * arithmetic would make a hedge fire on the deployment doing the most work.
101
+ * 4. **The floor**, when nothing about this deployment is measured.
102
+ *
103
+ * ⚠ **Rung 4 is why an unmeasured deployment still gets hedged, which is the opposite of the rule
104
+ * `latency-demotion.ts` follows — and the difference is deliberate.** Demotion PUNISHES, so
105
+ * unmeasured must mean "no opinion" or a cold cache would demote everything. A hedge only starts a
106
+ * second attempt that the walk was going to make anyway; the cost of hedging a deployment that was
107
+ * about to answer is one wasted free request, while the cost of NOT hedging the hang this feature
108
+ * exists for is 120 seconds. The asymmetry runs the other way, so the fallback does too.
109
+ *
110
+ * ⚠ **Every threshold is floored by `floorMs`.** Without it a deployment with a tiny p90 would be
111
+ * hedged on ordinary noise, and a fast pool would duplicate almost every request.
112
+ */
113
+ export declare function shouldHedge(input: HedgeInput, settings: HedgeSettings): HedgeVerdict | null;
114
+ /**
115
+ * How long to wait before starting the hedge, for an attempt that has produced NOTHING yet.
116
+ *
117
+ * ⚠ **The threshold IS the delay.** `shouldHedge` asks "has this attempt already run too long";
118
+ * a race asks "how long should I wait before starting the second one". They are the same number
119
+ * viewed from either side, so this returns it rather than letting a caller derive a second one.
120
+ *
121
+ * ⚠ `tokensSeen` is 0 by construction here, and that is the honest input for the case this serves:
122
+ * a race decided at RESPONSE RESOLUTION has, by definition, seen no tokens — the response has not
123
+ * arrived. The per-token rung therefore cannot apply, which is correct rather than a limitation:
124
+ * the measured hang produced no tokens for 120 s, so per-token could never have fired on it.
125
+ *
126
+ * Returns null when hedging is off or the deployment is not free — the D1 containment, applied
127
+ * once, here, so no caller repeats it.
128
+ */
129
+ export declare function hedgeDelayMs(pings: readonly PingRecord[], isFree: boolean, settings: HedgeSettings): number | null;
130
+ /**
131
+ * The same decision as `hedgeDelayMs`, carrying the RUNG that produced it.
132
+ *
133
+ * ⚠ Split out rather than folded into `hedgeDelayMs` so the shipped scalar signature is untouched.
134
+ * The basis exists for one reason: the three constants above are declared placeholders, and an
135
+ * operator cannot calibrate them without knowing which rung actually fired. `HEDGED_HEADER` states
136
+ * it, so a hedged pool is calibratable from its own responses.
137
+ */
138
+ export declare function hedgeDelayDecision(pings: readonly PingRecord[], isFree: boolean, settings: HedgeSettings): {
139
+ readonly delayMs: number;
140
+ readonly basis: HedgeVerdict["basis"];
141
+ } | null;
142
+ /** `"<spec> (28.4s > 20.0s, floor, 0 tokens seen)"` — bounded, metadata only, never content. */
143
+ export declare function hedgeLabel(spec: string, verdict: HedgeVerdict): string;
@@ -0,0 +1,55 @@
1
+ import { getP90, getP90MsPerToken, countMsPerTokenSamples } from "./ping/metrics.js";
2
+ export const DEFAULT_HEDGE_FLOOR_MS = 20_000;
3
+ export const DEFAULT_HEDGE_MARGIN = 2;
4
+ export const DEFAULT_HEDGE_MIN_SAMPLES = 5;
5
+ export function resolveHedgeSettings(settings) {
6
+ return {
7
+ enabled: settings?.enabled ?? true,
8
+ floorMs: settings?.floorMs ?? DEFAULT_HEDGE_FLOOR_MS,
9
+ margin: settings?.margin ?? DEFAULT_HEDGE_MARGIN,
10
+ minSamples: settings?.minSamples ?? DEFAULT_HEDGE_MIN_SAMPLES,
11
+ };
12
+ }
13
+ export function shouldHedge(input, settings) {
14
+ if (!settings.enabled)
15
+ return null;
16
+ if (!input.isFree)
17
+ return null;
18
+ if (!Number.isFinite(input.elapsedMs) || input.elapsedMs < 0)
19
+ return null;
20
+ const tokensSeen = Number.isFinite(input.tokensSeen) && input.tokensSeen > 0 ? input.tokensSeen : 0;
21
+ const { basis, thresholdMs } = hedgeThreshold([...input.pings], tokensSeen, settings);
22
+ return input.elapsedMs > thresholdMs
23
+ ? { basis, elapsedMs: input.elapsedMs, thresholdMs, tokensSeen }
24
+ : null;
25
+ }
26
+ function hedgeThreshold(pings, tokensSeen, settings) {
27
+ if (tokensSeen > 0 && countMsPerTokenSamples(pings) >= settings.minSamples) {
28
+ const rate = getP90MsPerToken(pings);
29
+ if (Number.isFinite(rate)) {
30
+ return { basis: "per-token", thresholdMs: Math.max(settings.floorMs, rate * tokensSeen * settings.margin) };
31
+ }
32
+ }
33
+ const probes = pings.filter((p) => p.source !== "request");
34
+ if (probes.length >= settings.minSamples) {
35
+ const p90 = getP90(probes);
36
+ if (Number.isFinite(p90)) {
37
+ return { basis: "absolute", thresholdMs: Math.max(settings.floorMs, p90 * settings.margin) };
38
+ }
39
+ }
40
+ return { basis: "floor", thresholdMs: settings.floorMs };
41
+ }
42
+ export function hedgeDelayMs(pings, isFree, settings) {
43
+ return hedgeDelayDecision(pings, isFree, settings)?.delayMs ?? null;
44
+ }
45
+ export function hedgeDelayDecision(pings, isFree, settings) {
46
+ if (!settings.enabled || !isFree)
47
+ return null;
48
+ const { basis, thresholdMs } = hedgeThreshold([...pings], 0, settings);
49
+ return { delayMs: thresholdMs, basis };
50
+ }
51
+ export function hedgeLabel(spec, verdict) {
52
+ const s = (ms) => `${(ms / 1000).toFixed(1)}s`;
53
+ return `${spec} (${s(verdict.elapsedMs)} > ${s(verdict.thresholdMs)}, ${verdict.basis}, ${verdict.tokensSeen} tokens seen)`;
54
+ }
55
+ //# sourceMappingURL=hedge-trigger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hedge-trigger.js","sourceRoot":"","sources":["../src/hedge-trigger.ts"],"names":[],"mappings":"AAwCA,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,sBAAsB,EAAmB,MAAM,mBAAmB,CAAC;AAStG,MAAM,CAAC,MAAM,sBAAsB,GAAG,MAAM,CAAC;AAC7C,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC;AACtC,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC;AAqB3C,MAAM,UAAU,oBAAoB,CAAC,QAAiC;IACpE,OAAO;QACL,OAAO,EAAE,QAAQ,EAAE,OAAO,IAAI,IAAI;QAClC,OAAO,EAAE,QAAQ,EAAE,OAAO,IAAI,sBAAsB;QACpD,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,oBAAoB;QAChD,UAAU,EAAE,QAAQ,EAAE,UAAU,IAAI,yBAAyB;KAC9D,CAAC;AACJ,CAAC;AA8CD,MAAM,UAAU,WAAW,CAAC,KAAiB,EAAE,QAAuB;IACpE,IAAI,CAAC,QAAQ,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IACnC,IAAI,CAAC,KAAK,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAC/B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,KAAK,CAAC,SAAS,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAE1E,MAAM,UAAU,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;IACpG,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,cAAc,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;IACtF,OAAO,KAAK,CAAC,SAAS,GAAG,WAAW;QAClC,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE;QAChE,CAAC,CAAC,IAAI,CAAC;AACX,CAAC;AAcD,SAAS,cAAc,CACrB,KAAmB,EACnB,UAAkB,EAClB,QAAuB;IAEvB,IAAI,UAAU,GAAG,CAAC,IAAI,sBAAsB,CAAC,KAAK,CAAC,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC;QAC3E,MAAM,IAAI,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;QACrC,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1B,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,EAAE,IAAI,GAAG,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QAC9G,CAAC;IACH,CAAC;IAGD,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC;IAC3D,IAAI,MAAM,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC;QACzC,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;QAC3B,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACzB,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,EAAE,GAAG,GAAG,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QAC/F,CAAC;IACH,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,QAAQ,CAAC,OAAO,EAAE,CAAC;AAC3D,CAAC;AAiBD,MAAM,UAAU,YAAY,CAC1B,KAA4B,EAC5B,MAAe,EACf,QAAuB;IAEvB,OAAO,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,IAAI,IAAI,CAAC;AACtE,CAAC;AAUD,MAAM,UAAU,kBAAkB,CAChC,KAA4B,EAC5B,MAAe,EACf,QAAuB;IAEvB,IAAI,CAAC,QAAQ,CAAC,OAAO,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAC9C,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,cAAc,CAAC,CAAC,GAAG,KAAK,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;IACvE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;AACzC,CAAC;AAGD,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,OAAqB;IAC5D,MAAM,CAAC,GAAG,CAAC,EAAU,EAAU,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;IAC/D,OAAO,GAAG,IAAI,KAAK,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,KAAK,OAAO,CAAC,KAAK,KAAK,OAAO,CAAC,UAAU,eAAe,CAAC;AAC9H,CAAC"}
@@ -69,8 +69,26 @@ export interface AttemptFailed extends AttemptOutcomeBase {
69
69
  readonly status: number | null;
70
70
  readonly retryAfterMs: number | null;
71
71
  }
72
+ /**
73
+ * WHO abandoned a cancelled attempt, and on which side of the response commit.
74
+ *
75
+ * `reason` below is free text for a human reading a log. This is the closed vocabulary a POLICY may
76
+ * branch on, and the two are not interchangeable: when a client disconnects mid-race the relay
77
+ * retires the hedge with the reason string "hedge loser aborted" whatever the true cause, so a
78
+ * discriminator read off that prose would misclassify exactly the case it exists to catch.
79
+ *
80
+ * - `client-gone-before-response` — the caller left and this deployment had committed NOTHING to it.
81
+ * The deployment produced no answer in the time it had.
82
+ * - `client-gone-mid-response` — the caller left while this deployment's answer was already
83
+ * reaching it. The deployment was answering; the caller changed its mind.
84
+ * - `relay-abandoned` — the RELAY aborted it (a hedge loser). It proves only that the relay stopped
85
+ * waiting, which is a statement about the relay's own policy, not about the deployment.
86
+ */
87
+ export type AttemptCancellationCause = "client-gone-before-response" | "client-gone-mid-response" | "relay-abandoned";
72
88
  export interface AttemptCancelled extends AttemptOutcomeBase {
73
89
  readonly terminal: "cancelled";
90
+ /** Required, so a new cancellation site must decide rather than inherit a default. */
91
+ readonly cause: AttemptCancellationCause;
74
92
  readonly reason: string | null;
75
93
  }
76
94
  export type AttemptOutcome = AttemptSucceeded | AttemptFailed | AttemptCancelled;
@@ -36,6 +36,14 @@ export declare const MEASURABLE_CODES: ReadonlySet<string>;
36
36
  export declare function getAvg(pings: PingRecord[]): number;
37
37
  /** Calculate 95th percentile latency from measurable pings. Returns Infinity if none. */
38
38
  export declare function getP95(pings: PingRecord[]): number;
39
+ /**
40
+ * 90th percentile latency from measurable pings. Returns Infinity if none.
41
+ *
42
+ * Added for the hedge trigger, which asks "is this attempt slower than this deployment normally
43
+ * is?" — a question p90 answers better than p95, because p95 over a short window is the worst
44
+ * sample and would almost never be exceeded.
45
+ */
46
+ export declare function getP90(pings: PingRecord[]): number;
39
47
  /**
40
48
  * 95th-percentile latency PER OUTPUT TOKEN, in ms — the only figure that compares a real
41
49
  * generation with anything else, because absolute latency scales with how much was generated.
@@ -55,11 +63,21 @@ export declare function getP95(pings: PingRecord[]): number;
55
63
  */
56
64
  export declare function getP95MsPerToken(pings: PingRecord[]): number;
57
65
  /**
58
- * How many samples actually back `getP95MsPerToken`.
66
+ * 90th-percentile ms per output token — the hedge trigger's signal.
67
+ *
68
+ * Same filter and same convention as the p95 above, so the two can never describe different
69
+ * populations. p90 rather than p95 because the question is "is THIS attempt slower than this
70
+ * deployment normally is?", and a p95 taken over a short window is the worst sample ever seen,
71
+ * which almost nothing exceeds.
72
+ */
73
+ export declare function getP90MsPerToken(pings: PingRecord[]): number;
74
+ /**
75
+ * How many samples actually back `getP95MsPerToken` and `getP90MsPerToken`.
59
76
  *
60
- * ⚠ It repeats that function's filter deliberately, and the two must stay identical: a count taken
61
- * over a WIDER set than the statistic it describes is how a sample floor comes to admit a figure
62
- * resting on one measurement.
77
+ * ⚠ It used to REPEAT their filter, under a comment saying the copies "must stay identical" — a
78
+ * requirement nothing enforced. All three now read `msPerTokenRates`, which is what enforces it.
79
+ * The reason is unchanged: a count taken over a WIDER set than the statistic it describes is how a
80
+ * sample floor comes to admit a figure resting on one measurement.
63
81
  */
64
82
  export declare function countMsPerTokenSamples(pings: PingRecord[]): number;
65
83
  /** Calculate latency standard deviation (jitter) in ms from measurable pings. */
@@ -6,15 +6,27 @@ export function getAvg(pings) {
6
6
  const sum = measurable.reduce((acc, p) => acc + p.ms, 0);
7
7
  return Math.round(sum / measurable.length);
8
8
  }
9
- export function getP95(pings) {
10
- const measurable = pings.filter((p) => MEASURABLE_CODES.has(p.code));
11
- if (measurable.length === 0)
9
+ function quantileOf(ascending, q) {
10
+ if (ascending.length === 0)
12
11
  return Infinity;
13
- const sorted = measurable.map((p) => p.ms).sort((a, b) => a - b);
14
- const idx = Math.ceil(sorted.length * 0.95) - 1;
15
- return sorted[Math.max(0, idx)];
12
+ return ascending[Math.max(0, Math.ceil(ascending.length * q) - 1)];
13
+ }
14
+ function measurableMs(pings) {
15
+ return pings.filter((p) => MEASURABLE_CODES.has(p.code)).map((p) => p.ms).sort((a, b) => a - b);
16
+ }
17
+ export function getP95(pings) {
18
+ return quantileOf(measurableMs(pings), 0.95);
19
+ }
20
+ export function getP90(pings) {
21
+ return quantileOf(measurableMs(pings), 0.9);
16
22
  }
17
23
  export function getP95MsPerToken(pings) {
24
+ return quantileOf(msPerTokenRates(pings), 0.95);
25
+ }
26
+ export function getP90MsPerToken(pings) {
27
+ return quantileOf(msPerTokenRates(pings), 0.9);
28
+ }
29
+ function msPerTokenRates(pings) {
18
30
  const rates = [];
19
31
  for (const p of pings) {
20
32
  if (p.source !== "request")
@@ -28,21 +40,10 @@ export function getP95MsPerToken(pings) {
28
40
  continue;
29
41
  rates.push(p.ms / tokens);
30
42
  }
31
- if (rates.length === 0)
32
- return Infinity;
33
- rates.sort((a, b) => a - b);
34
- const idx = Math.ceil(rates.length * 0.95) - 1;
35
- return rates[Math.max(0, idx)];
43
+ return rates.sort((a, b) => a - b);
36
44
  }
37
45
  export function countMsPerTokenSamples(pings) {
38
- return pings.filter((p) => p.source === "request" &&
39
- MEASURABLE_CODES.has(p.code) &&
40
- typeof p.tokens === "number" &&
41
- Number.isFinite(p.tokens) &&
42
- p.tokens > 0 &&
43
- typeof p.ms === "number" &&
44
- Number.isFinite(p.ms) &&
45
- p.ms >= 0).length;
46
+ return msPerTokenRates(pings).length;
46
47
  }
47
48
  export function getJitter(pings) {
48
49
  const measurable = pings.filter((p) => MEASURABLE_CODES.has(p.code));
@@ -1 +1 @@
1
- {"version":3,"file":"metrics.js","sourceRoot":"","sources":["../../src/ping/metrics.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"metrics.js","sourceRoot":"","sources":["../../src/ping/metrics.ts"],"names":[],"mappings":"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"}