robonix_executor/dispatch/
async_registry.rs1use robonix_atlas::client::AtlasClient;
5use robonix_atlas::pb as atlas_pb;
6
7#[derive(Debug, Clone)]
8pub struct AsyncGroup {
9 pub status_contract: String,
10 pub cancel_contract: String,
11}
12
13fn contract_leaf(contract_id: &str) -> &str {
14 contract_id
15 .rsplit_once('/')
16 .map(|(_, leaf)| leaf)
17 .unwrap_or(contract_id)
18}
19
20pub async fn resolve_async_group(
25 atlas: &mut AtlasClient,
26 provider_id: &str,
27 contract_id: &str,
28) -> Result<Option<AsyncGroup>, String> {
29 if matches!(contract_leaf(contract_id), "status" | "cancel") {
30 return Ok(None);
31 }
32 let status_contract = format!("{contract_id}/status");
33 let cancel_contract = format!("{contract_id}/cancel");
34
35 let providers = atlas
36 .query_capabilities(provider_id, "", atlas_pb::Transport::Mcp)
37 .await
38 .map_err(|e| format!("query_capabilities({provider_id}) failed: {e:#}"))?;
39 let Some(provider) = providers.into_iter().find(|p| p.id == provider_id) else {
40 return Ok(None);
41 };
42 let has_status = provider
43 .capabilities
44 .iter()
45 .any(|c| c.contract_id == status_contract);
46 let has_cancel = provider
47 .capabilities
48 .iter()
49 .any(|c| c.contract_id == cancel_contract);
50
51 match (has_status, has_cancel) {
52 (false, false) => Ok(None),
53 (true, true) => Ok(Some(AsyncGroup {
54 status_contract,
55 cancel_contract,
56 })),
57 _ => Err(format!(
58 "async capability '{contract_id}' on provider '{provider_id}' must register both '{status_contract}' and '{cancel_contract}'"
59 )),
60 }
61}