roboticus_plugin_sdk/
lib.rs1pub 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 #[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}