mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-08-04 11:43:57 +08:00
fix: sync openclaw and hermes live provider updates (#5098)
* fix: sync openclaw live provider updates * fix: sync hermes live provider updates * fix test: hermes live import stores models as array after denormalize The test import_hermes_providers_from_live_updates_existing_provider_from_live seeded models as a dict, but import_hermes_providers_from_live reads via get_providers() which calls denormalize_provider_models_for_read(), converting models from YAML dict to UI-friendly array. The test assertion accessed models as dict -> Null -> assertion failure. Fix: access models as array by index, matching the actual storage format after live import. Also verify the id field is preserved in the denormalized output.
This commit is contained in:
@@ -689,16 +689,16 @@ pub fn run() {
|
|||||||
}
|
}
|
||||||
match crate::services::provider::import_openclaw_providers_from_live(&app_state) {
|
match crate::services::provider::import_openclaw_providers_from_live(&app_state) {
|
||||||
Ok(count) if count > 0 => {
|
Ok(count) if count > 0 => {
|
||||||
log::info!("✓ Imported {count} OpenClaw provider(s) from live config");
|
log::info!("✓ Synced {count} OpenClaw provider(s) from live config");
|
||||||
}
|
}
|
||||||
Ok(_) => log::debug!("○ No new OpenClaw providers to import"),
|
Ok(_) => log::debug!("○ No OpenClaw provider changes from live config"),
|
||||||
Err(e) => log::warn!("✗ Failed to import OpenClaw providers: {e}"),
|
Err(e) => log::warn!("✗ Failed to import OpenClaw providers: {e}"),
|
||||||
}
|
}
|
||||||
match crate::services::provider::import_hermes_providers_from_live(&app_state) {
|
match crate::services::provider::import_hermes_providers_from_live(&app_state) {
|
||||||
Ok(count) if count > 0 => {
|
Ok(count) if count > 0 => {
|
||||||
log::info!("✓ Imported {count} Hermes provider(s) from live config");
|
log::info!("✓ Synced {count} Hermes provider(s) from live config");
|
||||||
}
|
}
|
||||||
Ok(_) => log::debug!("○ No new Hermes providers to import"),
|
Ok(_) => log::debug!("○ No Hermes provider changes from live config"),
|
||||||
Err(e) => log::warn!("✗ Failed to import Hermes providers: {e}"),
|
Err(e) => log::warn!("✗ Failed to import Hermes providers: {e}"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1479,6 +1479,7 @@ pub fn import_openclaw_providers_from_live(state: &AppState) -> Result<usize, Ap
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut imported = 0;
|
let mut imported = 0;
|
||||||
|
let mut updated = 0;
|
||||||
let existing_ids = state.db.get_provider_ids("openclaw")?;
|
let existing_ids = state.db.get_provider_ids("openclaw")?;
|
||||||
|
|
||||||
for (id, config) in providers {
|
for (id, config) in providers {
|
||||||
@@ -1492,12 +1493,6 @@ pub fn import_openclaw_providers_from_live(state: &AppState) -> Result<usize, Ap
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip if already exists in database
|
|
||||||
if existing_ids.contains(&id) {
|
|
||||||
log::debug!("OpenClaw provider '{id}' already exists in database, skipping");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert to Value for settings_config
|
// Convert to Value for settings_config
|
||||||
let settings_config = match serde_json::to_value(&config) {
|
let settings_config = match serde_json::to_value(&config) {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
@@ -1507,6 +1502,30 @@ pub fn import_openclaw_providers_from_live(state: &AppState) -> Result<usize, Ap
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if existing_ids.contains(&id) {
|
||||||
|
match state.db.get_provider_by_id(&id, "openclaw") {
|
||||||
|
Ok(Some(existing)) => {
|
||||||
|
if existing.settings_config != settings_config {
|
||||||
|
let mut provider = existing;
|
||||||
|
provider.settings_config = settings_config;
|
||||||
|
if let Err(e) = state.db.save_provider("openclaw", &provider) {
|
||||||
|
log::warn!(
|
||||||
|
"Failed to update OpenClaw provider '{id}' from live config: {e}"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
updated += 1;
|
||||||
|
log::info!("Updated OpenClaw provider '{id}' from live config");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None) => {
|
||||||
|
log::warn!("OpenClaw provider '{id}' disappeared while importing live config")
|
||||||
|
}
|
||||||
|
Err(e) => log::warn!("Failed to look up OpenClaw provider '{id}': {e}"),
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Determine display name: use first model name if available, otherwise use id
|
// Determine display name: use first model name if available, otherwise use id
|
||||||
let display_name = config
|
let display_name = config
|
||||||
.models
|
.models
|
||||||
@@ -1531,7 +1550,7 @@ pub fn import_openclaw_providers_from_live(state: &AppState) -> Result<usize, Ap
|
|||||||
log::info!("Imported OpenClaw provider '{id}' from live config");
|
log::info!("Imported OpenClaw provider '{id}' from live config");
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(imported)
|
Ok(imported + updated)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Import all providers from Hermes live config to database
|
/// Import all providers from Hermes live config to database
|
||||||
@@ -1548,6 +1567,7 @@ pub fn import_hermes_providers_from_live(state: &AppState) -> Result<usize, AppE
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut imported = 0;
|
let mut imported = 0;
|
||||||
|
let mut updated = 0;
|
||||||
let existing_ids = state.db.get_provider_ids("hermes")?;
|
let existing_ids = state.db.get_provider_ids("hermes")?;
|
||||||
|
|
||||||
for (name, config) in providers {
|
for (name, config) in providers {
|
||||||
@@ -1557,9 +1577,27 @@ pub fn import_hermes_providers_from_live(state: &AppState) -> Result<usize, AppE
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip if already exists in database
|
|
||||||
if existing_ids.contains(&name) {
|
if existing_ids.contains(&name) {
|
||||||
log::debug!("Hermes provider '{name}' already exists in database, skipping");
|
match state.db.get_provider_by_id(&name, "hermes") {
|
||||||
|
Ok(Some(existing)) => {
|
||||||
|
if existing.settings_config != config {
|
||||||
|
let mut provider = existing;
|
||||||
|
provider.settings_config = config;
|
||||||
|
if let Err(e) = state.db.save_provider("hermes", &provider) {
|
||||||
|
log::warn!(
|
||||||
|
"Failed to update Hermes provider '{name}' from live config: {e}"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
updated += 1;
|
||||||
|
log::info!("Updated Hermes provider '{name}' from live config");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None) => {
|
||||||
|
log::warn!("Hermes provider '{name}' disappeared while importing live config")
|
||||||
|
}
|
||||||
|
Err(e) => log::warn!("Failed to look up Hermes provider '{name}': {e}"),
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1580,7 +1618,7 @@ pub fn import_hermes_providers_from_live(state: &AppState) -> Result<usize, AppE
|
|||||||
log::info!("Imported Hermes provider '{name}' from live config");
|
log::info!("Imported Hermes provider '{name}' from live config");
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(imported)
|
Ok(imported + updated)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove a Hermes provider from live config
|
/// Remove a Hermes provider from live config
|
||||||
|
|||||||
@@ -310,6 +310,32 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn hermes_provider(id: &str) -> Provider {
|
||||||
|
Provider {
|
||||||
|
id: id.to_string(),
|
||||||
|
name: format!("Provider {id}"),
|
||||||
|
settings_config: json!({
|
||||||
|
"api": "openai-chat",
|
||||||
|
"base_url": "https://api.example.com/v1",
|
||||||
|
"api_key": "test-key",
|
||||||
|
"models": {
|
||||||
|
"gpt-4o": {
|
||||||
|
"name": "GPT-4o"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
website_url: None,
|
||||||
|
category: Some("custom".to_string()),
|
||||||
|
created_at: Some(1),
|
||||||
|
sort_index: Some(0),
|
||||||
|
notes: None,
|
||||||
|
meta: None,
|
||||||
|
icon: None,
|
||||||
|
icon_color: None,
|
||||||
|
in_failover_queue: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn opencode_provider(id: &str) -> Provider {
|
fn opencode_provider(id: &str) -> Provider {
|
||||||
Provider {
|
Provider {
|
||||||
id: id.to_string(),
|
id: id.to_string(),
|
||||||
@@ -1362,6 +1388,89 @@ base_url = "http://localhost:8080"
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn import_openclaw_providers_from_live_updates_existing_provider_from_live() {
|
||||||
|
with_test_home(|state, _| {
|
||||||
|
let mut provider = openclaw_provider("existing-openclaw");
|
||||||
|
provider.settings_config["models"] = json!([
|
||||||
|
{
|
||||||
|
"id": "claude-sonnet-4",
|
||||||
|
"name": "Claude Sonnet 4"
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
state
|
||||||
|
.db
|
||||||
|
.save_provider(AppType::OpenClaw.as_str(), &provider)
|
||||||
|
.expect("seed existing openclaw provider");
|
||||||
|
|
||||||
|
let mut live_settings = provider.settings_config.clone();
|
||||||
|
live_settings["baseUrl"] = Value::String("https://api.example.com/v1".to_string());
|
||||||
|
live_settings["models"][0]["name"] = Value::String("Claude Sonnet 4.1".to_string());
|
||||||
|
crate::openclaw_config::set_provider(&provider.id, live_settings)
|
||||||
|
.expect("seed edited live openclaw provider");
|
||||||
|
|
||||||
|
let updated = import_openclaw_providers_from_live(state)
|
||||||
|
.expect("import openclaw providers from live");
|
||||||
|
assert_eq!(updated, 1);
|
||||||
|
|
||||||
|
let saved = state
|
||||||
|
.db
|
||||||
|
.get_provider_by_id(&provider.id, AppType::OpenClaw.as_str())
|
||||||
|
.expect("query updated openclaw provider")
|
||||||
|
.expect("openclaw provider should exist");
|
||||||
|
assert_eq!(saved.name, provider.name);
|
||||||
|
assert_eq!(
|
||||||
|
saved.settings_config["baseUrl"],
|
||||||
|
json!("https://api.example.com/v1")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
saved.settings_config["models"][0]["name"],
|
||||||
|
json!("Claude Sonnet 4.1")
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn import_hermes_providers_from_live_updates_existing_provider_from_live() {
|
||||||
|
with_test_home(|state, _| {
|
||||||
|
let provider = hermes_provider("existing-hermes");
|
||||||
|
state
|
||||||
|
.db
|
||||||
|
.save_provider(AppType::Hermes.as_str(), &provider)
|
||||||
|
.expect("seed existing hermes provider");
|
||||||
|
|
||||||
|
let mut live_settings = provider.settings_config.clone();
|
||||||
|
live_settings["base_url"] = Value::String("https://api.hermes.example/v1".to_string());
|
||||||
|
live_settings["models"]["gpt-4o"]["name"] = Value::String("GPT-4o Updated".to_string());
|
||||||
|
crate::hermes_config::set_provider(&provider.id, live_settings)
|
||||||
|
.expect("seed edited live hermes provider");
|
||||||
|
|
||||||
|
let updated = import_hermes_providers_from_live(state)
|
||||||
|
.expect("import hermes providers from live");
|
||||||
|
assert_eq!(updated, 1);
|
||||||
|
|
||||||
|
let saved = state
|
||||||
|
.db
|
||||||
|
.get_provider_by_id(&provider.id, AppType::Hermes.as_str())
|
||||||
|
.expect("query updated hermes provider")
|
||||||
|
.expect("hermes provider should exist");
|
||||||
|
assert_eq!(saved.name, provider.name);
|
||||||
|
assert_eq!(
|
||||||
|
saved.settings_config["base_url"],
|
||||||
|
json!("https://api.hermes.example/v1")
|
||||||
|
);
|
||||||
|
// models are denormalized from YAML dict to UI-friendly array by
|
||||||
|
// get_providers(), so access by index rather than dict key
|
||||||
|
assert_eq!(
|
||||||
|
saved.settings_config["models"][0]["name"],
|
||||||
|
json!("GPT-4o Updated")
|
||||||
|
);
|
||||||
|
assert_eq!(saved.settings_config["models"][0]["id"], json!("gpt-4o"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[serial]
|
#[serial]
|
||||||
fn legacy_additive_provider_still_errors_on_live_config_parse_failure() {
|
fn legacy_additive_provider_still_errors_on_live_config_parse_failure() {
|
||||||
|
|||||||
Reference in New Issue
Block a user