oira666_pi-limits-wait 0.5.3 → 0.5.5

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
@@ -1,149 +1,171 @@
1
- # Pi Limits Wait
2
-
3
- [![npm](https://img.shields.io/npm/v/oira666_pi-limits-wait?style=flat-square&logo=npm&logoColor=white&label=npm&color=7c3aed)](https://www.npmjs.com/package/oira666_pi-limits-wait) [![node](https://img.shields.io/badge/node-%3E%3D22.19-7c3aed?style=flat-square&logo=nodedotjs&logoColor=white)](https://nodejs.org)
4
-
5
- Pi extension that keeps requests alive when a provider rate-limits you or returns `server_is_overloaded`.
6
-
7
- ## Install
8
-
9
- ```bash
10
- pi install npm:oira666_pi-limits-wait
11
- ```
12
-
13
- Restart Pi or run `/reload` after installing.
14
-
15
- ## What it does
16
-
17
- - Targets Pi 0.80.10+ (developed and tested against Pi 0.83.0) and works with model providers that use `ModelRuntime.streamSimple`.
18
- - Intercepts the current runtime without registering synthetic providers; every retry and fallback re-enters Pi's normal provider/auth/base URL/configured-header composition, including OAuth refresh checks.
19
- - Reload-safe ownership ensures `/reload` transfers interception cleanly and aborts waits still owned by the previous extension instance. The interception has one process-global active extension owner, matching the interactive Pi CLI's single active session runtime lifecycle; SDK processes running multiple concurrent Pi sessions are not supported.
20
- - On rate-limit errors (`429`, `rate_limit`, `too many requests`, quota/reset messages), waits and retries in a loop.
21
- - Uses provider retry timing when exposed through response callbacks or provider errors (`retry-after`, `retry-after-ms`, `retry in ...`, reset messages).
22
- - If no retry timing is available for a rate limit, waits 30 minutes before retrying.
23
- - On `server_is_overloaded`, waits 5 minutes, then retries. If the provider is still overloaded after Pi's normal retries, it waits another 5 minutes and repeats.
24
- - On transient network/transport failures — including undici idle-timeout aborts (`UND_ERR_HEADERS_TIMEOUT` / `UND_ERR_BODY_TIMEOUT`), `fetch failed`, `terminated`, `ECONNRESET`, `ETIMEDOUT`, etc. — treats the error as retryable with a short backoff (default 15s, or the provider's `retry-after`) instead of giving up. This prevents a stalled streaming request from turning into a silent hang.
25
- - For any other (unclassified) provider error — including an API `Internal server error` and HTTP `400`/`403` failures — waits and retries the same model. There is deliberately no permanent-HTTP-status allowlist. This retry-all behavior is enabled by default and uses a configurable retry limit and interval; it does not alter model-limit wait intervals.
26
- - Reports retry/fallback waits in chat notifications without adding persistent TUI status lines.
27
- - Press Enter during a retry wait to skip the wait and retry immediately.
28
- - Optionally falls back to configured models when the current/default model is rate-limited. Every fallback to a different model—even on the same provider—deliberately discards request-owned credentials, headers, environment overrides, and provider-specific options so the target runtime resolves its own configuration.
29
- - Preserves Pi's `before_provider_request` / `after_provider_response` callback flow and sanitises only the exact Anthropic OAuth system-block shape generated by current Pi; Pi remains responsible for OAuth identity insertion.
30
-
31
- ### Unsupported interception limitation
32
-
33
- Pi 0.83 has no public stream middleware that can intercept a failed provider stream, wait, and re-enter `ModelRuntime` for another attempt. For that reason, limits-wait still installs a reload-safe trampoline on `ModelRuntime.prototype.streamSimple`. This is an unsupported internal interception point and could require changes when Pi changes `ModelRuntime`; it also assumes the interactive CLI's one active session runtime per process. SDK processes with multiple concurrent Pi sessions are not supported.
34
-
35
- `after_provider_response` is preserved for observers, but it is not an equivalent retry hook: it cannot replace stream-error interception, own retry/fallback control flow, or recreate a complete target-model request. In particular, Pi 0.83 still does not expose a public factory for rebuilding the SDK's target-model-aware `transformHeaders` callback. Reusing the source request's closure on a fallback would apply source-model attribution and could leak extension-added headers, so limits-wait intentionally omits it for every different-model fallback. The target `ModelRuntime` still resolves target auth, base URL, configured provider/model headers, environment, and provider implementation canonically, but `before_provider_headers` cannot be re-fired for that fallback through the current public API.
36
-
37
- ## Wait event contract
38
-
39
- Every positive extension-managed wait emits public telemetry on Pi's shared event bus. Import the channel constant and payload union from the package entry point:
40
-
41
- ```ts
42
- import {
43
- LIMITS_WAIT_EVENT_CHANNEL,
44
- type LimitsWaitEventPayload,
45
- } from "oira666_pi-limits-wait";
46
-
47
- pi.events.on(LIMITS_WAIT_EVENT_CHANNEL, (data) => {
48
- const event = data as LimitsWaitEventPayload;
49
- // event.version === 1; event.phase is "start" or "end"
50
- });
51
- ```
52
-
53
- The versioned, namespaced channel is `oira666.pi-limits-wait.wait.v1`. No event is emitted for a zero/negative-duration wait.
54
-
55
- Both phases contain:
56
-
57
- - `version: 1`, `phase`, and a correlating `waitId`;
58
- - optional `periodId` for the associated extension retry period;
59
- - `reason` (`rate-limit`, `overloaded`, `authentication`, `model-frozen`, `network`, or `retry`);
60
- - `plannedDurationMs`, `plannedDeadline`, and `startedAt` (`plannedDeadline`/`startedAt` are Unix milliseconds);
61
- - optional `model: { provider, id }` and a normalized `error` string.
62
-
63
- The `end` phase additionally contains `endedAt`, `actualElapsedMs`, and `outcome: "waited" | "skipped" | "aborted"`. Start and end are emitted for normal completion, Enter-to-skip, Escape, signal abort, and pre-aborted positive waits. Telemetry is observational: event-bus failures are isolated and cannot cancel or alter a wait.
64
-
65
- ## Session retry summaries
66
-
67
- A retry period begins only when a failure will lead to another extension-created attempt or fallback. For each contiguous extension-managed retry period/stream, limits-wait queues one standard Pi custom entry and appends it after the associated assistant turn (normally on `turn_end`, with `agent_end` and `session_shutdown` as duplicate-safe fallbacks):
68
-
69
- ```json
70
- {
71
- "type": "custom",
72
- "customType": "limits-wait",
73
- "data": {
74
- "totalWaitingTime": 12.345,
75
- "reasons": ["HTTP 429 retry-after-ms 10000", "fetch failed"],
76
- "retries_total": 2
77
- }
78
- }
79
- ```
80
-
81
- `totalWaitingTime` is actual elapsed extension wait time in seconds, not the planned duration. `reasons` contains whitespace-normalized error messages, deduplicated while preserving first-seen order. `retries_total` counts attempts/fallbacks created by this extension: the initial provider request and retries performed internally by a provider SDK do not count; an immediate fallback or zero-wait retry does count. A wait aborted before another attempt does not create a retry count by itself.
82
-
83
- Pi custom entries created with `pi.appendEntry()` do not participate in LLM context. The summary is transcript metadata only.
84
-
85
- ## Optional fallback models
86
-
87
- By default, if you do not configure fallback models, the extension behaves exactly as before: it waits for the current model's limit to reset and retries.
88
-
89
- To enable automatic model fallback, add `fallback-models` to a `limits-wait.json` file.
90
-
91
- The extension reads these files in order; later files override earlier ones:
92
-
93
- | Location | Scope | Trust behavior |
94
- |----------|-------|----------------|
95
- | `~/.config/.pi/limits-wait.json` | Global defaults | Always loaded |
96
- | `<Pi agent dir>/limits-wait.json` (for example `~/.pi/agent/limits-wait.json`, or `PI_CODING_AGENT_DIR/limits-wait.json`) | Pi agent directory | Always loaded |
97
- | `.limits-wait.json` | Project root override | Loaded only when `ctx.isProjectTrusted()` is `true` |
98
- | `.pi/limits-wait.json` | Pi project override | Loaded only when `ctx.isProjectTrusted()` is `true` |
99
-
100
- For an untrusted project, both project-local paths are ignored completely (including malformed-file warnings); global and agent settings still apply. The trust decision is Pi's effective session trust, including temporary/CLI trust decisions. The same rule is applied every time settings are reread before an LLM call.
101
-
102
- Example:
103
-
104
- ```json
105
- {
106
- "fallback-models": [
107
- {
108
- "provider": "anthropic",
109
- "modelname": "claude-sonnet-4-5",
110
- "reasoning effort": "medium"
111
- },
112
- {
113
- "provider": "openai",
114
- "modelname": "gpt-5.1-codex"
115
- }
116
- ]
117
- }
118
- ```
119
-
120
- Each `fallback-models` entry supports:
121
-
122
- - `provider` — required. The Pi provider name, for example `anthropic`, `openai`, `google`, etc.
123
- - `modelname` — required. The model id/name as Pi knows it.
124
- - `reasoning effort` — optional. One of `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`. If omitted, Pi's current/default reasoning level is used.
125
-
126
- Fallback behavior:
127
-
128
- 1. Pi starts with the normal default or user-selected model.
129
- 2. If that model is rate-limited, the extension tries models in this order:
130
- - the original default/user-selected model;
131
- - then every model from `fallback-models`, top to bottom.
132
- 3. The first model that responds without a rate-limit becomes the active Pi model for the rest of the session/task.
133
- 4. If that model later becomes rate-limited too, the extension starts again from the same ordered list.
134
- 5. Rate-limit reset times are remembered only in memory, so known-limited models are skipped until their countdown expires. This memory is cleared when Pi restarts.
135
-
136
- The extension rereads these files before every LLM call, so changes made during a long-running task take effect on the next LLM call without restarting Pi or using `/reload`. Tool calls do not trigger a settings read. When the settings are loaded, the extension shows the full usable fallback model list. When models become rate-limited, it reports the wait in chat notifications.
137
-
138
- ## Environment variables
139
-
140
- | Variable | Default | Description |
141
- |----------|---------|-------------|
142
- | `PI_LIMITS_WAIT_DEFAULT_WAITING` | `true` | Whether unclassified/unknown provider errors enter the wait-and-retry cycle. Set to `false` (also accepts `0`, `no`, `off`) to surface the error immediately. Rate-limit, overload, authentication, and network retry behavior is unchanged. |
143
- | `PI_LIMITS_WAIT_MAX_RETRY` | `999999` | Maximum extension-created retries for an unclassified/unknown provider error. The initial failed request and provider-internal retries do not count. HTTP 400/403 remain eligible. Must be a non-negative integer. |
144
- | `PI_LIMITS_WAIT_RETRY_INTERVAL` | `5` | Seconds to wait between unclassified/unknown provider-error retries. Must be a non-negative integer (values above 2147483 are capped to Node's maximum reliable timer delay). This does not affect model-limit wait intervals. |
145
- | `PI_LIMITS_WAIT_FREEZING_ENABLED` | `true` | When a model keeps failing with a non-retryable error, it is normally "frozen" for 10 minutes and skipped in favour of other configured models. Set this to `false` (also accepts `0`, `no`, `off`) to disable freezing entirely: the extension will instead try each configured candidate once (after the bounded retries) and then surface the error, never blocking on a long "model-frozen" wait. Useful for non-interactive / subagent runs where no one can press Enter to skip. |
146
-
147
- ## License
148
-
149
- MIT
1
+ # Pi Limits Wait
2
+
3
+ [![npm](https://img.shields.io/npm/v/oira666_pi-limits-wait?style=flat-square&logo=npm&logoColor=white&label=npm&color=7c3aed)](https://www.npmjs.com/package/oira666_pi-limits-wait) [![node](https://img.shields.io/badge/node-%3E%3D22.19-7c3aed?style=flat-square&logo=nodedotjs&logoColor=white)](https://nodejs.org)
4
+
5
+ Pi extension that keeps requests alive when a provider rate-limits you or returns `server_is_overloaded`.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pi install npm:oira666_pi-limits-wait
11
+ ```
12
+
13
+ Restart Pi or run `/reload` after installing.
14
+
15
+ ## What it does
16
+
17
+ - Targets Pi 0.80.10+ (developed and tested against Pi 0.83.0) and works with model providers that use `ModelRuntime.streamSimple`.
18
+ - Intercepts the current runtime without registering synthetic providers; every retry and fallback re-enters Pi's normal provider/auth/base URL/configured-header composition, including OAuth refresh checks.
19
+ - Reload-safe ownership ensures `/reload` transfers interception cleanly and aborts waits still owned by the previous extension instance. The interception has one process-global active extension owner, matching the interactive Pi CLI's single active session runtime lifecycle; SDK processes running multiple concurrent Pi sessions are not supported.
20
+ - On rate-limit errors (`429`, `rate_limit`, `too many requests`, quota/reset messages), waits and retries in a loop.
21
+ - Uses provider retry timing when exposed through response callbacks or provider errors (`retry-after`, `retry-after-ms`, `retry in ...`, reset messages).
22
+ - If no retry timing is available for a rate limit, waits 30 minutes before retrying.
23
+ - On `server_is_overloaded`, waits 5 minutes, then retries. If the provider is still overloaded after Pi's normal retries, it waits another 5 minutes and repeats.
24
+ - On transient network/transport failures — including undici idle-timeout aborts (`UND_ERR_HEADERS_TIMEOUT` / `UND_ERR_BODY_TIMEOUT`), `fetch failed`, `terminated`, `ECONNRESET`, `ETIMEDOUT`, etc. — treats the error as retryable with a short backoff (default 15s, or the provider's `retry-after`) instead of giving up. This prevents a stalled streaming request from turning into a silent hang.
25
+ - Leaves context-overflow recovery entirely to Pi. The extension uses Pi's own `isContextOverflow()` detector and immediately surfaces matching failures without waiting, retrying, or selecting another fallback. If the overflow came from a fallback already attempted for the request, the extension makes that attempted model current when Pi is still on the request's original model and accepts the switch; it never overrides a newer user model selection. Pi intentionally ignores overflow errors from non-current models. Successful over-window responses and zero-output length stops also pass through unchanged, allowing Pi to compact and perform its built-in recovery.
26
+ - For any other (unclassified) provider error — including an API `Internal server error` and HTTP `400`/`403` failures — waits and retries the same model. There is deliberately no permanent-HTTP-status allowlist. This retry-all behavior is enabled by default and uses a configurable retry limit and interval; it does not alter model-limit wait intervals.
27
+ - Reports retry/fallback waits in chat notifications without adding persistent TUI status lines.
28
+ - In non-interactive hosts (`rpc` and `json` modes) the extension never reports a planned retry as an error. While it is waiting to retry, it publishes `info`-level liveliness notifications (plus a `setStatus` entry) describing what it is doing, how long it will wait, and why. An `error`-level notification is emitted only when the extension stops retrying and surfaces the terminal failure. See [Non-interactive (RPC) reporting](#non-interactive-rpc-reporting).
29
+ - Press Enter during a retry wait to skip the wait and retry immediately.
30
+ - Optionally falls back to configured models when the current/default model is rate-limited. Every fallback to a different model—even on the same provider—deliberately discards request-owned credentials, headers, environment overrides, and provider-specific options so the target runtime resolves its own configuration.
31
+ - Preserves Pi's `before_provider_request` / `after_provider_response` callback flow and sanitises only the exact Anthropic OAuth system-block shape generated by current Pi; Pi remains responsible for OAuth identity insertion.
32
+
33
+ ### Unsupported interception limitation
34
+
35
+ Pi 0.83 has no public stream middleware that can intercept a failed provider stream, wait, and re-enter `ModelRuntime` for another attempt. For that reason, limits-wait still installs a reload-safe trampoline on `ModelRuntime.prototype.streamSimple`. This is an unsupported internal interception point and could require changes when Pi changes `ModelRuntime`; it also assumes the interactive CLI's one active session runtime per process. SDK processes with multiple concurrent Pi sessions are not supported.
36
+
37
+ `after_provider_response` is preserved for observers, but it is not an equivalent retry hook: it cannot replace stream-error interception, own retry/fallback control flow, or recreate a complete target-model request. In particular, Pi 0.83 still does not expose a public factory for rebuilding the SDK's target-model-aware `transformHeaders` callback. Reusing the source request's closure on a fallback would apply source-model attribution and could leak extension-added headers, so limits-wait intentionally omits it for every different-model fallback. The target `ModelRuntime` still resolves target auth, base URL, configured provider/model headers, environment, and provider implementation canonically, but `before_provider_headers` cannot be re-fired for that fallback through the current public API.
38
+
39
+ ## Non-interactive (RPC) reporting
40
+
41
+ In RPC/JSON mode Pi turns `ctx.ui.setWorkingMessage()` into a no-op, so the interactive countdown is invisible to clients. To keep those clients informed without misreporting recoverable situations as failures, the extension separates progress from failure:
42
+
43
+ | Situation | RPC output |
44
+ | --- | --- |
45
+ | Rate limit, overload, auth refresh, network error, unknown error, frozen model — a retry/fallback is still planned | `extension_ui_request` `notify` with `notifyType: "info"`, repeated every `PI_LIMITS_WAIT_LIVELINESS_INTERVAL` seconds while waiting, plus a `setStatus` entry under key `oira666.pi-limits-wait` |
46
+ | Wait finished, retry started, or request completed | The `setStatus` entry is cleared |
47
+ | Retries exhausted / retrying disabled — the request ends with a provider error | one `extension_ui_request` `notify` with `notifyType: "error"` describing the model and the final error, alongside the normal terminal `error` event |
48
+ | Aborted by the client | no error notification (abort is not a failure); the status entry is cleared |
49
+
50
+ Liveliness messages include the retry reason, the model, the remaining wait, and the originating error message, e.g.:
51
+
52
+ ```
53
+ ⏳ pi-limits-wait: rate limited on anthropic/claude-sonnet-4-5; still alive, waiting 12m 04s before the next retry. Why: HTTP 429 retry-after-ms 724000
54
+ ```
55
+
56
+ Interactive (TUI) behavior is unchanged: retry notices remain `warning`-level chat notifications with the live countdown, and no duplicate error notification is added because the terminal error event is already rendered in the transcript.
57
+
58
+ ## Wait event contract
59
+
60
+ Every positive extension-managed wait emits public telemetry on Pi's shared event bus. Import the channel constant and payload union from the package entry point:
61
+
62
+ ```ts
63
+ import {
64
+ LIMITS_WAIT_EVENT_CHANNEL,
65
+ type LimitsWaitEventPayload,
66
+ } from "oira666_pi-limits-wait";
67
+
68
+ pi.events.on(LIMITS_WAIT_EVENT_CHANNEL, (data) => {
69
+ const event = data as LimitsWaitEventPayload;
70
+ // event.version === 1; event.phase is "start" or "end"
71
+ });
72
+ ```
73
+
74
+ The versioned, namespaced channel is `oira666.pi-limits-wait.wait.v1`. No event is emitted for a zero/negative-duration wait.
75
+
76
+ Both phases contain:
77
+
78
+ - `version: 1`, `phase`, and a correlating `waitId`;
79
+ - optional `periodId` for the associated extension retry period;
80
+ - `reason` (`rate-limit`, `overloaded`, `authentication`, `model-frozen`, `network`, or `retry`);
81
+ - `plannedDurationMs`, `plannedDeadline`, and `startedAt` (`plannedDeadline`/`startedAt` are Unix milliseconds);
82
+ - optional `model: { provider, id }` and a normalized `error` string.
83
+
84
+ The `end` phase additionally contains `endedAt`, `actualElapsedMs`, and `outcome: "waited" | "skipped" | "aborted"`. Start and end are emitted for normal completion, Enter-to-skip, Escape, signal abort, and pre-aborted positive waits. Telemetry is observational: event-bus failures are isolated and cannot cancel or alter a wait.
85
+
86
+ ## Session retry summaries
87
+
88
+ A retry period begins only when a failure will lead to another extension-created attempt or fallback. For each contiguous extension-managed retry period/stream, limits-wait queues one standard Pi custom entry and appends it after the associated assistant turn (normally on `turn_end`, with `agent_end` and `session_shutdown` as duplicate-safe fallbacks):
89
+
90
+ ```json
91
+ {
92
+ "type": "custom",
93
+ "customType": "limits-wait",
94
+ "data": {
95
+ "totalWaitingTime": 12.345,
96
+ "reasons": ["HTTP 429 retry-after-ms 10000", "fetch failed"],
97
+ "retries_total": 2
98
+ }
99
+ }
100
+ ```
101
+
102
+ `totalWaitingTime` is actual elapsed extension wait time in seconds, not the planned duration. `reasons` contains whitespace-normalized error messages, deduplicated while preserving first-seen order. `retries_total` counts attempts/fallbacks created by this extension: the initial provider request and retries performed internally by a provider SDK do not count; an immediate fallback or zero-wait retry does count. A wait aborted before another attempt does not create a retry count by itself.
103
+
104
+ Pi custom entries created with `pi.appendEntry()` do not participate in LLM context. The summary is transcript metadata only.
105
+
106
+ ## Optional fallback models
107
+
108
+ By default, if you do not configure fallback models, the extension behaves exactly as before: it waits for the current model's limit to reset and retries.
109
+
110
+ To enable automatic model fallback, add `fallback-models` to a `limits-wait.json` file.
111
+
112
+ The extension reads these files in order; later files override earlier ones:
113
+
114
+ | Location | Scope | Trust behavior |
115
+ |----------|-------|----------------|
116
+ | `~/.config/.pi/limits-wait.json` | Global defaults | Always loaded |
117
+ | `<Pi agent dir>/limits-wait.json` (for example `~/.pi/agent/limits-wait.json`, or `PI_CODING_AGENT_DIR/limits-wait.json`) | Pi agent directory | Always loaded |
118
+ | `.limits-wait.json` | Project root override | Loaded only when `ctx.isProjectTrusted()` is `true` |
119
+ | `.pi/limits-wait.json` | Pi project override | Loaded only when `ctx.isProjectTrusted()` is `true` |
120
+
121
+ For an untrusted project, both project-local paths are ignored completely (including malformed-file warnings); global and agent settings still apply. The trust decision is Pi's effective session trust, including temporary/CLI trust decisions. The same rule is applied every time settings are reread before an LLM call.
122
+
123
+ Example:
124
+
125
+ ```json
126
+ {
127
+ "fallback-models": [
128
+ {
129
+ "provider": "anthropic",
130
+ "modelname": "claude-sonnet-4-5",
131
+ "reasoning effort": "medium"
132
+ },
133
+ {
134
+ "provider": "openai",
135
+ "modelname": "gpt-5.1-codex"
136
+ }
137
+ ]
138
+ }
139
+ ```
140
+
141
+ Each `fallback-models` entry supports:
142
+
143
+ - `provider` — required. The Pi provider name, for example `anthropic`, `openai`, `google`, etc.
144
+ - `modelname` — required. The model id/name as Pi knows it.
145
+ - `reasoning effort` — optional. One of `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`. If omitted, Pi's current/default reasoning level is used.
146
+
147
+ Fallback behavior:
148
+
149
+ 1. Pi starts with the normal default or user-selected model.
150
+ 2. If that model is rate-limited, the extension tries models in this order:
151
+ - the original default/user-selected model;
152
+ - then every model from `fallback-models`, top to bottom.
153
+ 3. The first model that responds without a rate-limit becomes the active Pi model for the rest of the session/task.
154
+ 4. If that model later becomes rate-limited too, the extension starts again from the same ordered list.
155
+ 5. Rate-limit reset times are remembered only in memory, so known-limited models are skipped until their countdown expires. This memory is cleared when Pi restarts.
156
+
157
+ The extension rereads these files before every LLM call, so changes made during a long-running task take effect on the next LLM call without restarting Pi or using `/reload`. Tool calls do not trigger a settings read. When the settings are loaded, the extension shows the full usable fallback model list. When models become rate-limited, it reports the wait in chat notifications.
158
+
159
+ ## Environment variables
160
+
161
+ | Variable | Default | Description |
162
+ |----------|---------|-------------|
163
+ | `PI_LIMITS_WAIT_DEFAULT_WAITING` | `true` | Whether unclassified/unknown provider errors enter the wait-and-retry cycle. Set to `false` (also accepts `0`, `no`, `off`) to surface the error immediately. Rate-limit, overload, authentication, and network retry behavior is unchanged. |
164
+ | `PI_LIMITS_WAIT_MAX_RETRY` | `999999` | Maximum extension-created retries for an unclassified/unknown provider error. The initial failed request and provider-internal retries do not count. HTTP 400/403 remain eligible. Must be a non-negative integer. |
165
+ | `PI_LIMITS_WAIT_LIVELINESS_INTERVAL` | `15` | Seconds between liveliness notifications published while waiting in non-interactive (`rpc`/`json`) modes. Must be a non-negative integer; values below 1 second are clamped to 1 second. Ignored in TUI mode. |
166
+ | `PI_LIMITS_WAIT_RETRY_INTERVAL` | `5` | Seconds to wait between unclassified/unknown provider-error retries. Must be a non-negative integer (values above 2147483 are capped to Node's maximum reliable timer delay). This does not affect model-limit wait intervals. |
167
+ | `PI_LIMITS_WAIT_FREEZING_ENABLED` | `true` | When a model keeps failing with a non-retryable error, it is normally "frozen" for 10 minutes and skipped in favour of other configured models. Set this to `false` (also accepts `0`, `no`, `off`) to disable freezing entirely: the extension will instead try each configured candidate once (after the bounded retries) and then surface the error, never blocking on a long "model-frozen" wait. Useful for non-interactive / subagent runs where no one can press Enter to skip. |
168
+
169
+ ## License
170
+
171
+ MIT
package/package.json CHANGED
@@ -1,52 +1,52 @@
1
- {
2
- "name": "oira666_pi-limits-wait",
3
- "version": "0.5.3",
4
- "description": "Extension for PI that allows to automatically wait for limits to reset and retry the request",
5
- "type": "module",
6
- "exports": {
7
- ".": "./src/index.ts"
8
- },
9
- "license": "MIT",
10
- "files": [
11
- "src",
12
- "!src/__tests__",
13
- "README.md",
14
- "LICENSE"
15
- ],
16
- "engines": {
17
- "node": ">=22.19.0"
18
- },
19
- "repository": {
20
- "type": "git",
21
- "url": "https://github.com/gee666/pi-limits-wait.git"
22
- },
23
- "bugs": {
24
- "url": "https://github.com/gee666/pi-limits-wait/issues"
25
- },
26
- "homepage": "https://github.com/gee666/pi-limits-wait#readme",
27
- "publishConfig": {
28
- "access": "public"
29
- },
30
- "scripts": {
31
- "check": "tsc -p tsconfig.json --noEmit",
32
- "test": "tsx src/__tests__/test-extension.ts",
33
- "pack:check": "npm pack --dry-run",
34
- "prepublishOnly": "npm run check && npm test && npm run pack:check"
35
- },
36
- "pi": {
37
- "extensions": [
38
- "./src/index.ts"
39
- ]
40
- },
41
- "dependencies": {},
42
- "peerDependencies": {
43
- "@earendil-works/pi-ai": ">=0.80.10",
44
- "@earendil-works/pi-coding-agent": ">=0.80.10"
45
- },
46
- "devDependencies": {
47
- "@earendil-works/pi-ai": "0.83.0",
48
- "@earendil-works/pi-coding-agent": "0.83.0",
49
- "tsx": "^4.20.6",
50
- "typescript": "^5.9.3"
51
- }
52
- }
1
+ {
2
+ "name": "oira666_pi-limits-wait",
3
+ "version": "0.5.5",
4
+ "description": "Extension for PI that allows to automatically wait for limits to reset and retry the request",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": "./src/index.ts"
8
+ },
9
+ "license": "MIT",
10
+ "files": [
11
+ "src",
12
+ "!src/__tests__",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+ "engines": {
17
+ "node": ">=22.19.0"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/gee666/pi-limits-wait.git"
22
+ },
23
+ "bugs": {
24
+ "url": "https://github.com/gee666/pi-limits-wait/issues"
25
+ },
26
+ "homepage": "https://github.com/gee666/pi-limits-wait#readme",
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
30
+ "scripts": {
31
+ "check": "tsc -p tsconfig.json --noEmit",
32
+ "test": "tsx src/__tests__/test-extension.ts",
33
+ "pack:check": "npm pack --dry-run",
34
+ "prepublishOnly": "npm run check && npm test && npm run pack:check"
35
+ },
36
+ "pi": {
37
+ "extensions": [
38
+ "./src/index.ts"
39
+ ]
40
+ },
41
+ "dependencies": {},
42
+ "peerDependencies": {
43
+ "@earendil-works/pi-ai": ">=0.80.10",
44
+ "@earendil-works/pi-coding-agent": ">=0.80.10"
45
+ },
46
+ "devDependencies": {
47
+ "@earendil-works/pi-ai": "0.83.0",
48
+ "@earendil-works/pi-coding-agent": "0.83.0",
49
+ "tsx": "^4.20.6",
50
+ "typescript": "^5.9.3"
51
+ }
52
+ }
package/src/constants.ts CHANGED
@@ -1,38 +1,44 @@
1
- export const CLAUDE_CODE_IDENTITY =
2
- "You are Claude Code, Anthropic's official CLI for Claude.";
3
-
4
- export const PI_REMOVAL_ANCHORS = [
5
- "pi-coding-agent",
6
- "@earendil-works/pi-coding-agent",
7
- "badlogic/pi-mono",
8
- ] as const;
9
-
10
- export const PI_IDENTITY_SENTENCE_PATTERN =
11
- /(?:^|\n)\s*You are pi\b[^.!?\n]*(?:[.!?](?=\s|$)|(?=\n|$))/gi;
12
-
13
- // Strips the host-harness identity clause pi prepends to its system prompt, e.g.
14
- // "You are an expert coding assistant operating inside pi, a coding agent harness."
15
- // The Claude Code identity block already establishes who the assistant is, so
16
- // leaving this clause in tells the model (and Anthropic) it is running inside pi.
17
- export const PI_HARNESS_IDENTITY_PATTERN =
18
- /\s+operating inside pi,\s*a coding agent harness/gi;
19
-
20
- export const CLAUDE_CODE_IDENTITY_PATTERN =
21
- /(?:^|\n)\s*You are Claude Code, Anthropic's official CLI for Claude\.\s*/gi;
22
-
23
- export const DEFAULT_RATE_LIMIT_WAIT_MS = 30 * 60 * 1_000; // 30 minutes
24
- export const DEFAULT_OVERLOADED_WAIT_MS = 5 * 60 * 1_000; // 5 minutes
25
- export const DEFAULT_NETWORK_WAIT_MS = 15 * 1_000; // 15 seconds
26
- export const DEFAULT_NON_RETRYABLE_FREEZE_MS = 10 * 60 * 1_000; // 10 minutes
27
-
28
- // Unknown provider errors are retried separately from rate-limit, overload,
29
- // authentication, and network failures.
30
- export const DEFAULT_UNKNOWN_ERROR_MAX_RETRIES = 999_999;
31
- export const DEFAULT_UNKNOWN_ERROR_RETRY_INTERVAL_MS = 5 * 1_000; // 5 seconds
32
-
33
- export const SETTINGS_FILE_NAME = "limits-wait.json";
34
- export const FALLBACK_MODELS_KEY = "fallback-models";
35
- export const FREEZING_ENV_VAR = "PI_LIMITS_WAIT_FREEZING_ENABLED";
36
- export const DEFAULT_WAITING_ENV_VAR = "PI_LIMITS_WAIT_DEFAULT_WAITING";
37
- export const MAX_RETRY_ENV_VAR = "PI_LIMITS_WAIT_MAX_RETRY";
38
- export const RETRY_INTERVAL_ENV_VAR = "PI_LIMITS_WAIT_RETRY_INTERVAL";
1
+ export const CLAUDE_CODE_IDENTITY =
2
+ "You are Claude Code, Anthropic's official CLI for Claude.";
3
+
4
+ export const PI_REMOVAL_ANCHORS = [
5
+ "pi-coding-agent",
6
+ "@earendil-works/pi-coding-agent",
7
+ "badlogic/pi-mono",
8
+ ] as const;
9
+
10
+ export const PI_IDENTITY_SENTENCE_PATTERN =
11
+ /(?:^|\n)\s*You are pi\b[^.!?\n]*(?:[.!?](?=\s|$)|(?=\n|$))/gi;
12
+
13
+ // Strips the host-harness identity clause pi prepends to its system prompt, e.g.
14
+ // "You are an expert coding assistant operating inside pi, a coding agent harness."
15
+ // The Claude Code identity block already establishes who the assistant is, so
16
+ // leaving this clause in tells the model (and Anthropic) it is running inside pi.
17
+ export const PI_HARNESS_IDENTITY_PATTERN =
18
+ /\s+operating inside pi,\s*a coding agent harness/gi;
19
+
20
+ export const CLAUDE_CODE_IDENTITY_PATTERN =
21
+ /(?:^|\n)\s*You are Claude Code, Anthropic's official CLI for Claude\.\s*/gi;
22
+
23
+ export const DEFAULT_RATE_LIMIT_WAIT_MS = 30 * 60 * 1_000; // 30 minutes
24
+ export const DEFAULT_OVERLOADED_WAIT_MS = 5 * 60 * 1_000; // 5 minutes
25
+ export const DEFAULT_NETWORK_WAIT_MS = 15 * 1_000; // 15 seconds
26
+ export const DEFAULT_NON_RETRYABLE_FREEZE_MS = 10 * 60 * 1_000; // 10 minutes
27
+
28
+ // Unknown provider errors are retried separately from rate-limit, overload,
29
+ // authentication, and network failures.
30
+ export const DEFAULT_UNKNOWN_ERROR_MAX_RETRIES = 999_999;
31
+ export const DEFAULT_UNKNOWN_ERROR_RETRY_INTERVAL_MS = 5 * 1_000; // 5 seconds
32
+
33
+ // Non-interactive hosts (RPC/JSON) cannot render the TUI countdown, so retry
34
+ // progress is published as periodic liveliness notifications instead.
35
+ export const LIVELINESS_STATUS_KEY = "oira666.pi-limits-wait";
36
+ export const DEFAULT_LIVELINESS_INTERVAL_MS = 15 * 1_000; // 15 seconds
37
+
38
+ export const SETTINGS_FILE_NAME = "limits-wait.json";
39
+ export const FALLBACK_MODELS_KEY = "fallback-models";
40
+ export const FREEZING_ENV_VAR = "PI_LIMITS_WAIT_FREEZING_ENABLED";
41
+ export const DEFAULT_WAITING_ENV_VAR = "PI_LIMITS_WAIT_DEFAULT_WAITING";
42
+ export const MAX_RETRY_ENV_VAR = "PI_LIMITS_WAIT_MAX_RETRY";
43
+ export const RETRY_INTERVAL_ENV_VAR = "PI_LIMITS_WAIT_RETRY_INTERVAL";
44
+ export const LIVELINESS_INTERVAL_ENV_VAR = "PI_LIMITS_WAIT_LIVELINESS_INTERVAL";