pub mod permission;
pub mod skill;
use crate::error::Result;
use futures::future::BoxFuture;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(tag = "kind_type", rename_all = "snake_case")]
pub enum ToolResultKind {
Text,
Json,
Image { mime_type: String },
Table {
columns: Vec<String>,
rows: Vec<Vec<String>>,
},
Diff {
unified_diff: String,
},
FileReference {
path: String,
},
CommandOutput {
exit_code: Option<i32>,
},
StructuredError {
error_code: String,
},
}
impl Default for ToolResultKind {
fn default() -> Self {
Self::Text
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolResult {
#[serde(default)]
pub kind: ToolResultKind,
pub success: bool,
pub output: String,
pub error: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub bytes: Option<Vec<u8>>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub data: Option<serde_json::Value>,
#[serde(default)]
pub truncated: bool,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub mime_type: Option<String>,
#[serde(default)]
pub metadata: HashMap<String, String>,
}
impl ToolResult {
pub fn success(output: impl Into<String>) -> Self {
Self {
kind: ToolResultKind::Text,
success: true,
output: output.into(),
error: None,
bytes: None,
data: None,
truncated: false,
mime_type: None,
metadata: HashMap::new(),
}
}
pub fn success_json(data: serde_json::Value) -> Self {
let output = serde_json::to_string(&data).unwrap_or_default();
Self {
kind: ToolResultKind::Json,
success: true,
output,
error: None,
bytes: None,
data: Some(data),
truncated: false,
mime_type: None,
metadata: HashMap::new(),
}
}
pub fn success_with_kind(kind: ToolResultKind, output: impl Into<String>) -> Self {
Self {
kind,
success: true,
output: output.into(),
error: None,
bytes: None,
data: None,
truncated: false,
mime_type: None,
metadata: HashMap::new(),
}
}
pub fn error(error: impl Into<String>) -> Self {
Self {
kind: ToolResultKind::StructuredError { error_code: "tool_error".into() },
success: false,
output: String::new(),
error: Some(error.into()),
bytes: None,
data: None,
truncated: false,
mime_type: None,
metadata: HashMap::new(),
}
}
pub fn binary(bytes: Vec<u8>) -> Self {
Self {
kind: ToolResultKind::Text,
success: true,
output: String::new(),
error: None,
bytes: Some(bytes),
data: None,
truncated: false,
mime_type: None,
metadata: HashMap::new(),
}
}
pub fn with_bytes(mut self, bytes: Vec<u8>) -> Self {
self.bytes = Some(bytes);
self
}
pub fn with_output(mut self, output: impl Into<String>) -> Self {
self.output = output.into();
self
}
pub fn with_error(mut self, error: impl Into<String>) -> Self {
self.success = false;
self.error = Some(error.into());
self
}
pub fn with_data(mut self, data: serde_json::Value) -> Self {
self.data = Some(data);
self
}
pub fn with_truncated(mut self, truncated: bool) -> Self {
self.truncated = truncated;
self
}
pub fn with_mime_type(mut self, mime_type: impl Into<String>) -> Self {
self.mime_type = Some(mime_type.into());
self
}
pub fn with_meta(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.metadata.insert(key.into(), value.into());
self
}
pub fn with_metadata(mut self, meta: HashMap<String, String>) -> Self {
self.metadata = meta;
self
}
}
#[derive(Debug, Clone)]
pub struct ToolExecutionConfig {
pub timeout_ms: u64,
pub retry_on_fail: bool,
pub max_retries: u32,
pub retry_delay_ms: u64,
pub max_concurrency: Option<usize>,
pub max_read_concurrency: Option<usize>,
}
impl Default for ToolExecutionConfig {
fn default() -> Self {
Self {
timeout_ms: 30_000,
retry_on_fail: false,
max_retries: 2,
retry_delay_ms: 200,
max_concurrency: None,
max_read_concurrency: Some(32),
}
}
}
pub type ToolParameters = HashMap<String, serde_json::Value>;
#[derive(Debug, Clone, PartialEq)]
pub enum ParamValue {
String(String),
Number(f64),
Bool(bool),
Array(Vec<ParamValue>),
Object(HashMap<String, ParamValue>),
Null,
}
impl ParamValue {
pub fn from_json(v: &serde_json::Value) -> Self {
match v {
serde_json::Value::String(s) => Self::String(s.clone()),
serde_json::Value::Number(n) => {
Self::Number(n.as_f64().unwrap_or(0.0))
}
serde_json::Value::Bool(b) => Self::Bool(*b),
serde_json::Value::Array(arr) => {
Self::Array(arr.iter().map(|v| Self::from_json(v)).collect())
}
serde_json::Value::Object(obj) => {
Self::Object(obj.iter().map(|(k, v)| (k.clone(), Self::from_json(v))).collect())
}
serde_json::Value::Null => Self::Null,
}
}
pub fn as_str(&self) -> Option<&str> {
match self {
Self::String(s) => Some(s.as_str()),
_ => None,
}
}
pub fn as_f64(&self) -> Option<f64> {
match self {
Self::Number(n) => Some(*n),
_ => None,
}
}
pub fn as_bool(&self) -> Option<bool> {
match self {
Self::Bool(b) => Some(*b),
_ => None,
}
}
pub fn type_name(&self) -> &str {
match self {
Self::String(_) => "string",
Self::Number(_) => "number",
Self::Bool(_) => "bool",
Self::Array(_) => "array",
Self::Object(_) => "object",
Self::Null => "null",
}
}
}
#[derive(Debug, Clone)]
pub struct ToolCallParams {
pub raw: serde_json::Value,
parsed: HashMap<String, ParamValue>,
}
impl ToolCallParams {
pub fn from_value(value: &serde_json::Value) -> Self {
let parsed = if let serde_json::Value::Object(map) = value {
map.iter().map(|(k, v)| (k.clone(), ParamValue::from_json(v))).collect()
} else {
HashMap::new()
};
Self {
raw: value.clone(),
parsed,
}
}
pub fn from_params(params: &ToolParameters) -> Self {
let mut map = serde_json::Map::new();
for (k, v) in params {
map.insert(k.clone(), v.clone());
}
Self::from_value(&serde_json::Value::Object(map))
}
pub fn get_str(&self, key: &str) -> Option<&str> {
self.parsed.get(key).and_then(|v| v.as_str())
}
pub fn get_number(&self, key: &str) -> Option<f64> {
self.parsed.get(key).and_then(|v| v.as_f64())
}
pub fn get_bool(&self, key: &str) -> Option<bool> {
self.parsed.get(key).and_then(|v| v.as_bool())
}
pub fn get(&self, key: &str) -> Option<&ParamValue> {
self.parsed.get(key)
}
pub fn validate_required(&self, key: &str, expected_type: &str) -> std::result::Result<(), String> {
match self.parsed.get(key) {
None => Err(format!("Missing required parameter: {key}")),
Some(v) if v.type_name() != expected_type => {
Err(format!(
"Parameter '{key}': expected {expected_type}, got {}",
v.type_name()
))
}
_ => Ok(()),
}
}
pub fn has(&self, key: &str) -> bool {
self.parsed.contains_key(key)
}
pub fn len(&self) -> usize {
self.parsed.len()
}
pub fn is_empty(&self) -> bool {
self.parsed.is_empty()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
pub enum ToolRiskLevel {
ReadOnly,
#[default]
Standard,
Dangerous,
}
pub trait ToolRegistrar {
fn register(&mut self, tool: Box<dyn Tool>);
}
pub trait ToolRunner<P = ToolParameters>: Tool + Sized {
fn run(&self, params: P) -> impl std::future::Future<Output = Result<ToolResult>> + Send;
}
pub trait Tool: Send + Sync {
fn name(&self) -> &str;
fn description(&self) -> &str;
fn parameters(&self) -> serde_json::Value;
fn execute<'a>(&'a self, parameters: ToolParameters) -> BoxFuture<'a, Result<ToolResult>>;
fn validate_parameters<'a>(&'a self, _params: &'a ToolParameters) -> BoxFuture<'a, Result<()>> {
Box::pin(async { Ok(()) })
}
fn permissions(&self) -> Vec<permission::ToolPermission> {
vec![]
}
fn risk_level(&self) -> ToolRiskLevel {
ToolRiskLevel::Standard
}
fn capability_description(&self) -> &str {
match self.risk_level() {
ToolRiskLevel::ReadOnly => "Read-only: no side effects",
ToolRiskLevel::Standard => "Standard: limited side effects",
ToolRiskLevel::Dangerous => "Dangerous: irreversible side effects",
}
}
}