package bot import ( "encoding/json" "io" "net/http" "net/http/httptest" "strings" "sync" "testing" ) // TestExecTaskRetriesWithoutThinking pins the empty-result fallback: adaptive // thinking can spend the whole 4096-token budget before any text is written // (stop_reason max_tokens — seen live on a long feed digest), and the run // must retry once with thinking off instead of reporting an empty edition. func TestExecTaskRetriesWithoutThinking(t *testing.T) { var mu sync.Mutex var bodies []string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { b, _ := io.ReadAll(r.Body) mu.Lock() bodies = append(bodies, string(b)) call := len(bodies) mu.Unlock() if call == 1 { // The whole budget went on thinking: no text, truncated. json.NewEncoder(w).Encode(map[string]any{ "content": []map[string]any{{"type": "thinking", "thinking": "hmm…", "signature": "sig"}}, "stop_reason": "max_tokens", }) return } json.NewEncoder(w).Encode(map[string]any{ "content": []map[string]any{{"type": "text", "text": "the edition"}}, "stop_reason": "end_turn", }) })) defer srv.Close() t.Setenv("ANTHROPIC_BASE_URL", srv.URL) r := &Runtime{Session: &Session{Name: "Eleven"}, ai: newAnthropicClient("test-key", nil), Logf: t.Logf} res, err := r.execTask("Test Bot", &TaskSpec{Prompt: "write the edition"}) if err != nil { t.Fatal(err) } if res.text != "the edition" { t.Fatalf("text = %q, want the retry's edition", res.text) } if len(bodies) != 2 { t.Fatalf("want 2 model calls (attempt + thinking-free retry), got %d", len(bodies)) } if !strings.Contains(bodies[0], `"thinking":{"type":"adaptive"}`) { t.Error("first attempt should run with adaptive thinking") } if strings.Contains(bodies[1], `"thinking":{"type":"adaptive"}`) { t.Error("the retry must run with thinking off") } }