Skip to content

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).

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.

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)
}
  • 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: false plus a reason, when the human said no rather than supplied a result.
  • Runtime.Resume / RunWithAnswer — Where a built Answer actually gets applied to a halted run.