agentic_core/tool/handler.rs
1use std::future::Future;
2use std::pin::Pin;
3
4use serde_json::Value;
5
6use crate::types::io::FunctionTool;
7
8#[derive(Debug, Clone)]
9pub struct ToolOutput {
10 pub call_id: String,
11 pub output: String,
12}
13
14#[derive(Debug, thiserror::Error)]
15pub enum ToolError {
16 #[error("execution failed: {0}")]
17 Execution(String),
18 #[error("invalid tool config: {0}")]
19 Config(String),
20}
21
22/// Trait implemented by every tool type — client-owned and gateway-owned alike.
23///
24/// Covers validation and normalization: the steps that apply to all tools
25/// regardless of who executes them.
26///
27/// Implementations must be `Send + Sync` so they can be stored behind `Arc<dyn
28/// ToolHandler>` and used across async task boundaries.
29pub trait ToolHandler: Send + Sync {
30 #[must_use]
31 fn tool_type(&self) -> super::registry::ToolType;
32
33 /// Validate the tool param JSON.
34 ///
35 /// # Errors
36 ///
37 /// Returns [`ToolError::Config`] for obviously invalid configurations.
38 fn validate(&self, param: &Value) -> Result<(), ToolError>;
39
40 /// Normalise this tool declaration into vLLM-compatible `FunctionTool` entries.
41 #[must_use]
42 fn normalize(&self, param: &Value) -> Vec<FunctionTool>;
43}
44
45/// Extension of [`ToolHandler`] for tool types that are executed by the gateway.
46///
47/// Only gateway-owned tools (`Mcp`, `WebSearch`, `FileSearch`, `CodeInterpreter`)
48/// implement this trait. Client-owned tools (`Function`) do not — the type system
49/// makes it impossible to call `execute()` on them.
50///
51/// ## Note on `async fn` in traits
52///
53/// Native `async fn` in traits (Rust 1.75+) is not yet `dyn`-compatible. Since
54/// PR B will store handlers as `Arc<dyn GatewayExecutor>`, we use explicit
55/// `Pin<Box<dyn Future>>` return types.
56pub trait GatewayExecutor: ToolHandler + 'static {
57 /// Execute a tool call and return the result.
58 ///
59 /// ## `config` parameter
60 ///
61 /// `config` is the serialised **server-level** tool param (i.e. the `*ToolParam`
62 /// struct stored in [`super::registry::ToolEntry::config`]). It is **not** the
63 /// per-tool parameter schema.
64 ///
65 /// # Errors
66 ///
67 /// Returns [`ToolError::Execution`] if the tool call fails.
68 fn execute(
69 &self,
70 call_id: &str,
71 tool_name: &str,
72 arguments: &str,
73 config: &Value,
74 ) -> Pin<Box<dyn Future<Output = Result<ToolOutput, ToolError>> + Send + '_>>;
75}
76
77#[cfg(test)]
78mod tests {
79 use std::sync::Arc;
80
81 use super::*;
82
83 // Compile-time check: Arc<dyn GatewayExecutor> must be constructable.
84 // This fails to compile if GatewayExecutor ever becomes dyn-incompatible.
85 fn _assert_gateway_executor_dyn_compatible(_: Arc<dyn GatewayExecutor>) {}
86}