@smart-coders-hq/opencode-model-fallback 1.2.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 -132
- package/dist/index.js +104 -38
- package/dist/src/logging/logger.d.ts +1 -0
- package/dist/src/logging/logger.d.ts.map +1 -1
- package/dist/src/plugin.d.ts +1 -0
- package/dist/src/plugin.d.ts.map +1 -1
- package/dist/src/replay/orchestrator.d.ts +1 -0
- package/dist/src/replay/orchestrator.d.ts.map +1 -1
- package/dist/src/state/model-health.d.ts.map +1 -1
- package/dist/src/state/session-state.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
|
|
70
|
+
Most important knobs:
|
|
99
71
|
|
|
100
|
-
- `
|
|
101
|
-
- `
|
|
102
|
-
- `
|
|
103
|
-
- `
|
|
104
|
-
- `
|
|
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
|
|
105
78
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
Configure different fallback chains for different agents using the agent name as the key. The `"*"` wildcard is used for any agent without a specific entry.
|
|
79
|
+
Per-agent example:
|
|
109
80
|
|
|
110
81
|
```json
|
|
111
82
|
{
|
|
@@ -130,112 +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:**
|
|
151
|
-
|
|
152
|
-
```json
|
|
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.
|
|
104
|
+
If you still have `rate-limit-fallback.json`, it is discovered and auto-migrated on load.
|
|
162
105
|
|
|
163
|
-
## `/fallback-status`
|
|
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
|
-
## Health state machine
|
|
180
|
-
|
|
181
|
-
```
|
|
182
|
-
healthy ──[rate limit detected]──→ rate_limited
|
|
183
|
-
rate_limited ──[cooldownMs elapsed]──→ cooldown
|
|
184
|
-
cooldown ──[retryOriginalAfterMs elapsed]──→ healthy
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
- **healthy** — model is usable; preferred for fallback selection
|
|
188
|
-
- **rate_limited** — recently hit a limit; skipped when walking fallback chain
|
|
189
|
-
- **cooldown** — cooling off; used as last resort if no healthy model is available
|
|
190
|
-
- State transitions are checked every 30 seconds via a background timer
|
|
191
|
-
- 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.
|
|
192
121
|
|
|
193
122
|
## Troubleshooting
|
|
194
123
|
|
|
195
|
-
**
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
**"
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
**"all fallback models exhausted"**
|
|
202
|
-
All configured fallback models are currently rate-limited. Wait for `cooldownMs` to elapse or add more models to the chain.
|
|
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`
|
|
203
129
|
|
|
204
|
-
|
|
205
|
-
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.
|
|
206
|
-
|
|
207
|
-
**Check the logs:**
|
|
130
|
+
Check logs with:
|
|
208
131
|
|
|
209
132
|
```bash
|
|
210
133
|
tail -f ~/.local/share/opencode/logs/model-fallback.log | jq .
|
|
211
134
|
```
|
|
212
135
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
To see the full event stream (including `event.received` and `retry.nomatch`), set `"logLevel": "debug"` in your config and restart OpenCode.
|
|
216
|
-
|
|
217
|
-
## Release automation
|
|
218
|
-
|
|
219
|
-
- Uses **Conventional Commits** + `semantic-release` for automated versioning/changelog/release notes
|
|
220
|
-
- CI runs lint, tests, type check, and build on every push/PR via `.github/workflows/ci.yml`
|
|
221
|
-
- Release workflow runs on `main` after successful CI via `.github/workflows/release.yml`
|
|
222
|
-
- Published as `@smart-coders-hq/opencode-model-fallback`
|
|
223
|
-
- To publish to npm, set repository secret `NPM_TOKEN`
|
|
136
|
+
For more event detail, set `"logLevel": "debug"` and restart OpenCode.
|
|
224
137
|
|
|
225
138
|
## Development
|
|
226
139
|
|
|
227
140
|
```bash
|
|
228
141
|
bun install
|
|
229
|
-
bun run lint
|
|
230
|
-
bun test
|
|
231
|
-
bunx tsc --noEmit
|
|
232
|
-
bun run build
|
|
142
|
+
bun run lint
|
|
143
|
+
bun test
|
|
144
|
+
bunx tsc --noEmit
|
|
145
|
+
bun run build
|
|
233
146
|
```
|
|
234
147
|
|
|
235
|
-
|
|
148
|
+
Local plugin config:
|
|
236
149
|
|
|
237
150
|
```jsonc
|
|
238
151
|
{ "plugin": ["file:///absolute/path/to/dist/index.js"] }
|
|
239
152
|
```
|
|
240
153
|
|
|
241
|
-
|
|
154
|
+
For local testing, place `model-fallback.json` in `.opencode/` in your project directory.
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/plugin.ts
|
|
2
|
-
import {
|
|
2
|
+
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
3
3
|
import { homedir as homedir5 } from "os";
|
|
4
4
|
import { dirname as dirname2, join as join4 } from "path";
|
|
5
5
|
|
|
@@ -3305,7 +3305,7 @@ function labelModel(key) {
|
|
|
3305
3305
|
}
|
|
3306
3306
|
|
|
3307
3307
|
// src/logging/logger.ts
|
|
3308
|
-
import { appendFileSync,
|
|
3308
|
+
import { appendFileSync, mkdirSync, writeFileSync } from "fs";
|
|
3309
3309
|
import { dirname } from "path";
|
|
3310
3310
|
|
|
3311
3311
|
class Logger {
|
|
@@ -3314,6 +3314,7 @@ class Logger {
|
|
|
3314
3314
|
enabled;
|
|
3315
3315
|
minLevel;
|
|
3316
3316
|
dirCreated = false;
|
|
3317
|
+
fileErrorNotified = false;
|
|
3317
3318
|
constructor(client, logPath, enabled, minLevel = "info") {
|
|
3318
3319
|
this.client = client;
|
|
3319
3320
|
this.logPath = logPath;
|
|
@@ -3321,18 +3322,19 @@ class Logger {
|
|
|
3321
3322
|
this.minLevel = minLevel;
|
|
3322
3323
|
}
|
|
3323
3324
|
log(level, event, fields = {}) {
|
|
3325
|
+
const sanitizedFields = sanitizeFields(fields);
|
|
3324
3326
|
const entry = {
|
|
3325
3327
|
ts: new Date().toISOString(),
|
|
3326
3328
|
level,
|
|
3327
3329
|
event,
|
|
3328
|
-
...
|
|
3330
|
+
...sanitizedFields
|
|
3329
3331
|
};
|
|
3330
3332
|
const shouldWrite = this.enabled && (this.minLevel === "debug" || level !== "debug");
|
|
3331
3333
|
if (shouldWrite) {
|
|
3332
3334
|
this.writeToFile(entry);
|
|
3333
3335
|
}
|
|
3334
3336
|
if (level !== "debug") {
|
|
3335
|
-
const message = `[model-fallback] ${event}${Object.keys(
|
|
3337
|
+
const message = `[model-fallback] ${event}${Object.keys(sanitizedFields).length ? " " + JSON.stringify(sanitizedFields) : ""}`;
|
|
3336
3338
|
this.client.app.log({
|
|
3337
3339
|
body: { service: "model-fallback", level, message }
|
|
3338
3340
|
}).catch(() => {});
|
|
@@ -3356,13 +3358,65 @@ class Logger {
|
|
|
3356
3358
|
mkdirSync(dirname(this.logPath), { recursive: true, mode: 448 });
|
|
3357
3359
|
this.dirCreated = true;
|
|
3358
3360
|
}
|
|
3359
|
-
|
|
3360
|
-
writeFileSync(this.logPath, "", { mode: 384 });
|
|
3361
|
-
}
|
|
3361
|
+
try {
|
|
3362
|
+
writeFileSync(this.logPath, "", { mode: 384, flag: "ax" });
|
|
3363
|
+
} catch {}
|
|
3362
3364
|
appendFileSync(this.logPath, JSON.stringify(entry) + `
|
|
3363
3365
|
`, "utf-8");
|
|
3364
|
-
} catch {
|
|
3366
|
+
} catch (err) {
|
|
3367
|
+
if (!this.fileErrorNotified) {
|
|
3368
|
+
this.fileErrorNotified = true;
|
|
3369
|
+
const message = `[model-fallback] logging.file.write.failed ${JSON.stringify({
|
|
3370
|
+
logPath: this.logPath,
|
|
3371
|
+
error: summarizeError(err)
|
|
3372
|
+
})}`;
|
|
3373
|
+
this.client.app.log({
|
|
3374
|
+
body: { service: "model-fallback", level: "warn", message }
|
|
3375
|
+
}).catch(() => {});
|
|
3376
|
+
}
|
|
3377
|
+
}
|
|
3378
|
+
}
|
|
3379
|
+
}
|
|
3380
|
+
function sanitizeFields(fields) {
|
|
3381
|
+
const out = {};
|
|
3382
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
3383
|
+
out[key] = sanitizeValue(key, value);
|
|
3384
|
+
}
|
|
3385
|
+
return out;
|
|
3386
|
+
}
|
|
3387
|
+
function sanitizeValue(key, value) {
|
|
3388
|
+
if (value === null || value === undefined)
|
|
3389
|
+
return value;
|
|
3390
|
+
if (isSensitiveKey(key)) {
|
|
3391
|
+
if (typeof value === "string") {
|
|
3392
|
+
return { redacted: true, length: value.length };
|
|
3393
|
+
}
|
|
3394
|
+
if (value instanceof Error) {
|
|
3395
|
+
return { redacted: true, type: value.name, code: getErrorCode(value) };
|
|
3396
|
+
}
|
|
3397
|
+
return { redacted: true, type: typeof value };
|
|
3398
|
+
}
|
|
3399
|
+
if (value instanceof Error) {
|
|
3400
|
+
return { name: value.name, message: value.message };
|
|
3401
|
+
}
|
|
3402
|
+
return value;
|
|
3403
|
+
}
|
|
3404
|
+
function isSensitiveKey(key) {
|
|
3405
|
+
return /(?:^|_)(message|prompt|content|parts|error|err|stack|body)(?:$|_)/i.test(key);
|
|
3406
|
+
}
|
|
3407
|
+
function getErrorCode(err) {
|
|
3408
|
+
const code = err.code;
|
|
3409
|
+
return typeof code === "string" ? code : undefined;
|
|
3410
|
+
}
|
|
3411
|
+
function summarizeError(err) {
|
|
3412
|
+
if (err && typeof err === "object") {
|
|
3413
|
+
const e = err;
|
|
3414
|
+
return {
|
|
3415
|
+
type: typeof e.name === "string" ? e.name : "Error",
|
|
3416
|
+
code: typeof e.code === "string" ? e.code : undefined
|
|
3417
|
+
};
|
|
3365
3418
|
}
|
|
3419
|
+
return { type: typeof err };
|
|
3366
3420
|
}
|
|
3367
3421
|
|
|
3368
3422
|
// src/resolution/agent-resolver.ts
|
|
@@ -3505,7 +3559,7 @@ async function attemptFallback(sessionId, reason, client, store, config, logger,
|
|
|
3505
3559
|
const result = await client.session.messages({ path: { id: sessionId } });
|
|
3506
3560
|
messageEntries = Array.isArray(result.data) ? result.data : [];
|
|
3507
3561
|
} catch (err) {
|
|
3508
|
-
logger.error("replay.messages.failed", { sessionId, err
|
|
3562
|
+
logger.error("replay.messages.failed", { sessionId, err });
|
|
3509
3563
|
return { success: false, error: "messages fetch failed" };
|
|
3510
3564
|
}
|
|
3511
3565
|
let lastUserEntry = null;
|
|
@@ -3575,7 +3629,7 @@ async function attemptFallback(sessionId, reason, client, store, config, logger,
|
|
|
3575
3629
|
return { success: false, error: "all fallback models exhausted" };
|
|
3576
3630
|
}
|
|
3577
3631
|
const currentModel = sessionState.currentModel;
|
|
3578
|
-
if (currentModel) {
|
|
3632
|
+
if (currentModel && shouldMarkRateLimited(reason)) {
|
|
3579
3633
|
store.health.markRateLimited(currentModel, config.defaults.cooldownMs, config.defaults.retryOriginalAfterMs);
|
|
3580
3634
|
}
|
|
3581
3635
|
sessionState.lastFallbackAt = Date.now();
|
|
@@ -3583,7 +3637,7 @@ async function attemptFallback(sessionId, reason, client, store, config, logger,
|
|
|
3583
3637
|
await client.session.abort({ path: { id: sessionId } });
|
|
3584
3638
|
logger.debug("replay.abort.ok", { sessionId });
|
|
3585
3639
|
} catch (err) {
|
|
3586
|
-
logger.error("replay.abort.failed", { sessionId, err
|
|
3640
|
+
logger.error("replay.abort.failed", { sessionId, err });
|
|
3587
3641
|
return { success: false, error: "abort failed" };
|
|
3588
3642
|
}
|
|
3589
3643
|
try {
|
|
@@ -3596,7 +3650,7 @@ async function attemptFallback(sessionId, reason, client, store, config, logger,
|
|
|
3596
3650
|
messageID: lastUserEntry.id
|
|
3597
3651
|
});
|
|
3598
3652
|
} catch (err) {
|
|
3599
|
-
logger.error("replay.revert.failed", { sessionId, err
|
|
3653
|
+
logger.error("replay.revert.failed", { sessionId, err });
|
|
3600
3654
|
return { success: false, error: "revert failed" };
|
|
3601
3655
|
}
|
|
3602
3656
|
const promptParts = convertPartsForPrompt(lastUserEntry.parts);
|
|
@@ -3619,7 +3673,7 @@ async function attemptFallback(sessionId, reason, client, store, config, logger,
|
|
|
3619
3673
|
logger.error("replay.prompt.failed", {
|
|
3620
3674
|
sessionId,
|
|
3621
3675
|
fallbackModel,
|
|
3622
|
-
err
|
|
3676
|
+
err
|
|
3623
3677
|
});
|
|
3624
3678
|
return { success: false, error: "prompt failed" };
|
|
3625
3679
|
}
|
|
@@ -3634,11 +3688,14 @@ async function attemptFallback(sessionId, reason, client, store, config, logger,
|
|
|
3634
3688
|
reason,
|
|
3635
3689
|
depth: newDepth
|
|
3636
3690
|
});
|
|
3637
|
-
return { success: true, fallbackModel };
|
|
3691
|
+
return { success: true, fallbackModel, fromModel: currentModel };
|
|
3638
3692
|
} finally {
|
|
3639
3693
|
store.sessions.releaseLock(sessionId);
|
|
3640
3694
|
}
|
|
3641
3695
|
}
|
|
3696
|
+
function shouldMarkRateLimited(reason) {
|
|
3697
|
+
return reason === "rate_limit" || reason === "quota_exceeded";
|
|
3698
|
+
}
|
|
3642
3699
|
function sanitizeParts(parts) {
|
|
3643
3700
|
if (!Array.isArray(parts))
|
|
3644
3701
|
return [];
|
|
@@ -3652,7 +3709,7 @@ class ModelHealthStore {
|
|
|
3652
3709
|
onTransition;
|
|
3653
3710
|
constructor(opts) {
|
|
3654
3711
|
this.onTransition = opts?.onTransition;
|
|
3655
|
-
this.timer = setInterval(() => this.tick(), 30000);
|
|
3712
|
+
this.timer = setInterval(() => this.tick(), 30000).unref();
|
|
3656
3713
|
}
|
|
3657
3714
|
get(modelKey2) {
|
|
3658
3715
|
return this.store.get(modelKey2) ?? this.newHealth(modelKey2);
|
|
@@ -3782,6 +3839,7 @@ class SessionStateStore {
|
|
|
3782
3839
|
agentName
|
|
3783
3840
|
};
|
|
3784
3841
|
state.currentModel = toModel;
|
|
3842
|
+
state.fallbackDepth++;
|
|
3785
3843
|
state.recoveryNotifiedForModel = null;
|
|
3786
3844
|
state.fallbackHistory.push(event);
|
|
3787
3845
|
if (agentName)
|
|
@@ -4026,22 +4084,25 @@ function getLastUserModelAndAgent(data) {
|
|
|
4026
4084
|
}
|
|
4027
4085
|
|
|
4028
4086
|
// src/plugin.ts
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4087
|
+
function resolveFallbackStatusCommandPath() {
|
|
4088
|
+
return join4(homedir5(), ".config", "opencode", "commands", "fallback-status.md");
|
|
4089
|
+
}
|
|
4090
|
+
function ensureFallbackStatusCommand(logger, cmdPath) {
|
|
4033
4091
|
try {
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
}
|
|
4039
|
-
} catch (err) {
|
|
4040
|
-
logger.warn("fallback-status.command.write.failed", {
|
|
4041
|
-
cmdPath,
|
|
4042
|
-
err: String(err)
|
|
4092
|
+
mkdirSync2(dirname2(cmdPath), { recursive: true, mode: 448 });
|
|
4093
|
+
writeFileSync2(cmdPath, `Call the fallback-status tool and display the full output.
|
|
4094
|
+
`, {
|
|
4095
|
+
flag: "wx"
|
|
4043
4096
|
});
|
|
4097
|
+
} catch (err) {
|
|
4098
|
+
if (err.code !== "EEXIST") {
|
|
4099
|
+
logger.warn("fallback-status.command.write.failed", { cmdPath, err });
|
|
4100
|
+
}
|
|
4044
4101
|
}
|
|
4102
|
+
}
|
|
4103
|
+
var createPlugin = async ({ client, directory }) => {
|
|
4104
|
+
const { config, path: configPath, warnings, migrated } = loadConfig(directory);
|
|
4105
|
+
const logger = new Logger(client, config.logPath, config.logging, config.logLevel);
|
|
4045
4106
|
logger.info("plugin.init", {
|
|
4046
4107
|
configPath,
|
|
4047
4108
|
enabled: config.enabled,
|
|
@@ -4049,17 +4110,19 @@ var createPlugin = async ({ client, directory }) => {
|
|
|
4049
4110
|
agentCount: Object.keys(config.agents).length
|
|
4050
4111
|
});
|
|
4051
4112
|
for (const w of warnings) {
|
|
4052
|
-
logger.warn("config.warning", {
|
|
4113
|
+
logger.warn("config.warning", { warning: w });
|
|
4053
4114
|
}
|
|
4054
4115
|
if (migrated) {
|
|
4055
4116
|
logger.info("config.migrated", {
|
|
4056
|
-
|
|
4117
|
+
note: "Auto-migrated from old rate-limit-fallback.json format"
|
|
4057
4118
|
});
|
|
4058
4119
|
}
|
|
4059
4120
|
if (!config.enabled) {
|
|
4060
4121
|
logger.info("plugin.disabled");
|
|
4061
4122
|
return {};
|
|
4062
4123
|
}
|
|
4124
|
+
const cmdPath = resolveFallbackStatusCommandPath();
|
|
4125
|
+
ensureFallbackStatusCommand(logger, cmdPath);
|
|
4063
4126
|
const store = new FallbackStore(config, logger);
|
|
4064
4127
|
const hooks = {
|
|
4065
4128
|
async event({ event }) {
|
|
@@ -4124,8 +4187,7 @@ async function handleEvent(event, client, store, config, logger, directory) {
|
|
|
4124
4187
|
if (config.defaults.fallbackOn.includes(category)) {
|
|
4125
4188
|
const result = await attemptFallback(sessionID, category, client, store, config, logger, directory);
|
|
4126
4189
|
if (result.success && result.fallbackModel) {
|
|
4127
|
-
|
|
4128
|
-
await notifyFallback(client, state.originalModel, result.fallbackModel, category);
|
|
4190
|
+
await notifyFallback(client, result.fromModel ?? null, result.fallbackModel, category);
|
|
4129
4191
|
}
|
|
4130
4192
|
}
|
|
4131
4193
|
}
|
|
@@ -4145,12 +4207,16 @@ async function handleEvent(event, client, store, config, logger, directory) {
|
|
|
4145
4207
|
}
|
|
4146
4208
|
async function handleRetry(sessionId, message, client, store, config, logger, directory) {
|
|
4147
4209
|
if (!matchesAnyPattern(message, config.patterns)) {
|
|
4148
|
-
logger.debug("retry.nomatch", { sessionId, message });
|
|
4210
|
+
logger.debug("retry.nomatch", { sessionId, messageLength: message.length });
|
|
4149
4211
|
return;
|
|
4150
4212
|
}
|
|
4151
4213
|
const category = classifyError(message);
|
|
4152
4214
|
if (!config.defaults.fallbackOn.includes(category)) {
|
|
4153
|
-
logger.debug("retry.ignored", {
|
|
4215
|
+
logger.debug("retry.ignored", {
|
|
4216
|
+
sessionId,
|
|
4217
|
+
category,
|
|
4218
|
+
messageLength: message.length
|
|
4219
|
+
});
|
|
4154
4220
|
return;
|
|
4155
4221
|
}
|
|
4156
4222
|
const sessionState = store.sessions.get(sessionId);
|
|
@@ -4178,15 +4244,14 @@ async function handleRetry(sessionId, message, client, store, config, logger, di
|
|
|
4178
4244
|
}
|
|
4179
4245
|
logger.info("retry.detected", {
|
|
4180
4246
|
sessionId,
|
|
4181
|
-
message,
|
|
4247
|
+
messageLength: message.length,
|
|
4182
4248
|
category,
|
|
4183
4249
|
agentName: sessionState.agentName,
|
|
4184
4250
|
agentFile: sessionState.agentFile
|
|
4185
4251
|
});
|
|
4186
4252
|
const result = await attemptFallback(sessionId, category, client, store, config, logger, directory);
|
|
4187
4253
|
if (result.success && result.fallbackModel) {
|
|
4188
|
-
|
|
4189
|
-
await notifyFallback(client, state.originalModel, result.fallbackModel, category);
|
|
4254
|
+
await notifyFallback(client, result.fromModel ?? null, result.fallbackModel, category);
|
|
4190
4255
|
}
|
|
4191
4256
|
}
|
|
4192
4257
|
async function handleIdle(sessionId, client, store, _config, logger) {
|
|
@@ -4206,7 +4271,8 @@ async function handleIdle(sessionId, client, store, _config, logger) {
|
|
|
4206
4271
|
return;
|
|
4207
4272
|
logger.info("recovery.available", {
|
|
4208
4273
|
sessionId,
|
|
4209
|
-
originalModel: state.originalModel
|
|
4274
|
+
originalModel: state.originalModel,
|
|
4275
|
+
currentModel: state.currentModel
|
|
4210
4276
|
});
|
|
4211
4277
|
await notifyRecovery(client, state.originalModel);
|
|
4212
4278
|
state.recoveryNotifiedForModel = state.originalModel;
|
|
@@ -13,6 +13,7 @@ export declare class Logger {
|
|
|
13
13
|
private enabled;
|
|
14
14
|
private minLevel;
|
|
15
15
|
private dirCreated;
|
|
16
|
+
private fileErrorNotified;
|
|
16
17
|
constructor(client: Client, logPath: string, enabled: boolean, minLevel?: "debug" | "info");
|
|
17
18
|
log(level: LogLevel, event: string, fields?: Record<string, unknown>): void;
|
|
18
19
|
info(event: string, fields?: Record<string, unknown>): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../../src/logging/logger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAIvD,KAAK,MAAM,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;AAEpC,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAE3D,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,QAAQ,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,qBAAa,MAAM;IACjB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,QAAQ,CAAmB;IACnC,OAAO,CAAC,UAAU,CAAS;
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../../src/logging/logger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAIvD,KAAK,MAAM,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;AAEpC,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAE3D,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,QAAQ,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,qBAAa,MAAM;IACjB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,QAAQ,CAAmB;IACnC,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,iBAAiB,CAAS;gBAGhC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,OAAO,EAChB,QAAQ,GAAE,OAAO,GAAG,MAAe;IAQrC,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,IAAI;IA2B/E,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAI3D,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAI3D,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAI5D,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAI5D,OAAO,CAAC,WAAW;CA8BpB"}
|
package/dist/src/plugin.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { Event } from "@opencode-ai/sdk";
|
|
|
3
3
|
import { loadConfig } from "./config/loader.js";
|
|
4
4
|
import { Logger } from "./logging/logger.js";
|
|
5
5
|
import { FallbackStore } from "./state/store.js";
|
|
6
|
+
export declare function ensureFallbackStatusCommand(logger: Logger, cmdPath: string): void;
|
|
6
7
|
export declare const createPlugin: Plugin;
|
|
7
8
|
export declare function handleEvent(event: Event, client: Parameters<Plugin>[0]["client"], store: FallbackStore, config: ReturnType<typeof loadConfig>["config"], logger: Logger, directory: string): Promise<void>;
|
|
8
9
|
export declare function handleIdle(sessionId: string, client: Parameters<Plugin>[0]["client"], store: FallbackStore, _config: ReturnType<typeof loadConfig>["config"], logger: Logger): Promise<void>;
|
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;
|
|
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"}
|
|
@@ -6,6 +6,7 @@ type Client = PluginInput["client"];
|
|
|
6
6
|
export interface ReplayResult {
|
|
7
7
|
success: boolean;
|
|
8
8
|
fallbackModel?: ModelKey;
|
|
9
|
+
fromModel?: ModelKey | null;
|
|
9
10
|
error?: string;
|
|
10
11
|
}
|
|
11
12
|
export declare function attemptFallback(sessionId: string, reason: ErrorCategory, client: Client, store: FallbackStore, config: PluginConfig, logger: Logger, directory: string): Promise<ReplayResult>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../../src/replay/orchestrator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAGvD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAGnD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGzE,KAAK,MAAM,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;AAEpC,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,QAAQ,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,eAAe,CACnC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,aAAa,EACrB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,aAAa,EACpB,MAAM,EAAE,YAAY,EACpB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,YAAY,CAAC,CAwOvB"}
|
|
1
|
+
{"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../../src/replay/orchestrator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAGvD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAGnD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGzE,KAAK,MAAM,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;AAEpC,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,QAAQ,CAAC;IACzB,SAAS,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,eAAe,CACnC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,aAAa,EACrB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,aAAa,EACpB,MAAM,EAAE,YAAY,EACpB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,YAAY,CAAC,CAwOvB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-health.d.ts","sourceRoot":"","sources":["../../../src/state/model-health.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEtE,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,KAAK,CAAoC;IACjD,OAAO,CAAC,KAAK,CAA+C;IAC5D,OAAO,CAAC,YAAY,CAAC,CAAmE;gBAE5E,IAAI,CAAC,EAAE;QACjB,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,WAAW,KAAK,IAAI,CAAC;KACjF;
|
|
1
|
+
{"version":3,"file":"model-health.d.ts","sourceRoot":"","sources":["../../../src/state/model-health.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEtE,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,KAAK,CAAoC;IACjD,OAAO,CAAC,KAAK,CAA+C;IAC5D,OAAO,CAAC,YAAY,CAAC,CAAmE;gBAE5E,IAAI,CAAC,EAAE;QACjB,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,WAAW,KAAK,IAAI,CAAC;KACjF;IAOD,GAAG,CAAC,QAAQ,EAAE,QAAQ,GAAG,WAAW;IAIpC,eAAe,CAAC,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM,GAAG,IAAI;IAc3F,QAAQ,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO;IAKrC,WAAW,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM;IAQvC,MAAM,IAAI,WAAW,EAAE;IAIvB,OAAO,IAAI,IAAI;IAOf,OAAO,CAAC,IAAI;IA4BZ,OAAO,CAAC,SAAS;CAUlB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-state.d.ts","sourceRoot":"","sources":["../../../src/state/session-state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAiB,QAAQ,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEhG,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,KAAK,CAA2C;IAExD,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,oBAAoB;IAS5C,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IASvC,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAKpC,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,SAAQ,GAAG,OAAO;IAM7D,cAAc,CACZ,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,QAAQ,EACnB,OAAO,EAAE,QAAQ,EACjB,MAAM,EAAE,aAAa,EACrB,SAAS,EAAE,MAAM,GAAG,IAAI,GACvB,IAAI;IAmBP,wBAAwB,CACtB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,QAAQ,EACnB,OAAO,EAAE,QAAQ,EACjB,SAAS,EAAE,MAAM,GAAG,IAAI,GACvB,IAAI;
|
|
1
|
+
{"version":3,"file":"session-state.d.ts","sourceRoot":"","sources":["../../../src/state/session-state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAiB,QAAQ,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEhG,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,KAAK,CAA2C;IAExD,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,oBAAoB;IAS5C,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IASvC,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAKpC,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,SAAQ,GAAG,OAAO;IAM7D,cAAc,CACZ,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,QAAQ,EACnB,OAAO,EAAE,QAAQ,EACjB,MAAM,EAAE,aAAa,EACrB,SAAS,EAAE,MAAM,GAAG,IAAI,GACvB,IAAI;IAmBP,wBAAwB,CACtB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,QAAQ,EACnB,OAAO,EAAE,QAAQ,EACjB,SAAS,EAAE,MAAM,GAAG,IAAI,GACvB,IAAI;IAmBP,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,GAAG,IAAI;IAQ1D,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAKxD,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAKxD,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IASrC,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAI/B,MAAM,IAAI,oBAAoB,EAAE;IAIhC,OAAO,CAAC,QAAQ;CAcjB"}
|
package/package.json
CHANGED
package/plugin.json
CHANGED