Skip to main content

agent_sdk_toolkit/resources/
executor.rs

1//! Resource-reader executor. Use this module to resolve explicit resource URIs into
2//! content refs through a host-provided resolver and policy. Reads may touch backing
3//! stores selected by the resolver; network fetches remain host-owned.
4//!
5use agent_sdk_core::{
6    AgentError, ExecutorRef, PolicyRef, ResourceReadRequest, ResourceRouteSnapshot, ResourceRouter,
7    ToolExecutionOutput, ToolExecutionRequest, ToolExecutor, ToolPackId, ToolPackKind,
8    ToolPackSnapshot,
9    domain::ContentRef,
10    policy::{CapabilityPermission, EffectClass, RiskClass},
11};
12
13use crate::{
14    packs::{ToolkitPackBundle, tool_snapshot},
15    testing::{InMemoryJsonArgumentStore, InMemoryToolkitContentStore},
16};
17
18use super::types::ResourceReaderRequest;
19
20#[derive(Clone)]
21/// Resource resource reader executor request or result value.
22/// Creating the value does not fetch content; resource executors document resolver and content-store effects.
23pub struct ResourceReaderExecutor {
24    executor_ref: ExecutorRef,
25    router: ResourceRouter,
26    arguments: InMemoryJsonArgumentStore,
27    content: InMemoryToolkitContentStore,
28}
29
30impl ResourceReaderExecutor {
31    /// Creates a new resources::executor value with explicit
32    /// caller-provided inputs. This constructor is data-only and
33    /// performs no I/O or external side effects.
34    pub fn new(
35        router: ResourceRouter,
36        arguments: InMemoryJsonArgumentStore,
37        content: InMemoryToolkitContentStore,
38    ) -> Self {
39        Self {
40            executor_ref: ExecutorRef::new("executor.toolkit.resource_reader.v1"),
41            router,
42            arguments,
43            content,
44        }
45    }
46
47    /// Returns the pack bundle currently held by this value.
48    /// This returns the toolkit pack bundle that registers the executor routes; it does not
49    /// activate the pack itself.
50    pub fn pack_bundle(
51        source: agent_sdk_core::SourceRef,
52        policy_ref: PolicyRef,
53    ) -> Result<ToolkitPackBundle, AgentError> {
54        let snapshot = ToolPackSnapshot::new(
55            ToolPackId::new("toolpack.resource_reader.v1"),
56            ToolPackKind::ResourceReaders,
57            "v1",
58            source.clone(),
59        )
60        .with_resource_route(ResourceRouteSnapshot {
61            scheme: "memory".to_string(),
62            source: source.clone(),
63            permission_policy_ref: policy_ref.clone(),
64            parser_version: "toolkit.resource.memory.v1".to_string(),
65            max_bytes: 64 * 1024,
66            privacy: agent_sdk_core::PrivacyClass::ContentRefsOnly,
67        })
68        .with_tool(tool_snapshot(
69            "cap.toolkit.resource_reader",
70            "resource_read",
71            "executor.toolkit.resource_reader.v1",
72            "schema.toolkit.resource_reader.v1",
73            vec![policy_ref],
74            vec![CapabilityPermission::MemoryRead],
75            EffectClass::Read,
76            RiskClass::Medium,
77            &source,
78        ));
79        ToolkitPackBundle::from_snapshot(snapshot)
80    }
81}
82
83impl ToolExecutor for ResourceReaderExecutor {
84    fn executor_ref(&self) -> &ExecutorRef {
85        &self.executor_ref
86    }
87
88    fn execute(&self, request: &ToolExecutionRequest) -> Result<ToolExecutionOutput, AgentError> {
89        let args_ref = request.effect_intent.content_refs.first().ok_or_else(|| {
90            AgentError::missing_required_field("resource_read.argument_content_ref")
91        })?;
92        let resource_request: ResourceReaderRequest = self.arguments.get(args_ref)?;
93        let resolution = self.router.resolve(&ResourceReadRequest {
94            uri: resource_request.uri,
95            source: request.resolved_call.request.source.clone(),
96            policy_refs: request.resolved_call.route.policy_refs.clone(),
97            max_bytes: resource_request.max_bytes,
98        })?;
99        let content_ref = ContentRef::new(format!(
100            "content.{}.resource",
101            request.resolved_call.request.tool_call_id.as_str()
102        ));
103        self.content.put(content_ref.clone(), &resolution)?;
104        let mut output = ToolExecutionOutput::completed("resource reader returned content ref");
105        output.content_refs.push(content_ref);
106        Ok(output)
107    }
108}