use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolDefinition {
pub name: String,
pub description: String,
pub parameters: Value,
#[serde(skip_serializing_if = "Option::is_none")]
pub strict: Option<bool>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(from = "ToolCallWire")]
pub struct ToolCall {
pub id: String,
pub function_name: String,
pub arguments: Value,
}
impl Serialize for ToolCall {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let wire = ToolCallWire {
id: self.id.clone(),
tool_type: "function".to_string(),
function: ToolCallFunctionWire {
name: self.function_name.clone(),
arguments: serde_json::to_string(&self.arguments).unwrap_or_default(),
},
};
wire.serialize(serializer)
}
}
#[derive(Serialize, Deserialize)]
struct ToolCallWire {
id: String,
#[serde(rename = "type")]
tool_type: String,
function: ToolCallFunctionWire,
}
#[derive(Serialize, Deserialize)]
struct ToolCallFunctionWire {
name: String,
arguments: String,
}
impl From<ToolCallWire> for ToolCall {
fn from(wire: ToolCallWire) -> Self {
ToolCall {
id: wire.id,
function_name: wire.function.name,
arguments: serde_json::from_str(&wire.function.arguments).unwrap_or(Value::Null),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolResult {
pub tool_call_id: String,
pub content: String,
#[serde(default)]
pub is_error: bool,
}
#[cfg(test)]
mod tests {
use super::*;
fn test_tool_definition() -> ToolDefinition {
ToolDefinition {
name: "search".to_string(),
description: "Search the web".to_string(),
parameters: serde_json::json!({
"type": "object",
"properties": {
"query": { "type": "string" }
},
"required": ["query"]
}),
strict: None,
}
}
#[test]
fn test_tool_definition_with_strict_true() {
let mut td = test_tool_definition();
td.strict = Some(true);
let json = serde_json::to_string(&td).unwrap();
assert!(
json.contains(r#""strict":true"#),
"Expected 'strict': true in JSON output, got: {json}"
);
let deserialized: ToolDefinition = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.strict, Some(true));
assert_eq!(deserialized.name, "search");
assert_eq!(deserialized.description, "Search the web");
}
#[test]
fn test_tool_definition_without_strict() {
let td = test_tool_definition();
assert!(td.strict.is_none());
let json = serde_json::to_string(&td).unwrap();
assert!(!json.contains("strict"), "Expected no 'strict' key in JSON output, got: {json}");
let deserialized: ToolDefinition = serde_json::from_str(&json).unwrap();
assert!(deserialized.strict.is_none());
assert_eq!(deserialized.name, "search");
assert_eq!(deserialized.description, "Search the web");
}
#[test]
fn test_tool_definition_round_trip() {
let td = test_tool_definition();
let json = serde_json::to_string(&td).unwrap();
let deserialized: ToolDefinition = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.name, td.name);
assert_eq!(deserialized.description, td.description);
assert_eq!(deserialized.parameters, td.parameters);
assert_eq!(deserialized.strict, td.strict);
}
}