use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct BashParams {
pub command: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub timeout_secs: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct ReadParams {
pub file_path: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub offset: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<usize>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct WriteParams {
pub file_path: String,
pub content: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct EditParams {
pub file_path: String,
pub old_string: String,
pub new_string: String,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub replace_all: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct GrepParams {
pub pattern: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub glob: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct GlobParams {
pub pattern: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub path: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct WebFetchParams {
pub url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub prompt: Option<String>,
}
pub fn schema_for<T: JsonSchema>() -> serde_json::Value {
let schema = schemars::schema_for!(T);
serde_json::to_value(schema).unwrap_or_default()
}
pub fn tool_schema_for<T: JsonSchema>() -> serde_json::Value {
let schema = schemars::schema_for!(T);
let mut value = serde_json::to_value(schema).unwrap_or_default();
if let Some(obj) = value.as_object_mut() {
obj.remove("$schema");
obj.remove("title");
}
value
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bash_params_schema_has_required_command() {
let schema = tool_schema_for::<BashParams>();
let required = schema.get("required").and_then(|r| r.as_array());
assert!(required.is_some());
let required = required.unwrap();
assert!(required.iter().any(|v| v.as_str() == Some("command")));
}
#[test]
fn read_params_schema_has_file_path() {
let schema = tool_schema_for::<ReadParams>();
let props = schema.get("properties");
assert!(props.is_some());
let props = props.unwrap();
assert!(props.get("file_path").is_some());
}
#[test]
fn write_params_schema_has_required_fields() {
let schema = tool_schema_for::<WriteParams>();
let required = schema.get("required").and_then(|r| r.as_array());
assert!(required.is_some());
let required = required.unwrap();
assert!(required.iter().any(|v| v.as_str() == Some("file_path")));
assert!(required.iter().any(|v| v.as_str() == Some("content")));
}
#[test]
fn edit_params_schema_has_all_fields() {
let schema = tool_schema_for::<EditParams>();
let props = schema.get("properties");
assert!(props.is_some());
let props = props.unwrap();
assert!(props.get("file_path").is_some());
assert!(props.get("old_string").is_some());
assert!(props.get("new_string").is_some());
assert!(props.get("replace_all").is_some());
}
#[test]
fn bash_params_deserialize() {
let json = r#"{"command": "echo hello"}"#;
let params: BashParams = serde_json::from_str(json).unwrap();
assert_eq!(params.command, "echo hello");
assert!(params.timeout_secs.is_none());
}
#[test]
fn bash_params_with_timeout() {
let json = r#"{"command": "sleep 10", "timeout_secs": 30}"#;
let params: BashParams = serde_json::from_str(json).unwrap();
assert_eq!(params.timeout_secs, Some(30));
}
#[test]
fn read_params_with_offset_and_limit() {
let json = r#"{"file_path": "/tmp/test.txt", "offset": 10, "limit": 50}"#;
let params: ReadParams = serde_json::from_str(json).unwrap();
assert_eq!(params.file_path, "/tmp/test.txt");
assert_eq!(params.offset, Some(10));
assert_eq!(params.limit, Some(50));
}
#[test]
fn edit_params_replace_all_default_false() {
let json = r#"{"file_path": "/tmp/test.txt", "old_string": "foo", "new_string": "bar"}"#;
let params: EditParams = serde_json::from_str(json).unwrap();
assert!(!params.replace_all);
}
#[test]
fn edit_params_replace_all_true() {
let json = r#"{"file_path": "/tmp/test.txt", "old_string": "foo", "new_string": "bar", "replace_all": true}"#;
let params: EditParams = serde_json::from_str(json).unwrap();
assert!(params.replace_all);
}
#[test]
fn schema_is_valid_json() {
let bash = tool_schema_for::<BashParams>();
let read = tool_schema_for::<ReadParams>();
let write = tool_schema_for::<WriteParams>();
let edit = tool_schema_for::<EditParams>();
assert!(bash.get("properties").is_some());
assert!(read.get("properties").is_some());
assert!(write.get("properties").is_some());
assert!(edit.get("properties").is_some());
assert!(bash.get("$schema").is_none());
assert!(read.get("$schema").is_none());
}
}