Skip to content

feat(providers): cc-switch 式供应商自动故障转移(同厂商同模型) - #385

Open
coder-hhx wants to merge 6 commits into
mainfrom
feat/provider-auto-failover
Open

feat(providers): cc-switch 式供应商自动故障转移(同厂商同模型)#385
coder-hhx wants to merge 6 commits into
mainfrom
feat/provider-auto-failover

Conversation

@coder-hhx

@coder-hhx coder-hhx commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Linked issue

Closes #384

Summary

修复自动故障转移设置无法持久化的 bug,并将故障转移重构为 cc-switch 语义的供应商级设计:

  • 持久化修复modelFailover 此前未接入任何持久化通道(SQLite 与 localStorage 均无),退出设置页重进即丢失。现已接入 localStorage 通道(与 selectedModel 同级),读/写/变更检测三处补齐。
  • 供应商级队列(cc-switch 语义):failover 只换供应商、不换模型——失败请求用当前对话的模型 ID 原样发给队列里下一个同厂商供应商;未激活该模型的供应商在本轮自动跳过。队列旧格式(供应商+模型对)自动迁移为供应商 id 并去重。
  • 运行时:按 provider::model 维度的熔断器(连续失败阈值 → 熔断 → 冷却 → 半开探测);错误分类器仅对供应商故障类错误(5xx/网络/鉴权/配额)转移,客户端请求类错误(如上下文超限)不转移;流包装器在首个可见内容前缓冲事件,丢弃的尝试对用户不可见;成功应答的供应商粘性保持并写回对话选择。
  • UI:每个厂商标签页下的故障转移卡片管理供应商队列(名称 + baseUrl 展示)与熔断参数;zh/en 文案更新;修复 {vendor} 占位符多次出现时只替换首个的问题。

Change scope

  • Modules: agent-gui / agent-gateway(web 前端)
  • Key paths:
    • crates/agent-gui/src/lib/providers/runtime/providerFailover.ts(新增:熔断器 + 错误分类 + 流包装器)
    • crates/agent-gui/src/lib/settings/{index,storage}.ts(队列模型 + 迁移 + 持久化)
    • crates/agent-gui/src/pages/chat/runtime/{providerRuntimeConfig,useSendChatTurn}.ts(每轮 failover 计划)
    • crates/agent-gui/src/lib/chat/runner/agentRunner.ts(候选装配 + withProviderFailover 接入)
    • crates/agent-gui/src/pages/settings/ProvidersSection.tsx + web 端孪生 + 双端 i18n

Screenshots / preview

供应商设置(右上角设置按钮)——供应商级故障转移队列:

image

实测转移过程:将 P1 供应商(RightCode sale)的 API key 置为无效后发起对话,状态栏显示「第 1 轮:RightCode(sale)· claude-fable-5 不可用,正在切换到 RightCode · claude-fable-5…」,随后由 P2 供应商以同一模型完成应答,对话选择自动切到应答的供应商。

28e03389b75e80a37b17d8fce6edf06c

Verification

  • cd crates/agent-gui && ./node_modules/.bin/tsc --noEmit ✅(web 端同样通过)
  • biome check 两端 0 error ✅
  • node --test test/settings/model-failover-normalize.test.mjs test/providers/provider-failover-plan.test.mjs test/providers/provider-failover.test.mjs — 29 项全部通过(熔断器/流包装器 16、计划构建 5、归一化+迁移 8)
  • node --test test/providers/provider-runtime-config.test.mjs test/chat/agent-runner.test.mjs — 上游相关套件 39 项通过
  • node scripts/check-mirror.mjs ✅(115 文件)
  • 真机验证:pnpm tauri dev 启动桌面端,复现原 bug 路径(退出设置→重进),配置保留;旧数据自动迁移;实际断供触发转移成功(见截图说明)

Pre-submit checklist

  • A requirement issue is linked (or this is a trivial fix that needs no issue, as explained in the summary).
  • Synced with the target branch; no merge conflicts.
  • The change is focused, with no unrelated modifications.
  • No secrets, tokens, or personal data included.
  • Docs are updated for changes affecting user behavior, deployment, or configuration.

🤖 Generated with Claude Code

…viders

Adds automatic provider failover for chat requests, configured per vendor
tab in provider settings and modeled after cc-switch's proxy-side design:

- Failover switches providers, never models: the failed request is re-sent
  to the next provider in the queue with the same model id the conversation
  was using; queued providers without that model active are skipped.
- Per-target circuit breaker (consecutive-failure threshold, cooldown with
  half-open probe) keyed by provider::model, plus an error classifier that
  only fails over on provider-fault-class errors (5xx/network/auth/quota),
  never on client-request-class errors (context overflow etc.).
