use async_trait::async_trait;
use schemars::JsonSchema;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::ctx::ToolCtx;
use crate::error::ToolError;
pub trait ToolOutput {
fn to_prompt_text(&self) -> String;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ToolKind {
Shell,
Read,
Write,
Edit,
Glob,
Grep,
#[serde(other)]
Other,
}
impl ToolKind {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
ToolKind::Shell => "shell",
ToolKind::Read => "read",
ToolKind::Write => "write",
ToolKind::Edit => "edit",
ToolKind::Glob => "glob",
ToolKind::Grep => "grep",
ToolKind::Other => "other",
}
}
}
#[async_trait]
pub trait Tool: Send + Sync {
type Args: DeserializeOwned + JsonSchema + Send;
type Output: Serialize + ToolOutput + Send;
fn kind(&self) -> ToolKind;
fn description(&self) -> &str;
#[must_use]
fn parameters_schema(&self) -> Value {
let settings = schemars::generate::SchemaSettings::draft2020_12().with(|s| {
s.inline_subschemas = true;
});
let schema = settings
.into_generator()
.into_root_schema_for::<Self::Args>();
serde_json::to_value(schema).unwrap_or_else(|_| Value::Object(serde_json::Map::new()))
}
#[must_use]
fn input_format(&self) -> locode_protocol::ToolInputFormat {
locode_protocol::ToolInputFormat::JsonSchema {
parameters: self.parameters_schema(),
}
}
async fn run(&self, ctx: &ToolCtx, args: Self::Args) -> Result<Self::Output, ToolError>;
}