Repository navigation
Expand file tree
/
Copy pathprotocol.go
More file actions
302 lines (256 loc) · 10.5 KB
/
Copy pathprotocol.go
File metadata and controls
302 lines (256 loc) · 10.5 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
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
// Package codex is the agent_kind="codex" adapter. It drives the
// OpenAI Codex CLI (codex-rs / @openai/codex) via `codex app-server --stdio`,
// speaking the JSON-RPC 2.0 protocol that the app-server exposes over
// stdio.
//
// Codex uses an app-server transport:
//
// 1. The wire protocol is JSON-RPC (request / response / notification /
// server-request) rather than NDJSON event-stream. See rpc.go.
//
// 2. Multi-turn context lives in a Codex "thread" identified by the
// thread_id returned in the first `thread/started` notification.
// The daemon stamps that id into DonePayload.Metadata["agent_session_id"]
// so the connector's RememberSession path persists it.
// Subsequent turns spawn a fresh app-server and call `thread/resume`
// with that id to graft the prior turn's context back in.
package codex
import "encoding/json"
import "fmt"
// JsonRpcVersion is the JSON-RPC 2.0 marker carried on every outbound
// frame. Inbound frames omit the field per Codex's app-server convention,
// so the parser does not enforce it on responses.
const JsonRpcVersion = "2.0"
// ---------------------------------------------------------------------------
// JSON-RPC envelopes (over stdio NDJSON)
// ---------------------------------------------------------------------------
// JsonRpcRequest is an outbound RPC call (client → codex). id is a
// daemon-minted 16-hex string; an empty id marks a notification (no
// response expected).
type JsonRpcRequest struct {
JsonRpc string `json:"jsonrpc"`
ID string `json:"id,omitempty"`
Method string `json:"method"`
Params any `json:"params,omitempty"`
}
// JsonRpcResponse is an outbound reply to a server-initiated request.
// Either Result or Error must be non-nil.
type JsonRpcResponse struct {
JsonRpc string `json:"jsonrpc"`
ID any `json:"id"`
Result any `json:"result,omitempty"`
Error *JsonRpcError `json:"error,omitempty"`
}
// JsonRpcError carries a structured failure reply. Codes follow the
// JSON-RPC 2.0 spec (-32601 method-not-found, -32603 internal error,
// etc.).
type JsonRpcError struct {
Code int `json:"code"`
Message string `json:"message"`
Data any `json:"data,omitempty"`
}
func (e *JsonRpcError) Error() string { return fmt.Sprintf("%d %s", e.Code, e.Message) }
// ---------------------------------------------------------------------------
// initialize handshake
// ---------------------------------------------------------------------------
// InitializeCapabilities mirrors codex-rs/app-server/src/protocol.rs.
// experimentalApi=true opts into the experimental thread/* fields and
// notification stream.
type InitializeCapabilities struct {
ExperimentalAPI bool `json:"experimentalApi"`
RequestAttestation bool `json:"requestAttestation"`
OptOutMethods *[]string `json:"optOutNotificationMethods,omitempty"`
}
type InitializeClientInfo struct {
Name string `json:"name"`
Version string `json:"version"`
}
type InitializeParams struct {
ClientInfo InitializeClientInfo `json:"clientInfo"`
Capabilities *InitializeCapabilities `json:"capabilities"`
}
type InitializeResult struct {
UserAgent string `json:"userAgent"`
CodexHome string `json:"codexHome"`
PlatformFamily string `json:"platformFamily,omitempty"`
PlatformOs string `json:"platformOs,omitempty"`
}
type SkillsExtraRootsSetParams struct {
ExtraRoots []string `json:"extraRoots"`
}
// ---------------------------------------------------------------------------
// thread/start, thread/resume, thread/list
// ---------------------------------------------------------------------------
type ThreadStartParams struct {
HistoryMode string `json:"historyMode,omitempty"`
Cwd string `json:"cwd"`
Model string `json:"model,omitempty"`
ModelProvider string `json:"modelProvider,omitempty"`
ApprovalPolicy string `json:"approvalPolicy"`
// Sandbox is the kebab-case mode string. Codex silently falls back to
// read-only for the legacy sandboxPolicy object.
Sandbox string `json:"sandbox"`
DeveloperInstructions string `json:"developerInstructions,omitempty"`
RuntimeWorkspaceRoots []string `json:"runtimeWorkspaceRoots,omitempty"`
DynamicTools []dynamicFunctionTool `json:"dynamicTools,omitempty"`
}
type Thread struct {
ID string `json:"id"`
Cwd string `json:"cwd,omitempty"`
Preview string `json:"preview,omitempty"`
UpdatedAt int64 `json:"updatedAt,omitempty"`
CreatedAt int64 `json:"createdAt,omitempty"`
Status any `json:"status,omitempty"`
Path string `json:"path,omitempty"`
CLIVersion string `json:"cliVersion,omitempty"`
}
type ThreadStartResult struct {
Thread Thread `json:"thread"`
Model string `json:"model,omitempty"`
}
type ThreadResumeParams struct {
Cwd string `json:"cwd,omitempty"`
DeveloperInstructions string `json:"developerInstructions"`
ThreadID string `json:"threadId"`
ApprovalPolicy string `json:"approvalPolicy"`
Sandbox string `json:"sandbox"`
}
// ---------------------------------------------------------------------------
// turn/start, turn/interrupt
// ---------------------------------------------------------------------------
// UserInputType discriminates a UserInput element. Codex accepts mixed
// arrays of text + image entries on a single turn.
type UserInputType string
const (
UserInputText UserInputType = "text"
UserInputLocalImage UserInputType = "localImage"
UserInputRemoteImg UserInputType = "image"
)
// UserInput is one element of TurnStartParams.Input. Each variant uses a
// distinct field set; producers must populate only the fields that
// match Type.
type UserInput struct {
Type UserInputType `json:"type"`
// Text variant
Text string `json:"text,omitempty"`
TextElements []any `json:"text_elements,omitempty"`
// LocalImage variant
Path string `json:"path,omitempty"`
// Image (remote URL) variant
URL string `json:"url,omitempty"`
}
type TurnStartParams struct {
ThreadID string `json:"threadId"`
Input []UserInput `json:"input"`
// OutputSchema constrains this Turn's final assistant message natively.
OutputSchema json.RawMessage `json:"outputSchema,omitempty"`
}
type TurnInterruptParams struct {
ThreadID string `json:"threadId"`
TurnID string `json:"turnId"`
}
type TurnUsage struct {
observed bool
complete bool
ReasoningOutputTokens int `json:"reasoningOutputTokens,omitempty"`
InputTokens int `json:"inputTokens,omitempty"`
OutputTokens int `json:"outputTokens,omitempty"`
CachedInputTokens int `json:"cachedInputTokens,omitempty"`
CacheReadInputTokens int `json:"cacheReadInputTokens,omitempty"`
TotalTokens int `json:"totalTokens,omitempty"`
}
type Turn struct {
CompletedAt *int64 `json:"completedAt,omitempty"`
ID string `json:"id"`
Usage *TurnUsage `json:"usage,omitempty"`
Status string `json:"status,omitempty"`
// Failed Turns carry the native provider error here.
Error *TurnError `json:"error,omitempty"`
}
// CodexErrorInfo is a native enum with string and object variants.
type TurnError struct {
Message string `json:"message,omitempty"`
CodexErrorInfo json.RawMessage `json:"codexErrorInfo,omitempty"`
AdditionalDetails *string `json:"additionalDetails,omitempty"`
}
// ---------------------------------------------------------------------------
// ThreadItem (the variants we map; the rest fall through to a generic
// catch-all so codex upgrades that add new item kinds do not break
// parsing).
// ---------------------------------------------------------------------------
// ThreadItem is decoded loosely: parser keeps the raw JSON around so
// future fields can be inspected via a second unmarshal without round-
// tripping every variant through a hand-written struct.
type ThreadItem struct {
Type string `json:"type"`
ID string `json:"id,omitempty"`
// agentMessage / reasoning
Phase string `json:"phase,omitempty"`
Text string `json:"text,omitempty"`
Summary []string `json:"summary,omitempty"`
Content []string `json:"content,omitempty"`
SummaryText string `json:"summary_text,omitempty"`
// commandExecution
Command string `json:"command,omitempty"`
Cwd string `json:"cwd,omitempty"`
ExitCode *int `json:"exitCode,omitempty"`
Status string `json:"status,omitempty"`
// fileChange
Changes []map[string]any `json:"changes,omitempty"`
// mcpToolCall
Server string `json:"server,omitempty"`
Tool string `json:"tool,omitempty"`
Arguments any `json:"arguments,omitempty"`
// dynamicToolCall
Namespace string `json:"namespace,omitempty"`
// webSearch
Query string `json:"query,omitempty"`
}
// ---------------------------------------------------------------------------
// Notification params we subscribe to (param shapes only — method names
// live as constants in session.go's handler registration).
// ---------------------------------------------------------------------------
type ThreadStartedNotification struct {
Thread Thread `json:"thread"`
}
type TurnStartedNotification struct {
ThreadID string `json:"threadId"`
Turn Turn `json:"turn"`
}
type TurnCompletedNotification struct {
ThreadID string `json:"threadId"`
Turn Turn `json:"turn"`
}
type ItemStartedNotification struct {
ThreadID string `json:"threadId"`
TurnID string `json:"turnId"`
Item ThreadItem `json:"item"`
StartedAtMs int64 `json:"startedAtMs,omitempty"`
}
type ItemCompletedNotification struct {
ThreadID string `json:"threadId"`
TurnID string `json:"turnId"`
Item ThreadItem `json:"item"`
CompletedAtMs int64 `json:"completedAtMs,omitempty"`
}
type AgentMessageDeltaNotification struct {
ThreadID string `json:"threadId"`
TurnID string `json:"turnId"`
ItemID string `json:"itemId"`
Delta string `json:"delta"`
}
type ReasoningDeltaNotification = AgentMessageDeltaNotification
type ThreadTokenUsageUpdatedNotification struct {
ThreadID string `json:"threadId"`
TurnID string `json:"turnId"`
Usage *TurnUsage `json:"usage,omitempty"` // Legacy turn-level payload.
TokenUsage *struct {
Total *TurnUsage `json:"total"`
} `json:"tokenUsage,omitempty"`
}
type ErrorNotification struct {
ThreadID string `json:"threadId"`
TurnID string `json:"turnId"`
Error *TurnError `json:"error,omitempty"`
Message string `json:"message,omitempty"`
}