返回 DeepSeek-Reasonix
recovery.go
根目录 / internal / provider / recovery.go
1 package provider
2
3 import (
4 "context"
5 "encoding/json"
6 "errors"
7 "io"
8 "net"
9 "time"
10 )
11
12 type managedRecoveryKey struct{}
13
14 // WithManagedRecovery gives the caller sole ownership of retries. It does not
15 // change wire bytes and is intentionally opt-in for standalone provider users.
16 func WithManagedRecovery(ctx context.Context) context.Context {
17 return context.WithValue(ctx, managedRecoveryKey{}, true)
18 }
19 func ManagedRecovery(ctx context.Context) bool {
20 v, _ := ctx.Value(managedRecoveryKey{}).(bool)
21 return v
22 }
23
24 // RecoveryFailure is local diagnostic state, never part of a model request.
25 type RecoveryFailure struct {
26 Phase string
27 Status int
28 Code string
29 RetryAfter time.Duration
30 Retryable bool
31 }
32
33 func ClassifyRecovery(err error) RecoveryFailure {
34 f := RecoveryFailure{Phase: "unknown"}
35 if err == nil || errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
36 return f
37 }
38 if exhausted := AsRecoveryWaitExhausted(err); exhausted != nil {
39 f.Phase, f.Status, f.Code = exhausted.Phase, exhausted.Status, exhausted.Code
40 return f
41 }
42 if q := AsQuotaError(err); q != nil {
43 f.Phase, f.Status, f.Code = "quota", q.Status, q.Code
44 return f
45 }
46 var auth *AuthError
47 if errors.As(err, &auth) {
48 f.Phase = "auth"
49 return f
50 }
51 if AsContextLimitError(err) != nil || AsOutputLimitError(err) != nil {
52 f.Phase = "limit"
53 return f
54 }
55 if AsReasoningReplayError(err) != nil {
56 f.Phase = "protocol"
57 return f
58 }
59 var api *APIError
60 if errors.As(err, &api) {
61 f.Phase, f.Status, f.RetryAfter = "headers", api.Status, api.RetryAfter
62 var body struct {
63 Error struct {
64 Code string `json:"code"`
65 Type string `json:"type"`
66 } `json:"error"`
67 }
68 if json.Unmarshal([]byte(api.Body), &body) == nil {
69 f.Code = body.Error.Code
70 if f.Code == "" {
71 f.Code = body.Error.Type
72 }
73 }
74 f.Retryable = RetryableStatus(api.Status) || api.Status == 409
75 if api.ShouldRetry == "false" {
76 f.Retryable = false
77 }
78 return f
79 }
80 if IsStreamInterrupted(err) {
81 f.Phase, f.Retryable = "stream", true
82 return f
83 }
84 if errors.Is(err, ErrEmptyResponse) {
85 f.Phase, f.Retryable = "empty", true
86 return f
87 }
88 var ne net.Error
89 if errors.As(err, &ne) || errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) || IsConnReset(err) {
90 f.Phase, f.Retryable = "connect", true
91 }
92 return f
93 }
94
94 lines GO