Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

305 changes: 257 additions & 48 deletions crates/aionui-ai-agent/src/registry.rs

Large diffs are not rendered by default.

52 changes: 52 additions & 0 deletions crates/aionui-ai-agent/src/registry_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ async fn management_rows_derive_missing_diagnostics_from_probe_reason() {

let registry = AgentRegistry::new(repo);
registry.hydrate().await.unwrap();
registry.refresh_availability().await;

let row = registry
.list_management_rows()
Expand Down Expand Up @@ -224,6 +225,57 @@ async fn management_rows_derive_missing_diagnostics_from_probe_reason() {
);
}

#[tokio::test]
async fn management_rows_mark_unchecked_agents_unchecked_without_probe() {
let db = init_database_memory().await.unwrap();
let repo: Arc<dyn IAgentMetadataRepository> = Arc::new(SqliteAgentMetadataRepository::new(db.pool().clone()));

repo.upsert(&UpsertAgentMetadataParams {
id: "agent-unchecked-cli",
icon: None,
name: "Unchecked CLI Agent",
name_i18n: None,
description: None,
description_i18n: None,
backend: Some("custom"),
agent_type: "acp",
agent_source: "custom",
agent_source_info: Some(r#"{"binary_name":"unchecked-cli"}"#),
enabled: true,
command: Some("unchecked-cli"),
args: Some("[]"),
env: Some("[]"),
native_skills_dirs: None,
behavior_policy: None,
yolo_id: None,
agent_capabilities: None,
auth_methods: None,
config_options: None,
available_modes: None,
available_models: None,
available_commands: None,
sort_order: 100,
})
.await
.unwrap();

let registry = AgentRegistry::new(repo);
registry.hydrate().await.unwrap();

let row = registry
.list_management_rows()
.await
.into_iter()
.find(|item| item.id == "agent-unchecked-cli")
.unwrap();

let row_json = serde_json::to_value(&row).unwrap();
assert_eq!(row_json["status"].as_str(), Some("unchecked"));
assert!(!row.installed);
assert!(row.last_check_status.is_none());
assert!(row.last_check_error_code.is_none());
}

#[tokio::test]
async fn hydrate_continues_when_agent_metadata_config_options_has_invalid_utf8() {
let db = init_database_memory().await.unwrap();
Expand Down
11 changes: 0 additions & 11 deletions crates/aionui-ai-agent/src/routes/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
//! Endpoints:
//!
//! - `GET /api/agents/management` — list diagnostics-first agent rows
//! - `POST /api/agents/refresh` — refresh agent list (e.g. after new agent is added to the system)
//! - `POST /api/agents/custom/try-connect` — test custom agent configuration (e.g. ACP connection)

use axum::Router;
Expand All @@ -28,7 +27,6 @@ pub fn agent_routes(state: AgentRouterState) -> Router {
Router::new()
.route("/api/agents/logos", get(list_agent_logos))
.route("/api/agents/management", get(list_management_agents))
.route("/api/agents/refresh", post(refresh_agents))
.route("/api/agents/{id}/health-check", post(health_check_by_id))
.route("/api/agents/provider-health-check", post(provider_health_check))
.route("/api/agents/{id}/enabled", patch(set_agent_enabled))
Expand All @@ -42,15 +40,6 @@ pub fn agent_routes(state: AgentRouterState) -> Router {
.with_state(state)
}

async fn refresh_agents(
State(state): State<AgentRouterState>,
Extension(_user): Extension<CurrentUser>,
) -> Result<Json<ApiResponse<Vec<AgentMetadata>>>, ApiError> {
Ok(Json(ApiResponse::ok(
state.service.refresh_agents().await.map_err(agent_error_to_api_error)?,
)))
}

async fn list_agent_logos(
State(state): State<AgentRouterState>,
Extension(_user): Extension<CurrentUser>,
Expand Down
15 changes: 1 addition & 14 deletions crates/aionui-ai-agent/src/services/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@
use std::path::PathBuf;
use std::sync::Arc;

use aionui_api_types::{
AgentLogoEntry, AgentManagementRow, AgentMetadata, ProviderHealthCheckRequest, ProviderHealthCheckResponse,
};
use aionui_api_types::{AgentLogoEntry, AgentManagementRow, ProviderHealthCheckRequest, ProviderHealthCheckResponse};
use aionui_db::IProviderRepository;
use aionui_realtime::EventBroadcaster;

Expand Down Expand Up @@ -76,17 +74,6 @@ impl AgentService {

// Agent operations
impl AgentService {
pub async fn refresh_agents(&self) -> Result<Vec<AgentMetadata>, AgentError> {
self.registry.refresh_availability().await;
Ok(self
.registry
.list_all()
.await
.into_iter()
.filter(|agent| agent.agent_type.supports_new_conversation())
.collect())
}

pub async fn list_management_agents(&self) -> Result<Vec<AgentManagementRow>, AgentError> {
Ok(self.availability.list_management_rows().await)
}
Expand Down
14 changes: 4 additions & 10 deletions crates/aionui-ai-agent/src/services/availability/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,17 +53,15 @@ impl AgentAvailabilityService {
}

pub async fn list_management_rows(&self) -> Vec<AgentManagementRow> {
self.registry.refresh_availability().await;
self.registry.list_management_rows().await
}

pub async fn run_manual_health_check(&self, id: &str) -> Result<AgentManagementRow, AgentError> {
self.registry.invalidate_and_rehydrate().await?;
let meta = self
.registry
.get(id)
.reload_one(id)
.await
.ok_or_else(|| AgentError::not_found(format!("Agent '{id}' not found")))?;
.and_then(|row| row.ok_or_else(|| AgentError::not_found(format!("Agent '{id}' not found"))))?;

if !meta.available {
return self
Expand Down Expand Up @@ -113,11 +111,7 @@ impl AgentAvailabilityService {
}

pub async fn management_row_by_id(&self, id: &str) -> Option<AgentManagementRow> {
self.registry
.list_management_rows()
.await
.into_iter()
.find(|row| row.id == id)
self.registry.management_row_by_id(id).await
}

async fn persist_snapshot(&self, id: &str, snapshot: &AvailabilitySnapshot) -> Result<(), AgentError> {
Expand Down Expand Up @@ -156,7 +150,7 @@ impl AgentAvailabilityService {
.update_availability_snapshot(id, &params)
.await
.map_err(|error| AgentError::internal(format!("repo.update_availability_snapshot: {error}")))?;
self.registry.invalidate_and_rehydrate().await?;
self.registry.reload_one(id).await?;
Ok(())
}
}
Expand Down
12 changes: 6 additions & 6 deletions crates/aionui-ai-agent/src/services/custom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@ impl AgentService {
if !removed {
return Err(AgentError::not_found(format!("Agent '{id}' not found")));
}
if let Err(err) = self.registry().invalidate_and_rehydrate().await {
warn!(agent_id = %id, error = %err, "registry rehydrate failed after delete_custom_agent");
if let Err(err) = self.registry().reload_one(id).await {
warn!(agent_id = %id, error = %err, "registry reload failed after delete_custom_agent");
}
Ok(())
}
Expand All @@ -123,8 +123,8 @@ impl AgentService {
if !updated {
return Err(AgentError::not_found(format!("Agent '{id}' not found")));
}
if let Err(err) = self.registry().invalidate_and_rehydrate().await {
warn!(agent_id = %id, error = %err, "registry rehydrate failed after set_agent_enabled");
if let Err(err) = self.registry().reload_one(id).await {
warn!(agent_id = %id, error = %err, "registry reload failed after set_agent_enabled");
}
self.registry()
.get(id)
Expand Down Expand Up @@ -195,9 +195,9 @@ impl AgentService {
.map_err(|e| AgentError::internal(format!("repo.upsert: {e}")))?;

self.registry()
.invalidate_and_rehydrate()
.reload_one(id)
.await
.map_err(|e| AgentError::internal(format!("registry rehydrate: {e}")))?;
.map_err(|e| AgentError::internal(format!("registry reload: {e}")))?;

self.registry()
.get(id)
Expand Down
Loading
Loading