Files
EasyCLIProxyAPI/src-tauri/src/codex_catalog/runtime_context.rs
T

121 lines
4.9 KiB
Rust

use super::*;
pub(crate) fn apply_configured_context_limits(
runtime_models: &mut [CodexRuntimeModel],
content: &str,
) -> Result<(), String> {
let document: serde_norway::Value = serde_norway::from_str(content)
.map_err(|error| format!("Failed to parse kernel model context configuration: {error}"))?;
let root = document
.as_mapping()
.ok_or("Kernel configuration root must be a YAML mapping")?;
let mut limits: HashMap<String, u64> = HashMap::new();
for section in crate::MODEL_ALIAS_CONFIG_SECTIONS {
let Some(providers) =
crate::yaml_mapping_value(root, section).and_then(serde_norway::Value::as_sequence)
else {
continue;
};
for provider in providers {
let Some(models) = provider
.as_mapping()
.and_then(|provider| crate::yaml_mapping_value(provider, "models"))
.and_then(serde_norway::Value::as_sequence)
else {
continue;
};
for model in models {
let Some((_, public_name, _)) = crate::configured_model_identity(model) else {
continue;
};
let Some(limit) = model
.as_mapping()
.and_then(|model| crate::yaml_mapping_value(model, "max-context-length"))
.and_then(serde_norway::Value::as_u64)
.filter(|value| *value > 0)
else {
continue;
};
limits
.entry(normalize_id(&public_name))
.and_modify(|existing| *existing = (*existing).min(limit))
.or_insert(limit);
}
}
}
for runtime in runtime_models {
if let Some(&limit) = limits.get(&normalize_id(&runtime.slug)) {
runtime.context_window = Some(limit);
runtime.max_context_window = Some(limit);
runtime.context_source = "configuration";
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn codex_client_context_defaults_are_preserved_in_editor_and_generated_catalog() {
let runtime = parse_runtime_models(&serde_json::json!({"models":[
{"slug":"gpt-5.6-sol","context_window":272000,"max_context_window":872000},
{"slug":"fast-alias","context_window":128000,"max_context_window":256000},
{"slug":"missing-context"}
]}))
.unwrap();
let sources = parse_sources(MODEL_CATALOG_JSON).unwrap();
let state = CatalogState {
sources,
json: MODEL_CATALOG_JSON.to_string(),
customizations: Default::default(),
};
let snapshot = customizations::snapshot_for_state(&runtime, &state).unwrap();
let snapshot = serde_json::to_value(snapshot).unwrap();
let prepared =
prepare_catalog_with_customizations(&runtime, &state.sources, &state.customizations)
.unwrap();
let catalog: Value = serde_json::from_str(&prepared.json).unwrap();
for (slug, context, maximum, source) in [
("gpt-5.6-sol", 272_000, 872_000, "client"),
("fast-alias", 128_000, 256_000, "client"),
("missing-context", 272_000, 272_000, "template"),
] {
let model = snapshot["models"]
.as_array()
.unwrap()
.iter()
.find(|model| model["slug"] == slug)
.unwrap();
for field in ["configuration", "defaults"] {
assert_eq!(model[field]["context_window"], context);
assert_eq!(model[field]["max_context_window"], maximum);
}
assert_eq!(model["contextSource"], source);
let generated = catalog["models"]
.as_array()
.unwrap()
.iter()
.find(|model| model["slug"] == slug)
.unwrap();
assert_eq!(generated["context_window"], context);
assert_eq!(generated["max_context_window"], maximum);
}
}
#[test]
fn core_configured_context_limit_takes_precedence_over_client_defaults() {
let mut runtime = parse_runtime_models(&serde_json::json!({"models":[
{"slug":"custom-alias","context_window":921000,"max_context_window":921000},
{"slug":"unmodified","context_window":128000,"max_context_window":128000}
]}))
.unwrap();
apply_configured_context_limits(&mut runtime, "codex-api-key:\n - models:\n - name: upstream-model\n alias: custom-alias\n max-context-length: 64000\n").unwrap();
assert_eq!(runtime[0].context_window, Some(64_000));
assert_eq!(runtime[0].max_context_window, Some(64_000));
assert_eq!(runtime[0].context_source, "configuration");
assert_eq!(runtime[1].context_window, Some(128_000));
}
}