use agent_context_contract::{MessagesResponse, SearchMessagesRequest};
use agent_registry_contract::{
Agent, AgentId, AgentPage, CreateAgentRequest, ListAgentsRequest, UpdateAgentRequest,
};
use agent_runtime_identity_contract::{
CreateRuntimeIdentityRequest, CreateRuntimeIdentityResponse, ListRuntimeIdentitiesRequest,
ListRuntimeIdentitiesResponse, RuntimeIdentity, RuntimeIdentityId, TransitionIdentityRequest,
};
use reqwest::{Client, Url};
use crate::transport::{
CallOptions, ClientOptions, HttpTransport, InfraClientError, ServiceEndpoint,
};
const ADMIN_SERVICE: &str = "agent-infra-admin";
const ADMIN_AUDIENCE: &str = "agent-infra";
const ADMIN_AGENTS_PATH: &str = "/admin/api/agents";
const ADMIN_CONTEXT_SEARCH_PATH: &str = "/admin/api/context/messages/search";
const ADMIN_RUNTIMES_PATH: &str = "/admin/api/runtime-identities";
const ADMIN_RUNTIME_LIST_PATH: &str = "/admin/api/runtime-identities:list";
#[derive(Clone, Debug)]
pub struct AdminClient {
transport: HttpTransport,
}
impl AdminClient {
pub(crate) fn new_with_endpoint(
http: Client,
endpoint: ServiceEndpoint,
options: ClientOptions,
) -> Self {
let endpoint = endpoint.with_default_credential_audience(ADMIN_AUDIENCE);
Self {
transport: HttpTransport::new_with_options(http, ADMIN_SERVICE, endpoint, options),
}
}
pub async fn list_agents(
&self,
request: &ListAgentsRequest,
) -> Result<AgentPage, InfraClientError> {
let mut url = Url::parse("http://agent-infra-admin.invalid").expect("static URL is valid");
{
let mut query = url.query_pairs_mut();
if let Some(status) = request.status {
query.append_pair(
"status",
match status {
agent_registry_contract::AgentStatus::Active => "active",
agent_registry_contract::AgentStatus::Archived => "archived",
},
);
}
if let Some(agent_id) = &request.related_agent_id {
query.append_pair("relatedAgentId", agent_id.as_str());
}
if let Some(runtime_id) = &request.related_runtime_id {
query.append_pair("relatedRuntimeId", runtime_id);
}
if let Some(user_id) = &request.related_user_id {
query.append_pair("relatedUserId", user_id);
}
if let Some(cursor) = &request.cursor {
query.append_pair("cursor", cursor);
}
if let Some(limit) = request.limit {
query.append_pair("limit", &limit.to_string());
}
}
let path = match url.query() {
Some(query) if !query.is_empty() => format!("{ADMIN_AGENTS_PATH}?{query}"),
_ => ADMIN_AGENTS_PATH.to_string(),
};
self.transport.get_json(&path).await
}
pub async fn create_agent(
&self,
request: &CreateAgentRequest,
) -> Result<Agent, InfraClientError> {
self.transport.post_json(ADMIN_AGENTS_PATH, request).await
}
pub async fn update_agent(
&self,
agent_id: &AgentId,
request: &UpdateAgentRequest,
) -> Result<Agent, InfraClientError> {
self.transport
.patch_json_with_options(
&format!("{ADMIN_AGENTS_PATH}/{}", agent_id.as_str()),
request,
CallOptions::default().idempotent(true),
)
.await
}
pub async fn search_context(
&self,
request: &SearchMessagesRequest,
) -> Result<MessagesResponse, InfraClientError> {
self.transport
.post_json_idempotent(ADMIN_CONTEXT_SEARCH_PATH, request)
.await
}
pub async fn list_runtime_identities(
&self,
request: &ListRuntimeIdentitiesRequest,
) -> Result<ListRuntimeIdentitiesResponse, InfraClientError> {
self.transport
.post_json_idempotent(ADMIN_RUNTIME_LIST_PATH, request)
.await
}
pub async fn create_runtime_identity(
&self,
request: &CreateRuntimeIdentityRequest,
) -> Result<CreateRuntimeIdentityResponse, InfraClientError> {
self.transport.post_json(ADMIN_RUNTIMES_PATH, request).await
}
pub async fn transition_runtime_identity(
&self,
identity_id: &RuntimeIdentityId,
action: &str,
request: &TransitionIdentityRequest,
) -> Result<RuntimeIdentity, InfraClientError> {
if !matches!(action, "activate" | "suspend" | "resume" | "revoke") {
return Err(InfraClientError::Protocol {
service: ADMIN_SERVICE,
message: "unknown runtime identity administration action".into(),
});
}
self.transport
.post_json(
&format!("{ADMIN_RUNTIMES_PATH}/{}:{action}", identity_id.as_str()),
request,
)
.await
}
}