fix(providers): correct provider settings behavior#58
Conversation
Mingholy
left a comment
There was a problem hiding this comment.
Code Review: PR #58
Summary
This PR fixes several interrelated bugs in the provider settings flow across server and web:
saveWorkspaceConfigprovider set replacement -- Providers deleted in the admin UI were not actually removed from the config because the old code merged into the existing map instead of replacing it.getProvidersResponseextraction -- Inline code in the/api/providershandler was extracted to a reusable function inconfig.ts, with new behavior: personal providers whose${VAR}references don't resolve are now surfaced as disabled entries withmissingVarmetadata instead of being silently dropped.- Settings page
apiKeyRefpreservation -- Saving personal provider settings (e.g. changing models or baseUrl) no longer silently overwrites a custom${CUSTOM_KEY}reference with a generated${PROVIDERNAME_API_KEY}. - Admin dialog rename warning -- Renaming a workspace provider now warns the user that the API key cannot survive the rename and must be re-entered.
- Model selector missing-key hint -- Amber banner in the model dropdown shows which personal providers have unresolved env vars.
Bug Verification
Bug 1 (provider deletion): Confirmed real. In main, saveWorkspaceConfig does merged.providers[name] = ... inside a loop over cfg.providers, but never removes entries absent from cfg.providers. When the admin UI sends the desired provider set, stale providers survive.
Bug 2 (silent provider drop): Confirmed real. The old /api/providers handler only overlaid personal providers where hasKey was true. A user who configures a personal provider with ${MY_KEY} but hasn't yet created the vault env file would see the provider vanish from the UI with no explanation.
Bug 3 (apiKey ref overwrite): Confirmed real. The old SettingsPage.tsx save logic unconditionally wrote apiKey: \${${providerEnvVarName(name)}}`` regardless of whether the user touched the key. If the provider was originally configured with a non-standard var name, saving any other field (models, baseUrl, enabled) would silently break the key reference.
Bug 4 (rename key loss): Partially a bug, partially UX improvement. After rename, the server-side saveWorkspaceConfig looks up existingProv by the new name (which doesn't exist), so apiKey falls to "". The warning is the correct behavior given this server-side constraint.
Fix Analysis
All four fixes correctly address their respective bugs. A few observations:
Default resolution change (getProvidersResponse):
active = pCfg.default || Object.keys(pCfg.providers)[0] || activeThe old code was pCfg.default || active. The new fallback to Object.keys(pCfg.providers)[0] can set the default to a provider without a key -- e.g., a user adds a personal provider template but hasn't set the vault env yet. The UI would show this keyless provider as the "active" default, but pickProvider at runtime would skip it (it has requireKey logic). This creates a subtle mismatch between the UI indicator and actual runtime behavior. Not a blocker -- pickProvider is the safety net -- but worth being aware of. If you want strict correctness, filter to keyed providers before falling back:
active = pCfg.default || Object.keys(pCfg.providers).find(n => {
const p = pCfg.providers[n]
return typeof p.apiKey === "string" && p.apiKey.length > 0
}) || activereadPersonalDiskRaw extra read: The new getProvidersResponse calls readPersonalDiskRaw to extract the raw ${VAR} reference for missingVar. This is an additional uncached disk read on every GET /api/providers. loadPersonalConfig is already called (and cached); readPersonalDiskRaw bypasses that cache. For a settings endpoint the cost is negligible, but if this endpoint is polled frequently it could add up. Consider caching or combining with the existing read.
AdminDialog warnReEnterKey: The check !draft.providers[newName] is redundant -- the if (d.providers[newName]) return d guard inside setDraft already prevents the rename when the target name exists. Harmless, but unnecessary.
Code Quality
- Extraction to
getProvidersResponseis a solid improvement -- it centralizes provider response assembly, makes it testable, and exposes properProviderInfo/ProvidersResponsetypes. - Test coverage is well-targeted: the new test file covers the specific regression scenarios (provider deletion, missing var metadata, default fallback). Setup and teardown are clean.
apiKeyReftracking adds one field to the draft type and uses it precisely where needed. Minimal surface area.- Minor: trailing newline removal at end of
SettingsPage.tsx(line 1089) is fine but unrelated.
Security
No security concerns. The missingVar field only exposes the env var name (e.g., GHOST_API_KEY), never the value. The readPersonalDiskRaw call is scoped to the authenticated user's personal dir.
Verdict
LGTM with one minor suggestion. The core fixes are correct, well-scoped, and properly tested. The only substantive nit is the default-resolution fallback to Object.keys(pCfg.providers)[0] which can pick a keyless provider as "active" in the API response -- consider filtering to keyed providers in that fallback. Otherwise, good to merge.
…vider Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
已修复 default provider 回退逻辑( 改动说明: 原来的 fallback: 修复后只回退到有真实 apiKey 的 provider: commit: 08b8646 |
最终 Review 结论 · ✅ 可合并意图与达成度:四个关联 bug 在最新代码(
回归测试 历史 review comment:default 回退可能选中无 key 的 provider —— 已在 安全(密钥泄漏 / SSRF):clean。
遗留 nit(非阻塞): 无 Critical/High/Medium 问题。 🤖 Generated with Claude Code |
Summary
Test plan
cd web && bunx tsc -bbun run buildgit diff --checkweb, andserverpackage manifests do not define alintscript.Screenshots
1. Missing-key hint in the model selector
The newly surfaced "missing key" hint appears when a personal provider references a
${VAR}that is not resolvable from the vault env. Reproduced by configuring a provider (OpenRouter->${OPENROUTER_API_KEY}) without defining that variable, then opening the model selector./api/providers-- the model selector gives no indication that a provider is unusable or why.missingVarmetadata, and the model selector shows an amber hint:Missing key: OpenRouter needs OPENROUTER_API_KEY in Settings / AI Providers.2. Renaming a workspace provider warns that the stored key cannot be carried over
Renaming a workspace provider that has a stored key (Settings -> Workspace AI Providers). Because the key reference is derived from the provider name, the old reference cannot survive the rename -- the new code clears
hasKeyand warns the user to re-enter the key.hasKeyis cleared (key field empties, provider disabled) and a warning appears:Renamed "idealab" to "idealab-renamed". Re-enter and test the workspace API key before saving; the stored key reference cannot be preserved through this rename.