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]
85 pub fn into_builder(self) -> ToolBuilder<JsonValue> {
86 ToolBuilder {
87 tool: self,
88 _input: PhantomData,
89 }
90 }
91
92 #[must_use]
95 pub fn function<I: DeserializeOwned + JsonSchema + 'static>() -> ToolBuilder<I> {
96 ToolBuilder {
97 tool: base(ToolKind::Function, Schema::<I>::derived().erased()),
98 _input: PhantomData,
99 }
100 }
101
102 #[must_use]
105 pub fn function_with_schema(input_schema: Schema<JsonValue>) -> ToolBuilder<JsonValue> {
106 ToolBuilder {
107 tool: base(ToolKind::Function, input_schema),
108 _input: PhantomData,
109 }
110 }
111
112 #[must_use]
114 pub fn dynamic(input_schema: Schema<JsonValue>) -> ToolBuilder<JsonValue> {
115 ToolBuilder {
116 tool: base(ToolKind::Dynamic, input_schema),
117 _input: PhantomData,
118 }
119 }
120
121 #[must_use]
124 pub fn provider_defined(id: impl Into<String>, args: JsonObject) -> ToolBuilder<JsonValue> {
125 ToolBuilder {
126 tool: base(
127 ToolKind::ProviderDefined {
128 id: id.into(),
129 args,
130 },
131 Schema::any(),
132 ),
133 _input: PhantomData,
134 }
135 }
136
137 #[must_use]
139 pub fn provider_executed(id: impl Into<String>, args: JsonObject) -> ToolBuilder<JsonValue> {
140 ToolBuilder {
141 tool: base(
142 ToolKind::ProviderExecuted {
143 id: id.into(),
144 args,
145 supports_deferred_results: false,
146 },
147 Schema::any(),
148 ),
149 _input: PhantomData,
150 }
151 }
152}
153
154impl<I> ToolBuilder<I> {
155 #[must_use]
157 pub fn description(mut self, description: impl Into<String>) -> Self {
158 self.tool.description = Some(Description::Static(description.into()));
159 self
160 }
161
162 #[must_use]
164 pub fn description_fn<F, Fut>(mut self, function: F) -> Self
165 where
166 F: Fn(DescriptionContext) -> Fut + Send + Sync + 'static,
167 Fut: Future<Output = String> + Send + 'static,
168 {
169 self.tool.description = Some(Description::Dynamic(Arc::new(move |ctx| {
170 Box::pin(function(ctx))
171 })));
172 self
173 }
174
175 #[must_use]
177 pub fn title(mut self, title: impl Into<String>) -> Self {
178 self.tool.title = Some(title.into());
179 self
180 }
181
182 #[must_use]
184 pub fn input_schema(mut self, schema: Schema<JsonValue>) -> Self {
185 self.tool.input_schema = schema;
186 self
187 }
188
189 #[must_use]
191 pub fn output_schema(mut self, schema: Schema<JsonValue>) -> Self {
192 self.tool.output_schema = Some(schema);
193 self
194 }
195
196 #[must_use]
198 pub fn context_schema(mut self, schema: Schema<JsonValue>) -> Self {
199 self.tool.context_schema = Some(schema);
200 self
201 }
202
203 #[must_use]
205 pub fn needs_approval(mut self, needs_approval: NeedsApproval) -> Self {
206 self.tool.needs_approval = needs_approval;
207 self
208 }
209
210 #[must_use]
212 pub fn needs_approval_if<F, Fut>(mut self, function: F) -> Self
213 where
214 F: Fn(JsonValue, ToolContext) -> Fut + Send + Sync + 'static,
215 Fut: Future<Output = bool> + Send + 'static,
216 {
217 self.tool.needs_approval =
218 NeedsApproval::Dynamic(Arc::new(move |input, ctx| Box::pin(function(input, ctx))));
219 self
220 }
221
222 #[must_use]
224 pub fn strict(mut self, strict: bool) -> Self {
225 self.tool.strict = Some(strict);
226 self
227 }
228
229 #[must_use]
231 pub fn input_example(mut self, example: JsonObject) -> Self {
232 self.tool.input_examples.push(example);
233 self
234 }
235
236 #[must_use]
238 pub fn input_examples(mut self, examples: impl IntoIterator<Item = JsonObject>) -> Self {
239 self.tool.input_examples.extend(examples);
240 self
241 }
242
243 #[must_use]
245 pub fn metadata(mut self, metadata: JsonObject) -> Self {
246 self.tool.metadata = Some(metadata);
247 self
248 }
249
250 #[must_use]
252 pub fn provider_options(mut self, options: ProviderOptions) -> Self {
253 self.tool.provider_options = Some(options);
254 self
255 }
256
257 #[must_use]
259 pub fn on_input_start<F, Fut>(mut self, function: F) -> Self
260 where
261 F: Fn(ToolContext) -> Fut + Send + Sync + 'static,
262 Fut: Future<Output = ()> + Send + 'static,
263 {
264 self.tool.hooks.on_input_start = Some(Arc::new(move |ctx| Box::pin(function(ctx))));
265 self
266 }
267
268 #[must_use]
270 pub fn on_input_delta<F, Fut>(mut self, function: F) -> Self
271 where
272 F: Fn(String, ToolContext) -> Fut + Send + Sync + 'static,
273 Fut: Future<Output = ()> + Send + 'static,
274 {
275 self.tool.hooks.on_input_delta =
276 Some(Arc::new(move |delta, ctx| Box::pin(function(delta, ctx))));
277 self
278 }
279
280 #[must_use]
282 pub fn on_input_available<F, Fut>(mut self, function: F) -> Self
283 where
284 F: Fn(JsonValue, ToolContext) -> Fut + Send + Sync + 'static,
285 Fut: Future<Output = ()> + Send + 'static,
286 {
287 self.tool.hooks.on_input_available =
288 Some(Arc::new(move |input, ctx| Box::pin(function(input, ctx))));
289 self
290 }
291
292 #[must_use]
294 pub fn to_model_output<F>(mut self, function: F) -> Self
295 where
296 F: Fn(ModelOutputArgs<'_>) -> ToolResultOutput + Send + Sync + 'static,
297 {
298 self.tool.to_model_output = Some(Arc::new(function));
299 self
300 }
301
302 #[must_use]
304 pub fn caller(mut self, definition: ToolCallerDefinition) -> Self {
305 self.tool.caller_definition = Some(definition);
306 self
307 }
308
309 #[must_use]
312 pub fn supports_deferred_results(mut self, supported: bool) -> Self {
313 if let ToolKind::ProviderExecuted {
314 supports_deferred_results,
315 ..
316 } = &mut self.tool.kind
317 {
318 *supports_deferred_results = supported;
319 }
320 self
321 }
322
323 #[must_use]
325 pub fn execute_with(mut self, executor: Arc<dyn ToolExecute>) -> Self {
326 self.tool.execute = Some(executor);
327 self
328 }
329
330 #[must_use]
332 pub fn build(self) -> Tool {
333 self.tool
334 }
335}
336
337impl<I: DeserializeOwned + Send + 'static> ToolBuilder<I> {
338 #[must_use]
340 pub fn execute<F, Fut, O>(mut self, function: F) -> Self
341 where
342 F: Fn(I, ToolContext) -> Fut + Send + Sync + 'static,
343 Fut: Future<Output = Result<O, ToolError>> + Send + 'static,
344 O: Serialize + 'static,
345 {
346 self.tool.execute = Some(Arc::new(FnExecute {
347 function,
348 _marker: PhantomData::<fn() -> (I, O)>,
349 }));
350 self
351 }
352
353 #[must_use]
356 pub fn execute_stream<F, S, O>(mut self, function: F) -> Self
357 where
358 F: Fn(I, ToolContext) -> S + Send + Sync + 'static,
359 S: Stream<Item = Result<O, ToolError>> + Send + 'static,
360 O: Serialize + 'static,
361 {
362 self.tool.execute = Some(Arc::new(StreamExecute {
363 function,
364 _marker: PhantomData::<fn() -> (I, O)>,
365 }));
366 self
367 }
368}
369
370fn decode<I: DeserializeOwned>(input: JsonValue) -> Result<I, ToolError> {
371 serde_json::from_value(input).map_err(|error| {
372 ToolError::message(format!("invalid tool input: {error}")).with_cause(error)
373 })
374}
375
376fn encode<O: Serialize>(output: &O) -> Result<JsonValue, ToolError> {
377 serde_json::to_value(output).map_err(|error| {
378 ToolError::message(format!("tool output is not serializable: {error}")).with_cause(error)
379 })
380}
381
382struct FnExecute<I, O, F> {
383 function: F,
384 _marker: PhantomData<fn() -> (I, O)>,
385}
386
387impl<I, O, F, Fut> ToolExecute for FnExecute<I, O, F>
388where
389 I: DeserializeOwned + Send + 'static,
390 O: Serialize + 'static,
391 F: Fn(I, ToolContext) -> Fut + Send + Sync + 'static,
392 Fut: Future<Output = Result<O, ToolError>> + Send + 'static,
393{
394 fn execute(&self, input: JsonValue, ctx: ToolContext) -> ToolOutputStream {
395 let future = decode::<I>(input).map(|input| (self.function)(input, ctx));
396 Box::pin(futures_util::stream::once(async move {
397 let output = future?.await?;
398 encode(&output).map(ToolOutput::Final)
399 }))
400 }
401}
402
403struct StreamExecute<I, O, F> {
404 function: F,
405 _marker: PhantomData<fn() -> (I, O)>,
406}
407
408struct StreamState<S> {
409 inner: S,
410 last: Option<JsonValue>,
411 done: bool,
412}
413
414impl<I, O, F, S> ToolExecute for StreamExecute<I, O, F>
415where
416 I: DeserializeOwned + Send + 'static,
417 O: Serialize + 'static,
418 F: Fn(I, ToolContext) -> S + Send + Sync + 'static,
419 S: Stream<Item = Result<O, ToolError>> + Send + 'static,
420{
421 fn execute(&self, input: JsonValue, ctx: ToolContext) -> ToolOutputStream {
422 let inner = match decode::<I>(input) {
423 Ok(input) => (self.function)(input, ctx),
424 Err(error) => {
425 return Box::pin(futures_util::stream::once(std::future::ready(Err(error))));
426 }
427 };
428 let state = StreamState {
429 inner: Box::pin(inner),
430 last: None,
431 done: false,
432 };
433 Box::pin(futures_util::stream::unfold(
434 state,
435 |mut state| async move {
436 if state.done {
437 return None;
438 }
439 match state.inner.next().await {
440 Some(Ok(output)) => match encode(&output) {
441 Ok(value) => {
442 state.last = Some(value.clone());
443 Some((Ok(ToolOutput::Preliminary(value)), state))
444 }
445 Err(error) => {
446 state.done = true;
447 Some((Err(error), state))
448 }
449 },
450 Some(Err(error)) => {
451 state.done = true;
452 Some((Err(error), state))
453 }
454 None => {
455 state.done = true;
456 let value = state.last.take().unwrap_or(JsonValue::Null);
457 Some((Ok(ToolOutput::Final(value)), state))
458 }
459 }
460 },
461 ))
462 }
463}