-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathenvelope.go
More file actions
243 lines (223 loc) · 6.24 KB
/
envelope.go
File metadata and controls
243 lines (223 loc) · 6.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
package protocol
import (
"encoding/json"
"fmt"
"reflect"
"strings"
)
// Envelope is a parsed message ready for type-switching.
type Envelope struct {
Type string
Payload any
}
func (e Envelope) DecodePayload() (any, error) {
payload, err := payloadForType(e.Type)
if err != nil {
return nil, err
}
switch raw := e.Payload.(type) {
case json.RawMessage:
if err := json.Unmarshal(raw, payload); err != nil {
return nil, fmt.Errorf("decode %s: %w", e.Type, err)
}
case []byte:
if err := json.Unmarshal(raw, payload); err != nil {
return nil, fmt.Errorf("decode %s: %w", e.Type, err)
}
default:
return e.Payload, nil
}
return payload, nil
}
// ParseEnvelope reads raw JSON, looks at the type field, and unmarshals
// into the correct concrete struct.
func ParseEnvelope(data []byte) (*Envelope, error) {
return parseEnvelope(data)
}
// ParseEnvelopeWithLimits validates frame size, JSON nesting depth, object
// field counts, and array element counts before parsing the envelope.
func ParseEnvelopeWithLimits(data []byte, limits EnvelopeLimits) (*Envelope, error) {
if err := ValidateEnvelopeFrame(data, limits); err != nil {
return nil, err
}
return parseEnvelope(data)
}
func parseEnvelope(data []byte) (*Envelope, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return nil, fmt.Errorf("envelope: %w", err)
}
rawType, ok := raw["type"]
if !ok {
return nil, fmt.Errorf("envelope: missing type")
}
var msgType string
if err := json.Unmarshal(rawType, &msgType); err != nil {
return nil, fmt.Errorf("envelope type: %w", err)
}
payload, err := payloadForType(msgType)
if err != nil {
return nil, err
}
if err := populatePayload(payload, raw); err != nil {
return nil, fmt.Errorf("unmarshal %s: %w", msgType, err)
}
return &Envelope{
Type: msgType,
Payload: payload,
}, nil
}
func payloadForType(msgType string) (any, error) {
switch msgType {
case MsgTypeTask:
return &Task{}, nil
case MsgTypeTaskLifecycle:
return &TaskLifecycle{}, nil
case MsgTypeStop:
return &Stop{}, nil
case MsgTypePermissionResponse:
return &PermissionResponse{}, nil
case MsgTypeQuestionResponse:
return &QuestionResponse{}, nil
case MsgTypeBrowseDir:
return &BrowseDir{}, nil
case MsgTypeReadFile:
return &ReadFile{}, nil
case MsgTypeMkDir:
return &MkDir{}, nil
case MsgTypeMkDirResult:
return &MkDirResult{}, nil
case MsgTypeListSkills:
return &ListSkills{}, nil
case MsgTypeListSkillsResult:
return &ListSkillsResult{}, nil
case MsgTypeCompactRequest:
return &CompactRequest{}, nil
case MsgTypeContextStatsRequest:
return &ContextStatsRequest{}, nil
case MsgTypeStream:
return &Stream{}, nil
case MsgTypeTaskStarted:
return &TaskStarted{}, nil
case MsgTypeTaskComplete:
return &TaskComplete{}, nil
case MsgTypeTaskError:
return &TaskError{}, nil
case MsgTypeTaskCancelled:
return &TaskCancelled{}, nil
case MsgTypePermissionRequest:
return &PermissionRequest{}, nil
case MsgTypeQuestion:
return &Question{}, nil
case MsgTypeHeartbeat:
return &Heartbeat{}, nil
case MsgTypeBrowseDirResult:
return &BrowseDirResult{}, nil
case MsgTypeReadFileResult:
return &ReadFileResult{}, nil
case MsgTypeContextStats:
return &ContextStats{}, nil
case MsgTypeCompactStatus:
return &CompactStatus{}, nil
case MsgTypeHello:
return &Hello{}, nil
case MsgTypeWelcome:
return &Welcome{}, nil
case MsgTypeMachineStatus:
return &MachineStatus{}, nil
case MsgTypePreviewOpen:
return &PreviewOpen{}, nil
case MsgTypePreviewOpenResult:
return &PreviewOpenResult{}, nil
case MsgTypePreviewClose:
return &PreviewClose{}, nil
case MsgTypePreviewHTTPRequest:
return &PreviewHTTPRequest{}, nil
case MsgTypePreviewHTTPResponseHead:
return &PreviewHTTPResponseHead{}, nil
case MsgTypePreviewStreamChunk:
return &PreviewStreamChunk{}, nil
case MsgTypePreviewStreamCancel:
return &PreviewStreamCancel{}, nil
case MsgTypePreviewWebSocketOpen:
return &PreviewWebSocketOpen{}, nil
case MsgTypePreviewWebSocketOpenResult:
return &PreviewWebSocketOpenResult{}, nil
case MsgTypePreviewWebSocketData:
return &PreviewWebSocketData{}, nil
case MsgTypePreviewWebSocketClose:
return &PreviewWebSocketClose{}, nil
case MsgTypeLocalServerDetected:
return &LocalServerDetected{}, nil
case MsgTypeTerminalOpen:
return &TerminalOpen{}, nil
case MsgTypeTerminalOpened:
return &TerminalOpened{}, nil
case MsgTypeTerminalInput:
return &TerminalInput{}, nil
case MsgTypeTerminalOutput:
return &TerminalOutput{}, nil
case MsgTypeTerminalSnapshot:
return &TerminalSnapshot{}, nil
case MsgTypeTerminalResize:
return &TerminalResize{}, nil
case MsgTypeTerminalClose:
return &TerminalClose{}, nil
case MsgTypeTerminalExit:
return &TerminalExit{}, nil
case MsgTypeTerminalError:
return &TerminalError{}, nil
case MsgTypeAgentTerminalStarted:
return &AgentTerminalStarted{}, nil
case MsgTypeAgentTerminalUpdated:
return &AgentTerminalUpdated{}, nil
case MsgTypeAgentTerminalAttach:
return &AgentTerminalAttach{}, nil
case MsgTypeAgentTerminalSnapshotRequest:
return &AgentTerminalSnapshotRequest{}, nil
default:
return nil, fmt.Errorf("unknown message type: %q", msgType)
}
}
func populatePayload(dst any, raw map[string]json.RawMessage) error {
value := reflect.ValueOf(dst)
if value.Kind() != reflect.Pointer || value.Elem().Kind() != reflect.Struct {
return fmt.Errorf("payload must be a pointer to a struct, got %T", dst)
}
return populateStruct(value.Elem(), raw)
}
func populateStruct(dst reflect.Value, raw map[string]json.RawMessage) error {
dstType := dst.Type()
for i := 0; i < dstType.NumField(); i++ {
fieldType := dstType.Field(i)
if !fieldType.IsExported() {
continue
}
fieldName, skip := jsonFieldName(fieldType)
if skip {
continue
}
fieldData, ok := raw[fieldName]
if !ok {
continue
}
if err := json.Unmarshal(fieldData, dst.Field(i).Addr().Interface()); err != nil {
return fmt.Errorf("%s: %w", fieldName, err)
}
}
return nil
}
func jsonFieldName(field reflect.StructField) (string, bool) {
tag := field.Tag.Get("json")
if tag == "-" {
return "", true
}
if tag != "" {
name, _, _ := strings.Cut(tag, ",")
if name == "" {
return field.Name, false
}
return name, false
}
return field.Name, false
}