Skip to main content

robonix_executor/dispatch/
async_registry.rs

1// SPDX-License-Identifier: MulanPSL-2.0
2// Per-call async capability detection via required status/cancel sub-contracts.
3
4use 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
20/// Resolve the required async sub-contracts for `contract_id`.
21/// Async capabilities must register both `<contract_id>/status` and
22/// `<contract_id>/cancel`; registering only one is a provider configuration
23/// error surfaced to the caller before dispatching the initial run call.
24pub 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}