@azumag/opencode-rate-limit-fallback 1.0.14 → 1.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +20 -11
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -316,27 +316,36 @@ export const RateLimitFallback = async ({ client, directory }) => {
|
|
|
316
316
|
parts: parts,
|
|
317
317
|
model: { providerID: nextModel.providerID, modelID: nextModel.modelID },
|
|
318
318
|
};
|
|
319
|
-
//
|
|
319
|
+
// CRITICAL PATH: abort → promptAsync with NO delay between them.
|
|
320
|
+
//
|
|
321
|
+
// In headless mode (opencode run), the server disposes within milliseconds
|
|
322
|
+
// after session goes idle (observed ~8ms in production logs — this is not
|
|
323
|
+
// a guaranteed bound, just empirically observed). Any delay (setTimeout,
|
|
324
|
+
// awaited toast, etc.) means promptAsync arrives after the server is dead.
|
|
325
|
+
//
|
|
326
|
+
// The await on promptAsync waits for the HTTP round-trip (server acknowledgment),
|
|
327
|
+
// NOT for prompt completion — generation runs asynchronously on the server.
|
|
328
|
+
//
|
|
329
|
+
// Do NOT use prompt() (sync) here — it triggers the abort flag race condition
|
|
330
|
+
// in TUI mode, causing the new prompt to be immediately interrupted.
|
|
320
331
|
try {
|
|
321
332
|
await client.session.abort({ path: { id: sessionID } });
|
|
322
333
|
logToFile(`abort succeeded for session ${sessionID}`);
|
|
323
334
|
}
|
|
324
335
|
catch (abortErr) {
|
|
325
|
-
|
|
336
|
+
// If abort fails, the session may still be in its retry loop.
|
|
337
|
+
// We still send promptAsync as best-effort: when the retry loop eventually
|
|
338
|
+
// completes (timeout or success), the queued prompt should be processed.
|
|
339
|
+
logToFile(`abort failed: ${abortErr} — sending promptAsync as best-effort`);
|
|
326
340
|
}
|
|
327
|
-
// Wait for abort to fully complete before sending new prompt.
|
|
328
|
-
// Toast alone (~50ms) is insufficient — the abort flag may still be set,
|
|
329
|
-
// causing the new prompt to be immediately interrupted.
|
|
330
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
331
|
-
toast("Retrying", `Using ${nextModel.providerID}/${nextModel.modelID}`, "info").catch(() => { });
|
|
332
|
-
// Use promptAsync — returns immediately, compatible with both TUI and headless modes.
|
|
333
|
-
// prompt (sync) causes race condition: abort flag not yet cleared → prompt interrupted.
|
|
334
341
|
await client.session.promptAsync({
|
|
335
342
|
path: { id: sessionID },
|
|
336
343
|
body: promptBody,
|
|
337
344
|
});
|
|
338
|
-
logToFile(`promptAsync sent
|
|
339
|
-
|
|
345
|
+
logToFile(`promptAsync sent for session ${sessionID} with model ${nextModel.providerID}/${nextModel.modelID}`);
|
|
346
|
+
// Toasts are fire-and-forget: placed AFTER the critical path so they cannot
|
|
347
|
+
// interfere with the abort→promptAsync timing, even if they fail in headless.
|
|
348
|
+
toast("Fallback Active", `Now using ${nextModel.modelID}`, "success").catch(() => { });
|
|
340
349
|
retryState.delete(stateKey);
|
|
341
350
|
// Clear fallback flag to allow next fallback if needed
|
|
342
351
|
fallbackInProgress.delete(sessionID);
|
package/package.json
CHANGED