pi-provider-chain0.2.1
Pi 扩展:虚拟模型 + 链路(chain)路由 + 额度耗尽自动降级(quota / subscription / overdue 类错误自动切到后备链路)
Untested on omp
This is a pi package. No one has reported whether it works on omp yet. If you try it, add your result below.
Reports 0
No reports yet.
Readme
English | 简体中文
pi-provider-chain
给 Pi 的扩展:虚拟模型 + 额度耗尽自动降级。
为每条配置注册一个虚拟模型(默认挂在 yansongda provider 下,例如 yansongda/deepseek-v4.1-flash),把请求路由到 chain[0];一旦命中额度 / 订阅 / 欠费类错误,自动沿 chain[1..] 推进,并把降级状态写入共享状态文件,跨 session、跨进程、跨父子 subagent 生效。
特性
- 只对额度类错误降级:429 瞬时限流、5xx、网络错误不干预,交回 Pi 自身重试。
- 两条触发路径:
retry快路径(可重试错误);agent_before_settle兜底路径(不可重试错误,剔除失败消息后续跑一次)。 - 保缓存:
retry同档位、continuation沿用上一物理模型,尽量避免 prompt cache / 思考签名失效。 - 可解释:notify 提示 + 页脚常驻显示当前目标物理模型(用链路第一个 provider 时
→,降级后↓并给出最早恢复时间与所属 provider)+ 转录内可见条目(前缀[chain],不进模型上下文)。 - 自动恢复:降级窗口按错误里的
reset at ...解析,解析不到用默认时长;窗口过期后首次主用成功即提示恢复。 - 零运行时依赖:只用 Pi 提供的 host 包(peer),不引入任何传递依赖。
安装
# 从 npm 安装
pi install npm:pi-provider-chain
# 本地路径(开发用,改动无需重新安装)
pi install /Users/yansongda/000-Coding/pi-provider-chain
pi list
安装后配置 ~/.pi/agent/provider-chain.json(或 $PI_CODING_AGENT_DIR/provider-chain.json);完整示例见 examples/provider-chain.json,字段说明见 examples/README.md。改完 /reload 或新开 session 生效。用 pi-subagents 时还需让子会话加载本扩展(一行 npm:pi-provider-chain 即可),见与 pi-subagents 配合。
配置
最小可用配置(只需声明一个主用模型;全部可选字段的用法见示例文件):
{
"virtualProvider": "yansongda",
"models": [
{
"id": "deepseek-v4.1-flash",
"name": "DeepSeek V4.1 Flash · auto",
"chain": ["volcengine/deepseek-v4.1-flash", "workbuddy/deepseek-v4.1-flash"]
}
]
}
| 字段 | 必填 | 默认值 | 说明 |
|---|---|---|---|
virtualProvider |
✅ | - | 虚拟模型挂载的 provider id(可复用已有 provider,也可以是新 id) |
models[].id |
✅ | - | 虚拟模型 id,不能与 virtualProvider 下物理模型 id 相同;同一配置内重复会直接报错 |
models[].name |
- | models[].id |
/model 列表里显示的名字(非字符串时忽略) |
models[].thinkingLevels |
- | 全部档位 | 选择时可用的思考档位(非数组或过滤后为空时回落全部档位) |
models[].input |
- | 宿主默认(text + image) | 声明该虚拟模型接受的输入类型,仅 text / image 有效(其他值丢弃、去重);不声明则不下发该字段 |
models[].contextWindow / maxTokens |
- | 未声明 | 首个响应前展示的限值(非正数时忽略) |
models[].chain[] |
✅ | - | 链路:chain[0] 为主用,其后按顺序作为后备(provider/id);chain[0] 非法直接报错,后续非法项被静默忽略;建议链路总长 ≤ 5(业界网关默认上限,链过长会让一次故障在多个 provider 上依次付出失败代价) |
degradePatterns[] |
- | 内置 14 项 | 命中即降级的关键词(忽略大小写;非空列表完全覆盖默认值,留空/全空白则回落内置默认) |
resetPattern |
- | 内置正则 | 从错误文本解析恢复时间的正则(第 1/2 组为日期/时间,第 3 组可选时区) |
defaultDegradeMs |
- | 3600000(1h) |
解析不到恢复时间时的降级时长(超过 maxDegradeMs 时被钳制) |
maxDegradeMs |
- | 2592000000(30d) |
降级时长硬上限,钳制重置时间解析结果、defaultDegradeMs 与手动 /chain degrade <时长> |
transcriptNotice |
- | true |
是否在转录里追加可见条目(不影响模型上下文) |
stateFile |
- | ~/.pi/agent/provider-chain-state.json |
状态文件路径(支持 ~) |
内置默认 degradePatterns:AccountQuotaExceeded、QuotaExceeded、AgentPlanQuotaExceeded、SetLimitExceeded、InvalidSubscription、subscription has expired、AccountOverdueError、ServiceOverdue、overdue balance、usage quota、exceeded the quota、insufficient_quota、out of budget、billing。
纯字母/词组项会自动加词边界(
\bbilling\b),减少误判;含_、数字、驼峰的项按原样匹配。
与 pi-subagents 配合
虚拟模型是本扩展在加载时注册的。前台子代理继承父会话扩展注册的 provider,但不继承 pi.registerVirtualModel() 注册的虚拟模型,因此子代理一旦继承到虚拟模型(例如默认模型 yansongda/s-main),会因模型解析失败而无法启动。启动失败的报错会直接点明原因(It inherits the providers they registered, but not virtual models registered with pi.registerVirtualModel().)。让子会话额外加载本扩展即可:
{
"subagents": {
"defaultSubagentOnlyExtensions": ["npm:pi-provider-chain"]
}
}
写入 ~/.pi/agent/settings.json(或 $PI_CODING_AGENT_DIR/settings.json)。值按 Pi 的包源解析:npm:pi-provider-chain 会在首次子代理启动时自动从 npm 安装到 <agentDir>/tmp/extensions/npm/<hash>/ 并复用,不用记路径;也可以写扩展文件路径(绝对路径,支持 ~/)。
- 本地开发请用路径:
npm:形式加载的是 npm 上的已发布副本,与pi install <本地路径>的工作树不是同一份,会出现父子版本漂移;此时写仓库内extensions/provider-chain.ts的绝对路径。 npm:形式首次安装需要联网;PI_OFFLINE=1会静默跳过该源(子会话拿不到扩展),此时请改用路径。subagents.defaultSubagentOnlyExtensions只给子会话追加加载,不改变 ambient 扩展发现;不要改用subagents.defaultExtensions—— 它一旦存在(哪怕值是[])就会关闭子会话的 ambient 扩展发现。- 只想影响某个 agent:在其 frontmatter 写
subagentOnlyExtensions:(追加、保留 ambient)或extensions:(只要出现,包括[],就关闭该子会话的 ambient 扩展发现)。 async: true的后台子代理默认会加载 ambient 扩展,通常无需额外配置。- 权威上限
capabilityCeiling.denyExtensions会压掉上述全部配置。 - 验证:发起一个继承虚拟模型的子代理;或让 agent 调用
subagent({ action: "get", agent: "<名称>" })查看该 agent 生效的Subagent-only extensions;/subagents-doctor可看诊断。 - 子代理与父会话共用
provider-chain-state.json,降级档位跨进程生效。
命令
| 命令 | 说明 |
|---|---|
/chain 或 /chain status |
当前选中模型、配置与状态文件路径、每个虚拟模型的链路与降级状态(子命令有补全) |
/chain degrade [时长] |
手动降级到第 1 档备用(30m / 2h / 1d,默认 defaultDegradeMs,上限 maxDegradeMs) |
/chain reset |
清除当前模型所属 provider 的降级状态(别名 primary / clear) |
/chain reload |
重新加载扩展与配置(等价 /reload) |
状态文件
默认 ~/.pi/agent/provider-chain-state.json:
{
"version": 1,
"degraded": {
"volcengine": { "level": 1, "until": 1793203199000, "reason": "AccountQuotaExceeded", "at": 1792055998922 }
}
}
- key 为
chain[0]的 provider(同一 provider 下所有虚拟模型共享档位)。 - 写入采用
tmp + rename原子替换;并发写按「同 key 取更大 level」单调合并(父/子进程不会互相把档位打回去)。 - 读-改-写之间没有文件锁:两个进程同时降级不同 provider 时,理论上可能丢一条记录;该 provider 的下一次请求会重新降级,可自愈。
- 每次写入会清理
until + 24h < now的历史条目。 - 恢复后会写入
level: 0, reason: "recovered"的「墓碑」条目:让「已恢复」提示只出现一次,且并发下不会覆盖他进程刚写入的活跃降级(单调合并保留更深的档位)。 - 写入失败时降级仍在本进程生效,并通过提示告知「状态文件写入失败,本次降级仅本进程生效」。
工作原理
选中 yansongda/deepseek-v4.1-flash(虚拟模型)
│ route(request, ctx)
▼
读状态文件 → level(0 = 主用,1..n = 后备链路下标,窗口过期自动回到 0)
│
├─ retry 且错误文本命中 degradePatterns → 推进 level 并写状态(快路径)
├─ retry / continuation 且失败或上一模型就是当前档位 → 复用(保缓存)
├─ 否则从当前档位向后找有凭证的模型,再向前兜底
▼
返回物理模型 + 思考档位(assistant 消息记录的是物理模型)
不可重试的额度错误(如 403 订阅过期)由 agent_before_settle 兜底:命中后先写入降级状态,再剔除这条失败消息并续跑一次;若定位不到失败消息所在的 entry(无法剔除),仍会写入状态,让下一次请求走备用。
页脚常驻显示当前选中模型的目标物理模型(用链路第一个 provider 时 → volcengine/…;降级中 ↓ workbuddy/… · 10-28 23:59 恢复 volcengine,时间取该链路仍活跃条目里最早恢复的那条、末尾的 provider 即该窗口所属方),其它模型的降级信息由 /chain status 全量展示;转录条目前缀为 [chain],共三种可见条目:降级(kind: "degrade",含受限 provider from、原因 reason 与触发路径 trigger;文案与 notify 同一句)、恢复(kind: "recover")、路由异常(kind: "error",回落主用但把异常留在转录里便于排查)。
开发
bun install # 安装 devDeps / peer
bun run test # bun test:跑 tests/ 下全部 node:test 用例
bun run typecheck # tsc --noEmit
bun run pack # bun pm pack --dry-run,检查发布内容
# 同一套用例也可用 Node ≥ 22.18 直跑 TS(无测试框架);CI 以 Node 24 守护此承诺
bun run test:node # 等价 node --test "tests/**/*.test.ts"
extensions/provider-chain.ts 装配层(注册虚拟模型 / 事件 / 命令 / 渲染器 + ctx→端口适配)
src/constants.ts 机器常量 + 用户文案模板(叶子模块,零依赖)
src/model-ref.ts provider/id 解析
src/degrade.ts 降级判定与时间解析(纯函数)
src/state.ts 状态文件读写(原子写 / 单调合并 / 清理)
src/config.ts 配置加载与校验
src/route-policy.ts 路由决策(纯函数)
src/router.ts 降级状态机与决策(端口注入,可单测)
src/transcript.ts 转录条目契约与会话遍历(纯函数)
src/command.ts /chain 参数解析(纯函数)
src/ui.ts 用户文案组装(notify / 页脚 / 转录条目 / 命令输出)
examples/ 可直接复制的配置示例 + 字段说明
tests/ node:test 单测(含 stub pi 的装配集成测试)
docs/ 架构与设计文档(索引见 docs/README.md)
License
MIT