PendingOf
Go · package github.com/muthuishere/toolnexus/golang · SPEC §10 · golang/types.go
func PendingOf(result ToolResult) *RequestPendingOf reads the suspension back off a ToolResult: it returns &Request when
result.Metadata["pending"] holds one (checking both the Request and *Request shapes),
and nil for an ordinary result. It’s the read side of Pending
— the thing you call from test code, from a WaitFor implementation inspecting what a tool
asked for, or from any code that needs to tell “this call suspended” apart from “this call
failed.”
When to use it
Section titled “When to use it”Reach for PendingOf wherever you have a ToolResult and need to know whether it’s a
suspension: writing tests for your own suspending tools, building a WaitFor that branches on
req.Kind, or inspecting RunResult.ToolCalls[i]-shaped data after a run. It’s also the
correct way to distinguish “the tool suspended” from “the tool errored” — both set
IsError: true, and PendingOf is the only reliable discriminator.
Why this and not the alternative
Section titled “Why this and not the alternative”Examples
Section titled “Examples”1. The smallest useful call — suspension vs plain result
Section titled “1. The smallest useful call — suspension vs plain result”package main
import ( "fmt" "log"
toolnexus "github.com/muthuishere/toolnexus/golang")
func main() { suspended := toolnexus.Pending(toolnexus.Request{Kind: "input", Prompt: "which env?"}) plain := toolnexus.ToolResult{Output: "done"}
if toolnexus.PendingOf(suspended) == nil { log.Fatal("expected a suspension") } if toolnexus.PendingOf(plain) != nil { log.Fatal("expected no suspension on a plain result") }
fmt.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 IsError: true; only PendingOf tells them apart.
package main
import ( "fmt" "log"
toolnexus "github.com/muthuishere/toolnexus/golang")
func classify(res toolnexus.ToolResult) string { if req := toolnexus.PendingOf(res); req != nil { return "suspended: " + req.Kind } if res.IsError { return "failed: " + res.Output } return "ok: " + res.Output}
func main() { suspended := toolnexus.AuthRequired("https://example.com/login", "") failed := toolnexus.ToolResult{Output: "connection refused", IsError: true} ok := toolnexus.ToolResult{Output: "42"}
got := []string{classify(suspended), classify(failed), classify(ok)} want := []string{"suspended: authorization", "failed: connection refused", "ok: 42"} for i := range want { if got[i] != want[i] { log.Fatalf("case %d: got %q want %q", i, got[i], want[i]) } }
fmt.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”PendingOf 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.
package main
import ( "context" "fmt" "log" "net/http" "net/http/httptest"
toolnexus "github.com/muthuishere/toolnexus/golang")
func main() { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"choices":[{"message":{"role":"assistant","content":null,"tool_calls":[{"id":"c1","type":"function","function":{"name":"pick_env","arguments":"{}"}}]}}],"usage":{"prompt_tokens":2,"completion_tokens":2,"total_tokens":4}}`)) })) defer srv.Close()
var lastResult toolnexus.ToolResult pickEnv := toolnexus.Tool{ Name: "pick_env", Description: "asks which environment", InputSchema: toolnexus.JSONSchema{"type": "object", "properties": map[string]any{}}, Source: toolnexus.SourceCustom, Execute: func(_ map[string]any, _ *toolnexus.ToolContext) (toolnexus.ToolResult, error) { lastResult = toolnexus.Pending(toolnexus.Request{Kind: "input", Prompt: "which env?"}) return lastResult, nil }, }
tk, err := toolnexus.CreateToolkit(context.Background(), toolnexus.Options{ExtraTools: []toolnexus.Tool{pickEnv}}) if err != nil { log.Fatal(err) } defer tk.Close()
client := toolnexus.CreateClient(toolnexus.ClientOptions{BaseURL: srv.URL, Style: toolnexus.StyleOpenAI, Model: "gpt-4o-mini", APIKey: "test-key"}) res, err := client.Run(context.Background(), "deploy", tk) if err != nil { log.Fatal(err) } if res.Status != "pending" { log.Fatalf("expected a durable halt, got %q", res.Status) }
// PendingOf on the tool's own ToolResult, and RunResult.Pending, describe the same suspension. fromTool := toolnexus.PendingOf(lastResult) if fromTool == nil || res.Pending == nil { log.Fatal("expected both views to see the suspension") } if fromTool.ID != res.Pending.ID || fromTool.Prompt != res.Pending.Prompt { log.Fatalf("expected matching requests: tool=%+v run=%+v", fromTool, res.Pending) }
fmt.Println("ok:", fromTool.Kind, "-", fromTool.Prompt, "id matches:", fromTool.ID == res.Pending.ID)}See also
Section titled “See also”Pending— Return a Pending from a tool to park the run until someone answers.AuthRequired— The auth-shaped suspension: hand back a URL, resume once the user has granted access.WaitFor— The single hook where the host resolves a suspension — in-process prompt or durable queue, same contract.Client.Resume— The answer-carrying entry point: resume a parked run in a different process.