@mcp-abap-adt/llm-agent 22.0.1 → 22.2.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/index.d.ts CHANGED
@@ -5,6 +5,7 @@ export * from './external-results.js';
5
5
  export { assertClientDescriptors } from './interfaces/assert-client-descriptors.js';
6
6
  export * from './interfaces/index.js';
7
7
  export { BaseLLMProvider, type LLMProvider } from './llm/base-llm-provider.js';
8
+ export { DEFAULT_RATE_LIMIT_POLICY, findRateLimit, gateFor, isRateLimitedError, preserveRateLimit, type RateLimitedError, RateLimitGate, type RateLimitPolicy, type RateLimitRetryOptions, rateLimitGateCount, resetRateLimitGates, runWithRateLimitRetry, } from './llm/rate-limit.js';
8
9
  export { NeedsDecompositionError } from './needs-decomposition-error.js';
9
10
  export * from './rag/index.js';
10
11
  export * from './types.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,kBAAkB,EAClB,SAAS,EACT,aAAa,GACd,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AACzE,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,OAAO,EAAE,uBAAuB,EAAE,MAAM,2CAA2C,CAAC;AACpF,cAAc,uBAAuB,CAAC;AACtC,OAAO,EAAE,eAAe,EAAE,KAAK,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAC/E,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AACzE,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAI3B,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AAExE,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAC1E,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,EACL,sBAAsB,EACtB,KAAK,0BAA0B,EAC/B,KAAK,2BAA2B,EAChC,iCAAiC,EACjC,sBAAsB,GACvB,MAAM,gCAAgC,CAAC;AAExC,OAAO,EACL,sBAAsB,EACtB,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,iBAAiB,GACvB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,oBAAoB,EACpB,KAAK,oBAAoB,GAC1B,MAAM,wCAAwC,CAAC;AAEhD,YAAY,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,EAAE,iBAAiB,EAAE,MAAM,qCAAqC,CAAC;AACxE,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,KAAK,cAAc,EACnB,KAAK,oBAAoB,GAC1B,MAAM,gCAAgC,CAAC;AAExC,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,uBAAuB,EAAE,MAAM,wCAAwC,CAAC;AAEjF,OAAO,EAAE,2BAA2B,EAAE,MAAM,6CAA6C,CAAC;AAC1F,OAAO,EAAE,wBAAwB,EAAE,MAAM,yCAAyC,CAAC;AACnF,OAAO,EACL,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,yCAAyC,CAAC;AAEjD,OAAO,EACL,cAAc,EACd,KAAK,oBAAoB,EACzB,KAAK,YAAY,GAClB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACL,sBAAsB,EACtB,0BAA0B,EAC1B,kBAAkB,GACnB,MAAM,0CAA0C,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qCAAqC,CAAC;AACxE,OAAO,EACL,cAAc,EACd,KAAK,wBAAwB,EAC7B,wBAAwB,EACxB,KAAK,0BAA0B,EAC/B,qBAAqB,EACrB,eAAe,GAChB,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAC3D,OAAO,EACL,KAAK,oBAAoB,EACzB,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,EAClB,aAAa,EACb,SAAS,GACV,MAAM,gCAAgC,CAAC;AACxC,OAAO,EACL,qBAAqB,EACrB,eAAe,GAChB,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@ export * from './external-results.js';
6
6
  export { assertClientDescriptors } from './interfaces/assert-client-descriptors.js';
7
7
  export * from './interfaces/index.js';
8
8
  export { BaseLLMProvider } from './llm/base-llm-provider.js';
9
+ export { DEFAULT_RATE_LIMIT_POLICY, findRateLimit, gateFor, isRateLimitedError, preserveRateLimit, RateLimitGate, rateLimitGateCount, resetRateLimitGates, runWithRateLimitRetry, } from './llm/rate-limit.js';
9
10
  export { NeedsDecompositionError } from './needs-decomposition-error.js';
10
11
  export * from './rag/index.js';
11
12
  export * from './types.js';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAC3C,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,kBAAkB,EAClB,SAAS,EACT,aAAa,GACd,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AACzE,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,OAAO,EAAE,uBAAuB,EAAE,MAAM,2CAA2C,CAAC;AACpF,cAAc,uBAAuB,CAAC;AACtC,OAAO,EAAE,eAAe,EAAoB,MAAM,4BAA4B,CAAC;AAC/E,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AACzE,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAE3B,sEAAsE;AAEtE,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AACxE,eAAe;AACf,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAC1E,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAGlD,iBAAiB;AACjB,OAAO,EACL,sBAAsB,EAGtB,iCAAiC,EACjC,sBAAsB,GACvB,MAAM,gCAAgC,CAAC;AACxC,yBAAyB;AACzB,OAAO,EACL,sBAAsB,GAKvB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,oBAAoB,GAErB,MAAM,wCAAwC,CAAC;AAGhD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qCAAqC,CAAC;AACxE,OAAO,EACL,gBAAgB,EAChB,oBAAoB,GAGrB,MAAM,gCAAgC,CAAC;AAGxC,OAAO,EAAE,uBAAuB,EAAE,MAAM,wCAAwC,CAAC;AACjF,oBAAoB;AACpB,OAAO,EAAE,2BAA2B,EAAE,MAAM,6CAA6C,CAAC;AAC1F,OAAO,EAAE,wBAAwB,EAAE,MAAM,yCAAyC,CAAC;AACnF,OAAO,EACL,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,yCAAyC,CAAC;AACjD,aAAa;AACb,OAAO,EACL,cAAc,GAGf,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACL,sBAAsB,EACtB,0BAA0B,EAC1B,kBAAkB,GACnB,MAAM,0CAA0C,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qCAAqC,CAAC;AACxE,OAAO,EACL,cAAc,EAEd,wBAAwB,EAExB,qBAAqB,EACrB,eAAe,GAChB,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAC3D,OAAO,EAEL,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,EAClB,aAAa,EACb,SAAS,GACV,MAAM,gCAAgC,CAAC;AACxC,OAAO,EACL,qBAAqB,EACrB,eAAe,GAChB,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAC3C,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,kBAAkB,EAClB,SAAS,EACT,aAAa,GACd,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AACzE,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,OAAO,EAAE,uBAAuB,EAAE,MAAM,2CAA2C,CAAC;AACpF,cAAc,uBAAuB,CAAC;AACtC,OAAO,EAAE,eAAe,EAAoB,MAAM,4BAA4B,CAAC;AAC/E,OAAO,EACL,yBAAyB,EACzB,aAAa,EACb,OAAO,EACP,kBAAkB,EAClB,iBAAiB,EAEjB,aAAa,EAGb,kBAAkB,EAClB,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AACzE,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAE3B,sEAAsE;AAEtE,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AACxE,eAAe;AACf,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAC1E,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAGlD,iBAAiB;AACjB,OAAO,EACL,sBAAsB,EAGtB,iCAAiC,EACjC,sBAAsB,GACvB,MAAM,gCAAgC,CAAC;AACxC,yBAAyB;AACzB,OAAO,EACL,sBAAsB,GAKvB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,oBAAoB,GAErB,MAAM,wCAAwC,CAAC;AAGhD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qCAAqC,CAAC;AACxE,OAAO,EACL,gBAAgB,EAChB,oBAAoB,GAGrB,MAAM,gCAAgC,CAAC;AAGxC,OAAO,EAAE,uBAAuB,EAAE,MAAM,wCAAwC,CAAC;AACjF,oBAAoB;AACpB,OAAO,EAAE,2BAA2B,EAAE,MAAM,6CAA6C,CAAC;AAC1F,OAAO,EAAE,wBAAwB,EAAE,MAAM,yCAAyC,CAAC;AACnF,OAAO,EACL,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,yCAAyC,CAAC;AACjD,aAAa;AACb,OAAO,EACL,cAAc,GAGf,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACL,sBAAsB,EACtB,0BAA0B,EAC1B,kBAAkB,GACnB,MAAM,0CAA0C,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qCAAqC,CAAC;AACxE,OAAO,EACL,cAAc,EAEd,wBAAwB,EAExB,qBAAqB,EACrB,eAAe,GAChB,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAC3D,OAAO,EAEL,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,EAClB,aAAa,EACb,SAAS,GACV,MAAM,gCAAgC,CAAC;AACxC,OAAO,EACL,qBAAqB,EACrB,eAAe,GAChB,MAAM,uBAAuB,CAAC"}
