@ai-sdk/harness-deepagents 1.0.77 → 1.0.79

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,7 +24,9 @@ import {
24
24
  createBridgeStartupError,
25
25
  drainBridgeProcessStream,
26
26
  forwardBridgeProcessStream,
27
+ getRestrictedSandboxSession,
27
28
  maskSandboxCredentials,
29
+ resolveSandboxDefaultWorkingDirectory,
28
30
  resolveSandboxHomeDir,
29
31
  SandboxChannel,
30
32
  shellQuote,
@@ -32,7 +34,11 @@ import {
32
34
  waitForBridgeReady,
33
35
  writeSkills as writeHarnessSkills,
34
36
  } from '@ai-sdk/harness/utils';
35
- import { tool, type Experimental_SandboxProcess } from '@ai-sdk/provider-utils';
37
+ import {
38
+ tool,
39
+ type Experimental_SandboxProcess,
40
+ type Experimental_SandboxSession as SandboxSession,
41
+ } from '@ai-sdk/provider-utils';
36
42
  import { WebSocket } from 'ws';
37
43
  import { z } from 'zod/v4';
38
44
  import {
@@ -93,6 +99,11 @@ export type DeepAgentsHarnessSettings = {
93
99
  readonly effort?: 'low' | 'medium' | 'high' | 'xhigh' | 'max';
94
100
  /** Bridge port override; defaults to the sandbox's first declared port. */
95
101
  readonly port?: number;
102
+ /**
103
+ * Override the host endpoint used to connect to the sandbox bridge. Required
104
+ * together with `port` when using a basic sandbox session.
105
+ */
106
+ readonly portEndpoint?: HarnessV1PortEndpoint;
96
107
  /** Maximum milliseconds to wait for the bridge to advertise its port. Defaults to 120000. */
97
108
  readonly startupTimeoutMs?: number;
98
109
  /**
@@ -198,6 +209,26 @@ export function createDeepAgents(
198
209
  doStart: async startOpts => {
199
210
  const permissionMode = startOpts.permissionMode;
200
211
  const sandboxSession = startOpts.sandboxSession;
212
+ const toolSafeSandboxSession =
213
+ getRestrictedSandboxSession(sandboxSession);
214
+ const sandboxId = 'id' in sandboxSession ? sandboxSession.id : undefined;
215
+ validateBasicSandboxSettings({
216
+ sandboxSession,
217
+ port: settings.port,
218
+ portEndpoint: settings.portEndpoint,
219
+ });
220
+ if (settings.mintBridgeToken != null && sandboxId == null) {
221
+ throw new HarnessCapabilityUnsupportedError({
222
+ harnessId: 'deepagents',
223
+ message:
224
+ 'The deepagents harness cannot use `mintBridgeToken` with a sandbox session that does not expose an id.',
225
+ });
226
+ }
227
+ const defaultWorkingDirectory =
228
+ await resolveSandboxDefaultWorkingDirectory({
229
+ sandboxSession,
230
+ abortSignal: startOpts.abortSignal,
231
+ });
201
232
  const authenticationMode = resolveDeepAgentsAuthenticationMode({
202
233
  auth: settings.auth,
203
234
  });
@@ -205,7 +236,10 @@ export function createDeepAgents(
205
236
  auth: settings.auth,
206
237
  });
207
238
  let sandboxAuthEnvironment = resolvedAuthEnvironment;
208
- if (sandboxSession.addRequestTransformations != null) {
239
+ if (
240
+ 'addRequestTransformations' in sandboxSession &&
241
+ sandboxSession.addRequestTransformations != null
242
+ ) {
209
243
  const requestTransformations = createDeepAgentsRequestTransformations(
210
244
  resolvedAuthEnvironment,
211
245
  authenticationMode,
@@ -223,10 +257,8 @@ export function createDeepAgents(
223
257
  } else {
224
258
  warnCredentialBrokeringUnavailable();
225
259
  }
226
- const session = sandboxSession.restricted();
227
- const sandboxId = sandboxSession.id;
228
260
  const bootstrapDir = posix.resolve(
229
- sandboxSession.defaultWorkingDirectory,
261
+ defaultWorkingDirectory,
230
262
  BOOTSTRAP_DIR,
231
263
  );
232
264
 
@@ -239,7 +271,7 @@ export function createDeepAgents(
239
271
  : undefined;
240
272
 
241
273
  const workDir = startOpts.sessionWorkDir;
242
- const sessionDataDir = `${sandboxSession.defaultWorkingDirectory}/.agent-runs/${startOpts.sessionId}`;
274
+ const sessionDataDir = `${defaultWorkingDirectory}/.agent-runs/${startOpts.sessionId}`;
243
275
  const bridgeStateDir = `${sessionDataDir}/bridge`;
244
276
  const timeoutMs = settings.startupTimeoutMs ?? 120_000;
245
277
 
@@ -261,9 +293,10 @@ export function createDeepAgents(
261
293
  // Attach to the still-running bridge (continueFrom replays past the cursor); on failure fall through to a fresh spawn.
262
294
  if (coords) {
263
295
  try {
264
- const endpoint = await sandboxSession.getPortEndpoint({
296
+ const endpoint = await resolveBridgeEndpoint({
297
+ sandboxSession,
298
+ override: settings.portEndpoint,
265
299
  port: coords.port,
266
- protocol: 'ws',
267
300
  });
268
301
  const attachEndpoint = withBridgeToken({
269
302
  endpoint,
@@ -299,11 +332,14 @@ export function createDeepAgents(
299
332
  }
300
333
  }
301
334
 
302
- const port = resolveBridgePort(sandboxSession, settings.port);
335
+ const port = resolveBridgePort({
336
+ sandboxSession,
337
+ override: settings.port,
338
+ });
303
339
  const token =
304
340
  settings.mintBridgeToken == null
305
341
  ? randomBytes(32).toString('hex')
306
- : settings.mintBridgeToken(sandboxId);
342
+ : settings.mintBridgeToken(sandboxId!);
307
343
 
308
344
  // Always discover repo-provided skills under <workDir>/.agents/skills (e.g. a cloned repo); a missing dir is tolerated by deepagents.
309
345
  // Absolute paths: LocalShellBackend (non-virtual) treats a leading-slash path as a real fs path.
@@ -311,12 +347,12 @@ export function createDeepAgents(
311
347
  // Host-provided skills go to $HOME (out of the work dir) and take priority (listed last → wins on name collision).
312
348
  if ((startOpts.skills?.length ?? 0) > 0) {
313
349
  const homeDir = await resolveSandboxHomeDir({
314
- sandbox: session,
350
+ sandbox: toolSafeSandboxSession,
315
351
  abortSignal: startOpts.abortSignal,
316
352
  });
317
353
  const homeSkillsRoot = `${homeDir}${SKILLS_SOURCE_PATH}`;
318
354
  await writeSkills({
319
- sandbox: session,
355
+ sandbox: toolSafeSandboxSession,
320
356
  root: homeSkillsRoot,
321
357
  skills: startOpts.skills ?? [],
322
358
  abortSignal: startOpts.abortSignal,
@@ -331,19 +367,19 @@ export function createDeepAgents(
331
367
  BRIDGE_WS_PORT: String(port),
332
368
  };
333
369
 
334
- await session.run({
370
+ await toolSafeSandboxSession.run({
335
371
  command: `mkdir -p ${shellQuote(workDir)} ${shellQuote(bridgeStateDir)}`,
336
372
  abortSignal: startOpts.abortSignal,
337
373
  });
338
374
 
339
375
  await markBridgeStarting({
340
- sandbox: session,
376
+ sandbox: toolSafeSandboxSession,
341
377
  bridgeStateDir,
342
378
  bridgeType: 'deepagents',
343
379
  abortSignal: startOpts.abortSignal,
344
380
  });
345
381
 
346
- const proc = await session.spawn({
382
+ const proc = await toolSafeSandboxSession.spawn({
347
383
  command: `node ${shellQuote(`${bootstrapDir}/bridge.mjs`)} --workdir ${shellQuote(workDir)} --bridge-state-dir ${shellQuote(bridgeStateDir)} --bootstrap-dir ${shellQuote(bootstrapDir)}`,
348
384
  env,
349
385
  abortSignal: startOpts.abortSignal,
@@ -358,7 +394,7 @@ export function createDeepAgents(
358
394
 
359
395
  const { port: boundPort } = await waitForBridgeReady({
360
396
  proc,
361
- sandbox: session,
397
+ sandbox: toolSafeSandboxSession,
362
398
  bridgeStateDir,
363
399
  bridgeType: 'deepagents',
364
400
  timeoutMs,
@@ -382,9 +418,10 @@ export function createDeepAgents(
382
418
  });
383
419
  void drainBridgeProcessStream(proc.stdout);
384
420
 
385
- const endpoint = await sandboxSession.getPortEndpoint({
421
+ const endpoint = await resolveBridgeEndpoint({
422
+ sandboxSession,
423
+ override: settings.portEndpoint,
386
424
  port: boundPort,
387
- protocol: 'ws',
388
425
  });
389
426
  const bridgeEndpoint = withBridgeToken({ endpoint, token });
390
427
 
@@ -419,12 +456,17 @@ export function createDeepAgents(
419
456
  };
420
457
  }
421
458
 
422
- function resolveBridgePort(
423
- sandboxSession: HarnessV1NetworkSandboxSession,
424
- override: number | undefined,
425
- ): number {
459
+ function resolveBridgePort({
460
+ sandboxSession,
461
+ override,
462
+ }: {
463
+ sandboxSession: HarnessV1NetworkSandboxSession | SandboxSession;
464
+ override: number | undefined;
465
+ }): number {
426
466
  if (override !== undefined) return override;
427
- if (sandboxSession.ports.length > 0) return sandboxSession.ports[0];
467
+ if ('ports' in sandboxSession && sandboxSession.ports.length > 0) {
468
+ return sandboxSession.ports[0];
469
+ }
428
470
  throw new HarnessCapabilityUnsupportedError({
429
471
  harnessId: 'deepagents',
430
472
  message:
@@ -433,6 +475,52 @@ function resolveBridgePort(
433
475
  });
434
476
  }
435
477
 
478
+ function validateBasicSandboxSettings({
479
+ sandboxSession,
480
+ port,
481
+ portEndpoint,
482
+ }: {
483
+ sandboxSession: HarnessV1NetworkSandboxSession | SandboxSession;
484
+ port: number | undefined;
485
+ portEndpoint: HarnessV1PortEndpoint | undefined;
486
+ }): void {
487
+ if ('getPortEndpoint' in sandboxSession) return;
488
+ if (port == null) {
489
+ throw new HarnessCapabilityUnsupportedError({
490
+ harnessId: 'deepagents',
491
+ message:
492
+ 'The deepagents harness requires an explicit `port` when using a basic sandbox session.',
493
+ });
494
+ }
495
+ if (portEndpoint == null) {
496
+ throw new HarnessCapabilityUnsupportedError({
497
+ harnessId: 'deepagents',
498
+ message:
499
+ 'The deepagents harness requires an explicit `portEndpoint` when using a basic sandbox session.',
500
+ });
501
+ }
502
+ }
503
+
504
+ async function resolveBridgeEndpoint({
505
+ sandboxSession,
506
+ override,
507
+ port,
508
+ }: {
509
+ sandboxSession: HarnessV1NetworkSandboxSession | SandboxSession;
510
+ override: HarnessV1PortEndpoint | undefined;
511
+ port: number;
512
+ }): Promise<HarnessV1PortEndpoint> {
513
+ if (override != null) return override;
514
+ if ('getPortEndpoint' in sandboxSession) {
515
+ return sandboxSession.getPortEndpoint({ port, protocol: 'ws' });
516
+ }
517
+ throw new HarnessCapabilityUnsupportedError({
518
+ harnessId: 'deepagents',
519
+ message:
520
+ 'The deepagents harness requires an explicit `portEndpoint` when using a basic sandbox session.',
521
+ });
522
+ }
523
+
436
524
  // Materialize each skill as a native deepagents `<name>/SKILL.md` folder (+ attached files) under the given root, so skills load on demand and file references resolve.
437
525
  async function writeSkills({
438
526
  sandbox,
@@ -440,7 +528,7 @@ async function writeSkills({
440
528
  skills,
441
529
  abortSignal,
442
530
  }: {
443
- sandbox: ReturnType<HarnessV1NetworkSandboxSession['restricted']>;
531
+ sandbox: SandboxSession;
444
532
  root: string;
445
533
  skills: ReadonlyArray<HarnessV1Skill>;
446
534
  abortSignal?: AbortSignal;
@@ -523,7 +611,7 @@ function createSession({
523
611
  effort: 'low' | 'medium' | 'high' | 'xhigh' | 'max' | undefined;
524
612
  bridgePort: number;
525
613
  bridgeToken: string;
526
- sandboxId: string;
614
+ sandboxId: string | undefined;
527
615
  isResume: boolean;
528
616
  // True only when attaching to a live bridge that already built the agent with
529
617
  // its instructions. A fresh spawn (incl. a respawn on attach failure or a
@@ -641,9 +729,6 @@ function createSession({
641
729
  isError: input.isError,
642
730
  });
643
731
  },
644
- submitUserMessage: async text => {
645
- channel.send({ type: 'user-message', text });
646
- },
647
732
  submitToolApproval: async input => {
648
733
  channel.send({
649
734
  type: 'tool-approval-response',
@@ -746,7 +831,7 @@ function createSession({
746
831
  port: bridgePort,
747
832
  token: bridgeToken,
748
833
  lastSeenEventId,
749
- sandboxId,
834
+ ...(sandboxId == null ? {} : { sandboxId }),
750
835
  },
751
836
  },
752
837
  };
@@ -770,7 +855,7 @@ function createSession({
770
855
  port: bridgePort,
771
856
  token: bridgeToken,
772
857
  lastSeenEventId,
773
- sandboxId,
858
+ ...(sandboxId == null ? {} : { sandboxId }),
774
859
  },
775
860
  },
776
861
  };