feat: add agent publishing foundation
This commit is contained in:
@@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user