feat(settings): protect model provider deletion and cascade credential updates

- Block deletion of a model provider when it is referenced by any agent,
  returning an error that lists the agent names so the user knows what to unlink first
- When a provider's api_url or api_key is updated, automatically propagate
  the new credentials to all dependent agents: updates env_json in the database
  and patches on-disk config files (Claude Code settings.json, Gemini settings.json,
  Codex auth.json + config.toml, OpenCode auth.json) using the same field names
  and structure as the agent settings UI
- Fix error message display in provider dialogs for both Tauri and web transports,
  which throw plain objects rather than Error instances

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
xintaofei
2026-04-07 00:25:01 +08:00
parent 4245df2f4a
commit a3d5335e7f
17 changed files with 254 additions and 41 deletions

View File

@@ -89,8 +89,14 @@ export function AddModelProviderDialog({
toast.success(t("createSuccess"))
handleOpenChange(false)
onProviderAdded()
} catch (err) {
const msg = err instanceof Error ? err.message : String(err)
} catch (err: unknown) {
const raw = err as Record<string, unknown>
const msg =
typeof raw?.message === "string"
? raw.message
: err instanceof Error
? err.message
: String(err)
setError(msg)
} finally {
setLoading(false)

View File

@@ -97,8 +97,14 @@ export function EditModelProviderDialog({
toast.success(t("editSuccess"))
handleOpenChange(false)
onProviderUpdated()
} catch (err) {
const msg = err instanceof Error ? err.message : String(err)
} catch (err: unknown) {
const raw = err as Record<string, unknown>
const msg =
typeof raw?.message === "string"
? raw.message
: err instanceof Error
? err.message
: String(err)
setError(msg)
} finally {
setLoading(false)

View File

@@ -72,9 +72,21 @@ export function ModelProviderSettings() {
toast.success(t("deleteSuccess"))
setDeleteTarget(null)
await loadProviders()
} catch (err) {
const msg = err instanceof Error ? err.message : String(err)
toast.error(msg)
} catch (err: unknown) {
const raw = err as Record<string, unknown>
const msg =
typeof raw?.message === "string"
? raw.message
: err instanceof Error
? err.message
: String(err)
const prefix = "PROVIDER_IN_USE:"
if (msg.includes(prefix)) {
const agentNames = msg.substring(msg.indexOf(prefix) + prefix.length)
toast.error(t("deleteBlockedByAgent", { agents: agentNames }))
} else {
toast.error(msg)
}
}
}, [deleteTarget, loadProviders, t])