Skip to main content

lash_core/plugin/session_obj/
tools.rs

1use std::collections::{BTreeMap, BTreeSet};
2use std::sync::Arc;
3
4use super::*;
5
6impl PluginSession {
7    pub fn resolved_tool_catalog(
8        &self,
9        session_id: &str,
10    ) -> Result<Arc<crate::ToolCatalog>, PluginError> {
11        let tools = self.tools.tool_manifests();
12        let contract_provider = Arc::clone(&self.tools);
13        let resolve_contract: lash_sansio::ToolContractResolver =
14            Arc::new(move |name: &str| contract_provider.resolve_contract(name));
15        Ok(Arc::new(self.resolve_tool_catalog(ToolCatalogContext {
16            session_id: session_id.to_string(),
17            tools,
18            resolve_contract: Some(Arc::clone(&resolve_contract)),
19            tool_access: self.tool_access.clone(),
20            subagent: self.subagent.clone(),
21            extensions: self.extensions.clone(),
22        })?))
23    }
24
25    /// Project every Tool Catalog member to a JSON record for host-owned
26    /// discovery (e.g. the reference `search_tools` example in `lash-cli`).
27    pub fn tool_catalog(&self, session_id: &str) -> Result<Vec<serde_json::Value>, PluginError> {
28        let catalog = self.resolved_tool_catalog(session_id)?;
29        Ok(crate::tool_registry::project_tool_catalog(
30            catalog.tools.iter().cloned(),
31        ))
32    }
33
34    pub fn resolve_tool_catalog(
35        &self,
36        ctx: ToolCatalogContext,
37    ) -> Result<crate::ToolCatalog, PluginError> {
38        let mut contributions = collect_owned_sync(
39            &self.contributions.tool_catalog_contributors,
40            ToolCatalogContext {
41                session_id: ctx.session_id.clone(),
42                tools: ctx.tools.clone(),
43                resolve_contract: ctx.resolve_contract.clone(),
44                tool_access: ctx.tool_access.clone(),
45                subagent: ctx.subagent.clone(),
46                extensions: ctx.extensions.clone(),
47            },
48            |hook, ctx| hook(ctx),
49        )?
50        .into_iter()
51        .map(|owned| owned.value)
52        .collect::<Vec<_>>();
53        contributions.push(self.tool_catalog_overlay.clone());
54        let (tools, resolve_contract) = if ctx.tool_access.tools.is_empty() {
55            (ctx.tools, ctx.resolve_contract)
56        } else {
57            let contracts = ctx
58                .tool_access
59                .tools
60                .iter()
61                .map(|tool| (tool.name().to_string(), Arc::new(tool.contract())))
62                .collect::<BTreeMap<_, _>>();
63            (
64                ctx.tool_access
65                    .tools
66                    .iter()
67                    .map(|tool| tool.manifest())
68                    .collect(),
69                Some(Arc::new(move |name: &str| contracts.get(name).cloned())
70                    as lash_sansio::ToolContractResolver),
71            )
72        };
73        let authority_hidden_tools = tools
74            .iter()
75            .filter(|tool| ctx.tool_access.hides(&tool.name))
76            .map(|tool| tool.name.clone())
77            .collect::<BTreeSet<_>>();
78        if !authority_hidden_tools.is_empty() {
79            contributions.push(ToolCatalogContribution {
80                remove: authority_hidden_tools.into_iter().collect(),
81            });
82        }
83        Ok(crate::build_tool_catalog(crate::ToolCatalogBuildInput {
84            tools,
85            resolve_contract,
86            contributions,
87        }))
88    }
89}