pendingOf — read a suspension off a result
Rust · crate toolnexus · SPEC §10
pub fn pending_of(result: &ToolResult) -> Option<Request>Detect that a RunResult is parked rather than finished, and get the Request that parked it.
When to use it
Section titled “When to use it”To tell whether a result is a suspension (and read its request) — in a hook, a test, or a loop of your own.
Why this and not the alternative
Section titled “Why this and not the alternative”Use this rather than reading metadata["pending"] yourself; it decodes the typed Request.
Examples
Section titled “Examples”1. The smallest useful call — suspension vs plain result
Section titled “1. The smallest useful call — suspension vs plain result”use toolnexus::{Request, ToolResult, pending, pending_of};
fn main() { let suspended = pending(Request { kind: "input".into(), prompt: "which env?".into(), ..Default::default() }); let plain = ToolResult::ok("done");
assert!(pending_of(&suspended).is_some(), "expected a suspension"); assert!(pending_of(&plain).is_none(), "expected no suspension on a plain result"); println!("ok: suspension detected, plain result is not");}2. The realistic case — telling a real error apart from a suspension
Section titled “2. The realistic case — telling a real error apart from a suspension”Both set is_error: true; only pending_of tells them apart.
use toolnexus::{ToolResult, auth_required, pending_of};
fn classify(res: &ToolResult) -> String { if let Some(req) = pending_of(res) { return format!("suspended: {}", req.kind); } if res.is_error { return format!("failed: {}", res.output); } format!("ok: {}", res.output)}
fn main() { let suspended = auth_required("https://example.com/login", ""); let failed = ToolResult::error("connection refused"); let ok = ToolResult::ok("42");
let got: Vec<String> = [&suspended, &failed, &ok].into_iter().map(classify).collect(); assert_eq!(got, ["suspended: authorization", "failed: connection refused", "ok: 42"]); println!("ok: {got:?}");}3. The full surface — reading RunResult.pending after a durable halt
Section titled “3. The full surface — reading RunResult.pending after a durable halt”pending_of works the same on a ToolResult you built by hand or one that flowed all the way
through a real Client::run — this reads the request back off the halted RunResult and off
the underlying ToolResult that produced it.
use std::sync::{Arc, Mutex};
use serde_json::json;use toolnexus::client::{ GenerateFn, InProcessOptions, InProcessRequest, InProcessResponse, InProcessToolCall, create_in_process_client,};use toolnexus::{ BuiltinsConfig, Request, Tool, ToolResult, ToolSource, ToolkitOptions, create_toolkit, empty_schema, pending, pending_of,};
fn model() -> GenerateFn { Arc::new(|_req: InProcessRequest| { Box::pin(async move { Ok(InProcessResponse { tool_calls: vec![InProcessToolCall { id: "c1".into(), name: "pick_env".into(), arguments: json!({}) }], ..Default::default() }) }) })}
#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> { let last: Arc<Mutex<Option<ToolResult>>> = Arc::new(Mutex::new(None)); let seen = last.clone(); let pick_env = Tool { concurrency_safe: None, name: "pick_env".into(), description: "asks which environment".into(), input_schema: empty_schema(), source: ToolSource::Custom, hints: None, execute: Arc::new(move |_args, _ctx| { let seen = seen.clone(); Box::pin(async move { let r = pending(Request { kind: "input".into(), prompt: "which env?".into(), ..Default::default() }); *seen.lock().unwrap() = Some(r.clone()); r }) }), }; let tk = create_toolkit(ToolkitOptions { builtins: Some(BuiltinsConfig::off()), extra_tools: vec![pick_env], ..Default::default() }) .await?; let client = create_in_process_client(InProcessOptions { generate: Some(model()), ..Default::default() });
let res = client.run("deploy", &tk).await?; assert_eq!(res.status, "pending", "expected a durable halt");
// pending_of on the tool's own ToolResult, and RunResult.pending, describe the same suspension. let from_tool = pending_of(last.lock().unwrap().as_ref().unwrap()).expect("tool view"); let from_run = res.pending.expect("run view"); assert_eq!(from_tool.id, from_run.id); assert_eq!(from_tool.prompt, from_run.prompt); println!("ok: {} - {} id matches: {}", from_tool.kind, from_tool.prompt, from_tool.id == from_run.id); Ok(())}See also
Section titled “See also”pending— Return a Pending from a tool to park the run until someone answers.auth_required— The auth-shaped suspension: hand back a URL, resume once the user has granted access.WaitForFn— The single hook where the host resolves a suspension — in-process prompt or durable queue, same contract.