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