@ai-sdk/harness-codex 1.0.119 → 1.0.121

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.
@@ -77,6 +77,7 @@ export function createEmitStreamEvent({
77
77
  }): (event: CodexEvent) => void {
78
78
  const textByItem = new Map<string, string>();
79
79
  const reasoningByItem = new Map<string, string>();
80
+ const emittedWebSearchToolCalls = new Set<string>();
80
81
 
81
82
  return event => {
82
83
  if (
@@ -206,24 +207,32 @@ export function createEmitStreamEvent({
206
207
 
207
208
  if (item.type === 'web_search') {
208
209
  const nativeName = 'web_search';
209
- if (event.type === 'item.started') {
210
- send({
211
- type: 'tool-call',
212
- toolCallId: id,
213
- toolName: toCommonName(nativeName),
210
+ const query = getWebSearchQuery(item);
211
+ if (event.type === 'item.started' || event.type === 'item.updated') {
212
+ if (query !== undefined) {
213
+ emitWebSearchToolCall({
214
+ id,
215
+ query,
216
+ nativeName,
217
+ emittedWebSearchToolCalls,
218
+ send,
219
+ });
220
+ }
221
+ } else if (event.type === 'item.completed') {
222
+ emitWebSearchToolCall({
223
+ id,
224
+ query: query ?? '',
214
225
  nativeName,
215
- input: JSON.stringify({
216
- query: item.query ?? item.action?.query ?? '',
217
- }),
218
- providerExecuted: true,
226
+ emittedWebSearchToolCalls,
227
+ send,
219
228
  });
220
- } else if (event.type === 'item.completed') {
221
229
  send({
222
230
  type: 'tool-result',
223
231
  toolCallId: id,
224
232
  toolName: toCommonName(nativeName),
225
233
  result: item.result ?? item.action ?? null,
226
234
  });
235
+ emittedWebSearchToolCalls.delete(id);
227
236
  }
228
237
  observeStep();
229
238
  return;
@@ -256,6 +265,41 @@ export function createEmitStreamEvent({
256
265
  };
257
266
  }
258
267
 
268
+ function getWebSearchQuery(item: CodexItem): string | undefined {
269
+ if (typeof item.query === 'string' && item.query.length > 0) {
270
+ return item.query;
271
+ }
272
+ if (typeof item.action?.query === 'string' && item.action.query.length > 0) {
273
+ return item.action.query;
274
+ }
275
+ return undefined;
276
+ }
277
+
278
+ function emitWebSearchToolCall({
279
+ id,
280
+ query,
281
+ nativeName,
282
+ emittedWebSearchToolCalls,
283
+ send,
284
+ }: {
285
+ id: string;
286
+ query: string;
287
+ nativeName: string;
288
+ emittedWebSearchToolCalls: Set<string>;
289
+ send: Emit;
290
+ }): void {
291
+ if (emittedWebSearchToolCalls.has(id)) return;
292
+ emittedWebSearchToolCalls.add(id);
293
+ send({
294
+ type: 'tool-call',
295
+ toolCallId: id,
296
+ toolName: toCommonName(nativeName),
297
+ nativeName,
298
+ input: JSON.stringify({ query }),
299
+ providerExecuted: true,
300
+ });
301
+ }
302
+
259
303
  function extractMcpToolCallResult(item: CodexItem): unknown {
260
304
  if (
261
305
  item.result === undefined ||
@@ -4,7 +4,7 @@
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "dependencies": {
7
- "@openai/codex-sdk": "0.149.1",
7
+ "@openai/codex-sdk": "0.155.0",
8
8
  "ws": "8.21.0"
9
9
  }
10
10
  }
@@ -9,55 +9,55 @@ importers:
9
9
  .:
10
10
  dependencies:
11
11
  '@openai/codex-sdk':
12
- specifier: 0.149.1
13
- version: 0.149.1
12
+ specifier: 0.155.0
13
+ version: 0.155.0
14
14
  ws:
15
15
  specifier: 8.21.0
16
16
  version: 8.21.0
17
17
 
18
18
  packages:
19
19
 
20
- '@openai/codex-sdk@0.149.1':
21
- resolution: {integrity: sha512-R00Rz5327LefZggAxl28r7vFQq1vxa91OxtjZJOsQfAM/MyH8InW5qwwRu6pzUmRGp1E29XrOzm7u1TeV5Yz2A==}
20
+ '@openai/codex-sdk@0.155.0':
21
+ resolution: {integrity: sha512-oD/7iCZnXqDlrnoWWpGkZExrUStpAau1koyUO/fSW3WwkqewRf+MGj3OS41Buw7gNwj0oM6ilI9RHqP5QQWJaQ==}
22
22
  engines: {node: '>=18'}
23
23
 
24
- '@openai/codex@0.149.1':
25
- resolution: {integrity: sha512-6q5pbcpFbJbqOpkubSDBwXmktQ55aD8eUzGzBF1zASob2DjwhBKDSNGtdZKalfrNJUdTDTPDMmzCXEXs5tMBYA==}
24
+ '@openai/codex@0.155.0':
25
+ resolution: {integrity: sha512-35a85Hbwy9WXkDTJumLjTcmMgpR7BMdrTloWBVGjoA+FBCh7jb3+cYp2W+3U4klqGAjAskoIvQZv7/eedmCjHA==}
26
26
  engines: {node: '>=16'}
27
27
  hasBin: true
28
28
 
29
- '@openai/codex@0.149.1-darwin-arm64':
30
- resolution: {integrity: sha512-6X84kTCbnTgPIJ2EdcPsrvwS0Wxsqpa+bCswGmRf4BjhcQ5nPMnBC6yCAaCMj+vrbXQHj+L6sa9FaR4QkmA1qw==}
29
+ '@openai/codex@0.155.0-darwin-arm64':
30
+ resolution: {integrity: sha512-c0vbt2ZS6XiXq9gQkNictu7PH0deMyq/bw4Ia9bdihU1iJHuGuwC5vD4swU4xhLUKoTs4tMOHwYWKaofFZcVRg==}
31
31
  engines: {node: '>=16'}
32
32
  cpu: [arm64]
33
33
  os: [darwin]
34
34
 
35
- '@openai/codex@0.149.1-darwin-x64':
36
- resolution: {integrity: sha512-MfLBQLfcElJL9tvj6y45qVHHMGSXCPnQOixuD3/Zq0g1BW/eFizkrGLdn48cFpc+l8cK+gt5nYG5pQYwVs6g4A==}
35
+ '@openai/codex@0.155.0-darwin-x64':
36
+ resolution: {integrity: sha512-Epo33vwukf4uPyxg1c7+7+P4J6YgggdwQrCR/BRGuhjpadZAzoAntfm54CPCNHvW6uSq8BHQX/e9G+f5/0XQ7Q==}
37
37
  engines: {node: '>=16'}
38
38
  cpu: [x64]
39
39
  os: [darwin]
40
40
 
41
- '@openai/codex@0.149.1-linux-arm64':
42
- resolution: {integrity: sha512-OqxUfZ1TVvHd18zHPKK/8ZRlpk8Vy11mg5CMHaLxNWldTbwVImDKtSLWT+m8m4NM5Sz4PbjtZMrVT/RfpBW/mQ==}
41
+ '@openai/codex@0.155.0-linux-arm64':
42
+ resolution: {integrity: sha512-iMyMjIYHlBUUDeTEGeEftQozQz0h7nYYCaLDJlZnqDF2Y0+bfdD/ppDS3vsw6mIz/z49haC6TCer11iEWO6fsA==}
43
43
  engines: {node: '>=16'}
44
44
  cpu: [arm64]
45
45
  os: [linux]
46
46
 
47
- '@openai/codex@0.149.1-linux-x64':
48
- resolution: {integrity: sha512-Of5fGYgr7tAMsyj6vhXb4/RM/UoA3Zq8BLegUBDC09UNy1XTLGYP/2XD+UX8z3qh0NDwxYdCjFIWdDNijKZggQ==}
47
+ '@openai/codex@0.155.0-linux-x64':
48
+ resolution: {integrity: sha512-CiXkVTy4ERdFSXXP8n98/iOyVUCOYFPQ8cuwgeOEEbLhe1LnlQbgH/H8ZeuxO9WXKIZLJg0x626UgR9axY2zSA==}
49
49
  engines: {node: '>=16'}
50
50
  cpu: [x64]
51
51
  os: [linux]
52
52
 
53
- '@openai/codex@0.149.1-win32-arm64':
54
- resolution: {integrity: sha512-5K0DmOKGK9Bos627p8sK8ATHjovPK0sDyT6h9Cb+4v+5CW5SGw1HLgjGxoLfJ8g3cg6mtg/pRCXXo2L/j71UVA==}
53
+ '@openai/codex@0.155.0-win32-arm64':
54
+ resolution: {integrity: sha512-aFIONQhKxg+XEVsInLRI2OG8S9OUvfK+nzc4sWcbn8DgxlmuPkbVzloSrD5psh1lKnG4s2qd3QK8tCClSgLzJw==}
55
55
  engines: {node: '>=16'}
56
56
  cpu: [arm64]
57
57
  os: [win32]
58
58
 
59
- '@openai/codex@0.149.1-win32-x64':
60
- resolution: {integrity: sha512-G3QXGAg7nyyhqOeooAMUekBCeHd8a1QByhKcVAFyzNBaI06t6Ft7nsF+1SzFS0spuIdU4YyMi5YD26ukADBQUQ==}
59
+ '@openai/codex@0.155.0-win32-x64':
60
+ resolution: {integrity: sha512-yfB9DHAB0YLTy3oD4one21pzGIJ99b+U/PW4htAq6OLccPSZa8OZz5HeN3XVy1a2/BbCqwWmK+w4p/aG/3DCRw==}
61
61
  engines: {node: '>=16'}
62
62
  cpu: [x64]
63
63
  os: [win32]
@@ -76,35 +76,35 @@ packages:
76
76
 
77
77
  snapshots:
78
78
 
79
- '@openai/codex-sdk@0.149.1':
79
+ '@openai/codex-sdk@0.155.0':
80
80
  dependencies:
81
- '@openai/codex': 0.149.1
81
+ '@openai/codex': 0.155.0
82
82
 
83
- '@openai/codex@0.149.1':
83
+ '@openai/codex@0.155.0':
84
84
  optionalDependencies:
85
- '@openai/codex-darwin-arm64': '@openai/codex@0.149.1-darwin-arm64'
86
- '@openai/codex-darwin-x64': '@openai/codex@0.149.1-darwin-x64'
87
- '@openai/codex-linux-arm64': '@openai/codex@0.149.1-linux-arm64'
88
- '@openai/codex-linux-x64': '@openai/codex@0.149.1-linux-x64'
89
- '@openai/codex-win32-arm64': '@openai/codex@0.149.1-win32-arm64'
90
- '@openai/codex-win32-x64': '@openai/codex@0.149.1-win32-x64'
91
-
92
- '@openai/codex@0.149.1-darwin-arm64':
85
+ '@openai/codex-darwin-arm64': '@openai/codex@0.155.0-darwin-arm64'
86
+ '@openai/codex-darwin-x64': '@openai/codex@0.155.0-darwin-x64'
87
+ '@openai/codex-linux-arm64': '@openai/codex@0.155.0-linux-arm64'
88
+ '@openai/codex-linux-x64': '@openai/codex@0.155.0-linux-x64'
89
+ '@openai/codex-win32-arm64': '@openai/codex@0.155.0-win32-arm64'
90
+ '@openai/codex-win32-x64': '@openai/codex@0.155.0-win32-x64'
91
+
92
+ '@openai/codex@0.155.0-darwin-arm64':
93
93
  optional: true
94
94
 
95
- '@openai/codex@0.149.1-darwin-x64':
95
+ '@openai/codex@0.155.0-darwin-x64':
96
96
  optional: true
97
97
 
98
- '@openai/codex@0.149.1-linux-arm64':
98
+ '@openai/codex@0.155.0-linux-arm64':
99
99
  optional: true
100
100
 
101
- '@openai/codex@0.149.1-linux-x64':
101
+ '@openai/codex@0.155.0-linux-x64':
102
102
  optional: true
103
103
 
104
- '@openai/codex@0.149.1-win32-arm64':
104
+ '@openai/codex@0.155.0-win32-arm64':
105
105
  optional: true
106
106
 
107
- '@openai/codex@0.149.1-win32-x64':
107
+ '@openai/codex@0.155.0-win32-x64':
108
108
  optional: true
109
109
 
110
110
  ws@8.21.0: {}
@@ -39,6 +39,7 @@ import {
39
39
  waitForBridgeReady,
40
40
  withBridgeToken,
41
41
  writeSkills,
42
+ type SandboxChannelReconnectOptions,
42
43
  } from '@ai-sdk/harness/utils';
43
44
  import {
44
45
  type Experimental_SandboxProcess,
@@ -130,6 +131,13 @@ export type CodexHarnessSettings = {
130
131
  readonly portEndpoint?: HarnessV1PortEndpoint;
131
132
  /** Maximum milliseconds to wait for the bridge to advertise its port. Defaults to 120000. */
132
133
  readonly startupTimeoutMs?: number;
134
+ /**
135
+ * Configures reconnection attempts after an established bridge connection
136
+ * drops. The reconnect window includes connection establishment and
137
+ * backoff delays. Defaults to 30 seconds with exponential backoff from 50
138
+ * milliseconds up to 2 seconds.
139
+ */
140
+ readonly reconnect?: SandboxChannelReconnectOptions;
133
141
  /**
134
142
  * Creates the authentication token used by the sandbox bridge. Defaults to
135
143
  * a random 32-byte hexadecimal token.
@@ -370,11 +378,13 @@ export function createCodex(
370
378
  token: coords.token,
371
379
  });
372
380
  const attachChannel: CodexChannel = new SandboxChannel({
373
- connect: () => openWebSocket(attachEndpoint),
381
+ connect: ({ abortSignal }) =>
382
+ openWebSocket({ ...attachEndpoint, abortSignal }),
374
383
  outboundSchema: outboundMessageSchema,
375
384
  initialLastSeenEventId: coords.lastSeenEventId,
376
385
  onDiagnostic,
377
386
  onBridgeError,
387
+ reconnect: settings.reconnect,
378
388
  });
379
389
  await attachChannel.open(isContinue ? { resume: true } : undefined);
380
390
  return createSession({
@@ -526,10 +536,12 @@ export function createCodex(
526
536
  const bridgeEndpoint = withBridgeToken({ endpoint, token });
527
537
 
528
538
  const channel: CodexChannel = new SandboxChannel({
529
- connect: () => openWebSocket(bridgeEndpoint),
539
+ connect: ({ abortSignal }) =>
540
+ openWebSocket({ ...bridgeEndpoint, abortSignal }),
530
541
  outboundSchema: outboundMessageSchema,
531
542
  onDiagnostic,
532
543
  onBridgeError,
544
+ reconnect: settings.reconnect,
533
545
  // In replay mode the respawned bridge reloaded the finished turn from
534
546
  // disk; seed the cursor and resume so it streams the tail (incl.
535
547
  // `finish`).
@@ -638,21 +650,57 @@ async function resolveBridgeEndpoint({
638
650
  function openWebSocket({
639
651
  url,
640
652
  headers,
641
- }: HarnessV1PortEndpoint): Promise<WebSocket> {
653
+ abortSignal,
654
+ }: HarnessV1PortEndpoint & { abortSignal: AbortSignal }): Promise<WebSocket> {
642
655
  return new Promise((resolve, reject) => {
656
+ if (abortSignal.aborted) {
657
+ reject(abortSignal.reason ?? new Error('WebSocket connection aborted'));
658
+ return;
659
+ }
660
+
643
661
  const ws = new WebSocket(url, {
644
662
  headers: headers == null ? undefined : { ...headers },
645
663
  });
646
- const onOpen = () => {
664
+
665
+ let settled = false;
666
+ const cleanup = () => {
667
+ abortSignal.removeEventListener('abort', onAbort);
668
+ ws.off('open', onOpen);
647
669
  ws.off('error', onError);
670
+ };
671
+ const rejectWithCleanup = (error: unknown) => {
672
+ if (settled) return;
673
+ settled = true;
674
+ cleanup();
675
+ ws.once('error', () => {});
676
+ try {
677
+ ws.terminate();
678
+ } catch {
679
+ try {
680
+ ws.close();
681
+ } catch {
682
+ // best-effort
683
+ }
684
+ }
685
+ reject(error);
686
+ };
687
+ const onOpen = () => {
688
+ if (settled) return;
689
+ settled = true;
690
+ cleanup();
648
691
  resolve(ws);
649
692
  };
650
693
  const onError = (err: Error) => {
651
- ws.off('open', onOpen);
652
- reject(err);
694
+ rejectWithCleanup(err);
653
695
  };
696
+ const onAbort = () =>
697
+ rejectWithCleanup(
698
+ abortSignal.reason ?? new Error('WebSocket connection aborted'),
699
+ );
700
+
654
701
  ws.once('open', onOpen);
655
702
  ws.once('error', onError);
703
+ abortSignal.addEventListener('abort', onAbort, { once: true });
656
704
  });
657
705
  }
658
706