fix(v8): preserve migrated config and correct provider requests

- Preserve provider credentials, aliases, headers, and network settings during migration
- Normalize Claude cloak metadata and repair previously saved invalid types
- Use streaming Gemini health probes and honor group and key proxy settings
- Retain Codex customizations for temporarily unavailable models
- Add regression tests for migration, metadata, proxy routing, and catalog persistence
This commit is contained in:
lzt404 committed 2026-10-04 17:31:52 +08:00
1 parent 2fd0f73f4c
commit 06fd58d3d8
15 files changed
+433 -35

No files matched your search

+57 -1
View File
@@ -370,7 +370,17 @@ fn save_for_state(
state: &mut CatalogState,
) -> Result<CatalogEditorSnapshot, String> {
let snapshot = snapshot_for_state(runtime_models, state)?;
let customizations = customizations_from_request(&snapshot, request)?;
let mut customizations = customizations_from_request(&snapshot, request)?;
// The public catalog can temporarily omit disabled or unavailable models.
// Editing visible models must not erase the saved settings for those IDs.
let visible: HashSet<String> = snapshot.models.iter()
.map(|model| normalize_id(&model.slug))
.collect();
for (slug, configuration) in &state.customizations {
if !visible.contains(slug) {
customizations.insert(slug.clone(), configuration.clone());
}
}
let saved = SavedCustomizations {
version: 1,
models: customizations,
@@ -423,6 +433,52 @@ mod tests {
))
}
#[test]
fn saving_visible_models_preserves_unavailable_models_and_allows_visible_reset() {
let mut state = CatalogState {
sources: parse_sources(MODEL_CATALOG_JSON).unwrap(),
json: MODEL_CATALOG_JSON.to_string(),
customizations: Default::default(),
};
let hidden = serde_json::json!({
"base_instructions": "Saved prompt for temporarily unavailable A",
"context_window": 131_072,
"max_context_window": 262_144,
}).as_object().unwrap().clone();
state.customizations.insert("model-a".to_string(), hidden.clone());
let runtime = vec![runtime_model("model-b")];
let snapshot = snapshot_for_state(&runtime, &state).unwrap();
let mut configuration = snapshot.models[0].configuration.clone();
configuration.insert("display_name".to_string(), Value::String("Edited B".to_string()));
let path = temporary_path();
let saved = save_for_state(&path, &runtime, CatalogEditorRequest {
revision: snapshot.revision,
models: vec![CatalogEditorModelRequest { slug: "model-b".to_string(), configuration }],
}, &mut state).unwrap();
let persisted = decode_customizations(&std::fs::read(&path).unwrap()).unwrap();
assert_eq!(persisted.get("model-a"), Some(&hidden));
assert_eq!(persisted["model-b"]["display_name"], "Edited B");
save_for_state(&path, &runtime, CatalogEditorRequest {
revision: saved.revision,
models: vec![CatalogEditorModelRequest {
slug: "model-b".to_string(), configuration: saved.models[0].defaults.clone(),
}],
}, &mut state).unwrap();
assert!(!state.customizations.contains_key("model-b"));
assert_eq!(state.customizations.get("model-a"), Some(&hidden));
let empty = snapshot_for_state(&[], &state).unwrap();
save_for_state(&path, &[], CatalogEditorRequest {
revision: empty.revision, models: vec![],
}, &mut state).unwrap();
assert_eq!(decode_customizations(&std::fs::read(&path).unwrap()).unwrap().get("model-a"), Some(&hidden));
let restored = snapshot_for_state(&[runtime_model("model-a")], &state).unwrap();
assert_eq!(restored.models[0].configuration["base_instructions"], hidden["base_instructions"]);
assert_eq!(restored.models[0].configuration["context_window"], 131_072);
std::fs::remove_file(path).unwrap();
}
#[test]
fn customizations_persist_apply_and_restore_to_the_current_template() {
let sources = parse_sources(MODEL_CATALOG_JSON).unwrap();