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` -- Discover plugins from a directory (`plugin.toml` per subdirectory; loaded at server boot)
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    /// Optional companion skill path or name bundled with the plugin (`plugin.toml`).
44    #[serde(default)]
45    pub paired_skill: Option<String>,
46}
47
48fn default_tool_risk_level() -> RiskLevel {
49    RiskLevel::Caution
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct ToolResult {
54    pub success: bool,
55    pub output: String,
56    pub metadata: Option<Value>,
57}
58
59#[async_trait]
60pub trait Plugin: Send + Sync {
61    fn name(&self) -> &str;
62    fn version(&self) -> &str;
63    fn tools(&self) -> Vec<ToolDef>;
64    async fn init(&mut self) -> Result<()>;
65    async fn execute_tool(&self, tool_name: &str, input: &Value) -> Result<ToolResult>;
66    async fn shutdown(&mut self) -> Result<()>;
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
70pub enum PluginStatus {
71    Loaded,
72    Active,
73    Disabled,
74    Error,
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn tool_def_serde() {
83        let tool = ToolDef {
84            name: "test_tool".into(),
85            description: "A test tool".into(),
86            parameters: serde_json::json!({"type": "object"}),
87            risk_level: RiskLevel::Safe,
88            permissions: vec![],
89            paired_skill: None,
90        };
91        let json = serde_json::to_string(&tool).unwrap();
92        let back: ToolDef = serde_json::from_str(&json).unwrap();
93        assert_eq!(back.name, "test_tool");
94    }
95
96    #[test]
97    fn tool_def_defaults_to_caution_when_risk_missing() {
98        let raw = r#"{"name":"t","description":"d","parameters":{"type":"object"}}"#;
99        let back: ToolDef = serde_json::from_str(raw).unwrap();
100        assert_eq!(back.risk_level, RiskLevel::Caution);
101    }
102
103    #[test]
104    fn tool_result_serde() {
105        let result = ToolResult {
106            success: true,
107            output: "done".into(),
108            metadata: Some(serde_json::json!({"elapsed_ms": 42})),
109        };
110        let json = serde_json::to_string(&result).unwrap();
111        let back: ToolResult = serde_json::from_str(&json).unwrap();
112        assert!(back.success);
113        assert_eq!(back.output, "done");
114    }
115
116    #[test]
117    fn plugin_status_roundtrip() {
118        for status in [
119            PluginStatus::Loaded,
120            PluginStatus::Active,
121            PluginStatus::Disabled,
122            PluginStatus::Error,
123        ] {
124            let json = serde_json::to_string(&status).unwrap();
125            let back: PluginStatus = serde_json::from_str(&json).unwrap();
126            assert_eq!(status, back);
127        }
128    }
129}