@a5c-ai/agent-mux-adapters 0.4.0 → 0.4.2

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 (158) 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 +2 -1
  6. package/dist/amp-adapter.d.ts.map +1 -1
  7. package/dist/amp-adapter.js +93 -17
  8. package/dist/amp-adapter.js.map +1 -1
  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.d.ts +7 -0
  14. package/dist/base-adapter.d.ts.map +1 -1
  15. package/dist/base-adapter.js +113 -1
  16. package/dist/base-adapter.js.map +1 -1
  17. package/dist/claude-adapter.d.ts +14 -3
  18. package/dist/claude-adapter.d.ts.map +1 -1
  19. package/dist/claude-adapter.js +222 -25
  20. package/dist/claude-adapter.js.map +1 -1
  21. package/dist/claude-agent-sdk-adapter.d.ts +21 -34
  22. package/dist/claude-agent-sdk-adapter.d.ts.map +1 -1
  23. package/dist/claude-agent-sdk-adapter.js +629 -397
  24. package/dist/claude-agent-sdk-adapter.js.map +1 -1
  25. package/dist/claude-code/runtime-hooks/ephemeral-config.d.ts +12 -0
  26. package/dist/claude-code/runtime-hooks/ephemeral-config.d.ts.map +1 -0
  27. package/dist/claude-code/runtime-hooks/ephemeral-config.js +143 -0
  28. package/dist/claude-code/runtime-hooks/ephemeral-config.js.map +1 -0
  29. package/dist/claude-code/runtime-hooks/hook-socket-server.d.ts +10 -0
  30. package/dist/claude-code/runtime-hooks/hook-socket-server.d.ts.map +1 -0
  31. package/dist/claude-code/runtime-hooks/hook-socket-server.js +79 -0
  32. package/dist/claude-code/runtime-hooks/hook-socket-server.js.map +1 -0
  33. package/dist/claude-code/runtime-hooks/lifecycle.d.ts +3 -0
  34. package/dist/claude-code/runtime-hooks/lifecycle.d.ts.map +1 -0
  35. package/dist/claude-code/runtime-hooks/lifecycle.js +24 -0
  36. package/dist/claude-code/runtime-hooks/lifecycle.js.map +1 -0
  37. package/dist/claude-remote-control-adapter.d.ts +43 -0
  38. package/dist/claude-remote-control-adapter.d.ts.map +1 -0
  39. package/dist/claude-remote-control-adapter.js +505 -0
  40. package/dist/claude-remote-control-adapter.js.map +1 -0
  41. package/dist/codex-adapter.d.ts.map +1 -1
  42. package/dist/codex-adapter.js +64 -41
  43. package/dist/codex-adapter.js.map +1 -1
  44. package/dist/codex-sdk-adapter.d.ts.map +1 -1
  45. package/dist/codex-sdk-adapter.js +6 -2
  46. package/dist/codex-sdk-adapter.js.map +1 -1
  47. package/dist/codex-websocket-adapter.d.ts +11 -18
  48. package/dist/codex-websocket-adapter.d.ts.map +1 -1
  49. package/dist/codex-websocket-adapter.js +199 -90
  50. package/dist/codex-websocket-adapter.js.map +1 -1
  51. package/dist/codex-websocket-connection.d.ts +60 -40
  52. package/dist/codex-websocket-connection.d.ts.map +1 -1
  53. package/dist/codex-websocket-connection.js +692 -203
  54. package/dist/codex-websocket-connection.js.map +1 -1
  55. package/dist/copilot-adapter.d.ts.map +1 -1
  56. package/dist/copilot-adapter.js +4 -0
  57. package/dist/copilot-adapter.js.map +1 -1
  58. package/dist/cursor-adapter.d.ts +3 -1
  59. package/dist/cursor-adapter.d.ts.map +1 -1
  60. package/dist/cursor-adapter.js +5 -1
  61. package/dist/cursor-adapter.js.map +1 -1
  62. package/dist/droid-adapter.d.ts +4 -2
  63. package/dist/droid-adapter.d.ts.map +1 -1
  64. package/dist/droid-adapter.js +12 -11
  65. package/dist/droid-adapter.js.map +1 -1
  66. package/dist/gemini-adapter.d.ts +3 -1
  67. package/dist/gemini-adapter.d.ts.map +1 -1
  68. package/dist/gemini-adapter.js +5 -1
  69. package/dist/gemini-adapter.js.map +1 -1
  70. package/dist/hermes-adapter.d.ts.map +1 -1
  71. package/dist/hermes-adapter.js +4 -0
  72. package/dist/hermes-adapter.js.map +1 -1
  73. package/dist/index.d.ts +4 -1
  74. package/dist/index.d.ts.map +1 -1
  75. package/dist/index.js +3 -10
  76. package/dist/index.js.map +1 -1
  77. package/dist/mcp-plugins.d.ts +5 -3
  78. package/dist/mcp-plugins.d.ts.map +1 -1
  79. package/dist/mcp-plugins.js +58 -24
  80. package/dist/mcp-plugins.js.map +1 -1
  81. package/dist/omp-adapter.d.ts.map +1 -1
  82. package/dist/omp-adapter.js +4 -0
  83. package/dist/omp-adapter.js.map +1 -1
  84. package/dist/openclaw-adapter.d.ts +3 -1
  85. package/dist/openclaw-adapter.d.ts.map +1 -1
  86. package/dist/openclaw-adapter.js +5 -1
  87. package/dist/openclaw-adapter.js.map +1 -1
  88. package/dist/opencode-adapter.d.ts +3 -1
  89. package/dist/opencode-adapter.d.ts.map +1 -1
  90. package/dist/opencode-adapter.js +7 -3
  91. package/dist/opencode-adapter.js.map +1 -1
  92. package/dist/opencode-http-adapter.d.ts +3 -1
  93. package/dist/opencode-http-adapter.d.ts.map +1 -1
  94. package/dist/opencode-http-adapter.js +5 -1
  95. package/dist/opencode-http-adapter.js.map +1 -1
  96. package/dist/pi-adapter.d.ts.map +1 -1
  97. package/dist/pi-adapter.js +4 -0
  98. package/dist/pi-adapter.js.map +1 -1
  99. package/dist/pi-sdk-adapter.d.ts.map +1 -1
  100. package/dist/pi-sdk-adapter.js +4 -0
  101. package/dist/pi-sdk-adapter.js.map +1 -1
  102. package/dist/provider-translation.d.ts +10 -0
  103. package/dist/provider-translation.d.ts.map +1 -0
  104. package/dist/provider-translation.js +2 -0
  105. package/dist/provider-translation.js.map +1 -0
  106. package/dist/qwen-adapter.d.ts +3 -1
  107. package/dist/qwen-adapter.d.ts.map +1 -1
  108. package/dist/qwen-adapter.js +5 -1
  109. package/dist/qwen-adapter.js.map +1 -1
  110. package/dist/session-fs.d.ts +15 -5
  111. package/dist/session-fs.d.ts.map +1 -1
  112. package/dist/session-fs.js +249 -0
  113. package/dist/session-fs.js.map +1 -1
  114. package/dist/shared/runtime-hooks-virtual.d.ts +3 -0
  115. package/dist/shared/runtime-hooks-virtual.d.ts.map +1 -0
  116. package/dist/shared/runtime-hooks-virtual.js +13 -0
  117. package/dist/shared/runtime-hooks-virtual.js.map +1 -0
  118. package/dist/translate-for-harness.d.ts +6 -0
  119. package/dist/translate-for-harness.d.ts.map +1 -0
  120. package/dist/translate-for-harness.js +36 -0
  121. package/dist/translate-for-harness.js.map +1 -0
  122. package/dist/translations/claude-translation.d.ts +4 -0
  123. package/dist/translations/claude-translation.d.ts.map +1 -0
  124. package/dist/translations/claude-translation.js +50 -0
  125. package/dist/translations/claude-translation.js.map +1 -0
  126. package/dist/translations/codex-translation.d.ts +4 -0
  127. package/dist/translations/codex-translation.d.ts.map +1 -0
  128. package/dist/translations/codex-translation.js +32 -0
  129. package/dist/translations/codex-translation.js.map +1 -0
  130. package/dist/translations/gemini-translation.d.ts +4 -0
  131. package/dist/translations/gemini-translation.d.ts.map +1 -0
  132. package/dist/translations/gemini-translation.js +20 -0
  133. package/dist/translations/gemini-translation.js.map +1 -0
  134. package/dist/translations/generic-openai-translation.d.ts +4 -0
  135. package/dist/translations/generic-openai-translation.d.ts.map +1 -0
  136. package/dist/translations/generic-openai-translation.js +19 -0
  137. package/dist/translations/generic-openai-translation.js.map +1 -0
  138. package/dist/translations/opencode-translation.d.ts +4 -0
  139. package/dist/translations/opencode-translation.d.ts.map +1 -0
  140. package/dist/translations/opencode-translation.js +51 -0
  141. package/dist/translations/opencode-translation.js.map +1 -0
  142. package/package.json +4 -2
  143. package/dist/mocks/index.d.ts +0 -60
  144. package/dist/mocks/index.d.ts.map +0 -1
  145. package/dist/mocks/index.js +0 -203
  146. package/dist/mocks/index.js.map +0 -1
  147. package/dist/mocks/mock-types.d.ts +0 -260
  148. package/dist/mocks/mock-types.d.ts.map +0 -1
  149. package/dist/mocks/mock-types.js +0 -12
  150. package/dist/mocks/mock-types.js.map +0 -1
  151. package/dist/mocks/programmatic-mocks.d.ts +0 -50
  152. package/dist/mocks/programmatic-mocks.d.ts.map +0 -1
  153. package/dist/mocks/programmatic-mocks.js +0 -330
  154. package/dist/mocks/programmatic-mocks.js.map +0 -1
  155. package/dist/mocks/remote-mocks.d.ts +0 -52
  156. package/dist/mocks/remote-mocks.d.ts.map +0 -1
  157. package/dist/mocks/remote-mocks.js +0 -436
  158. package/dist/mocks/remote-mocks.js.map +0 -1