- Stream wrapper buffers events until first committed content so consumers
  never see output from a discarded attempt; sticky winner keeps follow-up
  rounds on the provider that answered, and onSwitched pins the conversation
  selection to it.
- Settings: modelFailover is now persisted (it previously round-tripped
  through neither the SQLite nor the localStorage channel, so the toggle
  and queue silently reset on every settings reopen); queue entries are
  provider ids with legacy {customProviderId, model} entries migrated by
  collapsing to their provider id.
- UI: failover card under each vendor tab manages an ordered provider
  queue with breaker knobs; i18n zh/en updated, {vendor} placeholder now
  replaced at every occurrence.

Covered by unit tests for the breaker/stream wrapper, plan builder, and
settings normalization/migration; verified end-to-end in the running app.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@StackCairn
StackCairn marked this pull request as draft August 5, 2026 13:11
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

PR governance checks passed. Awaiting human review.

coder-hhx and others added 2 commits August 5, 2026 21:19
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coder-hhx
coder-hhx marked this pull request as ready for review August 5, 2026 13:27
@FlowerRealm

Copy link
Copy Markdown
Contributor

没啥希望通过, 仓库所有者表示希望更简洁的provider, 如有此需求推荐安装一个cc-switch

@FlowerRealm

Copy link
Copy Markdown
Contributor

pr可以留着, 之后有插件体系了可以放插件里

…s drawer

The failover card no longer renders under each vendor's provider list; it
now lives in the drawer behind the gear button in the providers header,
scoped to the active vendor tab. The drawer is renamed from "Custom
Settings" to "Advanced Settings" (自定义设置 → 高级设置) in zh/en on both
the GUI and WebUI ends.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ouoiouo pushed a commit to ouoiouo/LiveAgent that referenced this pull request Aug 8, 2026
…pstream PR Stack-Cairn#385)

- 修复 modelFailover 设置无法持久化的问题(接入 localStorage 通道)
- cc-switch 语义供应商级故障转移:只换供应商不换模型,未激活跳过
- provider::model 维度熔断器(失败阈值→熔断→冷却→半开探测)
- 流包装器在首个可见内容前缓冲,丢弃尝试对用户不可见
- 成功应答供应商粘性保持并写回对话选择
ouoiouo pushed a commit to ouoiouo/LiveAgent that referenced this pull request Aug 8, 2026
coder-hhx and others added 2 commits August 8, 2026 13:06
The 3-column grid was cramped inside the 440px advanced-settings drawer;
one field per row reads better there.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The hint ("未选择时,标题生成会使用当前对话使用的模型") described the
title-model picker but sat under the drawer heading; it now renders
between the 标题生成模型 label and the picker on both ends.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@su-fen

su-fen commented Aug 8, 2026

Copy link
Copy Markdown
Member

审核结论:当前不建议直接合入,存在 1 个功能阻断。

阻断:WebUI 的 modelFailover 没有进入 Gateway settings-sync

WebUI 已在 ProvidersSection.tsx 中通过 updateModelFailover() 修改 settings.modelFailover,但 GUI/WebUI 两端的同步协议均未包含该字段:

  • GatewaySettingsSyncPayload 没有 modelFailover
  • GATEWAY_SETTINGS_SYNC_FIELDS 没有 modelFailover
  • buildGatewaySettingsSyncPayload() 没有序列化 settings.modelFailover
  • applyGatewaySettingsSyncPayload() 没有应用远端 modelFailover

相关位置:

这会导致 WebUI 上的开关、队列和熔断参数只保存在浏览器本地,真正执行聊天请求的桌面 Agent 收不到配置;桌面端修改也不会同步回 WebUI。界面会显示已保存,但运行时故障转移不会按 WebUI 配置生效。

建议补齐双端同步协议,并增加以下回归测试:

  1. 修改 modelFailover 后,buildGatewaySettingsSyncUpdatePayload() 必须包含该字段。
  2. GUI/WebUI 的 modelFailover 可以完整 round-trip。
  3. WebUI 配置同步到桌面后,可以被 buildModelFailoverPlan() 构造成实际候选计划。

其余审核结果:当前 head cd75640c 的 8 项远端检查全部通过;我也在最新 main27a2d928)上完成了无冲突合并模拟,双端 production build、完整前端测试、mirror check 和 git diff --check 均通过。修复上述同步阻断并补测试后,再重新跑 CI 即可复审。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

供应商自动故障转移:设置无法持久化,且队列应为供应商级(cc-switch 语义)

3 participants