@adaptic/utils 0.0.1038 → 0.0.1040

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 (47) hide show
  1. package/dist/index.cjs +1784 -345
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.mjs +1768 -346
  4. package/dist/index.mjs.map +1 -1
  5. package/dist/types/__tests__/llm/client/support/legacy-scenarios.d.ts +48 -0
  6. package/dist/types/__tests__/llm/client/support/legacy-scenarios.d.ts.map +1 -0
  7. package/dist/types/__tests__/llm/client/support/transports.d.ts +18 -0
  8. package/dist/types/__tests__/llm/client/support/transports.d.ts.map +1 -1
  9. package/dist/types/alpaca/index.d.ts.map +1 -1
  10. package/dist/types/alpaca/trading/index.d.ts +1 -0
  11. package/dist/types/alpaca/trading/index.d.ts.map +1 -1
  12. package/dist/types/alpaca/trading/trail-limits.d.ts +8 -0
  13. package/dist/types/alpaca/trading/trail-limits.d.ts.map +1 -0
  14. package/dist/types/alpaca/trading/trail-unit.d.ts +98 -0
  15. package/dist/types/alpaca/trading/trail-unit.d.ts.map +1 -0
  16. package/dist/types/alpaca/trading/trailing-stops.d.ts +28 -15
  17. package/dist/types/alpaca/trading/trailing-stops.d.ts.map +1 -1
  18. package/dist/types/alpaca-trading-api.d.ts.map +1 -1
  19. package/dist/types/index.d.ts.map +1 -1
  20. package/dist/types/llm/alias-client.d.ts +10 -1
  21. package/dist/types/llm/alias-client.d.ts.map +1 -1
  22. package/dist/types/llm/circuit-breaker.d.ts +64 -2
  23. package/dist/types/llm/circuit-breaker.d.ts.map +1 -1
  24. package/dist/types/llm/fallback-chain.d.ts +109 -23
  25. package/dist/types/llm/fallback-chain.d.ts.map +1 -1
  26. package/dist/types/llm/hedge.d.ts +107 -0
  27. package/dist/types/llm/hedge.d.ts.map +1 -0
  28. package/dist/types/llm/index.d.ts +9 -6
  29. package/dist/types/llm/index.d.ts.map +1 -1
  30. package/dist/types/llm/leg-attempt.d.ts +165 -0
  31. package/dist/types/llm/leg-attempt.d.ts.map +1 -0
  32. package/dist/types/llm/leg-latency-tracker.d.ts +126 -0
  33. package/dist/types/llm/leg-latency-tracker.d.ts.map +1 -0
  34. package/dist/types/llm/rate-guard.d.ts +17 -0
  35. package/dist/types/llm/rate-guard.d.ts.map +1 -1
  36. package/dist/types/llm/route-table.d.ts +17 -1
  37. package/dist/types/llm/route-table.d.ts.map +1 -1
  38. package/dist/types/llm/transports/gateway.d.ts +27 -2
  39. package/dist/types/llm/transports/gateway.d.ts.map +1 -1
  40. package/dist/types/llm/types.d.ts +161 -1
  41. package/dist/types/llm/types.d.ts.map +1 -1
  42. package/dist/types/schemas/alpaca-schemas.d.ts +16 -16
  43. package/dist/types/schemas/massive-schemas.d.ts +10 -10
  44. package/dist/types/trading-policy/schemas/effective-policy.schema.d.ts +6 -6
  45. package/dist/types/trading-policy/schemas/policy-mutation.schema.d.ts +12 -12
  46. package/dist/types/trading-policy/schemas/signal-consumption-prefs.schema.d.ts +8 -8
  47. package/package.json +1 -1
@@ -10,24 +10,31 @@
10
10
  * why the budget is enforced here — at the only place that knows both the
11
11
  * caller's deadline and how many legs are left to spend it on.
12
12
  *
13
+ * Each leg is first run as a group of SAME-MODEL attempts (see `hedge.ts`):
14
+ * hedged at the model's healthy p90, replaced after a measured timeout, and
15
+ * reaching the same model at another provider before the leg is given up.
16
+ * Only then does the walk move to the next leg — and a leg that serves a
17
+ * different model than the configured one runs only when the caller's
18
+ * cross-model policy allows it. With no latency evidence and no equivalent
19
+ * configured, each group is a single attempt with the leg's budget, which is
20
+ * the serial walk exactly.
21
+ *
13
22
  * Nothing here ever substitutes a value for an outcome. When every leg is
14
- * exhausted the caller gets a typed error naming each leg and why it failed,
15
- * because a default returned in place of an answer is a wrong answer that
16
- * nobody is told about.
23
+ * exhausted the caller gets a typed error naming each leg and why it failed —
24
+ * `LlmDeadlineExceededError` when the caller's deadline is what ran out,
25
+ * `ChainExhaustedError` with reason `cross_model_denied` when policy stopped
26
+ * the walk — because a default returned in place of an answer is a wrong
27
+ * answer that nobody is told about.
17
28
  *
18
29
  * @module llm/fallback-chain
19
30
  */
20
31
  import type { CircuitBreakerRegistry } from "./circuit-breaker";
