@azumag/opencode-rate-limit-fallback 1.0.15 → 1.0.17

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.
Files changed (2) hide show
  1. package/dist/index.js +22 -13
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -316,28 +316,37 @@ export const RateLimitFallback = async ({ client, directory }) => {
316
316
  parts: parts,
317
317
  model: { providerID: nextModel.providerID, modelID: nextModel.modelID },
318
318
  };
319
- toast("Retrying", `Using ${nextModel.providerID}/${nextModel.modelID}`, "info").catch(() => { });
320
- // Abort to cancel the retry loop, then promptAsync immediately.
321
- // CRITICAL: no delay between abort and promptAsync.
322
- // - In headless (opencode run), server disposes ~8ms after session idle.
323
- // Any delay (even 50ms) means promptAsync arrives after server is dead.
324
- // - promptAsync returns immediately (just queues), so it fires before
325
- // the server can shut down.
326
- // - Do NOT use prompt (sync) here it causes abort flag race condition
327
- // in TUI mode (prompt immediately interrupted).
319
+ // CRITICAL PATH: abort promptAsync with NO delay between them.
320
+ //
321
+ // In headless mode (opencode run), the server disposes shortly after
322
+ // session goes idle. Any delay (setTimeout, awaited toast, etc.) risks
323
+ // promptAsync arriving after the server is dead.
324
+ //
325
+ // promptAsync: HTTP POST /session/{id}/prompt_async → 204 (SDK sdk.gen.js).
326
+ // prompt (sync): blocks until generation completes abort flag race in TUI,
327
+ // server dispose in headless. Do NOT use.
328
+ const t0 = Date.now();
328
329
  try {
329
330
  await client.session.abort({ path: { id: sessionID } });
330
- logToFile(`abort succeeded for session ${sessionID}`);
331
+ logToFile(`abort succeeded for session ${sessionID} (${Date.now() - t0}ms)`);
331
332
  }
332
333
  catch (abortErr) {
333
- logToFile(`abort failed (non-critical): ${abortErr}`);
334
+ // If abort fails, the session may still be in its retry loop.
335
+ // We still send promptAsync as best-effort: when the retry loop eventually
336
+ // completes (timeout or success), the queued prompt should be processed.
337
+ logToFile(`abort failed (${Date.now() - t0}ms): ${abortErr} — sending promptAsync as best-effort`);
334
338
  }
339
+ const t1 = Date.now();
335
340
  await client.session.promptAsync({
336
341
  path: { id: sessionID },
337
342
  body: promptBody,
338
343
  });
339
- logToFile(`promptAsync sent successfully for session ${sessionID} with model ${nextModel.providerID}/${nextModel.modelID}`);
340
- toast("Fallback Successful", `Now using ${nextModel.modelID}`, "success").catch(() => { });
344
+ logToFile(`promptAsync completed for session ${sessionID} (${Date.now() - t1}ms, total ${Date.now() - t0}ms) with model ${nextModel.providerID}/${nextModel.modelID}`);
345
+ // Toast is best-effort notification. The toast() function (line ~185) has
346
+ // built-in fallback: showToast failure → app.log. After promptAsync the
347
+ // server may already be disposing, so both showToast and app.log could fail.
348
+ // The outer .catch() ensures even total toast() failure never blocks or throws.
349
+ toast("Fallback Active", `Now using ${nextModel.modelID}`, "success").catch(() => { });
341
350
  retryState.delete(stateKey);
342
351
  // Clear fallback flag to allow next fallback if needed
343
352
  fallbackInProgress.delete(sessionID);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@azumag/opencode-rate-limit-fallback",
3
- "version": "1.0.15",
3
+ "version": "1.0.17",
4
4
  "description": "OpenCode plugin that automatically switches to fallback models when rate limited",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",