omp packages

pi-provider-chain0.2.1

Pi 扩展:虚拟模型 + 链路(chain)路由 + 额度耗尽自动降级(quota / subscription / overdue 类错误自动切到后备链路)

GitHub Actions Published 7 days ago MIT ~675 downloads/mo

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.

Did it work for you?

Result

One report per person. A new report replaces your old one.

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