feat: connect alpine agents to admin api
This commit is contained in:
@@ -118,6 +118,13 @@ pub struct AgentPayload {
|
||||
pub tool_selection_policy: Value,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct UpdateAgentPayload {
|
||||
pub slug: String,
|
||||
pub display_name: String,
|
||||
pub description: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct AgentBindingPayload {
|
||||
pub operation_id: String,
|
||||
@@ -145,6 +152,33 @@ pub struct PublishAgentResponse {
|
||||
pub published_at: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct AgentSummaryView {
|
||||
pub id: String,
|
||||
pub workspace_id: String,
|
||||
pub slug: String,
|
||||
pub display_name: String,
|
||||
pub description: String,
|
||||
pub status: AgentStatus,
|
||||
pub current_draft_version: u32,
|
||||
pub latest_published_version: Option<u32>,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
pub published_at: Option<String>,
|
||||
pub operation_count: usize,
|
||||
pub operation_ids: Vec<String>,
|
||||
pub key_count: usize,
|
||||
pub calls_today: u64,
|
||||
pub mcp_endpoint: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct AgentMutationResult {
|
||||
pub agent_id: String,
|
||||
pub workspace_id: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct InvitationPayload {
|
||||
pub email: String,
|
||||
@@ -1170,9 +1204,54 @@ impl AdminService {
|
||||
pub async fn list_agents(
|
||||
&self,
|
||||
workspace_id: &WorkspaceId,
|
||||
) -> Result<Vec<AgentSummary>, ApiError> {
|
||||
) -> Result<Vec<AgentSummaryView>, ApiError> {
|
||||
self.ensure_workspace_exists(workspace_id).await?;
|
||||
Ok(self.registry.list_agents(workspace_id).await?)
|
||||
let workspace = self.get_workspace(workspace_id).await?;
|
||||
let summaries = self.registry.list_agents(workspace_id).await?;
|
||||
let usage = self
|
||||
.registry
|
||||
.list_usage_by_agent(UsageQuery {
|
||||
workspace_id,
|
||||
period: UsagePeriod::Last24Hours,
|
||||
source: None,
|
||||
created_after: &today_start_utc()?,
|
||||
bucket: crank_registry::UsageBucket::Hour,
|
||||
})
|
||||
.await?;
|
||||
let calls_today = usage
|
||||
.into_iter()
|
||||
.map(|item| (item.agent_id.as_str().to_owned(), item.calls_total))
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
let key_count = self
|
||||
.registry
|
||||
.list_platform_api_keys(workspace_id)
|
||||
.await?
|
||||
.len();
|
||||
|
||||
let mut items = Vec::with_capacity(summaries.len());
|
||||
for summary in summaries {
|
||||
let version = self
|
||||
.get_agent_version(workspace_id, &summary.id, summary.current_draft_version)
|
||||
.await?;
|
||||
let operation_ids = version
|
||||
.bindings
|
||||
.iter()
|
||||
.map(|binding| binding.operation_id.as_str().to_owned())
|
||||
.collect::<Vec<_>>();
|
||||
items.push(AgentSummaryView {
|
||||
operation_count: operation_ids.len(),
|
||||
operation_ids,
|
||||
key_count,
|
||||
calls_today: calls_today.get(summary.id.as_str()).copied().unwrap_or(0),
|
||||
mcp_endpoint: agent_mcp_endpoint(
|
||||
workspace.workspace.slug.as_str(),
|
||||
summary.slug.as_str(),
|
||||
),
|
||||
..map_agent_summary_view(summary)
|
||||
});
|
||||
}
|
||||
|
||||
Ok(items)
|
||||
}
|
||||
|
||||
#[instrument(skip(self))]
|
||||
@@ -1180,13 +1259,54 @@ impl AdminService {
|
||||
&self,
|
||||
workspace_id: &WorkspaceId,
|
||||
agent_id: &AgentId,
|
||||
) -> Result<AgentSummary, ApiError> {
|
||||
self.registry
|
||||
) -> Result<AgentSummaryView, ApiError> {
|
||||
self.ensure_workspace_exists(workspace_id).await?;
|
||||
let workspace = self.get_workspace(workspace_id).await?;
|
||||
let summary = self
|
||||
.registry
|
||||
.get_agent_summary(workspace_id, agent_id)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::not_found(format!("agent {} was not found", agent_id.as_str()))
|
||||
})
|
||||
})?;
|
||||
let version = self
|
||||
.get_agent_version(workspace_id, agent_id, summary.current_draft_version)
|
||||
.await?;
|
||||
let operation_ids = version
|
||||
.bindings
|
||||
.iter()
|
||||
.map(|binding| binding.operation_id.as_str().to_owned())
|
||||
.collect::<Vec<_>>();
|
||||
let usage = self
|
||||
.registry
|
||||
.get_usage_for_agent(
|
||||
UsageQuery {
|
||||
workspace_id,
|
||||
period: UsagePeriod::Last24Hours,
|
||||
source: None,
|
||||
created_after: &today_start_utc()?,
|
||||
bucket: crank_registry::UsageBucket::Hour,
|
||||
},
|
||||
agent_id,
|
||||
)
|
||||
.await?;
|
||||
let key_count = self
|
||||
.registry
|
||||
.list_platform_api_keys(workspace_id)
|
||||
.await?
|
||||
.len();
|
||||
|
||||
Ok(AgentSummaryView {
|
||||
operation_count: operation_ids.len(),
|
||||
operation_ids,
|
||||
key_count,
|
||||
calls_today: usage.map(|item| item.rollup.calls_total).unwrap_or(0),
|
||||
mcp_endpoint: agent_mcp_endpoint(
|
||||
workspace.workspace.slug.as_str(),
|
||||
summary.slug.as_str(),
|
||||
),
|
||||
..map_agent_summary_view(summary)
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument(skip(self))]
|
||||
@@ -1267,6 +1387,75 @@ impl AdminService {
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument(skip(self, payload), fields(workspace_id = %workspace_id.as_str(), agent_id = %agent_id.as_str()))]
|
||||
pub async fn update_agent(
|
||||
&self,
|
||||
workspace_id: &WorkspaceId,
|
||||
agent_id: &AgentId,
|
||||
payload: UpdateAgentPayload,
|
||||
) -> Result<AgentMutationResult, ApiError> {
|
||||
let existing = self
|
||||
.registry
|
||||
.get_agent_summary(workspace_id, agent_id)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::not_found(format!("agent {} was not found", agent_id.as_str()))
|
||||
})?;
|
||||
|
||||
if payload.slug != existing.slug
|
||||
&& self
|
||||
.find_agent_by_slug(workspace_id, &payload.slug)
|
||||
.await?
|
||||
.is_some()
|
||||
{
|
||||
return Err(ApiError::conflict(format!(
|
||||
"agent with slug {} already exists",
|
||||
payload.slug
|
||||
)));
|
||||
}
|
||||
|
||||
let updated_at = now_string()?;
|
||||
self.registry
|
||||
.update_agent_summary(
|
||||
workspace_id,
|
||||
agent_id,
|
||||
&payload.slug,
|
||||
&payload.display_name,
|
||||
&payload.description,
|
||||
&updated_at,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(AgentMutationResult {
|
||||
agent_id: agent_id.as_str().to_owned(),
|
||||
workspace_id: workspace_id.as_str().to_owned(),
|
||||
updated_at,
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument(skip(self), fields(workspace_id = %workspace_id.as_str(), agent_id = %agent_id.as_str()))]
|
||||
pub async fn delete_agent(
|
||||
&self,
|
||||
workspace_id: &WorkspaceId,
|
||||
agent_id: &AgentId,
|
||||
) -> Result<AgentMutationResult, ApiError> {
|
||||
let existing = self
|
||||
.registry
|
||||
.get_agent_summary(workspace_id, agent_id)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::not_found(format!("agent {} was not found", agent_id.as_str()))
|
||||
})?;
|
||||
|
||||
self.registry.delete_agent(workspace_id, agent_id).await?;
|
||||
|
||||
Ok(AgentMutationResult {
|
||||
agent_id: agent_id.as_str().to_owned(),
|
||||
workspace_id: workspace_id.as_str().to_owned(),
|
||||
updated_at: existing.updated_at,
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument(skip(self, payload), fields(workspace_id = %workspace_id.as_str(), agent_id = %agent_id.as_str()))]
|
||||
pub async fn save_agent_bindings(
|
||||
&self,
|
||||
@@ -2060,6 +2249,31 @@ fn agent_ref_map(items: Vec<OperationAgentRef>) -> BTreeMap<String, Vec<Operatio
|
||||
map
|
||||
}
|
||||
|
||||
fn map_agent_summary_view(summary: AgentSummary) -> AgentSummaryView {
|
||||
AgentSummaryView {
|
||||
id: summary.id.as_str().to_owned(),
|
||||
workspace_id: summary.workspace_id.as_str().to_owned(),
|
||||
slug: summary.slug,
|
||||
display_name: summary.display_name,
|
||||
description: summary.description,
|
||||
status: summary.status,
|
||||
current_draft_version: summary.current_draft_version,
|
||||
latest_published_version: summary.latest_published_version,
|
||||
created_at: summary.created_at,
|
||||
updated_at: summary.updated_at,
|
||||
published_at: summary.published_at,
|
||||
operation_count: 0,
|
||||
operation_ids: Vec::new(),
|
||||
key_count: 0,
|
||||
calls_today: 0,
|
||||
mcp_endpoint: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn agent_mcp_endpoint(workspace_slug: &str, agent_slug: &str) -> String {
|
||||
format!("/mcp/v1/{workspace_slug}/{agent_slug}")
|
||||
}
|
||||
|
||||
fn enrich_operation_summary(
|
||||
summary: OperationSummary,
|
||||
usage_summary: OperationUsageSummaryView,
|
||||
|
||||
Reference in New Issue
Block a user