@ai-sdk/harness-codex 1.0.79 → 1.0.81

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.
@@ -24,8 +24,10 @@ import {
24
24
  createBridgeStartupError,
25
25
  drainBridgeProcessStream,
26
26
  forwardBridgeProcessStream,
27
+ getRestrictedSandboxSession,
27
28
  markBridgeStarting,
28
29
  maskSandboxCredentials,
30
+ resolveSandboxDefaultWorkingDirectory,
29
31
  resolveSandboxHomeDir,
30
32
  SandboxChannel,
31
33
  shellQuote,
@@ -35,7 +37,7 @@ import {
35
37
  } from '@ai-sdk/harness/utils';
36
38
  import {
37
39
  type Experimental_SandboxProcess,
38
- type Experimental_SandboxSession,
40
+ type Experimental_SandboxSession as SandboxSession,
39
41
  } from '@ai-sdk/provider-utils';
40
42
  import { WebSocket } from 'ws';
41
43
  import { z } from 'zod/v4';
@@ -118,6 +120,11 @@ export type CodexHarnessSettings = {
118
120
  * is reserved for something else.
119
121
  */
120
122
  readonly port?: number;
123
+ /**
124
+ * Override the host endpoint used to connect to the sandbox bridge. Required
125
+ * together with `port` when using a basic sandbox session.
126
+ */
127
+ readonly portEndpoint?: HarnessV1PortEndpoint;
121
128
  /** Maximum milliseconds to wait for the bridge to advertise its port. Defaults to 120000. */
122
129
  readonly startupTimeoutMs?: number;
123
130
  /**
@@ -208,10 +215,33 @@ export function createCodex(
208
215
  });
209
216
  }
210
217
  const sandboxSession = startOpts.sandboxSession;
218
+ const toolSafeSandboxSession =
219
+ getRestrictedSandboxSession(sandboxSession);
220
+ const sandboxId = 'id' in sandboxSession ? sandboxSession.id : undefined;
221
+ validateBasicSandboxSettings({
222
+ sandboxSession,
223
+ port: settings.port,
224
+ portEndpoint: settings.portEndpoint,
225
+ });
226
+ if (settings.mintBridgeToken != null && sandboxId == null) {
227
+ throw new HarnessCapabilityUnsupportedError({
228
+ harnessId: 'codex',
229
+ message:
230
+ 'The codex harness cannot use `mintBridgeToken` with a sandbox session that does not expose an id.',
231
+ });
232
+ }
233
+ const defaultWorkingDirectory =
234
+ await resolveSandboxDefaultWorkingDirectory({
235
+ sandboxSession,
236
+ abortSignal: startOpts.abortSignal,
237
+ });
211
238
  const authenticationMode = resolveCodexAuthenticationMode(settings.auth);
212
239
  const resolvedAuthEnvironment = resolveCodexEnv(settings.auth);
213
240
  let sandboxAuthEnvironment = resolvedAuthEnvironment;
214
- if (sandboxSession.addRequestTransformations != null) {
241
+ if (
242
+ 'addRequestTransformations' in sandboxSession &&
243
+ sandboxSession.addRequestTransformations != null
244
+ ) {
215
245
  const requestTransformations = createCodexRequestTransformations(
216
246
  resolvedAuthEnvironment,
217
247
  authenticationMode,
@@ -242,10 +272,8 @@ export function createCodex(
242
272
  } else {
243
273
  warnCredentialBrokeringUnavailable();
244
274
  }
245
- const session = sandboxSession.restricted();
246
- const sandboxId = sandboxSession.id;
247
275
  const bootstrapDir = path.posix.resolve(
248
- sandboxSession.defaultWorkingDirectory,
276
+ defaultWorkingDirectory,
249
277
  BOOTSTRAP_DIR,
250
278
  );
251
279
  const lifecycleState = startOpts.continueFrom ?? startOpts.resumeFrom;
@@ -266,7 +294,7 @@ export function createCodex(
266
294
  const coords = resumeData?.bridge;
267
295
 
268
296
  const workDir = startOpts.sessionWorkDir;
269
- const sessionDataDir = `${sandboxSession.defaultWorkingDirectory}/.agent-runs/${startOpts.sessionId}`;
297
+ const sessionDataDir = `${defaultWorkingDirectory}/.agent-runs/${startOpts.sessionId}`;
270
298
  const bridgeStateDir = `${sessionDataDir}/bridge`;
271
299
  const cliShimDir = `${sessionDataDir}/codex`;
272
300
  const cliShimPath = `${cliShimDir}/${CLI_SHIM_FILENAME}`;
@@ -300,9 +328,10 @@ export function createCodex(
300
328
  */
301
329
  if (coords) {
302
330
  try {
303
- const endpoint = await sandboxSession.getPortEndpoint({
331
+ const endpoint = await resolveBridgeEndpoint({
332
+ sandboxSession,
333
+ override: settings.portEndpoint,
304
334
  port: coords.port,
305
- protocol: 'ws',
306
335
  });
307
336
  const attachEndpoint = withBridgeToken({
308
337
  endpoint,
@@ -355,7 +384,7 @@ export function createCodex(
355
384
  : undefined;
356
385
  if (coords && isContinue) {
357
386
  const logRaw = await Promise.resolve(
358
- session.readTextFile({
387
+ toolSafeSandboxSession.readTextFile({
359
388
  path: `${bridgeStateDir}/event-log.ndjson`,
360
389
  abortSignal: startOpts.abortSignal,
361
390
  }),
@@ -365,15 +394,18 @@ export function createCodex(
365
394
  }
366
395
  }
367
396
 
368
- const port = resolveBridgePort(sandboxSession, settings.port);
397
+ const port = resolveBridgePort({
398
+ sandboxSession,
399
+ override: settings.port,
400
+ });
369
401
  const token =
370
402
  settings.mintBridgeToken == null
371
403
  ? randomBytes(32).toString('hex')
372
- : settings.mintBridgeToken(sandboxId);
404
+ : settings.mintBridgeToken(sandboxId!);
373
405
  const codexSkillSetup =
374
406
  startOpts.skills && startOpts.skills.length > 0
375
407
  ? await writeCodexSkills({
376
- sandbox: session,
408
+ sandbox: toolSafeSandboxSession,
377
409
  skills: startOpts.skills,
378
410
  abortSignal: startOpts.abortSignal,
379
411
  })
@@ -395,20 +427,20 @@ export function createCodex(
395
427
  };
396
428
 
397
429
  if (respawnStrategy === undefined) {
398
- await session.run({
430
+ await toolSafeSandboxSession.run({
399
431
  command: `mkdir -p ${shellQuote(workDir)} ${shellQuote(bridgeStateDir)}`,
400
432
  abortSignal: startOpts.abortSignal,
401
433
  });
402
434
  }
403
435
 
404
436
  await markBridgeStarting({
405
- sandbox: session,
437
+ sandbox: toolSafeSandboxSession,
406
438
  bridgeStateDir,
407
439
  bridgeType: 'codex',
408
440
  abortSignal: startOpts.abortSignal,
409
441
  });
410
442
 
411
- const proc = await session.spawn({
443
+ const proc = await toolSafeSandboxSession.spawn({
412
444
  command: `node ${shellQuote(`${bootstrapDir}/bridge.mjs`)} --workdir ${shellQuote(workDir)} --bridge-state-dir ${shellQuote(bridgeStateDir)} --cli-shim-dir ${shellQuote(cliShimDir)}`,
413
445
  env,
414
446
  abortSignal: startOpts.abortSignal,
@@ -423,7 +455,7 @@ export function createCodex(
423
455
 
424
456
  const { port: boundPort } = await waitForBridgeReady({
425
457
  proc,
426
- sandbox: session,
458
+ sandbox: toolSafeSandboxSession,
427
459
  bridgeStateDir,
428
460
  bridgeType: 'codex',
429
461
  timeoutMs,
@@ -447,9 +479,10 @@ export function createCodex(
447
479
  });
448
480
  void drainBridgeProcessStream(proc.stdout);
449
481
 
450
- const endpoint = await sandboxSession.getPortEndpoint({
482
+ const endpoint = await resolveBridgeEndpoint({
483
+ sandboxSession,
484
+ override: settings.portEndpoint,
451
485
  port: boundPort,
452
- protocol: 'ws',
453
486
  });
454
487
  const bridgeEndpoint = withBridgeToken({ endpoint, token });
455
488
 
@@ -493,12 +526,17 @@ export function createCodex(
493
526
  };
494
527
  }
495
528
 
496
- function resolveBridgePort(
497
- sandboxSession: HarnessV1NetworkSandboxSession,
498
- override: number | undefined,
499
- ): number {
529
+ function resolveBridgePort({
530
+ sandboxSession,
531
+ override,
532
+ }: {
533
+ sandboxSession: HarnessV1NetworkSandboxSession | SandboxSession;
534
+ override: number | undefined;
535
+ }): number {
500
536
  if (override !== undefined) return override;
501
- if (sandboxSession.ports.length > 0) return sandboxSession.ports[0];
537
+ if ('ports' in sandboxSession && sandboxSession.ports.length > 0) {
538
+ return sandboxSession.ports[0];
539
+ }
502
540
  throw new HarnessCapabilityUnsupportedError({
503
541
  harnessId: 'codex',
504
542
  message:
@@ -507,12 +545,58 @@ function resolveBridgePort(
507
545
  });
508
546
  }
509
547
 
548
+ function validateBasicSandboxSettings({
549
+ sandboxSession,
550
+ port,
551
+ portEndpoint,
552
+ }: {
553
+ sandboxSession: HarnessV1NetworkSandboxSession | SandboxSession;
554
+ port: number | undefined;
555
+ portEndpoint: HarnessV1PortEndpoint | undefined;
556
+ }): void {
557
+ if ('getPortEndpoint' in sandboxSession) return;
558
+ if (port == null) {
559
+ throw new HarnessCapabilityUnsupportedError({
560
+ harnessId: 'codex',
561
+ message:
562
+ 'The codex harness requires an explicit `port` when using a basic sandbox session.',
563
+ });
564
+ }
565
+ if (portEndpoint == null) {
566
+ throw new HarnessCapabilityUnsupportedError({
567
+ harnessId: 'codex',
568
+ message:
569
+ 'The codex harness requires an explicit `portEndpoint` when using a basic sandbox session.',
570
+ });
571
+ }
572
+ }
573
+
574
+ async function resolveBridgeEndpoint({
575
+ sandboxSession,
576
+ override,
577
+ port,
578
+ }: {
579
+ sandboxSession: HarnessV1NetworkSandboxSession | SandboxSession;
580
+ override: HarnessV1PortEndpoint | undefined;
581
+ port: number;
582
+ }): Promise<HarnessV1PortEndpoint> {
583
+ if (override != null) return override;
584
+ if ('getPortEndpoint' in sandboxSession) {
585
+ return sandboxSession.getPortEndpoint({ port, protocol: 'ws' });
586
+ }
587
+ throw new HarnessCapabilityUnsupportedError({
588
+ harnessId: 'codex',
589
+ message:
590
+ 'The codex harness requires an explicit `portEndpoint` when using a basic sandbox session.',
591
+ });
592
+ }
593
+
510
594
  async function writeCodexSkills({
511
595
  sandbox,
512
596
  skills,
513
597
  abortSignal,
514
598
  }: {
515
- sandbox: Experimental_SandboxSession;
599
+ sandbox: SandboxSession;
516
600
  skills: ReadonlyArray<HarnessV1Skill>;
517
601
  abortSignal?: AbortSignal;
518
602
  }): Promise<WriteSkillsResult> {
@@ -609,7 +693,7 @@ function createSession({
609
693
  rerunContinue: boolean;
610
694
  bridgePort: number;
611
695
  bridgeToken: string;
612
- sandboxId: string;
696
+ sandboxId: string | undefined;
613
697
  debug: HarnessV1DebugConfig | undefined;
614
698
  permissionMode: HarnessV1PermissionMode | undefined;
615
699
  }): HarnessV1Session {
@@ -772,9 +856,6 @@ function createSession({
772
856
  reason: input.reason,
773
857
  });
774
858
  },
775
- submitUserMessage: async text => {
776
- channel.send({ type: 'user-message', text });
777
- },
778
859
  done,
779
860
  };
780
861
 
@@ -964,7 +1045,7 @@ function createSession({
964
1045
  port: bridgePort,
965
1046
  token: bridgeToken,
966
1047
  lastSeenEventId,
967
- sandboxId,
1048
+ ...(sandboxId == null ? {} : { sandboxId }),
968
1049
  },
969
1050
  },
970
1051
  };
@@ -1102,7 +1183,7 @@ function createSession({
1102
1183
  port: bridgePort,
1103
1184
  token: bridgeToken,
1104
1185
  lastSeenEventId,
1105
- sandboxId,
1186
+ ...(sandboxId == null ? {} : { sandboxId }),
1106
1187
  },
1107
1188
  },
1108
1189
  };