use std::sync::Arc;
use async_trait::async_trait;
use car_fleet::{
now_ms, FleetAgent, FleetAgentKind, FleetCapability, FleetCapabilityKind, FleetError,
FleetModel, FleetModelKind, InstanceInventory, InstanceRef, InventoryProvider,
};
use crate::session::ServerState;
const MAX_SKILLS_REPORTED: usize = 200;
pub async fn local_inventory(
state: &ServerState,
runtime: Option<&Arc<car_engine::Runtime>>,
memgine: Option<&tokio::sync::Mutex<car_memgine::MemgineEngine>>,
) -> InstanceInventory {
let identity = car_identity::IdentityStore::from_home().load_or_default();
let mut instance = InstanceRef::local(car_a2a::lan::host_label());
instance.version = Some(env!("CARGO_PKG_VERSION").to_string());
instance.platform = Some(format!(
"{}/{}",
std::env::consts::OS,
std::env::consts::ARCH
));
let mut inv = InstanceInventory::full(instance, now_ms());
inv.agents.push(FleetAgent {
id: crate::assistant::register::ASSISTANT_AGENT_ID.to_string(),
kind: FleetAgentKind::Assistant,
display_name: Some(identity.name.clone()),
status: None,
capability: None,
addressable: true,
});
for peer in crate::peers::snapshot_attached(state).await {
inv.agents.push(FleetAgent {
id: peer.name,
kind: FleetAgentKind::Attached,
display_name: peer.display_name,
status: Some("attached".to_string()),
capability: peer.capability,
addressable: true,
});
}
if let Ok(registry) = car_registry::AgentRegistry::user_default() {
if let Ok(entries) = registry.list() {
for entry in entries {
inv.agents.push(FleetAgent {
id: entry.name,
kind: FleetAgentKind::Supervised,
display_name: entry.display_name,
status: Some(format!("{:?}", entry.status).to_lowercase()),
capability: entry.capability,
addressable: false,
});
}
}
}
if let Ok(decl) = state.declagents() {
for spec in decl.list().into_iter().filter(|s| s.enabled) {
inv.agents.push(FleetAgent {
id: spec.id,
kind: FleetAgentKind::Declarative,
display_name: Some(spec.name),
status: Some("enabled".to_string()),
capability: Some(spec.identity),
addressable: false,
});
}
}
for spec in super::detected_adapters().await {
inv.agents.push(FleetAgent {
id: spec.id.clone(),
kind: FleetAgentKind::ExternalCli,
display_name: Some(spec.display_name.clone()),
status: spec.version.clone(),
capability: None,
addressable: false,
});
}
if let Some(runtime) = runtime {
for schema in runtime.tool_schemas().await {
let mut tags = Vec::new();
if schema.idempotent {
tags.push("idempotent".to_string());
}
if schema.cache_ttl_secs.is_some() {
tags.push("cacheable".to_string());
}
if schema.rate_limit.is_some() {
tags.push("rate-limited".to_string());
}
inv.capabilities.push(FleetCapability {
name: schema.name,
kind: FleetCapabilityKind::Tool,
description: Some(schema.description),
tags,
});
}
}
if let Some(memgine) = memgine {
let engine = memgine.lock().await;
let mut skills: Vec<FleetCapability> = engine
.graph
.inner
.node_indices()
.filter_map(|nix| {
let node = engine.graph.inner.node_weight(nix)?;
if node.kind != car_memgine::MemKind::Skill {
return None;
}
let meta = car_memgine::SkillMeta::from_node(node)?;
Some(FleetCapability {
name: meta.name,
kind: FleetCapabilityKind::Skill,
description: None,
tags: Vec::new(),
})
})
.collect();
skills.sort_by(|a, b| a.name.cmp(&b.name));
skills.truncate(MAX_SKILLS_REPORTED);
inv.capabilities.extend(skills);
}
for model in crate::handler::get_inference_engine(state).list_models_unified() {
inv.models.push(FleetModel {
id: model.id,
kind: if model.is_local {
FleetModelKind::Local
} else {
FleetModelKind::Cloud
},
provider: Some(model.provider),
available: model.available,
context_window: Some(model.context_length as u64),
capabilities: model
.capabilities
.iter()
.map(|c| format!("{c:?}").to_lowercase())
.collect(),
});
}
inv.worker = Some(super::worker_profile().await);
inv
}
pub struct PeerInventoryProvider {
instance: InstanceRef,
identity: Option<Arc<car_a2a::peer_auth::PeerIdentity>>,
}
impl PeerInventoryProvider {
pub fn new(
instance: InstanceRef,
identity: Option<Arc<car_a2a::peer_auth::PeerIdentity>>,
) -> Self {
Self { instance, identity }
}
fn client(&self, base_url: &str) -> car_a2a::client::A2aClient {
let client = car_a2a::client::A2aClient::new(base_url);
match &self.identity {
Some(id) => client.with_peer_identity(Arc::clone(id)),
None => client,
}
}
}
#[async_trait]
impl InventoryProvider for PeerInventoryProvider {
fn instance(&self) -> InstanceRef {
self.instance.clone()
}
async fn inventory(&self) -> Result<InstanceInventory, FleetError> {
let base_url = self
.instance
.base_url
.as_deref()
.ok_or_else(|| FleetError::Protocol("peer has no base url".into()))?;
let client = self.client(base_url);
match client
.call::<_, serde_json::Value>("car/fleetInventory", &serde_json::json!({}))
.await
{
Ok(value) => {
let mut inv: InstanceInventory = serde_json::from_value(value)
.map_err(|e| FleetError::Protocol(format!("inventory shape: {e}")))?;
let name = self.instance.name.clone();
let reported = inv.instance.name.clone();
inv.instance = InstanceRef {
name,
version: inv.instance.version,
platform: inv.instance.platform,
..self.instance.clone()
};
if reported != inv.instance.name {
inv.instance.reference = self.instance.reference.clone();
}
Ok(inv)
}
Err(car_a2a::client::ClientError::Rpc { code: -32601, .. }) => {
self.card_only(&client).await
}
Err(car_a2a::client::ClientError::Status { code: 401, .. }) => {
Err(FleetError::Unreachable(format!(
"`{}` does not trust this host's peer key — a CAR daemon accepts keys published on the signed-in device roster, so sign in to the same account on both machines",
self.instance.name
)))
}
Err(e) => Err(FleetError::Unreachable(e.to_string())),
}
}
}
impl PeerInventoryProvider {
async fn card_only(
&self,
client: &car_a2a::client::A2aClient,
) -> Result<InstanceInventory, FleetError> {
let card = client
.agent_card()
.await
.map_err(|e| FleetError::Unreachable(e.to_string()))?;
let capabilities = card
.skills
.into_iter()
.map(|s| FleetCapability {
name: s.name,
kind: FleetCapabilityKind::A2aSkill,
description: Some(s.description),
tags: s.tags,
})
.collect();
let mut instance = self.instance.clone();
instance.version = Some(card.version);
Ok(InstanceInventory::card_only(
instance,
capabilities,
now_ms(),
Some(
"peer does not implement `car/fleetInventory`; agents and models are unknown, \
not absent"
.to_string(),
),
))
}
}