1use std::{borrow::Cow, sync::Arc};
2
3#[cfg(feature = "server")]
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
8use serde_json::Value;
9
10use super::{Icon, JsonObject, MetaObject};
11
12#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
14#[serde(rename_all = "camelCase")]
15#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
16#[non_exhaustive]
17pub struct Tool {
18 pub name: Cow<'static, str>,
20 #[serde(skip_serializing_if = "Option::is_none")]
22 pub title: Option<String>,
23 #[serde(skip_serializing_if = "Option::is_none")]
25 pub description: Option<Cow<'static, str>>,
26 pub input_schema: Arc<JsonObject>,
28 #[serde(skip_serializing_if = "Option::is_none")]
30 pub output_schema: Option<Arc<JsonObject>>,
31 #[serde(skip_serializing_if = "Option::is_none")]
32 pub annotations: Option<ToolAnnotations>,
34 #[serde(skip_serializing_if = "Option::is_none")]
36 pub icons: Option<Vec<Icon>>,
37 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
39 pub meta: Option<MetaObject>,
40}
41
42#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
51#[serde(rename_all = "camelCase")]
52#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
53#[non_exhaustive]
54pub struct ToolAnnotations {
55 #[serde(skip_serializing_if = "Option::is_none")]
57 pub title: Option<String>,
58
59 #[serde(skip_serializing_if = "Option::is_none")]
63 pub read_only_hint: Option<bool>,
64
65 #[serde(skip_serializing_if = "Option::is_none")]
73 pub destructive_hint: Option<bool>,
74
75 #[serde(skip_serializing_if = "Option::is_none")]
82 pub idempotent_hint: Option<bool>,
83
84 #[serde(skip_serializing_if = "Option::is_none")]
91 pub open_world_hint: Option<bool>,
92}
93
94impl ToolAnnotations {
95 pub fn new() -> Self {
96 Self::default()
97 }
98
99 pub fn from_raw(
101 title: Option<String>,
102 read_only_hint: Option<bool>,
103 destructive_hint: Option<bool>,
104 idempotent_hint: Option<bool>,
105 open_world_hint: Option<bool>,
106 ) -> Self {
107 ToolAnnotations {
108 title,
109 read_only_hint,
110 destructive_hint,
111 idempotent_hint,
112 open_world_hint,
113 }
114 }
115
116 pub fn with_title<T>(title: T) -> Self
117 where
118 T: Into<String>,
119 {
120 ToolAnnotations {
121 title: Some(title.into()),
122 ..Self::default()
123 }
124 }
125 pub fn read_only(self, read_only: bool) -> Self {
126 ToolAnnotations {
127 read_only_hint: Some(read_only),
128 ..self
129 }
130 }
131 pub fn destructive(self, destructive: bool) -> Self {
132 ToolAnnotations {
133 destructive_hint: Some(destructive),
134 ..self
135 }
136 }
137 pub fn idempotent(self, idempotent: bool) -> Self {
138 ToolAnnotations {
139 idempotent_hint: Some(idempotent),
140 ..self
141 }
142 }
143 pub fn open_world(self, open_world: bool) -> Self {
144 ToolAnnotations {
145 open_world_hint: Some(open_world),
146 ..self
147 }
148 }
149
150 pub fn is_destructive(&self) -> bool {
152 self.destructive_hint.unwrap_or(true)
153 }
154
155 pub fn is_idempotent(&self) -> bool {
157 self.idempotent_hint.unwrap_or(false)
158 }
159}
160
161impl Tool {
162 pub fn new<N, D, S>(name: N, description: D, input_schema: S) -> Self
164 where
165 N: Into<Cow<'static, str>>,
166 D: Into<Cow<'static, str>>,
167 S: Into<Arc<JsonObject>>,
168 {
169 Tool {
170 name: name.into(),
171 title: None,
172 description: Some(description.into()),
173 input_schema: input_schema.into(),
174 output_schema: None,
175 annotations: None,
176 icons: None,
177 meta: None,
178 }
179 }
180
181 pub fn new_with_raw<N, S>(
183 name: N,
184 description: Option<Cow<'static, str>>,
185 input_schema: S,
186 ) -> Self
187 where
188 N: Into<Cow<'static, str>>,
189 S: Into<Arc<JsonObject>>,
190 {
191 Tool {
192 name: name.into(),
193 title: None,
194 description,
195 input_schema: input_schema.into(),
196 output_schema: None,
197 annotations: None,
198 icons: None,
199 meta: None,
200 }
201 }
202
203 pub fn with_title(mut self, title: impl Into<String>) -> Self {
205 self.title = Some(title.into());
206 self
207 }
208
209 pub fn with_raw_output_schema(mut self, output_schema: Arc<JsonObject>) -> Self {
211 self.output_schema = Some(output_schema);
212 self
213 }
214
215 pub fn with_annotations(mut self, annotations: ToolAnnotations) -> Self {
217 self.annotations = Some(annotations);
218 self
219 }
220
221 pub fn with_icons(mut self, icons: Vec<Icon>) -> Self {
223 self.icons = Some(icons);
224 self
225 }
226
227 pub fn with_meta(mut self, meta: MetaObject) -> Self {
229 self.meta = Some(meta);
230 self
231 }
232
233 pub fn annotate(self, annotations: ToolAnnotations) -> Self {
234 Tool {
235 annotations: Some(annotations),
236 ..self
237 }
238 }
239
240 #[cfg(feature = "server")]
242 pub fn with_output_schema<T: JsonSchema + 'static>(mut self) -> Self {
243 self.output_schema = Some(crate::handler::server::tool::schema_for_output::<T>());
244 self
245 }
246
247 #[cfg(feature = "server")]
249 pub fn with_input_schema<T: JsonSchema + 'static>(mut self) -> Self {
250 self.input_schema = crate::handler::server::tool::schema_for_input::<T>()
251 .unwrap_or_else(|e| panic!("Invalid input schema for tool '{}': {}", self.name, e));
252 self
253 }
254
255 pub fn schema_as_json_value(&self) -> Value {
257 Value::Object(self.input_schema.as_ref().clone())
258 }
259}