@azumag/opencode-rate-limit-fallback 1.0.10 → 1.0.12

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 +32 -13
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -225,8 +225,7 @@ export const RateLimitFallback = async ({ client, directory }) => {
225
225
  currentModelID = tracked.modelID;
226
226
  }
227
227
  }
228
- await client.session.abort({ path: { id: sessionID } });
229
- await toast("Rate Limit Detected", `Switching from ${currentModelID || 'current model'}...`, "warning");
228
+ // Fetch messages BEFORE abort session must still be alive
230
229
  const messagesResult = await client.session.messages({ path: { id: sessionID } });
231
230
  if (!messagesResult.data)
232
231
  return;
@@ -234,6 +233,7 @@ export const RateLimitFallback = async ({ client, directory }) => {
234
233
  const lastUserMessage = [...messages].reverse().find(m => m.info.role === "user");
235
234
  if (!lastUserMessage)
236
235
  return;
236
+ toast("Rate Limit Detected", `Switching from ${currentModelID || 'current model'}...`, "warning").catch(() => { });
237
237
  const stateKey = `${sessionID}:${lastUserMessage.info.id}`;
238
238
  let state = retryState.get(stateKey);
239
239
  if (!state || Date.now() - state.lastAttemptTime > 30000) {
@@ -259,12 +259,11 @@ export const RateLimitFallback = async ({ client, directory }) => {
259
259
  // Try the last model in the list once, then reset on next prompt
260
260
  const lastModel = config.fallbackModels[config.fallbackModels.length - 1];
261
261
  if (lastModel) {
262
- const lastKey = getModelKey(lastModel.providerID, lastModel.modelID);
263
262
  const isLastModelCurrent = currentProviderID === lastModel.providerID && currentModelID === lastModel.modelID;
264
263
  if (!isLastModelCurrent && !isModelRateLimited(lastModel.providerID, lastModel.modelID)) {
265
264
  // Use the last model for one more try
266
265
  nextModel = lastModel;
267
- await toast("Last Resort", `Trying ${lastModel.modelID} one more time...`, "warning");
266
+ toast("Last Resort", `Trying ${lastModel.modelID} one more time...`, "warning").catch(() => { });
268
267
  }
269
268
  else {
270
269
  // Last model also failed, reset for next prompt
@@ -279,9 +278,9 @@ export const RateLimitFallback = async ({ client, directory }) => {
279
278
  // "stop" mode: nextModel remains null, will show error below
280
279
  }
281
280
  if (!nextModel) {
282
- await toast("No Fallback Available", config.fallbackMode === "stop"
281
+ toast("No Fallback Available", config.fallbackMode === "stop"
283
282
  ? "All fallback models exhausted"
284
- : "All models are rate limited", "error");
283
+ : "All models are rate limited", "error").catch(() => { });
285
284
  retryState.delete(stateKey);
286
285
  fallbackInProgress.delete(sessionID);
287
286
  return;
@@ -300,22 +299,35 @@ export const RateLimitFallback = async ({ client, directory }) => {
300
299
  .filter(Boolean);
301
300
  if (parts.length === 0)
302
301
  return;
303
- await toast("Retrying", `Using ${nextModel.providerID}/${nextModel.modelID}`, "info");
302
+ toast("Retrying", `Using ${nextModel.providerID}/${nextModel.modelID}`, "info").catch(() => { });
304
303
  // Track the new model for this session
305
304
  currentSessionModel.set(sessionID, { providerID: nextModel.providerID, modelID: nextModel.modelID });
306
- await client.session.prompt({
305
+ const promptBody = {
306
+ parts: parts,
307
+ model: { providerID: nextModel.providerID, modelID: nextModel.modelID },
308
+ };
309
+ // Abort first to cancel the retry loop, then promptAsync immediately
310
+ // The abort→promptAsync gap is minimal, so even in headless mode
311
+ // the server won't shut down before promptAsync fires
312
+ try {
313
+ await client.session.abort({ path: { id: sessionID } });
314
+ logToFile(`abort succeeded for session ${sessionID}`);
315
+ }
316
+ catch (abortErr) {
317
+ logToFile(`abort failed (non-critical): ${abortErr}`);
318
+ }
319
+ await client.session.promptAsync({
307
320
  path: { id: sessionID },
308
- body: {
309
- parts: parts,
310
- model: { providerID: nextModel.providerID, modelID: nextModel.modelID },
311
- },
321
+ body: promptBody,
312
322
  });
313
- await toast("Fallback Successful", `Now using ${nextModel.modelID}`, "success");
323
+ logToFile(`promptAsync sent successfully for session ${sessionID} with model ${nextModel.providerID}/${nextModel.modelID}`);
324
+ toast("Fallback Successful", `Now using ${nextModel.modelID}`, "success").catch(() => { });
314
325
  retryState.delete(stateKey);
315
326
  // Clear fallback flag to allow next fallback if needed
316
327
  fallbackInProgress.delete(sessionID);
317
328
  }
318
329
  catch (err) {
330
+ logToFile(`handleRateLimitFallback error: ${err}`);
319
331
  // Fallback failed, clear the flag
320
332
  fallbackInProgress.delete(sessionID);
321
333
  }
@@ -384,6 +396,13 @@ export const RateLimitFallback = async ({ client, directory }) => {
384
396
  catch {
385
397
  console.log("[rate-limit-fallback] message.updated:", info);
386
398
  }
399
+ // Track assistant message model info for later use in fallback
400
+ if (info?.role === "assistant" && info?.sessionID && info?.providerID && info?.modelID) {
401
+ currentSessionModel.set(info.sessionID, {
402
+ providerID: info.providerID,
403
+ modelID: info.modelID,
404
+ });
405
+ }
387
406
  if (info?.error && isRateLimitError(info.error)) {
388
407
  logToFile("Rate limit error in message, attempting fallback");
389
408
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@azumag/opencode-rate-limit-fallback",
3
- "version": "1.0.10",
3
+ "version": "1.0.12",
4
4
  "description": "OpenCode plugin that automatically switches to fallback models when rate limited",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",