Skip to main content

Extension

Struct Extension 

Source
pub struct Extension { /* private fields */ }
Expand description

The author-facing registration surface for a PXB extension binary.

Implementations§

Source§

impl Extension

Source

pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self

Constructs a module; call run once everything is registered.

Examples found in repository?
examples/hello.rs (line 6)
5fn main() -> Result<(), phi::Error> {
6    let mut m = phi::Extension::new("hello", "0.1.0");
7
8    m.register_command(
9        "hello",
10        phi::Command::new("Say hi", |_args, ctx| {
11            ctx.notify("info", "Hello!");
12            // ctx.submit("follow-up"); // after /hello returns
13            // ctx.send_user_message("…"); // enqueue a turn anytime
14            Ok(())
15        }),
16    );
17
18    m.on_user_input(|_ev| {
19        // return Some(phi::UserInputResult { handled: true, ..Default::default() }) to swallow
20        // return Some(phi::UserInputResult { text: Some("rewritten".into()), ..Default::default() }) to transform
21        None
22    });
23
24    m.on_tool_call(|_ev| {
25        // return Some(phi::ToolCallResult { block: true, reason: "...".into(), ..Default::default() }) to deny
26        None
27    });
28
29    m.on_tool_result(|_ev| {
30        // return Some(phi::ToolResultResult { stop: true, ..Default::default() }) to end the agent loop
31        None
32    });
33
34    m.on_turn_stopping(|_ev| {
35        // return Some(phi::TurnStoppingResult { continue_: true, message: "check X".into(), ..Default::default() }) to steer
36        None
37    });
38
39    m.subscribe(pxb::Event::SessionStart, |ev| {
40        let _ = ev; // Reason, PreviousSessionID, …
41    });
42
43    m.run()
44}
More examples
Hide additional examples
examples/full.rs (line 6)
5fn main() -> Result<(), phi::Error> {
6    let mut m = phi::Extension::new("full", "0.1.0");
7
8    m.register_tool(
9        phi::Tool::new(
10            "echo",
11            "Echo the input back",
12            phi::Schema::object()
13                .property("text", phi::Schema::string())
14                .required(["text"]),
15            |args| {
16                let text = String::from_utf8_lossy(args);
17                Ok(phi::ToolResult {
18                    content: format!("echo: {text}"),
19                    ..Default::default()
20                })
21            },
22        )
23        .detail_from_args(|args| String::from_utf8_lossy(args).into_owned()),
24    );
25
26    m.register_tool(
27        phi::Tool::new_async(
28            "async-echo",
29            "Echo the input back (async handler)",
30            phi::Schema::object()
31                .property("text", phi::Schema::string())
32                .required(["text"]),
33            |args| async move {
34                // Yield once: proves the SDK's runtime drives the future
35                // rather than just polling a ready block.
36                tokio::task::yield_now().await;
37                let text = String::from_utf8_lossy(&args);
38                Ok(phi::ToolResult {
39                    content: format!("async echo: {text}"),
40                    ..Default::default()
41                })
42            },
43        )
44        .timeout_sec(10),
45    );
46
47    m.register_command(
48        "ask",
49        phi::Command::new("Ask a yes/no question", |_args, ctx| {
50            let reply = ctx.confirm("Confirm?", "Proceed with /tmp/x?");
51            if reply.ok {
52                ctx.notify("info", "Confirmed!");
53            } else {
54                ctx.notify("warning", "Declined.");
55            }
56            ctx.submit("follow-up from ask");
57            Ok(())
58        }),
59    );
60
61    m.run()
62}
Source

pub fn register_tool(&mut self, tool: Tool)

Adds an LLM-callable tool.

