@hu3rror/pi-failover 0.3.0 → 0.4.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 +20 -3
- package/README.zh-CN.md +20 -3
- package/package.json +1 -1
- package/src/auth-catalog.ts +3 -1
- package/src/failover-engine.ts +16 -1
- package/src/index.ts +1 -0
- package/src/provider-types.ts +1 -0
package/README.md
CHANGED
|
@@ -68,8 +68,9 @@ The command shows redacted runtime status only. It never prints raw credential v
|
|
|
68
68
|
If the active key receives a handled failure during a user request, `pi-failover` can:
|
|
69
69
|
|
|
70
70
|
- switch to the next backup key for the same provider
|
|
71
|
-
- switch to the next configured provider
|
|
71
|
+
- switch to the next configured provider (after every backup key of the current provider is exhausted; a provider without `backupKeys` never switches providers)
|
|
72
72
|
- retry the same user request automatically after a successful switch
|
|
73
|
+
- hand a failure back to Pi's built-in retry when the active provider has no backup keys
|
|
73
74
|
- show only the final provider error when every configured option is exhausted
|
|
74
75
|
|
|
75
76
|
Intermediate provider errors are replaced by a hidden continuation, so no second user message is required. TUI and RPC modes still show one redacted warning for each applied credential or provider switch.
|
|
@@ -96,8 +97,8 @@ Within one user request, failed credentials and providers are disabled or cooled
|
|
|
96
97
|
|
|
97
98
|
| Failure | What pi-failover does |
|
|
98
99
|
| --- | --- |
|
|
99
|
-
| `401` / `403` | Disables the current credential for the session, switches to the next backup key or the next provider, then retries the same request. |
|
|
100
|
-
| `429` | Cools down the current credential by `Retry-After`, or by 60 seconds when the header is absent, switches to the next backup key, then retries. |
|
|
100
|
+
| `401` / `403` | Disables the current credential for the session, switches to the next backup key or the next provider, then retries the same request. A provider without `backupKeys` hands the failure back to Pi's own retry instead of switching providers. |
|
|
101
|
+
| `429` | Cools down the current credential by `Retry-After`, or by 60 seconds when the header is absent, switches to the next backup key, then retries. Without `backupKeys` the failure is handed back to Pi's built-in retry. |
|
|
101
102
|
| `529` or overloaded responses | Cools down the provider by `Retry-After`, or by 30 seconds when the header is absent, changes provider, then retries. |
|
|
102
103
|
| `500`, `502`, `503`, `504`, network, timeout | Cools down the provider for 30 seconds, changes provider, then retries. |
|
|
103
104
|
| Other failures | Leaves Pi's normal error handling unchanged. |
|
|
@@ -106,6 +107,22 @@ When switching providers, `pi-failover` prefers the current model ID. If that mo
|
|
|
106
107
|
|
|
107
108
|
Status and warning messages identify credential slots without exposing values: the primary credential is `primary`, the first backup is `backup`, and later backups are `backup-2`, `backup-3`, and so on.
|
|
108
109
|
|
|
110
|
+
An `api_key` provider without `backupKeys` never triggers provider failover: when its credential fails, `pi-failover` leaves the error message untouched and hands the failure back to Pi's own retry (`settings.retry`). OAuth providers, which structurally cannot hold backup keys, keep participating in provider failover and remain reachable as fallback targets.
|
|
111
|
+
|
|
112
|
+
## Working with Pi's Retry Settings
|
|
113
|
+
|
|
114
|
+
`pi-failover` cooperates with Pi's built-in retry instead of replacing it. Recommended `settings.json` values:
|
|
115
|
+
|
|
116
|
+
| Setting | Recommended | Why |
|
|
117
|
+
| --- | --- | --- |
|
|
118
|
+
| `retry.enabled` | `true` | Backstop for failures `pi-failover` deliberately does not touch (e.g. a provider without `backupKeys`). |
|
|
119
|
+
| `retry.maxRetries` | `3` (default) | Higher budgets only delay the final error after every failover option is exhausted. |
|
|
120
|
+
| `retry.provider.maxRetries` | `0` (default) | SDK-level retries swallow failures before Pi sees them, so failover never observes the error. |
|
|
121
|
+
| `retry.provider.timeoutMs` | e.g. `60000` | The SDK default is one hour; a hung provider blocks failover's network classification for that long. |
|
|
122
|
+
|
|
123
|
+
When the active provider has no `backupKeys` and fails, `pi-failover` does not replace the error message, so Pi's built-in retry runs with its own exponential backoff (`retry.baseDelayMs`). With backups, `pi-failover` switches the key or provider and retries immediately, then returns control to Pi.
|
|
124
|
+
|
|
125
|
+
|
|
109
126
|
## Commands
|
|
110
127
|
|
|
111
128
|
- `/failover login`: interactively add a backup API key to an `api_key` provider; run it bare to pick from the current providers, or pass a provider name (`/failover login <provider>`, with autocompletion); the key is entered in a prompt (never on the command line), confirmed, then written to `auth.json` and the catalog is rebuilt
|
package/README.zh-CN.md
CHANGED
|
@@ -67,8 +67,9 @@ pi install npm:@hu3rror/pi-failover
|
|
|
67
67
|
当当前 key 在一次用户请求中遇到已接管的故障时,`pi-failover` 会按情况执行:
|
|
68
68
|
|
|
69
69
|
- 切到同一 provider 的下一把备用 key
|
|
70
|
-
- 切到下一个已配置 provider
|
|
70
|
+
- 切到下一个已配置 provider(当前 provider 的所有备用 key 都用尽之后;没有 `backupKeys` 的 provider 永远不会切换 provider)
|
|
71
71
|
- 成功切换后自动重试同一次用户请求
|
|
72
|
+
- 当当前 provider 没有备用 key 时,把失败交还给 Pi 的内置重试
|
|
72
73
|
- 当所有可选项都耗尽时,只显示最后一次 provider 错误
|
|
73
74
|
|
|
74
75
|
中间 provider 错误会被替换为隐藏的续跑消息,因此用户无需再次发送相同内容。TUI 和 RPC 模式仍会为每次实际生效的凭据或 provider 切换显示一条脱敏警告。
|
|
@@ -96,8 +97,8 @@ pi install npm:@hu3rror/pi-failover
|
|
|
96
97
|
|
|
97
98
|
| 故障类型 | `pi-failover` 的处理方式 |
|
|
98
99
|
| --- | --- |
|
|
99
|
-
| `401` / `403` | 将当前凭证在本次会话中标记为不可用,切换到下一把备用 key 或下一个 provider
|
|
100
|
-
| `429` | 按 `Retry-After` 冷却当前凭证;如果没有该响应头,则冷却 60 秒,切换到下一把备用 key
|
|
100
|
+
| `401` / `403` | 将当前凭证在本次会话中标记为不可用,切换到下一把备用 key 或下一个 provider,然后重试同一次请求。没有 `backupKeys` 的 provider 会把失败交还给 Pi 自己的重试,而不是切换 provider。 |
|
|
101
|
+
| `429` | 按 `Retry-After` 冷却当前凭证;如果没有该响应头,则冷却 60 秒,切换到下一把备用 key 后重试。没有 `backupKeys` 时,失败交还给 Pi 的内置重试。 |
|
|
101
102
|
| `529` 或 overloaded 响应 | 按 `Retry-After` 冷却当前 provider;如果没有该响应头,则冷却 30 秒,切换 provider 后重试。 |
|
|
102
103
|
| `500`、`502`、`503`、`504`、网络错误、超时 | 将当前 provider 冷却 30 秒,切换 provider 后重试。 |
|
|
103
104
|
| 其他故障 | 保持 Pi 原有的错误处理逻辑,不额外接管。 |
|
|
@@ -106,6 +107,22 @@ pi install npm:@hu3rror/pi-failover
|
|
|
106
107
|
|
|
107
108
|
状态和警告信息只显示脱敏后的凭证槽位:主凭证为 `primary`,第一把备用凭证为 `backup`,后续依次为 `backup-2`、`backup-3`……
|
|
108
109
|
|
|
110
|
+
没有 `backupKeys` 的 `api_key` provider 永远不会触发 provider 级切换:其凭证失败时,`pi-failover` 不修改错误消息,直接把失败交还给 Pi 自己的重试(`settings.retry`)。OAuth provider 结构上无法持有备用 key,仍然参与 provider 级切换,并且始终可以作为被切换的目标。
|
|
111
|
+
|
|
112
|
+
## 与 Pi 的 retry 设置配合
|
|
113
|
+
|
|
114
|
+
`pi-failover` 与 Pi 的内置重试协作,而不是取代它。推荐的 `settings.json` 取值:
|
|
115
|
+
|
|
116
|
+
| 设置项 | 推荐值 | 原因 |
|
|
117
|
+
| --- | --- | --- |
|
|
118
|
+
| `retry.enabled` | `true` | 作为 `pi-failover` 刻意不接管的失败(例如没有 `backupKeys` 的 provider)的兜底。 |
|
|
119
|
+
| `retry.maxRetries` | `3`(默认值) | 更大的预算只会在所有 failover 选项耗尽后拖延最终错误出现的时间。 |
|
|
120
|
+
| `retry.provider.maxRetries` | `0`(默认值) | SDK 层的重试会在 Pi 看到错误之前把失败吞掉,failover 将永远观察不到该错误。 |
|
|
121
|
+
| `retry.provider.timeoutMs` | 例如 `60000` | SDK 默认是 1 小时;provider 挂起时,failover 的网络故障分类会被阻塞同样长的时间。 |
|
|
122
|
+
|
|
123
|
+
当当前 provider 没有 `backupKeys` 且请求失败时,`pi-failover` 不替换错误消息,Pi 的内置重试会按自己的指数退避(`retry.baseDelayMs`)执行。配置了备用 key 时,`pi-failover` 会立即切换 key 或 provider 并重试,随后把控制权交还给 Pi。
|
|
124
|
+
|
|
125
|
+
|
|
109
126
|
## 命令
|
|
110
127
|
|
|
111
128
|
- `/failover login`:交互式地为 `api_key` provider 添加备用 key;不带参数时从现有 provider 中选择,也可直接指定 provider(`/failover login <provider>`,支持自动补全);key 在输入框中录入(绝不通过命令行参数传入),经确认后写入 `auth.json` 并立即重建 failover catalog,新 key 立即可用
|
package/package.json
CHANGED
package/src/auth-catalog.ts
CHANGED
|
@@ -2,9 +2,11 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
|
|
5
|
+
import type { ProviderType } from "./provider-types.ts";
|
|
6
|
+
|
|
5
7
|
export interface AuthProviderEntry {
|
|
6
8
|
provider: string;
|
|
7
|
-
type:
|
|
9
|
+
type: ProviderType;
|
|
8
10
|
backupKeys?: string[];
|
|
9
11
|
}
|
|
10
12
|
|
package/src/failover-engine.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { ProviderType } from "./provider-types.ts";
|
|
2
|
+
|
|
1
3
|
export type FailureKind =
|
|
2
4
|
| "unauthorized"
|
|
3
5
|
| "rate-limited"
|
|
@@ -52,8 +54,8 @@ export interface ProviderFallbackRequest {
|
|
|
52
54
|
export interface FailoverProvider {
|
|
53
55
|
id: string;
|
|
54
56
|
backupKeyCount?: number;
|
|
57
|
+
type: ProviderType;
|
|
55
58
|
}
|
|
56
|
-
|
|
57
59
|
export interface FailoverEngineOptions {
|
|
58
60
|
providers: readonly FailoverProvider[];
|
|
59
61
|
now: () => number;
|
|
@@ -72,6 +74,8 @@ interface KeyState {
|
|
|
72
74
|
|
|
73
75
|
interface ProviderState {
|
|
74
76
|
id: string;
|
|
77
|
+
type: ProviderType;
|
|
78
|
+
backupKeyCount: number;
|
|
75
79
|
keys: KeyState[];
|
|
76
80
|
cooldownUntil: number;
|
|
77
81
|
}
|
|
@@ -107,6 +111,8 @@ export class FailoverEngine {
|
|
|
107
111
|
constructor(options: FailoverEngineOptions) {
|
|
108
112
|
this.providers = options.providers.map((provider) => ({
|
|
109
113
|
id: provider.id,
|
|
114
|
+
type: provider.type,
|
|
115
|
+
backupKeyCount: provider.backupKeyCount ?? 0,
|
|
110
116
|
keys: [
|
|
111
117
|
{ slot: "primary" as const, disabled: false, cooldownUntil: 0 },
|
|
112
118
|
...Array.from({ length: provider.backupKeyCount ?? 0 }, (_, index) => ({
|
|
@@ -214,6 +220,15 @@ export class FailoverEngine {
|
|
|
214
220
|
}
|
|
215
221
|
|
|
216
222
|
private selectFallback(current: ProviderPlan): FailoverDecision {
|
|
223
|
+
// A provider without backup keys never triggers provider failover: hand the
|
|
224
|
+
// failure back to Pi's own retry instead of switching providers. Providers
|
|
225
|
+
// without backups remain reachable as fallback *targets* (selectPlan does
|
|
226
|
+
// not apply this gate); only the active provider's own escape is blocked.
|
|
227
|
+
const activeProvider = this.provider(current.providerId);
|
|
228
|
+
if (activeProvider?.type === "api_key" && activeProvider.backupKeyCount === 0) {
|
|
229
|
+
return this.setDecision({ kind: "none" });
|
|
230
|
+
}
|
|
231
|
+
|
|
217
232
|
const considered = new Set<string>();
|
|
218
233
|
for (let remaining = this.providers.length; remaining > 0; remaining -= 1) {
|
|
219
234
|
const plan = this.nextProvider({
|
package/src/index.ts
CHANGED
|
@@ -125,6 +125,7 @@ export function createFailoverExtension(options: FailoverExtensionOptions = {})
|
|
|
125
125
|
? new FailoverEngine({
|
|
126
126
|
providers: catalog.providers.map((provider) => ({
|
|
127
127
|
id: provider.provider,
|
|
128
|
+
type: provider.type,
|
|
128
129
|
backupKeyCount: provider.backupKeys?.length ?? 0,
|
|
129
130
|
})),
|
|
130
131
|
now,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type ProviderType = "api_key" | "oauth";
|