@@ -2,6 +2,7 @@
2
2
  * Base interface for LLM providers
3
3
  */
4
4
  import type { IModelInfo, LLMCallOptions, LLMProviderConfig, LLMResponse, Message } from '@mcp-abap-adt/llm-agent';
5
+ import { type RateLimitRetryOptions } from './rate-limit.js';
5
6
  export interface LLMProvider {
6
7
  /**
7
8
  * Send a chat message and get response
@@ -29,5 +30,73 @@ export declare abstract class BaseLLMProvider<C extends LLMProviderConfig = LLMP
29
30
  * Validate configuration
30
31
  */
31
32
  protected validateConfig(): void;
33
+ /**
34
+ * Which account and endpoint this call bills to.
35
+ *
36
+ * A quota belongs to an account at an endpoint, so two providers that differ
37
+ * in either one do not share a limit. In a multi-tenant process a key built
38
+ * from the class and the model alone would let one tenant's 429 pause
39
+ * another's for a minute, on a quota that tenant never touched.
40
+ *
41
+ * The credential is reduced to a short digest: the key is an in-memory map
42
+ * key and may reach a log line, so it must identify an account without being
43
+ * the account's secret. Override to add a provider's own account fields
44
+ * (organization, project, resource group) — never the raw secret.
45
+ */
46
+ protected rateLimitScope(): string;
47
+ /**
48
+ * Reduce spellings of one endpoint to one key.
49
+ *
50
+ * Two callers that reach the same server must land on the same gate, and a
51
+ * URL has more than one way of naming it: a trailing slash, an upper-case
52
+ * host, a port that was already the default. Written differently they would
53
+ * be metered as two quotas and stop coordinating — the same defect as reading
54
+ * the configured URL instead of the resolved one, one level down.
55
+ *
56
+ * The query string is kept verbatim: some endpoints carry a deployment or an
57
+ * API version there, and merging those would be worse than splitting them.
58
+ */
59
+ protected canonicalEndpoint(raw: string): string;
60
+ /**
61
+ * The endpoint this provider actually reaches — the RESOLVED one, not the
62
+ * configured one. A provider left to its default and a provider handed that
63
+ * same default explicitly talk to the same server and share its limit; read
64
+ * from the config alone they would look like two quotas and stop
65
+ * coordinating. Override wherever a default is filled in.
66
+ */
67
+ protected quotaEndpoint(): string;
68
+ /** Available to subclasses building their own scope out of other fields. */
69
+ protected credentialFingerprint(secret: string | undefined): string;
70
+ /**
71
+ * Which quota this call spends. Limits are per model, so the model is in the
72
+ * key — and it must be the model the CALL uses, not the configured default.
73
+ * A per-request override spends a different quota: keyed by the default, one
74
+ * model's 429 would pause another's, and two overrides would share a gate
75
+ * neither of them belongs to.
76
+ */
77
+ protected rateLimitKey(model?: string): string;
78
+ /** Is this "too many requests" rather than a real failure? */
79
+ protected isRateLimited(error: unknown): boolean;
80
+ /**
81
+ * How long the server asked us to wait.
82
+ *
83
+ * `Retry-After` is seconds in the SAP AI Core contract and is usually seconds
84
+ * elsewhere, but the HTTP spec also allows an HTTP-date, so both are read.
85
+ */
86
+ protected retryAfterSeconds(error: unknown): number | undefined;
87
+ /**
88
+ * Keep the 429 visible through the provider's own error wrapping.
89
+ *
90
+ * Providers rethrow their transport error as a readable one. Call this on the
91
+ * way out so the consumer still reads a fact, not a substring.
92
+ */
93
+ protected preserveRateLimit<E extends Error>(original: unknown, wrapped: E): E;
94
+ /** Wrap one provider call in the shared policy. */
95
+ protected withRateLimitRetry<T>(fn: () => Promise<T>, extra?: {
96
+ /** The model this call actually uses, when it overrides the configured one. */
97
+ model?: string;
98
+ signal?: AbortSignal;
99
+ onRetry?: RateLimitRetryOptions['onRetry'];
100
+ }): Promise<T>;
32
101
  }
33
102
  //# sourceMappingURL=base-llm-provider.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"base-llm-provider.d.ts","sourceRoot":"","sources":["../../src/llm/base-llm-provider.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACV,UAAU,EACV,cAAc,EACd,iBAAiB,EACjB,WAAW,EACX,OAAO,EACR,MAAM,yBAAyB,CAAC;AAEjC,MAAM,WAAW,WAAW;IAC1B;;OAEG;IACH,IAAI,CACF,QAAQ,EAAE,OAAO,EAAE,EACnB,KAAK,CAAC,EAAE,OAAO,EAAE,EACjB,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,WAAW,CAAC,CAAC;IAExB;;OAEG;IACH,UAAU,CACR,QAAQ,EAAE,OAAO,EAAE,EACnB,KAAK,CAAC,EAAE,OAAO,EAAE,EACjB,OAAO,CAAC,EAAE,cAAc,GACvB,aAAa,CAAC,WAAW,CAAC,CAAC;IAE9B;;OAEG;IACH,SAAS,CAAC,IAAI,OAAO,CAAC,MAAM,EAAE,GAAG,UAAU,EAAE,CAAC,CAAC;IAE/C;;OAEG;IACH,kBAAkB,CAAC,IAAI,OAAO,CAAC,MAAM,EAAE,GAAG,UAAU,EAAE,CAAC,CAAC;CACzD;AAED,8BAAsB,eAAe,CACnC,CAAC,SAAS,iBAAiB,GAAG,iBAAiB,CAC/C,YAAW,WAAW;IAEtB,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;gBAEP,MAAM,EAAE,CAAC;IAIrB,QAAQ,CAAC,IAAI,CACX,QAAQ,EAAE,OAAO,EAAE,EACnB,KAAK,CAAC,EAAE,OAAO,EAAE,EACjB,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,WAAW,CAAC;IAEvB,QAAQ,CAAC,UAAU,CACjB,QAAQ,EAAE,OAAO,EAAE,EACnB,KAAK,CAAC,EAAE,OAAO,EAAE,EACjB,OAAO,CAAC,EAAE,cAAc,GACvB,aAAa,CAAC,WAAW,CAAC;IAE7B;;OAEG;IACH,SAAS,CAAC,cAAc,IAAI,IAAI;CAKjC"}
