返回 DeepSeek-Reasonix
reasoning_replay_error.go
根目录 / internal / provider / reasoning_replay_error.go
1 package provider
2
3 import (
4 "errors"
5 "net/http"
6 "strings"
7 )
8
9 // ReasoningReplayError is a trusted provider rejection of replayed
10 // thinking/reasoning history: HTTP 400 whose body says the request's
11 // thinking/reasoning blocks must be passed back. Unwrap returns the original
12 // APIError so localization, trace IDs, and telemetry keep working. The body is
13 // never persisted or replayed.
14 type ReasoningReplayError struct {
15 APIError *APIError
16 }
17
18 func (e *ReasoningReplayError) Error() string {
19 if e == nil || e.APIError == nil {
20 return "reasoning replay rejected"
21 }
22 return e.APIError.Error()
23 }
24
25 func (e *ReasoningReplayError) Unwrap() error {
26 if e == nil {
27 return nil
28 }
29 return e.APIError
30 }
31
32 // ParseReasoningReplayError extracts a trusted thinking-replay rejection from
33 // an APIError. The match is deliberately exact — DeepSeek's documented shape is
34 // "The `content[].thinking` in the thinking mode must be passed back to the
35 // API" — so only a 400 naming thinking/reasoning content AND the pass-back
36 // obligation qualifies. Any other 400 (and every other status) returns nil so
37 // the retry budget can never swallow an unrelated client error.
38 func ParseReasoningReplayError(apiErr *APIError) *ReasoningReplayError {
39 if apiErr == nil || apiErr.Status != http.StatusBadRequest {
40 return nil
41 }
42 body := strings.ToLower(apiErr.Body)
43 namesReasoning := strings.Contains(body, "content[].thinking") || strings.Contains(body, "reasoning_content") || strings.Contains(body, "reasoning_text")
44 if !namesReasoning || !strings.Contains(body, "must be passed back") {
45 return nil
46 }
47 return &ReasoningReplayError{APIError: apiErr}
48 }
49
50 // AsReasoningReplayError unwraps err to a trusted thinking-replay rejection,
51 // if any.
52 func AsReasoningReplayError(err error) *ReasoningReplayError {
53 var replay *ReasoningReplayError
54 if err != nil && errors.As(err, &replay) {
55 return replay
56 }
57 return nil
58 }
59
59 lines GO