Examples found in repository?
examples/full.rs (lines 8-24)
5fn main() -> Result<(), phi::Error> {
6    let mut m = phi::Extension::new("full", "0.1.0");
7
8    m.register_tool(
9        phi::Tool::new(
10            "echo",
11            "Echo the input back",
12            phi::Schema::object()
13                .property("text", phi::Schema::string())
14                .required(["text"]),
15            |args| {
16                let text = String::from_utf8_lossy(args);
17                Ok(phi::ToolResult {
18                    content: format!("echo: {text}"),
19                    ..Default::default()
20                })
21            },
22        )
23        .detail_from_args(|args| String::from_utf8_lossy(args).into_owned()),
24    );
25
26    m.register_tool(
27        phi::Tool::new_async(
28            "async-echo",
29            "Echo the input back (async handler)",
30            phi::Schema::object()
31                .property("text", phi::Schema::string())
32                .required(["text"]),
33            |args| async move {
34                // Yield once: proves the SDK's runtime drives the future
35                // rather than just polling a ready block.
36                tokio::task::yield_now().await;
37                let text = String::from_utf8_lossy(&args);
38                Ok(phi::ToolResult {
39                    content: format!("async echo: {text}"),
40                    ..Default::default()
41                })
42            },
43        )
44        .timeout_sec(10),
45    );
46
47    m.register_command(
48        "ask",
49        phi::Command::new("Ask a yes/no question", |_args, ctx| {
50            let reply = ctx.confirm("Confirm?", "Proceed with /tmp/x?");
51            if reply.ok {
52                ctx.notify("info", "Confirmed!");
53            } else {
54                ctx.notify("warning", "Declined.");
55            }
56            ctx.submit("follow-up from ask");
57            Ok(())
58        }),
59    );
60
61    m.run()
62}
Source

pub fn register_command(&mut self, name: impl Into<String>, cmd: Command)

Adds a slash command (cannot override builtins at host level).

Examples found in repository?
examples/hello.rs (lines 8-16)
5fn main() -> Result<(), phi::Error> {
6    let mut m = phi::Extension::new("hello", "0.1.0");
7
8    m.register_command(
9        "hello",
10        phi::Command::new("Say hi", |_args, ctx| {
11            ctx.notify("info", "Hello!");
12            // ctx.submit("follow-up"); // after /hello returns
13            // ctx.send_user_message("…"); // enqueue a turn anytime
14            Ok(())
15        }),
16    );
17
18    m.on_user_input(|_ev| {
19        // return Some(phi::UserInputResult { handled: true, ..Default::default() }) to swallow
20        // return Some(phi::UserInputResult { text: Some("rewritten".into()), ..Default::default() }) to transform
21        None
22    });
23
24    m.on_tool_call(|_ev| {
25        // return Some(phi::ToolCallResult { block: true, reason: "...".into(), ..Default::default() }) to deny
26        None
27    });
28
29    m.on_tool_result(|_ev| {
30        // return Some(phi::ToolResultResult { stop: true, ..Default::default() }) to end the agent loop
31        None
32    });
33
34    m.on_turn_stopping(|_ev| {
35        // return Some(phi::TurnStoppingResult { continue_: true, message: "check X".into(), ..Default::default() }) to steer
36        None
37    });
38
39    m.subscribe(pxb::Event::SessionStart, |ev| {
40        let _ = ev; // Reason, PreviousSessionID, …
41    });
42
43    m.run()
44}
More examples
Hide additional examples
examples/full.rs (lines 47-59)
5fn main() -> Result<(), phi::Error> {
6    let mut m = phi::Extension::new("full", "0.1.0");
7
8    m.register_tool(
9        phi::Tool::new(
10            "echo",
11            "Echo the input back",
12            phi::Schema::object()
13                .property("text", phi::Schema::string())
14                .required(["text"]),
15            |args| {
16                let text = String::from_utf8_lossy(args);
17                Ok(phi::ToolResult {
18                    content: format!("echo: {text}"),
19                    ..Default::default()
20                })
21            },
22        )
23        .detail_from_args(|args| String::from_utf8_lossy(args).into_owned()),
24    );
25
26    m.register_tool(
27        phi::Tool::new_async(
28            "async-echo",
29            "Echo the input back (async handler)",
30            phi::Schema::object()
31                .property("text", phi::Schema::string())
32                .required(["text"]),
33            |args| async move {
34                // Yield once: proves the SDK's runtime drives the future
35                // rather than just polling a ready block.
36                tokio::task::yield_now().await;
37                let text = String::from_utf8_lossy(&args);
38                Ok(phi::ToolResult {
39                    content: format!("async echo: {text}"),
40                    ..Default::default()
41                })
42            },
43        )
44        .timeout_sec(10),
45    );
46
47    m.register_command(
48        "ask",
49        phi::Command::new("Ask a yes/no question", |_args, ctx| {
50            let reply = ctx.confirm("Confirm?", "Proceed with /tmp/x?");
51            if reply.ok {
52                ctx.notify("info", "Confirmed!");
53            } else {
54                ctx.notify("warning", "Declined.");
55            }
56            ctx.submit("follow-up from ask");
57            Ok(())
58        }),
59    );
60
61    m.run()
62}
Source

