@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.
- package/dist/index.js +32 -13
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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