package bot import ( "encoding/json" "strings" "testing" ) // TestAnthBlockMarshalArglessToolUse pins the echo wire shape: a tool_use the // model called with NO arguments must round-trip with an explicit empty // input (the API 400s without it — "tool_use.input: Field required"), while // other block shapes never grow an input field. func TestAnthBlockMarshalArglessToolUse(t *testing.T) { argless, err := json.Marshal(anthBlock{Type: "tool_use", ID: "tu_1", Name: "issues.list"}) if err != nil { t.Fatal(err) } if !strings.Contains(string(argless), `"input":{}`) { t.Fatalf("argless tool_use must carry an explicit empty input: %s", argless) } withArgs, _ := json.Marshal(anthBlock{Type: "tool_use", ID: "tu_2", Name: "fetch", Input: map[string]any{"url": "https://a.com/"}}) if !strings.Contains(string(withArgs), `"url":"https://a.com/"`) { t.Fatalf("tool args lost: %s", withArgs) } for _, b := range []anthBlock{ {Type: "text", Text: "hi"}, {Type: "thinking", Thinking: "hmm", Signature: "sig"}, {Type: "tool_result", ToolUseID: "tu_1", Content: "ok"}, } { out, _ := json.Marshal(b) if strings.Contains(string(out), `"input"`) { t.Fatalf("%s block grew an input field: %s", b.Type, out) } } } // TestAnthBlockMarshalEmptyThinking pins the other half of the echo trap: a // ZERO-LENGTH thinking block (adaptive thinking emits these on continuation // turns — seen live from the read_app_source loop) must round-trip with an // explicit empty thinking field, or the echo 400s ("thinking.thinking: Field // required") and the whole reply goes silent. Non-thinking blocks never grow // the field. func TestAnthBlockMarshalEmptyThinking(t *testing.T) { empty, err := json.Marshal(anthBlock{Type: "thinking", Signature: "sig"}) if err != nil { t.Fatal(err) } if !strings.Contains(string(empty), `"thinking":""`) { t.Fatalf("empty thinking block must carry an explicit thinking field: %s", empty) } if !strings.Contains(string(empty), `"signature":"sig"`) { t.Fatalf("signature lost from empty thinking block: %s", empty) } full, _ := json.Marshal(anthBlock{Type: "thinking", Thinking: "hmm", Signature: "sig"}) if !strings.Contains(string(full), `"thinking":"hmm"`) { t.Fatalf("thinking text lost: %s", full) } for _, b := range []anthBlock{ {Type: "text", Text: "hi"}, {Type: "tool_result", ToolUseID: "tu_1", Content: "ok"}, } { out, _ := json.Marshal(b) if strings.Contains(string(out), `"thinking"`) { t.Fatalf("%s block grew a thinking field: %s", b.Type, out) } } }