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: boolSide-effect-free: the host may run a batch of Readable calls
concurrently. Set with Tool::readable.
timeout_sec: u32Host 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
impl Tool
Sourcepub fn new(
name: impl Into<String>,
description: impl Into<String>,
schema: impl Into<Schema>,
execute: impl FnMut(&[u8]) -> Result<ToolResult, String> + 'static,
) -> Self
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?
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}Sourcepub fn new_async<F, Fut>(
name: impl Into<String>,
description: impl Into<String>,
schema: impl Into<Schema>,
execute: F,
) -> Self
pub fn new_async<F, Fut>( name: impl Into<String>, description: impl Into<String>, schema: impl Into<Schema>, execute: F, ) -> Self
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?
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}Sourcepub fn timeout_sec(self, secs: u32) -> Self
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?
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}Sourcepub fn detail_from_args(self, f: impl FnMut(&[u8]) -> String + 'static) -> Self
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?
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}