The official Rust Plugin Development Kit for the PRX WASM Plugin System.
Build type-safe, ergonomic plugins for PRX using idiomatic Rust and the WIT Component Model. The PDK wraps all host functions in clean modules so you can focus on plugin logic rather than low-level ABI details.
cargo install cargo-component
rustup target add wasm32-wasip2# Tool plugin (LLM-callable)
cargo new --lib my-tool
cd my-toolEdit Cargo.toml:
[package]
name = "my-tool"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
prx-pdk = { git = "https://github.com/openprx/openprx", subdirectory = "pdk/rust/prx-pdk" }
wit-bindgen = { version = "0.51", default-features = false, features = ["macros"] }
[package.metadata.component]
package = "prx:plugin@0.1.0"Edit src/lib.rs:
use prx_pdk::prelude::*;
pub struct MyTool;
impl MyTool {
pub fn get_spec_impl() -> ToolSpec {
ToolSpec {
name: "my_tool".to_string(),
description: "What this tool does".to_string(),
parameters_schema: r#"{"type":"object","properties":{"input":{"type":"string"}},"required":["input"]}"#.to_string(),
}
}
pub fn execute_impl(args_json: &str) -> PluginResult {
let args: JsonValue = serde_json::from_str(args_json)
.unwrap_or_default();
let input = args["input"].as_str().unwrap_or("");
log::info(&format!("Processing: {input}"));
let count = kv::increment("call_count", 1).unwrap_or(0);
log::debug(&format!("Call #{count}"));
PluginResult::ok(format!("Processed: {input}"))
}
}
// Wire up WIT exports for cargo-component builds
#[cfg(target_arch = "wasm32")]
mod wasm_exports {
use super::MyTool;
use bindings::Guest;
impl Guest for MyTool {
fn get_spec() -> bindings::ToolSpec {
let s = MyTool::get_spec_impl();
bindings::ToolSpec {
name: s.name,
description: s.description,
parameters_schema: s.parameters_schema,
}
}
fn execute(args: String) -> bindings::PluginResult {
let r = MyTool::execute_impl(&args);
bindings::PluginResult { success: r.success, output: r.output, error: r.error }
}
}
bindings::export!(MyTool with_types_in bindings);
}# Build WASM component
cargo component build --release
# Copy to PRX plugins directory
cp target/wasm32-wasip2/release/my_tool.wasm /path/to/plugins/my-tool/plugin.wasm
cp plugin.toml /path/to/plugins/my-tool/log::trace("low-level trace");
log::debug("debugging info");
log::info("informational");
log::warn("warning");
log::error("error occurred");Read-only access to [config] values from plugin.toml.
let timeout = config::get_or("timeout_ms", "5000");
let all = config::get_all(); // Vec<(String, String)>Isolated per-plugin persistent key-value store.
// Bytes
kv::set("key", b"value").unwrap();
let bytes: Option<Vec<u8>> = kv::get("key");
// Strings
kv::set_str("name", "Alice").unwrap();
let name = kv::get_str("name");
// JSON
kv::set_json("config", &my_struct).unwrap();
let cfg: MyStruct = kv::get_json("config").unwrap();
// Atomic counter
let count = kv::increment("calls", 1).unwrap();
// List keys
let keys = kv::list_keys("prefix:");Fire-and-forget publish/subscribe event bus.
// Publish (requires "events" permission)
events::publish("my.event", r#"{"status":"ok"}"#).unwrap();
events::publish_json("my.event", &my_struct).unwrap();
// Subscribe (hook plugins)
let sub_id = events::subscribe("prx.lifecycle.*").unwrap();
events::unsubscribe(sub_id).unwrap();Outbound HTTP (requires "http-outbound" permission + http_allowlist).
let resp = http::get("https://api.example.com/data", &[]).unwrap();
let json: serde_json::Value = resp.json().unwrap();
let resp = http::post_json(
"https://api.example.com/submit",
&[("Authorization", "Bearer token")],
&payload,
).unwrap();
println!("Status: {}", resp.status);let ts_ms: u64 = clock::now_ms(); // Unix milliseconds
let tz: &str = clock::timezone(); // "UTC"Long-term memory (requires "memory" permission).
let id = memory::store("important fact", "fact").unwrap();
let entries = memory::recall("query about facts", 5).unwrap();
for entry in entries {
println!("{}: {} (importance: {})", entry.id, entry.text, entry.importance);
}Responds to LLM tool calls. Implements prx:plugin/tool-exports.
[[capabilities]]
type = "tool"
name = "my_tool"Observes lifecycle events without modifying them. Implements prx:plugin/hook-exports.
[[capabilities]]
type = "hook"
[[capabilities.events]]
pattern = "prx.lifecycle.*"Transforms data at pipeline stages. Implements prx:plugin/middleware-exports.
[[capabilities]]
type = "middleware"
priority = 50Receives stage (one of "inbound", "outbound", "llm_request", "llm_response")
and data_json. Returns modified JSON.
Runs on a schedule. Implements prx:plugin/cron-exports.
[[capabilities]]
type = "cron"
schedule = "0 * * * *" # every hourDeclare required/optional permissions in plugin.toml:
[permissions]
required = ["log", "kv", "events"]
optional = ["http-outbound", "memory"]| Permission | Enables |
|---|---|
log |
log::* functions |
config |
config::* functions |
kv |
kv::* functions |
events |
events::* functions |
http-outbound |
http::* functions |
memory |
memory::* functions |
| prx-pdk | wit-bindgen | wasmtime (host) |
|---|---|---|
| 0.1.x | 0.51.x | 31.x |
The PDK uses wit-bindgen = "0.51", which aligns with the wasmtime 31.x runtime
embedded in PRX. Always use matching versions to ensure ABI compatibility.
| Example | Plugin Type | Demonstrates |
|---|---|---|
base64-tool |
Tool | JSON args, KV counters, pure-Rust logic |
audit-hook |
Hook | Event subscriptions, KV state, event publishing |
All crates compile as rlib on the host for development and testing:
# No cargo-component needed for these commands:
cargo build # compile as rlib
cargo test # run unit tests
cargo doc --no-deps # generate docsThe WASM-specific export wiring (wit-bindgen Guest trait, export! macro) is
guarded by #[cfg(target_arch = "wasm32")] and excluded from host builds.
pdk/rust/
├── README.md ← this file
├── prx-pdk/
│ ├── Cargo.toml
│ ├── src/lib.rs ← all wrapper modules + types + macros
│ └── wit/ ← WIT definitions (copied from main project)
│ ├── package.wit ← prx:host@0.1.0
│ ├── log.wit
│ ├── config.wit
│ ├── kv.wit
│ ├── event.wit
│ ├── http.wit
│ ├── memory.wit
│ └── pdk-worlds.wit ← pdk-full world for bindgen
└── examples/
├── base64-tool/ ← Tool plugin example
└── audit-hook/ ← Hook plugin example