oira666_pi-limits-wait 0.5.8 → 0.5.9
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 +3 -3
- package/package.json +1 -1
- package/src/ui.ts +8 -33
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@ Restart Pi or run `/reload` after installing.
|
|
|
26
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
27
|
- Reports retry/fallback waits in chat notifications without adding persistent TUI status lines.
|
|
28
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
|
-
-
|
|
29
|
+
- Leaves terminal input to Pi, so built-in selectors remain usable during a wait. Choosing another model with `/model` immediately ends the pending wait and continues with the newly selected provider/model.
|
|
30
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
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
32
|
|
|
@@ -119,7 +119,7 @@ import {
|
|
|
119
119
|
|
|
120
120
|
### Skipping a wait from the host
|
|
121
121
|
|
|
122
|
-
|
|
122
|
+
To skip a wait without changing models, set `PI_LIMITS_WAIT_CONTROL_FILE` to an absolute path (one per pi process; the extension echoes it back as `controlFile` in every structured frame). While — and only while — a wait is in progress, the extension polls that path once per second from the countdown ticker it already runs. Signals are deliberately **not** used: `SIGUSR2` with no handler installed terminates the process, and a signal cannot carry a `waitId`.
|
|
123
123
|
|
|
124
124
|
Write a token to make the extension stop waiting and retry immediately:
|
|
125
125
|
|
|
@@ -132,7 +132,7 @@ Write a token to make the extension stop waiting and retry immediately:
|
|
|
132
132
|
- `issuedAt` is epoch ms and must be `>= startedAt` of the current wait.
|
|
133
133
|
- Writers should write a temp file and rename it into place (atomic move) so a partial read is impossible.
|
|
134
134
|
|
|
135
|
-
The extension consumes a token by atomically renaming the canonical control path to a unique `.consumed-<waitId>-<random>` path in the same directory, then reading and validating that claimed file. It removes only the unique claimed path. A writer may therefore rename a newer token onto the canonical path immediately after the claim without cleanup deleting that replacement. An accepted claim settles the wait with `outcome: "skipped"
|
|
135
|
+
The extension consumes a token by atomically renaming the canonical control path to a unique `.consumed-<waitId>-<random>` path in the same directory, then reading and validating that claimed file. It removes only the unique claimed path. A writer may therefore rename a newer token onto the canonical path immediately after the claim without cleanup deleting that replacement. An accepted claim settles the wait with `outcome: "skipped"`, so the retry proceeds normally and a `wait_end` frame with `"outcome":"skipped"` is published. Polls are serialized; a slow filesystem operation cannot overlap the next one-second tick.
|
|
136
136
|
|
|
137
137
|
Two content guards ensure a claimed leftover token can never skip a **later** legitimate wait: (a) `waitId` equality — a token written for wait *N* cannot match wait *N+1*'s freshly generated id; (b) `issuedAt >= startedAt` — a `"*"` token issued before this wait began is rejected. Claimed malformed, stale, and non-matching files are discarded, while any newer token at the canonical path is left untouched. The extension deliberately does not unlink the canonical path at wait start, because that would race with a token written microseconds earlier.
|
|
138
138
|
|
package/package.json
CHANGED
package/src/ui.ts
CHANGED
|
@@ -215,15 +215,12 @@ function startLivelinessReporting(
|
|
|
215
215
|
};
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
-
function countdownText(reason: RetryReason, deadline: number
|
|
218
|
+
function countdownText(reason: RetryReason, deadline: number): string {
|
|
219
219
|
const remaining = Math.max(0, deadline - Date.now());
|
|
220
220
|
const totalSecs = Math.ceil(remaining / 1_000);
|
|
221
221
|
const mins = Math.floor(totalSecs / 60);
|
|
222
222
|
const secs = (totalSecs % 60).toString().padStart(2, "0");
|
|
223
|
-
return (
|
|
224
|
-
`⏳ ${reasonLabel(reason)} — next retry in ${mins}m ${secs}s` +
|
|
225
|
-
(allowSkip ? " (Enter to retry now)" : "")
|
|
226
|
-
);
|
|
223
|
+
return `⏳ ${reasonLabel(reason)} — next retry in ${mins}m ${secs}s`;
|
|
227
224
|
}
|
|
228
225
|
|
|
229
226
|
export function showAmbientRetryStatus(reason: RetryReason, waitMs: number): void {
|
|
@@ -239,7 +236,7 @@ export function showAmbientRetryStatus(reason: RetryReason, waitMs: number): voi
|
|
|
239
236
|
state.ambientStatusCleanup?.();
|
|
240
237
|
return;
|
|
241
238
|
}
|
|
242
|
-
ctx.ui.setWorkingMessage(countdownText(reason, deadline
|
|
239
|
+
ctx.ui.setWorkingMessage(countdownText(reason, deadline));
|
|
243
240
|
};
|
|
244
241
|
|
|
245
242
|
tick();
|
|
@@ -341,7 +338,6 @@ export function waitForRetry(
|
|
|
341
338
|
const ctx = state.sharedCtx;
|
|
342
339
|
let done = false;
|
|
343
340
|
const stopLiveliness = startLivelinessReporting(statusContext, reason, plannedDeadline, model, telemetry?.error);
|
|
344
|
-
let unsubInput: (() => void) | undefined;
|
|
345
341
|
let ticker: ReturnType<typeof setInterval> | undefined;
|
|
346
342
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
347
343
|
|
|
@@ -355,7 +351,6 @@ export function waitForRetry(
|
|
|
355
351
|
state.activeWaitSkips.delete(skipWait);
|
|
356
352
|
try { stopLiveliness(false); } catch { /* UI unavailable */ }
|
|
357
353
|
signal?.removeEventListener("abort", onAbort);
|
|
358
|
-
try { unsubInput?.(); } catch { /* UI unavailable */ }
|
|
359
354
|
try { ctx?.ui.setWorkingMessage(); } catch { /* UI unavailable */ }
|
|
360
355
|
try { clearAmbientRetryStatus(); } catch { /* UI unavailable */ }
|
|
361
356
|
}
|
|
@@ -437,32 +432,12 @@ export function waitForRetry(
|
|
|
437
432
|
return;
|
|
438
433
|
}
|
|
439
434
|
|
|
435
|
+
// Raw terminal listeners run before whichever component currently owns
|
|
436
|
+
// focus. Consuming Enter here would steal confirmation from built-in
|
|
437
|
+
// selectors such as /model. Model changes and the out-of-band control
|
|
438
|
+
// channel call this skip function directly instead.
|
|
440
439
|
state.activeWaitSkips.add(skipWait);
|
|
441
440
|
|
|
442
|
-
try {
|
|
443
|
-
const unsubscribe = ctx?.ui.onTerminalInput((data) => {
|
|
444
|
-
if (done) return undefined;
|
|
445
|
-
if (data === "\r" || data === "\n") {
|
|
446
|
-
// Enter belongs to the editor whenever it contains text. In
|
|
447
|
-
// particular, slash-command autocomplete needs Enter to confirm and
|
|
448
|
-
// then execute a command; do not steal either press from it.
|
|
449
|
-
let editorText = "";
|
|
450
|
-
try { editorText = ctx?.ui.getEditorText() ?? ""; } catch { /* UI unavailable */ }
|
|
451
|
-
if (editorText.length > 0) return undefined;
|
|
452
|
-
settle("skipped");
|
|
453
|
-
return { consume: true };
|
|
454
|
-
}
|
|
455
|
-
if (data === "\x1b") {
|
|
456
|
-
settle("aborted");
|
|
457
|
-
return { consume: true };
|
|
458
|
-
}
|
|
459
|
-
return undefined;
|
|
460
|
-
});
|
|
461
|
-
if (done) unsubscribe?.();
|
|
462
|
-
else unsubInput = unsubscribe;
|
|
463
|
-
} catch { /* UI unavailable */ }
|
|
464
|
-
if (done) return;
|
|
465
|
-
|
|
466
441
|
signal?.addEventListener("abort", onAbort);
|
|
467
442
|
if (signal?.aborted) {
|
|
468
443
|
settle("aborted");
|
|
@@ -470,7 +445,7 @@ export function waitForRetry(
|
|
|
470
445
|
}
|
|
471
446
|
|
|
472
447
|
const tick = () => {
|
|
473
|
-
try { ctx?.ui.setWorkingMessage(countdownText(reason, plannedDeadline
|
|
448
|
+
try { ctx?.ui.setWorkingMessage(countdownText(reason, plannedDeadline)); } catch { /* UI unavailable */ }
|
|
474
449
|
};
|
|
475
450
|
|
|
476
451
|
tick();
|