pi-model-fallback 0.5.0 → 0.5.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/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## [0.5.1] - 2026-09-30
6
+
7
+ ### Changed
8
+
9
+ - Update `@earendil-works/pi-*` dependencies to `0.99.1`.
10
+
5
11
  ## [0.5.0] - 2026-09-22
6
12
 
7
13
  ### Added
package/README.md CHANGED
@@ -97,7 +97,7 @@ Rule fields:
97
97
  Top-level fields:
98
98
 
99
99
  - `enabled`: toggle the extension without deleting rules
100
- - `autoRetry`: when a fallback fires from a failed turn, automatically re-queue the failed user prompt so the turn retries on the fallback model (defaults to `true`; set to `false` to only switch models). The failed prompt is queued once, not retried indefinitely.
100
+ - `autoRetry`: when a fallback fires from a failed turn, automatically re-queue the failed user prompt so the turn retries on the fallback model (defaults to `true`; set to `false` to only switch models). The failed prompt is queued once per fallback transition, so a fallback chain may replay it more than once.
101
101
 
102
102
  Rules use first-match order: the first rule whose provider/model and status match wins. Put specific `matchModels` rules before broad `matchProviders` rules when they should take priority. `model_fallback_config validate`, `save`, `read`, and `status` report warning-only diagnostics when a later rule or model entry is completely shadowed by an earlier rule; `/model-fallback:status` also includes a concise warning summary for the current config. Warning-bearing config remains valid and can still be saved.
103
103
 
@@ -124,7 +124,7 @@ If the package is installed project-locally and the current project references i
124
124
  - Successful responses do nothing.
125
125
  - Matching failures from `after_provider_response` can trigger fallback immediately.
126
126
  - Assistant error messages parsed at `turn_end` can also persist fallback state for SDK/provider failures that do not emit the normal response hook. Status extraction looks for HTTP-style tokens (for example `status 429`, `HTTP 503`, or `rate limit`) rather than any bare 3-digit number in the message.
127
- - With the default `autoRetry: true`, the failed prompt is automatically queued once on the fallback model. Set `autoRetry: false` to switch models without replaying it.
127
+ - With the default `autoRetry: true`, the failed prompt is automatically queued once per fallback transition on the fallback model. A fallback chain may therefore replay the prompt more than once. Set `autoRetry: false` to switch models without replaying it.
128
128
 
129
129
  Rules never fall back to the failing model itself; a rule whose fallback target equals the failing model is skipped so the next matching rule (or no rule) applies.
130
130
 
package/docs/usage.md CHANGED
@@ -6,7 +6,7 @@ Install:
6
6
  pi install npm:pi-model-fallback
7
7
  ```
8
8
 
9
- Default behavior: when a `zai/*` model receives `429`, `500`, `502`, `503`, or `504`, Pi switches to `deepseek/deepseek-v4-flash` for the next prompt. The failed prompt is automatically queued once on the fallback model; set `"autoRetry": false` in `model-fallback/config.json` to switch models without retrying it.
9
+ Default behavior: when a `zai/*` model receives `429`, `500`, `502`, `503`, or `504`, Pi switches to `deepseek/deepseek-v4-flash` for the next prompt. The failed prompt is automatically queued once per fallback transition on the fallback model, so a fallback chain may replay it more than once; set `"autoRetry": false` in `model-fallback/config.json` to switch models without retrying it.
10
10
 
11
11
  Commands:
12
12
 
@@ -15,7 +15,30 @@ export function parseReasonFromErrorMessage(message: string): string | undefined
15
15
  return undefined;
16
16
  }
17
17
 
18
+ // Failure modes where the transport reports HTTP success but the provider
19
+ // yields nothing usable — no numeric status is present in any of them, so the
20
+ // status parser must synthesize one for the fallback rules to fire:
21
+ // - "Provider returned an empty response" (OpenRouter stealth/pool models)
22
+ // - "Stream ended without finish_reason" (chunk stream truncated mid-flight)
23
+ // - "<provider> response has no body" (Anthropic/Mistral/generic, 200 empty body)
24
+ // - "<provider> stream ended without a terminal event" (SSE stream dies silently)
25
+ const EMPTY_PROVIDER_RESPONSE_PATTERNS: RegExp[] = [
26
+ /provider returned an empty response/i,
27
+ /response (has|with) no body/i,
28
+ /stream ended without a terminal event/i,
29
+ /stream ended without finish_reason/i,
30
+ ];
31
+
18
32
  export function parseStatusFromErrorMessage(message: string): number | undefined {
33
+ for (const pattern of EMPTY_PROVIDER_RESPONSE_PATTERNS) {
34
+ if (pattern.test(message)) {
35
+ // Synthesize 502 so status-based fallback rules fire instead of the
36
+ // agent silently retrying the same model. 502 is deliberately chosen
37
+ // over a novel code: it lands in the 5xx status lists users already
38
+ // configure and inherits the standard 10-minute cooldown.
39
+ return 502;
40
+ }
41
+ }
19
42
  const patterns: RegExp[] = [
20
43
  // Leading bare status code, optionally prefixed with "Error:", e.g. "401: {\"type\":\"CreditsError\"...}"
21
44
  // or "Error: 401: {\"type\":\"CreditsError\"...}" (OpenCode Go, OpenRouter relays)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-model-fallback",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Pi extension that switches to a fallback model after provider failures such as 429 rate limits.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -50,11 +50,11 @@
50
50
  "typebox": "*"
51
51
  },
52
52
  "devDependencies": {
53
- "@earendil-works/pi-agent-core": "^0.86.1",
54
- "@earendil-works/pi-ai": "^0.86.1",
55
- "@earendil-works/pi-coding-agent": "^0.86.1",
56
- "@earendil-works/pi-tui": "^0.86.1",
57
- "@types/node": "^26.0.0",
53
+ "@earendil-works/pi-agent-core": "^0.99.1",
54
+ "@earendil-works/pi-ai": "^0.99.1",
55
+ "@earendil-works/pi-coding-agent": "^0.99.1",
56
+ "@earendil-works/pi-tui": "^0.99.1",
57
+ "@types/node": "^26.6.3",
58
58
  "jiti": "^2.7.0",
59
59
  "typebox": "latest",
60
60
  "typescript": "^7.0.2"