use super::*;
pub(in crate::controller) type CatalogFetch<'a> = &'a dyn Fn(&str, &str) -> Result<Vec<u8>>;
pub(super) const STAGED_CATALOG_FILE: &str = "models.json";
pub(in crate::controller) trait CatalogCache {
fn load(&self, profile_id: &str, fingerprint: &str) -> Option<String>;
fn store(&self, profile_id: &str, fingerprint: &str, body: &str);
}
pub(in crate::controller) struct SharedCatalogCache;
impl CatalogCache for SharedCatalogCache {
fn load(&self, profile_id: &str, fingerprint: &str) -> Option<String> {
crate::database::load_profile_config_cache(profile_id, "", fingerprint)
.ok()
.flatten()
}
fn store(&self, profile_id: &str, fingerprint: &str, body: &str) {
if let Err(error) = crate::database::save_profile_config_cache(
profile_id.to_owned(),
String::new(),
fingerprint.to_owned(),
body.to_owned(),
) {
tracing::warn!(profile_id, "could not cache the model catalog: {error:#}");
}
}
}
pub(in crate::controller) fn stage_codex_catalog(
profile_id: &str,
profile: &mj_core::config::HarnessProfile,
destination: &Path,
fetch: CatalogFetch<'_>,
cache: &dyn CatalogCache,
) -> Result<()> {
let Some(provider) = profile.codex_provider()? else {
return Ok(());
};
let Some(env_key) = provider.env_key.as_deref() else {
return Ok(());
};
let api_key = profile.environment.get(env_key).with_context(|| {
format!("profile {profile_id:?} has no {env_key} entry to read its model catalog with")
})?;
let url = format!("{}/models", provider.base_url.trim_end_matches('/'));
let fingerprint = format!("catalog:{}", provider.base_url);
let body = match fetch(&url, api_key) {
Ok(body) => {
if let Ok(text) = std::str::from_utf8(&body) {
cache.store(profile_id, &fingerprint, text);
}
body
}
Err(error) => match cache.load(profile_id, &fingerprint) {
Some(body) => {
tracing::warn!(
profile_id,
provider = %provider.id,
"could not fetch the model catalog from {url}, using the last cached copy: {error:#}"
);
body.into_bytes()
}
None => bail!(
"profile {profile_id:?}: could not fetch the model catalog from {url} and no cached copy is available: {error:#}"
),
},
};
let mut catalog = mj_core::codex_catalog::parse(&body)
.with_context(|| format!("profile {profile_id:?}: model catalog from {url}"))?;
apply_catalog_overrides(profile_id, &profile.home, &mut catalog)?;
stamp_guardian_reviewer(profile_id, profile, &mut catalog)?;
std::fs::create_dir_all(destination)?;
std::fs::write(destination.join(STAGED_CATALOG_FILE), catalog.to_json())?;
point_config_at_catalog(&destination.join("config.toml"))
}
pub(super) fn apply_catalog_overrides(
profile_id: &str,
home: &Path,
catalog: &mut mj_core::codex_catalog::CodexCatalog,
) -> Result<()> {
let path = home.join(STAGED_CATALOG_FILE);
let bytes = match std::fs::read(&path) {
Ok(bytes) => bytes,
Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(()),
Err(error) => {
return Err(error).with_context(|| format!("read {}", path.display()));
}
};
let overrides = mj_core::codex_catalog::parse_codex_shape(&bytes).with_context(|| {
format!(
"profile {profile_id:?}: model catalog overrides in {}",
path.display()
)
})?;
mj_core::codex_catalog::merge_overrides(catalog, &overrides);
Ok(())
}
pub(super) fn stamp_guardian_reviewer(
profile_id: &str,
profile: &mj_core::config::HarnessProfile,
catalog: &mut mj_core::codex_catalog::CodexCatalog,
) -> Result<()> {
let setting = profile
.guardian_review_model
.as_deref()
.unwrap_or(mj_core::config::GUARDIAN_REVIEW_NEWEST_FLASH);
if setting == mj_core::config::GUARDIAN_REVIEW_SESSION {
tracing::info!(
profile_id,
"guardian_review_model is \"session\"; Guardian reviews run on the session model"
);
return Ok(());
}
if setting == mj_core::config::GUARDIAN_REVIEW_NEWEST_FLASH {
match mj_core::codex_catalog::guardian_review_model(catalog.slugs()) {
Some(reviewer) => mj_core::codex_catalog::stamp_reviewer(catalog, &reviewer),
None => tracing::info!(
profile_id,
"the model catalog lists no flash model; Guardian reviews run on the session model"
),
}
return Ok(());
}
let slugs = catalog.slugs();
if !slugs.iter().any(|slug| slug == setting) {
bail!(
"profile {profile_id:?}: guardian_review_model {setting:?} is not in the provider's model catalog, which lists {}",
slugs.join(", ")
);
}
mj_core::codex_catalog::stamp_reviewer(catalog, setting);
Ok(())
}
pub(super) fn point_config_at_catalog(path: &Path) -> Result<()> {
let existing = std::fs::read_to_string(path).unwrap_or_default();
std::fs::write(
path,
format!("model_catalog_json = \"{STAGED_CATALOG_FILE}\"\n{existing}"),
)
.with_context(|| format!("point {} at the staged model catalog", path.display()))
}
pub(in crate::controller) fn fetch_catalog_over_https(url: &str, api_key: &str) -> Result<Vec<u8>> {
on_dedicated_thread(|| {
let response = reqwest::blocking::Client::builder()
.timeout(std::time::Duration::from_secs(8))
.redirect(reqwest::redirect::Policy::none())
.build()?
.get(url)
.bearer_auth(api_key)
.header(reqwest::header::ACCEPT, "application/json")
.send()?
.error_for_status()?;
Ok(response.bytes()?.to_vec())
})
}