Skip to main content

agent_sdk_toolkit/discovery/
index.rs

1//! Tool discovery index and activation delta helpers. Use this module to search
2//! hidden toolkit candidates and construct package deltas for later activation.
3//! Searching is data-only; applying the returned delta is a separate package step.
4//!
5use std::collections::BTreeMap;
6
7use agent_sdk_core::{
8    AgentError, CapabilityCatalogSnapshot, CapabilityId, CapabilitySourceKind, PackageDelta,
9    PolicyRef, ToolPackSnapshot, TrustClass,
10};
11
12use super::types::ToolDiscoveryCandidate;
13
14#[derive(Clone, Debug, Default)]
15/// Discovery tool discovery index request or result value.
16/// Creating the value does not register tools; discovery executors document catalog and package-bundle effects.
17pub struct ToolDiscoveryIndex {
18    candidates: BTreeMap<String, ToolPackSnapshot>,
19}
20
21impl ToolDiscoveryIndex {
22    /// Creates a new discovery::index value with explicit
23    /// caller-provided inputs. This constructor is data-only and
24    /// performs no I/O or external side effects.
25    pub fn new() -> Self {
26        Self::default()
27    }
28
29    /// Adds data to this in-memory discovery::index collection. It does not
30    /// perform external I/O, execute tools, or append journals.
31    pub fn insert(&mut self, snapshot: ToolPackSnapshot) {
32        self.candidates
33            .insert(snapshot.pack_id.as_str().to_string(), snapshot);
34    }
35
36    /// Searches the in-memory discovery index for pack IDs or tool names that
37    /// contain the query string. This is read-only and does not activate the
38    /// returned candidates.
39    pub fn search(&self, query: &str) -> Vec<ToolDiscoveryCandidate> {
40        self.candidates
41            .iter()
42            .filter(|(id, snapshot)| {
43                id.contains(query)
44                    || snapshot
45                        .tools
46                        .iter()
47                        .any(|tool| tool.canonical_tool_name.as_str().contains(query))
48            })
49            .map(|(id, snapshot)| ToolDiscoveryCandidate {
50                pack_id: id.clone(),
51                tool_names: snapshot
52                    .tools
53                    .iter()
54                    .map(|tool| tool.canonical_tool_name.as_str().to_string())
55                    .collect(),
56                package_delta_required: true,
57            })
58            .collect()
59    }
60
61    /// Builds the package delta needed to activate one discovered candidate.
62    /// The active runtime package is not mutated until the caller applies the
63    /// returned delta through `RuntimePackage::apply_delta`.
64    pub fn activation_delta(
65        &self,
66        pack_id: &str,
67        package: &agent_sdk_core::RuntimePackage,
68        requested_by: agent_sdk_core::SourceRef,
69        activation_policy_ref: PolicyRef,
70    ) -> Result<PackageDelta, AgentError> {
71        let snapshot = self
72            .candidates
73            .get(pack_id)
74            .cloned()
75            .ok_or_else(|| AgentError::missing_required_field("tool_discovery.candidate"))?;
76        let capabilities = snapshot.capability_specs()?;
77        let catalog = CapabilityCatalogSnapshot {
78            catalog_id: format!("catalog.discovery.{pack_id}"),
79            source_kind: CapabilitySourceKind::DiscoveryIndex,
80            source_ref: requested_by.clone(),
81            version: Some("v1".to_string()),
82            content_hash: Some(snapshot.content_hash()?),
83            trust_state: TrustClass::SdkGenerated,
84            activation_policy_ref,
85            candidates: capabilities
86                .iter()
87                .map(|capability| capability.capability_id.clone())
88                .collect::<Vec<CapabilityId>>(),
89        };
90        Ok(PackageDelta {
91            previous_fingerprint: package.fingerprint()?,
92            requested_by,
93            reason: "activate hidden tool discovery candidate for next package".to_string(),
94            activated_capabilities: capabilities,
95            deactivated_capability_ids: Vec::new(),
96            catalogs: vec![catalog],
97            sidecars: vec![snapshot.package_sidecar_snapshot()?],
98        })
99    }
100}