@smart-coders-hq/opencode-model-fallback 1.3.0 → 1.3.1
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 +45 -140
- package/dist/index.js +2 -1
- package/dist/src/plugin.d.ts.map +1 -1
- package/package.json +1 -1
- package/plugin.json +1 -1
package/README.md
CHANGED
|
@@ -1,29 +1,27 @@
|
|
|
1
1
|
# opencode-model-fallback
|
|
2
2
|
|
|
3
|
-
OpenCode plugin that
|
|
3
|
+
OpenCode plugin that automatically switches to the next model in a configured fallback chain when the current one hits a rate limit, quota error, timeout, overload, or configured 5xx path.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Features
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
- Preemptive redirect via `chat.message` when a model is already known to be rate-limited
|
|
8
|
+
- Reactive fallback from both `session.status` retry events and `session.error` API errors
|
|
9
|
+
- Per-agent ordered fallback chains with `"*"` wildcard support
|
|
10
|
+
- Global model health tracking with automatic recovery windows
|
|
11
|
+
- `/fallback-status` slash command for session depth, history, and model health
|
|
12
|
+
- Structured logs with provider free-form error text redacted
|
|
12
13
|
|
|
13
14
|
## Installation
|
|
14
15
|
|
|
15
|
-
Add
|
|
16
|
+
Add the plugin to `~/.config/opencode/opencode.jsonc`:
|
|
16
17
|
|
|
17
18
|
```jsonc
|
|
18
19
|
{
|
|
19
|
-
"plugin": [
|
|
20
|
-
// ... existing plugins
|
|
21
|
-
"@smart-coders-hq/opencode-model-fallback",
|
|
22
|
-
],
|
|
20
|
+
"plugin": ["@smart-coders-hq/opencode-model-fallback"],
|
|
23
21
|
}
|
|
24
22
|
```
|
|
25
23
|
|
|
26
|
-
|
|
24
|
+
For local development:
|
|
27
25
|
|
|
28
26
|
```jsonc
|
|
29
27
|
{
|
|
@@ -31,14 +29,14 @@ Or load locally during development:
|
|
|
31
29
|
}
|
|
32
30
|
```
|
|
33
31
|
|
|
34
|
-
Then create a config file (see [Configuration](#configuration)).
|
|
35
|
-
|
|
36
32
|
## Configuration
|
|
37
33
|
|
|
38
|
-
|
|
34
|
+
Create `model-fallback.json` in either:
|
|
35
|
+
|
|
36
|
+
- `.opencode/model-fallback.json`
|
|
37
|
+
- `~/.config/opencode/model-fallback.json`
|
|
39
38
|
|
|
40
|
-
|
|
41
|
-
- `~/.config/opencode/model-fallback.json` — global
|
|
39
|
+
Minimal example:
|
|
42
40
|
|
|
43
41
|
```json
|
|
44
42
|
{
|
|
@@ -63,49 +61,22 @@ Place `model-fallback.json` at either:
|
|
|
63
61
|
]
|
|
64
62
|
}
|
|
65
63
|
},
|
|
66
|
-
"patterns": [
|
|
67
|
-
"rate limit",
|
|
68
|
-
"usage limit",
|
|
69
|
-
"too many requests",
|
|
70
|
-
"quota exceeded",
|
|
71
|
-
"overloaded",
|
|
72
|
-
"capacity exceeded",
|
|
73
|
-
"credits exhausted",
|
|
74
|
-
"billing limit",
|
|
75
|
-
"429"
|
|
76
|
-
],
|
|
77
64
|
"logging": true,
|
|
78
65
|
"logLevel": "info",
|
|
79
66
|
"logPath": "~/.local/share/opencode/logs/model-fallback.log"
|
|
80
67
|
}
|
|
81
68
|
```
|
|
82
69
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
| Field | Type | Default | Description |
|
|
86
|
-
| ------------------------------- | -------- | ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
|
87
|
-
| `enabled` | boolean | `true` | Enable/disable the plugin |
|
|
88
|
-
| `defaults.fallbackOn` | string[] | all categories | Error categories that trigger fallback |
|
|
89
|
-
| `defaults.cooldownMs` | number | `300000` (5 min) | How long before a rate-limited model enters cooldown. Min: 10000 |
|
|
90
|
-
| `defaults.retryOriginalAfterMs` | number | `900000` (15 min) | How long before a cooldown model is considered healthy again. Min: 10000 |
|
|
91
|
-
| `defaults.maxFallbackDepth` | number | `3` | Maximum number of fallbacks per session. Max: 10 |
|
|
92
|
-
| `agents` | object | `{"*": {}}` | Per-agent fallback chains (see below) |
|
|
93
|
-
| `patterns` | string[] | see defaults | Case-insensitive substrings to match in retry messages |
|
|
94
|
-
| `logging` | boolean | `true` | Write structured logs to `logPath` |
|
|
95
|
-
| `logLevel` | string | `"info"` | Minimum log level written to file: `"info"` suppresses debug noise, `"debug"` logs every event (useful for incident investigation) |
|
|
96
|
-
| `logPath` | string | `~/.local/share/opencode/logs/model-fallback.log` | Log file path (must be within `$HOME`) |
|
|
97
|
-
|
|
98
|
-
### Error categories
|
|
99
|
-
|
|
100
|
-
- `rate_limit` — 429, "rate limit", "too many requests", "usage limit"
|
|
101
|
-
- `quota_exceeded` — "quota exceeded", "credits exhausted", "billing limit"
|
|
102
|
-
- `overloaded` — "overloaded", "capacity exceeded"
|
|
103
|
-
- `timeout` — "timeout", "timed out"
|
|
104
|
-
- `5xx` — 500/502/503/504, "internal server error", "bad gateway"
|
|
70
|
+
Most important knobs:
|
|
105
71
|
|
|
106
|
-
|
|
72
|
+
- `defaults.fallbackOn` - which error categories trigger fallback
|
|
73
|
+
- `defaults.cooldownMs` - how long a rate-limited model stays unavailable
|
|
74
|
+
- `defaults.retryOriginalAfterMs` - when a cooled-down model becomes healthy again
|
|
75
|
+
- `defaults.maxFallbackDepth` - max cascading fallbacks within one message
|
|
76
|
+
- `agents` - ordered fallback chains per agent
|
|
77
|
+
- `logging`, `logLevel`, `logPath` - structured file logging controls
|
|
107
78
|
|
|
108
|
-
|
|
79
|
+
Per-agent example:
|
|
109
80
|
|
|
110
81
|
```json
|
|
111
82
|
{
|
|
@@ -130,120 +101,54 @@ Configure different fallback chains for different agents using the agent name as
|
|
|
130
101
|
}
|
|
131
102
|
```
|
|
132
103
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
## Migrating from opencode-rate-limit-fallback
|
|
136
|
-
|
|
137
|
-
If you have an existing `rate-limit-fallback.json` config, the plugin auto-migrates it on load — no manual steps needed.
|
|
138
|
-
|
|
139
|
-
**Old format:**
|
|
140
|
-
|
|
141
|
-
```json
|
|
142
|
-
{
|
|
143
|
-
"fallbackModel": "anthropic/claude-opus-4-5",
|
|
144
|
-
"cooldownMs": 300000,
|
|
145
|
-
"patterns": ["rate limit"],
|
|
146
|
-
"logging": true
|
|
147
|
-
}
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
**Automatically converted to:**
|
|
104
|
+
If you still have `rate-limit-fallback.json`, it is discovered and auto-migrated on load.
|
|
151
105
|
|
|
152
|
-
|
|
153
|
-
{
|
|
154
|
-
"agents": { "*": { "fallbackModels": ["anthropic/claude-opus-4-5"] } },
|
|
155
|
-
"defaults": { "cooldownMs": 300000 },
|
|
156
|
-
"patterns": ["rate limit"],
|
|
157
|
-
"logging": true
|
|
158
|
-
}
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
The plugin checks both `rate-limit-fallback.json` and `model-fallback.json` — old configs are found and migrated automatically.
|
|
162
|
-
|
|
163
|
-
## `/fallback-status` command
|
|
106
|
+
## `/fallback-status`
|
|
164
107
|
|
|
165
108
|
Run `/fallback-status` in any OpenCode session to see:
|
|
166
109
|
|
|
167
|
-
-
|
|
168
|
-
-
|
|
169
|
-
-
|
|
110
|
+
- current session fallback depth and history
|
|
111
|
+
- tracked model health and time remaining
|
|
112
|
+
- active agent name
|
|
170
113
|
|
|
171
|
-
|
|
114
|
+
Verbose mode adds token and cost breakdown by fallback period:
|
|
172
115
|
|
|
173
|
-
```
|
|
116
|
+
```text
|
|
174
117
|
/fallback-status verbose:true
|
|
175
118
|
```
|
|
176
119
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
When enabled, the plugin auto-creates `~/.config/opencode/commands/fallback-status.md` at startup so the slash command is available without manual setup.
|
|
180
|
-
|
|
181
|
-
## Health state machine
|
|
182
|
-
|
|
183
|
-
```
|
|
184
|
-
healthy ──[rate limit detected]──→ rate_limited
|
|
185
|
-
rate_limited ──[cooldownMs elapsed]──→ cooldown
|
|
186
|
-
cooldown ──[retryOriginalAfterMs elapsed]──→ healthy
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
- **healthy** — model is usable; preferred for fallback selection
|
|
190
|
-
- **rate_limited** — recently hit a limit; skipped when walking fallback chain
|
|
191
|
-
- **cooldown** — cooling off; used as last resort if no healthy model is available
|
|
192
|
-
- State transitions are checked every 30 seconds via a background timer (the timer is unref'ed so it does not keep the process alive)
|
|
193
|
-
- When the original model recovers to healthy, a toast appears on the next `session.idle`
|
|
120
|
+
When enabled, the plugin auto-creates `~/.config/opencode/commands/fallback-status.md` at startup.
|
|
194
121
|
|
|
195
122
|
## Troubleshooting
|
|
196
123
|
|
|
197
|
-
**
|
|
198
|
-
|
|
124
|
+
- **No toast appears** - toasts require an active OpenCode TUI session; headless/API runs still log events
|
|
125
|
+
- **`/fallback-status` is missing** - verify `~/.config/opencode/commands/` is writable and check logs for `fallback-status.command.write.failed`
|
|
126
|
+
- **"no fallback chain configured"** - add `agents["*"]` or an entry for the active agent with at least one `fallbackModels` value
|
|
127
|
+
- **"all fallback models exhausted"** - every configured fallback is currently rate-limited; wait for recovery or add more models
|
|
128
|
+
- **"max fallback depth reached"** - all models in the chain failed within one message; start a new session or raise `maxFallbackDepth`
|
|
199
129
|
|
|
200
|
-
|
|
201
|
-
The plugin writes `~/.config/opencode/commands/fallback-status.md` on startup. If the command does not appear, verify the directory is writable and check for `fallback-status.command.write.failed` in OpenCode logs.
|
|
202
|
-
|
|
203
|
-
**"no fallback chain configured"**
|
|
204
|
-
Your `model-fallback.json` has no `agents["*"].fallbackModels` (or no entry for the active agent). Add at least a wildcard entry with one model.
|
|
205
|
-
|
|
206
|
-
**"all fallback models exhausted"**
|
|
207
|
-
All configured fallback models are currently rate-limited. Wait for `cooldownMs` to elapse or add more models to the chain.
|
|
208
|
-
|
|
209
|
-
**"max fallback depth reached"**
|
|
210
|
-
The session has hit `maxFallbackDepth` cascading fallbacks within a single message (all models failing in sequence). Depth resets automatically when the TUI reverts to the original model between messages, so this typically indicates all configured models are rate-limited simultaneously. Start a new session or increase `maxFallbackDepth` in config.
|
|
211
|
-
|
|
212
|
-
**Check the logs:**
|
|
130
|
+
Check logs with:
|
|
213
131
|
|
|
214
132
|
```bash
|
|
215
133
|
tail -f ~/.local/share/opencode/logs/model-fallback.log | jq .
|
|
216
134
|
```
|
|
217
135
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
To see the full event stream (including `event.received` and `retry.nomatch`), set `"logLevel": "debug"` in your config and restart OpenCode.
|
|
221
|
-
|
|
222
|
-
For safety, free-form provider error text is redacted in plugin logs; use category/model/session fields for diagnosis.
|
|
223
|
-
|
|
224
|
-
## Release automation
|
|
225
|
-
|
|
226
|
-
- Uses **Conventional Commits** + `semantic-release` for automated versioning/changelog/release notes
|
|
227
|
-
- CI runs lint, tests, type check, and build on every push/PR via `.github/workflows/ci.yml`
|
|
228
|
-
- Trusted release gate runs on pushes to `main` via `.github/workflows/release-gate.yml`
|
|
229
|
-
- Release workflow (`.github/workflows/release.yml`) runs on successful `Release Gate` completion (`workflow_run`), only for `push` events on `main`, and only when `head_sha` matches `github.sha`
|
|
230
|
-
- Published as `@smart-coders-hq/opencode-model-fallback`
|
|
231
|
-
- To publish to npm, set repository secret `NPM_TOKEN`
|
|
136
|
+
For more event detail, set `"logLevel": "debug"` and restart OpenCode.
|
|
232
137
|
|
|
233
138
|
## Development
|
|
234
139
|
|
|
235
140
|
```bash
|
|
236
141
|
bun install
|
|
237
|
-
bun run lint
|
|
238
|
-
bun test
|
|
239
|
-
bunx tsc --noEmit
|
|
240
|
-
bun run build
|
|
142
|
+
bun run lint
|
|
143
|
+
bun test
|
|
144
|
+
bunx tsc --noEmit
|
|
145
|
+
bun run build
|
|
241
146
|
```
|
|
242
147
|
|
|
243
|
-
|
|
148
|
+
Local plugin config:
|
|
244
149
|
|
|
245
150
|
```jsonc
|
|
246
151
|
{ "plugin": ["file:///absolute/path/to/dist/index.js"] }
|
|
247
152
|
```
|
|
248
153
|
|
|
249
|
-
|
|
154
|
+
For local testing, place `model-fallback.json` in `.opencode/` in your project directory.
|
package/dist/index.js
CHANGED
|
@@ -4271,7 +4271,8 @@ async function handleIdle(sessionId, client, store, _config, logger) {
|
|
|
4271
4271
|
return;
|
|
4272
4272
|
logger.info("recovery.available", {
|
|
4273
4273
|
sessionId,
|
|
4274
|
-
originalModel: state.originalModel
|
|
4274
|
+
originalModel: state.originalModel,
|
|
4275
|
+
currentModel: state.currentModel
|
|
4275
4276
|
});
|
|
4276
4277
|
await notifyRecovery(client, state.originalModel);
|
|
4277
4278
|
state.recoveryNotifiedForModel = state.originalModel;
|
package/dist/src/plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAS,MAAM,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAK9C,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAIhD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAG7C,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAQjD,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAWjF;AAED,eAAO,MAAM,YAAY,EAAE,MA2F1B,CAAC;AAEF,wBAAsB,WAAW,CAC/B,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EACvC,KAAK,EAAE,aAAa,EACpB,MAAM,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC,QAAQ,CAAC,EAC/C,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAwDf;AAwFD,wBAAsB,UAAU,CAC9B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EACvC,KAAK,EAAE,aAAa,EACpB,OAAO,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC,QAAQ,CAAC,EAChD,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAS,MAAM,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAK9C,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAIhD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAG7C,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAQjD,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAWjF;AAED,eAAO,MAAM,YAAY,EAAE,MA2F1B,CAAC;AAEF,wBAAsB,WAAW,CAC/B,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EACvC,KAAK,EAAE,aAAa,EACpB,MAAM,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC,QAAQ,CAAC,EAC/C,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAwDf;AAwFD,wBAAsB,UAAU,CAC9B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EACvC,KAAK,EAAE,aAAa,EACpB,OAAO,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC,QAAQ,CAAC,EAChD,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAwBf"}
|
package/package.json
CHANGED
package/plugin.json
CHANGED