1use std::{
2 collections::HashSet,
3 sync::{Arc, Mutex},
4};
5
6use anyhow::Result;
7use async_trait::async_trait;
8use goose_provider_types::conversation::{
9 message::{Message, MessageContent, ToolRequest},
10 Conversation,
11};
12use rmcp::{
13 handler::server::router::tool::{AsyncTool, SyncTool, ToolBase},
14 model::{CallToolRequestParams, CallToolResult, ErrorData, JsonObject, Tool},
15};
16use serde_json::{json, Value};
17
18use crate::operation::{
19 applied, messages_since_kickoff, not_applicable, Emitter, Operation, OperationFuture,
20 OperationResult,
21};
22use goose_provider_types::maybe_send::{MaybeSend, MaybeSync};
23
24fn empty_input_schema() -> Arc<JsonObject> {
25 Arc::new(
26 serde_json::from_value(json!({
27 "type": "object",
28 "properties": {},
29 "additionalProperties": false
30 }))
31 .expect("empty tool input schema is an object"),
32 )
33}
34
35fn definition<T: ToolBase>() -> Tool {
36 let mut tool = Tool::new_with_raw(
37 T::name(),
38 T::description(),
39 T::input_schema().unwrap_or_else(empty_input_schema),
40 );
41 if let Some(title) = T::title() {
42 tool = tool.with_title(title);
43 }
44 if let Some(output_schema) = T::output_schema() {
45 tool = tool.with_raw_output_schema(output_schema);
46 }
47 if let Some(annotations) = T::annotations() {
48 tool = tool.with_annotations(annotations);
49 }
50 if let Some(icons) = T::icons() {
51 tool = tool.with_icons(icons);
52 }
53 if let Some(meta) = T::meta() {
54 tool = tool.with_meta(meta);
55 }
56 tool
57}
58
59fn pending_requests(requests: Vec<ToolRequest>, tool_names: &HashSet<&str>) -> Vec<ToolRequest> {
60 requests
61 .into_iter()
62 .filter(|request| {
63 request
64 .tool_call
65 .as_ref()
66 .map_or(true, |call| tool_names.contains(call.name.as_ref()))
67 })
68 .collect()
69}
70
71fn parameters<T: ToolBase>(arguments: Option<JsonObject>) -> Result<T::Parameter, ErrorData> {
72 if T::input_schema().is_none() {
73 return Ok(T::Parameter::default());
74 }
75
76 serde_json::from_value(Value::Object(arguments.unwrap_or_default())).map_err(|error| {
77 ErrorData::invalid_params(format!("failed to deserialize parameters: {error}"), None)
78 })
79}
80
81fn result<T: ToolBase>(output: Result<T::Output, T::Error>) -> Result<CallToolResult, ErrorData> {
82 let output = output.map_err(Into::into)?;
83 let value = serde_json::to_value(output).map_err(|error| {
84 ErrorData::internal_error(format!("failed to serialize tool output: {error}"), None)
85 })?;
86 Ok(CallToolResult::structured(value))
87}
88
89#[cfg(not(target_arch = "wasm32"))]
90async fn invoke_sync<S, T>(
91 session: S,
92 parameters: T::Parameter,
93) -> Result<CallToolResult, ErrorData>
94where
95 S: Send + Sync + 'static,
96 T: SyncTool<S> + 'static,
97{
98 tokio::task::spawn_blocking(move || result::<T>(T::invoke(&session, parameters)))
99 .await
100 .map_err(|error| {
101 ErrorData::internal_error(format!("synchronous tool task failed: {error}"), None)
102 })?
103}
104
105#[cfg(target_arch = "wasm32")]
108async fn invoke_sync<S, T>(
109 session: S,
110 parameters: T::Parameter,
111) -> Result<CallToolResult, ErrorData>
112where
113 S: MaybeSend + MaybeSync + 'static,
114 T: SyncTool<S>,
115{
116 result::<T>(T::invoke(&session, parameters))
117}
118
119#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
124#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
125pub trait ToolProvider<S>: MaybeSend + MaybeSync {
126 async fn tools(&self, session: &S) -> Result<Vec<Tool>>;
127
128 async fn call(
129 &self,
130 session: &S,
131 request_id: &str,
132 call: CallToolRequestParams,
133 emit: &Emitter,
134 ) -> Result<CallToolResult, ErrorData>;
135}
136
137#[cfg(not(target_arch = "wasm32"))]
138type ToolHandler<S> = dyn for<'a> Fn(&'a S, Option<JsonObject>) -> OperationFuture<'a, Result<CallToolResult, ErrorData>>
139 + Send
140 + Sync;
141
142#[cfg(target_arch = "wasm32")]
143type ToolHandler<S> = dyn for<'a> Fn(
144 &'a S,
145 Option<JsonObject>,
146) -> OperationFuture<'a, Result<CallToolResult, ErrorData>>;
147
148struct RegisteredTool<S> {
149 definition: Tool,
150 handler: Arc<ToolHandler<S>>,
151}
152
153struct RegisteredToolProvider<S> {
154 tools: Vec<RegisteredTool<S>>,
155}
156
157#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
158#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
159impl<S> ToolProvider<S> for RegisteredToolProvider<S>
160where
161 S: MaybeSend + MaybeSync + 'static,
162{
163 async fn tools(&self, _session: &S) -> Result<Vec<Tool>> {
164 Ok(self
165 .tools
166 .iter()
167 .map(|tool| tool.definition.clone())
168 .collect())
169 }
170
171 async fn call(
172 &self,
173 session: &S,
174 _request_id: &str,
175 call: CallToolRequestParams,
176 _emit: &Emitter,
177 ) -> Result<CallToolResult, ErrorData> {
178 let tool = self
179 .tools
180 .iter()
181 .find(|tool| tool.definition.name == call.name)
182 .ok_or_else(|| {
183 ErrorData::invalid_params(format!("unknown tool {}", call.name), None)
184 })?;
185 (tool.handler)(session, call.arguments).await
186 }
187}
188
189pub struct ToolOperation<S> {
194 registered: RegisteredToolProvider<S>,
195 providers: Vec<Arc<dyn ToolProvider<S>>>,
196 response: Mutex<Option<Message>>,
197}
198
199impl<S> ToolOperation<S>
200where
201 S: MaybeSend + MaybeSync + 'static,
202{
203 pub fn new() -> Self {
204 Self {
205 registered: RegisteredToolProvider { tools: Vec::new() },
206 providers: Vec::new(),
207 response: Mutex::new(None),
208 }
209 }
210
211 fn register(&mut self, tool: RegisteredTool<S>) {
212 if let Some(existing) = self
213 .registered
214 .tools
215 .iter_mut()
216 .find(|existing| existing.definition.name == tool.definition.name)
217 {
218 *existing = tool;
219 } else {
220 self.registered.tools.push(tool);
221 }
222 }
223
224 pub fn with_provider(mut self, provider: Arc<dyn ToolProvider<S>>) -> Self {
225 self.providers.push(provider);
226 self
227 }
228
229 pub fn with_sync_tool<T>(mut self) -> Self
230 where
231 S: Clone,
232 T: SyncTool<S> + MaybeSend + MaybeSync + 'static,
233 {
234 self.register(RegisteredTool {
235 definition: definition::<T>(),
236 handler: Arc::new(|session, arguments| {
237 let session = session.clone();
238 Box::pin(async move {
239 let parameters = parameters::<T>(arguments)?;
240 invoke_sync::<S, T>(session, parameters).await
241 })
242 }),
243 });
244 self
245 }
246
247 pub fn with_async_tool<T>(mut self) -> Self
248 where
249 T: AsyncTool<S> + MaybeSend + MaybeSync + 'static,
250 {
251 self.register(RegisteredTool {
252 definition: definition::<T>(),
253 handler: Arc::new(|session, arguments| {
254 Box::pin(async move {
255 let parameters = parameters::<T>(arguments)?;
256 result::<T>(T::invoke(session, parameters).await)
257 })
258 }),
259 });
260 self
261 }
262
263 async fn available_tools(&self, session: &S) -> Result<Vec<(Tool, &dyn ToolProvider<S>)>> {
264 let mut available = self
265 .registered
266 .tools(session)
267 .await?
268 .into_iter()
269 .map(|tool| (tool, &self.registered as &dyn ToolProvider<S>))
270 .collect::<Vec<_>>();
271 for provider in &self.providers {
272 for tool in provider.tools(session).await? {
273 if available
274 .iter()
275 .any(|(available, _)| available.name == tool.name)
276 {
277 anyhow::bail!("multiple tool providers registered '{}'", tool.name);
278 }
279 available.push((tool, provider.as_ref()));
280 }
281 }
282 Ok(available)
283 }
284}
285
286impl<S> Default for ToolOperation<S>
287where
288 S: MaybeSend + MaybeSync + 'static,
289{
290 fn default() -> Self {
291 Self::new()
292 }
293}
294
295#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
296#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
297impl<S, E> Operation<S, E> for ToolOperation<S>
298where
299 S: MaybeSend + MaybeSync + 'static,
300 E: From<Message> + MaybeSend + 'static,
301{
302 fn name(&self) -> &'static str {
303 "tools"
304 }
305
306 async fn finalize_cancellation(
307 &self,
308 _session: &S,
309 _conversation: &Conversation,
310 emit: &Emitter,
311 ) -> Vec<E> {
312 let response = self.response.lock().unwrap().take();
313 match response {
314 Some(response) => vec![E::from(emit.message(response))],
315 None => Vec::new(),
316 }
317 }
318
319 async fn inference_tools(&self, session: &S) -> Result<Vec<Tool>> {
320 Ok(self
321 .available_tools(session)
322 .await?
323 .into_iter()
324 .map(|(tool, _)| tool)
325 .collect())
326 }
327
328 async fn run(
329 &self,
330 session: &S,
331 conversation: &Conversation,
332 emit: &Emitter,
333 ) -> Result<OperationResult<E>> {
334 let turn = messages_since_kickoff(conversation)?;
335 let answered: HashSet<&str> = turn
336 .iter()
337 .flat_map(Message::get_tool_response_ids)
338 .collect();
339 let pending = turn
340 .iter()
341 .flat_map(|message| message.content.iter())
342 .filter_map(MessageContent::as_tool_request)
343 .filter(|request| {
344 !request.was_executed_externally() && !answered.contains(request.id.as_str())
345 })
346 .cloned()
347 .collect::<Vec<_>>();
348 if pending.is_empty() {
349 return not_applicable();
350 }
351
352 let available = self.available_tools(session).await?;
353 let available_names = available
354 .iter()
355 .map(|(tool, _)| tool.name.as_ref())
356 .collect();
357 let pending = pending_requests(pending, &available_names);
358 if pending.is_empty() {
359 return not_applicable();
360 }
361
362 for request in pending {
363 let provider = match request.tool_call.as_ref() {
364 Ok(call) => available
365 .iter()
366 .find(|(tool, _)| tool.name == call.name)
367 .map(|(_, provider)| *provider)
368 .expect("pending requests were filtered by available tools"),
369 Err(_) => &self.registered as &dyn ToolProvider<S>,
370 };
371 let tool_result = match request.tool_call.as_ref() {
372 Err(error) => Err(error.clone()),
373 Ok(call) => {
374 provider
375 .call(session, &request.id, call.clone(), emit)
376 .await
377 }
378 };
379 let mut output = self.response.lock().unwrap();
380 let message =
381 output.get_or_insert_with(|| Message::user().with_generated_id_if_missing());
382 message.add_tool_response_with_metadata(
383 request.id,
384 tool_result,
385 request.metadata.as_ref(),
386 );
387 }
388 let message = self
389 .response
390 .lock()
391 .unwrap()
392 .take()
393 .expect("tool responses retained");
394 let message = emit.message(message);
395 applied([E::from(message)])
396 }
397}