1use std::fmt::Debug;
2
3use serde::{Deserialize, Deserializer, Serialize};
4use serde_json::Value;
5use thiserror::Error;
6
7use crate::ToolName;
8
9pub const MAX_TOOL_RESOURCES: usize = 128;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
14#[serde(rename_all = "snake_case")]
15pub enum ToolResourceAccess {
16 Read,
18 Write,
20 Delete,
22 Execute,
24 Unknown,
26}
27
28#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
30#[serde(rename_all = "camelCase")]
31pub struct ToolResource {
32 scheme: String,
33 locator: String,
34 access: ToolResourceAccess,
35}
36
37impl ToolResource {
38 pub fn new(
44 scheme: impl Into<String>,
45 locator: impl Into<String>,
46 access: ToolResourceAccess,
47 ) -> Result<Self, ToolResourceError> {
48 let scheme = scheme.into();
49 let locator = locator.into();
50 let mut bytes = scheme.bytes();
51 if scheme.len() > 64
52 || !bytes.next().is_some_and(|byte| byte.is_ascii_lowercase())
53 || !bytes.all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || byte == b'-')
54 {
55 return Err(ToolResourceError::InvalidScheme);
56 }
57 if locator.is_empty() || locator.len() > 2048 || locator.chars().any(char::is_control) {
58 return Err(ToolResourceError::InvalidLocator);
59 }
60 Ok(Self {
61 scheme,
62 locator,
63 access,
64 })
65 }
66
67 #[must_use]
69 pub fn scheme(&self) -> &str {
70 &self.scheme
71 }
72
73 #[must_use]
75 pub fn locator(&self) -> &str {
76 &self.locator
77 }
78
79 #[must_use]
81 pub const fn access(&self) -> ToolResourceAccess {
82 self.access
83 }
84}
85
86#[derive(Deserialize)]
87#[serde(rename_all = "camelCase")]
88struct RawToolResource {
89 scheme: String,
90 locator: String,
91 access: ToolResourceAccess,
92}
93
94impl<'de> Deserialize<'de> for ToolResource {
95 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
96 where
97 D: Deserializer<'de>,
98 {
99 let raw = RawToolResource::deserialize(deserializer)?;
100 Self::new(raw.scheme, raw.locator, raw.access).map_err(serde::de::Error::custom)
101 }
102}
103
104pub trait ToolResourceResolver: Debug + Send + Sync {
106 fn resolve(
112 &self,
113 tool_name: &ToolName,
114 arguments: &Value,
115 ) -> Result<Vec<ToolResource>, ToolResourceError>;
116}
117
118#[derive(Debug, Clone)]
120pub struct ArgumentResourceResolver {
121 argument: String,
122 scheme: String,
123 access: ToolResourceAccess,
124}
125
126impl ArgumentResourceResolver {
127 pub fn new(
133 argument: impl Into<String>,
134 scheme: impl Into<String>,
135 access: ToolResourceAccess,
136 ) -> Result<Self, ToolResourceError> {
137 let argument = argument.into();
138 let scheme = scheme.into();
139 if argument.is_empty()
140 || argument.len() > 128
141 || !argument
142 .bytes()
143 .all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || byte == b'_')
144 {
145 return Err(ToolResourceError::Unresolved);
146 }
147 ToolResource::new(&scheme, "validation", access)?;
148 Ok(Self {
149 argument,
150 scheme,
151 access,
152 })
153 }
154}
155
156impl ToolResourceResolver for ArgumentResourceResolver {
157 fn resolve(
158 &self,
159 _tool_name: &ToolName,
160 arguments: &Value,
161 ) -> Result<Vec<ToolResource>, ToolResourceError> {
162 let locator = arguments
163 .get(&self.argument)
164 .and_then(Value::as_str)
165 .ok_or(ToolResourceError::Unresolved)?;
166 Ok(vec![ToolResource::new(&self.scheme, locator, self.access)?])
167 }
168}
169
170#[derive(Debug, Clone)]
172pub struct StaticResourceResolver {
173 resources: Vec<ToolResource>,
174}
175
176impl StaticResourceResolver {
177 pub fn new(
183 resources: impl IntoIterator<Item = ToolResource>,
184 ) -> Result<Self, ToolResourceError> {
185 let mut resources = resources.into_iter().collect::<Vec<_>>();
186 resources.sort();
187 resources.dedup();
188 if resources.len() > MAX_TOOL_RESOURCES {
189 return Err(ToolResourceError::TooManyResources);
190 }
191 Ok(Self { resources })
192 }
193
194 #[must_use]
196 pub fn resources(&self) -> &[ToolResource] {
197 &self.resources
198 }
199}
200
201impl ToolResourceResolver for StaticResourceResolver {
202 fn resolve(
203 &self,
204 _tool_name: &ToolName,
205 _arguments: &Value,
206 ) -> Result<Vec<ToolResource>, ToolResourceError> {
207 Ok(self.resources.clone())
208 }
209}
210
211#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
213pub enum ToolResourceError {
214 #[error("tool resource scheme is invalid")]
216 InvalidScheme,
217 #[error("tool resource locator is invalid")]
219 InvalidLocator,
220 #[error("tool invocation resolves too many resources")]
222 TooManyResources,
223 #[error("tool resource cannot be resolved from arguments")]
225 Unresolved,
226}