21
- import { UnsupportedCapabilityError } from "./param-matrix";
22
- import type { AliasAttemptRecord, LlmTransport, LlmTransportRequest, LlmTransportResponse, LlmUsageRecord, ResolvedRoute } from "./types";
23
- /** What one leg of the chain needs in order to run. */
24
- export interface ChainLeg {
25
- readonly route: ResolvedRoute;
26
- /** The transport that will carry this leg. */
27
- readonly transport: LlmTransport;
28
- /** Provider-normalised parameters, or the error that made this leg unusable. */
29
- readonly params: Record<string, unknown> | UnsupportedCapabilityError;
30
- }
32
+ import type { AttemptFields, SameModelPolicy } from "./hedge";
33
+ import type { ChainLeg } from "./leg-attempt";
34
+ import type { LegLatencyTracker } from "./leg-latency-tracker";
35
+ import type { AliasAttemptRecord, LlmCrossModelPolicy, LlmModelClassRelation, LlmTransportRequest, LlmTransportResponse, LlmUsageRecord, ResolvedRoute } from "./types";
36
+ export { isCapacitySignal } from "./leg-attempt";
37
+ export type { ChainLeg } from "./leg-attempt";
31
38
  /** Everything the executor needs for one call. */
32
39
  export interface ChainExecution {
33
40
  readonly legs: readonly ChainLeg[];
@@ -53,8 +60,25 @@ export interface ChainExecution {
53
60
  readonly deadlineAtMs?: number;
54
61
  /** Clock, injected so elapsed time is observable in tests without waiting. */
55
62
  readonly now?: () => number;
56
- /** Invoked once per leg after it settles, for metrics and shadow comparison. */
63
+ /** Invoked once per attempt after it settles, for metrics and shadow comparison. */
57
64
  readonly onAttempt?: (record: AliasAttemptRecord) => void;
65
+ /**
66
+ * Same-model controls. Absent: every leg is one attempt with its full
67
+ * budget, as in the serial chain.
68
+ */
69
+ readonly hedging?: SameModelPolicy;
70
+ /** Healthy-latency evidence the hedging controls read, and the chain feeds. */
71
+ readonly latency?: LegLatencyTracker;
72
+ /** Whether a duplicate same-provider attempt may start; absent means never. */
73
+ readonly admitDuplicate?: (route: ResolvedRoute, reserveFraction: number) => boolean;
74
+ /** Whether a different-model leg may run. Absent means `allow_record`. */
75
+ readonly crossModelPolicy?: LlmCrossModelPolicy;
76
+ /**
77
+ * The configured model's class. Absent means the first leg's; supplied when
78
+ * the legs are a subset of the chain (the degraded direct path), whose first
79
+ * leg is not the configured model.
80
+ */
81
+ readonly configuredModelClass?: string;
58
82
  }
59
83
  /** Result of walking a chain to a successful leg. */
60
84
  export interface ChainOutcome<T> {
@@ -62,7 +86,13 @@ export interface ChainOutcome<T> {
62
86
  readonly servedBy: ResolvedRoute;
63
87
  readonly attempts: readonly AliasAttemptRecord[];
64
88
  readonly totalUsage: LlmUsageRecord;
89
+ /** Whether the answer came from the configured model. */
90
+ readonly modelClassRelation: LlmModelClassRelation;
91
+ /** Whether the answering attempt was a same-model hedge. */
92
+ readonly hedged: boolean;
65
93
  }
94
+ /** Why a chain ended without an answer. */
95
+ export type ChainExhaustionReason = "exhausted" | "cross_model_denied" | "deadline_exceeded";
66
96
  /**
67
97
  * Thrown when every leg of a chain has been tried and none produced an answer.
68
98
  *
@@ -78,12 +108,44 @@ export declare class ChainExhaustedError extends Error {
78
108
  readonly attempts: readonly AliasAttemptRecord[];
79
109
  /** Usage spent across the failed attempts, so the spend is still accounted for. */
80
110
  readonly totalUsage: LlmUsageRecord;
111
+ /**
112
+ * Why the chain ended. `cross_model_denied`: the configured model's attempts
113
+ * were spent and policy forbade a different model. Callers map every reason
114
+ * to no decision; the reason says which remedy applies.
115
+ */
116
+ readonly reason: ChainExhaustionReason;
117
+ /**
118
+ * @param alias The alias.
119
+ * @param attempts The attempt record.
120
+ * @param totalUsage Usage spent across all attempts.
121
+ * @param reason Why the chain ended; defaults to plain exhaustion.
122
+ */
123
+ constructor(alias: string, attempts: readonly AliasAttemptRecord[], totalUsage: LlmUsageRecord, reason?: ChainExhaustionReason);
124
+ }
125
+ /**
126
+ * Thrown when the caller's deadline ran out before any leg answered.
127
+ *
128
+ * A subclass of {@link ChainExhaustedError}, so a consumer that already treats
129
+ * exhaustion as "no answer" keeps doing so, while one that needs to tell "the
130
+ * models failed" from "we ran out of time" can match this class — the two call
131
+ * for different remedies (a provider problem versus a budget problem), and
132
+ * both map to no decision, never to a default.
133
+ */
134
+ export declare class LlmDeadlineExceededError extends ChainExhaustedError {
135
+ /** Discriminant for consumers that switch on shape rather than class. */
136
+ readonly kind: "deadline_exceeded";
137
+ /** The whole-call budget the chain started with, in milliseconds. */
138
+ readonly deadlineMs: number;
139
+ /** The model class of the last attempt dispatched, or null when none was. */
140
+ readonly lastModelClass: string | null;
81
141
  /**
82
142
  * @param alias The alias.
83
143
  * @param attempts The attempt record.
84
144
  * @param totalUsage Usage spent across all attempts.
145
+ * @param deadlineMs The budget the chain started with.
146
+ * @param lastModelClass The last dispatched attempt's model class.
85
147
  */
86
- constructor(alias: string, attempts: readonly AliasAttemptRecord[], totalUsage: LlmUsageRecord);
148
+ constructor(alias: string, attempts: readonly AliasAttemptRecord[], totalUsage: LlmUsageRecord, deadlineMs: number, lastModelClass: string | null);
87
149
  }
88
150
  /**
89
151
  * Add two usage records.
@@ -117,23 +179,47 @@ export declare function sumUsage(a: LlmUsageRecord, b: LlmUsageRecord | undefine
117
179
  */
118
180
  export declare function legBudgetMs(routeBudgetMs: number, deadlineAtMs: number | undefined, nowMs: number): number;
119
181
  /**
120
- * Whether a failure is the provider saying it is full rather than broken.
182
+ * The model a leg serves, independent of which provider hosts it.
183
+ *
184
+ * @param route The leg's route.
185
+ * @returns Its model class.
186
+ */
187
+ export declare function modelClassOf(route: ResolvedRoute): string;
188
+ /**
189
+ * Whether a provider-reported model names the model a route addressed.
190
+ *
191
+ * Providers report with or without an organisation prefix and in their own
192
+ * case, so the comparison is case-insensitive and accepts one side being a
193
+ * `/`-suffix of the other.
194
+ *
195
+ * @param reported The provider's report.
196
+ * @param expected The route's model id.
197
+ * @returns Whether they name the same model.
198
+ */
199
+ export declare function isSameReportedModel(reported: string, expected: string): boolean;
200
+ /**
201
+ * How an attempt's model relates to the configured one.
121
202
  *
122
- * Read by shape rather than by class, because the same signal reaches the
123
- * chain from more than one transport and not every transport's error class is
124
- * importable here.
203
+ * A leg addressed to a different model class is `different` whatever it
204
+ * reported. A leg addressed to the configured class is `same` only when it
205
+ * answered and the provider named the expected model; `different` when the
206
+ * provider named another; `unknown` when it answered without saying. An
207
+ * attempt that never answered carries the relation of the model it was
208
+ * addressed to.
125
209
  *
126
- * @param error The thrown value.
127
- * @param reason Its message.
128
- * @returns Whether it is a capacity signal.
210
+ * @param route The attempt's route.
211
+ * @param configuredClass The configured model class.
212
+ * @param fields What the attempt recorded.
213
+ * @returns The relation.
129
214
  */
130
- export declare function isCapacitySignal(error: unknown, reason: string): boolean;
215
+ export declare function modelClassRelationOf(route: ResolvedRoute, configuredClass: string, fields: Pick<AttemptFields, "outcome" | "servedModel">): LlmModelClassRelation;
131
216
  /**
132
217
  * Walk a chain until a leg answers.
133
218
  *
134
219
  * @param alias The alias being served, for error attribution.
135
220
  * @param execution The call context.
136
221
  * @returns The first successful leg's answer, with the full attempt record.
222
+ * @throws {LlmDeadlineExceededError} When the caller's deadline ran out first.
137
223
  * @throws {ChainExhaustedError} When no leg produced an answer.
138
224
  */
139
225
  export declare function executeChain<T>(alias: string, execution: ChainExecution): Promise<ChainOutcome<T>>;
@@ -1 +1 @@
1
- {"version":3,"file":"fallback-chain.d.ts","sourceRoot":"","sources":["../../../src/llm/fallback-chain.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAsB,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AACpF,OAAO,EAEL,0BAA0B,EAE3B,MAAM,gBAAgB,CAAC;AAGxB,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACZ,mBAAmB,EACnB,oBAAoB,EACpB,cAAc,EACd,aAAa,EACd,MAAM,SAAS,CAAC;AAWjB,uDAAuD;AACvD,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,8CAA8C;IAC9C,QAAQ,CAAC,SAAS,EAAE,YAAY,CAAC;IACjC,gFAAgF;IAChF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,0BAA0B,CAAC;CACvE;AAED,kDAAkD;AAClD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,SAAS,QAAQ,EAAE,CAAC;IACnC,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,OAAO,EAAE,CAAC;IAC9C,QAAQ,CAAC,cAAc,EAAE,mBAAmB,CAAC,gBAAgB,CAAC,CAAC;IAC/D;;;;OAIG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;IACtC,QAAQ,CAAC,QAAQ,EAAE,sBAAsB,CAAC;IAC1C,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,2EAA2E;IAC3E,QAAQ,CAAC,YAAY,CAAC,EAAE,WAAW,CAAC;IACpC;;;;;OAKG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,8EAA8E;IAC9E,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IAC5B,gFAAgF;IAChF,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,IAAI,CAAC;CAC3D;AAED,qDAAqD;AACrD,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,CAAC,CAAC,CAAC,CAAC;IAC3C,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IACjC,QAAQ,CAAC,QAAQ,EAAE,SAAS,kBAAkB,EAAE,CAAC;IACjD,QAAQ,CAAC,UAAU,EAAE,cAAc,CAAC;CACrC;AAED;;;;;;;GAOG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,2CAA2C;IAC3C,SAAgB,KAAK,EAAE,MAAM,CAAC;IAE9B,mDAAmD;IACnD,SAAgB,QAAQ,EAAE,SAAS,kBAAkB,EAAE,CAAC;IAExD,mFAAmF;IACnF,SAAgB,UAAU,EAAE,cAAc,CAAC;IAE3C;;;;OAIG;gBAED,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,SAAS,kBAAkB,EAAE,EACvC,UAAU,EAAE,cAAc;CAiB7B;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,QAAQ,CACtB,CAAC,EAAE,cAAc,EACjB,CAAC,EAAE,cAAc,GAAG,SAAS,GAC5B,cAAc,CAmBhB;AAaD;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CACzB,aAAa,EAAE,MAAM,EACrB,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,KAAK,EAAE,MAAM,GACZ,MAAM,CAKR;AAgGD;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAQxE;AAsID;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAAC,CAAC,EAClC,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,cAAc,GACxB,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CA2H1B"}
1
+ {"version":3,"file":"fallback-chain.d.ts","sourceRoot":"","sources":["../../../src/llm/fallback-chain.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Same-model attempts for one leg: hedging, measured attempt timeouts, and
3
+ * reserving deadline for a same-model alternative.
4
+ *
5
+ * A leg is a model. Before the chain gives up on it and reaches a DIFFERENT
6
+ * model — which changes the answer's quality, not just its latency — it is
7
+ * worth spending the leg's budget on every way of getting that same model to
8
+ * answer: the same model at another provider (an "equivalent"), or a second
9
+ * request to the same provider when that provider has capacity to spare (a
10
+ * "duplicate"). This module runs those attempts as one group.
11
+ *
12
+ * Three mechanics, all bounded by the leg's budget and none of them selecting
13
+ * a different model:
14
+ *
15
+ * - **Hedging.** Once an attempt has run past the model's healthy p90 (from
16
+ * the latency tracker), the next same-model attempt starts beside it. The
17
+ * first answer wins and every other attempt is cancelled through its own
18
+ * abort signal. A hedge that loses, or an attempt cancelled because another
19
+ * won, says nothing about the provider and never touches its breaker.
20
+ *
21
+ * - **Measured attempt timeout.** With latency evidence, an attempt that has
22
+ * run past `k × p99` of healthy latency is replaced by a same-model
23
+ * alternative instead of holding the budget to its end. It is replaced only
24
+ * when an alternative exists: with none, cutting it short would only move
25
+ * the call to a different model sooner, and it runs to the leg budget as
26
+ * before.
27
+ *
28
+ * - **Deadline reservation.** When a same-model equivalent exists, no single
29
+ * attempt holds more than `max_attempt_share` of the remaining budget before
30
+ * the equivalent starts beside it, so a slow first attempt cannot consume the
31
+ * whole deadline while the equivalent that could have answered never runs.
32
+ *
33
+ * With no latency evidence and no equivalent, none of the three can act and
34
+ * the group is exactly one attempt with the leg's full budget: the serial
35
+ * chain's behaviour, which a fresh process therefore starts from.
36
+ *
37
+ * @module llm/hedge
38
+ */
39
+ import type { CircuitBreakerRegistry } from "./circuit-breaker";
40
+ import type { AttemptRequest, ChainLeg } from "./leg-attempt";
41
+ import type { LegLatencyTracker } from "./leg-latency-tracker";
42
+ import type { AliasAttemptRecord, LlmHedgingDefaults, LlmTransportResponse, LlmUsageRecord, ResolvedRoute } from "./types";
43
+ /** Same-model controls, read from the route table's `hedging` defaults. */
44
+ export interface SameModelPolicy {
45
+ readonly maxExtraAttempts: number;
46
+ readonly hedgeQuantile: number;
47
+ readonly timeoutQuantile: number;
48
+ readonly kTimeout: number;
49
+ readonly attemptTimeoutFloorMs: number;
50
+ readonly maxAttemptShare: number;
51
+ readonly duplicateReserve: number;
52
+ }
53
+ /**
54
+ * Read the policy from the table's defaults.
55
+ *
56
+ * @param defaults The `hedging` defaults.
57
+ * @returns The policy.
58
+ */
59
+ export declare function sameModelPolicyFrom(defaults: LlmHedgingDefaults): SameModelPolicy;
60
+ /** The legacy fields of one attempt record; the chain adds provenance. */
61
+ export type AttemptFields = Omit<AliasAttemptRecord, "servedProvider" | "modelClass" | "modelClassRelation" | "hedged" | "attemptIndex">;
62
+ /** What the group needs from the chain around it. */
63
+ export interface SameModelGroupContext {
64
+ readonly request: AttemptRequest;
65
+ readonly breakers: CircuitBreakerRegistry;
66
+ readonly now: () => number;
67
+ /** Absent: no hedging, no measured timeouts — one attempt with the full budget. */
68
+ readonly policy?: SameModelPolicy;
69
+ readonly tracker?: LegLatencyTracker;
70
+ readonly promptTokens: number | null;
71
+ readonly admitDuplicate: (route: ResolvedRoute, reserveFraction: number) => boolean;
72
+ /** Record one settled attempt. `servedProvider` is the provider's own report, if any. */
73
+ readonly record: (route: ResolvedRoute, fields: AttemptFields, dispatch: {
74
+ readonly hedged: boolean;
75
+ readonly attemptIndex: number;
76
+ }, servedProvider: string | null | undefined) => void;
77
+ /** The next zero-based dispatch index across the whole call. */
78
+ readonly nextAttemptIndex: () => number;
79
+ }
80
+ /** How a group ended. */
81
+ export interface SameModelGroupResult<T> {
82
+ readonly answer?: {
83
+ readonly response: LlmTransportResponse<T>;
84
+ readonly route: ResolvedRoute;
85
+ readonly hedged: boolean;
86
+ };
87
+ /** Usage billed by every attempt in the group, answered or not. */
88
+ readonly billed: readonly LlmUsageRecord[];
89
+ /** Whether the group ended because the caller's deadline ran out. */
90
+ readonly deadlineBound: boolean;
91
+ }
92
+ /**
93
+ * Run every same-model attempt for one leg until one answers or the budget,
94
+ * the alternatives, or the caller run out.
95
+ *
96
+ * The caller has already found the leg servable (breaker allows it, budget
97
+ * positive). The returned promise settles only once every attempt it started
98
+ * has been recorded, so the call's attempt record is complete when it returns.
99
+ *
100
+ * @param leg The leg, with its equivalents.
101
+ * @param groupBudgetMs The leg's budget: its route budget cut to the deadline.
102
+ * @param budgetIsDeadline Whether that budget was cut by the caller's deadline.
103
+ * @param ctx The chain around the group.
104
+ * @returns How the group ended.
105
+ */
106
+ export declare function runSameModelGroup<T>(leg: ChainLeg, groupBudgetMs: number, budgetIsDeadline: boolean, ctx: SameModelGroupContext): Promise<SameModelGroupResult<T>>;
107
+ //# sourceMappingURL=hedge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hedge.d.ts","sourceRoot":"","sources":["../../../src/llm/hedge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAShE,OAAO,KAAK,EAAiB,cAAc,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC7E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAE/D,OAAO,KAAK,EACV,kBAAkB,EAClB,kBAAkB,EAClB,oBAAoB,EACpB,cAAc,EACd,aAAa,EACd,MAAM,SAAS,CAAC;AAEjB,2EAA2E;AAC3E,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC;IACvC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;CACnC;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,kBAAkB,GAAG,eAAe,CAUjF;AAED,0EAA0E;AAC1E,MAAM,MAAM,aAAa,GAAG,IAAI,CAC9B,kBAAkB,EAClB,gBAAgB,GAAG,YAAY,GAAG,oBAAoB,GAAG,QAAQ,GAAG,cAAc,CACnF,CAAC;AAEF,qDAAqD;AACrD,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC,QAAQ,CAAC,QAAQ,EAAE,sBAAsB,CAAC;IAC1C,QAAQ,CAAC,GAAG,EAAE,MAAM,MAAM,CAAC;IAC3B,mFAAmF;IACnF,QAAQ,CAAC,MAAM,CAAC,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,OAAO,CAAC,EAAE,iBAAiB,CAAC;IACrC,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,cAAc,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,KAAK,OAAO,CAAC;IACpF,yFAAyF;IACzF,QAAQ,CAAC,MAAM,EAAE,CACf,KAAK,EAAE,aAAa,EACpB,MAAM,EAAE,aAAa,EACrB,QAAQ,EAAE;QAAE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;KAAE,EACrE,cAAc,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,KACtC,IAAI,CAAC;IACV,gEAAgE;IAChE,QAAQ,CAAC,gBAAgB,EAAE,MAAM,MAAM,CAAC;CACzC;AAED,yBAAyB;AACzB,MAAM,WAAW,oBAAoB,CAAC,CAAC;IACrC,QAAQ,CAAC,MAAM,CAAC,EAAE;QAChB,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,CAAC,CAAC,CAAC,CAAC;QAC3C,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;QAC9B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;KAC1B,CAAC;IACF,mEAAmE;IACnE,QAAQ,CAAC,MAAM,EAAE,SAAS,cAAc,EAAE,CAAC;IAC3C,qEAAqE;IACrE,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;CACjC;AAkCD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EACjC,GAAG,EAAE,QAAQ,EACb,aAAa,EAAE,MAAM,EACrB,gBAAgB,EAAE,OAAO,EACzB,GAAG,EAAE,qBAAqB,GACzB,OAAO,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,CA6WlC"}
@@ -8,13 +8,16 @@
8
8
  *
9
9
  * @module llm
10
10
  */
11
- export { callLLMByAlias, configureLlmClient, llmAliases, llmBreakers } from "./alias-client";
12
- export { ChainExhaustedError, legBudgetMs, sumUsage, } from "./fallback-chain";
11
+ export { callLLMByAlias, configureLlmClient, llmAliases, llmBreakers, llmLatencyTracker, } from "./alias-client";
12
+ export { ChainExhaustedError, LlmDeadlineExceededError, isSameReportedModel, legBudgetMs, modelClassOf, modelClassRelationOf, sumUsage, } from "./fallback-chain";
13
+ export type { ChainExhaustionReason } from "./fallback-chain";
14
+ export { LegLatencyTracker, estimatePromptTokens } from "./leg-latency-tracker";
15
+ export type { LatencyTrackerConfig } from "./leg-latency-tracker";
13
16
  export type { AliasAttemptRecord } from "./types";
14
- export { NoServableRouteError, UnknownAliasError, closedIncumbentLeg, gatewayModelNameFor, listAliases, orderedRoutes, resolveChain, routeKeyFor, routeTable, } from "./route-table";
17
+ export { EQUIVALENT_SEPARATOR, NoServableRouteError, UnknownAliasError, closedIncumbentLeg, gatewayModelNameFor, listAliases, orderedRoutes, resolveChain, routeKeyFor, routeTable, tailLatencyViolations, } from "./route-table";
15
18
  export type { ResolvedChain, RouteExclusion } from "./route-table";
16
19
  export { ToolChoiceIgnoredError, UnsupportedCapabilityError, assertToolChoiceHonoured, normaliseParams, routeSupports, } from "./param-matrix";
17
- export { RateGuardTimeoutError, guardSnapshots, limitsFor, limitsInventory, resetProviderGuards, withProviderGuards, } from "./rate-guard";
20
+ export { RateGuardTimeoutError, guardSnapshots, hasDuplicateHeadroom, limitsFor, limitsInventory, resetProviderGuards, withProviderGuards, } from "./rate-guard";
18
21
  export type { GuardCallScope, GuardSnapshot, ModelLimitOverride, ProviderLimitBasis, ProviderLimitScope, ProviderLimits, } from "./rate-guard";
19
22
  export { LlmResponseFormatError } from "./structured-content";
20
23
  export type { StructuredResponseFormat } from "./structured-content";
@@ -24,9 +27,9 @@ export { SchemaRetryExhaustedError, buildRetryPrompt, callWithValidation } from
24
27
  export type { ValidatedOutcome } from "./schema-retry";
25
28
  export { StreamProviderError, StreamTruncatedError, collectStream, normaliseAnthropicStream, normaliseOpenAiStream, normaliseStream, } from "./streaming";
26
29
  export type { StreamChunk } from "./streaming";
27
- export { GatewayResponseError, GatewayUnreachableError, createGatewayTransport, } from "./transports/gateway";
30
+ export { GatewayResponseError, GatewayUnreachableError, SERVED_MODEL_HEADER, SERVED_PROVIDER_HEADER, createGatewayTransport, servedModelOf, } from "./transports/gateway";
28
31
  export type { GatewayTransportConfig } from "./transports/gateway";
29
32
  export { DirectTransportRefusedError, createDirectTransport, resolveDefaultDirectCaller, } from "./transports/direct";
30
33
  export type { DirectCaller, DirectCallerUsage, DirectTransportConfig, } from "./transports/direct";
31
- export type { AliasCallOptions, AliasCallResult, LlmAlias, LlmAliasBudget, LlmAliasDefinition, LlmClientConfig, LlmCriticality, LlmLatencyClass, LlmModelIdStatus, LlmOpenItem, LlmPriceAnchor, LlmProvider, LlmProviderTier, LlmResponseFormat, LlmRoute, LlmRouteDefaults, LlmRouteParams, LlmRouteRole, LlmRouteTable, LlmToolCall, LlmToolChoice, LlmTransport, LlmTransportRequest, LlmTransportResponse, LlmUsageRecord, LlmValidationOutcome, ResolvedRoute, } from "./types";
34
+ export type { AliasCallOptions, AliasCallResult, LlmAlias, LlmAliasBudget, LlmAliasDefinition, LlmClientConfig, LlmCriticality, LlmCrossModelPolicy, LlmHedgingDefaults, LlmLatencyClass, LlmLatencyTripDefaults, LlmModelClassRelation, LlmModelIdStatus, LlmOpenItem, LlmPriceAnchor, LlmProvider, LlmProviderTier, LlmResponseFormat, LlmRoute, LlmRouteDefaults, LlmRouteEquivalent, LlmRouteParams, LlmRouteRole, LlmRouteTable, LlmToolCall, LlmToolChoice, LlmTransport, LlmTransportRequest, LlmTransportResponse, LlmUsageRecord, LlmValidationOutcome, ResolvedRoute, } from "./types";
32
35
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/llm/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7F,OAAO,EACL,mBAAmB,EACnB,WAAW,EACX,QAAQ,GACT,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAElD,OAAO,EACL,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,WAAW,EACX,aAAa,EACb,YAAY,EACZ,WAAW,EACX,UAAU,GACX,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEnE,OAAO,EACL,sBAAsB,EACtB,0BAA0B,EAC1B,wBAAwB,EACxB,eAAe,EACf,aAAa,GACd,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,qBAAqB,EACrB,cAAc,EACd,SAAS,EACT,eAAe,EACf,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,cAAc,CAAC;AACtB,YAAY,EACV,cAAc,EACd,aAAa,EACb,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EAClB,cAAc,GACf,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,YAAY,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAErE,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,YAAY,EAAE,kBAAkB,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAE3F,OAAO,EAAE,yBAAyB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACjG,YAAY,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,OAAO,EACL,mBAAmB,EACnB,oBAAoB,EACpB,aAAa,EACb,wBAAwB,EACxB,qBAAqB,EACrB,eAAe,GAChB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAEnE,OAAO,EACL,2BAA2B,EAC3B,qBAAqB,EACrB,0BAA0B,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EACV,YAAY,EACZ,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,qBAAqB,CAAC;AAE7B,YAAY,EACV,gBAAgB,EAChB,eAAe,EACf,QAAQ,EACR,cAAc,EACd,kBAAkB,EAClB,eAAe,EACf,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,WAAW,EACX,cAAc,EACd,WAAW,EACX,eAAe,EACf,iBAAiB,EACjB,QAAQ,EACR,gBAAgB,EAChB,cAAc,EACd,YAAY,EACZ,aAAa,EACb,WAAW,EACX,aAAa,EACb,YAAY,EACZ,mBAAmB,EACnB,oBAAoB,EACpB,cAAc,EACd,oBAAoB,EACpB,aAAa,GACd,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/llm/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,165 @@
1
+ /**
2
+ * One attempt against one leg: dispatch under a hard timeout, and the
3
+ * classification of how it ended.
4
+ *
5
+ * Split from the chain walker so the same-model hedge runner and the walker
6
+ * share exactly one definition of what a timeout, a capacity refusal, a
7
+ * cancellation and a superseded attempt are. Two copies of that
8
+ * classification would drift, and the breaker would then read the same event
9
+ * differently depending on which code path produced it.
10
+ *
11
+ * @module llm/leg-attempt
12
+ */
13
+ import { UnsupportedCapabilityError } from "./param-matrix";
14
+ import type { BreakerFailureKind } from "./circuit-breaker";
15
+ import type { AliasAttemptRecord, LlmTransport, LlmTransportRequest, LlmTransportResponse, LlmUsageRecord, ResolvedRoute } from "./types";
16
+ /** What one leg of the chain needs in order to run. */
17
+ export interface ChainLeg {
18
+ readonly route: ResolvedRoute;
19
+ /** The transport that will carry this leg. */
20
+ readonly transport: LlmTransport;
21
+ /** Provider-normalised parameters, or the error that made this leg unusable. */
22
+ readonly params: Record<string, unknown> | UnsupportedCapabilityError;
23
+ /**
24
+ * The same model at other live providers, prepared like the leg itself.
25
+ * Reached before any different-model leg, and only through hedging.
26
+ */
27
+ readonly equivalents?: readonly ChainLeg[];
28
+ }
29
+ /** The request-shaped part of a call that every attempt carries unchanged. */
30
+ export interface AttemptRequest {
31
+ readonly content: string | readonly unknown[];
32
+ readonly responseFormat: LlmTransportRequest["responseFormat"];
33
+ readonly developerPrompt?: string;
34
+ readonly context?: readonly unknown[];
35
+ readonly correlationId?: string;
36
+ /** The caller's own cancellation, honoured ahead of any per-attempt budget. */
37
+ readonly callerSignal?: AbortSignal;
38
+ }
39
+ /** Raised internally when an attempt exceeds its hard budget. */
40
+ export declare class LegTimeoutError extends Error {
41
+ /**
42
+ * @param routeKey The leg that timed out.
43
+ * @param budgetMs Its budget in milliseconds.
44
+ */
45
+ constructor(routeKey: string, budgetMs: number);
46
+ }
47
+ /**
48
+ * Raised internally when an attempt ran past its MEASURED timeout and was
49
+ * replaced by another attempt on the same model.
50
+ *
51
+ * Not a verdict on the provider: the measured timeout is the chain's own
52
+ * impatience, applied only because a same-model alternative could take over,
53
+ * so the breaker learns nothing from it.
54
+ */
55
+ export declare class AttemptSupersededError extends Error {
56
+ /**
57
+ * @param routeKey The attempt's leg.
58
+ * @param afterMs How long it ran before it was replaced.
59
+ */
60
+ constructor(routeKey: string, afterMs: number);
61
+ }
62
+ /**
63
+ * Raised internally on an attempt that was still running when another
64
+ * attempt on the same model answered first. Not a verdict on the provider.
65
+ */
66
+ export declare class HedgeLoserError extends Error {
67
+ /**
68
+ * @param routeKey The losing attempt's leg.
69
+ */
70
+ constructor(routeKey: string);
71
+ }
72
+ /** An attempt in flight. */
73
+ export interface AttemptHandle<T> {
74
+ readonly promise: Promise<LlmTransportResponse<T>>;
75
+ /** Cancel the attempt, recording why; the first reason given is kept. */
76
+ readonly abort: (reason: Error) => void;
77
+ /** The reason this attempt was cancelled by the chain, if it was. */
78
+ readonly abortReason: () => Error | undefined;
79
+ /** Whether the attempt's hard budget ran out. */
80
+ readonly timedOut: () => boolean;
81
+ }
82
+ /**
83
+ * Start one attempt under a hard timeout, honouring the caller's own cancellation.
84
+ *
85
+ * The timer is always cleared and the abort listener always removed, including
86
+ * on the success path. A long-lived process that leaked one timer per LLM call
87
+ * would accumulate them at exactly the rate it does useful work.
88
+ *
89
+ * @param leg The leg to run.
90
+ * @param params Normalised parameters for this leg.
91
+ * @param request The call's request fields and cancellation.
92
+ * @param budgetMs The attempt's hard budget.
93
+ * @returns A handle on the attempt.
94
+ */
95
+ export declare function startAttempt<T>(leg: ChainLeg, params: Record<string, unknown>, request: AttemptRequest, budgetMs: number): AttemptHandle<T>;
96
+ /**
97
+ * Whether a failure is the provider saying it is full rather than broken.
98
+ *
99
+ * Read by shape rather than by class, because the same signal reaches the
100
+ * chain from more than one transport and not every transport's error class is
101
+ * importable here.
102
+ *
103
+ * @param error The thrown value.
104
+ * @param reason Its message.
105
+ * @returns Whether it is a capacity signal.
106
+ */
107
+ export declare function isCapacitySignal(error: unknown, reason: string): boolean;
108
+ /** What the chain learned from one failed leg. */
109
+ export interface LegFailure {
110
+ readonly outcome: AliasAttemptRecord["outcome"];
111
+ readonly reason: string;
112
+ readonly countsAgainstHealth: boolean;
113
+ /** Which cooldown the failure earns, when it counts against health. */
114
+ readonly failureKind: BreakerFailureKind;
115
+ }
116
+ /**
117
+ * Classify why a leg failed.
118
+ *
119
+ * The distinction matters to the breaker: a timeout and a 5xx are evidence the
120
+ * provider is unhealthy, while the caller cancelling is not. Counting a
121
+ * cancellation as a provider failure would let a burst of user-cancelled
122
+ * requests open the breaker on a perfectly healthy route.
123
+ *
124
+ * Among failures that do count, a capacity signal (the provider said it is
125
+ * busy, or the leg ran out its budget waiting on it) is told apart from a hard
126
+ * failure so the breaker can re-admit a busy route sooner than a broken one. A
127
+ * timeout is read as capacity: on a reachable provider it is what a full queue
128
+ * looks like from outside, and a provider that is actually down still costs no
129
+ * more than one probe per capacity cooldown.
130
+ *
131
+ * An attempt the chain itself cancelled — replaced after its measured
132
+ * timeout, or beaten by a same-model attempt — is not a verdict on the
133
+ * provider either, and is classified by the chain's reason rather than by
134
+ * whatever the transport happened to throw on the way out.
135
+ *
136
+ * @param error The thrown value.
137
+ * @param callerSignal The caller's cancellation signal, if any.
138
+ * @returns The outcome and whether it counts against route health.
139
+ */
140
+ export declare function classify(error: unknown, callerSignal: AbortSignal | undefined): LegFailure;
141
+ /**
142
+ * Whether the caller has stopped waiting.
143
+ *
144
+ * Read through a function rather than inline, because `AbortSignal.aborted` is
145
+ * a live getter: it can flip to true while a leg is in flight, but a compiler
146
+ * that narrowed it at the top of the loop would prove the later check
147
+ * unreachable and invite its removal. The check is not redundant — it is the
148
+ * only thing that stops the chain spending money on an answer nobody will read.
149
+ *
150
+ * @param signal The caller's signal, if any.
151
+ * @returns Whether the call has been cancelled.
152
+ */
153
+ export declare function isAborted(signal: AbortSignal | undefined): boolean;
154
+ /**
155
+ * The usage a failed leg was billed for, when the leg reached an answer.
156
+ *
157
+ * A leg that failed after the provider answered — content that does not parse,
158
+ * or prose where a tool call was mandatory — was still charged. A leg that
159
+ * never answered (timeout, outage, skip) carries no usage, and none is invented.
160
+ *
161
+ * @param error The thrown value.
162
+ * @returns The billed usage, or undefined when the leg never produced an answer.
163
+ */
164
+ export declare function billedUsageOf(error: unknown): LlmUsageRecord | undefined;
165
+ //# sourceMappingURL=leg-attempt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"leg-attempt.d.ts","sourceRoot":"","sources":["../../../src/llm/leg-attempt.ts"],"names":[],"mappings":"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"}