@vymalo/opencode-ratelimit 0.5.0 → 0.6.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/README.md +34 -1
- package/dist/config.d.ts +14 -4
- package/dist/config.js +87 -6
- package/dist/config.js.map +1 -1
- package/dist/lib.d.ts +3 -3
- package/dist/lib.js +1 -1
- package/dist/lib.js.map +1 -1
- package/dist/plugin.d.ts +19 -15
- package/dist/plugin.js +123 -31
- package/dist/plugin.js.map +1 -1
- package/dist/types.d.ts +35 -6
- package/package.json +1 -1
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`)
|
|
9
|
-
* both cases the provider's fetch is left untouched.
|
|
9
|
+
* (no `meta.rateLimit`) or has explicitly disabled it (`enabled: false`).
|
|
10
10
|
*
|
|
11
|
-
*
|
|
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`)
|
|
35
|
-
* both cases the provider's fetch is left untouched.
|
|
82
|
+
* (no `meta.rateLimit`) or has explicitly disabled it (`enabled: false`).
|
|
36
83
|
*
|
|
37
|
-
*
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
package/dist/config.js.map
CHANGED
|
@@ -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;
|
|
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,
|
|
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
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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
|
-
*
|
|
15
|
-
* `
|
|
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.
|
|
56
|
-
* 2.
|
|
57
|
-
* 3.
|
|
58
|
-
*
|
|
59
|
-
*
|
|
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,
|
|
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
|
|
35
|
-
options.fetch = makeRateLimitFetch(providerId, opts,
|
|
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
|
-
|
|
40
|
-
|
|
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.
|
|
49
|
-
* 2.
|
|
50
|
-
* 3.
|
|
51
|
-
*
|
|
52
|
-
*
|
|
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,
|
|
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
|
-
|
|
64
|
-
|
|
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(),
|
|
78
|
+
const waitMs = clampWait(state.cooldownUntilMs - now(), state.cooldownMaxWaitMs);
|
|
67
79
|
if (waitMs > 0) {
|
|
68
|
-
logger.info("ratelimit_throttle_wait", { providerId,
|
|
69
|
-
await waitGate(state, sleep, now,
|
|
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
|
-
//
|
|
84
|
+
// 3-5. Attempt loop.
|
|
73
85
|
let attempt = 0;
|
|
74
86
|
for (;;) {
|
|
75
|
-
|
|
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
|
-
|
|
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
|
-
|
|
93
|
-
|
|
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()),
|
|
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,
|
|
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
|
|
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
|
|
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
|
}
|
package/dist/plugin.js.map
CHANGED
|
@@ -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
|
|
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
|
-
*
|
|
11
|
-
*
|
|
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
|
-
|
|
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.
|
|
3
|
+
"version": "0.6.0",
|
|
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",
|