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