Skip to main content

agentic_core/tool/
normalize.rs

1use crate::types::io::FunctionTool;
2use crate::types::io::input::FunctionToolResultMessage;
3use crate::types::tools::ResponsesTool;
4use crate::utils::common::serialize_to_value_or_custom_default;
5
6use super::codex::CodexNamespaceHandler;
7use super::custom::CustomHandler;
8use super::function::FunctionHandler;
9use super::handler::{ToolError, ToolHandler, ToolOutput};
10use super::mcp::McpHandler;
11use super::registry::ToolType;
12use super::web_search::web_search_function_tool;
13
14impl ResponsesTool {
15    /// Validate this declaration through its tool handler before normalization.
16    ///
17    /// # Errors
18    ///
19    /// Returns [`ToolError::Config`] when the declaration cannot be safely
20    /// represented by the corresponding model-visible tool.
21    pub fn validate(&self) -> Result<(), ToolError> {
22        match self {
23            Self::Function(param) => serialize_to_value_or_custom_default(
24                param,
25                "function tool config serialization failed",
26                |param| FunctionHandler.validate(&param),
27                Err(ToolError::Config(
28                    "function tool config serialization failed".to_owned(),
29                )),
30            ),
31            Self::Mcp(param) => serialize_to_value_or_custom_default(
32                param,
33                "MCP tool config serialization failed",
34                |param| McpHandler::spec_from_param(&param).validate(&param),
35                Err(ToolError::Config("MCP tool config serialization failed".to_owned())),
36            ),
37            Self::WebSearch(_) | Self::FileSearch(_) | Self::CodeInterpreter(_) | Self::Unknown => Ok(()),
38            Self::Namespace(param) => serialize_to_value_or_custom_default(
39                param,
40                "namespace tool config serialization failed",
41                |param| CodexNamespaceHandler.validate(&param),
42                Err(ToolError::Config(
43                    "namespace tool config serialization failed".to_owned(),
44                )),
45            ),
46            Self::Custom(param) => serialize_to_value_or_custom_default(
47                param,
48                "custom tool config serialization failed",
49                |param| CustomHandler.validate(&param),
50                Err(ToolError::Config("custom tool config serialization failed".to_owned())),
51            ),
52        }
53    }
54
55    /// Return the gateway routing type this declaration would register as.
56    #[must_use]
57    pub fn tool_type(&self) -> Option<ToolType> {
58        match self {
59            Self::Function(_) => Some(ToolType::Function),
60            Self::Mcp(_) => Some(ToolType::Mcp),
61            Self::WebSearch(_) => Some(ToolType::WebSearch),
62            Self::FileSearch(_) => Some(ToolType::FileSearch),
63            Self::CodeInterpreter(_) => Some(ToolType::CodeInterpreter),
64            Self::Namespace(_) => Some(ToolType::CodexNamespace),
65            Self::Custom(_) => Some(ToolType::Custom),
66            Self::Unknown => None,
67        }
68    }
69
70    #[must_use]
71    pub fn is_gateway_owned(&self) -> bool {
72        self.tool_type().is_some_and(ToolType::is_gateway_owned)
73    }
74
75    /// Normalise function-like tool declarations to the `FunctionTool` wire format that vLLM understands.
76    ///
77    /// - `Function` variants convert via [`From<&FunctionToolParam>`] for `FunctionTool`.
78    ///   Returns an empty list and logs at `debug` level if the name is empty.
79    /// - `Mcp` variants convert gateway MCP built-ins to the function specs
80    ///   vLLM can call.
81    /// - Unformatted `Custom` variants become function tools with one string
82    ///   `input` parameter; formatted declarations are rejected by the request
83    ///   path because normalization cannot preserve constrained decoding.
84    /// - Unimplemented variants (`FileSearch`, `CodeInterpreter`) return
85    ///   an empty list and emit a `tracing::debug!`.
86    ///
87    /// `RequestPayload::to_upstream_request()` uses this conversion for
88    /// all model-visible tools.
89    #[must_use]
90    pub fn to_function_tools(&self) -> Vec<FunctionTool> {
91        match self {
92            // name is NonEmptyToolName — empty names are rejected by serde at
93            // deserialization time, so no runtime check is needed here.
94            Self::Function(p) => serialize_to_value_or_custom_default(
95                p,
96                "function tool config serialization failed",
97                |param| FunctionHandler.normalize(&param).into_iter().take(1).collect(),
98                vec![],
99            ),
100            Self::Mcp(p) => serialize_to_value_or_custom_default(
101                p,
102                "MCP tool config serialization failed",
103                |param| McpHandler::spec_from_param(&param).normalize(&param),
104                vec![],
105            ),
106            Self::WebSearch(_) => vec![web_search_function_tool()],
107            Self::FileSearch(_) => {
108                tracing::debug!("file_search tool skipped in normalize - handler not yet registered");
109                vec![]
110            }
111            Self::CodeInterpreter(_) => {
112                tracing::debug!("code_interpreter tool skipped in normalize - handler not yet registered");
113                vec![]
114            }
115            Self::Namespace(p) => serialize_to_value_or_custom_default(
116                p,
117                "function tool config serialization failed",
118                |param| CodexNamespaceHandler.normalize(&param),
119                vec![],
120            ),
121            Self::Custom(p) => serialize_to_value_or_custom_default(
122                p,
123                "custom tool config serialization failed",
124                |param| CustomHandler.normalize(&param),
125                vec![],
126            ),
127            Self::Unknown => {
128                tracing::debug!("unknown tool skipped in normalize");
129                vec![]
130            }
131        }
132    }
133}
134
135impl From<ToolOutput> for FunctionToolResultMessage {
136    fn from(o: ToolOutput) -> Self {
137        Self {
138            call_id: o.call_id,
139            output: o.output.into(),
140        }
141    }
142}