pub fn on_tool_call( &mut self, f: impl FnMut(ToolCallEvent) -> Option<ToolCallResult> + 'static, )

Registers a pre-gate tool_call intercept.

Examples found in repository?
examples/hello.rs (lines 24-27)
5fn main() -> Result<(), phi::Error> {
6    let mut m = phi::Extension::new("hello", "0.1.0");
7
8    m.register_command(
9        "hello",
10        phi::Command::new("Say hi", |_args, ctx| {
11            ctx.notify("info", "Hello!");
12            // ctx.submit("follow-up"); // after /hello returns
13            // ctx.send_user_message("…"); // enqueue a turn anytime
14            Ok(())
15        }),
16    );
17
18    m.on_user_input(|_ev| {
19        // return Some(phi::UserInputResult { handled: true, ..Default::default() }) to swallow
20        // return Some(phi::UserInputResult { text: Some("rewritten".into()), ..Default::default() }) to transform
21        None
22    });
23
24    m.on_tool_call(|_ev| {
25        // return Some(phi::ToolCallResult { block: true, reason: "...".into(), ..Default::default() }) to deny
26        None
27    });
28
29    m.on_tool_result(|_ev| {
30        // return Some(phi::ToolResultResult { stop: true, ..Default::default() }) to end the agent loop
31        None
32    });
33
34    m.on_turn_stopping(|_ev| {
35        // return Some(phi::TurnStoppingResult { continue_: true, message: "check X".into(), ..Default::default() }) to steer
36        None
37    });
38
39    m.subscribe(pxb::Event::SessionStart, |ev| {
40        let _ = ev; // Reason, PreviousSessionID, …
41    });
42
43    m.run()
44}
Source

pub fn on_tool_result( &mut self, f: impl FnMut(ToolResultEvent) -> Option<ToolResultResult> + 'static, )

Registers a post-tool tool_result intercept.

Examples found in repository?
examples/hello.rs (lines 29-32)
5fn main() -> Result<(), phi::Error> {
6    let mut m = phi::Extension::new("hello", "0.1.0");
7
8    m.register_command(
9        "hello",
10        phi::Command::new("Say hi", |_args, ctx| {
11            ctx.notify("info", "Hello!");
12            // ctx.submit("follow-up"); // after /hello returns
13            // ctx.send_user_message("…"); // enqueue a turn anytime
14            Ok(())
15        }),
16    );
17
18    m.on_user_input(|_ev| {
19        // return Some(phi::UserInputResult { handled: true, ..Default::default() }) to swallow
20        // return Some(phi::UserInputResult { text: Some("rewritten".into()), ..Default::default() }) to transform
21        None
22    });
23
24    m.on_tool_call(|_ev| {
25        // return Some(phi::ToolCallResult { block: true, reason: "...".into(), ..Default::default() }) to deny
26        None
27    });
28
29    m.on_tool_result(|_ev| {
30        // return Some(phi::ToolResultResult { stop: true, ..Default::default() }) to end the agent loop
31        None
32    });
33
34    m.on_turn_stopping(|_ev| {
35        // return Some(phi::TurnStoppingResult { continue_: true, message: "check X".into(), ..Default::default() }) to steer
36        None
37    });
38
39    m.subscribe(pxb::Event::SessionStart, |ev| {
40        let _ = ev; // Reason, PreviousSessionID, …
41    });
42
43    m.run()
44}
Source

pub fn on_before_agent_start( &mut self, f: impl FnMut(BeforeAgentStartEvent) -> Option<BeforeAgentStartResult> + 'static, )

May append system prompt text before the agent loop starts.

Source

pub fn on_session_before_switch( &mut self, f: impl FnMut(SessionBeforeSwitchEvent) -> Option<SessionBeforeSwitchResult> + 'static, )

May cancel a session switch.

Source

pub fn on_user_input( &mut self, f: impl FnMut(UserInputEvent) -> Option<UserInputResult> + 'static, )

May transform or swallow the user prompt before the agent loop.

Examples found in repository?
examples/hello.rs (lines 18-22)
5fn main() -> Result<(), phi::Error> {
6    let mut m = phi::Extension::new("hello", "0.1.0");
7
8    m.register_command(
9        "hello",
10        phi::Command::new("Say hi", |_args, ctx| {
11            ctx.notify("info", "Hello!");
12            // ctx.submit("follow-up"); // after /hello returns
13            // ctx.send_user_message("…"); // enqueue a turn anytime
14            Ok(())
15        }),
16    );
17
18    m.on_user_input(|_ev| {
19        // return Some(phi::UserInputResult { handled: true, ..Default::default() }) to swallow
20        // return Some(phi::UserInputResult { text: Some("rewritten".into()), ..Default::default() }) to transform
21        None
22    });
23
24    m.on_tool_call(|_ev| {
25        // return Some(phi::ToolCallResult { block: true, reason: "...".into(), ..Default::default() }) to deny
26        None
27    });
28
29    m.on_tool_result(|_ev| {
30        // return Some(phi::ToolResultResult { stop: true, ..Default::default() }) to end the agent loop
31        None
32    });
33
34    m.on_turn_stopping(|_ev| {
35        // return Some(phi::TurnStoppingResult { continue_: true, message: "check X".into(), ..Default::default() }) to steer
36        None
37    });
38
39    m.subscribe(pxb::Event::SessionStart, |ev| {
40        let _ = ev; // Reason, PreviousSessionID, …
41    });
42
43    m.run()
44}
Source

pub fn on_turn_stopping( &mut self, f: impl FnMut(TurnStoppingEvent) -> Option<TurnStoppingResult> + 'static, )

May steer another agent step when the model stops with no tools.

Examples found in repository?
examples/hello.rs (lines 34-37)
5fn main() -> Result<(), phi::Error> {
6    let mut m = phi::Extension::new("hello", "0.1.0");
7
8    m.register_command(
9        "hello",
10        phi::Command::new("Say hi", |_args, ctx| {
11            ctx.notify("info", "Hello!");
12            // ctx.submit("follow-up"); // after /hello returns
13            // ctx.send_user_message("…"); // enqueue a turn anytime
14            Ok(())
15        }),
16    );
17
18    m.on_user_input(|_ev| {
19        // return Some(phi::UserInputResult { handled: true, ..Default::default() }) to swallow
20        // return Some(phi::UserInputResult { text: Some("rewritten".into()), ..Default::default() }) to transform
21        None
22    });
23
24    m.on_tool_call(|_ev| {
25        // return Some(phi::ToolCallResult { block: true, reason: "...".into(), ..Default::default() }) to deny
26        None
27    });
28
29    m.on_tool_result(|_ev| {
30        // return Some(phi::ToolResultResult { stop: true, ..Default::default() }) to end the agent loop
31        None
32    });
33
34    m.on_turn_stopping(|_ev| {
35        // return Some(phi::TurnStoppingResult { continue_: true, message: "check X".into(), ..Default::default() }) to steer
36        None
37    });
38
39    m.subscribe(pxb::Event::SessionStart, |ev| {
40        let _ = ev; // Reason, PreviousSessionID, …
41    });
42
43    m.run()
44}
Source

pub fn subscribe(&mut self, event: Event, f: impl FnMut(EventNotify) + 'static)

Adds a fire-and-forget lifecycle listener; the payload is the wire EventNotify. Unknown events are ignored.

Examples found in repository?
examples/hello.rs (lines 39-41)
5fn main() -> Result<(), phi::Error> {
6    let mut m = phi::Extension::new("hello", "0.1.0");
7
8    m.register_command(
9        "hello",
10        phi::Command::new("Say hi", |_args, ctx| {
11            ctx.notify("info", "Hello!");
12            // ctx.submit("follow-up"); // after /hello returns
13            // ctx.send_user_message("…"); // enqueue a turn anytime
14            Ok(())
15        }),
16    );
17
18    m.on_user_input(|_ev| {
19        // return Some(phi::UserInputResult { handled: true, ..Default::default() }) to swallow
20        // return Some(phi::UserInputResult { text: Some("rewritten".into()), ..Default::default() }) to transform
21        None
22    });
23
24    m.on_tool_call(|_ev| {
25        // return Some(phi::ToolCallResult { block: true, reason: "...".into(), ..Default::default() }) to deny
26        None
27    });
28
29    m.on_tool_result(|_ev| {
30        // return Some(phi::ToolResultResult { stop: true, ..Default::default() }) to end the agent loop
31        None
32    });
33
34    m.on_turn_stopping(|_ev| {
35        // return Some(phi::TurnStoppingResult { continue_: true, message: "check X".into(), ..Default::default() }) to steer
36        None
37    });
38
39    m.subscribe(pxb::Event::SessionStart, |ev| {
40        let _ = ev; // Reason, PreviousSessionID, …
41    });
42
43    m.run()
44}
Source

pub fn run(self) -> Result<(), Error>

Speaks PXB on stdin/stdout until the host shuts down.

Examples found in repository?
examples/hello.rs (line 43)
5fn main() -> Result<(), phi::Error> {
6    let mut m = phi::Extension::new("hello", "0.1.0");
7
8    m.register_command(
9        "hello",
10        phi::Command::new("Say hi", |_args, ctx| {
11            ctx.notify("info", "Hello!");
12            // ctx.submit("follow-up"); // after /hello returns
13            // ctx.send_user_message("…"); // enqueue a turn anytime
14            Ok(())
15        }),
16    );
17
18    m.on_user_input(|_ev| {
19        // return Some(phi::UserInputResult { handled: true, ..Default::default() }) to swallow
20        // return Some(phi::UserInputResult { text: Some("rewritten".into()), ..Default::default() }) to transform
21        None
22    });
23
24    m.on_tool_call(|_ev| {
25        // return Some(phi::ToolCallResult { block: true, reason: "...".into(), ..Default::default() }) to deny
26        None
27    });
28
29    m.on_tool_result(|_ev| {
30        // return Some(phi::ToolResultResult { stop: true, ..Default::default() }) to end the agent loop
31        None
32    });
33
34    m.on_turn_stopping(|_ev| {
35        // return Some(phi::TurnStoppingResult { continue_: true, message: "check X".into(), ..Default::default() }) to steer
36        None
37    });
38
39    m.subscribe(pxb::Event::SessionStart, |ev| {
40        let _ = ev; // Reason, PreviousSessionID, …
41    });
42
43    m.run()
44}
More examples
Hide additional examples
examples/full.rs (line 61)
5fn main() -> Result<(), phi::Error> {
6    let mut m = phi::Extension::new("full", "0.1.0");
7
8    m.register_tool(
9        phi::Tool::new(
10            "echo",
11            "Echo the input back",
12            phi::Schema::object()
13                .property("text", phi::Schema::string())
14                .required(["text"]),
15            |args| {
16                let text = String::from_utf8_lossy(args);
17                Ok(phi::ToolResult {
18                    content: format!("echo: {text}"),
19                    ..Default::default()
20                })
21            },
22        )
23        .detail_from_args(|args| String::from_utf8_lossy(args).into_owned()),
24    );
25
26    m.register_tool(
27        phi::Tool::new_async(
28            "async-echo",
29            "Echo the input back (async handler)",
30            phi::Schema::object()
31                .property("text", phi::Schema::string())
32                .required(["text"]),
33            |args| async move {
34                // Yield once: proves the SDK's runtime drives the future
35                // rather than just polling a ready block.
36                tokio::task::yield_now().await;
37                let text = String::from_utf8_lossy(&args);
38                Ok(phi::ToolResult {
39                    content: format!("async echo: {text}"),
40                    ..Default::default()
41                })
42            },
43        )
44        .timeout_sec(10),
45    );
46
47    m.register_command(
48        "ask",
49        phi::Command::new("Ask a yes/no question", |_args, ctx| {
50            let reply = ctx.confirm("Confirm?", "Proceed with /tmp/x?");
51            if reply.ok {
52                ctx.notify("info", "Confirmed!");
53            } else {
54                ctx.notify("warning", "Declined.");
55            }
56            ctx.submit("follow-up from ask");
57            Ok(())
58        }),
59    );
60
61    m.run()
62}

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.