use std::collections::HashMap;
use std::sync::{Arc, OnceLock};
use std::time::Duration;
use clawft_llm::{CachedResolver, PlacementResolver};
use clawft_types::config::Config;
use crate::local_llm_bridge::{local_placement_allowed, resolve_local_llm};
struct Hook {
cache: Arc<CachedResolver>,
roles: HashMap<String, String>,
}
static HOOK: OnceLock<Hook> = OnceLock::new();
pub type ResolveFn = Arc<dyn Fn(&str) -> Option<String> + Send + Sync>;
struct FnResolver(ResolveFn);
impl PlacementResolver for FnResolver {
fn resolve_base_url(&self, role: &str) -> Option<String> {
(self.0)(role)
}
}
pub fn install(
resolve: ResolveFn,
ttl: Duration,
roles: HashMap<String, String>,
) -> bool {
install_resolver(Arc::new(FnResolver(resolve)), ttl, roles)
}
pub fn install_resolver(
resolver: Arc<dyn PlacementResolver>,
ttl: Duration,
roles: HashMap<String, String>,
) -> bool {
HOOK.set(Hook {
cache: Arc::new(CachedResolver::new(resolver, ttl)),
roles,
})
.is_ok()
}
pub fn invalidate_all() {
if let Some(h) = HOOK.get() {
h.cache.invalidate_all();
}
}
pub fn placement_for(provider: &str, config: &Config) -> Option<(String, Arc<CachedResolver>)> {
let h = HOOK.get()?;
let role = h.roles.get(provider)?;
if provider != "local" {
return None;
}
let resolution = resolve_local_llm(config, "placement");
local_placement_allowed(config, &resolution).then(|| (role.clone(), h.cache.clone()))
}
#[cfg(test)]
mod tests {
use super::*;
fn fixed() -> ResolveFn {
Arc::new(|_| Some("http://127.0.0.1:1/v1".into()))
}
#[test]
fn no_hook_no_placement_then_explicit_settings_win() {
let config = Config::default();
assert!(placement_for("local", &config).is_none(), "nothing installed");
let mut roles = HashMap::new();
roles.insert("local".to_string(), "hermes".to_string());
roles.insert("ollama".to_string(), "other".to_string());
assert!(install(fixed(), Duration::from_secs(5), roles));
assert!(!install(fixed(), Duration::from_secs(5), HashMap::new()), "first wins");
assert!(placement_for("openai", &config).is_none(), "no role for the provider");
assert!(placement_for("ollama", &config).is_none(), "only local has a precedence rule");
if resolve_local_llm(&config, "t").url_source == "default:local-adr060" {
assert_eq!(placement_for("local", &config).unwrap().0, "hermes");
}
let mut explicit = Config::default();
explicit.providers.local.api_base = Some("http://127.0.0.1:9100/v1".into());
assert!(placement_for("local", &explicit).is_none(), "explicit api_base wins");
}
}