1
+ {"version":3,"file":"base-llm-provider.d.ts","sourceRoot":"","sources":["../../src/llm/base-llm-provider.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EACV,UAAU,EACV,cAAc,EACd,iBAAiB,EACjB,WAAW,EACX,OAAO,EACR,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAEL,KAAK,qBAAqB,EAE3B,MAAM,iBAAiB,CAAC;AAEzB,MAAM,WAAW,WAAW;IAC1B;;OAEG;IACH,IAAI,CACF,QAAQ,EAAE,OAAO,EAAE,EACnB,KAAK,CAAC,EAAE,OAAO,EAAE,EACjB,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,WAAW,CAAC,CAAC;IAExB;;OAEG;IACH,UAAU,CACR,QAAQ,EAAE,OAAO,EAAE,EACnB,KAAK,CAAC,EAAE,OAAO,EAAE,EACjB,OAAO,CAAC,EAAE,cAAc,GACvB,aAAa,CAAC,WAAW,CAAC,CAAC;IAE9B;;OAEG;IACH,SAAS,CAAC,IAAI,OAAO,CAAC,MAAM,EAAE,GAAG,UAAU,EAAE,CAAC,CAAC;IAE/C;;OAEG;IACH,kBAAkB,CAAC,IAAI,OAAO,CAAC,MAAM,EAAE,GAAG,UAAU,EAAE,CAAC,CAAC;CACzD;AAED,8BAAsB,eAAe,CACnC,CAAC,SAAS,iBAAiB,GAAG,iBAAiB,CAC/C,YAAW,WAAW;IAEtB,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;gBAEP,MAAM,EAAE,CAAC;IAIrB,QAAQ,CAAC,IAAI,CACX,QAAQ,EAAE,OAAO,EAAE,EACnB,KAAK,CAAC,EAAE,OAAO,EAAE,EACjB,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,WAAW,CAAC;IAEvB,QAAQ,CAAC,UAAU,CACjB,QAAQ,EAAE,OAAO,EAAE,EACnB,KAAK,CAAC,EAAE,OAAO,EAAE,EACjB,OAAO,CAAC,EAAE,cAAc,GACvB,aAAa,CAAC,WAAW,CAAC;IAE7B;;OAEG;IACH,SAAS,CAAC,cAAc,IAAI,IAAI;IAchC;;;;;;;;;;;;OAYG;IACH,SAAS,CAAC,cAAc,IAAI,MAAM;IAOlC;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAahD;;;;;;OAMG;IACH,SAAS,CAAC,aAAa,IAAI,MAAM;IAIjC,4EAA4E;IAC5E,SAAS,CAAC,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM;IAInE;;;;;;OAMG;IACH,SAAS,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM;IAM9C,8DAA8D;IAC9D,SAAS,CAAC,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO;IAIhD;;;;;OAKG;IACH,SAAS,CAAC,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS;IAU/D;;;;;OAKG;IACH,SAAS,CAAC,iBAAiB,CAAC,CAAC,SAAS,KAAK,EACzC,QAAQ,EAAE,OAAO,EACjB,OAAO,EAAE,CAAC,GACT,CAAC;IAIJ,mDAAmD;IACnD,SAAS,CAAC,kBAAkB,CAAC,CAAC,EAC5B,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,KAAK,CAAC,EAAE;QACN,+EAA+E;QAC/E,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,WAAW,CAAC;QACrB,OAAO,CAAC,EAAE,qBAAqB,CAAC,SAAS,CAAC,CAAC;KAC5C,GACA,OAAO,CAAC,CAAC,CAAC;CAUd"}
@@ -1,6 +1,8 @@
1
1
  /**
2
2
  * Base interface for LLM providers
3
3
  */
4
+ import { createHash } from 'node:crypto';
5
+ import { preserveRateLimit, runWithRateLimitRetry, } from './rate-limit.js';
4
6
  export class BaseLLMProvider {
5
7
  config;
6
8
  constructor(config) {
@@ -14,5 +16,163 @@ export class BaseLLMProvider {
14
16
  throw new Error('API key is required');
15
17
  }
16
18
  }
19
+ // --- Rate limiting (issue #282) -----------------------------------------
20
+ //
21
+ // The policy is shared (see `rate-limit.ts`); a provider supplies only the
22
+ // two facts the policy cannot know. The defaults below read an AxiosError,
23
+ // which covers every provider here: anthropic and openai use axios directly,
24
+ // sap-aicore gets one through @sap-ai-sdk, and deepseek and ollama build on
25
+ // the openai provider. A provider on another transport overrides them.
26
+ /**
27
+ * Which account and endpoint this call bills to.
28
+ *
29
+ * A quota belongs to an account at an endpoint, so two providers that differ
30
+ * in either one do not share a limit. In a multi-tenant process a key built
31
+ * from the class and the model alone would let one tenant's 429 pause
32
+ * another's for a minute, on a quota that tenant never touched.
33
+ *
34
+ * The credential is reduced to a short digest: the key is an in-memory map
35
+ * key and may reach a log line, so it must identify an account without being
36
+ * the account's secret. Override to add a provider's own account fields
37
+ * (organization, project, resource group) — never the raw secret.
38
+ */
39
+ rateLimitScope() {
40
+ return [
41
+ this.canonicalEndpoint(this.quotaEndpoint()),
42
+ this.credentialFingerprint(this.config.apiKey),
43
+ ].join('|');
44
+ }
45
+ /**
46
+ * Reduce spellings of one endpoint to one key.
47
+ *
48
+ * Two callers that reach the same server must land on the same gate, and a
49
+ * URL has more than one way of naming it: a trailing slash, an upper-case
50
+ * host, a port that was already the default. Written differently they would
51
+ * be metered as two quotas and stop coordinating — the same defect as reading
52
+ * the configured URL instead of the resolved one, one level down.
53
+ *
54
+ * The query string is kept verbatim: some endpoints carry a deployment or an
55
+ * API version there, and merging those would be worse than splitting them.
56
+ */
57
+ canonicalEndpoint(raw) {
58
+ const trimmed = raw.trim();
59
+ if (!trimmed)
60
+ return 'default';
61
+ try {
62
+ // `origin` already lower-cases the host and drops a default port.
63
+ const url = new URL(trimmed);
64
+ return `${url.origin}${url.pathname.replace(/\/+$/, '')}${url.search}`;
65
+ }
66
+ catch {
67
+ // Not absolute (a path, a host:port). Normalise what is safe to.
68
+ return trimmed.replace(/\/+$/, '');
69
+ }
70
+ }
71
+ /**
72
+ * The endpoint this provider actually reaches — the RESOLVED one, not the
73
+ * configured one. A provider left to its default and a provider handed that
74
+ * same default explicitly talk to the same server and share its limit; read
75
+ * from the config alone they would look like two quotas and stop
76
+ * coordinating. Override wherever a default is filled in.
77
+ */
78
+ quotaEndpoint() {
79
+ return this.config.baseURL ?? 'default';
80
+ }
81
+ /** Available to subclasses building their own scope out of other fields. */
82
+ credentialFingerprint(secret) {
83
+ return fingerprint(secret);
84
+ }
85
+ /**
86
+ * Which quota this call spends. Limits are per model, so the model is in the
87
+ * key — and it must be the model the CALL uses, not the configured default.
88
+ * A per-request override spends a different quota: keyed by the default, one
89
+ * model's 429 would pause another's, and two overrides would share a gate
90
+ * neither of them belongs to.
91
+ */
92
+ rateLimitKey(model) {
93
+ return `${this.constructor.name}:${this.rateLimitScope()}:${model ?? this.config.model ?? 'default'}`;
94
+ }
95
+ /** Is this "too many requests" rather than a real failure? */
96
+ isRateLimited(error) {
97
+ return httpStatusOf(error) === 429;
98
+ }
99
+ /**
100
+ * How long the server asked us to wait.
101
+ *
102
+ * `Retry-After` is seconds in the SAP AI Core contract and is usually seconds
103
+ * elsewhere, but the HTTP spec also allows an HTTP-date, so both are read.
104
+ */
105
+ retryAfterSeconds(error) {
106
+ const raw = headerOf(error, 'retry-after');
107
+ if (raw === undefined)
108
+ return undefined;
109
+ const seconds = Number(raw);
110
+ if (Number.isFinite(seconds) && seconds >= 0)
111
+ return seconds;
112
+ const when = Date.parse(String(raw));
113
+ if (!Number.isNaN(when))
114
+ return Math.max(0, (when - Date.now()) / 1000);
115
+ return undefined;
116
+ }
117
+ /**
118
+ * Keep the 429 visible through the provider's own error wrapping.
119
+ *
120
+ * Providers rethrow their transport error as a readable one. Call this on the
121
+ * way out so the consumer still reads a fact, not a substring.
122
+ */
123
+ preserveRateLimit(original, wrapped) {
124
+ return preserveRateLimit(original, wrapped);
125
+ }
126
+ /** Wrap one provider call in the shared policy. */
127
+ withRateLimitRetry(fn, extra) {
128
+ return runWithRateLimitRetry(fn, {
129
+ key: this.rateLimitKey(extra?.model),
130
+ policy: this.config.rateLimit,
131
+ isRateLimited: (e) => this.isRateLimited(e),
132
+ retryAfterSeconds: (e) => this.retryAfterSeconds(e),
133
+ signal: extra?.signal,
134
+ onRetry: extra?.onRetry,
135
+ });
136
+ }
137
+ }
138
+ /**
139
+ * A short, stable, non-reversible stand-in for a credential.
140
+ *
141
+ * Enough to tell two accounts apart in a map key; not enough to be the secret
142
+ * if that key is ever printed. An absent credential is its own scope — several
143
+ * providers configured without one are, as far as we can tell, the same one.
144
+ */
145
+ function fingerprint(secret) {
146
+ if (!secret)
147
+ return 'anonymous';
148
+ return createHash('sha256').update(secret).digest('hex').slice(0, 12);
149
+ }
150
+ /** Status of an axios-shaped error, or undefined when it is shaped otherwise. */
151
+ function httpStatusOf(error) {
152
+ const e = error;
153
+ // axios keeps it under response; several SDKs hoist it onto the error itself.
154
+ for (const candidate of [e?.response?.status, e?.status, e?.statusCode]) {
155
+ if (typeof candidate === 'number')
156
+ return candidate;
157
+ }
158
+ return undefined;
159
+ }
160
+ /** One response header, case-insensitively, from an axios-shaped error. */
161
+ function headerOf(error, name) {
162
+ const headers = error?.response
163
+ ?.headers;
164
+ if (!headers || typeof headers !== 'object')
165
+ return undefined;
166
+ // AxiosHeaders exposes a get(); a plain object does not.
167
+ const get = headers.get;
168
+ if (typeof get === 'function') {
169
+ const v = get.call(headers, name);
170
+ return v === undefined || v === null ? undefined : String(v);
171
+ }
172
+ for (const [k, v] of Object.entries(headers)) {
173
+ if (k.toLowerCase() === name)
174
+ return v === undefined || v === null ? undefined : String(v);
175
+ }
176
+ return undefined;
17
177
  }
18
178
  //# sourceMappingURL=base-llm-provider.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"base-llm-provider.js","sourceRoot":"","sources":["../../src/llm/base-llm-provider.ts"],"names":[],"mappings":"AAAA;;GAEG;AAwCH,MAAM,OAAgB,eAAe;IAI1B,MAAM,CAAI;IAEnB,YAAY,MAAS;QACnB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAcD;;OAEG;IACO,cAAc;QACtB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;QACzC,CAAC;IACH,CAAC;CACF"}
1
+ {"version":3,"file":"base-llm-provider.js","sourceRoot":"","sources":["../../src/llm/base-llm-provider.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Shared rate-limit handling for every LLM provider.
3
+ *
4
+ * None of the providers handled HTTP 429 (issue #282). Each one let the status
5
+ * collapse into an error message, so consumers were reduced to matching the
6
+ * substring "429" in prose to decide whether to retry.
7
+ *
8
+ * The policy lives here rather than in a provider because the providers differ
9
+ * in SDK but not in the shape of the problem: a shared quota, a documented
10
+ * `Retry-After`, and a herd of concurrent callers that will all rediscover the
11
+ * same limit unless something holds them back together.
12
+ *
13
+ * SAP AI Core states the rules this implements
14
+ * (help.sap.com, Rate Limit Management):
15
+ *
16
+ * Do not retry immediately after a 429. Use exponential backoff with jitter
17
+ * to prevent thundering-herd behavior. If the response includes a
18
+ * `Retry-After` header, use its value as the wait interval. Limit retries by
19
+ * using a maximum cap (for example, 5 retries or 60 seconds total wait).
20
+ *
21
+ * The same rules fit Anthropic and OpenAI, which return 429 with `Retry-After`
22
+ * as ordinary HTTP. Ollama is local and never rate-limited; it simply never
23
+ * enters this path.
24
+ */
25
+ export interface RateLimitPolicy {
26
+ /** Set false to pass every error straight through. */
27
+ enabled: boolean;
28
+ /** Total attempts INCLUDING the first. 1 disables retrying without disabling the gate. */
29
+ maxAttempts: number;
30
+ /** Give up once the accumulated waiting would exceed this. */
31
+ maxTotalWaitMs: number;
32
+ /** First backoff step when the server names no time of its own. */
33
+ baseDelayMs: number;
34
+ /** Ceiling for one computed step, before jitter. */
35
+ maxDelayMs: number;
36
+ }
37
+ export declare const DEFAULT_RATE_LIMIT_POLICY: RateLimitPolicy;
38
+ /** An error annotated by this module, so consumers read a fact instead of prose. */
39
+ export interface RateLimitedError extends Error {
40
+ rateLimited: true;
41
+ /** What the server asked for, when it asked. */
42
+ retryAfterSeconds?: number;
43
+ /** How many attempts were spent before giving up. */
44
+ attempts: number;
45
+ }
46
+ export declare function isRateLimitedError(e: unknown): e is RateLimitedError;
47
+ /**
48
+ * One shared pause per quota.
49
+ *
50
+ * A rate limit belongs to the quota, not to the request that happened to
51
+ * discover it. When one call is told to wait, every other call against the same
52
+ * quota is already over the limit too — letting each find that out for itself
53
+ * means N more rejected requests and N more penalties, which is how a limit
54
+ * that should last one window lasts several.
55
+ *
56
+ * Keyed because limits are per model: one model being throttled says nothing
57
+ * about another.
58
+ */
59
+ export declare class RateLimitGate {
60
+ private notBefore;
61
+ /** Hold every caller until the named time. Never shortens an existing hold. */
62
+ penalise(ms: number, now?: number): void;
63
+ /** Milliseconds still to wait, or 0. */
64
+ remaining(now?: number): number;
65
+ clear(): void;
66
+ /**
67
+ * Wait out any active hold.
68
+ *
69
+ * Re-checked after every wake, because the hold is not fixed at the moment
70
+ * this caller started waiting: another caller's 429 extends it, and a sleeper
71
+ * timed against the old deadline would wake early and spend a request on a
72
+ * quota the server has since said is still closed.
73
+ *
74
+ * The wait is jittered per caller: without that, everyone released by one
75
+ * penalty wakes in the same millisecond and rebuilds the herd the penalty was
76
+ * meant to break up.
77
+ */
78
+ waitUntilOpen(signal?: AbortSignal, maxWaitMs?: number): Promise<void>;
79
+ }
80
+ /**
81
+ * Keys are open-ended — a per-request model override mints a new one — so the
82
+ * registry is pruned rather than left to grow for the life of the process.
83
+ * Only entries that are both open and long idle are dropped: a held gate is
84
+ * doing its job, and a recently used one may still be someone's in-flight call.
85
+ */
86
+ export declare const GATE_IDLE_TTL_MS: number;
87
+ /**
88
+ * How many quotas the registry keeps. Enforced by evicting gates that are both
89
+ * open and unheld, so a process with more than this many pauses or in-flight
90
+ * calls at once can exceed it — those gates are load-bearing. Evicting an idle
91
+ * one costs nothing: it carries no deadline, and the next call mints an
92
+ * equivalent.
93
+ */
94
+ export declare const GATE_LIMIT = 500;
95
+ /** The gate for one quota. Shared across every provider instance in the process. */
96
+ export declare function gateFor(key: string): RateLimitGate;
97
+ /**
98
+ * Hold a gate for the length of one call, and keep it in the registry meanwhile.
99
+ *
100
+ * Being open is not the same as being unused. A request is in flight for as
101
+ * long as it takes the server to answer, and until that answer arrives the gate
102
+ * it belongs to has no pause on it. Evicted in that window, the call would go on
103
+ * to penalise an object the registry no longer knows: the pause would be real
104
+ * and invisible, and the next caller would be handed a fresh open gate and sent
105
+ * straight into a quota that had just closed. Worse than no gate at all, since
106
+ * the penalty is paid and nothing is bought with it.
107
+ *
108
+ * Release in a `finally`. Releasing twice is harmless.
109
+ */
110
+ export declare function leaseRateLimitGate(key: string): {
111
+ gate: RateLimitGate;
112
+ release: () => void;
113
+ };
114
+ /**
115
+ * Reclaim gates that are open and long idle. Exported so a long-lived host can
116
+ * run it on a timer, and so the reclaim can be tested at a chosen `now` without
117
+ * waiting ten minutes. It frees only what is certainly stale; the size bound is
118
+ * enforced separately, by `reclaimGates`.
119
+ */
120
+ export declare function pruneRateLimitGates(now?: number): void;
121
+ /** How many quotas are currently tracked. Diagnostics and tests. */
122
+ export declare function rateLimitGateCount(): number;
123
+ /** Test seam — drops every gate. */
124
+ export declare function resetRateLimitGates(): void;
125
+ export interface RateLimitRetryOptions {
126
+ /** Identifies the quota being spent. Limits are per model, so include it. */
127
+ key: string;
128
+ policy?: Partial<RateLimitPolicy>;
129
+ /** Does this error mean "too many requests"? */
130
+ isRateLimited: (error: unknown) => boolean;
131
+ /** What the server asked us to wait, in seconds, if it said. */
132
+ retryAfterSeconds?: (error: unknown) => number | undefined;
133
+ signal?: AbortSignal;
134
+ onRetry?: (info: {
135
+ attempt: number;
136
+ delayMs: number;
137
+ retryAfterSeconds?: number;
138
+ }) => void;
139
+ }
140
+ /**
141
+ * Run `fn`, respecting the shared pause and retrying a rate limit.
142
+ *
143
+ * Any error that is not a rate limit is rethrown untouched and immediately —
144
+ * this is not a general-purpose retry, and turning a genuine failure into four
145
+ * more of them would be worse than the failure.
146
+ */
147
+ export declare function runWithRateLimitRetry<T>(fn: () => Promise<T>, opts: RateLimitRetryOptions): Promise<T>;
148
+ /**
149
+ * The rate-limit marker on an error or anywhere in its `cause` chain.
150
+ *
151
+ * Layers above the provider rewrap errors — the ILlm adapter turns anything
152
+ * thrown into an `LlmError` — so the marker is rarely still on the outermost
153
+ * object by the time a retry decorator inspects it. Bounded by depth and a
154
+ * visited set, like `extractStatusCode`, so a cyclic chain cannot hang.
155
+ */
156
+ export declare function findRateLimit(err: unknown): RateLimitedError | undefined;
157
+ /**
158
+ * Carry the rate-limit facts onto a provider's own error.
159
+ *
160
+ * Every provider catches its transport error and rethrows something friendlier.
161
+ * That is fine for a message and fatal for a fact: the 429 would be lost at the
162
+ * exact boundary where the consumer needs it. This moves the facts across.
163
+ */
164
+ export declare function preserveRateLimit<E extends Error>(original: unknown, wrapped: E): E;
165
+ //# sourceMappingURL=rate-limit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../src/llm/rate-limit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,394 @@
1
+ /**
2
+ * Shared rate-limit handling for every LLM provider.
3
+ *
4
+ * None of the providers handled HTTP 429 (issue #282). Each one let the status
5
+ * collapse into an error message, so consumers were reduced to matching the
6
+ * substring "429" in prose to decide whether to retry.
7
+ *
8
+ * The policy lives here rather than in a provider because the providers differ
9
+ * in SDK but not in the shape of the problem: a shared quota, a documented
10
+ * `Retry-After`, and a herd of concurrent callers that will all rediscover the
11
+ * same limit unless something holds them back together.
12
+ *
13
+ * SAP AI Core states the rules this implements
14
+ * (help.sap.com, Rate Limit Management):
15
+ *
16
+ * Do not retry immediately after a 429. Use exponential backoff with jitter
17
+ * to prevent thundering-herd behavior. If the response includes a
18
+ * `Retry-After` header, use its value as the wait interval. Limit retries by
19
+ * using a maximum cap (for example, 5 retries or 60 seconds total wait).
20
+ *
21
+ * The same rules fit Anthropic and OpenAI, which return 429 with `Retry-After`
22
+ * as ordinary HTTP. Ollama is local and never rate-limited; it simply never
23
+ * enters this path.
24
+ */
25
+ import { DefaultWaitStrategy } from '../interfaces/wait-strategy.js';
26
+ export const DEFAULT_RATE_LIMIT_POLICY = {
27
+ enabled: true,
28
+ maxAttempts: 5,
29
+ maxTotalWaitMs: 60_000,
30
+ baseDelayMs: 1_000,
31
+ maxDelayMs: 20_000,
32
+ };
33
+ export function isRateLimitedError(e) {
34
+ return (typeof e === 'object' &&
35
+ e !== null &&
36
+ e.rateLimited === true);
37
+ }
38
+ const waiter = new DefaultWaitStrategy();
39
+ /**
40
+ * Sleep, or throw if the signal aborts.
41
+ *
42
+ * Delegated to the shared wait strategy rather than hand-rolled: the listener
43
+ * bookkeeping is where a sleep like this goes wrong. One added per backoff and
44
+ * never removed accumulates on a request- or session-scoped signal until Node
45
+ * warns about the leak — a defect this repository has already fixed once, in
46
+ * the retry decorators. Fixed in one place, it stays fixed for every waiter.
47
+ */
48
+ const sleep = async (ms, signal) => {
49
+ const aborted = () => signal?.reason ?? new Error('Aborted');
50
+ if (signal?.aborted)
51
+ throw aborted();
52
+ if ((await waiter.wait(ms, signal)) === 'aborted')
53
+ throw aborted();
54
+ };
55
+ /**
56
+ * Per-caller spread on waking, so everyone released by one penalty does not
57
+ * wake in the same millisecond and rebuild the herd the penalty broke up.
58
+ *
59
+ * It is added to a wait, never to a budget: a wait capped at exactly the budget
60
+ * simply loses the spread, which costs nothing — a caller whose budget ends
61
+ * with the pause is leaving either way.
62
+ */
63
+ const WAKE_SPREAD_MS = 250;
64
+ /**
65
+ * One shared pause per quota.
66
+ *
67
+ * A rate limit belongs to the quota, not to the request that happened to
68
+ * discover it. When one call is told to wait, every other call against the same
69
+ * quota is already over the limit too — letting each find that out for itself
70
+ * means N more rejected requests and N more penalties, which is how a limit
71
+ * that should last one window lasts several.
72
+ *
73
+ * Keyed because limits are per model: one model being throttled says nothing
74
+ * about another.
75
+ */
76
+ export class RateLimitGate {
77
+ notBefore = 0;
78
+ /** Hold every caller until the named time. Never shortens an existing hold. */
79
+ penalise(ms, now = Date.now()) {
80
+ this.notBefore = Math.max(this.notBefore, now + ms);
81
+ }
82
+ /** Milliseconds still to wait, or 0. */
83
+ remaining(now = Date.now()) {
84
+ return Math.max(0, this.notBefore - now);
85
+ }
86
+ clear() {
87
+ this.notBefore = 0;
88
+ }
89
+ /**
90
+ * Wait out any active hold.
91
+ *
92
+ * Re-checked after every wake, because the hold is not fixed at the moment
93
+ * this caller started waiting: another caller's 429 extends it, and a sleeper
94
+ * timed against the old deadline would wake early and spend a request on a
95
+ * quota the server has since said is still closed.
96
+ *
97
+ * The wait is jittered per caller: without that, everyone released by one
98
+ * penalty wakes in the same millisecond and rebuilds the herd the penalty was
99
+ * meant to break up.
100
+ */
101
+ async waitUntilOpen(signal, maxWaitMs = Number.POSITIVE_INFINITY) {
102
+ const deadline = Date.now() + maxWaitMs;
103
+ for (;;) {
104
+ const left = this.remaining();
105
+ if (left <= 0)
106
+ return;
107
+ const budget = deadline - Date.now();
108
+ // Out of the caller's budget with the gate still shut. It is told, not
109
+ // held: `remaining()` still reports the hold, and the caller decides.
110
+ if (budget <= 0)
111
+ return;
112
+ // A timer that lands a hair early is simply waited out again on the next
113
+ // turn, which is why the cap needs no padding to be safe.
114
+ await sleep(Math.min(left + Math.random() * WAKE_SPREAD_MS, budget), signal);
115
+ }
116
+ }
117
+ }
118
+ const gates = new Map();
119
+ /**
120
+ * Keys are open-ended — a per-request model override mints a new one — so the
121
+ * registry is pruned rather than left to grow for the life of the process.
122
+ * Only entries that are both open and long idle are dropped: a held gate is
123
+ * doing its job, and a recently used one may still be someone's in-flight call.
124
+ */
125
+ export const GATE_IDLE_TTL_MS = 10 * 60_000;
126
+ /**
127
+ * How many quotas the registry keeps. Enforced by evicting gates that are both
128
+ * open and unheld, so a process with more than this many pauses or in-flight
129
+ * calls at once can exceed it — those gates are load-bearing. Evicting an idle
130
+ * one costs nothing: it carries no deadline, and the next call mints an
131
+ * equivalent.
132
+ */
133
+ export const GATE_LIMIT = 500;
134
+ /** The gate for one quota. Shared across every provider instance in the process. */
135
+ export function gateFor(key) {
136
+ const now = Date.now();
137
+ const existing = gates.get(key);
138
+ if (existing) {
139
+ existing.lastUsed = now;
140
+ return existing.gate;
141
+ }
142
+ if (gates.size >= GATE_LIMIT)
143
+ reclaimGates(now);
144
+ const entry = {
145
+ gate: new RateLimitGate(),
146
+ lastUsed: now,
147
+ leases: 0,
148
+ };
149
+ gates.set(key, entry);
150
+ return entry.gate;
151
+ }
152
+ /**
153
+ * Hold a gate for the length of one call, and keep it in the registry meanwhile.
154
+ *
155
+ * Being open is not the same as being unused. A request is in flight for as
156
+ * long as it takes the server to answer, and until that answer arrives the gate
157
+ * it belongs to has no pause on it. Evicted in that window, the call would go on
158
+ * to penalise an object the registry no longer knows: the pause would be real
159
+ * and invisible, and the next caller would be handed a fresh open gate and sent
160
+ * straight into a quota that had just closed. Worse than no gate at all, since
161
+ * the penalty is paid and nothing is bought with it.
162
+ *
163
+ * Release in a `finally`. Releasing twice is harmless.
164
+ */
165
+ export function leaseRateLimitGate(key) {
166
+ const gate = gateFor(key);
167
+ // By identity, not by key: `resetRateLimitGates` may have replaced the entry
168
+ // by the time this is released, and that entry's count is not ours to touch.
169
+ const entry = gates.get(key);
170
+ if (entry)
171
+ entry.leases += 1;
172
+ let released = false;
173
+ return {
174
+ gate,
175
+ release: () => {
176
+ if (released || !entry)
177
+ return;
178
+ released = true;
179
+ entry.leases -= 1;
180
+ entry.lastUsed = Date.now();
181
+ },
182
+ };
183
+ }
184
+ /**
185
+ * Reclaim gates that are open and long idle. Exported so a long-lived host can
186
+ * run it on a timer, and so the reclaim can be tested at a chosen `now` without
187
+ * waiting ten minutes. It frees only what is certainly stale; the size bound is
188
+ * enforced separately, by `reclaimGates`.
189
+ */
190
+ export function pruneRateLimitGates(now = Date.now()) {
191
+ for (const [key, entry] of gates) {
192
+ if (isEvictable(entry, now) && now - entry.lastUsed > GATE_IDLE_TTL_MS) {
193
+ gates.delete(key);
194
+ }
195
+ }
196
+ }
197
+ /**
198
+ * Safe to forget: nobody is waiting behind it, and no call is holding it.
199
+ *
200
+ * An open, unleased gate carries no state at all — the next call mints an
201
+ * equivalent one and loses nothing.
202
+ */
203
+ function isEvictable(entry, now) {
204
+ return entry.leases === 0 && entry.gate.remaining(now) === 0;
205
+ }
206
+ /**
207
+ * Hold the registry at `GATE_LIMIT`.
208
+ *
209
+ * Age alone is not a bound: a burst of per-request model overrides mints keys
210
+ * far faster than the ten-minute idle rule retires them, and the map grows for
211
+ * as long as the burst lasts. So once the stale entries are gone, the least
212
+ * recently used OPEN gates go too, oldest first, until the registry is back
213
+ * under the limit.
214
+ *
215
+ * Only idle gates are evicted: not one holding a pause, which IS the pause, and
216
+ * not one a call is still holding, which may be about to become a pause.
217
+ */
218
+ function reclaimGates(now) {
219
+ pruneRateLimitGates(now);
220
+ if (gates.size < GATE_LIMIT)
221
+ return;
222
+ const evictable = [...gates]
223
+ .filter(([, entry]) => isEvictable(entry, now))
224
+ .sort((a, b) => a[1].lastUsed - b[1].lastUsed);
225
+ for (const [key] of evictable) {
226
+ if (gates.size < GATE_LIMIT)
227
+ return;
228
+ gates.delete(key);
229
+ }
230
+ }
231
+ /** How many quotas are currently tracked. Diagnostics and tests. */
232
+ export function rateLimitGateCount() {
233
+ return gates.size;
234
+ }
235
+ /** Test seam — drops every gate. */
236
+ export function resetRateLimitGates() {
237
+ gates.clear();
238
+ }
239
+ /**
240
+ * Run `fn`, respecting the shared pause and retrying a rate limit.
241
+ *
242
+ * Any error that is not a rate limit is rethrown untouched and immediately —
243
+ * this is not a general-purpose retry, and turning a genuine failure into four
244
+ * more of them would be worse than the failure.
245
+ */
246
+ export async function runWithRateLimitRetry(fn, opts) {
247
+ const policy = { ...DEFAULT_RATE_LIMIT_POLICY, ...opts.policy };
248
+ if (!policy.enabled)
249
+ return fn();
250
+ const { gate, release } = leaseRateLimitGate(opts.key);
251
+ try {
252
+ return await attemptWithGate(fn, opts, policy, gate);
253
+ }
254
+ finally {
255
+ release();
256
+ }
257
+ }
258
+ async function attemptWithGate(fn, opts, policy, gate) {
259
+ let attempt = 0;
260
+ let waited = 0;
261
+ for (;;) {
262
+ // Waiting at the gate is waiting. Uncounted it was free, so a caller with a
263
+ // ten-millisecond budget could sit out a minute-long pause another caller
264
+ // had earned, its declared budget saying nothing about the time it spent.
265
+ // Decided before the wait, never during it: a hold longer than what is left
266
+ // of the budget is refused outright rather than half-served.
267
+ const hold = gate.remaining();
268
+ if (hold > 0) {
269
+ const budgetLeft = policy.maxTotalWaitMs - waited;
270
+ if (hold > budgetLeft)
271
+ throw outOfBudgetAtGate(hold, attempt);
272
+ const before = Date.now();
273
+ // Capped as well as pre-checked: a pause that fits when the wait starts
274
+ // can be extended past the budget by someone else's 429 while it runs.
275
+ // The cap is the budget exactly. Padding it to protect the wake spread
276
+ // would make the budget the one thing it must not be — approximate.
277
+ await gate.waitUntilOpen(opts.signal, budgetLeft);
278
+ waited += Date.now() - before;
279
+ const stillShut = gate.remaining();
280
+ if (stillShut > 0)
281
+ throw outOfBudgetAtGate(stillShut, attempt);
282
+ }
283
+ try {
284
+ return await fn();
285
+ }
286
+ catch (error) {
287
+ if (!opts.isRateLimited(error))
288
+ throw error;
289
+ attempt += 1;
290
+ const retryAfter = opts.retryAfterSeconds?.(error);
291
+ const served = retryAfter !== undefined && Number.isFinite(retryAfter);
292
+ // The server's own number wins. A computed guess is for when it stays
293
+ // silent — it is an estimate of something the server already knows.
294
+ const wait = served
295
+ ? retryAfter * 1000
296
+ : jittered(policy.baseDelayMs * 2 ** (attempt - 1), policy.maxDelayMs);
297
+ // Hold everyone else too, and hold them whether or not THIS caller has
298
+ // budget left to retry. The pause describes the quota, not one request:
299
+ // giving up with the gate open sends every other caller straight back
300
+ // into a limit the server just said was closed.
301
+ //
302
+ // The gate is given the interval alone. This caller then serves its own
303
+ // backoff there, on the next turn of the loop, rather than sleeping
304
+ // beside it: two mechanisms waiting out one pause double-count it, and
305
+ // the leftover milliseconds between them decide outcomes at the margin.
306
+ // The per-caller spread that keeps the released callers from waking
307
+ // together lives in `waitUntilOpen`, where the waiting is done.
308
+ gate.penalise(wait);
309
+ const outOfAttempts = attempt >= policy.maxAttempts;
310
+ const outOfTime = waited + wait > policy.maxTotalWaitMs;
311
+ if (outOfAttempts || outOfTime) {
312
+ throw annotate(error, attempt, retryAfter);
313
+ }
314
+ opts.onRetry?.({
315
+ attempt,
316
+ delayMs: wait,
317
+ retryAfterSeconds: retryAfter,
318
+ });
319
+ }
320
+ }
321
+ }
322
+ /**
323
+ * Full jitter: a random point in [0, computed], not computed ± a nudge.
324
+ *
325
+ * Scaling a shared delay by a factor near 1 keeps the herd together; drawing
326
+ * from the whole interval is what actually spreads it.
327
+ */
328
+ function jittered(computed, maxDelayMs) {
329
+ return Math.random() * Math.min(computed, maxDelayMs);
330
+ }
331
+ const MAX_CAUSE_DEPTH = 5;
332
+ /**
333
+ * The rate-limit marker on an error or anywhere in its `cause` chain.
334
+ *
335
+ * Layers above the provider rewrap errors — the ILlm adapter turns anything
336
+ * thrown into an `LlmError` — so the marker is rarely still on the outermost
337
+ * object by the time a retry decorator inspects it. Bounded by depth and a
338
+ * visited set, like `extractStatusCode`, so a cyclic chain cannot hang.
339
+ */
340
+ export function findRateLimit(err) {
341
+ const visited = new Set();
342
+ let cur = err;
343
+ for (let depth = 0; depth < MAX_CAUSE_DEPTH; depth++) {
344
+ if (typeof cur !== 'object' || cur === null || visited.has(cur))
345
+ return undefined;
346
+ visited.add(cur);
347
+ if (isRateLimitedError(cur))
348
+ return cur;
349
+ cur = cur.cause;
350
+ }
351
+ return undefined;
352
+ }
353
+ /**
354
+ * Carry the rate-limit facts onto a provider's own error.
355
+ *
356
+ * Every provider catches its transport error and rethrows something friendlier.
357
+ * That is fine for a message and fatal for a fact: the 429 would be lost at the
358
+ * exact boundary where the consumer needs it. This moves the facts across.
359
+ */
360
+ export function preserveRateLimit(original, wrapped) {
361
+ if (!isRateLimitedError(original))
362
+ return wrapped;
363
+ const w = wrapped;
364
+ w.rateLimited = true;
365
+ w.attempts = original.attempts;
366
+ if (original.retryAfterSeconds !== undefined) {
367
+ w.retryAfterSeconds = original.retryAfterSeconds;
368
+ }
369
+ return w;
370
+ }
371
+ /**
372
+ * The shared pause outlasts what this caller said it would wait.
373
+ *
374
+ * There is no server error to annotate here — the request was never sent,
375
+ * because sending it into a quota known to be closed is the one thing the gate
376
+ * exists to prevent. It is still a rate limit, and says so, carrying what is
377
+ * left of the pause so the consumer can decide.
378
+ */
379
+ function outOfBudgetAtGate(remainingMs, attempts) {
380
+ const e = new Error('rate limited: the pause on this quota outlasts the configured wait budget');
381
+ e.rateLimited = true;
382
+ e.attempts = attempts;
383
+ e.retryAfterSeconds = remainingMs / 1000;
384
+ return e;
385
+ }
386
+ function annotate(error, attempts, retryAfterSeconds) {
387
+ const e = (error instanceof Error ? error : new Error(String(error)));
388
+ e.rateLimited = true;
389
+ e.attempts = attempts;
390
+ if (retryAfterSeconds !== undefined)
391
+ e.retryAfterSeconds = retryAfterSeconds;
392
+ return e;
393
+ }
394
+ //# sourceMappingURL=rate-limit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../../src/llm/rate-limit.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  * Core types for LLM Proxy
3
3
  */
4
4
  import type { LlmUsage } from './interfaces/types.js';
5
+ import type { RateLimitPolicy } from './llm/rate-limit.js';
5
6
  export interface Message {
6
7
  /**
7
8
  * Message role
@@ -77,6 +78,13 @@ export interface LLMProviderConfig {
77
78
  model?: string;
78
79
  temperature?: number;
79
80
  maxTokens?: number;
81
+ /**
82
+ * How this provider answers HTTP 429. Omit for the shared default: back off
83
+ * with jitter, honour `Retry-After`, give up after 5 attempts or 60 seconds
84
+ * of waiting. Set `{ enabled: false }` to pass every 429 straight to the
85
+ * caller.
86
+ */
87
+ rateLimit?: Partial<RateLimitPolicy>;
80
88
  }
81
89
  export interface LLMCallOptions {
82
90
  /** Per-request model override. */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEtD,MAAM,WAAW,OAAO;IACtB;;;;;;OAMG;IACH,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,QAAQ,GAAG,MAAM,CAAC;IAC/C,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,iGAAiG;IACjG,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uFAAuF;IACvF,UAAU,CAAC,EAAE,KAAK,CAAC;QACjB,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,UAAU,CAAC;QACjB,QAAQ,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,SAAS,EAAE,MAAM,CAAA;SAAE,CAAC;KAC/C,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;+EAG2E;IAC3E,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,KAAK,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;QACd,EAAE,CAAC,EAAE,MAAM,CAAC;QACZ,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC,CAAC;IACH;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,QAAQ,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,8FAA8F;IAC9F,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,kCAAkC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAMD,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,SAAS,EAAE,QAAQ,EAAE,CAAA;CAAE,GAC7C;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAA;CAAE,GACjE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,GAAG,YAAY,GAAG,QAAQ,GAAG,OAAO,CAAA;CAAE,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-abap-adt/llm-agent",
3
- "version": "22.0.1",
3
+ "version": "22.2.0",
4
4
  "description": "Core interfaces, types, and lightweight default implementations for LLM agent orchestration.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -24,7 +24,7 @@
24
24
  "test": "node --import tsx/esm --test --test-reporter=spec 'src/**/*.test.ts'"
25
25
  },
26
26
  "dependencies": {
27
- "zod": "^4.4.3"
27
+ "zod": "^4.5.4"
28
28
  },
29
29
  "license": "LGPL-3.0-only",
30
30
  "repository": {