Skip to main content

monoloop_loop/transaction/
resolved_tools.rs

1//! Request-scoped immutable resolved tool set with linked handlers.
2
3use super::host_tools::RegisteredTool;
4use super::tool_handler::ToolHandler;
5use monoloop_contracts::{ToolId, ToolName, ToolSpec};
6use std::collections::HashMap;
7use std::sync::Arc;
8
9/// One tool admitted into a transaction.
10#[derive(Clone)]
11pub struct ResolvedTool {
12    /// Spec (encoder/MCP projection).
13    pub spec: ToolSpec,
14    /// Handler (same Arc as host registry).
15    pub handler: Arc<dyn ToolHandler>,
16}
17
18impl std::fmt::Debug for ResolvedTool {
19    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20        f.debug_struct("ResolvedTool")
21            .field("spec", &self.spec)
22            .field("handler", &"<dyn ToolHandler>")
23            .finish()
24    }
25}
26
27/// Immutable tool set resolved at admission from the host registry.
28#[derive(Clone, Debug, Default)]
29pub struct ResolvedToolSet {
30    by_id: HashMap<ToolId, ResolvedTool>,
31    by_name: HashMap<ToolName, ToolId>,
32    ordered: Vec<ToolId>,
33}
34
35impl ResolvedToolSet {
36    /// Empty set (required empty-tool path).
37    pub fn empty() -> Self {
38        Self::default()
39    }
40
41    /// Build from registered tools selected by request tool ids (already validated).
42    pub fn from_registered(tools: Vec<RegisteredTool>) -> Self {
43        let mut by_id = HashMap::new();
44        let mut by_name = HashMap::new();
45        let mut ordered = Vec::with_capacity(tools.len());
46        for tool in tools {
47            by_name.insert(tool.spec.name.clone(), tool.spec.id.clone());
48            ordered.push(tool.spec.id.clone());
49            by_id.insert(
50                tool.spec.id.clone(),
51                ResolvedTool {
52                    spec: tool.spec,
53                    handler: tool.handler,
54                },
55            );
56        }
57        Self {
58            by_id,
59            by_name,
60            ordered,
61        }
62    }
63
64    /// Whether empty.
65    pub fn is_empty(&self) -> bool {
66        self.by_id.is_empty()
67    }
68
69    /// Number of tools.
70    pub fn len(&self) -> usize {
71        self.by_id.len()
72    }
73
74    /// Specs in request order.
75    pub fn specs(&self) -> Vec<&ToolSpec> {
76        self.ordered
77            .iter()
78            .filter_map(|id| self.by_id.get(id).map(|t| &t.spec))
79            .collect()
80    }
81
82    /// Ordered resolved tools.
83    pub fn tools(&self) -> Vec<&ResolvedTool> {
84        self.ordered
85            .iter()
86            .filter_map(|id| self.by_id.get(id))
87            .collect()
88    }
89
90    /// Lookup by id.
91    pub fn get(&self, id: &ToolId) -> Option<&ResolvedTool> {
92        self.by_id.get(id)
93    }
94
95    /// Lookup by name.
96    pub fn get_by_name(&self, name: &ToolName) -> Option<&ResolvedTool> {
97        self.by_name.get(name).and_then(|id| self.by_id.get(id))
98    }
99
100    /// Whether name is allowlisted.
101    pub fn contains_name(&self, name: &ToolName) -> bool {
102        self.by_name.contains_key(name)
103    }
104}