@@ -1,244 +1,733 @@
1
- /**
2
- * WebSocket connection + mock response simulator for CodexWebSocketAdapter.
3
- * Extracted to keep codex-websocket-adapter.ts under the 400-line cap.
4
- */
5
- import { EventEmitter } from 'node:events';
1
+ import WebSocket from 'ws';
6
2
  export class CodexWebSocketConnection {
7
3
  connectionId;
8
4
  connectionType = 'websocket';
9
5
  websocketUrl;
10
6
  endpoint;
11
- ws;
12
- eventEmitter = new EventEmitter();
13
- messageQueue = [];
14
- subscriptions = new Map();
7
+ prompt;
8
+ cwd;
9
+ requestedModel;
10
+ approvalMode;
11
+ initialSessionId;
12
+ models;
13
+ createSocket;
14
+ ws = null;
15
15
  connected = false;
16
- requestId = 0;
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();
17
28
  constructor(options) {
18
29
  this.websocketUrl = options.websocketUrl;
19
30
  this.connectionId = options.connectionId;
20
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));
21
39
  }
22
40
  async connect() {
23
- this.ws = {
24
- readyState: 1,
25
- send: (data) => {
26
- setTimeout(() => {
27
- this.handleMockResponse(JSON.parse(data));
28
- }, 50);
29
- },
30
- close: () => {
31
- this.connected = false;
32
- this.eventEmitter.emit('close');
33
- },
34
- addEventListener: (event, handler) => {
35
- this.eventEmitter.on(event, handler);
36
- },
37
- };
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
+ });
38
82
  this.connected = true;
