use std::sync::Arc;
use serde_json::Value;
#[derive(Clone)]
pub struct ToolSpec {
pub name: String,
pub description: String,
pub parameters: Value,
pub handler: Arc<dyn Fn(Value) -> Result<String, String> + Send + Sync>,
}
impl ToolSpec {
pub fn new(
name: impl Into<String>,
description: impl Into<String>,
parameters: Value,
handler: Arc<dyn Fn(Value) -> Result<String, String> + Send + Sync>,
) -> Self {
Self {
name: name.into(),
description: description.into(),
parameters,
handler,
}
}
pub fn call(&self, args: Value) -> Result<String, String> {
(self.handler)(args)
}
pub fn to_openai_tool(&self) -> Value {
serde_json::json!({
"type": "function",
"function": {
"name": self.name,
"description": self.description,
"parameters": self.parameters,
}
})
}
}
pub fn param_string(description: impl Into<String>) -> Value {
serde_json::json!({
"type": "string",
"description": description.into(),
})
}
pub fn param_integer(description: impl Into<String>) -> Value {
serde_json::json!({
"type": "integer",
"description": description.into(),
})
}
#[allow(dead_code)]
pub fn param_number(description: impl Into<String>) -> Value {
serde_json::json!({
"type": "number",
"description": description.into(),
})
}
#[allow(dead_code)]
pub fn param_boolean(description: impl Into<String>) -> Value {
serde_json::json!({
"type": "boolean",
"description": description.into(),
})
}
pub fn required_params(params: &[(&str, Value)]) -> (Value, Vec<String>) {
let mut properties = serde_json::Map::new();
let mut required = Vec::new();
for (name, schema) in params {
let is_required = schema.get("default").is_none();
properties.insert(name.to_string(), schema.clone());
if is_required {
required.push(name.to_string());
}
}
let mut obj = serde_json::json!({
"type": "object",
"properties": properties,
});
if !required.is_empty() {
obj["required"] = Value::Array(required.iter().map(|s| Value::String(s.clone())).collect());
}
(obj, required)
}