use crate::types::StructuredContent;
use crate::Result;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt::Debug;
use std::sync::Arc;
use tokio::sync::RwLock;
use uuid::Uuid;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CapabilityMetadata {
pub success: bool,
pub duration_ms: u64,
pub side_effects: Vec<String>,
pub timestamp: crate::types::Timestamp,
}
impl CapabilityMetadata {
pub fn success() -> Self {
Self {
success: true,
duration_ms: 0,
side_effects: Vec::new(),
timestamp: crate::types::Timestamp::now(),
}
}
pub fn failure(error: String) -> Self {
Self {
success: false,
duration_ms: 0,
side_effects: vec![error],
timestamp: crate::types::Timestamp::now(),
}
}
pub fn with_side_effect(mut self, effect: String) -> Self {
self.side_effects.push(effect);
self
}
pub fn with_duration(mut self, ms: u64) -> Self {
self.duration_ms = ms;
self
}
}
impl Default for CapabilityMetadata {
fn default() -> Self {
Self::success()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CapabilityResult<D = StructuredContent> {
pub data: D,
pub metadata: CapabilityMetadata,
}
impl<D> CapabilityResult<D> {
pub fn new(data: D, metadata: CapabilityMetadata) -> Self {
Self { data, metadata }
}
pub fn success(data: D) -> Self {
Self::new(data, CapabilityMetadata::success())
}
pub fn failure(data: D, error: String) -> Self {
Self::new(data, CapabilityMetadata::failure(error))
}
pub fn is_success(&self) -> bool {
self.metadata.success
}
pub fn data(&self) -> &D {
&self.data
}
pub fn unwrap(self) -> D
where
D: Debug,
{
if !self.is_success() {
panic!("Called unwrap on failed capability result");
}
self.data
}
}
#[async_trait]
pub trait Capability: Send + Sync {
fn name(&self) -> &str;
fn describe(&self) -> CapabilityContract;
fn can_invoke(&self, state: &StructuredContent) -> bool {
let contract = self.describe();
contract.preconditions.iter().all(|pre| self.check_precondition(pre, state))
}
fn check_precondition(&self, precondition: &str, state: &StructuredContent) -> bool;
async fn invoke(&self, input: StructuredContent) -> Result<CapabilityResult>;
fn simulate(&self, input: &StructuredContent) -> Result<CapabilityResult> {
Ok(CapabilityResult::success(StructuredContent::text("Simulation completed")))
}
async fn reflect(&self, traces: &[CapabilityTrace]) -> Result<StructuredContent> {
Ok(StructuredContent::json(serde_json::json!({
"reflection": "Default reflection",
"traces_analyzed": traces.len()
})))
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
pub struct CapabilityContract {
pub preconditions: Vec<String>,
pub effects: Vec<String>,
pub failure_modes: Vec<String>,
pub permissions: Vec<String>,
pub constraints: HashMap<String, String>,
}
impl CapabilityContract {
pub fn new() -> Self {
Self::default()
}
pub fn with_precondition(mut self, precondition: impl Into<String>) -> Self {
self.preconditions.push(precondition.into());
self
}
pub fn with_effect(mut self, effect: impl Into<String>) -> Self {
self.effects.push(effect.into());
self
}
pub fn with_failure_mode(mut self, failure: impl Into<String>) -> Self {
self.failure_modes.push(failure.into());
self
}
pub fn build(self) -> Self {
self
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CapabilityTrace {
pub id: Uuid,
pub capability_name: String,
pub input: StructuredContent,
pub output: StructuredContent,
pub metadata: CapabilityMetadata,
pub success: bool,
pub error: Option<String>,
}
impl CapabilityTrace {
pub fn new(capability_name: impl Into<String>, input: StructuredContent, output: StructuredContent, metadata: CapabilityMetadata, success: bool) -> Self {
Self {
id: Uuid::new_v4(),
capability_name: capability_name.into(),
input,
output,
metadata,
success,
error: None,
}
}
pub fn failed(capability_name: impl Into<String>, input: StructuredContent, error: String) -> Self {
Self {
id: Uuid::new_v4(),
capability_name: capability_name.into(),
input,
output: StructuredContent::json(serde_json::json!({"error": &error})),
metadata: CapabilityMetadata::failure(error.clone()),
success: false,
error: Some(error),
}
}
}
pub struct CapabilityRegistry {
capabilities: Arc<RwLock<HashMap<String, Arc<dyn Capability>>>>,
traces: Arc<RwLock<Vec<CapabilityTrace>>>,
}
impl CapabilityRegistry {
pub fn new() -> Self {
Self {
capabilities: Arc::new(RwLock::new(HashMap::new())),
traces: Arc::new(RwLock::new(Vec::new())),
}
}
pub async fn register(&self, capability: Arc<dyn Capability>) -> Result<()> {
let mut caps = self.capabilities.write().await;
caps.insert(capability.name().to_string(), capability);
Ok(())
}
pub async fn get(&self, name: &str) -> Result<Arc<dyn Capability>> {
let caps = self.capabilities.read().await;
caps.get(name)
.cloned()
.ok_or_else(|| crate::Error::NotFound(format!("Capability '{}' not found", name)))
}
pub async fn list(&self) -> Vec<String> {
let caps = self.capabilities.read().await;
caps.keys().cloned().collect()
}
pub async fn invoke(&self, name: &str, input: StructuredContent) -> Result<CapabilityResult> {
let cap = self.get(name).await?;
if !cap.can_invoke(&input) {
return Err(crate::Error::PreconditionNotMet(
format!("Preconditions not met for capability '{}'", name)
));
}
let result = cap.invoke(input.clone()).await?;
let trace = CapabilityTrace::new(
name,
input,
result.data.clone(),
result.metadata.clone(),
result.is_success(),
);
self.record_trace(trace).await;
Ok(result)
}
pub async fn record_trace(&self, trace: CapabilityTrace) {
let mut traces = self.traces.write().await;
traces.push(trace);
}
pub async fn traces(&self) -> Vec<CapabilityTrace> {
let traces = self.traces.read().await;
traces.clone()
}
pub async fn clear_traces(&self) {
let mut traces = self.traces.write().await;
traces.clear();
}
pub async fn simulate(&self, name: &str, input: &StructuredContent) -> Result<CapabilityResult> {
let cap = self.get(name).await?;
cap.simulate(input)
}
}
impl Default for CapabilityRegistry {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Result;
struct MockCapability {
name: String,
}
#[async_trait]
impl Capability for MockCapability {
fn name(&self) -> &str {
&self.name
}
fn describe(&self) -> CapabilityContract {
CapabilityContract::new()
.with_precondition("has_input")
.with_effect("produces_output")
.build()
}
fn check_precondition(&self, precondition: &str, _state: &StructuredContent) -> bool {
precondition == "has_input"
}
async fn invoke(&self, input: StructuredContent) -> Result<CapabilityResult> {
Ok(CapabilityResult::success(input))
}
}
#[tokio::test]
async fn test_capability_registry() {
let registry = CapabilityRegistry::new();
let mock = Arc::new(MockCapability {
name: "test".into(),
});
registry.register(mock).await.unwrap();
let caps = registry.list().await;
assert_eq!(caps, vec!["test"]);
let cap = registry.get("test").await.unwrap();
assert_eq!(cap.name(), "test");
}
#[test]
fn test_capability_contract() {
let contract = CapabilityContract::new()
.with_precondition("needs_input")
.with_effect("produces_output")
.with_failure_mode("may_fail")
.build();
assert_eq!(contract.preconditions, vec!["needs_input"]);
assert_eq!(contract.effects, vec!["produces_output"]);
assert_eq!(contract.failure_modes, vec!["may_fail"]);
}
#[test]
fn test_capability_result() {
let result = CapabilityResult::success(StructuredContent::text("success"));
assert!(result.is_success());
let data = result.data.as_text().unwrap();
assert_eq!(data, "success");
}
}