1use abi_stable::{
8 declare_root_module_statics,
9 library::RootModule,
10 package_version_strings,
11 sabi_types::VersionStrings,
12 std_types::{ROption, RStr, RString, RVec},
13 StableAbi,
14};
15
16pub const PLUGIN_API_VERSION: u32 = 1;
19
20#[repr(C)]
22#[derive(StableAbi, Debug, Clone)]
23pub struct PluginToolResult {
24 pub success: bool,
26 pub output: RString,
28 pub error: ROption<RString>,
30}
31
32impl PluginToolResult {
33 pub fn success(output: impl Into<String>) -> Self {
35 Self {
36 success: true,
37 output: RString::from(output.into()),
38 error: ROption::RNone,
39 }
40 }
41
42 pub fn failure(error: impl Into<String>) -> Self {
44 Self {
45 success: false,
46 output: RString::new(),
47 error: ROption::RSome(RString::from(error.into())),
48 }
49 }
50}
51
52#[repr(C)]
54#[derive(StableAbi, Debug, Clone)]
55pub struct PluginToolInfo {
56 pub name: RString,
58 pub description: RString,
60 pub parameters_json: RString,
62}
63
64impl PluginToolInfo {
65 pub fn new(
67 name: impl Into<String>,
68 description: impl Into<String>,
69 parameters_json: impl Into<String>,
70 ) -> Self {
71 Self {
72 name: RString::from(name.into()),
73 description: RString::from(description.into()),
74 parameters_json: RString::from(parameters_json.into()),
75 }
76 }
77}
78
79#[repr(C)]
84#[derive(StableAbi)]
85pub struct PluginTool {
86 pub info: extern "C" fn() -> PluginToolInfo,
88
89 pub execute: extern "C" fn(args_json: RStr<'_>) -> PluginToolResult,
97
98 pub initialize: Option<extern "C" fn(context_json: RStr<'_>) -> bool>,
109}
110
111pub type PluginToolRef = &'static PluginTool;
113
114#[repr(C)]
119#[derive(StableAbi)]
120#[sabi(kind(Prefix(prefix_ref = PluginModuleRef)))]
121pub struct PluginModule {
122 pub api_version: extern "C" fn() -> u32,
126
127 pub get_tools: extern "C" fn() -> RVec<PluginToolRef>,
129
130 pub plugin_name: extern "C" fn() -> RString,
132
133 #[sabi(last_prefix_field)]
135 pub shutdown: Option<extern "C" fn()>,
136}
137
138impl RootModule for PluginModuleRef {
139 declare_root_module_statics! {PluginModuleRef}
140
141 const BASE_NAME: &'static str = "spec_ai_plugin";
142 const NAME: &'static str = "spec_ai_plugin";
143 const VERSION_STRINGS: VersionStrings = package_version_strings!();
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 #[test]
151 fn test_plugin_tool_result_success() {
152 let result = PluginToolResult::success("test output");
153 assert!(result.success);
154 assert_eq!(result.output.as_str(), "test output");
155 assert!(result.error.is_none());
156 }
157
158 #[test]
159 fn test_plugin_tool_result_failure() {
160 let result = PluginToolResult::failure("test error");
161 assert!(!result.success);
162 assert!(result.output.is_empty());
163 match &result.error {
164 ROption::RSome(s) => assert_eq!(s.as_str(), "test error"),
165 ROption::RNone => panic!("Expected error message"),
166 }
167 }
168
169 #[test]
170 fn test_plugin_tool_info() {
171 let info = PluginToolInfo::new("test", "A test tool", r#"{"type": "object"}"#);
172 assert_eq!(info.name.as_str(), "test");
173 assert_eq!(info.description.as_str(), "A test tool");
174 assert_eq!(info.parameters_json.as_str(), r#"{"type": "object"}"#);
175 }
176}