use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct InternalTool {
pub name: String,
pub description: String,
pub parameters: Value,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub metadata: HashMap<String, Value>,
}
impl InternalTool {
pub fn new(name: impl Into<String>, description: impl Into<String>, parameters: Value) -> Self {
Self {
name: name.into(),
description: description.into(),
parameters,
metadata: HashMap::new(),
}
}
pub fn with_metadata(mut self, key: impl Into<String>, value: Value) -> Self {
self.metadata.insert(key.into(), value);
self
}
pub fn has_metadata(&self, key: &str) -> bool {
self.metadata.contains_key(key)
}
pub fn get_metadata(&self, key: &str) -> Option<&Value> {
self.metadata.get(key)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct InternalToolCall {
pub id: String,
pub name: String,
pub arguments: Value,
}
impl InternalToolCall {
pub fn new(id: impl Into<String>, name: impl Into<String>, arguments: Value) -> Self {
Self {
id: id.into(),
name: name.into(),
arguments,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct InternalToolResult {
pub tool_call_id: String,
pub content: String,
#[serde(default)]
pub is_error: bool,
}
impl InternalToolResult {
pub fn success(tool_call_id: impl Into<String>, content: impl Into<String>) -> Self {
Self {
tool_call_id: tool_call_id.into(),
content: content.into(),
is_error: false,
}
}
pub fn error(tool_call_id: impl Into<String>, error: impl Into<String>) -> Self {
Self {
tool_call_id: tool_call_id.into(),
content: error.into(),
is_error: true,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_internal_tool_creation() {
let tool = InternalTool::new(
"get_weather",
"Get weather for a location",
json!({
"type": "object",
"properties": {
"location": { "type": "string" }
},
"required": ["location"]
}),
);
assert_eq!(tool.name, "get_weather");
assert_eq!(tool.description, "Get weather for a location");
assert!(tool.metadata.is_empty());
}
#[test]
fn test_internal_tool_with_metadata() {
let tool = InternalTool::new("test", "Test tool", json!({}))
.with_metadata("mcp_annotations", json!({"readOnlyHint": true}));
assert!(tool.has_metadata("mcp_annotations"));
assert_eq!(
tool.get_metadata("mcp_annotations"),
Some(&json!({"readOnlyHint": true}))
);
}
#[test]
fn test_internal_tool_call() {
let call = InternalToolCall::new("call_123", "get_weather", json!({"location": "SF"}));
assert_eq!(call.id, "call_123");
assert_eq!(call.name, "get_weather");
assert_eq!(call.arguments["location"], "SF");
}
#[test]
fn test_internal_tool_result() {
let success = InternalToolResult::success("call_123", "72°F, sunny");
assert!(!success.is_error);
assert_eq!(success.content, "72°F, sunny");
let error = InternalToolResult::error("call_456", "Location not found");
assert!(error.is_error);
assert_eq!(error.content, "Location not found");
}
#[test]
fn test_internal_tool_serialization() {
let tool = InternalTool::new("test", "Test", json!({"type": "object"}))
.with_metadata("source", json!("mcp"));
let json = serde_json::to_string(&tool).unwrap();
let deserialized: InternalTool = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, tool);
}
}