feat(providers): multiple API keys per provider with primary-first failover - #392
Open
YUZHEthefool wants to merge 1 commit into
Open
feat(providers): multiple API keys per provider with primary-first failover#392YUZHEthefool wants to merge 1 commit into
YUZHEthefool wants to merge 1 commit into
Conversation
StackCairn
marked this pull request as draft
August 7, 2026 16:36
Contributor
|
PR governance checks passed. Awaiting human review. |
YUZHEthefool
marked this pull request as ready for review
August 7, 2026 16:38
每个供应商可配置多个 API Key(参照 Cherry Studio)。请求优先使用主 Key
(首个),流式开始前遇到限额/鉴权/网络瞬时等可重试错误时,自动切换到
下一个 Key 重试,Key 用尽回退主 Key 继续重试预算;一旦开始产出内容则
不再换 Key,避免半截输出。单 Key 链路(用量查询、Go 模型拉取)零回归。
- 数据模型:CustomProvider 新增 apiKeys?: string[] + apiKeyCount?: number
(脱敏快照),apiKey 恒派生为 apiKeys[0],旧单 Key 快照自动迁移。
- 归一化:normalizeApiKeys 逐项 trim/去空/去重保序。
- 故障转移:streamRetry 增 apiKeyFailover,重试前 rotate;streamByApi 各
API 分支 factory 每次重读 attemptAuth(apiKey+鉴权头),仅替换鉴权头
保留代理路由/会话/自定义头。createProviderApiKeyFailover 构造 mutable
holder + rotate 回调。
- 网关同步:providerApiKeyUpdates 由 Record<string,string> 升级为
Record<string,string[]>;脱敏摘除 apiKeys 并写 apiKeyCount。
- 桌面端 UI:API Key 改为多行编辑器(增删/统一显隐)+ 故障转移提示。
WebUI 保持单行脱敏输入 + "已配置 N 个 Key" 提示(替换语义)。
- Cherry Studio 导入:导入全部启用的 Key(v1 逗号分隔 / v2 {isEnabled,
key} 数组),不再只取第一个、不再报"将使用第一个"警告。
- Rust 脱敏:redact_provider_credential 摘除 apiKeys、写 apiKeyCount。
- gateway web 镜像同步相同改动保持 gateway-build 一致。
Closes Stack-Cairn#365
YUZHEthefool
force-pushed
the
feat/provider-multi-api-key-failover
branch
from
August 7, 2026 16:43
3428b4d to
69e1495
Compare
Member
|
与之前做的故障转移存在冲突了 |
Member
|
#385 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Linked issue
Closes #365
Summary
Following Cherry Studio, a single provider can now configure multiple API keys. Requests use the primary key (the first one) first; on a retryable failure before streaming starts (quota / auth / transient network errors), the request automatically falls over to the next key, cycling back to the primary once exhausted and continuing within the existing retry budget. Once content is committed (
text_delta/thinking_delta/toolcall_start) the key is no longer switched, so partial output is never retried. Single-key paths (usage query, Go model fetch) are unchanged (zero regression), and prompt-cache hit rate is unaffected since the primary key is preferred and only swapped on failure.Change scope
agent-gui/src/lib/settings/index.ts,normalize.ts:CustomProvidergainsapiKeys?: string[]+apiKeyCount?: number(redacted snapshot);apiKeyis always derived asapiKeys[0].normalizeApiKeystrims/dedupes preserving order; legacy single-key snapshots migrate to a single-element array.agent-gui/src/lib/providers/runtime/:streamRetry.ts:StreamRetryConfiggainsapiKeyFailover;rotate(attemptIndex)is invoked before each retry.streamByApi.ts: each API branch's factory re-readsapiKey/auth headers from theattemptAuthholder on everyfactory()call;resolveAttemptHeadersreplaces only auth headers (authorization/x-api-key/x-goog-api-key), preserving proxy-routing/session/custom headers.requestOptions.ts:createProviderApiKeyFailover()builds the mutable holder + rotate callback (rebuilds auth headers per key).providerRuntimeConfig.ts/types.ts: switched from cross-request round-robin to primary-first;ProviderRuntimeConfigcarriesapiKeys.textOnlyRuntime.ts/chat/runner/agentRunner.ts: call sites wire inattemptAuth+apiKeyFailover.agent-gui/src/lib/settings/sync.ts:providerApiKeyUpdatesupgrades fromRecord<string,string>toRecord<string,string[]>; redaction stripsapiKeysand emitsapiKeyCount;mergeSyncedCustomProviderssupports sidecar full-replace /apiKeyConfigured===falseexplicit clear / otherwise keep-local.agent-gui/src/pages/settings/ProvidersSection.tsx: API Key becomes a multi-row editor (add/remove, unified show/hide) with a failover hint; WebUI (remote) keeps a single redacted input plus an "N keys configured, re-entering replaces all" hint (replace semantics, no regression).agent-gui/src/pages/settings/CherryStudioImportModal.tsx,src-tauri/src/commands/config/settings/cherry_import.rs: imports all enabled keys (v1 comma-separated / v2{isEnabled,key}array) instead of only the first; drops the "will use the first" warning.src-tauri/src/commands/config/settings/providers.rs:redact_provider_credentialstripsapiKeysand writesapiKeyCount.agent-gateway/web/src/(i18n/config.ts, lib/settings/{index,normalize,sync}.ts, pages/settings/ProvidersSection.tsx): mirrored to keepgateway-buildconsistent (no runtime layer, no failover wiring).agent-gui/test/settings/normalization.test.mjs,test/chat/agent-runner.test.mjs: updated sidecar assertions (string → string[]), addedcreateProviderApiKeyFailoverto thellmMock.Screenshots / preview
Failover behavior (smoke test, calling
createProviderApiKeyFailoverdirectly):Verification
cd crates/agent-gui && tsc --noEmit→ exit 0 (no type errors).node --test test/providers/*.test.mjs test/settings/normalization.test.mjs test/chat/agent-runner.test.mjs ...→ 859/863 pass; the 4 failures are pre-existing onmain(composer/caret DOM tests, reproduced on a cleanmainviagit stash, unrelated to this change).cargo build --release(src-tauri) → passes;cargo test --release→ 646 pass / 2 fail (automation_commands::hookPowerShell-in-bash environment issue, fails identically on cleanmain, unrelated).string→string[], agent-runnerllmMockgainscreateProviderApiKeyFailover).Pre-submit checklist
00a2c6fc).