ToolResult.pendingOf
Java · package io.github.muthuishere:toolnexus · SPEC §10 · ToolResult.java
public static Request pendingOf(ToolResult result)Reads the suspension back off a ToolResult: returns the Request when result.metadata()
carries a pending entry of that type, null otherwise (including when result itself is
null). This is the reader half of pending/
authRequired — the one place code should ask “is this a
suspension?” rather than inspecting metadata by hand. Because a suspension also sets isError
true, pendingOf is what distinguishes a parked call from a genuine failure — don’t read
isError() alone as “the tool broke”.
When to use it
Section titled “When to use it”Anywhere you’re inspecting a ToolResult (or a halted RunResult, whose transcript carries the
first suspension’s placeholder) and need to tell a suspension apart from an ordinary error — a
beforeTool/afterTool hook classifying results, a durable host deciding whether to persist a
RunResult.pending request, or a test asserting a tool suspended for the right reason.
Why this and not the alternative
Section titled “Why this and not the alternative”Examples
Section titled “Examples”1. The smallest useful call — null on an ordinary result, non-null on a suspension
Section titled “1. The smallest useful call — null on an ordinary result, non-null on a suspension”import io.github.muthuishere.toolnexus.Request;import io.github.muthuishere.toolnexus.ToolResult;
public class Example { public static void main(String[] args) { ToolResult ordinary = ToolResult.ok("done"); if (ToolResult.pendingOf(ordinary) != null) throw new AssertionError("an ordinary result is not pending");
ToolResult failed = ToolResult.error("not found"); if (ToolResult.pendingOf(failed) != null) throw new AssertionError("a real error is not pending either");
ToolResult suspended = ToolResult.pending(new Request("", "input", "Which environment?")); Request req = ToolResult.pendingOf(suspended); if (req == null) throw new AssertionError("expected the Request back"); if (!req.kind().equals("input")) throw new AssertionError(req.kind());
System.out.println("ok: ordinary=null, failed=null, suspended=" + req.kind()); }}2. The realistic case — distinguishing a suspension from a real error in a hook
Section titled “2. The realistic case — distinguishing a suspension from a real error in a hook”import io.github.muthuishere.toolnexus.*;import java.util.List;import java.util.Map;import java.util.concurrent.CopyOnWriteArrayList;
public class Example { public static void main(String[] args) { List<String> classifications = new CopyOnWriteArrayList<>();
LlmClient.Hooks hooks = new LlmClient.Hooks().afterTool(ev -> { ToolResult r = ev.result(); if (ToolResult.pendingOf(r) != null) { classifications.add("suspended"); } else if (r.isError()) { classifications.add("failed"); } else { classifications.add("ok"); } return null; });
// Exercise the classification logic directly against three representative results — // afterTool would see the same three shapes in a real run. List<ToolResult> results = List.of( ToolResult.ok("42"), ToolResult.error("boom"), ToolResult.pending(new Request("", "approval", "Approve?"))); for (ToolResult r : results) { hooks.afterTool.apply(new LlmClient.AfterToolEvent("t", Map.of(), r, "c1", 0)); }
if (!classifications.equals(List.of("ok", "failed", "suspended"))) { throw new AssertionError(classifications); }
System.out.println("ok: " + classifications); }}3. The full surface — reading the halted request back off a durable RunResult
Section titled “3. The full surface — reading the halted request back off a durable RunResult”import com.sun.net.httpserver.HttpServer;import io.github.muthuishere.toolnexus.*;import java.io.OutputStream;import java.net.InetSocketAddress;import java.nio.charset.StandardCharsets;
public class Example { public static void main(String[] args) throws Exception { HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0); server.createContext("/", ex -> { ex.getRequestBody().readAllBytes(); byte[] b = ("{\"choices\":[{\"message\":{\"content\":null,\"tool_calls\":[{\"id\":\"c1\"," + "\"type\":\"function\",\"function\":{\"name\":\"approve_refund\",\"arguments\":\"{}\"}}]}}]}") .getBytes(StandardCharsets.UTF_8); ex.getResponseHeaders().add("Content-Type", "application/json"); ex.sendResponseHeaders(200, b.length); try (OutputStream os = ex.getResponseBody()) { os.write(b); } }); server.start();
Tool refund = new Tool() { @Override public String name() { return "approve_refund"; } @Override public String description() { return "Refund a customer. Needs approval."; } @Override public java.util.Map<String, Object> inputSchema() { return java.util.Map.of("type", "object", "properties", java.util.Map.of()); } @Override public String source() { return "custom"; } @Override public ToolResult execute(java.util.Map<String, Object> args, ToolContext ctx) { return ToolResult.pending(new Request("", "approval", "Approve the refund?")); } };
try (Toolkit tk = Toolkit.create(new Toolkit.Options().builtins(false).extraTools(refund))) { LlmClient client = LlmClient.create(new LlmClient.Options() .baseUrl("http://127.0.0.1:" + server.getAddress().getPort()) .style("openai").model("test-model").apiKey("test-key")); // no waitFor
LlmClient.RunResult res = client.run("please refund order 42", tk);
if (!"pending".equals(res.status)) throw new AssertionError(res.status); // RunResult.pending IS the Request pendingOf would have read off the halted tool call. if (res.pending == null || !res.pending.kind().equals("approval")) throw new AssertionError(res.pending);
System.out.println("ok: " + res.status + " kind=" + res.pending.kind()); } finally { server.stop(0); } }}Members
Section titled “Members”| Member | Type | What it is |
|---|---|---|
pendingOf(result) |
Request |
The suspension’s Request, or null when result is null/not a suspension. |
See also
Section titled “See also”ToolResult.pending— Return a Pending from a tool to park the run until someone answers.ToolResult.authRequired— The auth-shaped suspension: hand back a URL, resume once the user has granted access.LlmClient.waitFor— The single hook where the host resolves a suspension — in-process prompt or durable queue, same contract.