@a5c-ai/agent-mux-adapters 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/README.md +3 -1
  2. package/dist/agent-mux-remote-adapter.d.ts.map +1 -1
  3. package/dist/agent-mux-remote-adapter.js +4 -0
  4. package/dist/agent-mux-remote-adapter.js.map +1 -1
  5. package/dist/amp-adapter.d.ts +32 -0
  6. package/dist/amp-adapter.d.ts.map +1 -0
  7. package/dist/amp-adapter.js +412 -0
  8. package/dist/amp-adapter.js.map +1 -0
  9. package/dist/babysitter-adapter.d.ts +29 -0
  10. package/dist/babysitter-adapter.d.ts.map +1 -0
  11. package/dist/babysitter-adapter.js +338 -0
  12. package/dist/babysitter-adapter.js.map +1 -0
  13. package/dist/base-adapter-helpers.d.ts +8 -0
  14. package/dist/base-adapter-helpers.d.ts.map +1 -0
  15. package/dist/base-adapter-helpers.js +68 -0
  16. package/dist/base-adapter-helpers.js.map +1 -0
  17. package/dist/base-adapter.d.ts +21 -9
  18. package/dist/base-adapter.d.ts.map +1 -1
  19. package/dist/base-adapter.js +136 -67
  20. package/dist/base-adapter.js.map +1 -1
  21. package/dist/claude-adapter.d.ts +14 -3
  22. package/dist/claude-adapter.d.ts.map +1 -1
  23. package/dist/claude-adapter.js +224 -24
  24. package/dist/claude-adapter.js.map +1 -1
  25. package/dist/claude-agent-sdk-adapter.d.ts +40 -0
  26. package/dist/claude-agent-sdk-adapter.d.ts.map +1 -0
  27. package/dist/claude-agent-sdk-adapter.js +839 -0
  28. package/dist/claude-agent-sdk-adapter.js.map +1 -0
  29. package/dist/claude-code/runtime-hooks/ephemeral-config.d.ts +12 -0
  30. package/dist/claude-code/runtime-hooks/ephemeral-config.d.ts.map +1 -0
  31. package/dist/claude-code/runtime-hooks/ephemeral-config.js +143 -0
  32. package/dist/claude-code/runtime-hooks/ephemeral-config.js.map +1 -0
  33. package/dist/claude-code/runtime-hooks/hook-socket-server.d.ts +10 -0
  34. package/dist/claude-code/runtime-hooks/hook-socket-server.d.ts.map +1 -0
  35. package/dist/claude-code/runtime-hooks/hook-socket-server.js +79 -0
  36. package/dist/claude-code/runtime-hooks/hook-socket-server.js.map +1 -0
  37. package/dist/claude-code/runtime-hooks/lifecycle.d.ts +3 -0
  38. package/dist/claude-code/runtime-hooks/lifecycle.d.ts.map +1 -0
  39. package/dist/claude-code/runtime-hooks/lifecycle.js +24 -0
  40. package/dist/claude-code/runtime-hooks/lifecycle.js.map +1 -0
  41. package/dist/claude-remote-control-adapter.d.ts +43 -0
  42. package/dist/claude-remote-control-adapter.d.ts.map +1 -0
  43. package/dist/claude-remote-control-adapter.js +505 -0
  44. package/dist/claude-remote-control-adapter.js.map +1 -0
  45. package/dist/codex-adapter.d.ts.map +1 -1
  46. package/dist/codex-adapter.js +128 -17
  47. package/dist/codex-adapter.js.map +1 -1
  48. package/dist/codex-sdk-adapter.d.ts +32 -0
  49. package/dist/codex-sdk-adapter.d.ts.map +1 -0
  50. package/dist/codex-sdk-adapter.js +388 -0
  51. package/dist/codex-sdk-adapter.js.map +1 -0
  52. package/dist/codex-sdk-mocks.d.ts +51 -0
  53. package/dist/codex-sdk-mocks.d.ts.map +1 -0
  54. package/dist/codex-sdk-mocks.js +97 -0
  55. package/dist/codex-sdk-mocks.js.map +1 -0
  56. package/dist/codex-websocket-adapter.d.ts +34 -0
  57. package/dist/codex-websocket-adapter.d.ts.map +1 -0
  58. package/dist/codex-websocket-adapter.js +398 -0
  59. package/dist/codex-websocket-adapter.js.map +1 -0
  60. package/dist/codex-websocket-connection.d.ts +78 -0
  61. package/dist/codex-websocket-connection.d.ts.map +1 -0
  62. package/dist/codex-websocket-connection.js +733 -0
  63. package/dist/codex-websocket-connection.js.map +1 -0
  64. package/dist/copilot-adapter.d.ts.map +1 -1
  65. package/dist/copilot-adapter.js +11 -3
  66. package/dist/copilot-adapter.js.map +1 -1
  67. package/dist/cursor-adapter.d.ts +3 -1
  68. package/dist/cursor-adapter.d.ts.map +1 -1
  69. package/dist/cursor-adapter.js +10 -3
  70. package/dist/cursor-adapter.js.map +1 -1
  71. package/dist/droid-adapter.d.ts +32 -0
  72. package/dist/droid-adapter.d.ts.map +1 -0
  73. package/dist/droid-adapter.js +378 -0
  74. package/dist/droid-adapter.js.map +1 -0
  75. package/dist/gemini-adapter.d.ts +3 -1
  76. package/dist/gemini-adapter.d.ts.map +1 -1
  77. package/dist/gemini-adapter.js +10 -3
  78. package/dist/gemini-adapter.js.map +1 -1
  79. package/dist/hermes-adapter.d.ts.map +1 -1
  80. package/dist/hermes-adapter.js +10 -2
  81. package/dist/hermes-adapter.js.map +1 -1
  82. package/dist/index.d.ts +13 -0
  83. package/dist/index.d.ts.map +1 -1
  84. package/dist/index.js +13 -1
  85. package/dist/index.js.map +1 -1
  86. package/dist/mcp-plugins.d.ts +5 -3
  87. package/dist/mcp-plugins.d.ts.map +1 -1
  88. package/dist/mcp-plugins.js +58 -24
  89. package/dist/mcp-plugins.js.map +1 -1
  90. package/dist/omp-adapter.d.ts.map +1 -1
  91. package/dist/omp-adapter.js +10 -3
  92. package/dist/omp-adapter.js.map +1 -1
  93. package/dist/openclaw-adapter.d.ts +3 -1
  94. package/dist/openclaw-adapter.d.ts.map +1 -1
  95. package/dist/openclaw-adapter.js +11 -4
  96. package/dist/openclaw-adapter.js.map +1 -1
  97. package/dist/opencode-adapter.d.ts +5 -3
  98. package/dist/opencode-adapter.d.ts.map +1 -1
  99. package/dist/opencode-adapter.js +179 -63
  100. package/dist/opencode-adapter.js.map +1 -1
  101. package/dist/opencode-http-adapter.d.ts +43 -0
  102. package/dist/opencode-http-adapter.d.ts.map +1 -0
  103. package/dist/opencode-http-adapter.js +374 -0
  104. package/dist/opencode-http-adapter.js.map +1 -0
  105. package/dist/opencode-http-connection.d.ts +33 -0
  106. package/dist/opencode-http-connection.d.ts.map +1 -0
  107. package/dist/opencode-http-connection.js +198 -0
  108. package/dist/opencode-http-connection.js.map +1 -0
  109. package/dist/pi-adapter.d.ts.map +1 -1
  110. package/dist/pi-adapter.js +9 -2
  111. package/dist/pi-adapter.js.map +1 -1
  112. package/dist/pi-sdk-adapter.d.ts +46 -0
  113. package/dist/pi-sdk-adapter.d.ts.map +1 -0
  114. package/dist/pi-sdk-adapter.js +553 -0
  115. package/dist/pi-sdk-adapter.js.map +1 -0
  116. package/dist/programmatic-adapter-base.d.ts +88 -0
  117. package/dist/programmatic-adapter-base.d.ts.map +1 -0
  118. package/dist/programmatic-adapter-base.js +169 -0
  119. package/dist/programmatic-adapter-base.js.map +1 -0
  120. package/dist/provider-translation.d.ts +10 -0
  121. package/dist/provider-translation.d.ts.map +1 -0
  122. package/dist/provider-translation.js +2 -0
  123. package/dist/provider-translation.js.map +1 -0
  124. package/dist/qwen-adapter.d.ts +3 -1
  125. package/dist/qwen-adapter.d.ts.map +1 -1
  126. package/dist/qwen-adapter.js +10 -3
  127. package/dist/qwen-adapter.js.map +1 -1
  128. package/dist/remote-adapter-base.d.ts +85 -0
  129. package/dist/remote-adapter-base.d.ts.map +1 -0
  130. package/dist/remote-adapter-base.js +102 -0
  131. package/dist/remote-adapter-base.js.map +1 -0
  132. package/dist/session-fs.d.ts +15 -5
  133. package/dist/session-fs.d.ts.map +1 -1
  134. package/dist/session-fs.js +249 -0
  135. package/dist/session-fs.js.map +1 -1
  136. package/dist/shared/runtime-hooks-virtual.d.ts +3 -0
  137. package/dist/shared/runtime-hooks-virtual.d.ts.map +1 -0
  138. package/dist/shared/runtime-hooks-virtual.js +13 -0
  139. package/dist/shared/runtime-hooks-virtual.js.map +1 -0
  140. package/dist/translate-for-harness.d.ts +6 -0
  141. package/dist/translate-for-harness.d.ts.map +1 -0
  142. package/dist/translate-for-harness.js +36 -0
  143. package/dist/translate-for-harness.js.map +1 -0
  144. package/dist/translations/claude-translation.d.ts +4 -0
  145. package/dist/translations/claude-translation.d.ts.map +1 -0
  146. package/dist/translations/claude-translation.js +50 -0
  147. package/dist/translations/claude-translation.js.map +1 -0
  148. package/dist/translations/codex-translation.d.ts +4 -0
  149. package/dist/translations/codex-translation.d.ts.map +1 -0
  150. package/dist/translations/codex-translation.js +32 -0
  151. package/dist/translations/codex-translation.js.map +1 -0
  152. package/dist/translations/gemini-translation.d.ts +4 -0
  153. package/dist/translations/gemini-translation.d.ts.map +1 -0
  154. package/dist/translations/gemini-translation.js +20 -0
  155. package/dist/translations/gemini-translation.js.map +1 -0
  156. package/dist/translations/generic-openai-translation.d.ts +4 -0
  157. package/dist/translations/generic-openai-translation.d.ts.map +1 -0
  158. package/dist/translations/generic-openai-translation.js +19 -0
  159. package/dist/translations/generic-openai-translation.js.map +1 -0
  160. package/dist/translations/opencode-translation.d.ts +4 -0
  161. package/dist/translations/opencode-translation.d.ts.map +1 -0
  162. package/dist/translations/opencode-translation.js +51 -0
  163. package/dist/translations/opencode-translation.js.map +1 -0
  164. package/package.json +4 -2
