@season179/pi-model-fallback 26.7.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Season Saw
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # @season179/pi-model-fallback
2
+
3
+ Automatic model failover for Pi. When the selected model fails with a persistent provider error (rate limit, overload, 5xx, network), Pi switches to the next model in a user-defined fallback chain and resumes work automatically.
4
+
5
+ Main model settings (`settings.json`) are configured nowhere in this extension — the fallback chain lives in its own config file.
6
+
7
+ ## What It Does
8
+
9
+ - Triggers only after Pi's built-in same-model retries are exhausted.
10
+ - Classifies errors by HTTP status first, error-message patterns as backstop.
11
+ - Falls back on rate limit (429), overload (529), 5xx, and network errors.
12
+ - Never falls back on user aborts, context-overflow, or 4xx request errors (those fail identically on every model).
13
+ - Auth errors (401/403) do not trigger fallback unless explicitly enabled.
14
+ - Resumes the interrupted run automatically via a "continue" message.
15
+ - Restores your original model on the next interactive message after a cooldown (default 10 minutes), so an ongoing outage doesn't cause churn.
16
+ - A manual model switch (`/model`, Ctrl+P) cancels the pending restore — your choice wins.
17
+ - Persists fallback state in the session: resuming a session left on a fallback model restores the original immediately.
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ pi install npm:@season179/pi-model-fallback
23
+ ```
24
+
25
+ ## Configuration
26
+
27
+ Create `~/.pi/agent/fallback-models.json` (global) or `.pi/fallback-models.json` (project-local, wins if present). It contains **only** the fallback chain:
28
+
29
+ ```json
30
+ {
31
+ "fallbacks": [
32
+ { "provider": "openai-codex", "model": "gpt-5.6-sol" },
33
+ { "provider": "google", "model": "gemini-3-pro" }
34
+ ],
35
+ "restoreCooldownMinutes": 10,
36
+ "fallbackOnAuthErrors": false
37
+ }
38
+ ```
39
+
40
+ - `fallbacks` — ordered chain; entries without a configured API key are skipped.
41
+ - `restoreCooldownMinutes` — how long after the last failure before the original model is restored on your next message (default `10`).
42
+ - `fallbackOnAuthErrors` — set `true` to also fall back on 401/403. Off by default because it can silently shift spend to a metered key and mask an expired credential.
43
+
44
+ The config is re-read on every trigger, so edits apply without reloading Pi.
45
+
46
+ ## Usage
47
+
48
+ - `/fallback` — show the chain, current model, and whether a fallback is active.
49
+ - `/fallback restore` — force-switch back to the original model before the cooldown expires.
50
+
51
+ ## Known Pi-Core Limitation
52
+
53
+ Pi persists *every* model switch (including this extension's) as the new global default in `~/.pi/agent/settings.json`; there is no session-only `setModel` in the extension API. The restore path writes the original defaults back, and resumed sessions repair them at startup. But while a fallback is active — or after a crash mid-fallback, until that session is resumed — new Pi sessions elsewhere will start on the fallback model.
@@ -0,0 +1,43 @@
1
+ /**
2
+ * model-fallback — automatic model failover for pi.
3
+ *
4
+ * When an agent run fails with a persistent provider error (rate limit,
5
+ * overload, 5xx, network), this extension switches to the next model in a
6
+ * user-defined fallback chain and resumes work automatically. The main model
7
+ * settings (settings.json) are never touched.
8
+ *
9
+ * Config (fallback models ONLY), first found wins:
10
+ * - <project>/.pi/fallback-models.json (project-local)
11
+ * - ~/.pi/agent/fallback-models.json (global)
12
+ *
13
+ * {
14
+ * "fallbacks": [
15
+ * { "provider": "openai", "model": "gpt-5.2" },
16
+ * { "provider": "google", "model": "gemini-3-pro" }
17
+ * ],
18
+ * "restoreCooldownMinutes": 10,
19
+ * "fallbackOnAuthErrors": false
20
+ * }
21
+ *
22
+ * Behavior:
23
+ * - Triggers only after pi's built-in same-model retries are exhausted.
24
+ * - Never triggers on user aborts, context-overflow, or 4xx request errors.
25
+ * - Auth errors (401/403) trigger only when fallbackOnAuthErrors is true.
26
+ * - Restores the original model on the next interactive message, once the
27
+ * last failure is older than restoreCooldownMinutes (avoids churn during
28
+ * an ongoing outage). `/fallback restore` forces it immediately.
29
+ * - A manual model switch (/model, Ctrl+P) cancels the pending restore.
30
+ * - The original model is persisted in the session, so resuming a session
31
+ * that was left on a fallback model triggers an immediate restore attempt.
32
+ *
33
+ * KNOWN PI-CORE LIMITATION: pi persists *every* model switch (including this
34
+ * extension's) as the new global default in ~/.pi/agent/settings.json — there
35
+ * is no session-only setModel in the extension API. The restore path writes
36
+ * the original defaults back, and resumed sessions repair them at startup,
37
+ * but while a fallback is active (or after a crash mid-fallback until the
38
+ * session is resumed), new pi sessions elsewhere will start on the fallback
39
+ * model.
40
+ */
41
+ import { type ExtensionAPI } from "@earendil-works/pi-coding-agent";
42
+ export default function (pi: ExtensionAPI): void;
43
+ //# sourceMappingURL=model-fallback.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-fallback.d.ts","sourceRoot":"","sources":["../../src/extensions/model-fallback.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,OAAO,EAAmB,KAAK,YAAY,EAAyB,MAAM,iCAAiC,CAAC;AAqB5G,MAAM,CAAC,OAAO,WAAW,EAAE,EAAE,YAAY,QAwTxC"}
@@ -0,0 +1,333 @@
1
+ /**
2
+ * model-fallback — automatic model failover for pi.
3
+ *
4
+ * When an agent run fails with a persistent provider error (rate limit,
5
+ * overload, 5xx, network), this extension switches to the next model in a
6
+ * user-defined fallback chain and resumes work automatically. The main model
7
+ * settings (settings.json) are never touched.
8
+ *
9
+ * Config (fallback models ONLY), first found wins:
10
+ * - <project>/.pi/fallback-models.json (project-local)
11
+ * - ~/.pi/agent/fallback-models.json (global)
12
+ *
13
+ * {
14
+ * "fallbacks": [
15
+ * { "provider": "openai", "model": "gpt-5.2" },
16
+ * { "provider": "google", "model": "gemini-3-pro" }
17
+ * ],
18
+ * "restoreCooldownMinutes": 10,
19
+ * "fallbackOnAuthErrors": false
20
+ * }
21
+ *
22
+ * Behavior:
23
+ * - Triggers only after pi's built-in same-model retries are exhausted.
24
+ * - Never triggers on user aborts, context-overflow, or 4xx request errors.
25
+ * - Auth errors (401/403) trigger only when fallbackOnAuthErrors is true.
26
+ * - Restores the original model on the next interactive message, once the
27
+ * last failure is older than restoreCooldownMinutes (avoids churn during
28
+ * an ongoing outage). `/fallback restore` forces it immediately.
29
+ * - A manual model switch (/model, Ctrl+P) cancels the pending restore.
30
+ * - The original model is persisted in the session, so resuming a session
31
+ * that was left on a fallback model triggers an immediate restore attempt.
32
+ *
33
+ * KNOWN PI-CORE LIMITATION: pi persists *every* model switch (including this
34
+ * extension's) as the new global default in ~/.pi/agent/settings.json — there
35
+ * is no session-only setModel in the extension API. The restore path writes
36
+ * the original defaults back, and resumed sessions repair them at startup,
37
+ * but while a fallback is active (or after a crash mid-fallback until the
38
+ * session is resumed), new pi sessions elsewhere will start on the fallback
39
+ * model.
40
+ */
41
+ import { CONFIG_DIR_NAME } from "@earendil-works/pi-coding-agent";
42
+ import { existsSync, readFileSync } from "node:fs";
43
+ import { homedir } from "node:os";
44
+ import { join } from "node:path";
45
+ const STATE_ENTRY = "model-fallback-state";
46
+ const DEFAULT_COOLDOWN_MINUTES = 10;
47
+ export default function (pi) {
48
+ // ---- state -------------------------------------------------------------
49
+ let originalModel; // set on first incident
50
+ let chainIndex = 0; // next fallback to try
51
+ let lastFailureAt = 0; // epoch ms of last classified failure
52
+ let internalSwitch = false; // guards our own setModel calls
53
+ let restoreFailureWarned = false; // warn only once when auto-restore can't switch back
54
+ let lastHttpStatus; // from after_provider_response
55
+ let stashedStopReason; // from agent_end
56
+ let stashedErrorMessage;
57
+ // ---- config ------------------------------------------------------------
58
+ function loadConfig(cwd) {
59
+ const candidates = [
60
+ join(cwd, CONFIG_DIR_NAME, "fallback-models.json"),
61
+ join(homedir(), ".pi", "agent", "fallback-models.json"),
62
+ ];
63
+ for (const path of candidates) {
64
+ if (!existsSync(path))
65
+ continue;
66
+ try {
67
+ const raw = JSON.parse(readFileSync(path, "utf8"));
68
+ const fallbacks = Array.isArray(raw.fallbacks)
69
+ ? raw.fallbacks.filter((f) => !!f &&
70
+ typeof f.provider === "string" &&
71
+ typeof f.model === "string")
72
+ : [];
73
+ return {
74
+ fallbacks,
75
+ restoreCooldownMinutes: typeof raw.restoreCooldownMinutes === "number"
76
+ ? raw.restoreCooldownMinutes
77
+ : DEFAULT_COOLDOWN_MINUTES,
78
+ fallbackOnAuthErrors: raw.fallbackOnAuthErrors === true,
79
+ };
80
+ }
81
+ catch (err) {
82
+ notifySafe(`model-fallback: invalid config at ${path}: ${String(err)}`, "error");
83
+ return undefined;
84
+ }
85
+ }
86
+ return undefined;
87
+ }
88
+ // ---- helpers -----------------------------------------------------------
89
+ let notifySafe = () => { };
90
+ function modelKey(m) {
91
+ return `${m.provider}/${m.model}`;
92
+ }
93
+ function persistState(active) {
94
+ pi.appendEntry(STATE_ENTRY, { active, originalModel: originalModel ?? null });
95
+ }
96
+ function classify(errorMessage, status, cfg) {
97
+ const msg = (errorMessage ?? "").toLowerCase();
98
+ // Context overflow: never fall back — that's compaction's job, and a
99
+ // smaller-context fallback would only make it worse.
100
+ const overflowPatterns = [
101
+ "context length",
102
+ "context_length",
103
+ "maximum context",
104
+ "prompt is too long",
105
+ "too many tokens",
106
+ "exceeds the context",
107
+ "input is too long",
108
+ ];
109
+ if (overflowPatterns.some((p) => msg.includes(p)))
110
+ return "skip";
111
+ // Prefer numeric status; errorMessage often embeds it as a prefix ("529 {...}").
112
+ let code = status;
113
+ if (code === undefined) {
114
+ const m = /^\s*(\d{3})\b/.exec(errorMessage ?? "");
115
+ if (m)
116
+ code = Number(m[1]);
117
+ }
118
+ if (code !== undefined) {
119
+ if (code === 401 || code === 403)
120
+ return cfg.fallbackOnAuthErrors ? "fallback" : "auth";
121
+ if (code === 429 || code >= 500)
122
+ return "fallback";
123
+ if (code >= 400 && code < 500)
124
+ return "skip"; // bad request: same failure on every model
125
+ }
126
+ // No status: check for network-level failures.
127
+ const networkPatterns = [
128
+ "econnreset",
129
+ "econnrefused",
130
+ "etimedout",
131
+ "enotfound",
132
+ "eai_again",
133
+ "fetch failed",
134
+ "network",
135
+ "socket hang up",
136
+ "terminated",
137
+ "overloaded",
138
+ "rate limit",
139
+ "rate_limit",
140
+ ];
141
+ if (networkPatterns.some((p) => msg.includes(p)))
142
+ return "fallback";
143
+ return "unknown";
144
+ }
145
+ async function switchTo(ref, ctx) {
146
+ const model = ctx.modelRegistry.find(ref.provider, ref.model);
147
+ if (!model)
148
+ return false;
149
+ internalSwitch = true;
150
+ try {
151
+ return await pi.setModel(model);
152
+ }
153
+ finally {
154
+ internalSwitch = false;
155
+ }
156
+ }
157
+ // ---- wiring ------------------------------------------------------------
158
+ pi.on("session_start", async (_event, ctx) => {
159
+ notifySafe = (msg, level = "info") => {
160
+ if (ctx.hasUI)
161
+ ctx.ui.notify(msg, level);
162
+ else
163
+ console.error(`[model-fallback] ${msg}`);
164
+ };
165
+ // Re-arm restore if this session was left on a fallback model.
166
+ let persisted;
167
+ for (const entry of ctx.sessionManager.getEntries()) {
168
+ if (entry.type === "custom" && entry.customType === STATE_ENTRY) {
169
+ persisted = entry.data;
170
+ }
171
+ }
172
+ if (persisted?.active && persisted.originalModel) {
173
+ originalModel = persisted.originalModel;
174
+ lastFailureAt = 0; // outage was in a previous session
175
+ // Restore immediately: this also repairs the global default model that
176
+ // pi persisted when the fallback switch happened.
177
+ const target = originalModel;
178
+ if (await switchTo(target, ctx)) {
179
+ notifySafe(`model-fallback: restored original model ${modelKey(target)} from previous session.`, "info");
180
+ originalModel = undefined;
181
+ chainIndex = 0;
182
+ persistState(false);
183
+ }
184
+ else {
185
+ notifySafe(`model-fallback: session was left on a fallback model and ${modelKey(target)} is unavailable; will retry on your next message (or /fallback restore).`, "warning");
186
+ }
187
+ }
188
+ });
189
+ // Primary classification signal: HTTP status of the most recent provider response.
190
+ pi.on("after_provider_response", (event, _ctx) => {
191
+ lastHttpStatus = event.status;
192
+ });
193
+ pi.on("agent_start", async () => {
194
+ lastHttpStatus = undefined;
195
+ });
196
+ // Stash error details here; pi may still auto-retry/compact after agent_end.
197
+ pi.on("agent_end", async (event) => {
198
+ stashedStopReason = undefined;
199
+ stashedErrorMessage = undefined;
200
+ for (let i = event.messages.length - 1; i >= 0; i--) {
201
+ const m = event.messages[i];
202
+ if (m.role === "assistant") {
203
+ stashedStopReason = m.stopReason;
204
+ stashedErrorMessage = m.errorMessage;
205
+ break;
206
+ }
207
+ }
208
+ });
209
+ // Act only when pi has truly given up (retries and compaction exhausted).
210
+ pi.on("agent_settled", async (_event, ctx) => {
211
+ if (stashedStopReason !== "error") {
212
+ chainIndex = 0; // healthy (or aborted) run: reset chain position
213
+ return;
214
+ }
215
+ const cfg = loadConfig(ctx.cwd);
216
+ if (!cfg || cfg.fallbacks.length === 0)
217
+ return;
218
+ const cls = classify(stashedErrorMessage, lastHttpStatus, cfg);
219
+ if (cls === "skip")
220
+ return;
221
+ if (cls === "auth") {
222
+ notifySafe("model-fallback: auth error (401/403) — not falling back. Fix credentials, or set \"fallbackOnAuthErrors\": true in fallback-models.json.", "error");
223
+ return;
224
+ }
225
+ if (cls === "unknown") {
226
+ notifySafe(`model-fallback: unrecognized error, not falling back: ${stashedErrorMessage ?? "(no message)"}`, "warning");
227
+ return;
228
+ }
229
+ lastFailureAt = Date.now();
230
+ // Remember the model we're abandoning (first incident only).
231
+ if (!originalModel && ctx.model) {
232
+ originalModel = { provider: ctx.model.provider, model: ctx.model.id };
233
+ }
234
+ // Walk the chain from the current position. If a previous incident
235
+ // exhausted the chain, start over — the user may have fixed a key or the
236
+ // outage may have ended for an earlier entry.
237
+ if (chainIndex >= cfg.fallbacks.length)
238
+ chainIndex = 0;
239
+ while (chainIndex < cfg.fallbacks.length) {
240
+ const candidate = cfg.fallbacks[chainIndex];
241
+ chainIndex++;
242
+ if (originalModel && modelKey(candidate) === modelKey(originalModel))
243
+ continue;
244
+ if (ctx.model && modelKey(candidate) === `${ctx.model.provider}/${ctx.model.id}`)
245
+ continue;
246
+ if (await switchTo(candidate, ctx)) {
247
+ persistState(true);
248
+ notifySafe(`model-fallback: ${stashedErrorMessage?.slice(0, 120) ?? "provider error"} — switched to ${modelKey(candidate)}, resuming.`, "warning");
249
+ pi.sendUserMessage("continue");
250
+ return;
251
+ }
252
+ notifySafe(`model-fallback: ${modelKey(candidate)} unavailable (not found or no API key), skipping.`, "warning");
253
+ }
254
+ notifySafe("model-fallback: all fallback models exhausted. Stopping.", "error");
255
+ });
256
+ // Restore the original model on the next interactive input, after cooldown.
257
+ pi.on("input", async (event, ctx) => {
258
+ if (event.source !== "interactive")
259
+ return;
260
+ if (!originalModel)
261
+ return;
262
+ const cfg = loadConfig(ctx.cwd);
263
+ const cooldownMs = (cfg?.restoreCooldownMinutes ?? DEFAULT_COOLDOWN_MINUTES) * 60_000;
264
+ if (Date.now() - lastFailureAt < cooldownMs)
265
+ return; // likely still in the outage window
266
+ const target = originalModel;
267
+ if (await switchTo({ provider: target.provider, model: target.model }, ctx)) {
268
+ notifySafe(`model-fallback: restored original model ${modelKey(target)}.`, "info");
269
+ originalModel = undefined;
270
+ chainIndex = 0;
271
+ restoreFailureWarned = false;
272
+ persistState(false);
273
+ }
274
+ else if (!restoreFailureWarned) {
275
+ restoreFailureWarned = true;
276
+ notifySafe(`model-fallback: cannot restore ${modelKey(target)} (not found or no API key). Staying on fallback; fix credentials and use /fallback restore.`, "warning");
277
+ }
278
+ });
279
+ // A genuine manual model switch cancels the pending restore.
280
+ pi.on("model_select", async (event, _ctx) => {
281
+ if (internalSwitch)
282
+ return;
283
+ if (event.source === "restore")
284
+ return; // session restore, not a user decision
285
+ if (originalModel) {
286
+ notifySafe("model-fallback: manual model change — pending restore cancelled.", "info");
287
+ originalModel = undefined;
288
+ chainIndex = 0;
289
+ persistState(false);
290
+ }
291
+ });
292
+ // ---- command -----------------------------------------------------------
293
+ pi.registerCommand("fallback", {
294
+ description: "Show model-fallback status; '/fallback restore' switches back to the original model now",
295
+ handler: async (args, ctx) => {
296
+ const cfg = loadConfig(ctx.cwd);
297
+ if (args?.trim() === "restore") {
298
+ if (!originalModel) {
299
+ notifySafe("model-fallback: nothing to restore.", "info");
300
+ return;
301
+ }
302
+ const target = originalModel;
303
+ if (await switchTo(target, ctx)) {
304
+ notifySafe(`model-fallback: restored original model ${modelKey(target)}.`, "info");
305
+ originalModel = undefined;
306
+ chainIndex = 0;
307
+ restoreFailureWarned = false;
308
+ persistState(false);
309
+ }
310
+ else {
311
+ notifySafe(`model-fallback: could not restore ${modelKey(target)} (not found or no API key).`, "error");
312
+ }
313
+ return;
314
+ }
315
+ const lines = [];
316
+ if (!cfg) {
317
+ lines.push("No config found. Create ~/.pi/agent/fallback-models.json:");
318
+ lines.push('{ "fallbacks": [ { "provider": "openai", "model": "gpt-5.2" } ] }');
319
+ }
320
+ else {
321
+ lines.push(`Chain: ${cfg.fallbacks.map(modelKey).join(" -> ") || "(empty)"}`);
322
+ lines.push(`Cooldown: ${cfg.restoreCooldownMinutes}m | Auth-error fallback: ${cfg.fallbackOnAuthErrors ? "on" : "off"}`);
323
+ }
324
+ lines.push(originalModel
325
+ ? `ACTIVE: on fallback, original ${modelKey(originalModel)} pending restore (/fallback restore to force).`
326
+ : "Idle: running on your selected model.");
327
+ const current = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : "unknown";
328
+ lines.push(`Current model: ${current}`);
329
+ notifySafe(lines.join("\n"), "info");
330
+ },
331
+ });
332
+ }
333
+ //# sourceMappingURL=model-fallback.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-fallback.js","sourceRoot":"","sources":["../../src/extensions/model-fallback.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@season179/pi-model-fallback",
3
+ "version": "26.7.0",
4
+ "description": "Automatic model failover for Pi, driven by a standalone fallback-models.json config.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "Season Saw",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/season179/pi-ecosystem.git",
11
+ "directory": "packages/pi-model-fallback"
12
+ },
13
+ "keywords": [
14
+ "pi",
15
+ "pi-package",
16
+ "pi-extension",
17
+ "fallback",
18
+ "failover",
19
+ "model",
20
+ "agent"
21
+ ],
22
+ "files": [
23
+ "dist",
24
+ "README.md",
25
+ "LICENSE"
26
+ ],
27
+ "pi": {
28
+ "extensions": [
29
+ "./dist/extensions/model-fallback.js"
30
+ ]
31
+ },
32
+ "scripts": {
33
+ "clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
34
+ "build": "tsc -p tsconfig.json",
35
+ "prepack": "npm run clean && npm run build",
36
+ "prepublishOnly": "npm run clean && npm run build"
37
+ },
38
+ "engines": {
39
+ "node": ">=22"
40
+ }
41
+ }