use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Tool {
pub id: String,
pub name: String,
pub description: String,
pub category: CategoryPath,
pub parameters: Value,
pub returns: ReturnType,
}
impl Tool {
pub fn new(
id: impl Into<String>,
name: impl Into<String>,
description: impl Into<String>,
category: CategoryPath,
parameters: Value,
) -> Self {
Self {
id: id.into(),
name: name.into(),
description: description.into(),
category,
parameters,
returns: ReturnType::default(),
}
}
pub fn with_returns(mut self, returns: ReturnType) -> Self {
self.returns = returns;
self
}
pub fn required_params(&self) -> Vec<String> {
self.parameters
.get("required")
.and_then(|v| v.as_array())
.map(|arr| {
arr.iter()
.filter_map(|v| v.as_str().map(|s| s.to_string()))
.collect()
})
.unwrap_or_default()
}
pub fn param_properties(&self) -> Option<&Value> {
self.parameters.get("properties")
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
pub struct CategoryPath(Vec<String>);
impl CategoryPath {
pub fn new(path: Vec<String>) -> Self {
Self(path)
}
pub fn from_string(path: &str) -> Self {
let parts: Vec<String> = path
.split('/')
.filter(|s| !s.is_empty())
.map(|s| s.to_string())
.collect();
Self(parts)
}
pub fn to_path_string(&self) -> String {
self.0.join("/")
}
pub fn segments(&self) -> &[String] {
&self.0
}
pub fn parent(&self) -> Option<Self> {
if self.0.len() <= 1 {
None
} else {
Some(Self(self.0[..self.0.len() - 1].to_vec()))
}
}
pub fn depth(&self) -> usize {
self.0.len()
}
pub fn is_child_of(&self, other: &CategoryPath) -> bool {
if self.0.len() <= other.0.len() {
return false;
}
self.0.starts_with(&other.0)
}
pub fn contains(&self, other: &CategoryPath) -> bool {
if self.0.len() > other.0.len() {
return false;
}
other.0.starts_with(&self.0)
}
pub fn name(&self) -> Option<&str> {
self.0.last().map(|s| s.as_str())
}
}
pub struct JsonSchema;
impl JsonSchema {
pub fn object() -> ObjectSchemaBuilder {
ObjectSchemaBuilder::new()
}
pub fn string() -> TypeBuilder {
TypeBuilder::new("string")
}
pub fn integer() -> TypeBuilder {
TypeBuilder::new("integer")
}
pub fn number() -> TypeBuilder {
TypeBuilder::new("number")
}
pub fn boolean() -> TypeBuilder {
TypeBuilder::new("boolean")
}
pub fn array(item_schema: Value) -> TypeBuilder {
let mut builder = TypeBuilder::new("array");
builder.schema["items"] = item_schema;
builder
}
pub fn string_array() -> TypeBuilder {
Self::array(json!({"type": "string"}))
}
pub fn enum_values(values: Vec<&str>) -> TypeBuilder {
let mut builder = TypeBuilder::new("string");
builder.schema["enum"] = json!(values);
builder
}
}
use serde_json::json;
pub struct ObjectSchemaBuilder {
schema: Value,
}
impl ObjectSchemaBuilder {
fn new() -> Self {
Self {
schema: json!({
"type": "object",
"properties": {},
"required": []
}),
}
}
pub fn property(mut self, name: &str, builder: TypeBuilder) -> Self {
let is_required = builder.required;
let properties = self.schema["properties"]
.as_object_mut()
.expect("Expected 'properties' to be an object in schema");
properties.insert(name.to_string(), builder.build());
if is_required {
let required = self.schema["required"]
.as_array_mut()
.expect("Expected 'required' to be an array in schema");
required.push(json!(name));
}
self
}
pub fn raw_property(mut self, name: &str, schema: Value, is_required: bool) -> Self {
let properties = self.schema["properties"]
.as_object_mut()
.expect("Expected 'properties' to be an object in schema");
properties.insert(name.to_string(), schema);
if is_required {
let required = self.schema["required"]
.as_array_mut()
.expect("Expected 'required' to be an array in schema");
required.push(json!(name));
}
self
}
pub fn build(self) -> Value {
self.schema
}
}
pub struct TypeBuilder {
schema: Value,
required: bool,
}
impl TypeBuilder {
fn new(type_name: &str) -> Self {
Self {
schema: json!({"type": type_name}),
required: true,
}
}
pub fn description(mut self, desc: &str) -> Self {
self.schema["description"] = json!(desc);
self
}
pub fn optional(mut self) -> Self {
self.required = false;
self
}
pub fn enum_values(mut self, values: Vec<&str>) -> Self {
self.schema["enum"] = json!(values);
self
}
pub fn build(self) -> Value {
self.schema
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReturnType {
pub description: String,
pub return_schema: Value,
}
impl ReturnType {
pub fn new(description: impl Into<String>, return_schema: Value) -> Self {
Self {
description: description.into(),
return_schema,
}
}
}
impl Default for ReturnType {
fn default() -> Self {
Self {
description: "无返回值".to_string(),
return_schema: json!({"type": "null"}),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum MatchType {
Exact,
#[default]
Fuzzy,
}
pub trait ToolProvider: Send + Sync {
fn list_tools(&self) -> Vec<Tool>;
fn search_tools(&self, query: &str, match_type: MatchType) -> Vec<Tool>;
fn list_tools_by_category(&self, category: &str) -> Vec<Tool>;
fn get_category_tree(&self) -> CategoryNodeInfo;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CategoryNodeInfo {
pub name: String,
pub path: String,
pub tool_count: usize,
pub children: Vec<CategoryNodeInfo>,
}
impl CategoryNodeInfo {
pub fn new(name: impl Into<String>, path: impl Into<String>) -> Self {
Self {
name: name.into(),
path: path.into(),
tool_count: 0,
children: Vec::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct ToolCallContext {
pub caller_id: String,
pub timestamp: i64,
pub session_id: Option<String>,
}
impl ToolCallContext {
pub fn new(caller_id: impl Into<String>) -> Self {
Self {
caller_id: caller_id.into(),
timestamp: chrono::Utc::now().timestamp(),
session_id: None,
}
}
pub fn with_session_id(mut self, session_id: impl Into<String>) -> Self {
self.session_id = Some(session_id.into());
self
}
}