rpi-plugin-sdk
The stable #[repr(C)] ABI contract for rpi plugins. Depend on this crate, and
nothing else, to build a Rust cdylib that extends the rpi agent with tools,
providers, slash commands, event handlers, renderers and discovered resources.
This crate has no dependency on the rpi runtime on purpose. A plugin must not link the host — the host links the plugin. Both sides share only the types defined here.
Install
[]
= "my-rpi-extension"
[]
= ["cdylib"]
[]
= "0.3"
= "1"
Minimal plugin
use ;
export_plugin!;
The macro emits the rpi_plugin_register symbol. The version lives inside the
PluginApi struct (abi_version, struct_size) rather than in the symbol name,
so the entrypoint signature does not change when the struct grows.
Why a hand-written C ABI
Both sides are Rust, but they are compiled separately and may use different
compiler versions and different versions of this crate. That rules out passing
any Rust type that is not #[repr(C)] or that has a Drop impl. The design
rules the SDK enforces:
- Every crossing type is
#[repr(C)], and enums in unions carry an explicit#[repr(u32)]so the discriminant width is pinned. - No
Droptype crosses. NoVec,String,serde_json::Value,Optionof those, orResult. Owned strings cross asStbString(pointer + length) together with an explicitfree_stringfunction the producer exports, so each allocation is freed exactly once, by the side that received it. - Structured data crosses as JSON in a
StbString.serde_jsonmust be configured consistently on host and plugin (preserve_order+arbitrary_precision), or integers beyondu64/i64lose precision and object key order can change. - Unions hold
Copypayloads only, so a variant read isunsafeand the caller discriminates on the tag. - Unwinding never crosses the boundary. Every call is
extern "C", and both sides contain panics withcatch_unwind; a panic is converted into a status code rather than aborting the process.
These are not stylistic preferences — violating any of them is undefined behaviour at the boundary.
Compatibility
The host loader only resolves rpi_plugin_register, exported with
export_plugin!. The host passes a PluginApi containing registration slots,
runtime_action and declare (priority and platforms).
The SDK checks abi_version against RPI_PLUGIN_ABI_VERSION_UNIFIED and
requires struct_size to cover the PluginApi expected by the plugin before
calling its registration body. A mismatch returns a nonzero status and the
host reports a load failure. Extensions using retired entrypoints must be
updated to export_plugin! and rebuilt with the current SDK.
Versioning
A plugin depends on rpi-plugin-sdk only, so its version is decoupled from the
rest of the family. ABI compatibility is checked through abi_version and
struct_size; each optional capability must also be checked before use.
Rebuild extensions when the SDK contract they require changes.
Examples and docs
examples/plugin-stub— a complete plugin registering anechotool with the full four-function lifecycle (execute→poll→cancel→destroy), an event handler, and a resource-discovery handler.- Extension authoring guide — templates, safety rules, testing and release checklist.
- Host-side loader:
rpi-extensions. - Ready-to-install extensions:
pi-rust/rpi-package.