use std::collections::HashMap;
use std::sync::Arc;
use parking_lot::Mutex;
use nexil::Tool;
pub static REGISTRY: std::sync::LazyLock<Mutex<HashMap<String, Tool>>> =
std::sync::LazyLock::new(|| Mutex::new(HashMap::new()));
type ModelToolsSnapshot = (u64, Arc<Vec<Tool>>);
static MODEL_TOOLS_CACHE: std::sync::LazyLock<Mutex<Option<ModelToolsSnapshot>>> =
std::sync::LazyLock::new(|| Mutex::new(None));
fn registry_fingerprint(reg: &HashMap<String, Tool>) -> u64 {
use std::hash::{Hash, Hasher};
let mut acc = reg.len() as u64;
for (name, tool) in reg {
let mut h = std::collections::hash_map::DefaultHasher::new();
name.hash(&mut h);
tool.description.hash(&mut h);
acc = acc.wrapping_add(h.finish());
}
acc
}
pub fn populate_model_tools_cache() {
let _ = model_tools_cached();
}
pub fn model_tools_cached() -> Vec<Tool> {
let reg = REGISTRY.lock();
let fp = registry_fingerprint(®);
if let Some((cached_fp, tools)) = MODEL_TOOLS_CACHE.lock().as_ref()
&& *cached_fp == fp
{
let snapshot = Arc::clone(tools);
drop(reg);
return snapshot.as_ref().clone();
}
let built = Arc::new(model_tools(®.values().cloned().collect::<Vec<_>>()));
drop(reg);
*MODEL_TOOLS_CACHE.lock() = Some((fp, Arc::clone(&built)));
built.as_ref().clone()
}
fn to_model_name(name: &str) -> String {
name.replace('.', "_")
}
pub fn model_tools(tools: &[Tool]) -> Vec<Tool> {
tools
.iter()
.map(|tool| {
let mut cloned = tool.clone();
cloned.name = to_model_name(&cloned.name);
cloned
})
.collect()
}
pub fn shorten_text(text: &str, width: usize) -> String {
if text.len() <= width {
return text.to_owned();
}
let placeholder = "...";
let available = width.saturating_sub(placeholder.len());
if available == 0 {
return placeholder.to_owned();
}
format!("{}{placeholder}", &text[..available])
}
#[cfg(test)]
mod tests {
use super::*;
use nexil::Tool;
use serde_json::json;
fn make_tool(name: &str, description: &str) -> Tool {
Tool::schema_only(name, description, json!({}))
}
#[test]
fn test_to_model_name_replaces_dots() {
assert_eq!(to_model_name("tests.rename_me"), "tests_rename_me");
}
#[test]
fn test_to_model_name_no_dots() {
assert_eq!(to_model_name("simple"), "simple");
}
#[test]
fn test_to_model_name_multiple_dots() {
assert_eq!(to_model_name("a.b.c"), "a_b_c");
}
#[test]
fn test_model_tools_rewrites_names_without_mutating_original() {
let tool = make_tool("tests.rename_me", "rename");
let rewritten = model_tools(std::slice::from_ref(&tool));
assert_eq!(rewritten.len(), 1);
assert_eq!(rewritten[0].name, "tests_rename_me");
assert_eq!(tool.name, "tests.rename_me");
}
#[test]
fn test_model_tools_empty() {
let rewritten = model_tools(&[]);
assert!(rewritten.is_empty());
}
#[test]
fn model_tools_cache_reflects_post_init_registration() {
let probe = "tier1.cache_probe_postinit";
let model_name = "tier1_cache_probe_postinit";
populate_model_tools_cache();
let before = model_tools_cached();
assert!(
!before.iter().any(|t| t.name == model_name),
"probe tool must not exist before registration"
);
REGISTRY
.lock()
.insert(probe.to_string(), make_tool(probe, "probe"));
let after = model_tools_cached();
let visible = after.iter().any(|t| t.name == model_name);
REGISTRY.lock().remove(probe);
assert!(
visible,
"post-init registered tool must appear in model_tools_cached()"
);
}
#[test]
fn test_registry_insert_and_lookup() {
let tool = make_tool("test.registry_tool", "a tool");
{
let mut reg = REGISTRY.lock();
reg.insert("test.registry_tool".into(), tool.clone());
}
let reg = REGISTRY.lock();
assert!(reg.contains_key("test.registry_tool"));
assert_eq!(reg["test.registry_tool"].name, "test.registry_tool");
}
#[test]
fn test_shorten_text_short_enough() {
assert_eq!(shorten_text("hello", 10), "hello");
}
#[test]
fn test_shorten_text_truncates_with_ellipsis() {
assert_eq!(shorten_text("hello world", 8), "hello...");
}
#[test]
fn test_shorten_text_very_small_width() {
assert_eq!(shorten_text("hello", 3), "...");
}
#[test]
fn test_shorten_text_zero_width() {
assert_eq!(shorten_text("hello", 0), "...");
}
}