| 1 | package anthropic |
| 2 | |
| 3 | import ( |
| 4 | "strings" |
| 5 | |
| 6 | "reasonix/internal/provider" |
| 7 | ) |
| 8 | |
| 9 | func (c *client) requiresReceivedReasoning(m provider.Message) bool { |
| 10 | if !c.replaysReceivedThinking() { |
| 11 | return false |
| 12 | } |
| 13 | observed := m.ReasoningContent != "" || m.ReasoningSignature != "" |
| 14 | for _, b := range m.ThinkingBlocks { |
| 15 | observed = observed || b.Thinking != "" || b.Signature != "" || b.Data != "" |
| 16 | } |
| 17 | unsafe := m.ReasoningStatus == "in_progress" || m.ReasoningStatus == "incomplete" |
| 18 | switch m.ReasoningState { |
| 19 | case "", provider.ReasoningEmpty, provider.ReasoningComplete: |
| 20 | default: |
| 21 | unsafe = true |
| 22 | } |
| 23 | if !c.nativeAnthropic { |
| 24 | // Receiving the Anthropic envelope is not evidence that a gateway |
| 25 | // requires Claude signatures. Preserve its actual blocks instead. |
| 26 | return observed || unsafe |
| 27 | } |
| 28 | activity := len(m.ToolCalls) > 0 || len(m.ServerSearch) > 0 |
| 29 | return c.replaysSignedThinking() && (observed || unsafe || activity) |
| 30 | } |
| 31 | |
| 32 | // ConvertReasoningReplay uses Anthropic's ordinary assistant-text representation |
| 33 | // only for complete, unsigned, non-tool history. Tool continuations still need |
| 34 | // their original proof; DeepSeek and unknown gateways keep their own blocks. |
| 35 | func (c *client) ConvertReasoningReplay(m provider.Message) (provider.Message, bool) { |
| 36 | if !c.nativeAnthropic || c.deepseek || !c.replaysSignedThinking() || |
| 37 | m.Role != provider.RoleAssistant || len(m.ToolCalls) > 0 || len(m.ServerSearch) > 0 || |
| 38 | m.ReasoningSignature != "" || len(m.ResponsesItems) > 0 { |
| 39 | return m, false |
| 40 | } |
| 41 | var text []string |
| 42 | if len(m.ThinkingBlocks) > 0 { |
| 43 | for _, b := range m.ThinkingBlocks { |
| 44 | if b.Type != "thinking" || b.Signature != "" || b.Data != "" { |
| 45 | return m, false |
| 46 | } |
| 47 | if b.Thinking != "" { |
| 48 | text = append(text, b.Thinking) |
| 49 | } |
| 50 | } |
| 51 | } else if m.ReasoningContent != "" { |
| 52 | text = append(text, m.ReasoningContent) |
| 53 | } |
| 54 | if len(text) == 0 || strings.TrimSpace(strings.Join(text, "")) == "" { |
| 55 | return m, false |
| 56 | } |
| 57 | if m.Content != "" { |
| 58 | text = append(text, m.Content) |
| 59 | } |
| 60 | m.Content = strings.Join(text, "\n\n") |
| 61 | m.ReasoningContent, m.ReasoningSignature, m.ReasoningID, m.ReasoningStatus = "", "", "", "" |
| 62 | m.ReasoningState, m.ThinkingBlocks = "", nil |
| 63 | return m, true |
| 64 | } |
| 65 |