Skip to main content

Tool

Struct Tool 

Source
pub struct Tool {
    pub name: String,
    pub description: String,
    pub schema: Schema,
    pub readable: bool,
    pub timeout_sec: u32,
    pub detail_from_args: Option<Box<dyn FnMut(&[u8]) -> String>>,
    pub execute: Box<dyn FnMut(&[u8]) -> Pin<Box<dyn Future<Output = Result<ToolResult, String>>>>>,
}
Expand description

An LLM-callable tool. schema is a typed JSON Schema for parameters (same role as Go’s Parameters / Codex’s schemars-generated input schema). The execute handler returns a boxed future so sync (Tool::new) and async (Tool::new_async) handlers share one storage type; it is run to completion on a single-threaded tokio runtime, blocking the PXB loop the same way a sync handler does (the host waits for the result anyway).

Fields§

§name: String§description: String§schema: Schema§readable: bool

Side-effect-free: the host may run a batch of Readable calls concurrently. Set with Tool::readable.

§timeout_sec: u32

Host RPC wait for execute, in seconds. 0 = host default (30s).

§detail_from_args: Option<Box<dyn FnMut(&[u8]) -> String>>

Optional one-line TUI detail from raw JSON args (before execute).

§execute: Box<dyn FnMut(&[u8]) -> Pin<Box<dyn Future<Output = Result<ToolResult, String>>>>>

Tool handler: takes raw JSON args, returns a future yielding the result. Not Send — the run loop is single-threaded, so handlers may capture non-Send state.

Implementations§

Source§

impl Tool

Source

pub fn new( name: impl Into<String>, description: impl Into<String>, schema: impl Into<Schema>, execute: impl FnMut(&[u8]) -> Result<ToolResult, String> + 'static, ) -> Self

Examples found in repository?
examples/full.rs (lines 9-22)
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 new_async<F, Fut>( name: impl Into<String>, description: impl Into<String>, schema: impl Into<Schema>, execute: F, ) -> Self
where F: FnMut(Vec<u8>) -> Fut + 'static, Fut: Future<Output = Result<ToolResult, String>> + 'static,

Builds a tool with an async handler (network / IO friendly). The closure returns a future that the SDK drives to completion on its single-threaded runtime when the host invokes the tool. Args are owned (Vec<u8>) so |args| async move { … } can capture them directly in a 'static future.

Examples found in repository?
examples/full.rs (lines 27-43)
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 timeout_sec(self, secs: u32) -> Self

Sets how long the host waits for this tool’s result (1–3600; host clamps).

Examples found in repository?
examples/full.rs (line 44)
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 detail_from_args(self, f: impl FnMut(&[u8]) -> String + 'static) -> Self

Sets a one-line TUI detail formatter for raw JSON arguments.

Examples found in repository?
examples/full.rs (line 23)
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 readable(self) -> Self

Marks this tool side-effect-free so the host may run a batch of Readable calls concurrently.

Auto Trait Implementations§

§

impl !RefUnwindSafe for Tool

§

impl !Send for Tool

§

impl !Sync for Tool

§

impl !UnwindSafe for Tool

§

impl Freeze for Tool

§

impl Unpin for Tool

§

impl UnsafeUnpin for Tool

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.