use std::string::String;
use std::vec::Vec;
use std::{fmt, time::Duration};
use crate::capability::Capability;
use crate::hash::Hash;
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize)]
pub struct ToolId {
pub name: String,
pub version: String,
}
impl ToolId {
#[must_use]
pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
Self {
name: name.into(),
version: version.into(),
}
}
#[must_use]
pub fn as_str(&self) -> String {
format!("{}@{}", self.name, self.version)
}
}
impl fmt::Display for ToolId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}@{}", self.name, self.version)
}
}
impl Default for ToolId {
fn default() -> Self {
Self {
name: String::new(),
version: String::new(),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum SideEffect {
Pure,
Impure,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum Determinism {
Deterministic,
BoundedNonDeterminism,
NonDeterministic,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct ResourceBounds {
pub timeout_ms: u64,
pub max_memory_bytes: Option<u64>,
pub max_fuel: Option<u64>,
}
impl ResourceBounds {
#[must_use]
pub fn default() -> Self {
Self {
timeout_ms: 30_000, max_memory_bytes: None,
max_fuel: None,
}
}
#[must_use]
pub const fn with_timeout(timeout_ms: u64) -> Self {
Self {
timeout_ms,
max_memory_bytes: None,
max_fuel: None,
}
}
#[must_use]
pub fn timeout(&self) -> Duration {
Duration::from_millis(self.timeout_ms)
}
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct ToolRequestMetadata {
pub tool_id: ToolId,
pub timestamp: u64,
pub request_hash: Hash,
pub required_capabilities: Vec<Capability>,
}
impl ToolRequestMetadata {
#[must_use]
pub fn new(
tool_id: ToolId,
timestamp: u64,
request_hash: Hash,
required_capabilities: Vec<Capability>,
) -> Self {
Self {
tool_id,
timestamp,
request_hash,
required_capabilities,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct ToolResponse<T> {
pub data: T,
pub response_hash: Hash,
pub metadata: ToolResponseMetadata,
}
impl<T: serde::Serialize> ToolResponse<T> {
pub fn new(data: T) -> Self {
let response_hash = Hash::from_canonical(&data);
Self {
data,
response_hash,
metadata: ToolResponseMetadata::default(),
}
}
pub fn with_metadata(data: T, metadata: ToolResponseMetadata) -> Self {
let response_hash = Hash::from_canonical(&data);
Self {
data,
response_hash,
metadata,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct ToolResponseMetadata {
pub source: ResponseSource,
pub normalized: bool,
pub duration_ms: u64,
pub extra: Vec<(String, String)>,
}
impl ToolResponseMetadata {
#[must_use]
pub fn default() -> Self {
Self {
source: ResponseSource::Tool,
normalized: true,
duration_ms: 0,
extra: Vec::new(),
}
}
#[must_use]
pub fn cached(duration_ms: u64) -> Self {
Self {
source: ResponseSource::Cache,
normalized: true,
duration_ms,
extra: Vec::new(),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum ResponseSource {
Tool,
Cache,
Mock,
Error,
}
pub trait Tool: Send + Sync {
fn id(&self) -> &ToolId;
fn required_capabilities(&self) -> &[Capability];
fn side_effects(&self) -> SideEffect;
fn determinism(&self) -> Determinism;
fn resource_bounds(&self) -> &ResourceBounds;
fn execute(&self, input: &[u8], context: &ExecutionContext) -> ToolResult<Vec<u8>>;
fn input_schema(&self) -> &str;
fn output_schema(&self) -> &str;
}
pub type ToolResult<T> = core::result::Result<T, ToolError>;
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum ToolError {
NotFound(String),
Denied { capability: String, reason: String },
Timeout { tool: String, duration_ms: u64 },
ExecutionFailed { tool: String, reason: String },
InvalidInput { tool: String, reason: String },
SerializationFailed { tool: String, reason: String },
ResourceExceeded { tool: String, limit: String },
Other(String),
}
impl fmt::Display for ToolError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ToolError::NotFound(name) => write!(f, "Tool not found: {}", name),
ToolError::Denied { capability, reason } => {
write!(f, "Capability denied '{}' - {}", capability, reason)
}
ToolError::Timeout { tool, duration_ms } => {
write!(f, "Tool {} timed out after {}ms", tool, duration_ms)
}
ToolError::ExecutionFailed { tool, reason } => {
write!(f, "Tool {} execution failed: {}", tool, reason)
}
ToolError::InvalidInput { tool, reason } => {
write!(f, "Tool {} invalid input: {}", tool, reason)
}
ToolError::SerializationFailed { tool, reason } => {
write!(f, "Tool {} serialization failed: {}", tool, reason)
}
ToolError::ResourceExceeded { tool, limit } => {
write!(f, "Tool {} exceeded resource limit: {}", tool, limit)
}
ToolError::Other(msg) => write!(f, "Tool error: {}", msg),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct ExecutionContext {
pub logical_time: u64,
pub run_id: u64,
pub random_seed: Option<u64>,
}
impl ExecutionContext {
#[must_use]
pub fn new(logical_time: u64, run_id: u64) -> Self {
Self {
logical_time,
run_id,
random_seed: None,
}
}
#[must_use]
pub fn with_seed(logical_time: u64, run_id: u64, random_seed: u64) -> Self {
Self {
logical_time,
run_id,
random_seed: Some(random_seed),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tool_id() {
let id = ToolId::new("test_tool", "1.0.0");
assert_eq!(id.name, "test_tool");
assert_eq!(id.version, "1.0.0");
assert_eq!(id.as_str(), "test_tool@1.0.0");
}
#[test]
fn test_resource_bounds() {
let bounds = ResourceBounds::with_timeout(5000);
assert_eq!(bounds.timeout_ms, 5000);
assert_eq!(bounds.timeout(), Duration::from_millis(5000));
}
#[test]
fn test_execution_context() {
let ctx = ExecutionContext::with_seed(100, 42, 12345);
assert_eq!(ctx.logical_time, 100);
assert_eq!(ctx.run_id, 42);
assert_eq!(ctx.random_seed, Some(12345));
}
}