Skip to content

ToolResult

Go · module github.com/muthuishere/toolnexus/golang · SPEC §1 · golang/types.go

type ToolResult struct {
Output string `json:"output"`
IsError bool `json:"isError"`
Metadata map[string]any `json:"metadata,omitempty"`
}

The first half of what every Execute returns. Three fields, and the whole tool-calling loop is built on them: Output is the text handed back to the model, IsError says whether the call failed, and Metadata is free-form — except for one reserved key that turns a result into a suspension.

Every time you write a tool. It is the first return value of Tool.Execute, so you construct one on every code path.

Output is always a string — it is what the model reads. Serialize structured data yourself (json.Marshal) rather than expecting the loop to do it.

package main
import (
"fmt"
"log"
toolnexus "github.com/muthuishere/toolnexus/golang"
)
var config = map[string]string{"region": "eu-west-1"}
func readConfig(key string) toolnexus.ToolResult {
v, ok := config[key]
if !ok {
// Recoverable: the model can read this and try another key.
return toolnexus.ToolResult{Output: "No such config key: " + key, IsError: true}
}
return toolnexus.ToolResult{Output: v, IsError: false}
}
func main() {
found := readConfig("region")
if found.Output != "eu-west-1" || found.IsError {
log.Fatalf("unexpected: %+v", found)
}
missing := readConfig("nope")
if !missing.IsError {
log.Fatal("expected IsError")
}
fmt.Println("ok:", found.Output, "|", missing.Output)
}

Output must be a string, so serialize deliberately. Metadata rides alongside for your code — the model never sees it, which makes it the right place for bookkeeping.

package main
import (
"fmt"
"log"
"strings"
toolnexus "github.com/muthuishere/toolnexus/golang"
)
func search(q string) toolnexus.ToolResult {
hits := []struct {
ID int
Title string
}{
{1, "Getting started"},
{2, "Advanced usage"},
}
var lines []string
var ids []int
for _, h := range hits {
lines = append(lines, fmt.Sprintf("#%d %s", h.ID, h.Title))
ids = append(ids, h.ID)
}
return toolnexus.ToolResult{
// The model reads this. Make it legible, not just valid.
Output: strings.Join(lines, "\n"),
IsError: false,
// Your code reads this. The model never sees it.
Metadata: map[string]any{"title": "search: " + q, "count": len(hits), "ids": ids},
}
}
func main() {
res := search("usage")
if res.Metadata["count"] != 2 {
log.Fatalf("unexpected count: %v", res.Metadata["count"])
}
if !strings.Contains(res.Output, "Advanced usage") {
log.Fatal("expected the second hit in output")
}
fmt.Println("ok:", res.Metadata["title"])
}

3. The reserved key — Metadata["pending"] is a suspension

Section titled “3. The reserved key — Metadata["pending"] is a suspension”

Metadata is free-form with one exception. A pending key holding a Request means “this tool cannot finish until something out-of-band happens” — the loop parks the run instead of returning. You rarely write this by hand; Pending builds it for you.

package main
import (
"fmt"
"log"
toolnexus "github.com/muthuishere/toolnexus/golang"
)
func main() {
// Pending() returns a ToolResult carrying Metadata["pending"] = Request.
res := toolnexus.Pending(toolnexus.Request{Kind: "input", Prompt: "Which environment?"})
if !res.IsError {
log.Fatal("a parked call is not a success")
}
req := toolnexus.PendingOf(res)
if req == nil {
log.Fatal("PendingOf should read the suspension back off the result")
}
if req.Kind != "input" || req.Prompt != "Which environment?" {
log.Fatalf("unexpected request: %+v", req)
}
if req.ID == "" {
log.Fatal("an id is generated as the correlation key")
}
// AuthRequired is sugar for the login case. Go takes the prompt explicitly —
// pass "" to accept the default ("Authorization required to continue").
auth := toolnexus.AuthRequired("https://example.com/login", "")
authReq := toolnexus.PendingOf(auth)
if authReq.Kind != "authorization" || authReq.URL != "https://example.com/login" {
log.Fatalf("unexpected auth request: %+v", authReq)
}
// An ordinary result has no suspension.
if toolnexus.PendingOf(toolnexus.ToolResult{Output: "done"}) != nil {
log.Fatal("expected no suspension on a plain result")
}
fmt.Println("ok:", req.Kind, "|", authReq.Kind)
}
Field Type What it is
Output string The text handed to the model. Always a string — serialize structured data yourself.
IsError bool Whether the call failed. Fed back to the model, distinct from a returned error.
Metadata map[string]any Free-form, for your code. Reserved: pending holds a §10 Request.