use abi_stable::{
declare_root_module_statics,
library::RootModule,
package_version_strings,
sabi_types::VersionStrings,
std_types::{ROption, RStr, RString, RVec},
StableAbi,
};
pub const PLUGIN_API_VERSION: u32 = 1;
#[repr(C)]
#[derive(StableAbi, Debug, Clone)]
pub struct PluginToolResult {
pub success: bool,
pub output: RString,
pub error: ROption<RString>,
}
impl PluginToolResult {
pub fn success(output: impl Into<String>) -> Self {
Self {
success: true,
output: RString::from(output.into()),
error: ROption::RNone,
}
}
pub fn failure(error: impl Into<String>) -> Self {
Self {
success: false,
output: RString::new(),
error: ROption::RSome(RString::from(error.into())),
}
}
}
#[repr(C)]
#[derive(StableAbi, Debug, Clone)]
pub struct PluginToolInfo {
pub name: RString,
pub description: RString,
pub parameters_json: RString,
}
impl PluginToolInfo {
pub fn new(
name: impl Into<String>,
description: impl Into<String>,
parameters_json: impl Into<String>,
) -> Self {
Self {
name: RString::from(name.into()),
description: RString::from(description.into()),
parameters_json: RString::from(parameters_json.into()),
}
}
}
#[repr(C)]
#[derive(StableAbi)]
pub struct PluginTool {
pub info: extern "C" fn() -> PluginToolInfo,
pub execute: extern "C" fn(args_json: RStr<'_>) -> PluginToolResult,
pub initialize: Option<extern "C" fn(context_json: RStr<'_>) -> bool>,
}
pub type PluginToolRef = &'static PluginTool;
#[repr(C)]
#[derive(StableAbi)]
#[sabi(kind(Prefix(prefix_ref = PluginModuleRef)))]
pub struct PluginModule {
pub api_version: extern "C" fn() -> u32,
pub get_tools: extern "C" fn() -> RVec<PluginToolRef>,
pub plugin_name: extern "C" fn() -> RString,
#[sabi(last_prefix_field)]
pub shutdown: Option<extern "C" fn()>,
}
impl RootModule for PluginModuleRef {
declare_root_module_statics! {PluginModuleRef}
const BASE_NAME: &'static str = "spec_ai_plugin";
const NAME: &'static str = "spec_ai_plugin";
const VERSION_STRINGS: VersionStrings = package_version_strings!();
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_plugin_tool_result_success() {
let result = PluginToolResult::success("test output");
assert!(result.success);
assert_eq!(result.output.as_str(), "test output");
assert!(result.error.is_none());
}
#[test]
fn test_plugin_tool_result_failure() {
let result = PluginToolResult::failure("test error");
assert!(!result.success);
assert!(result.output.is_empty());
match &result.error {
ROption::RSome(s) => assert_eq!(s.as_str(), "test error"),
ROption::RNone => panic!("Expected error message"),
}
}
#[test]
fn test_plugin_tool_info() {
let info = PluginToolInfo::new("test", "A test tool", r#"{"type": "object"}"#);
assert_eq!(info.name.as_str(), "test");
assert_eq!(info.description.as_str(), "A test tool");
assert_eq!(info.parameters_json.as_str(), r#"{"type": "object"}"#);
}
}