39
- this.eventEmitter.emit('open');
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
+ }
40
132
  }
41
133
  async send(data) {
42
- if (!this.connected)
43
- throw new Error('WebSocket not connected');
44
- const message = {
45
- id: `msg_${++this.requestId}`,
46
- type: 'request',
47
- payload: data,
48
- timestamp: Date.now(),
49
- };
50
- this.ws.send(JSON.stringify(message));
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
+ }
51
145
  }
52
146
  async *receive() {
53
- if (!this.connected)
54
- await this.connect();
55
- for (const message of this.messageQueue) {
56
- const event = this.parseMessageToEvent(message);
57
- if (event)
147
+ while (!this.closed || this.eventQueue.length > 0) {
148
+ const event = this.eventQueue.shift();
149
+ if (event) {
58
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
+ }
59
159
  }
60
- this.messageQueue = [];
61
- const messageHandler = (message) => {
62
- const event = this.parseMessageToEvent(message);
63
- if (event)
64
- this.eventEmitter.emit('agentEvent', event);
65
- };
66
- this.eventEmitter.on('message', messageHandler);
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;
67
183
  try {
68
- while (this.connected) {
69
- const event = await new Promise((resolve, reject) => {
70
- const timeout = setTimeout(() => reject(new Error('Receive timeout')), 30000);
71
- this.eventEmitter.once('agentEvent', (ev) => {
72
- clearTimeout(timeout);
73
- resolve(ev);
74
- });
75
- this.eventEmitter.once('close', () => {
76
- clearTimeout(timeout);
77
- reject(new Error('WebSocket closed'));
78
- });
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}`,
79
302
  });
80
- yield event;
81
303
  }
82
- }
83
- finally {
84
- this.eventEmitter.off('message', messageHandler);
85
304
  }
86
305
  }
87
- subscribe(channel) {
88
- const subscriptionId = `sub_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
89
- if (!this.subscriptions.has(channel))
90
- this.subscriptions.set(channel, new Set());
91
- this.subscriptions.get(channel).add(subscriptionId);
92
- this.send({ type: 'subscribe', channel, subscriptionId }).catch(() => { });
93
- const self = this;
94
- return {
95
- async *[Symbol.asyncIterator]() {
96
- for await (const event of self.receive()) {
97
- if (event.type === 'debug' &&
98
- typeof event.message === 'string' &&
99
- event.message.includes(channel)) {
100
- yield event;
101
- }
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
+ });
102
427
  }
103
- },
104
- next: async () => {
105
- throw new Error('Use for-await-of loop instead of calling next() directly');
106
- },
107
- };
108
- }
109
- async unsubscribe(channel) {
110
- const subs = this.subscriptions.get(channel);
111
- if (subs) {
112
- for (const subId of subs) {
113
- await this.send({ type: 'unsubscribe', channel, subscriptionId: subId });
428
+ return;
114
429
  }
115
- this.subscriptions.delete(channel);
430
+ default:
431
+ return;
116
432
  }
117
433
  }
118
- async close() {
119
- this.connected = false;
120
- if (this.ws)
121
- this.ws.close();
122
- this.eventEmitter.removeAllListeners();
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
+ }
123
498
  }
124
- parseMessageToEvent(message) {
125
- const base = {
126
- runId: this.connectionId,
127
- agent: 'codex-websocket',
128
- timestamp: message.timestamp || Date.now(),
129
- };
130
- if (message.type === 'event' && typeof message.payload === 'object' && message.payload) {
131
- const payload = message.payload;
132
- switch (payload.type) {
133
- case 'text_delta': {
134
- const d = payload.data;
135
- return { ...base, type: 'text_delta', delta: d.delta, accumulated: d.accumulated };
136
- }
137
- case 'tool_call': {
138
- const d = payload.data;
139
- return {
140
- ...base,
141
- type: 'tool_call_start',
142
- toolCallId: d.id,
143
- toolName: d.name,
144
- inputAccumulated: d.arguments,
145
- };
146
- }
147
- case 'tool_result': {
148
- const d = payload.data;
149
- return {
150
- ...base,
151
- type: 'tool_result',
152
- toolCallId: d.id,
153
- toolName: d.name,
154
- output: d.output,
155
- durationMs: 0,
156
- };
157
- }
158
- case 'error': {
159
- const d = payload.data;
160
- return {
161
- ...base,
162
- type: 'error',
163
- code: d.code || 'WEBSOCKET_ERROR',
164
- message: d.message,
165
- recoverable: false,
166
- };
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;
167
560
  }
168
- case 'done': {
169
- const d = payload.data;
170
- const events = [
171
- { ...base, type: 'message_stop', text: d.text },
172
- ];
173
- if (d.usage) {
174
- events.push({
175
- ...base,
176
- type: 'cost',
177
- cost: {
178
- totalUsd: 0,
179
- inputTokens: d.usage.prompt_tokens,
180
- outputTokens: d.usage.completion_tokens,
181
- },
182
- });
183
- }
184
- if (events.length > 1) {
185
- for (let i = 1; i < events.length; i++) {
186
- this.messageQueue.push({
187
- id: message.id,
188
- type: 'event',
189
- payload: events[i],
190
- timestamp: Date.now(),
191
- });
192
- }
193
- }
194
- return events[0];
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;
195
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;
196
593
  }
594
+ default:
595
+ return;
197
596
  }
198
- return null;
199
597
  }
200
- handleMockResponse(request) {
201
- if (request.type === 'request' && typeof request.payload === 'object') {
202
- const payload = request.payload;
203
- if (payload.type === 'chat')
204
- this.simulateChatResponse(request.id, payload);
598
+ async startTurn(text) {
599
+ if (!this.threadId) {
600
+ throw new Error('Codex app-server thread is not initialized');
205
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;
206
608
  }
207
- simulateChatResponse(requestId, _chatRequest) {
208
- const events = [
209
- { type: 'text_delta', data: { delta: "I'll help you with that. ", accumulated: "I'll help you with that. " } },
210
- { type: 'text_delta', data: { delta: 'Let me execute some code.', accumulated: "I'll help you with that. Let me execute some code." } },
211
- {
212
- type: 'tool_call',
213
- data: {
214
- id: 'call_123',
215
- name: 'execute_code',
216
- arguments: JSON.stringify({ language: 'python', code: 'print("Hello from WebSocket!")' }),
217
- },
218
- },
219
- {
220
- type: 'tool_result',
221
- data: { id: 'call_123', name: 'execute_code', output: 'Hello from WebSocket!' },
222
- },
223
- {
224
- type: 'done',
225
- data: {
226
- text: "I'll help you with that. Let me execute some code.",
227
- usage: { total_tokens: 150, prompt_tokens: 100, completion_tokens: 50 },
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
+ },
228
639
  },
229
- },
230
- ];
231
- events.forEach((event, index) => {
232
- setTimeout(() => {
233
- const message = {
234
- id: `${requestId}_${index}`,
235
- type: 'event',
236
- payload: event,
237
- timestamp: Date.now(),
238
- };
239
- this.eventEmitter.emit('message', message);
240
- }, (index + 1) * 200);
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 });
241
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
+ };
242
731
  }
243
732
  }
244
733
  //# sourceMappingURL=codex-websocket-connection.js.map