@@ -0,0 +1,733 @@
1
+ import WebSocket from 'ws';
2
+ export class CodexWebSocketConnection {
3
+ connectionId;
4
+ connectionType = 'websocket';
5
+ websocketUrl;
6
+ endpoint;
7
+ prompt;
8
+ cwd;
9
+ requestedModel;
10
+ approvalMode;
11
+ initialSessionId;
12
+ models;
13
+ createSocket;
14
+ ws = null;
15
+ connected = false;
16
+ closed = false;
17
+ requestSeq = 0;
18
+ pendingResponses = new Map();
19
+ pendingInteractionRequests = new Map();
20
+ eventQueue = [];
21
+ waitingResolver = null;
22
+ threadId = null;
23
+ currentTurnId = null;
24
+ currentModelId;
25
+ turnIndex = -1;
26
+ textByItemId = new Map();
27
+ thinkingByItemId = new Map();
28
+ constructor(options) {
29
+ this.websocketUrl = options.websocketUrl;
30
+ this.connectionId = options.connectionId;
31
+ this.endpoint = options.websocketUrl;
32
+ this.prompt = options.prompt;
33
+ this.cwd = options.cwd;
34
+ this.requestedModel = options.requestedModel;
35
+ this.approvalMode = options.approvalMode;
36
+ this.initialSessionId = options.sessionId;
37
+ this.models = options.models;
38
+ this.createSocket = options.createSocket ?? ((url) => new WebSocket(url));
39
+ }
40
+ async connect() {
41
+ const socket = this.createSocket(this.websocketUrl);
42
+ this.ws = socket;
43
+ await new Promise((resolve, reject) => {
44
+ const onOpen = () => {
45
+ cleanup();
46
+ resolve();
47
+ };
48
+ const onError = (error) => {
49
+ cleanup();
50
+ reject(error);
51
+ };
52
+ const cleanup = () => {
53
+ socket.off('open', onOpen);
54
+ socket.off('error', onError);
55
+ };
56
+ socket.once('open', onOpen);
57
+ socket.once('error', onError);
58
+ });
59
+ socket.on('message', (data) => {
60
+ void this.handleIncomingMessage(data.toString());
61
+ });
62
+ socket.on('close', () => {
63
+ this.connected = false;
64
+ this.closed = true;
65
+ if (this.waitingResolver) {
66
+ const resolve = this.waitingResolver;
67
+ this.waitingResolver = null;
68
+ resolve(null);
69
+ }
70
+ });
71
+ socket.on('error', (error) => {
72
+ this.pushEvent({
73
+ type: 'error',
74
+ runId: this.connectionId,
75
+ agent: 'codex-websocket',
76
+ timestamp: Date.now(),
77
+ code: 'INTERNAL',
78
+ message: error.message,
79
+ recoverable: false,
80
+ });
81
+ });
82
+ this.connected = true;
83
+ await this.request('initialize', {
84
+ clientInfo: { name: 'agent-mux', version: '0.4.0' },
85
+ capabilities: {
86
+ experimentalApi: true,
87
+ },
88
+ });
89
+ await this.notify('initialized');
90
+ if (this.initialSessionId) {
91
+ const resumed = await this.request('thread/resume', {
92
+ threadId: this.initialSessionId,
93
+ cwd: this.cwd,
94
+ approvalPolicy: this.mapApprovalPolicy(),
95
+ persistExtendedHistory: false,
96
+ ...(this.requestedModel ? { model: this.requestedModel } : {}),
97
+ });
98
+ this.threadId = resumed.thread.id;
99
+ this.currentModelId = resumed.model ?? this.requestedModel;
100
+ this.pushEvent({
101
+ type: 'session_start',
102
+ runId: this.connectionId,
103
+ agent: 'codex-websocket',
104
+ timestamp: Date.now(),
105
+ sessionId: resumed.thread.id,
106
+ resumed: true,
107
+ });
108
+ }
109
+ else {
110
+ const started = await this.request('thread/start', {
111
+ cwd: this.cwd,
112
+ approvalPolicy: this.mapApprovalPolicy(),
113
+ sandbox: 'danger-full-access',
114
+ experimentalRawEvents: false,
115
+ persistExtendedHistory: false,
116
+ ...(this.requestedModel ? { model: this.requestedModel } : {}),
117
+ });
118
+ this.threadId = started.thread.id;
119
+ this.currentModelId = started.model ?? this.requestedModel;
120
+ this.pushEvent({
121
+ type: 'session_start',
122
+ runId: this.connectionId,
123
+ agent: 'codex-websocket',
124
+ timestamp: Date.now(),
125
+ sessionId: started.thread.id,
126
+ resumed: false,
127
+ });
128
+ }
129
+ if (this.prompt.trim().length > 0) {
130
+ await this.startTurn(this.prompt);
131
+ }
132
+ }
133
+ async send(data) {
134
+ if (!this.connected || this.closed) {
135
+ throw new Error('Codex app-server connection is not active');
136
+ }
137
+ if (this.isUserMessagePayload(data)) {
138
+ await this.sendUserMessage(data.text);
139
+ return;
140
+ }
141
+ if (this.isInteractionResponsePayload(data)) {
142
+ await this.sendInteractionResponse(data.interactionId, data.response);
143
+ return;
144
+ }
145
+ }
146
+ async *receive() {
147
+ while (!this.closed || this.eventQueue.length > 0) {
148
+ const event = this.eventQueue.shift();
149
+ if (event) {
150
+ yield event;
151
+ continue;
152
+ }
153
+ const nextEvent = await new Promise((resolve) => {
154
+ this.waitingResolver = resolve;
155
+ });
156
+ if (nextEvent) {
157
+ yield nextEvent;
158
+ }
159
+ }
160
+ }
161
+ subscribe(_channel) {
162
+ return this.receive();
163
+ }
164
+ async unsubscribe(_channel) { }
165
+ async close() {
166
+ this.closed = true;
167
+ const socket = this.ws;
168
+ this.ws = null;
169
+ if (socket && socket.readyState === WebSocket.OPEN) {
170
+ await new Promise((resolve) => {
171
+ socket.once('close', () => resolve());
172
+ socket.close();
173
+ });
174
+ }
175
+ if (this.waitingResolver) {
176
+ const resolve = this.waitingResolver;
177
+ this.waitingResolver = null;
178
+ resolve(null);
179
+ }
180
+ }
181
+ async handleIncomingMessage(raw) {
182
+ let message;
183
+ try {
184
+ message = JSON.parse(raw);
185
+ }
186
+ catch {
187
+ this.pushEvent({
188
+ type: 'error',
189
+ runId: this.connectionId,
190
+ agent: 'codex-websocket',
191
+ timestamp: Date.now(),
192
+ code: 'INTERNAL',
193
+ message: `Invalid Codex app-server message: ${raw}`,
194
+ recoverable: false,
195
+ });
196
+ return;
197
+ }
198
+ if (typeof message['method'] === 'string' && message['id'] != null) {
199
+ this.handleServerRequest({
200
+ id: message['id'],
201
+ method: message['method'],
202
+ params: message['params'] ?? {},
203
+ });
204
+ return;
205
+ }
206
+ if (typeof message['method'] === 'string') {
207
+ this.handleNotification({
208
+ method: message['method'],
209
+ params: message['params'] ?? {},
210
+ });
211
+ return;
212
+ }
213
+ if (message['id'] != null) {
214
+ this.handleResponse({
215
+ id: message['id'],
216
+ result: message['result'],
217
+ error: message['error'],
218
+ });
219
+ }
220
+ }
221
+ handleResponse(response) {
222
+ const pending = this.pendingResponses.get(response.id);
223
+ if (!pending) {
224
+ return;
225
+ }
226
+ this.pendingResponses.delete(response.id);
227
+ if (response.error) {
228
+ pending.reject(new Error(response.error.message ?? `Codex app-server request failed (${String(response.id)})`));
229
+ return;
230
+ }
231
+ pending.resolve(response.result);
232
+ }
233
+ handleServerRequest(request) {
234
+ const interactionId = String(request.id);
235
+ const params = request.params ?? {};
236
+ this.pendingInteractionRequests.set(interactionId, {
237
+ id: request.id,
238
+ method: request.method,
239
+ params,
240
+ });
241
+ const base = this.baseEvent();
242
+ switch (request.method) {
243
+ case 'item/commandExecution/requestApproval': {
244
+ const command = String(params['command'] ?? 'command');
245
+ const reason = params['reason'];
246
+ this.pushEvent({
247
+ ...base,
248
+ type: 'approval_request',
249
+ interactionId,
250
+ action: 'command_execution',
251
+ detail: typeof reason === 'string' && reason.length > 0 ? `${command}\n${reason}` : command,
252
+ toolName: command,
253
+ riskLevel: 'high',
254
+ });
255
+ return;
256
+ }
257
+ case 'item/fileChange/requestApproval': {
258
+ const reason = params['reason'];
259
+ this.pushEvent({
260
+ ...base,
261
+ type: 'approval_request',
262
+ interactionId,
263
+ action: 'file_change',
264
+ detail: typeof reason === 'string' && reason.length > 0 ? reason : 'Codex requested file changes',
265
+ toolName: 'file_change',
266
+ riskLevel: 'medium',
267
+ });
268
+ return;
269
+ }
270
+ case 'item/permissions/requestApproval': {
271
+ const reason = params['reason'];
272
+ this.pushEvent({
273
+ ...base,
274
+ type: 'approval_request',
275
+ interactionId,
276
+ action: 'permission_request',
277
+ detail: typeof reason === 'string' && reason.length > 0 ? reason : 'Codex requested additional permissions',
278
+ toolName: 'permissions',
279
+ riskLevel: 'high',
280
+ });
281
+ return;
282
+ }
283
+ case 'item/tool/requestUserInput': {
284
+ const questions = params['questions'];
285
+ const firstQuestion = Array.isArray(questions) ? questions[0] : undefined;
286
+ this.pushEvent({
287
+ ...base,
288
+ type: 'input_required',
289
+ interactionId,
290
+ question: String(firstQuestion?.['question'] ?? 'Codex needs input'),
291
+ context: undefined,
292
+ source: 'tool',
293
+ });
294
+ return;
295
+ }
296
+ default: {
297
+ this.pushEvent({
298
+ ...base,
299
+ type: 'debug',
300
+ level: 'warn',
301
+ message: `Unhandled Codex app-server server request: ${request.method}`,
302
+ });
303
+ }
304
+ }
305
+ }
306
+ handleNotification(notification) {
307
+ const params = notification.params ?? {};
308
+ switch (notification.method) {
309
+ case 'turn/started': {
310
+ const turn = params['turn'];
311
+ this.currentTurnId = typeof turn?.['id'] === 'string' ? turn['id'] : this.currentTurnId;
312
+ this.turnIndex += 1;
313
+ this.pushEvent({
314
+ ...this.baseEvent(),
315
+ type: 'turn_start',
316
+ turnIndex: this.turnIndex,
317
+ });
318
+ return;
319
+ }
320
+ case 'turn/completed': {
321
+ const turn = params['turn'];
322
+ const durationMs = typeof turn?.['durationMs'] === 'number' ? turn['durationMs'] : undefined;
323
+ this.currentTurnId = null;
324
+ this.pushEvent({
325
+ ...this.baseEvent(),
326
+ type: 'turn_end',
327
+ turnIndex: this.turnIndex,
328
+ ...(durationMs != null ? { cost: undefined } : {}),
329
+ });
330
+ return;
331
+ }
332
+ case 'item/started': {
333
+ const item = params['item'];
334
+ if (!item)
335
+ return;
336
+ this.handleItemStarted(item);
337
+ return;
338
+ }
339
+ case 'item/completed': {
340
+ const item = params['item'];
341
+ if (!item)
342
+ return;
343
+ this.handleItemCompleted(item);
344
+ return;
345
+ }
346
+ case 'item/agentMessage/delta': {
347
+ const itemId = String(params['itemId'] ?? '');
348
+ const delta = String(params['delta'] ?? '');
349
+ const accumulated = (this.textByItemId.get(itemId) ?? '') + delta;
350
+ this.textByItemId.set(itemId, accumulated);
351
+ this.pushEvent({
352
+ ...this.baseEvent(),
353
+ type: 'text_delta',
354
+ delta,
355
+ accumulated,
356
+ });
357
+ return;
358
+ }
359
+ case 'item/reasoning/textDelta':
360
+ case 'item/reasoning/summaryTextDelta': {
361
+ const itemId = String(params['itemId'] ?? '');
362
+ const delta = String(params['delta'] ?? '');
363
+ const accumulated = (this.thinkingByItemId.get(itemId) ?? '') + delta;
364
+ this.thinkingByItemId.set(itemId, accumulated);
365
+ this.pushEvent({
366
+ ...this.baseEvent(),
367
+ type: 'thinking_delta',
368
+ delta,
369
+ accumulated,
370
+ });
371
+ return;
372
+ }
373
+ case 'item/commandExecution/outputDelta': {
374
+ const delta = String(params['delta'] ?? '');
375
+ this.pushEvent({
376
+ ...this.baseEvent(),
377
+ type: 'shell_stdout_delta',
378
+ delta,
379
+ });
380
+ return;
381
+ }
382
+ case 'thread/tokenUsage/updated': {
383
+ const tokenUsage = params['tokenUsage'];
384
+ const total = tokenUsage?.['total'];
385
+ if (!total)
386
+ return;
387
+ const inputTokens = Number(total['inputTokens'] ?? 0);
388
+ const outputTokens = Number(total['outputTokens'] ?? 0);
389
+ const cachedTokens = Number(total['cachedInputTokens'] ?? 0);
390
+ const thinkingTokens = Number(total['reasoningOutputTokens'] ?? 0);
391
+ const cost = this.buildCostRecord(inputTokens, outputTokens, thinkingTokens, cachedTokens);
392
+ this.pushEvent({
393
+ ...this.baseEvent(),
394
+ type: 'token_usage',
395
+ inputTokens,
396
+ outputTokens,
397
+ thinkingTokens,
398
+ cachedTokens,
399
+ });
400
+ this.pushEvent({
401
+ ...this.baseEvent(),
402
+ type: 'cost',
403
+ cost,
404
+ });
405
+ return;
406
+ }
407
+ case 'error': {
408
+ const error = params['error'];
409
+ this.pushEvent({
410
+ ...this.baseEvent(),
411
+ type: 'error',
412
+ code: 'INTERNAL',
413
+ message: String(error?.['message'] ?? 'Codex app-server error'),
414
+ recoverable: Boolean(params['willRetry']),
415
+ });
416
+ return;
417
+ }
418
+ case 'mcpServer/startupStatus/updated': {
419
+ const status = String(params['status'] ?? '');
420
+ if (status === 'failed') {
421
+ this.pushEvent({
422
+ ...this.baseEvent(),
423
+ type: 'debug',
424
+ level: 'warn',
425
+ message: String(params['error'] ?? 'Codex MCP server failed to start'),
426
+ });
427
+ }
428
+ return;
429
+ }
430
+ default:
431
+ return;
432
+ }
433
+ }
434
+ handleItemStarted(item) {
435
+ const itemType = String(item['type'] ?? '');
436
+ switch (itemType) {
437
+ case 'agentMessage': {
438
+ const itemId = String(item['id'] ?? '');
439
+ this.textByItemId.set(itemId, '');
440
+ this.pushEvent({
441
+ ...this.baseEvent(),
442
+ type: 'message_start',
443
+ });
444
+ return;
445
+ }
446
+ case 'reasoning': {
447
+ const itemId = String(item['id'] ?? '');
448
+ this.thinkingByItemId.set(itemId, '');
449
+ this.pushEvent({
450
+ ...this.baseEvent(),
451
+ type: 'thinking_start',
452
+ effort: undefined,
453
+ });
454
+ return;
455
+ }
456
+ case 'commandExecution': {
457
+ const toolCallId = String(item['id'] ?? '');
458
+ const command = String(item['command'] ?? 'command');
459
+ this.pushEvent({
460
+ ...this.baseEvent(),
461
+ type: 'tool_call_start',
462
+ toolCallId,
463
+ toolName: 'commandExecution',
464
+ inputAccumulated: command,
465
+ });
466
+ this.pushEvent({
467
+ ...this.baseEvent(),
468
+ type: 'shell_start',
469
+ command,
470
+ cwd: String(item['cwd'] ?? this.cwd),
471
+ });
472
+ return;
473
+ }
474
+ case 'mcpToolCall': {
475
+ this.pushEvent({
476
+ ...this.baseEvent(),
477
+ type: 'mcp_tool_call_start',
478
+ toolCallId: String(item['id'] ?? ''),
479
+ server: String(item['server'] ?? ''),
480
+ toolName: String(item['tool'] ?? ''),
481
+ input: item['arguments'] ?? {},
482
+ });
483
+ return;
484
+ }
485
+ case 'dynamicToolCall': {
486
+ this.pushEvent({
487
+ ...this.baseEvent(),
488
+ type: 'tool_call_start',
489
+ toolCallId: String(item['id'] ?? ''),
490
+ toolName: String(item['tool'] ?? ''),
491
+ inputAccumulated: JSON.stringify(item['arguments'] ?? {}),
492
+ });
493
+ return;
494
+ }
495
+ default:
496
+ return;
497
+ }
498
+ }
499
+ handleItemCompleted(item) {
500
+ const itemType = String(item['type'] ?? '');
501
+ switch (itemType) {
502
+ case 'agentMessage': {
503
+ const itemId = String(item['id'] ?? '');
504
+ const text = String(item['text'] ?? this.textByItemId.get(itemId) ?? '');
505
+ this.textByItemId.delete(itemId);
506
+ this.pushEvent({
507
+ ...this.baseEvent(),
508
+ type: 'message_stop',
509
+ text,
510
+ });
511
+ return;
512
+ }
513
+ case 'reasoning': {
514
+ const itemId = String(item['id'] ?? '');
515
+ const thinking = this.thinkingByItemId.get(itemId) ?? '';
516
+ this.thinkingByItemId.delete(itemId);
517
+ this.pushEvent({
518
+ ...this.baseEvent(),
519
+ type: 'thinking_stop',
520
+ thinking,
521
+ });
522
+ return;
523
+ }
524
+ case 'commandExecution': {
525
+ const toolCallId = String(item['id'] ?? '');
526
+ const command = String(item['command'] ?? 'command');
527
+ const output = item['aggregatedOutput'] ?? '';
528
+ const durationMs = Number(item['durationMs'] ?? 0);
529
+ const exitCode = Number(item['exitCode'] ?? 0);
530
+ this.pushEvent({
531
+ ...this.baseEvent(),
532
+ type: 'tool_result',
533
+ toolCallId,
534
+ toolName: 'commandExecution',
535
+ output,
536
+ durationMs,
537
+ });
538
+ this.pushEvent({
539
+ ...this.baseEvent(),
540
+ type: 'shell_exit',
541
+ exitCode,
542
+ durationMs,
543
+ });
544
+ return;
545
+ }
546
+ case 'mcpToolCall': {
547
+ const toolCallId = String(item['id'] ?? '');
548
+ const server = String(item['server'] ?? '');
549
+ const toolName = String(item['tool'] ?? '');
550
+ if (item['error']) {
551
+ this.pushEvent({
552
+ ...this.baseEvent(),
553
+ type: 'mcp_tool_error',
554
+ toolCallId,
555
+ server,
556
+ toolName,
557
+ error: JSON.stringify(item['error']),
558
+ });
559
+ return;
560
+ }
561
+ this.pushEvent({
562
+ ...this.baseEvent(),
563
+ type: 'mcp_tool_result',
564
+ toolCallId,
565
+ server,
566
+ toolName,
567
+ output: item['result'] ?? null,
568
+ });
569
+ return;
570
+ }
571
+ case 'dynamicToolCall': {
572
+ const toolCallId = String(item['id'] ?? '');
573
+ const toolName = String(item['tool'] ?? '');
574
+ if (item['success'] === false) {
575
+ this.pushEvent({
576
+ ...this.baseEvent(),
577
+ type: 'tool_error',
578
+ toolCallId,
579
+ toolName,
580
+ error: JSON.stringify(item['contentItems'] ?? []),
581
+ });
582
+ return;
583
+ }
584
+ this.pushEvent({
585
+ ...this.baseEvent(),
586
+ type: 'tool_result',
587
+ toolCallId,
588
+ toolName,
589
+ output: item['contentItems'] ?? [],
590
+ durationMs: Number(item['durationMs'] ?? 0),
591
+ });
592
+ return;
593
+ }
594
+ default:
595
+ return;
596
+ }
597
+ }
598
+ async startTurn(text) {
599
+ if (!this.threadId) {
600
+ throw new Error('Codex app-server thread is not initialized');
601
+ }
602
+ const response = await this.request('turn/start', {
603
+ threadId: this.threadId,
604
+ input: [this.asTextInput(text)],
605
+ approvalPolicy: this.mapApprovalPolicy(),
606
+ });
607
+ this.currentTurnId = response.turn.id;
608
+ }
609
+ async sendUserMessage(text) {
610
+ if (!this.threadId) {
611
+ throw new Error('Codex app-server thread is not initialized');
612
+ }
613
+ if (this.currentTurnId) {
614
+ await this.request('turn/steer', {
615
+ threadId: this.threadId,
616
+ input: [this.asTextInput(text)],
617
+ expectedTurnId: this.currentTurnId,
618
+ });
619
+ return;
620
+ }
621
+ await this.startTurn(text);
622
+ }
623
+ async sendInteractionResponse(interactionId, response) {
624
+ const pending = this.pendingInteractionRequests.get(interactionId);
625
+ if (!pending || !this.ws) {
626
+ throw new Error(`No pending Codex interaction ${interactionId}`);
627
+ }
628
+ this.pendingInteractionRequests.delete(interactionId);
629
+ const result = this.buildInteractionResult(pending.method, response);
630
+ this.ws.send(JSON.stringify({ id: pending.id, result }));
631
+ }
632
+ buildInteractionResult(method, response) {
633
+ if (method === 'item/tool/requestUserInput') {
634
+ return {
635
+ answers: {
636
+ response: {
637
+ answers: [response.type === 'text' ? response.text : response.type],
638
+ },
639
+ },
640
+ };
641
+ }
642
+ const approved = response.type === 'approve';
643
+ switch (method) {
644
+ case 'item/commandExecution/requestApproval':
645
+ return { decision: approved ? 'accept' : 'decline' };
646
+ case 'item/fileChange/requestApproval':
647
+ return { decision: approved ? 'accept' : 'decline' };
648
+ case 'item/permissions/requestApproval':
649
+ return approved
650
+ ? { permissions: {}, scope: 'turn' }
651
+ : { permissions: {}, scope: 'turn' };
652
+ default:
653
+ return {};
654
+ }
655
+ }
656
+ async request(method, params) {
657
+ if (!this.ws) {
658
+ throw new Error('Codex app-server socket is not connected');
659
+ }
660
+ const id = ++this.requestSeq;
661
+ const payload = { method, id, params };
662
+ const promise = new Promise((resolve, reject) => {
663
+ this.pendingResponses.set(id, { resolve: resolve, reject });
664
+ });
665
+ this.ws.send(JSON.stringify(payload));
666
+ return promise;
667
+ }
668
+ async notify(method) {
669
+ if (!this.ws) {
670
+ throw new Error('Codex app-server socket is not connected');
671
+ }
672
+ this.ws.send(JSON.stringify({ method }));
673
+ }
674
+ asTextInput(text) {
675
+ return {
676
+ type: 'text',
677
+ text,
678
+ text_elements: [],
679
+ };
680
+ }
681
+ mapApprovalPolicy() {
682
+ return this.approvalMode === 'yolo' ? 'never' : 'on-request';
683
+ }
684
+ buildCostRecord(inputTokens, outputTokens, thinkingTokens, cachedTokens) {
685
+ const model = this.models.find((entry) => entry.modelId === this.currentModelId) ?? this.models[0];
686
+ const inputPrice = model?.inputPricePerMillion ?? 0;
687
+ const outputPrice = model?.outputPricePerMillion ?? 0;
688
+ const thinkingPrice = model?.thinkingPricePerMillion ?? 0;
689
+ const cachedPrice = model?.cachedInputPricePerMillion ?? inputPrice;
690
+ const totalUsd = ((inputTokens - cachedTokens) / 1_000_000) * inputPrice +
691
+ (cachedTokens / 1_000_000) * cachedPrice +
692
+ (outputTokens / 1_000_000) * outputPrice +
693
+ (thinkingTokens / 1_000_000) * thinkingPrice;
694
+ return {
695
+ totalUsd,
696
+ inputTokens,
697
+ outputTokens,
698
+ thinkingTokens,
699
+ cachedTokens,
700
+ };
701
+ }
702
+ isUserMessagePayload(data) {
703
+ if (typeof data !== 'object' || data === null) {
704
+ return false;
705
+ }
706
+ const candidate = data;
707
+ return candidate.type === 'user_message' && typeof candidate.text === 'string';
708
+ }
709
+ isInteractionResponsePayload(data) {
710
+ if (typeof data !== 'object' || data === null) {
711
+ return false;
712
+ }
713
+ const candidate = data;
714
+ return candidate.type === 'interaction_response' && typeof candidate.interactionId === 'string' && candidate.response != null;
715
+ }
716
+ pushEvent(event) {
717
+ if (this.waitingResolver) {
718
+ const resolve = this.waitingResolver;
719
+ this.waitingResolver = null;
720
+ resolve(event);
721
+ return;
722
+ }
723
+ this.eventQueue.push(event);
724
+ }
725
+ baseEvent() {
726
+ return {
727
+ runId: this.connectionId,
728
+ agent: 'codex-websocket',
729
+ timestamp: Date.now(),
730
+ };
731
+ }
732
+ }
733
+ //# sourceMappingURL=codex-websocket-connection.js.map