Skip to main content

roboticus_plugin_sdk/
lib.rs

1//! # roboticus-plugin-sdk
2//!
3//! Plugin system for the Roboticus agent runtime. Plugins extend the agent with
4//! custom tools that are discovered, loaded, and executed through a unified
5//! async trait interface.
6//!
7//! ## Key Types
8//!
9//! - [`Plugin`] -- Async trait defining the plugin lifecycle
10//! - [`ToolDef`] -- Tool definition with name, description, and JSON Schema parameters
11//! - [`ToolResult`] -- Execution result (success flag, output text, optional metadata)
12//! - [`PluginStatus`] -- Plugin state: Loaded, Active, Disabled, Error
13//!
14//! ## Modules
15//!
16//! - `loader` -- Load plugins from a directory with auto-discovery and hot-reload
17//! - `manifest` -- TOML manifest parsing and validation
18//! - `registry` -- Plugin registration, lookup, enable/disable
19//! - `script` -- Script-based plugin execution (subprocess with sandboxing)
20
21pub mod archive;
22pub mod catalog;
23pub mod loader;
24pub mod manifest;
25pub mod registry;
26pub mod script;
27
28use async_trait::async_trait;
29use serde::{Deserialize, Serialize};
30use serde_json::Value;
31
32use roboticus_core::{Result, RiskLevel};
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct ToolDef {
36    pub name: String,
37    pub description: String,
38    pub parameters: Value,
39    #[serde(default = "default_tool_risk_level")]
40    pub risk_level: RiskLevel,
41    #[serde(default)]
42    pub permissions: Vec<String>,
43}
44
45fn default_tool_risk_level() -> RiskLevel {
46    RiskLevel::Caution
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct ToolResult {
51    pub success: bool,
52    pub output: String,
53    pub metadata: Option<Value>,
54}
55
56#[async_trait]
57pub trait Plugin: Send + Sync {
58    fn name(&self) -> &str;
59    fn version(&self) -> &str;
60    fn tools(&self) -> Vec<ToolDef>;
61    async fn init(&mut self) -> Result<()>;
62    async fn execute_tool(&self, tool_name: &str, input: &Value) -> Result<ToolResult>;
63    async fn shutdown(&mut self) -> Result<()>;
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
67pub enum PluginStatus {
68    Loaded,
69    Active,
70    Disabled,
71    Error,
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    #[test]
79    fn tool_def_serde() {
80        let tool = ToolDef {
81            name: "test_tool".into(),
82            description: "A test tool".into(),
83            parameters: serde_json::json!({"type": "object"}),
84            risk_level: RiskLevel::Safe,
85            permissions: vec![],
86        };
87        let json = serde_json::to_string(&tool).unwrap();
88        let back: ToolDef = serde_json::from_str(&json).unwrap();
89        assert_eq!(back.name, "test_tool");
90    }
91
92    #[test]
93    fn tool_def_defaults_to_caution_when_risk_missing() {
94        let raw = r#"{"name":"t","description":"d","parameters":{"type":"object"}}"#;
95        let back: ToolDef = serde_json::from_str(raw).unwrap();
96        assert_eq!(back.risk_level, RiskLevel::Caution);
97    }
98
99    #[test]
100    fn tool_result_serde() {
101        let result = ToolResult {
102            success: true,
103            output: "done".into(),
104            metadata: Some(serde_json::json!({"elapsed_ms": 42})),
105        };
106        let json = serde_json::to_string(&result).unwrap();
107        let back: ToolResult = serde_json::from_str(&json).unwrap();
108        assert!(back.success);
109        assert_eq!(back.output, "done");
110    }
111
112    #[test]
113    fn plugin_status_roundtrip() {
114        for status in [
115            PluginStatus::Loaded,
116            PluginStatus::Active,
117            PluginStatus::Disabled,
118            PluginStatus::Error,
119        ] {
120            let json = serde_json::to_string(&status).unwrap();
121            let back: PluginStatus = serde_json::from_str(&json).unwrap();
122            assert_eq!(status, back);
123        }
124    }
125}