Rust is a first-class language for Phi extensions — on par with the Go SDK
in ext/go. Same PXB wire protocol on stdin/stdout, same host
features (LLM tools, slash commands, intercepts, event subscriptions, confirm
dialogs), byte-for-byte interop, and the same install flow (phi.yaml + a
binary under ~/.phi/extensions/<name>/). The only dependencies are
serde/serde_json at the JSON edges (tool schemas, confirm payloads); the
PXB wire codec stays hand-rolled — no reflection, no runtime protocol deps.
Wire compatibility with the Go SDK is pinned byte-for-byte by golden tests
against ext/go/pxb/testdata/*.bin (tests/pxb_test.rs).
| Path | Role |
|---|---|
src/pxb/ |
Wire protocol: frames (codec), tagged fields (fields), message codecs (msg), types/events (types) |
src/phi/ |
Author SDK: Extension, Tool, Command, Context, Schema |
examples/ |
Runnable extensions: hello (commands, intercepts, subscribe), full (tool, confirm, submit) |
tests/ |
Golden byte-compat + end-to-end fake-host tests |
The crate publishes to crates.io; release tags are ext/rust/vX.Y.Z. Until
the first publish, depend on it from the repo (main); after a release, use
the published version:
[dependencies]
# pre-release: tracks the latest main
phi-ext = { git = "https://github.com/pulseaiclub/phi", branch = "main" }
# release: published crate version (tagged ext/rust/vX.Y.Z)
# phi-ext = "0.1.0"use phi_ext::{phi, pxb};
fn main() -> Result<(), phi::Error> {
let mut m = phi::Extension::new("hello", "0.1.0");
m.register_command("hello", phi::Command::new("Say hi", |_args, ctx| {
ctx.notify("info", "Hello!");
// ctx.submit("follow-up"); // after /hello returns
// ctx.send_user_message("…"); // enqueue a turn anytime
Ok(())
}));
// .needs_args() → picker/bare "/plan" fills "/plan " for the user to finish
m.register_command(
"plan",
phi::Command::new("plan mode — /plan on|off|status", |_args, _ctx| Ok(())).needs_args(),
);
m.on_user_input(|_ev| None); // return Some(UserInputResult { handled: true, .. }) to swallow
m.on_tool_call(|_ev| None); // return Some(ToolCallResult { block: true, reason: "…", .. }) to deny
m.on_tool_result(|_ev| None); // return Some(ToolResultResult { stop: true, .. }) to end the loop
m.on_turn_stopping(|_ev| None); // return Some(TurnStoppingResult { continue_: true, message: "…", .. }) to steer
m.subscribe(pxb::Event::SessionStart, |_ev| {});
m.run()
}Command handlers get a phi::Context for host interaction:
notify, set_status, submit, send_user_message, confirm,
confirm_opts.
Build and install (a phi.yaml manifest must live next to the binary):
cargo build --release --example hello
mkdir -p ~/.phi/extensions/hello
cp target/release/examples/hello phi.yaml ~/.phi/extensions/hello/Reload in the TUI: Ctrl+K → extensions → reload.
PXB codec numbers (vs Go / JSON lines) live in the root
README. Re-run the probe with
cargo run --release --example bench.
cargo test # unit + golden + end-to-end fake-host tests
cargo fmt --check
cargo clippy --all-targets -- -D warningsThe run loop is single-threaded; the borrow checker replaces the Go SDK's
mutexes (handlers get &mut state and cannot alias the registry).