feat: add agent publishing foundation

This commit is contained in:
a.tolmachev
2026-03-29 22:10:15 +03:00
parent d757adb192
commit 7b7699cf8b
18 changed files with 1520 additions and 105 deletions
+74 -21
View File
@@ -4,7 +4,7 @@ use std::{
time::{Duration, Instant},
};
use crank_registry::{PostgresRegistry, RegistryError, RegistryOperation};
use crank_registry::{PostgresRegistry, PublishedAgentTool, RegistryError};
use tokio::sync::RwLock;
use tracing::info;
@@ -12,14 +12,20 @@ use tracing::info;
pub struct PublishedToolCatalog {
registry: PostgresRegistry,
refresh_interval: Duration,
cached: Arc<RwLock<CachedCatalog>>,
cached: Arc<RwLock<HashMap<CatalogKey, CachedCatalog>>>,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
struct CatalogKey {
workspace_slug: String,
agent_slug: String,
}
#[derive(Default)]
struct CachedCatalog {
loaded_at: Option<Instant>,
tools: Vec<RegistryOperation>,
tools_by_name: HashMap<String, RegistryOperation>,
tools: Vec<PublishedAgentTool>,
tools_by_name: HashMap<String, PublishedAgentTool>,
}
impl PublishedToolCatalog {
@@ -27,30 +33,47 @@ impl PublishedToolCatalog {
Self {
registry,
refresh_interval,
cached: Arc::new(RwLock::new(CachedCatalog::default())),
cached: Arc::new(RwLock::new(HashMap::new())),
}
}
pub async fn list_tools(&self) -> Result<Vec<RegistryOperation>, RegistryError> {
self.refresh_if_stale().await?;
pub async fn list_tools(
&self,
workspace_slug: &str,
agent_slug: &str,
) -> Result<Vec<PublishedAgentTool>, RegistryError> {
self.refresh_if_stale(workspace_slug, agent_slug).await?;
let guard = self.cached.read().await;
Ok(guard.tools.clone())
Ok(guard
.get(&CatalogKey::new(workspace_slug, agent_slug))
.map(|entry| entry.tools.clone())
.unwrap_or_default())
}
pub async fn get_tool(
&self,
workspace_slug: &str,
agent_slug: &str,
tool_name: &str,
) -> Result<Option<RegistryOperation>, RegistryError> {
self.refresh_if_stale().await?;
) -> Result<Option<PublishedAgentTool>, RegistryError> {
self.refresh_if_stale(workspace_slug, agent_slug).await?;
let guard = self.cached.read().await;
Ok(guard.tools_by_name.get(tool_name).cloned())
Ok(guard
.get(&CatalogKey::new(workspace_slug, agent_slug))
.and_then(|entry| entry.tools_by_name.get(tool_name))
.cloned())
}
async fn refresh_if_stale(&self) -> Result<(), RegistryError> {
async fn refresh_if_stale(
&self,
workspace_slug: &str,
agent_slug: &str,
) -> Result<(), RegistryError> {
let key = CatalogKey::new(workspace_slug, agent_slug);
let should_refresh = {
let guard = self.cached.read().await;
match guard.loaded_at {
match guard.get(&key).and_then(|entry| entry.loaded_at) {
Some(loaded_at) => loaded_at.elapsed() >= self.refresh_interval,
None => true,
}
@@ -60,25 +83,55 @@ impl PublishedToolCatalog {
return Ok(());
}
let tools = self.registry.list_published_operations().await?;
let tools = match self
.registry
.get_published_agent_tools_by_slug(workspace_slug, agent_slug)
.await
{
Ok(tools) => tools,
Err(RegistryError::PublishedAgentNotFound { .. }) => Vec::new(),
Err(error) => return Err(error),
};
let tools_by_name = tools
.iter()
.cloned()
.map(|operation| (operation.name.clone(), operation))
.map(|tool| (tool.tool_name.clone(), tool))
.collect::<HashMap<_, _>>();
let mut guard = self.cached.write().await;
let previous_count = guard.tools.len();
let previous_count = guard
.get(&key)
.map(|entry| entry.tools.len())
.unwrap_or_default();
guard.loaded_at = Some(Instant::now());
guard.tools = tools;
guard.tools_by_name = tools_by_name;
guard.insert(
key,
CachedCatalog {
loaded_at: Some(Instant::now()),
tools,
tools_by_name,
},
);
info!(
published_tool_count = guard.tools.len(),
workspace_slug,
agent_slug,
published_tool_count = guard
.get(&CatalogKey::new(workspace_slug, agent_slug))
.map(|entry| entry.tools.len())
.unwrap_or_default(),
previous_published_tool_count = previous_count,
"published tool catalog refreshed"
"published agent catalog refreshed"
);
Ok(())
}
}
impl CatalogKey {
fn new(workspace_slug: &str, agent_slug: &str) -> Self {
Self {
workspace_slug: workspace_slug.to_owned(),
agent_slug: agent_slug.to_owned(),
}
}
}