agent_sdk_toolkit/discovery/
executor.rs1use agent_sdk_core::{
8 AgentError, ExecutorRef, PolicyRef, ToolExecutionOutput, ToolExecutionRequest, ToolExecutor,
9 ToolPackId, ToolPackKind, ToolPackSnapshot,
10 domain::ContentRef,
11 policy::{CapabilityPermission, EffectClass, RiskClass},
12};
13
14use crate::{
15 packs::{ToolkitPackBundle, tool_snapshot},
16 testing::{InMemoryJsonArgumentStore, InMemoryToolkitContentStore},
17};
18
19use super::{
20 index::ToolDiscoveryIndex,
21 types::{ToolDiscoveryOutput, ToolDiscoveryRequest},
22};
23
24#[derive(Clone)]
25pub struct ToolDiscoveryExecutor {
28 executor_ref: ExecutorRef,
29 index: ToolDiscoveryIndex,
30 arguments: InMemoryJsonArgumentStore,
31 content: InMemoryToolkitContentStore,
32}
33
34impl ToolDiscoveryExecutor {
35 pub fn new(
39 index: ToolDiscoveryIndex,
40 arguments: InMemoryJsonArgumentStore,
41 content: InMemoryToolkitContentStore,
42 ) -> Self {
43 Self {
44 executor_ref: ExecutorRef::new("executor.toolkit.tool_discovery.v1"),
45 index,
46 arguments,
47 content,
48 }
49 }
50
51 pub fn pack_bundle(
55 source: agent_sdk_core::SourceRef,
56 policy_ref: PolicyRef,
57 ) -> Result<ToolkitPackBundle, AgentError> {
58 let snapshot = ToolPackSnapshot::new(
59 ToolPackId::new("toolpack.tool_discovery.v1"),
60 ToolPackKind::ToolDiscovery,
61 "v1",
62 source.clone(),
63 )
64 .with_discovery(agent_sdk_core::ToolDiscoverySnapshot {
65 discovery_index_id: "toolkit.discovery.default".to_string(),
66 activation_policy_ref: policy_ref.clone(),
67 package_delta_required: true,
68 })
69 .with_tool(tool_snapshot(
70 "cap.toolkit.tool_discovery",
71 "tool_discovery",
72 "executor.toolkit.tool_discovery.v1",
73 "schema.toolkit.tool_discovery.v1",
74 vec![policy_ref],
75 vec![CapabilityPermission::FilesystemRead],
76 EffectClass::Read,
77 RiskClass::Low,
78 &source,
79 ));
80 ToolkitPackBundle::from_snapshot(snapshot)
81 }
82}
83
84impl ToolExecutor for ToolDiscoveryExecutor {
85 fn executor_ref(&self) -> &ExecutorRef {
86 &self.executor_ref
87 }
88
89 fn execute(&self, request: &ToolExecutionRequest) -> Result<ToolExecutionOutput, AgentError> {
90 let args_ref = request.effect_intent.content_refs.first().ok_or_else(|| {
91 AgentError::missing_required_field("tool_discovery.argument_content_ref")
92 })?;
93 let discovery_request: ToolDiscoveryRequest = self.arguments.get(args_ref)?;
94 let output = ToolDiscoveryOutput {
95 query: discovery_request.query.clone(),
96 candidates: self.index.search(&discovery_request.query),
97 package_delta_required: true,
98 };
99 let content_ref = ContentRef::new(format!(
100 "content.{}.tool_discovery",
101 request.resolved_call.request.tool_call_id.as_str()
102 ));
103 self.content.put(content_ref.clone(), &output)?;
104 let mut envelope = ToolExecutionOutput::completed(
105 "tool discovery returned candidates without mutating active package",
106 );
107 envelope.content_refs.push(content_ref);
108 Ok(envelope)
109 }
110}