use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ToolKind {
Read,
Edit,
Execute,
Search,
Fetch,
Think,
SwitchMode,
#[default]
Other,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolCallLocation {
pub path: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub line: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub column: Option<u32>,
}
impl ToolCallLocation {
pub fn new(path: impl Into<String>) -> Self {
Self {
path: path.into(),
line: None,
column: None,
}
}
pub fn with_line(path: impl Into<String>, line: u32) -> Self {
Self {
path: path.into(),
line: Some(line),
column: None,
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ToolInfo {
pub title: String,
pub kind: ToolKind,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub content: Vec<ToolInfoContent>,
#[serde(skip_serializing_if = "Option::is_none")]
pub locations: Option<Vec<ToolCallLocation>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ToolInfoContent {
Text { text: String },
Diff { diff: String },
Terminal { output: String },
}
impl ToolInfo {
pub fn new(title: impl Into<String>, kind: ToolKind) -> Self {
Self {
title: title.into(),
kind,
content: Vec::new(),
locations: None,
}
}
pub fn with_location(mut self, path: impl Into<String>) -> Self {
self.locations
.get_or_insert_with(Vec::new)
.push(ToolCallLocation::new(path));
self
}
pub fn with_text(mut self, text: impl Into<String>) -> Self {
self.content
.push(ToolInfoContent::Text { text: text.into() });
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ToolUseType {
#[default]
ToolUse,
ServerToolUse,
McpToolUse,
}
#[derive(Debug, Clone)]
pub struct ToolUseEntry {
pub tool_type: ToolUseType,
pub id: String,
pub name: String,
pub input: serde_json::Value,
}
impl ToolUseEntry {
pub fn new(id: String, name: String, input: serde_json::Value) -> Self {
Self {
tool_type: ToolUseType::ToolUse,
id,
name,
input,
}
}
pub fn with_type(mut self, tool_type: ToolUseType) -> Self {
self.tool_type = tool_type;
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_tool_kind_serialization() {
assert_eq!(serde_json::to_string(&ToolKind::Read).unwrap(), "\"read\"");
assert_eq!(
serde_json::to_string(&ToolKind::Execute).unwrap(),
"\"execute\""
);
}
#[test]
fn test_tool_info_builder() {
let info = ToolInfo::new("Read file.txt", ToolKind::Read)
.with_location("/path/to/file.txt")
.with_text("File content preview...");
assert_eq!(info.title, "Read file.txt");
assert_eq!(info.kind, ToolKind::Read);
assert_eq!(info.locations.as_ref().unwrap().len(), 1);
assert_eq!(info.content.len(), 1);
}
#[test]
fn test_tool_use_entry() {
let entry = ToolUseEntry::new(
"tool_123".to_string(),
"Read".to_string(),
json!({"file_path": "/test.txt"}),
);
assert_eq!(entry.id, "tool_123");
assert_eq!(entry.name, "Read");
assert_eq!(entry.tool_type, ToolUseType::ToolUse);
}
#[test]
fn test_tool_call_location() {
let loc = ToolCallLocation::with_line("/path/to/file.rs", 42);
assert_eq!(loc.path, "/path/to/file.rs");
assert_eq!(loc.line, Some(42));
assert!(loc.column.is_none());
}
}