1use std::marker::PhantomData;
4use std::sync::Arc;
5
6use ferrin_schema::JsonSchema;
7use ferrin_schema::Schema;
8use ferrin_spec::JsonObject;
9use ferrin_spec::JsonValue;
10use ferrin_spec::ProviderOptions;
11use ferrin_spec::language_model::prompt::ToolResultOutput;
12use futures_core::Stream;
13use futures_util::StreamExt;
14use serde::Serialize;
15use serde::de::DeserializeOwned;
16
17use crate::callers::ToolCallerDefinition;
18use crate::error::ToolError;
19use crate::execute::ToolContext;
20use crate::execute::ToolExecute;
21use crate::execute::ToolOutput;
22use crate::execute::ToolOutputStream;
23use crate::tool::Description;
24use crate::tool::DescriptionContext;
25use crate::tool::ModelOutputArgs;
26use crate::tool::NeedsApproval;
27use crate::tool::Tool;
28use crate::tool::ToolHooks;
29use crate::tool::ToolKind;
30
31pub struct ToolBuilder<I> {
33 tool: Tool,
34 _input: PhantomData<fn() -> I>,
35}
36
37impl<I> std::fmt::Debug for ToolBuilder<I> {
38 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 f.debug_struct("ToolBuilder")
40 .field("tool", &self.tool)
41 .finish()
42 }
43}
44
45fn base(kind: ToolKind, input_schema: Schema<JsonValue>) -> Tool {
46 Tool {
47 kind,
48 description: None,
49 title: None,
50 input_schema,
51 output_schema: None,
52 context_schema: None,
53 execute: None,
54 needs_approval: NeedsApproval::Never,
55 strict: None,
56 input_examples: Vec::new(),
57 metadata: None,
58 provider_options: None,
59 hooks: ToolHooks::default(),
60 to_model_output: None,
61 caller_definition: None,
62 }
63}
64
65impl Tool {
66 #[must_use]
69 pub fn function<I: DeserializeOwned + JsonSchema + 'static>() -> ToolBuilder<I> {
70 ToolBuilder {
71 tool: base(ToolKind::Function, Schema::<I>::derived().erased()),
72 _input: PhantomData,
73 }
74 }
75
76 #[must_use]
79 pub fn function_with_schema(input_schema: Schema<JsonValue>) -> ToolBuilder<JsonValue> {
80 ToolBuilder {
81 tool: base(ToolKind::Function, input_schema),
82 _input: PhantomData,
83 }
84 }
85
86 #[must_use]
88 pub fn dynamic(input_schema: Schema<JsonValue>) -> ToolBuilder<JsonValue> {
89 ToolBuilder {
90 tool: base(ToolKind::Dynamic, input_schema),
91 _input: PhantomData,
92 }
93 }
94
95 #[must_use]
98 pub fn provider_defined(id: impl Into<String>, args: JsonObject) -> ToolBuilder<JsonValue> {
99 ToolBuilder {
100 tool: base(
101 ToolKind::ProviderDefined {
102 id: id.into(),
103 args,
104 },
105 Schema::any(),
106 ),
107 _input: PhantomData,
108 }
109 }
110
111 #[must_use]
113 pub fn provider_executed(id: impl Into<String>, args: JsonObject) -> ToolBuilder<JsonValue> {
114 ToolBuilder {
115 tool: base(
116 ToolKind::ProviderExecuted {
117 id: id.into(),
118 args,
119 supports_deferred_results: false,
120 },
121 Schema::any(),
122 ),
123 _input: PhantomData,
124 }
125 }
126}
127
128impl<I> ToolBuilder<I> {
129 #[must_use]
131 pub fn description(mut self, description: impl Into<String>) -> Self {
132 self.tool.description = Some(Description::Static(description.into()));
133 self
134 }
135
136 #[must_use]
138 pub fn description_fn<F, Fut>(mut self, function: F) -> Self
139 where
140 F: Fn(DescriptionContext) -> Fut + Send + Sync + 'static,
141 Fut: Future<Output = String> + Send + 'static,
142 {
143 self.tool.description = Some(Description::Dynamic(Arc::new(move |ctx| {
144 Box::pin(function(ctx))
145 })));
146 self
147 }
148
149 #[must_use]
151 pub fn title(mut self, title: impl Into<String>) -> Self {
152 self.tool.title = Some(title.into());
153 self
154 }
155
156 #[must_use]
158 pub fn input_schema(mut self, schema: Schema<JsonValue>) -> Self {
159 self.tool.input_schema = schema;
160 self
161 }
162
163 #[must_use]
165 pub fn output_schema(mut self, schema: Schema<JsonValue>) -> Self {
166 self.tool.output_schema = Some(schema);
167 self
168 }
169
170 #[must_use]
172 pub fn context_schema(mut self, schema: Schema<JsonValue>) -> Self {
173 self.tool.context_schema = Some(schema);
174 self
175 }
176
177 #[must_use]
179 pub fn needs_approval(mut self, needs_approval: NeedsApproval) -> Self {
180 self.tool.needs_approval = needs_approval;
181 self
182 }
183
184 #[must_use]
186 pub fn needs_approval_if<F, Fut>(mut self, function: F) -> Self
187 where
188 F: Fn(JsonValue, ToolContext) -> Fut + Send + Sync + 'static,
189 Fut: Future<Output = bool> + Send + 'static,
190 {
191 self.tool.needs_approval =
192 NeedsApproval::Dynamic(Arc::new(move |input, ctx| Box::pin(function(input, ctx))));
193 self
194 }
195
196 #[must_use]
198 pub fn strict(mut self, strict: bool) -> Self {
199 self.tool.strict = Some(strict);
200 self
201 }
202
203 #[must_use]
205 pub fn input_example(mut self, example: JsonObject) -> Self {
206 self.tool.input_examples.push(example);
207 self
208 }
209
210 #[must_use]
212 pub fn input_examples(mut self, examples: impl IntoIterator<Item = JsonObject>) -> Self {
213 self.tool.input_examples.extend(examples);
214 self
215 }
216
217 #[must_use]
219 pub fn metadata(mut self, metadata: JsonObject) -> Self {
220 self.tool.metadata = Some(metadata);
221 self
222 }
223
224 #[must_use]
226 pub fn provider_options(mut self, options: ProviderOptions) -> Self {
227 self.tool.provider_options = Some(options);
228 self
229 }
230
231 #[must_use]
233 pub fn on_input_start<F, Fut>(mut self, function: F) -> Self
234 where
235 F: Fn(ToolContext) -> Fut + Send + Sync + 'static,
236 Fut: Future<Output = ()> + Send + 'static,
237 {
238 self.tool.hooks.on_input_start = Some(Arc::new(move |ctx| Box::pin(function(ctx))));
239 self
240 }
241
242 #[must_use]
244 pub fn on_input_delta<F, Fut>(mut self, function: F) -> Self
245 where
246 F: Fn(String, ToolContext) -> Fut + Send + Sync + 'static,
247 Fut: Future<Output = ()> + Send + 'static,
248 {
249 self.tool.hooks.on_input_delta =
250 Some(Arc::new(move |delta, ctx| Box::pin(function(delta, ctx))));
251 self
252 }
253
254 #[must_use]
256 pub fn on_input_available<F, Fut>(mut self, function: F) -> Self
257 where
258 F: Fn(JsonValue, ToolContext) -> Fut + Send + Sync + 'static,
259 Fut: Future<Output = ()> + Send + 'static,
260 {
261 self.tool.hooks.on_input_available =
262 Some(Arc::new(move |input, ctx| Box::pin(function(input, ctx))));
263 self
264 }
265
266 #[must_use]
268 pub fn to_model_output<F>(mut self, function: F) -> Self
269 where
270 F: Fn(ModelOutputArgs<'_>) -> ToolResultOutput + Send + Sync + 'static,
271 {
272 self.tool.to_model_output = Some(Arc::new(function));
273 self
274 }
275
276 #[must_use]
278 pub fn caller(mut self, definition: ToolCallerDefinition) -> Self {
279 self.tool.caller_definition = Some(definition);
280 self
281 }
282
283 #[must_use]
286 pub fn supports_deferred_results(mut self, supported: bool) -> Self {
287 if let ToolKind::ProviderExecuted {
288 supports_deferred_results,
289 ..
290 } = &mut self.tool.kind
291 {
292 *supports_deferred_results = supported;
293 }
294 self
295 }
296
297 #[must_use]
299 pub fn execute_with(mut self, executor: Arc<dyn ToolExecute>) -> Self {
300 self.tool.execute = Some(executor);
301 self
302 }
303
304 #[must_use]
306 pub fn build(self) -> Tool {
307 self.tool
308 }
309}
310
311impl<I: DeserializeOwned + Send + 'static> ToolBuilder<I> {
312 #[must_use]
314 pub fn execute<F, Fut, O>(mut self, function: F) -> Self
315 where
316 F: Fn(I, ToolContext) -> Fut + Send + Sync + 'static,
317 Fut: Future<Output = Result<O, ToolError>> + Send + 'static,
318 O: Serialize + 'static,
319 {
320 self.tool.execute = Some(Arc::new(FnExecute {
321 function,
322 _marker: PhantomData::<fn() -> (I, O)>,
323 }));
324 self
325 }
326
327 #[must_use]
330 pub fn execute_stream<F, S, O>(mut self, function: F) -> Self
331 where
332 F: Fn(I, ToolContext) -> S + Send + Sync + 'static,
333 S: Stream<Item = Result<O, ToolError>> + Send + 'static,
334 O: Serialize + 'static,
335 {
336 self.tool.execute = Some(Arc::new(StreamExecute {
337 function,
338 _marker: PhantomData::<fn() -> (I, O)>,
339 }));
340 self
341 }
342}
343
344fn decode<I: DeserializeOwned>(input: JsonValue) -> Result<I, ToolError> {
345 serde_json::from_value(input).map_err(|error| {
346 ToolError::message(format!("invalid tool input: {error}")).with_cause(error)
347 })
348}
349
350fn encode<O: Serialize>(output: &O) -> Result<JsonValue, ToolError> {
351 serde_json::to_value(output).map_err(|error| {
352 ToolError::message(format!("tool output is not serializable: {error}")).with_cause(error)
353 })
354}
355
356struct FnExecute<I, O, F> {
357 function: F,
358 _marker: PhantomData<fn() -> (I, O)>,
359}
360
361impl<I, O, F, Fut> ToolExecute for FnExecute<I, O, F>
362where
363 I: DeserializeOwned + Send + 'static,
364 O: Serialize + 'static,
365 F: Fn(I, ToolContext) -> Fut + Send + Sync + 'static,
366 Fut: Future<Output = Result<O, ToolError>> + Send + 'static,
367{
368 fn execute(&self, input: JsonValue, ctx: ToolContext) -> ToolOutputStream {
369 let future = decode::<I>(input).map(|input| (self.function)(input, ctx));
370 Box::pin(futures_util::stream::once(async move {
371 let output = future?.await?;
372 encode(&output).map(ToolOutput::Final)
373 }))
374 }
375}
376
377struct StreamExecute<I, O, F> {
378 function: F,
379 _marker: PhantomData<fn() -> (I, O)>,
380}
381
382struct StreamState<S> {
383 inner: S,
384 last: Option<JsonValue>,
385 done: bool,
386}
387
388impl<I, O, F, S> ToolExecute for StreamExecute<I, O, F>
389where
390 I: DeserializeOwned + Send + 'static,
391 O: Serialize + 'static,
392 F: Fn(I, ToolContext) -> S + Send + Sync + 'static,
393 S: Stream<Item = Result<O, ToolError>> + Send + 'static,
394{
395 fn execute(&self, input: JsonValue, ctx: ToolContext) -> ToolOutputStream {
396 let inner = match decode::<I>(input) {
397 Ok(input) => (self.function)(input, ctx),
398 Err(error) => {
399 return Box::pin(futures_util::stream::once(std::future::ready(Err(error))));
400 }
401 };
402 let state = StreamState {
403 inner: Box::pin(inner),
404 last: None,
405 done: false,
406 };
407 Box::pin(futures_util::stream::unfold(
408 state,
409 |mut state| async move {
410 if state.done {
411 return None;
412 }
413 match state.inner.next().await {
414 Some(Ok(output)) => match encode(&output) {
415 Ok(value) => {
416 state.last = Some(value.clone());
417 Some((Ok(ToolOutput::Preliminary(value)), state))
418 }
419 Err(error) => {
420 state.done = true;
421 Some((Err(error), state))
422 }
423 },
424 Some(Err(error)) => {
425 state.done = true;
426 Some((Err(error), state))
427 }
428 None => {
429 state.done = true;
430 let value = state.last.take().unwrap_or(JsonValue::Null);
431 Some((Ok(ToolOutput::Final(value)), state))
432 }
433 }
434 },
435 ))
436 }
437}