mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-05-22 21:50:44 +08:00
refactor(hermes): share provider-source marker constants and write guard
After /simplify review of the P1-3 second wave, two small cleanups: - Lift the `_cc_source` / `providers_dict` magic strings out of ProviderCard into a shared helper (`isHermesReadOnlyProvider`) and named constants in hermesProviderPresets.ts. Front-end and back-end now document the same marker contract in two mirrored places instead of drifting strings. - Replace the duplicate `is_dict_only_provider` + `format!` branches at the top of `set_provider` / `remove_provider` with a single `ensure_provider_writable(config, name, verb)` guard. Future error copy tweaks only have to happen once. No behaviour change; all 52 hermes_config tests stay green.
This commit is contained in:
@@ -635,6 +635,23 @@ pub fn get_providers() -> Result<serde_json::Map<String, serde_json::Value>, App
|
||||
Ok(map)
|
||||
}
|
||||
|
||||
/// Reject writes that would target a dict-only overlay entry.
|
||||
///
|
||||
/// `verb` is inlined into the user-facing error so both "edit" and "remove"
|
||||
/// callers can share one implementation.
|
||||
fn ensure_provider_writable(
|
||||
config: &serde_yaml::Value,
|
||||
name: &str,
|
||||
verb: &str,
|
||||
) -> Result<(), AppError> {
|
||||
if is_dict_only_provider(config, name) {
|
||||
return Err(AppError::Config(format!(
|
||||
"Provider '{name}' is managed by Hermes' 'providers:' dict — {verb} via Hermes Web UI"
|
||||
)));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// True when `name` appears in `providers:` dict but not in `custom_providers:`
|
||||
/// list — i.e. it is a read-only overlay CC Switch must not touch.
|
||||
fn is_dict_only_provider(config: &serde_yaml::Value, name: &str) -> bool {
|
||||
@@ -691,11 +708,7 @@ pub fn set_provider(
|
||||
let _guard = hermes_write_lock().lock()?;
|
||||
|
||||
let config = read_hermes_config()?;
|
||||
if is_dict_only_provider(&config, name) {
|
||||
return Err(AppError::Config(format!(
|
||||
"Provider '{name}' is managed by Hermes' 'providers:' dict — edit via Hermes Web UI"
|
||||
)));
|
||||
}
|
||||
ensure_provider_writable(&config, name, "edit")?;
|
||||
let mut providers: Vec<serde_yaml::Value> = config
|
||||
.get("custom_providers")
|
||||
.and_then(|v| v.as_sequence())
|
||||
@@ -768,11 +781,7 @@ pub fn remove_provider(name: &str) -> Result<HermesWriteOutcome, AppError> {
|
||||
let _guard = hermes_write_lock().lock()?;
|
||||
let config = read_hermes_config()?;
|
||||
|
||||
if is_dict_only_provider(&config, name) {
|
||||
return Err(AppError::Config(format!(
|
||||
"Provider '{name}' is managed by Hermes' 'providers:' dict — remove via Hermes Web UI"
|
||||
)));
|
||||
}
|
||||
ensure_provider_writable(&config, name, "remove")?;
|
||||
|
||||
let mut providers: Vec<serde_yaml::Value> = config
|
||||
.get("custom_providers")
|
||||
|
||||
@@ -15,6 +15,7 @@ import SubscriptionQuotaFooter from "@/components/SubscriptionQuotaFooter";
|
||||
import CopilotQuotaFooter from "@/components/CopilotQuotaFooter";
|
||||
import CodexOauthQuotaFooter from "@/components/CodexOauthQuotaFooter";
|
||||
import { PROVIDER_TYPES } from "@/config/constants";
|
||||
import { isHermesReadOnlyProvider } from "@/config/hermesProviderPresets";
|
||||
import { ProviderHealthBadge } from "@/components/providers/ProviderHealthBadge";
|
||||
import { FailoverPriorityBadge } from "@/components/providers/FailoverPriorityBadge";
|
||||
import { extractCodexBaseUrl } from "@/utils/providerConfigUtils";
|
||||
@@ -183,9 +184,7 @@ export function ProviderCard({
|
||||
// Hermes v12+ overlay entries live under the `providers:` dict and are
|
||||
// read-only here — writes have to go through Hermes Web UI.
|
||||
const isHermesReadOnly =
|
||||
appId === "hermes" &&
|
||||
(provider.settingsConfig as Record<string, unknown>)?._cc_source ===
|
||||
"providers_dict";
|
||||
appId === "hermes" && isHermesReadOnlyProvider(provider.settingsConfig);
|
||||
const isCodexOauth =
|
||||
provider.meta?.providerType === PROVIDER_TYPES.CODEX_OAUTH;
|
||||
|
||||
|
||||
@@ -5,6 +5,29 @@
|
||||
import type { ProviderCategory } from "../types";
|
||||
import type { PresetTheme, TemplateValueConfig } from "./claudeProviderPresets";
|
||||
|
||||
/**
|
||||
* Marker field and source values that `hermes_config.rs::get_providers`
|
||||
* injects onto each settings payload. Kept in sync with the Rust constants
|
||||
* `PROVIDER_SOURCE_FIELD` / `PROVIDER_SOURCE_CUSTOM_LIST` / `PROVIDER_SOURCE_DICT`.
|
||||
*/
|
||||
export const HERMES_PROVIDER_SOURCE_FIELD = "_cc_source";
|
||||
export const HERMES_PROVIDER_SOURCE_CUSTOM_LIST = "custom_providers";
|
||||
export const HERMES_PROVIDER_SOURCE_DICT = "providers_dict";
|
||||
|
||||
/**
|
||||
* True when the provider was sourced from Hermes' v12+ `providers:` dict —
|
||||
* CC Switch renders those read-only and routes edits to Hermes Web UI.
|
||||
*/
|
||||
export function isHermesReadOnlyProvider(settingsConfig: unknown): boolean {
|
||||
if (!settingsConfig || typeof settingsConfig !== "object") {
|
||||
return false;
|
||||
}
|
||||
const marker = (settingsConfig as Record<string, unknown>)[
|
||||
HERMES_PROVIDER_SOURCE_FIELD
|
||||
];
|
||||
return marker === HERMES_PROVIDER_SOURCE_DICT;
|
||||
}
|
||||
|
||||
/**
|
||||
* A model entry under a Hermes custom_provider.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user