Skip to main content

agentic_core/tool/
function.rs

1use std::collections::HashMap;
2
3use serde_json::Value;
4
5use crate::types::io::FunctionTool;
6use crate::types::tools::FunctionToolParam;
7use crate::utils::common::serialize_to_value_or_custom_default;
8
9use super::handler::{ToolError, ToolHandler};
10use super::registry::{ToolEntry, ToolType};
11
12impl From<&FunctionToolParam> for FunctionTool {
13    fn from(p: &FunctionToolParam) -> Self {
14        Self {
15            type_: "function".to_owned(),
16            name: p.name.as_str().to_owned(),
17            description: p.description.clone(),
18            parameters: p.parameters.clone(),
19            strict: p.strict,
20        }
21    }
22}
23
24/// Handler for `type: "function"` tools.
25///
26/// Function tools are client-owned: the gateway normalises them for vLLM but
27/// never executes them. `FunctionHandler` intentionally implements only
28/// [`ToolHandler`], not [`super::handler::GatewayExecutor`] — the type system
29/// makes it impossible to call `execute()` on a client-owned tool.
30#[derive(Debug)]
31pub struct FunctionHandler;
32
33impl ToolHandler for FunctionHandler {
34    fn tool_type(&self) -> ToolType {
35        ToolType::Function
36    }
37
38    fn validate(&self, param: &Value) -> Result<(), ToolError> {
39        match param.get("name").and_then(Value::as_str) {
40            Some(name) if !name.is_empty() => Ok(()),
41            _ => Err(ToolError::Config("function tool must have a non-empty name".into())),
42        }
43    }
44
45    fn normalize(&self, param: &Value) -> Vec<FunctionTool> {
46        // Deserialize into the typed struct so From<&FunctionToolParam> is the single
47        // conversion path. name is NonEmptyToolName so serde rejects empty names;
48        // any remaining deserialize error means validate() was not called first.
49        match serde_json::from_value::<FunctionToolParam>(param.clone()) {
50            Ok(p) => vec![FunctionTool::from(&p)],
51            Err(e) => {
52                tracing::warn!("normalize() called with invalid param: {e} — validate() must be called first");
53                vec![]
54            }
55        }
56    }
57}
58
59pub(crate) fn insert_function_entry(entries: &mut HashMap<String, ToolEntry>, p: &FunctionToolParam) {
60    // p.name is NonEmptyToolName — empty names are impossible here
61    // (serde rejects them at deserialization time).
62    serialize_to_value_or_custom_default(
63        p,
64        "function tool config serialization failed",
65        |config| {
66            if entries
67                .insert(
68                    p.name.as_str().to_owned(),
69                    ToolEntry {
70                        tool_type: ToolType::Function,
71                        config,
72                        server_label: None,
73                        handler: None,
74                    },
75                )
76                .is_some()
77            {
78                tracing::warn!(name = %p.name, "duplicate tool name — previous definition overwritten");
79            }
80        },
81        (),
82    );
83}