use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FunctionTool {
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parameters: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub strict: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum KnownTool {
Function(FunctionTool),
WebSearch {
#[serde(default, skip_serializing_if = "Option::is_none")]
search_context_size: Option<String>,
},
FileSearch {
vector_store_ids: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
max_num_results: Option<u32>,
},
CodeInterpreter {
container: serde_json::Value,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Tool {
Known(KnownTool),
Other(serde_json::Value),
}
impl Tool {
pub fn function(
name: impl Into<String>,
description: impl Into<String>,
parameters: serde_json::Value,
) -> Self {
Self::Known(KnownTool::Function(FunctionTool {
name: name.into(),
description: Some(description.into()),
parameters: Some(parameters),
strict: None,
}))
}
pub fn web_search() -> Self {
Self::Known(KnownTool::WebSearch {
search_context_size: None,
})
}
pub fn file_search(vector_store_ids: Vec<String>) -> Self {
Self::Known(KnownTool::FileSearch {
vector_store_ids,
max_num_results: None,
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ToolChoice {
Mode(String),
Typed(serde_json::Value),
}
impl ToolChoice {
pub const NONE: &'static str = "none";
pub const AUTO: &'static str = "auto";
pub const REQUIRED: &'static str = "required";
pub fn mode(mode: impl Into<String>) -> Self {
Self::Mode(mode.into())
}
pub fn function(name: impl Into<String>) -> Self {
Self::Typed(serde_json::json!({
"type": "function",
"name": name.into(),
}))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn function_tool_serializes() {
let tool = Tool::function(
"get_weather",
"Get current weather",
serde_json::json!({"type": "object"}),
);
assert_eq!(
serde_json::to_value(&tool).unwrap(),
serde_json::json!({
"type": "function",
"name": "get_weather",
"description": "Get current weather",
"parameters": {"type": "object"}
})
);
}
#[test]
fn unknown_tool_preserved() {
let json = serde_json::json!({"type": "mcp", "server_label": "s", "server_url": "u"});
let tool: Tool = serde_json::from_value(json.clone()).unwrap();
assert!(matches!(tool, Tool::Other(_)));
assert_eq!(serde_json::to_value(&tool).unwrap(), json);
}
#[test]
fn tool_choice_mode_serializes_as_plain_string() {
assert_eq!(
serde_json::to_value(ToolChoice::mode("auto")).unwrap(),
"auto"
);
}
}