Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 

README.md

Phi Rust extension SDK (ext/rust)

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).

Layout

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

Authoring

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.

Development

cargo test            # unit + golden + end-to-end fake-host tests
cargo fmt --check
cargo clippy --all-targets -- -D warnings

The run loop is single-threaded; the borrow checker replaces the Go SDK's mutexes (handlers get &mut state and cannot alias the registry).