@vymalo/opencode-ratelimit 0.5.0 → 0.6.1

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/README.md CHANGED
@@ -56,12 +56,44 @@ A provider opts in via `options.meta.rateLimit`. All fields are optional:
56
56
  | Field | Default | Meaning |
57
57
  | --- | --- | --- |
58
58
  | `enabled` | `true` | Set `false` to opt a provider out while keeping the block. Omitting the whole `rateLimit` block also opts out. |
59
+ | `scope` | `model` | `model` keys cooldown state per `(provider, model)`; `provider` shares one cooldown across the whole provider. See [Per-model scope](#per-model-scope). |
59
60
  | `maxWaitMs` | `0` | Upper bound on any single wait, in ms. **`0` = unlimited** (wait the full reset window). |
60
61
  | `maxRetries` | `5` | How many times a `429` is retried before the response is handed back as-is. |
61
62
  | `headerPrefix` | `x-ratelimit` | Lowercased prefix of the draft-03 triple. Remap for gateways that use e.g. `ratelimit-*`. |
63
+ | `tiers` | — | Optional array of policy bands keyed by reset magnitude. Replaces flat `maxWaitMs`/`maxRetries` with per-window behavior. See [Tiered policies](#tiered-policies). |
62
64
 
63
65
  If you also use `@vymalo/opencode-oauth2`, list it **before** this plugin in `plugin` (config hooks run in registration order). It is only a soft recommendation — this plugin's fetch wrapping is auth-independent.
64
66
 
67
+ ### Tiered policies
68
+
69
+ A single `maxWaitMs` can't tell a **per-minute burst reset** (≤60s — worth waiting through) from a **monthly-budget reset** (days away — should error fast): both arrive as the same `x-ratelimit-reset`. The discriminator is the reset's *magnitude*, and `tiers` lets you act on it. Each tier matches resets up to `maxResetSeconds` (first match wins; ascending, catch-all last):
70
+
71
+ ```jsonc
72
+ "meta": { "rateLimit": {
73
+ "scope": "model",
74
+ "tiers": [
75
+ { "maxResetSeconds": 120, "action": "wait", "maxWaitMs": 0, "maxRetries": 3 }, // ≤2min: wait it out
76
+ { "action": "error" } // longer: surface the 429 now
77
+ ]
78
+ } }
79
+ ```
80
+
81
+ | Tier field | Default | Meaning |
82
+ | --- | --- | --- |
83
+ | `maxResetSeconds` | `null` (catch-all) | Inclusive upper bound, in seconds, of the resets this tier matches. |
84
+ | `action` | `wait` | `wait` throttles + backs off; `error` surfaces the `429` **immediately** (no wait, no retry). |
85
+ | `maxWaitMs` | `0` | (`wait` only) cap on a single wait; `0` = unlimited. |
86
+ | `maxRetries` | `5` | (`wait` only) `429` retries before surfacing. |
87
+
88
+ Notes:
89
+ - A catch-all tier (no `maxResetSeconds`) is auto-appended if you don't supply one; the implicit fallback is `wait` (the safe direction — never hammer the gateway). For "error on anything unexpectedly long," add an explicit `{ "action": "error" }` last.
90
+ - The flat `maxWaitMs`/`maxRetries` form still works — it's normalized into a single catch-all `wait` tier.
91
+ - An `error`-tier `remaining: 0` does **not** pre-arm the gate; the next request hits a real `429` and is surfaced immediately.
92
+
93
+ ### Per-model scope
94
+
95
+ If your gateway buckets per model (e.g. Envoy `BackendTrafficPolicy` selectors that include the model), `scope: "model"` (the default) keys cooldown state per `(provider, model)` — so exhausting one model's bucket never blocks requests to a sibling model that still has quota. The model is read from the request body (`@ai-sdk/openai-compatible` sends a JSON body with `model`); when it can't be parsed the bucket falls back to provider-wide. Use `scope: "provider"` if your gateway buckets per provider/account.
96
+
65
97
  ## Caveats
66
98
 
67
99
  ### Long waits can be cut short by OpenCode's own timeouts
@@ -93,7 +125,8 @@ Structured events flow through OpenCode's log stream (service `opencode-ratelimi
93
125
  | `ratelimit_provider_skipped` | debug | a provider did not opt in |
94
126
  | `ratelimit_quota` | debug | every response (`remaining`, `limit`, `resetSeconds`) |
95
127
  | `ratelimit_throttle_wait` | info | pre-request gate engaged |
96
- | `ratelimit_429_backoff` | warn | a `429` was retried |
128
+ | `ratelimit_429_backoff` | warn | a `429` was retried (`wait` tier) |
129
+ | `ratelimit_failfast` | warn | a `429` was surfaced immediately (`error` tier — reset too far out to wait) |
97
130
  | `ratelimit_wait_aborted` | warn | a wait was cancelled by the request signal |
98
131
  | `ratelimit_giveup` | error | `maxRetries` exhausted, `429` returned as-is |
99
132
  | `ratelimit_header_parse_failed` | debug | a response's headers could not be parsed |
package/dist/config.d.ts CHANGED
@@ -1,13 +1,23 @@
1
- import type { RateLimitOptions } from "./types.js";
1
+ import type { RateLimitOptions, RateLimitScope, RateLimitTier } from "./types.js";
2
2
  export declare const DEFAULT_MAX_WAIT_MS = 0;
3
3
  export declare const DEFAULT_MAX_RETRIES = 5;
4
4
  export declare const DEFAULT_HEADER_PREFIX = "x-ratelimit";
5
+ export declare const DEFAULT_SCOPE: RateLimitScope;
5
6
  /**
6
7
  * Parse a provider's `options.meta.rateLimit` block into resolved
7
8
  * {@link RateLimitOptions}. Returns `null` when the provider has NOT opted in
8
- * (no `meta.rateLimit`) or has explicitly disabled it (`enabled: false`) — in
9
- * both cases the provider's fetch is left untouched.
9
+ * (no `meta.rateLimit`) or has explicitly disabled it (`enabled: false`).
10
10
  *
11
- * Mirrors `@vymalo/opencode-models-info`'s `parseMetaOptions` opt-in idiom.
11
+ * Two config styles, both supported:
12
+ * - **Flat**: `maxWaitMs` / `maxRetries` → normalized into a single catch-all
13
+ * `"wait"` tier (the 0.5.0 behavior).
14
+ * - **Tiered**: a `tiers` array, each entry `{ maxResetSeconds?, action?,
15
+ * maxWaitMs?, maxRetries? }`, matched by reset magnitude.
12
16
  */
13
17
  export declare function parseRateLimitOptions(providerOptions: Record<string, unknown> | undefined): RateLimitOptions | null;
18
+ /**
19
+ * Select the policy tier for an observed reset. `tiers` is pre-sorted ascending
20
+ * with a catch-all last, so the first tier whose bound covers `resetSeconds`
21
+ * wins. An unknown reset is treated as the smallest band (wait a default backoff).
22
+ */
23
+ export declare function selectTier(tiers: RateLimitTier[], resetSeconds: number | undefined): RateLimitTier;
package/dist/config.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export const DEFAULT_MAX_WAIT_MS = 0; // 0 = unlimited (wait the full reset window)
2
2
  export const DEFAULT_MAX_RETRIES = 5;
3
3
  export const DEFAULT_HEADER_PREFIX = "x-ratelimit";
4
+ export const DEFAULT_SCOPE = "model";
4
5
  const META_KEY = "meta";
5
6
  const RATE_LIMIT_KEY = "rateLimit";
6
7
  function asRecord(value) {
@@ -28,13 +29,63 @@ function asNonNegativeInt(value, fallback) {
28
29
  }
29
30
  return fallback;
30
31
  }
32
+ function asScope(value) {
33
+ return value === "provider" || value === "model" ? value : DEFAULT_SCOPE;
34
+ }
35
+ function asAction(value) {
36
+ return value === "error" ? "error" : "wait";
37
+ }
38
+ /** A positive int, or `null` (the catch-all sentinel) when absent/non-positive. */
39
+ function asMaxResetSeconds(value) {
40
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) {
41
+ return Math.floor(value);
42
+ }
43
+ return null;
44
+ }
45
+ function parseTier(raw) {
46
+ return {
47
+ maxResetSeconds: asMaxResetSeconds(raw.maxResetSeconds),
48
+ action: asAction(raw.action),
49
+ maxWaitMs: asNonNegativeInt(raw.maxWaitMs, DEFAULT_MAX_WAIT_MS),
50
+ maxRetries: asPositiveInt(raw.maxRetries, DEFAULT_MAX_RETRIES)
51
+ };
52
+ }
53
+ /**
54
+ * Sort ascending by `maxResetSeconds` (catch-all `null` last) and guarantee a
55
+ * trailing catch-all so every reset magnitude matches some tier. Without an
56
+ * explicit catch-all the ultimate fallback is "wait the full window" — the safe
57
+ * direction (never hammer the gateway).
58
+ */
59
+ function normalizeTiers(tiers) {
60
+ const sorted = [...tiers].sort((a, b) => {
61
+ if (a.maxResetSeconds === b.maxResetSeconds)
62
+ return 0; // equal (incl. both null)
63
+ if (a.maxResetSeconds === null)
64
+ return 1;
65
+ if (b.maxResetSeconds === null)
66
+ return -1;
67
+ return a.maxResetSeconds - b.maxResetSeconds;
68
+ });
69
+ if (!sorted.some((t) => t.maxResetSeconds === null)) {
70
+ sorted.push({
71
+ maxResetSeconds: null,
72
+ action: "wait",
73
+ maxWaitMs: DEFAULT_MAX_WAIT_MS,
74
+ maxRetries: DEFAULT_MAX_RETRIES
75
+ });
76
+ }
77
+ return sorted;
78
+ }
31
79
  /**
32
80
  * Parse a provider's `options.meta.rateLimit` block into resolved
33
81
  * {@link RateLimitOptions}. Returns `null` when the provider has NOT opted in
34
- * (no `meta.rateLimit`) or has explicitly disabled it (`enabled: false`) — in
35
- * both cases the provider's fetch is left untouched.
82
+ * (no `meta.rateLimit`) or has explicitly disabled it (`enabled: false`).
36
83
  *
37
- * Mirrors `@vymalo/opencode-models-info`'s `parseMetaOptions` opt-in idiom.
84
+ * Two config styles, both supported:
85
+ * - **Flat**: `maxWaitMs` / `maxRetries` → normalized into a single catch-all
86
+ * `"wait"` tier (the 0.5.0 behavior).
87
+ * - **Tiered**: a `tiers` array, each entry `{ maxResetSeconds?, action?,
88
+ * maxWaitMs?, maxRetries? }`, matched by reset magnitude.
38
89
  */
39
90
  export function parseRateLimitOptions(providerOptions) {
40
91
  if (!providerOptions) {
@@ -52,11 +103,41 @@ export function parseRateLimitOptions(providerOptions) {
52
103
  if (!enabled) {
53
104
  return null;
54
105
  }
106
+ const rawTiers = Array.isArray(rateLimit.tiers) ? rateLimit.tiers : undefined;
107
+ let tiers;
108
+ if (rawTiers && rawTiers.length > 0) {
109
+ tiers = normalizeTiers(rawTiers.map((t) => parseTier(asRecord(t) ?? {})));
110
+ }
111
+ else {
112
+ // Flat config → one catch-all "wait" tier (backward compatible with 0.5.0).
113
+ tiers = [
114
+ {
115
+ maxResetSeconds: null,
116
+ action: "wait",
117
+ maxWaitMs: asNonNegativeInt(rateLimit.maxWaitMs, DEFAULT_MAX_WAIT_MS),
118
+ maxRetries: asPositiveInt(rateLimit.maxRetries, DEFAULT_MAX_RETRIES)
119
+ }
120
+ ];
121
+ }
55
122
  return {
56
123
  enabled: true,
57
- maxWaitMs: asNonNegativeInt(rateLimit.maxWaitMs, DEFAULT_MAX_WAIT_MS),
58
- maxRetries: asPositiveInt(rateLimit.maxRetries, DEFAULT_MAX_RETRIES),
59
- headerPrefix: (asString(rateLimit.headerPrefix) ?? DEFAULT_HEADER_PREFIX).toLowerCase()
124
+ scope: asScope(rateLimit.scope),
125
+ headerPrefix: (asString(rateLimit.headerPrefix) ?? DEFAULT_HEADER_PREFIX).toLowerCase(),
126
+ tiers
60
127
  };
61
128
  }
129
+ /**
130
+ * Select the policy tier for an observed reset. `tiers` is pre-sorted ascending
131
+ * with a catch-all last, so the first tier whose bound covers `resetSeconds`
132
+ * wins. An unknown reset is treated as the smallest band (wait a default backoff).
133
+ */
134
+ export function selectTier(tiers, resetSeconds) {
135
+ const reset = resetSeconds ?? 0;
136
+ for (const tier of tiers) {
137
+ if (tier.maxResetSeconds === null || reset <= tier.maxResetSeconds) {
138
+ return tier;
139
+ }
140
+ }
141
+ return tiers[tiers.length - 1];
142
+ }
62
143
  //# sourceMappingURL=config.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC,CAAC,6CAA6C;AACnF,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC;AACrC,MAAM,CAAC,MAAM,qBAAqB,GAAG,aAAa,CAAC;AAEnD,MAAM,QAAQ,GAAG,MAAM,CAAC;AACxB,MAAM,cAAc,GAAG,WAAW,CAAC;AAEnC,SAAS,QAAQ,CAAC,KAAc;IAC9B,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAChE,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,KAAgC,CAAC;AAC1C,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AACzF,CAAC;AAED,SAAS,SAAS,CAAC,KAAc,EAAE,QAAiB;IAClD,OAAO,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC;AACvD,CAAC;AAED,SAAS,aAAa,CAAC,KAAc,EAAE,QAAgB;IACrD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QACrE,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC3B,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,iGAAiG;AACjG,SAAS,gBAAgB,CAAC,KAAc,EAAE,QAAgB;IACxD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;QACtE,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC3B,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,qBAAqB,CACnC,eAAoD;IAEpD,IAAI,CAAC,eAAe,EAAE,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,IAAI,GAAG,QAAQ,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAC;IACjD,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,SAAS,GAAG,QAAQ,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC;IACjD,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,OAAO,GAAG,SAAS,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACnD,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO;QACL,OAAO,EAAE,IAAI;QACb,SAAS,EAAE,gBAAgB,CAAC,SAAS,CAAC,SAAS,EAAE,mBAAmB,CAAC;QACrE,UAAU,EAAE,aAAa,CAAC,SAAS,CAAC,UAAU,EAAE,mBAAmB,CAAC;QACpE,YAAY,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,YAAY,CAAC,IAAI,qBAAqB,CAAC,CAAC,WAAW,EAAE;KACxF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
package/dist/lib.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { createOpencodeRatelimitPlugin, OpencodeRatelimitPlugin, type OpenCodePluginFactoryOptions } from "./opencode.js";
2
- export { DEFAULT_HEADER_PREFIX, DEFAULT_MAX_RETRIES, DEFAULT_MAX_WAIT_MS, parseRateLimitOptions } from "./config.js";
2
+ export { DEFAULT_HEADER_PREFIX, DEFAULT_MAX_RETRIES, DEFAULT_MAX_WAIT_MS, DEFAULT_SCOPE, parseRateLimitOptions, selectTier } from "./config.js";
3
3
  export { parseRateLimit } from "./headers.js";
4
4
  export { createJsonConsoleLogger, DEFAULT_LOG_LEVEL, fromOpenCodeLogLevel, type LogFields, type Logger, type LogLevel } from "./logging.js";
5
- export { createProviderState, DEFAULT_BACKOFF_MS, type InstallConfigInput, installRateLimiter, makeRateLimitFetch, type ProviderConfigLike, type ProviderRateState, type RateLimitDeps } from "./plugin.js";
6
- export type { RateLimitOptions, RateLimitSnapshot } from "./types.js";
5
+ export { createProviderState, DEFAULT_BACKOFF_MS, type InstallConfigInput, installRateLimiter, makeRateLimitFetch, type ProviderConfigLike, type ProviderRateState, type RateLimitDeps, type RateStateStore } from "./plugin.js";
6
+ export type { RateLimitAction, RateLimitOptions, RateLimitScope, RateLimitSnapshot, RateLimitTier } from "./types.js";
package/dist/lib.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export { createOpencodeRatelimitPlugin, OpencodeRatelimitPlugin } from "./opencode.js";
2
- export { DEFAULT_HEADER_PREFIX, DEFAULT_MAX_RETRIES, DEFAULT_MAX_WAIT_MS, parseRateLimitOptions } from "./config.js";
2
+ export { DEFAULT_HEADER_PREFIX, DEFAULT_MAX_RETRIES, DEFAULT_MAX_WAIT_MS, DEFAULT_SCOPE, parseRateLimitOptions, selectTier } from "./config.js";
3
3
  export { parseRateLimit } from "./headers.js";
4
4
  export { createJsonConsoleLogger, DEFAULT_LOG_LEVEL, fromOpenCodeLogLevel } from "./logging.js";
5
5
  export { createProviderState, DEFAULT_BACKOFF_MS, installRateLimiter, makeRateLimitFetch } from "./plugin.js";
package/dist/lib.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"lib.js","sourceRoot":"","sources":["../src/lib.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,6BAA6B,EAC7B,uBAAuB,EAExB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,mBAAmB,EACnB,qBAAqB,EACtB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,EACL,uBAAuB,EACvB,iBAAiB,EACjB,oBAAoB,EAIrB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAElB,kBAAkB,EAClB,kBAAkB,EAInB,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"lib.js","sourceRoot":"","sources":["../src/lib.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,6BAA6B,EAC7B,uBAAuB,EAExB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,mBAAmB,EACnB,aAAa,EACb,qBAAqB,EACrB,UAAU,EACX,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,EACL,uBAAuB,EACvB,iBAAiB,EACjB,oBAAoB,EAIrB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAElB,kBAAkB,EAClB,kBAAkB,EAKnB,MAAM,aAAa,CAAC"}
package/dist/plugin.d.ts CHANGED
@@ -3,21 +3,20 @@ import type { RateLimitOptions } from "./types.js";
3
3
  /** Fallback backoff when a 429 carries no `x-ratelimit-reset` and no `Retry-After`. */
4
4
  export declare const DEFAULT_BACKOFF_MS = 1000;
5
5
  /**
6
- * Mutable, in-memory rate-limit state for a single provider. Created fresh per
7
- * {@link installRateLimiter} call and captured by the provider's fetch wrapper
8
- * closure, so it lives for the process lifetime. Never persisted — a reset
9
- * window is measured in seconds, so survival across process boots is pointless.
6
+ * Mutable, in-memory rate-limit state for a single bucket (a provider, or a
7
+ * `(provider, model)` pair when `scope: "model"`). Lives for the process
8
+ * lifetime in the provider's state store. Never persisted — a reset window is
9
+ * measured in seconds, so survival across process boots is pointless.
10
10
  */
11
11
  export interface ProviderRateState {
12
12
  /**
13
13
  * Epoch ms until which new requests should be held back. 0 = no cooldown.
14
- * This is the ONLY field that drives the gate it is set from a response's
15
- * `x-ratelimit-reset` (when `remaining` hits 0) or from a 429 backoff. We
16
- * deliberately do not retain the raw `remaining`/`limit`: out-of-order
17
- * responses would race on it and nothing reads it (the `ratelimit_quota` log
18
- * uses the per-response snapshot directly).
14
+ * The sole gate driver — set from a response's `x-ratelimit-reset` (when
15
+ * `remaining` hits 0, and the matched tier is `"wait"`) or from a 429 backoff.
19
16
  */
20
17
  cooldownUntilMs: number;
18
+ /** The `maxWaitMs` of the tier that armed the current cooldown (0 = unlimited). */
19
+ cooldownMaxWaitMs: number;
21
20
  /** A single in-flight wait shared by every caller during a cooldown window. */
22
21
  cooldownPromise?: Promise<void>;
23
22
  /**
@@ -43,6 +42,8 @@ export interface ProviderConfigLike {
43
42
  export interface InstallConfigInput {
44
43
  provider?: Record<string, ProviderConfigLike | undefined>;
45
44
  }
45
+ /** A per-bucket state store (key = provider id, or `${providerId}\0${model}`). */
46
+ export type RateStateStore = Map<string, ProviderRateState>;
46
47
  /**
47
48
  * Walk every provider in the assembled OpenCode config; for each one that has
48
49
  * opted in via `options.meta.rateLimit`, wrap its `options.fetch` with a
@@ -52,13 +53,16 @@ export interface InstallConfigInput {
52
53
  export declare function installRateLimiter(input: InstallConfigInput, deps: RateLimitDeps): void;
53
54
  /**
54
55
  * Build the fetch wrapper for one provider. The wrapper:
55
- * 1. Pre-request gate: if a cooldown window is armed, waits until it clears.
56
- * 2. Sends the request via the underlying fetch.
57
- * 3. Reads the rate-limit headers; if the window is exhausted, arms the gate
58
- * (`cooldownUntilMs`) for the next callers.
59
- * 4. On a 429, waits the reset window and retries (up to `maxRetries`).
56
+ * 1. Resolves the bucket (provider, or `(provider, model)` under `scope: "model"`).
57
+ * 2. Pre-request gate: if that bucket's cooldown is armed, waits until it clears.
58
+ * 3. Sends the request via the underlying fetch.
59
+ * 4. Reads the rate-limit headers; selects the policy tier by reset magnitude.
60
+ * On `remaining: 0` it arms the gate for the next callers (only when the
61
+ * matched tier is `"wait"`).
62
+ * 5. On a 429: a `"wait"` tier waits the reset window and retries (up to the
63
+ * tier's `maxRetries`); an `"error"` tier surfaces the 429 immediately.
60
64
  *
61
65
  * The Response is returned untouched (no `.clone()`) — we only read `status`
62
66
  * and `headers`, so the body stream is delivered to OpenCode intact.
63
67
  */
64
- export declare function makeRateLimitFetch(providerId: string, opts: RateLimitOptions, state: ProviderRateState, deps: RateLimitDeps, delegate?: typeof fetch): typeof fetch;
68
+ export declare function makeRateLimitFetch(providerId: string, opts: RateLimitOptions, store: RateStateStore, deps: RateLimitDeps, delegate?: typeof fetch): typeof fetch;
package/dist/plugin.js CHANGED
@@ -1,9 +1,9 @@
1
- import { parseRateLimitOptions } from "./config.js";
1
+ import { parseRateLimitOptions, selectTier } from "./config.js";
2
2
  import { parseRateLimit } from "./headers.js";
3
3
  /** Fallback backoff when a 429 carries no `x-ratelimit-reset` and no `Retry-After`. */
4
4
  export const DEFAULT_BACKOFF_MS = 1000;
5
5
  export function createProviderState() {
6
- return { cooldownUntilMs: 0 };
6
+ return { cooldownUntilMs: 0, cooldownMaxWaitMs: 0 };
7
7
  }
8
8
  /**
9
9
  * Walk every provider in the assembled OpenCode config; for each one that has
@@ -31,13 +31,13 @@ export function installRateLimiter(input, deps) {
31
31
  // Compose with any fetch a prior plugin already installed (capture it now,
32
32
  // not at call time, so we delegate to the original — not to ourselves).
33
33
  const delegate = typeof options.fetch === "function" ? options.fetch : undefined;
34
- const state = createProviderState();
35
- options.fetch = makeRateLimitFetch(providerId, opts, state, deps, delegate);
34
+ const store = new Map();
35
+ options.fetch = makeRateLimitFetch(providerId, opts, store, deps, delegate);
36
36
  enabledCount += 1;
37
37
  deps.logger.info("ratelimit_provider_enabled", {
38
38
  providerId,
39
- maxWaitMs: opts.maxWaitMs,
40
- maxRetries: opts.maxRetries,
39
+ scope: opts.scope,
40
+ tiers: opts.tiers.length,
41
41
  headerPrefix: opts.headerPrefix
42
42
  });
43
43
  }
@@ -45,68 +45,162 @@ export function installRateLimiter(input, deps) {
45
45
  }
46
46
  /**
47
47
  * Build the fetch wrapper for one provider. The wrapper:
48
- * 1. Pre-request gate: if a cooldown window is armed, waits until it clears.
49
- * 2. Sends the request via the underlying fetch.
50
- * 3. Reads the rate-limit headers; if the window is exhausted, arms the gate
51
- * (`cooldownUntilMs`) for the next callers.
52
- * 4. On a 429, waits the reset window and retries (up to `maxRetries`).
48
+ * 1. Resolves the bucket (provider, or `(provider, model)` under `scope: "model"`).
49
+ * 2. Pre-request gate: if that bucket's cooldown is armed, waits until it clears.
50
+ * 3. Sends the request via the underlying fetch.
51
+ * 4. Reads the rate-limit headers; selects the policy tier by reset magnitude.
52
+ * On `remaining: 0` it arms the gate for the next callers (only when the
53
+ * matched tier is `"wait"`).
54
+ * 5. On a 429: a `"wait"` tier waits the reset window and retries (up to the
55
+ * tier's `maxRetries`); an `"error"` tier surfaces the 429 immediately.
53
56
  *
54
57
  * The Response is returned untouched (no `.clone()`) — we only read `status`
55
58
  * and `headers`, so the body stream is delivered to OpenCode intact.
56
59
  */
57
- export function makeRateLimitFetch(providerId, opts, state, deps, delegate) {
60
+ export function makeRateLimitFetch(providerId, opts, store, deps, delegate) {
58
61
  const now = deps.now ?? Date.now;
59
62
  const sleep = deps.sleep ?? defaultSleep;
60
63
  const underlying = delegate ?? deps.fetchImpl ?? globalThis.fetch;
61
64
  const { logger } = deps;
65
+ const isRequest = (value) => typeof Request !== "undefined" && value instanceof Request;
62
66
  const wrapped = async (input, init) => {
63
- const signal = init?.signal ?? undefined;
64
- // 1. Pre-request gate.
67
+ // Honor an abort signal whether it rides on `init` or on a `Request` input.
68
+ const signal = init?.signal ?? (isRequest(input) ? input.signal : undefined) ?? undefined;
69
+ const model = opts.scope === "model" ? await modelFromRequest(input, init) : undefined;
70
+ const key = model ? `${providerId}\u0000${model}` : providerId;
71
+ let state = store.get(key);
72
+ if (!state) {
73
+ state = createProviderState();
74
+ store.set(key, state);
75
+ }
76
+ // 2. Pre-request gate — wait out a cooldown a previous "wait" tier armed.
65
77
  if (state.cooldownUntilMs > now()) {
66
- const waitMs = clampWait(state.cooldownUntilMs - now(), opts.maxWaitMs);
78
+ const waitMs = clampWait(state.cooldownUntilMs - now(), state.cooldownMaxWaitMs);
67
79
  if (waitMs > 0) {
68
- logger.info("ratelimit_throttle_wait", { providerId, waitMs, reason: "remaining_zero" });
69
- await waitGate(state, sleep, now, opts.maxWaitMs, signal, logger, providerId, "pre_request");
80
+ logger.info("ratelimit_throttle_wait", { providerId, model, waitMs });
81
+ await waitGate(state, sleep, now, state.cooldownMaxWaitMs, signal, logger, providerId, model, "pre_request");
70
82
  }
71
83
  }
72
- // 2-4. Attempt loop.
84
+ // 3-5. Attempt loop.
73
85
  let attempt = 0;
74
86
  for (;;) {
75
- const response = await underlying(input, init);
87
+ // A Request body is single-use; send a fresh clone each attempt so a
88
+ // wait-tier retry doesn't fail with "body already used". (The original is
89
+ // never sent directly, so each clone has an unconsumed body.)
90
+ const attemptInput = isRequest(input) ? input.clone() : input;
91
+ const response = await underlying(attemptInput, init);
76
92
  const snapshot = readSnapshot(response, opts, now(), logger, providerId);
77
93
  logger.debug("ratelimit_quota", {
78
94
  providerId,
95
+ model,
79
96
  remaining: snapshot.remaining,
80
97
  limit: snapshot.limit,
81
98
  resetSeconds: snapshot.resetSeconds
82
99
  });
83
100
  if (response.status !== 429) {
84
- // Arm the gate for the NEXT callers when the window is exhausted.
101
+ // Arm the gate for the NEXT callers when the window is exhausted — but
102
+ // only under a "wait" tier. An "error" tier wants the next request to
103
+ // hit a real 429 and be surfaced, so we leave the gate unarmed.
85
104
  if (snapshot.remaining !== undefined &&
86
105
  snapshot.remaining <= 0 &&
87
106
  snapshot.resetAtMs !== undefined) {
88
- state.cooldownUntilMs = snapshot.resetAtMs;
107
+ const tier = selectTier(opts.tiers, effectiveResetSeconds(snapshot));
108
+ if (tier.action === "wait") {
109
+ state.cooldownUntilMs = snapshot.resetAtMs;
110
+ state.cooldownMaxWaitMs = tier.maxWaitMs;
111
+ }
112
+ else {
113
+ // Error tier → don't arm, and drop any stale cooldown a prior
114
+ // "wait" window left so the next request hits a real 429 at once.
115
+ state.cooldownUntilMs = 0;
116
+ state.cooldownMaxWaitMs = 0;
117
+ }
89
118
  }
90
119
  return response;
91
120
  }
92
- if (attempt >= opts.maxRetries) {
93
- logger.error("ratelimit_giveup", { providerId, attempts: attempt });
121
+ const tier = selectTier(opts.tiers, effectiveResetSeconds(snapshot));
122
+ if (tier.action === "error") {
123
+ // Drop any cooldown a prior "wait" tier armed, so the fail-fast is
124
+ // actually fast — later requests must not sleep a stale window first.
125
+ state.cooldownUntilMs = 0;
126
+ state.cooldownMaxWaitMs = 0;
127
+ logger.warn("ratelimit_failfast", {
128
+ providerId,
129
+ model,
130
+ resetSeconds: snapshot.resetSeconds
131
+ });
132
+ return response;
133
+ }
134
+ if (attempt >= tier.maxRetries) {
135
+ logger.error("ratelimit_giveup", { providerId, model, attempts: attempt });
94
136
  return response;
95
137
  }
96
- const waitMs = clampWait(computeBackoff(snapshot, now()), opts.maxWaitMs);
138
+ const waitMs = clampWait(computeBackoff(snapshot, now()), tier.maxWaitMs);
97
139
  state.cooldownUntilMs = now() + waitMs;
140
+ state.cooldownMaxWaitMs = tier.maxWaitMs;
98
141
  logger.warn("ratelimit_429_backoff", {
99
142
  providerId,
143
+ model,
100
144
  attempt: attempt + 1,
101
145
  waitMs,
102
146
  resetSeconds: snapshot.resetSeconds
103
147
  });
104
- await waitGate(state, sleep, now, opts.maxWaitMs, signal, logger, providerId, "backoff");
148
+ await waitGate(state, sleep, now, tier.maxWaitMs, signal, logger, providerId, model, "backoff");
105
149
  attempt += 1;
106
150
  }
107
151
  };
108
152
  return wrapped;
109
153
  }
154
+ /**
155
+ * Best-effort extraction of the `model` from an OpenAI-compatible request.
156
+ * The AI SDK calls `fetch(url, { body: "<json>" })`, so the common path reads a
157
+ * JSON string body synchronously (non-destructive). It also supports the
158
+ * `fetch(new Request(...))` shape by reading a clone of the Request body, so a
159
+ * Request-style caller doesn't silently collapse to the provider-wide bucket.
160
+ * Anything unparseable → `undefined` → caller falls back to the provider bucket.
161
+ */
162
+ async function modelFromRequest(input, init) {
163
+ const fromInit = modelFromBody(init?.body);
164
+ if (fromInit !== undefined) {
165
+ return fromInit;
166
+ }
167
+ if (typeof Request !== "undefined" && input instanceof Request) {
168
+ try {
169
+ return modelFromBody(await input.clone().text());
170
+ }
171
+ catch {
172
+ return undefined;
173
+ }
174
+ }
175
+ return undefined;
176
+ }
177
+ function modelFromBody(body) {
178
+ if (typeof body !== "string") {
179
+ return undefined;
180
+ }
181
+ try {
182
+ const parsed = JSON.parse(body);
183
+ return typeof parsed.model === "string" && parsed.model.length > 0 ? parsed.model : undefined;
184
+ }
185
+ catch {
186
+ return undefined;
187
+ }
188
+ }
189
+ /**
190
+ * Reset magnitude (seconds) used for tier selection: prefer `x-ratelimit-reset`,
191
+ * else derive from a `Retry-After` fallback — so a `Retry-After`-only 429 with a
192
+ * multi-day delay still lands in a long-reset (`error`) tier instead of the
193
+ * smallest band.
194
+ */
195
+ function effectiveResetSeconds(snapshot) {
196
+ if (snapshot.resetSeconds !== undefined) {
197
+ return snapshot.resetSeconds;
198
+ }
199
+ if (snapshot.retryAfterMs !== undefined) {
200
+ return Math.ceil(snapshot.retryAfterMs / 1000);
201
+ }
202
+ return undefined;
203
+ }
110
204
  function readSnapshot(response, opts, nowMs, logger, providerId) {
111
205
  try {
112
206
  return parseRateLimit(response.headers, opts.headerPrefix, nowMs);
@@ -144,18 +238,16 @@ function clampWait(ms, maxWaitMs) {
144
238
  * request's cancellation never aborts the others.
145
239
  * - **Honors the longest window.** After the shared timer resolves we re-check
146
240
  * `cooldownUntilMs`. If another caller extended the window (e.g. a 429 landed
147
- * with a further-out reset) while we were waiting or the shared timer was
148
- * created for a shorter wait than we now require — we wait again for the
241
+ * with a further-out reset) while we were waiting we wait again for the
149
242
  * remainder instead of returning early and hammering the gateway.
150
243
  *
151
244
  * `maxWaitMs` (when > 0) bounds the TOTAL wait of a single call: once a caller
152
245
  * has waited that long it proceeds even if the window hasn't fully elapsed. A
153
246
  * caller that needs LESS than the in-flight shared timer (the window shrank, or
154
247
  * the timer was created by a caller with a later deadline) falls back to its own
155
- * private sleep so it is never held past its own required time — the shared
156
- * timer is reserved for the common case where everyone is waiting the same span.
248
+ * private sleep so it is never held past its own required time.
157
249
  */
158
- async function waitGate(state, sleep, now, maxWaitMs, signal, logger, providerId, phase) {
250
+ async function waitGate(state, sleep, now, maxWaitMs, signal, logger, providerId, model, phase) {
159
251
  const deadlineMs = maxWaitMs > 0 ? now() + maxWaitMs : Number.POSITIVE_INFINITY;
160
252
  for (;;) {
161
253
  const target = Math.min(state.cooldownUntilMs, deadlineMs);
@@ -190,7 +282,7 @@ async function waitGate(state, sleep, now, maxWaitMs, signal, logger, providerId
190
282
  }
191
283
  catch (error) {
192
284
  if (isAbortError(error)) {
193
- logger.warn("ratelimit_wait_aborted", { providerId, phase });
285
+ logger.warn("ratelimit_wait_aborted", { providerId, model, phase });
194
286
  }
195
287
  throw error;
196
288
  }
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAI9C,uFAAuF;AACvF,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAC;AA4BvC,MAAM,UAAU,mBAAmB;IACjC,OAAO,EAAE,eAAe,EAAE,CAAC,EAAE,CAAC;AAChC,CAAC;AAoBD;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAyB,EAAE,IAAmB;IAC/E,MAAM,SAAS,GAAG,KAAK,CAAC,QAAQ,CAAC;IACjC,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,8BAA8B,EAAE,EAAE,aAAa,EAAE,CAAC,EAAE,CAAC,CAAC;QACvE,OAAO;IACT,CAAC;IAED,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,KAAK,MAAM,CAAC,UAAU,EAAE,cAAc,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;QACrE,IAAI,CAAC,cAAc,EAAE,CAAC;YACpB,SAAS;QACX,CAAC;QACD,MAAM,IAAI,GAAG,qBAAqB,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;QAC3D,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,4BAA4B,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC,CAAC;YACxF,SAAS;QACX,CAAC;QAED,MAAM,OAAO,GAAG,CAAC,cAAc,CAAC,OAAO,KAAK,EAAE,CAAC,CAAC;QAChD,2EAA2E;QAC3E,wEAAwE;QACxE,MAAM,QAAQ,GACZ,OAAO,OAAO,CAAC,KAAK,KAAK,UAAU,CAAC,CAAC,CAAE,OAAO,CAAC,KAAsB,CAAC,CAAC,CAAC,SAAS,CAAC;QACpF,MAAM,KAAK,GAAG,mBAAmB,EAAE,CAAC;QACpC,OAAO,CAAC,KAAK,GAAG,kBAAkB,CAAC,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;QAC5E,YAAY,IAAI,CAAC,CAAC;QAClB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,4BAA4B,EAAE;YAC7C,UAAU;YACV,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,YAAY,EAAE,IAAI,CAAC,YAAY;SAChC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,8BAA8B,EAAE,EAAE,aAAa,EAAE,YAAY,EAAE,CAAC,CAAC;AACpF,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,kBAAkB,CAChC,UAAkB,EAClB,IAAsB,EACtB,KAAwB,EACxB,IAAmB,EACnB,QAAuB;IAEvB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;IACjC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,YAAY,CAAC;IACzC,MAAM,UAAU,GAAG,QAAQ,IAAI,IAAI,CAAC,SAAS,IAAI,UAAU,CAAC,KAAK,CAAC;IAClE,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAExB,MAAM,OAAO,GAAiB,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;QAClD,MAAM,MAAM,GAAG,IAAI,EAAE,MAAM,IAAI,SAAS,CAAC;QAEzC,uBAAuB;QACvB,IAAI,KAAK,CAAC,eAAe,GAAG,GAAG,EAAE,EAAE,CAAC;YAClC,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,CAAC,eAAe,GAAG,GAAG,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;YACxE,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;gBACf,MAAM,CAAC,IAAI,CAAC,yBAAyB,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,CAAC,CAAC;gBACzF,MAAM,QAAQ,CACZ,KAAK,EACL,KAAK,EACL,GAAG,EACH,IAAI,CAAC,SAAS,EACd,MAAM,EACN,MAAM,EACN,UAAU,EACV,aAAa,CACd,CAAC;YACJ,CAAC;QACH,CAAC;QAED,qBAAqB;QACrB,IAAI,OAAO,GAAG,CAAC,CAAC;QAChB,SAAS,CAAC;YACR,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;YAC/C,MAAM,QAAQ,GAAG,YAAY,CAAC,QAAQ,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC;YACzE,MAAM,CAAC,KAAK,CAAC,iBAAiB,EAAE;gBAC9B,UAAU;gBACV,SAAS,EAAE,QAAQ,CAAC,SAAS;gBAC7B,KAAK,EAAE,QAAQ,CAAC,KAAK;gBACrB,YAAY,EAAE,QAAQ,CAAC,YAAY;aACpC,CAAC,CAAC;YAEH,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;gBAC5B,kEAAkE;gBAClE,IACE,QAAQ,CAAC,SAAS,KAAK,SAAS;oBAChC,QAAQ,CAAC,SAAS,IAAI,CAAC;oBACvB,QAAQ,CAAC,SAAS,KAAK,SAAS,EAChC,CAAC;oBACD,KAAK,CAAC,eAAe,GAAG,QAAQ,CAAC,SAAS,CAAC;gBAC7C,CAAC;gBACD,OAAO,QAAQ,CAAC;YAClB,CAAC;YAED,IAAI,OAAO,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;gBAC/B,MAAM,CAAC,KAAK,CAAC,kBAAkB,EAAE,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;gBACpE,OAAO,QAAQ,CAAC;YAClB,CAAC;YAED,MAAM,MAAM,GAAG,SAAS,CAAC,cAAc,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;YAC1E,KAAK,CAAC,eAAe,GAAG,GAAG,EAAE,GAAG,MAAM,CAAC;YACvC,MAAM,CAAC,IAAI,CAAC,uBAAuB,EAAE;gBACnC,UAAU;gBACV,OAAO,EAAE,OAAO,GAAG,CAAC;gBACpB,MAAM;gBACN,YAAY,EAAE,QAAQ,CAAC,YAAY;aACpC,CAAC,CAAC;YACH,MAAM,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC;YACzF,OAAO,IAAI,CAAC,CAAC;QACf,CAAC;IACH,CAAC,CAAC;IAEF,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAS,YAAY,CACnB,QAAkB,EAClB,IAAsB,EACtB,KAAa,EACb,MAAc,EACd,UAAkB;IAElB,IAAI,CAAC;QACH,OAAO,cAAc,CAAC,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;IACpE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,KAAK,CAAC,+BAA+B,EAAE;YAC5C,UAAU;YACV,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;SAC9D,CAAC,CAAC;QACH,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CAAC,QAA2B,EAAE,KAAa;IAChE,IAAI,QAAQ,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;QACrC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,SAAS,GAAG,KAAK,CAAC,CAAC;IACjD,CAAC;IACD,IAAI,QAAQ,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;QACxC,OAAO,QAAQ,CAAC,YAAY,CAAC;IAC/B,CAAC;IACD,OAAO,kBAAkB,CAAC;AAC5B,CAAC;AAED,qEAAqE;AACrE,SAAS,SAAS,CAAC,EAAU,EAAE,SAAiB;IAC9C,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACpC,OAAO,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC;AACxE,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,KAAK,UAAU,QAAQ,CACrB,KAAwB,EACxB,KAA0D,EAC1D,GAAiB,EACjB,SAAiB,EACjB,MAA+B,EAC/B,MAAc,EACd,UAAkB,EAClB,KAAgC;IAEhC,MAAM,UAAU,GAAG,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,iBAAiB,CAAC;IAChF,SAAS,CAAC;QACR,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;QAC3D,MAAM,WAAW,GAAG,MAAM,GAAG,GAAG,EAAE,CAAC;QACnC,IAAI,WAAW,IAAI,CAAC,EAAE,CAAC;YACrB,OAAO;QACT,CAAC;QAED,4EAA4E;QAC5E,IAAI,OAAO,GAAG,KAAK,CAAC,eAAe,CAAC;QACpC,IACE,OAAO;YACP,KAAK,CAAC,sBAAsB,KAAK,SAAS;YAC1C,KAAK,CAAC,sBAAsB,GAAG,GAAG,EAAE,GAAG,WAAW,EAClD,CAAC;YACD,OAAO,GAAG,SAAS,CAAC;QACtB,CAAC;QACD,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,OAAO,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE;gBAC9C,IAAI,KAAK,CAAC,eAAe,KAAK,OAAO,EAAE,CAAC;oBACtC,KAAK,CAAC,eAAe,GAAG,SAAS,CAAC;oBAClC,KAAK,CAAC,sBAAsB,GAAG,SAAS,CAAC;gBAC3C,CAAC;YACH,CAAC,CAAC,CAAC;YACH,wEAAwE;YACxE,iEAAiE;YACjE,IAAI,CAAC,KAAK,CAAC,eAAe,EAAE,CAAC;gBAC3B,KAAK,CAAC,eAAe,GAAG,OAAO,CAAC;gBAChC,KAAK,CAAC,sBAAsB,GAAG,GAAG,EAAE,GAAG,WAAW,CAAC;YACrD,CAAC;YACD,OAAO,GAAG,OAAO,CAAC;QACpB,CAAC;QAED,IAAI,CAAC;YACH,MAAM,aAAa,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QACvC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;gBACxB,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC;YAC/D,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;QACD,kEAAkE;IACpE,CAAC;AACH,CAAC;AAED,SAAS,aAAa,CAAC,OAAsB,EAAE,MAA+B;IAC5E,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,OAAO,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9C,CAAC;IACD,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC3C,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC;QACnD,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1D,OAAO,CAAC,IAAI,CACV,GAAG,EAAE;YACH,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC7C,OAAO,EAAE,CAAC;QACZ,CAAC,EACD,CAAC,KAAK,EAAE,EAAE;YACR,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC7C,MAAM,CAAC,KAAK,CAAC,CAAC;QAChB,CAAC,CACF,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,YAAY,CAAC,EAAU,EAAE,MAAoB;IACpD,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC3C,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;YACpB,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC;YAC7B,OAAO;QACT,CAAC;QACD,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC9C,OAAO,EAAE,CAAC;QACZ,CAAC,EAAE,EAAE,CAAC,CAAC;QACP,MAAM,OAAO,GAAG,GAAG,EAAE;YACnB,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,MAAM,CAAC,YAAY,CAAC,MAAqB,CAAC,CAAC,CAAC;QAC9C,CAAC,CAAC;QACF,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,YAAY,CAAC,MAAmB;IACvC,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;IAC7B,IAAI,MAAM,YAAY,KAAK,EAAE,CAAC;QAC5B,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,OAAO,IAAI,YAAY,CAAC,2BAA2B,EAAE,YAAY,CAAC,CAAC;AACrE,CAAC;AAED,SAAS,YAAY,CAAC,KAAc;IAClC,OAAO,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY,CAAC;AAC/D,CAAC"}
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -1,23 +1,52 @@
1
1
  export type LogLevel = "debug" | "info" | "warn" | "error";
2
+ /** How rate-limit state is partitioned. */
3
+ export type RateLimitScope = "provider" | "model";
4
+ /** What to do when a tier matches the observed reset window. */
5
+ export type RateLimitAction = "wait" | "error";
6
+ /**
7
+ * One band of the tiered policy, matched by the magnitude of the observed
8
+ * `x-ratelimit-reset`. Tiers let a config distinguish a short burst reset
9
+ * (worth waiting through) from a multi-day budget reset (should error fast) —
10
+ * both arrive as the same header, so reset duration is the only discriminator.
11
+ */
12
+ export interface RateLimitTier {
13
+ /**
14
+ * Inclusive upper bound, in seconds, of the resets this tier matches.
15
+ * `null` is the catch-all (matches any reset; sorts last).
16
+ */
17
+ maxResetSeconds: number | null;
18
+ /** `"wait"` throttles/backs-off; `"error"` surfaces the 429 immediately. */
19
+ action: RateLimitAction;
20
+ /** For `"wait"`: cap on a single wait, ms. `0` = unlimited. Ignored for `"error"`. */
21
+ maxWaitMs: number;
22
+ /** For `"wait"`: 429 retries before the response is surfaced. Ignored for `"error"`. */
23
+ maxRetries: number;
24
+ }
2
25
  /**
3
26
  * Resolved, defaults-applied rate-limit configuration for a single provider,
4
- * derived from `provider.options.meta.rateLimit`. See {@link parseRateLimitOptions}.
27
+ * derived from `provider.options.meta.rateLimit`. See `parseRateLimitOptions`.
5
28
  */
6
29
  export interface RateLimitOptions {
7
30
  /** Whether rate-limit handling is active for this provider. */
8
31
  enabled: boolean;
9
32
  /**
10
- * Upper bound on any single wait (pre-request throttle or 429 backoff), in
11
- * milliseconds. `0` means unlimitedwait the full reset window.
33
+ * `"model"` keys cooldown state per `(provider, model)` so exhausting one
34
+ * model's bucket doesn't gate the others matches gateways that rate-limit
35
+ * per model. `"provider"` shares one cooldown across the whole provider.
12
36
  */
13
- maxWaitMs: number;
14
- /** How many times a 429 is retried before the response is handed back as-is. */
15
- maxRetries: number;
37
+ scope: RateLimitScope;
16
38
  /**
17
39
  * Lowercased header-name prefix for the IETF draft-03 triple, e.g.
18
40
  * `x-ratelimit` → `x-ratelimit-limit` / `-remaining` / `-reset`.
19
41
  */
20
42
  headerPrefix: string;
43
+ /**
44
+ * Ordered policy bands, sorted ascending by `maxResetSeconds` (catch-all
45
+ * last). Always non-empty and always ends with a catch-all tier. A flat
46
+ * `maxWaitMs`/`maxRetries` config is normalized into a single catch-all
47
+ * `"wait"` tier.
48
+ */
49
+ tiers: RateLimitTier[];
21
50
  }
22
51
  /**
23
52
  * What a single response told us about the current rate-limit window. Every
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vymalo/opencode-ratelimit",
3
- "version": "0.5.0",
3
+ "version": "0.6.1",
4
4
  "description": "OpenCode plugin that makes OpenAI-compatible providers rate-limit aware: it reads Envoy Gateway / IETF draft-03 rate-limit response headers (x-ratelimit-limit/remaining/reset), proactively throttles requests when the quota is exhausted, and backs off and retries on HTTP 429.",
5
5
  "license": "MIT",
6
6
  "author": "vymalo contributors",