use serde::{Deserialize, Serialize};
use serde_json::Value;
pub use crate::types::ToolDefinition;
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ToolChoice {
#[default]
Auto,
Tool { name: String },
Any,
None,
}
impl ToolChoice {
pub fn tool(name: impl Into<String>) -> Self {
ToolChoice::Tool { name: name.into() }
}
}
#[derive(Debug, Clone)]
pub struct ToolBuilder {
name: String,
description: String,
properties: serde_json::Map<String, Value>,
required: Vec<String>,
}
impl ToolBuilder {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
description: String::new(),
properties: serde_json::Map::new(),
required: Vec::new(),
}
}
pub fn description(mut self, desc: impl Into<String>) -> Self {
self.description = desc.into();
self
}
pub fn string_param(
mut self,
name: impl Into<String>,
description: impl Into<String>,
required: bool,
) -> Self {
let name = name.into();
self.properties.insert(
name.clone(),
serde_json::json!({
"type": "string",
"description": description.into()
}),
);
if required {
self.required.push(name);
}
self
}
pub fn integer_param(
mut self,
name: impl Into<String>,
description: impl Into<String>,
required: bool,
) -> Self {
let name = name.into();
self.properties.insert(
name.clone(),
serde_json::json!({
"type": "integer",
"description": description.into()
}),
);
if required {
self.required.push(name);
}
self
}
pub fn number_param(
mut self,
name: impl Into<String>,
description: impl Into<String>,
required: bool,
) -> Self {
let name = name.into();
self.properties.insert(
name.clone(),
serde_json::json!({
"type": "number",
"description": description.into()
}),
);
if required {
self.required.push(name);
}
self
}
pub fn boolean_param(
mut self,
name: impl Into<String>,
description: impl Into<String>,
required: bool,
) -> Self {
let name = name.into();
self.properties.insert(
name.clone(),
serde_json::json!({
"type": "boolean",
"description": description.into()
}),
);
if required {
self.required.push(name);
}
self
}
pub fn array_param(
mut self,
name: impl Into<String>,
description: impl Into<String>,
item_type: &str,
required: bool,
) -> Self {
let name = name.into();
self.properties.insert(
name.clone(),
serde_json::json!({
"type": "array",
"description": description.into(),
"items": { "type": item_type }
}),
);
if required {
self.required.push(name);
}
self
}
pub fn enum_param(
mut self,
name: impl Into<String>,
description: impl Into<String>,
values: &[&str],
required: bool,
) -> Self {
let name = name.into();
self.properties.insert(
name.clone(),
serde_json::json!({
"type": "string",
"description": description.into(),
"enum": values
}),
);
if required {
self.required.push(name);
}
self
}
pub fn custom_param(mut self, name: impl Into<String>, schema: Value, required: bool) -> Self {
let name = name.into();
self.properties.insert(name.clone(), schema);
if required {
self.required.push(name);
}
self
}
pub fn build(self) -> ToolDefinition {
let input_schema = serde_json::json!({
"type": "object",
"properties": self.properties,
"required": self.required
});
ToolDefinition {
name: self.name,
description: self.description,
input_schema,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tool_builder() {
let tool = ToolBuilder::new("bash")
.description("Execute a shell command")
.string_param("command", "The command to execute", true)
.integer_param("timeout", "Timeout in seconds", false)
.build();
assert_eq!(tool.name, "bash");
assert_eq!(tool.description, "Execute a shell command");
let schema = &tool.input_schema;
assert_eq!(schema["type"], "object");
assert!(schema["properties"]["command"]["type"] == "string");
assert!(schema["required"]
.as_array()
.unwrap()
.contains(&serde_json::json!("command")));
}
#[test]
fn test_enum_param() {
let tool = ToolBuilder::new("select")
.description("Select an option")
.enum_param("option", "The option to select", &["a", "b", "c"], true)
.build();
let schema = &tool.input_schema;
let option_schema = &schema["properties"]["option"];
assert_eq!(option_schema["type"], "string");
assert_eq!(option_schema["enum"], serde_json::json!(["a", "b", "c"]));
}
}