AnswerOutput
Go · package github.com/muthuishere/toolnexus/golang · SPEC §10 · golang/relay.go
func AnswerOutput(id, output string) Answer
type Answer struct { ID string `json:"id"` // echoes the pending Request.ID Ok bool `json:"ok"` // satisfied, vs declined/aborted/expired Data map[string]any `json:"data,omitempty"` // kind-specific payload Reason string `json:"reason,omitempty"`}AnswerOutput(id, output) builds the Answer for a single outstanding relayed tool call:
Ok: true and the tool’s output stored under the one key (RelayOutputKey)
the resume path reads. It exists so a caller never has to hand-build
Answer{ID: id, Ok: true, Data: map[string]any{"output": output}} and get that one required key
wrong or misspelled — which used to be exactly how a malformed answer got Ok: true with no
determinable result and turned into a fabricated tool result reaching the model (issue #89,
ADR 0026).
When to use it
Section titled “When to use it”Reach for AnswerOutput whenever you’re the host resolving a suspended
RelayTool call with the tool’s real output — inline through
WaitFor, or out-of-band through Client.RunWithAnswer/AskWithAnswer.
It is the success half of the pair; use AnswerDeclined
instead when the human refused rather than executed the call.
Why this and not the alternative
Section titled “Why this and not the alternative”Examples
Section titled “Examples”1. The smallest useful call — build the Answer and inspect its shape
Section titled “1. The smallest useful call — build the Answer and inspect its shape”package main
import ( "fmt" "log"
toolnexus "github.com/muthuishere/toolnexus/golang")
func main() { ans := toolnexus.AnswerOutput("req-1", "staging")
if ans.ID != "req-1" || !ans.Ok { log.Fatalf("unexpected answer: %+v", ans) } out, ok := ans.Data[toolnexus.RelayOutputKey].(string) if !ok || out != "staging" { log.Fatalf("expected output %q under %q, got %+v", "staging", toolnexus.RelayOutputKey, ans.Data) }
fmt.Println("ok:", ans.ID, out)}2. The realistic case — halt on a relayed tool call, then resume with the host’s real output
Section titled “2. The realistic case — halt on a relayed tool call, then resume with the host’s real output”package main
import ( "context" "fmt" "log" "net/http" "net/http/httptest"
toolnexus "github.com/muthuishere/toolnexus/golang")
func main() { turn := 0 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { turn++ w.Header().Set("Content-Type", "application/json") if turn == 1 { _, _ = w.Write([]byte(`{"choices":[{"message":{"role":"assistant","content":null,"tool_calls":[{"id":"c1","type":"function","function":{"name":"weather","arguments":"{}"}}]}}],"usage":{"prompt_tokens":2,"completion_tokens":2,"total_tokens":4}}`)) return } _, _ = w.Write([]byte(`{"choices":[{"message":{"role":"assistant","content":"it is sunny"}}],"usage":{"prompt_tokens":2,"completion_tokens":1,"total_tokens":3}}`)) })) defer srv.Close()
// A relay tool declares the call; only the HOST can actually execute it. weather := toolnexus.RelayTool("weather", "look up the weather", nil) tk, err := toolnexus.CreateToolkit(context.Background(), toolnexus.Options{Builtins: false, ExtraTools: []toolnexus.Tool{weather}}) if err != nil { log.Fatal(err) } defer tk.Close()
client := toolnexus.CreateClient(toolnexus.ClientOptions{BaseURL: srv.URL, Style: toolnexus.StyleOpenAI, Model: "stub", APIKey: "k"})
halted, err := client.Run(context.Background(), "what's the weather?", tk) if err != nil { log.Fatal(err) } if halted.Status != "pending" { log.Fatalf("expected a durable halt, got %q", halted.Status) }
// The host executed the real lookup out-of-band; hand its output back. resumed, err := client.RunWithAnswer(context.Background(), tk, halted.Messages, *halted.Pending, toolnexus.AnswerOutput(halted.Pending.ID, "sunny, 24C")) if err != nil { log.Fatal(err) } if resumed.Status != "done" { log.Fatalf("expected done, got %q", resumed.Status) }
fmt.Println("ok:", resumed.Text)}3. The full surface — a malformed hand-built Answer errors instead of fabricating a result
Section titled “3. The full surface — a malformed hand-built Answer errors instead of fabricating a result”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":"weather","arguments":"{}"}}]}}],"usage":{"prompt_tokens":2,"completion_tokens":2,"total_tokens":4}}`)) })) defer srv.Close()
weather := toolnexus.RelayTool("weather", "look up the weather", nil) tk, err := toolnexus.CreateToolkit(context.Background(), toolnexus.Options{Builtins: false, ExtraTools: []toolnexus.Tool{weather}}) if err != nil { log.Fatal(err) } defer tk.Close()
client := toolnexus.CreateClient(toolnexus.ClientOptions{BaseURL: srv.URL, Style: toolnexus.StyleOpenAI, Model: "stub", APIKey: "k"})
halted, err := client.Run(context.Background(), "what's the weather?", tk) if err != nil { log.Fatal(err) }
// Hand-built, wrong key ("value" instead of "output") — NOT what AnswerOutput builds. badAnswer := toolnexus.Answer{ID: halted.Pending.ID, Ok: true, Data: map[string]any{"value": "sunny"}} _, err = client.RunWithAnswer(context.Background(), tk, halted.Messages, *halted.Pending, badAnswer) if err == nil { log.Fatal("a malformed Ok:true answer must error to the host, never resume silently") }
fmt.Println("ok: rejected malformed answer:", err)}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.PendingOf— Detect that a RunResult is parked rather than finished, and get the Request that parked it.AnswerDeclined— The refusal counterpart:Ok: falseplus a reason, when the human said no rather than supplied a result.Runtime.Resume/RunWithAnswer— Where a builtAnsweractually gets applied to a halted run.