1use std::{fmt::Display, future::Future};
4
5use schemars::{JsonSchema, Schema};
6use serde::Serialize;
7use serde_json::Value;
8
9use crate::{
10 content::IntoContents,
11 protocol::{
12 content::Content,
13 rpc::RpcError,
14 tool::{Tool as PTool, ToolsCallResponse},
15 },
16};
17
18pub trait IntoToolResponse {
20 fn output_schema() -> Option<Schema>;
22
23 fn into_tool_response(self) -> ToolsCallResponse;
25}
26
27impl IntoToolResponse for () {
28 fn output_schema() -> Option<Schema> {
29 None
30 }
31
32 fn into_tool_response(self) -> ToolsCallResponse {
33 ToolsCallResponse {
34 content: vec![],
35 structured_content: None,
36 is_error: false,
37 }
38 }
39}
40
41impl<E> IntoToolResponse for Result<(), E>
42where
43 E: Display,
44{
45 fn output_schema() -> Option<Schema> {
46 None
47 }
48
49 fn into_tool_response(self) -> ToolsCallResponse {
50 match self {
51 Ok(_) => ToolsCallResponse {
52 content: vec![],
53 structured_content: None,
54 is_error: false,
55 },
56 Err(error) => ToolsCallResponse {
57 content: vec![Content::Text {
58 text: error.to_string(),
59 }],
60 structured_content: None,
61 is_error: true,
62 },
63 }
64 }
65}
66
67impl<T> IntoToolResponse for T
68where
69 T: IntoContents,
70{
71 fn output_schema() -> Option<Schema> {
72 None
73 }
74
75 fn into_tool_response(self) -> ToolsCallResponse {
76 ToolsCallResponse {
77 content: self.into_contents(),
78 structured_content: None,
79 is_error: false,
80 }
81 }
82}
83
84impl<T, E> IntoToolResponse for Result<T, E>
85where
86 T: IntoContents,
87 E: Display,
88{
89 fn output_schema() -> Option<Schema> {
90 None
91 }
92
93 fn into_tool_response(self) -> ToolsCallResponse {
94 match self {
95 Ok(value) => ToolsCallResponse {
96 content: value.into_contents(),
97 structured_content: None,
98 is_error: false,
99 },
100 Err(error) => ToolsCallResponse {
101 content: vec![Content::Text {
102 text: error.to_string(),
103 }],
104 structured_content: None,
105 is_error: true,
106 },
107 }
108 }
109}
110
111#[derive(Debug, Clone, Copy)]
113pub struct StructuredContent<T>(pub T);
114
115impl<T> IntoToolResponse for StructuredContent<T>
116where
117 T: Serialize + JsonSchema,
118{
119 fn output_schema() -> Option<Schema> {
120 Some(schemars::SchemaGenerator::default().into_root_schema_for::<T>())
121 }
122
123 fn into_tool_response(self) -> ToolsCallResponse {
124 ToolsCallResponse {
125 content: vec![Content::Text {
126 text: serde_json::to_string(&self.0).unwrap_or_default(),
127 }],
128 structured_content: Some(serde_json::to_value(&self.0).unwrap_or_default()),
129 is_error: false,
130 }
131 }
132}
133
134impl<T, E> IntoToolResponse for Result<StructuredContent<T>, E>
135where
136 T: Serialize + JsonSchema,
137 E: Display,
138{
139 fn output_schema() -> Option<Schema> {
140 Some(schemars::SchemaGenerator::default().into_root_schema_for::<T>())
141 }
142
143 fn into_tool_response(self) -> ToolsCallResponse {
144 match self {
145 Ok(value) => ToolsCallResponse {
146 content: vec![Content::Text {
147 text: serde_json::to_string(&value.0).unwrap_or_default(),
148 }],
149 structured_content: Some(serde_json::to_value(&value.0).unwrap_or_default()),
150 is_error: false,
151 },
152 Err(error) => ToolsCallResponse {
153 content: vec![Content::Text {
154 text: error.to_string(),
155 }],
156 structured_content: None,
157 is_error: true,
158 },
159 }
160 }
161}
162
163pub trait Tools {
167 fn instructions() -> &'static str;
169
170 fn list() -> Vec<PTool>;
172
173 fn call(
175 &mut self,
176 name: &str,
177 arguments: Value,
178 ) -> impl Future<Output = Result<ToolsCallResponse, RpcError>> + Send;
179}
180
181#[derive(Debug, Clone, Copy)]
183pub struct NoTools;
184
185impl Tools for NoTools {
186 #[inline]
187 fn instructions() -> &'static str {
188 ""
189 }
190
191 #[inline]
192 fn list() -> Vec<PTool> {
193 vec![]
194 }
195
196 #[inline]
197 async fn call(&mut self, name: &str, _arguments: Value) -> Result<ToolsCallResponse, RpcError> {
198 Err(RpcError::method_not_found(format!(
199 "tool '{name}' not found"
200 )))
201 }
202}