@renjfk/opencode-model-fallback 0.1.2 → 0.2.0

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 CHANGED
@@ -55,7 +55,6 @@ If you want to set plugin options, use the tuple form:
55
55
  - `retry_on_errors`: retryable HTTP status codes. Defaults to `429`.
56
56
  - `retryable_error_patterns`: retryable error message patterns. Defaults to `["rate.?limit"]`.
57
57
  - `cooldown_seconds`: how long a failed original model remains on fallback. Defaults to `3600`.
58
- - `timeout_seconds`: abort and retry if a response is inactive for this long. Defaults to `30`.
59
58
  - `notify_on_fallback`: show fallback/recovery toasts. Defaults to `true`.
60
59
 
61
60
  ## How it works
@@ -227,7 +226,7 @@ at the local repo path, not the npm package name:
227
226
 
228
227
  ```json
229
228
  {
230
- "plugin": ["/Users/your-user/opencode-model-fallback"]
229
+ "plugin": ["/Users/your-user/opencode-model-fallback/index.js"]
231
230
  }
232
231
  ```
233
232
 
package/lib/options.js CHANGED
@@ -3,7 +3,6 @@ const DEFAULT_OPTIONS = {
3
3
  retry_on_errors: [429],
4
4
  retryable_error_patterns: ["rate.?limit"],
5
5
  cooldown_seconds: 3600,
6
- timeout_seconds: 30,
7
6
  notify_on_fallback: true,
8
7
  };
9
8
 
package/lib/router.js CHANGED
@@ -13,7 +13,6 @@ export function createMappedFallbackRouter(ctx, rawOptions) {
13
13
  );
14
14
  const store = createStateStore();
15
15
  const retrying = new Set();
16
- const timers = new Map();
17
16
  const selfAbortAt = new Map();
18
17
  const activeOriginals = new Map();
19
18
  const activeTargets = new Map();
@@ -42,13 +41,6 @@ export function createMappedFallbackRouter(ctx, rawOptions) {
42
41
  return cooldown ? fallback : original;
43
42
  }
44
43
 
45
- function timedOriginal(requested) {
46
- if (!hasMapping(requested)) return undefined;
47
- const cooldown = store.getModelCooldown(requested);
48
- if (cooldown) return undefined;
49
- return requested;
50
- }
51
-
52
44
  function shouldRoute(requested) {
53
45
  return hasMapping(requested) || !!fallbackToOriginal[requested];
54
46
  }
@@ -66,31 +58,6 @@ export function createMappedFallbackRouter(ctx, rawOptions) {
66
58
  return !cooldown;
67
59
  }
68
60
 
69
- function clearTimer(sessionID) {
70
- const timer = timers.get(sessionID);
71
- if (timer) clearTimeout(timer);
72
- timers.delete(sessionID);
73
- }
74
-
75
- function scheduleTimeout(sessionID, original, agent) {
76
- clearTimer(sessionID);
77
- if (options.timeout_seconds <= 0 || !hasMapping(original)) return;
78
- timers.set(
79
- sessionID,
80
- setTimeout(async () => {
81
- timers.delete(sessionID);
82
- if (retrying.has(sessionID) || !timedOriginal(original)) return;
83
- retrying.add(sessionID);
84
- try {
85
- await abortCurrentSession(sessionID);
86
- await retryWithFallback(sessionID, original, agent, "timeout");
87
- } finally {
88
- retrying.delete(sessionID);
89
- }
90
- }, options.timeout_seconds * 1000),
91
- );
92
- }
93
-
94
61
  async function abortCurrentSession(sessionID) {
95
62
  const aborted = await abortSession(ctx.client, sessionID);
96
63
  if (aborted) selfAbortAt.set(sessionID, Date.now());
@@ -105,7 +72,6 @@ export function createMappedFallbackRouter(ctx, rawOptions) {
105
72
  store.setModelCooldown(original, reason, failedAt, cooldownUntil);
106
73
  const parts = await getReplayParts(ctx.client, ctx.directory, sessionID);
107
74
  if (parts.length === 0) return;
108
- clearTimer(sessionID);
109
75
  try {
110
76
  await new Promise((resolve) => setTimeout(resolve, POST_ABORT_DELAY_MS));
111
77
  await ctx.client.session.promptAsync({
@@ -152,7 +118,6 @@ export function createMappedFallbackRouter(ctx, rawOptions) {
152
118
  }
153
119
  if (!shouldFallbackFromError(failed, original)) return;
154
120
  retrying.add(sessionID);
155
- clearTimer(sessionID);
156
121
  try {
157
122
  await retryWithFallback(sessionID, original, agent, source);
158
123
  } finally {
@@ -180,7 +145,6 @@ export function createMappedFallbackRouter(ctx, rawOptions) {
180
145
  }
181
146
  if (!shouldFallbackFromError(failed, original)) return;
182
147
  retrying.add(sessionID);
183
- clearTimer(sessionID);
184
148
  try {
185
149
  await abortCurrentSession(sessionID);
186
150
  await retryWithFallback(sessionID, original, agent, "session.status");
@@ -212,8 +176,6 @@ export function createMappedFallbackRouter(ctx, rawOptions) {
212
176
  const model = modelObject(target);
213
177
  if (model && output.message) output.message.model = model;
214
178
  await toastRouteChange(sessionID, requested, target, original);
215
- if (hasMapping(target)) scheduleTimeout(sessionID, target, input.agent);
216
- else clearTimer(sessionID);
217
179
  },
218
180
 
219
181
  event: async ({ event }) => {
@@ -224,7 +186,6 @@ export function createMappedFallbackRouter(ctx, rawOptions) {
224
186
  retrying.delete(id);
225
187
  activeOriginals.delete(id);
226
188
  activeTargets.delete(id);
227
- clearTimer(id);
228
189
  }
229
190
  return;
230
191
  }
@@ -245,11 +206,8 @@ export function createMappedFallbackRouter(ctx, rawOptions) {
245
206
  if (event.type === "message.updated") {
246
207
  const info = props?.info;
247
208
  if (info?.role !== "assistant") return;
209
+ if (!info?.error) return;
248
210
  const sessionID = info?.sessionID;
249
- if (!info?.error) {
250
- if (sessionID) clearTimer(sessionID);
251
- return;
252
- }
253
211
  const model =
254
212
  info?.model ??
255
213
  (typeof info?.providerID === "string" && typeof info?.modelID === "string"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@renjfk/opencode-model-fallback",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "Mapped model fallback router for OpenCode. Routes retryable model failures to configured fallback models and recovers after cooldown.",
5
5
  "keywords": [
6
6
  "fallback",
@@ -39,6 +39,6 @@
39
39
  "devDependencies": {
40
40
  "oxfmt": "0.50.0",
41
41
  "oxlint": "1.65.0",
42
- "vitest": "latest"
42
+ "vitest": "4.1.6"
43
43
  }
44
44
  }