返回 DeepSeek-Reasonix
errors.go
根目录 / internal / extension / protocol / errors.go
1 package protocol
2
3 import (
4 "fmt"
5 "sort"
6
7 "reasonix/internal/extension/rpcwire"
8 )
9
10 // DomainErrorCode is the JSON-RPC code every extension domain error uses on
11 // the wire. The structured ProtocolErrorData reason distinguishes them.
12 // Errors that map onto a standard JSON-RPC failure (malformed request,
13 // unknown method, invalid params, internal) use the standard codes instead.
14 const DomainErrorCode = -32000
15
16 // ErrorReason is the stable wire reason string carried in ProtocolErrorData.
17 type ErrorReason string
18
19 const (
20 ErrProtocolError ErrorReason = "protocol_error"
21 ErrUnknownMethod ErrorReason = "unknown_method"
22 ErrInvalidParams ErrorReason = "invalid_params"
23 ErrFrameTooLarge ErrorReason = "frame_too_large"
24 ErrContentRefExpired ErrorReason = "content_ref_expired"
25 ErrUnsupportedVersion ErrorReason = "unsupported_version"
26 ErrCapabilityNotDeclared ErrorReason = "capability_not_declared"
27 ErrShutdownTimeout ErrorReason = "shutdown_timeout"
28 ErrStreamGap ErrorReason = "stream_gap"
29 ErrStreamCancelled ErrorReason = "stream_cancelled"
30 ErrProviderFailed ErrorReason = "provider_failed"
31 ErrProviderInterrupted ErrorReason = "provider_interrupted"
32 ErrInterceptTimeout ErrorReason = "intercept_timeout"
33 ErrInternal ErrorReason = "internal"
34 )
35
36 type errorSpec struct {
37 Code int
38 Message string
39 Retryable bool
40 }
41
42 // frozenErrorSpecs is the frozen error table. Adding an entry is a conscious
43 // protocol change: the table is frozen into the generated schema document and
44 // pinned by tests.
45 var frozenErrorSpecs = map[ErrorReason]errorSpec{
46 ErrProtocolError: {rpcwire.ErrInvalidRequest, "The extension protocol frame or envelope is invalid.", false},
47 ErrUnknownMethod: {rpcwire.ErrMethodNotFound, "The method is not registered in Extension Protocol v1.", false},
48 ErrInvalidParams: {rpcwire.ErrInvalidParams, "The params do not match the registered method schema.", false},
49 ErrFrameTooLarge: {DomainErrorCode, "The frame exceeds the frozen protocol size limit.", false},
50 ErrContentRefExpired: {DomainErrorCode, "The referenced content has expired.", true},
51 ErrUnsupportedVersion: {DomainErrorCode, "The peer's extension protocol version is not supported.", false},
52 ErrCapabilityNotDeclared: {DomainErrorCode, "The extension used a capability its manifest did not declare.", false},
53 ErrShutdownTimeout: {DomainErrorCode, "The extension did not shut down within the requested timeout.", true},
54 ErrStreamGap: {DomainErrorCode, "A provider stream chunk is missing from the ordered sequence.", true},
55 ErrStreamCancelled: {DomainErrorCode, "The provider stream was cancelled.", false},
56 ErrProviderFailed: {DomainErrorCode, "The extension provider stream failed.", true},
57 ErrProviderInterrupted: {DomainErrorCode, "The extension provider stream was interrupted.", true},
58 ErrInterceptTimeout: {DomainErrorCode, "The extension did not answer an intercept within its timeout.", true},
59 ErrInternal: {rpcwire.ErrInternal, "An internal extension protocol error occurred.", true},
60 }
61
62 // ProtocolErrorData is the structured JSON-RPC error data for extension
63 // domain errors.
64 type ProtocolErrorData struct {
65 Reason ErrorReason `json:"reason"`
66 Retryable bool `json:"retryable"`
67 }
68
69 // Validate checks the data against the frozen error table.
70 func (d ProtocolErrorData) Validate() error {
71 spec, ok := frozenErrorSpecs[d.Reason]
72 if !ok {
73 return fmt.Errorf("unknown extension error reason %q", d.Reason)
74 }
75 if d.Retryable != spec.Retryable {
76 return fmt.Errorf("retryable must match the frozen error table for %q", d.Reason)
77 }
78 return nil
79 }
80
81 // ProtocolError is the extension protocol's structured error, the equivalent
82 // of Remote's RemoteError. Message is the frozen generic text; details that
83 // could carry sidecar internals never cross the wire.
84 type ProtocolError struct {
85 Reason ErrorReason
86 Message string
87 }
88
89 func (e *ProtocolError) Error() string {
90 if e == nil {
91 return ""
92 }
93 return e.Message
94 }
95
96 // RPCError converts to the transport error object with the frozen JSON-RPC
97 // code and structured data.
98 func (e *ProtocolError) RPCError() *rpcwire.RPCError {
99 if e == nil {
100 return &rpcwire.RPCError{Code: rpcwire.ErrInternal, Message: "internal error"}
101 }
102 spec := frozenErrorSpecs[e.Reason]
103 return &rpcwire.RPCError{
104 Code: spec.Code,
105 Message: e.Message,
106 Data: ProtocolErrorData{Reason: e.Reason, Retryable: spec.Retryable},
107 }
108 }
109
110 // NewProtocolError builds the frozen error for a reason.
111 func NewProtocolError(reason ErrorReason) (*ProtocolError, error) {
112 spec, ok := frozenErrorSpecs[reason]
113 if !ok {
114 return nil, fmt.Errorf("protocol: unknown extension error reason %q", reason)
115 }
116 return &ProtocolError{Reason: reason, Message: spec.Message}, nil
117 }
118
119 // MustProtocolError is NewProtocolError for reasons known to be frozen.
120 func MustProtocolError(reason ErrorReason) *ProtocolError {
121 errValue, err := NewProtocolError(reason)
122 if err != nil {
123 panic(err)
124 }
125 return errValue
126 }
127
128 // ErrorContract is the schema- and documentation-visible form of one frozen
129 // error table entry.
130 type ErrorContract struct {
131 Reason ErrorReason `json:"reason"`
132 JSONRPCCode int `json:"jsonRpcCode"`
133 Message string `json:"message"`
134 Retryable bool `json:"retryable"`
135 }
136
137 // ErrorContracts returns the frozen error table sorted by reason.
138 func ErrorContracts() []ErrorContract {
139 out := make([]ErrorContract, 0, len(frozenErrorSpecs))
140 for reason, spec := range frozenErrorSpecs {
141 out = append(out, ErrorContract{reason, spec.Code, spec.Message, spec.Retryable})
142 }
143 sort.Slice(out, func(i, j int) bool { return out[i].Reason < out[j].Reason })
144 return out
145 }
146
146 lines GO