@ai-sdk/harness-acp 1.0.15 → 1.0.16

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/harness-acp",
3
- "version": "1.0.15",
3
+ "version": "1.0.16",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -27,7 +27,7 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "ws": "^8.21.0",
30
- "@ai-sdk/harness": "1.0.77",
30
+ "@ai-sdk/harness": "1.0.78",
31
31
  "@ai-sdk/provider-utils": "5.0.28"
32
32
  },
33
33
  "peerDependencies": {
@@ -1,4 +1,4 @@
1
- import type { HarnessV1 } from '@ai-sdk/harness';
1
+ import type { HarnessV1, HarnessV1PortEndpoint } from '@ai-sdk/harness';
2
2
  import type { ToolSet } from '@ai-sdk/provider-utils';
3
3
  import { z } from 'zod/v4';
4
4
  import type { ACPClientApp } from './acp-auth';
@@ -24,6 +24,11 @@ export type ACPHarnessSettings<TBuiltinTools extends ToolSet = {}> = {
24
24
  readonly mcpServers?: Record<string, unknown>;
25
25
  readonly isMcpToolCall?: (toolCall: ACPToolCall) => boolean;
26
26
  readonly port?: number;
27
+ /**
28
+ * Override the host endpoint used to connect to the sandbox bridge. Required
29
+ * together with `port` when using a basic sandbox session.
30
+ */
31
+ readonly portEndpoint?: HarnessV1PortEndpoint;
27
32
  readonly startupTimeoutMs?: number;
28
33
  readonly clientApp?: ACPClientApp;
29
34
  readonly version?: ACPV1Settings['version'];
@@ -125,6 +130,7 @@ export function createACP<TBuiltinTools extends ToolSet = {}>(
125
130
  builtinTools:
126
131
  settings.builtinTools ?? (ACP_BUILTIN_TOOLS as TBuiltinTools),
127
132
  port: settings.port,
133
+ portEndpoint: settings.portEndpoint,
128
134
  startupTimeoutMs: settings.startupTimeoutMs,
129
135
  clientApp,
130
136
  lifecycleStateSchema: acpResumeStateSchema,
@@ -19,8 +19,10 @@ import {
19
19
  classifyDiskLog,
20
20
  drainBridgeProcessStream,
21
21
  forwardBridgeProcessStream,
22
+ getRestrictedSandboxSession,
22
23
  markBridgeStarting,
23
24
  maskSandboxCredentials,
25
+ resolveSandboxDefaultWorkingDirectory,
24
26
  resolveSandboxHomeDir,
25
27
  SandboxChannel,
26
28
  shellQuote,
@@ -30,6 +32,7 @@ import {
30
32
  import {
31
33
  asSchema,
32
34
  type Experimental_SandboxProcess,
35
+ type Experimental_SandboxSession as SandboxSession,
33
36
  type ToolSet,
34
37
  } from '@ai-sdk/provider-utils';
35
38
  import { WebSocket } from 'ws';
@@ -114,6 +117,7 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
114
117
  settings,
115
118
  builtinTools,
116
119
  port: portOverride,
120
+ portEndpoint: portEndpointOverride,
117
121
  startupTimeoutMs,
118
122
  clientApp,
119
123
  lifecycleStateSchema,
@@ -121,6 +125,7 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
121
125
  settings: ACPV1Settings;
122
126
  builtinTools: TBuiltinTools;
123
127
  port?: number;
128
+ portEndpoint?: HarnessV1PortEndpoint;
124
129
  startupTimeoutMs?: number;
125
130
  clientApp: ACPClientApp;
126
131
  lifecycleStateSchema: NonNullable<
@@ -210,6 +215,26 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
210
215
  compatibility: providerAuthenticationCompatibility,
211
216
  });
212
217
  const sandboxSession = startOptions.sandboxSession;
218
+ const toolSafeSandboxSession =
219
+ getRestrictedSandboxSession(sandboxSession);
220
+ const sandboxId = 'id' in sandboxSession ? sandboxSession.id : undefined;
221
+ validateBasicSandboxSettings({
222
+ sandboxSession,
223
+ port: portOverride,
224
+ portEndpoint: portEndpointOverride,
225
+ harnessId: settings.harnessId,
226
+ });
227
+ if (settings.mintBridgeToken != null && sandboxId == null) {
228
+ throw unsupported({
229
+ harnessId: settings.harnessId,
230
+ message: `The ${settings.harnessId} ACP 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: startOptions.abortSignal,
237
+ });
213
238
  const implementationEnvironment = resolveImplementationEnvironment({
214
239
  implementation,
215
240
  env,
@@ -221,6 +246,7 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
221
246
 
222
247
  if (
223
248
  settings.credentialBrokering != null &&
249
+ 'addRequestTransformations' in sandboxSession &&
224
250
  sandboxSession.addRequestTransformations != null
225
251
  ) {
226
252
  const providerEnvironment = resolveProviderEnvironment({
@@ -275,15 +301,14 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
275
301
  } else if (settings.credentialBrokering != null) {
276
302
  warnCredentialBrokeringUnavailable();
277
303
  }
278
- const sandbox = sandboxSession.restricted();
279
304
  const resolvedBridgeDir = posix.resolve(
280
- sandboxSession.defaultWorkingDirectory,
305
+ defaultWorkingDirectory,
281
306
  bootstrap.bootstrapDir,
282
307
  );
283
308
  const resolvedImplementationDir = `${resolvedBridgeDir}/implementation`;
284
309
  const workDir = startOptions.sessionWorkDir;
285
310
  const sandboxHomeDir = await resolveSandboxHomeDir({
286
- sandbox,
311
+ sandbox: toolSafeSandboxSession,
287
312
  abortSignal: startOptions.abortSignal,
288
313
  });
289
314
  const privateSessionDir = resolveACPPrivateSessionDirectory({
@@ -309,7 +334,7 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
309
334
  implementationIdentity,
310
335
  authenticationProfile,
311
336
  lifecycleData,
312
- sandboxId: sandboxSession.id,
337
+ sandboxId,
313
338
  });
314
339
  }
315
340
  const skills = startOptions.skills ?? [];
@@ -323,7 +348,7 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
323
348
  if (skills.length > 0) {
324
349
  skillCatalog = (
325
350
  await materializeACPSkills({
326
- sandbox,
351
+ sandbox: toolSafeSandboxSession,
327
352
  sandboxHomeDir,
328
353
  sessionWorkDir: workDir,
329
354
  harnessId: settings.harnessId,
@@ -365,9 +390,11 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
365
390
  }
366
391
  if (coords != null) {
367
392
  try {
368
- const endpoint = await sandboxSession.getPortEndpoint({
393
+ const endpoint = await resolveBridgeEndpoint({
394
+ sandboxSession,
395
+ override: portEndpointOverride,
369
396
  port: coords.port,
370
- protocol: 'ws',
397
+ harnessId: settings.harnessId,
371
398
  });
372
399
  const attachEndpoint = withBridgeToken({
373
400
  endpoint,
@@ -407,7 +434,7 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
407
434
  acpSessionId: lifecycleData.acpSessionId,
408
435
  bridgePort: coords.port,
409
436
  bridgeToken: coords.token,
410
- sandboxId: sandboxSession.id,
437
+ sandboxId,
411
438
  isResume: true,
412
439
  turnInFlight: isContinue,
413
440
  bridgeStateDir,
@@ -420,7 +447,7 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
420
447
  } catch (error) {
421
448
  if (isContinue) {
422
449
  const eventLog = await Promise.resolve(
423
- sandbox.readTextFile({
450
+ toolSafeSandboxSession.readTextFile({
424
451
  path: `${bridgeStateDir}/event-log.ndjson`,
425
452
  abortSignal: startOptions.abortSignal,
426
453
  }),
@@ -502,19 +529,19 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
502
529
  const token =
503
530
  settings.mintBridgeToken == null
504
531
  ? randomBytes(32).toString('hex')
505
- : settings.mintBridgeToken(sandboxSession.id);
506
- await sandbox.run({
532
+ : settings.mintBridgeToken(sandboxId!);
533
+ await toolSafeSandboxSession.run({
507
534
  command: `mkdir -p ${shellQuote(workDir)} ${shellQuote(bridgeStateDir)}`,
508
535
  abortSignal: startOptions.abortSignal,
509
536
  });
510
537
  await markBridgeStarting({
511
- sandbox,
538
+ sandbox: toolSafeSandboxSession,
512
539
  bridgeStateDir,
513
540
  bridgeType: settings.harnessId,
514
541
  abortSignal: startOptions.abortSignal,
515
542
  });
516
543
 
517
- const proc = await sandbox.spawn({
544
+ const proc = await toolSafeSandboxSession.spawn({
518
545
  command:
519
546
  `node ${shellQuote(`${resolvedBridgeDir}/bridge.mjs`)}` +
520
547
  ` --workdir ${shellQuote(workDir)}` +
@@ -551,7 +578,7 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
551
578
  const timeoutMs = startupTimeoutMs ?? 120_000;
552
579
  const { port: boundPort } = await waitForBridgeReady({
553
580
  proc,
554
- sandbox,
581
+ sandbox: toolSafeSandboxSession,
555
582
  bridgeStateDir,
556
583
  bridgeType: settings.harnessId,
557
584
  timeoutMs,
@@ -575,9 +602,11 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
575
602
  });
576
603
  void drainBridgeProcessStream(proc.stdout);
577
604
 
578
- const endpoint = await sandboxSession.getPortEndpoint({
605
+ const endpoint = await resolveBridgeEndpoint({
606
+ sandboxSession,
607
+ override: portEndpointOverride,
579
608
  port: boundPort,
580
- protocol: 'ws',
609
+ harnessId: settings.harnessId,
581
610
  });
582
611
  const bridgeEndpoint = withBridgeToken({ endpoint, token });
583
612
  const channel: ACPChannel = new SandboxChannel({
@@ -660,7 +689,7 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
660
689
  acpSessionId: lifecycleData?.acpSessionId,
661
690
  bridgePort: boundPort,
662
691
  bridgeToken: token,
663
- sandboxId: sandboxSession.id,
692
+ sandboxId,
664
693
  isResume,
665
694
  turnInFlight:
666
695
  respawnStrategy?.mode === 'disk-replay' ||
@@ -745,12 +774,14 @@ function resolveBridgePort({
745
774
  override,
746
775
  harnessId,
747
776
  }: {
748
- sandboxSession: HarnessV1NetworkSandboxSession;
777
+ sandboxSession: HarnessV1NetworkSandboxSession | SandboxSession;
749
778
  override: number | undefined;
750
779
  harnessId: string;
751
780
  }): number {
752
781
  if (override !== undefined) return override;
753
- if (sandboxSession.ports.length > 0) return sandboxSession.ports[0];
782
+ if ('ports' in sandboxSession && sandboxSession.ports.length > 0) {
783
+ return sandboxSession.ports[0];
784
+ }
754
785
  throw unsupported({
755
786
  harnessId,
756
787
  message:
@@ -759,6 +790,53 @@ function resolveBridgePort({
759
790
  });
760
791
  }
761
792
 
793
+ function validateBasicSandboxSettings({
794
+ sandboxSession,
795
+ port,
796
+ portEndpoint,
797
+ harnessId,
798
+ }: {
799
+ sandboxSession: HarnessV1NetworkSandboxSession | SandboxSession;
800
+ port: number | undefined;
801
+ portEndpoint: HarnessV1PortEndpoint | undefined;
802
+ harnessId: string;
803
+ }): void {
804
+ if ('getPortEndpoint' in sandboxSession) return;
805
+ if (port == null) {
806
+ throw unsupported({
807
+ harnessId,
808
+ message: `The ${harnessId} ACP harness requires an explicit \`port\` when using a basic sandbox session.`,
809
+ });
810
+ }
811
+ if (portEndpoint == null) {
812
+ throw unsupported({
813
+ harnessId,
814
+ message: `The ${harnessId} ACP harness requires an explicit \`portEndpoint\` when using a basic sandbox session.`,
815
+ });
816
+ }
817
+ }
818
+
819
+ async function resolveBridgeEndpoint({
820
+ sandboxSession,
821
+ override,
822
+ port,
823
+ harnessId,
824
+ }: {
825
+ sandboxSession: HarnessV1NetworkSandboxSession | SandboxSession;
826
+ override: HarnessV1PortEndpoint | undefined;
827
+ port: number;
828
+ harnessId: string;
829
+ }): Promise<HarnessV1PortEndpoint> {
830
+ if (override != null) return override;
831
+ if ('getPortEndpoint' in sandboxSession) {
832
+ return sandboxSession.getPortEndpoint({ port, protocol: 'ws' });
833
+ }
834
+ throw unsupported({
835
+ harnessId,
836
+ message: `The ${harnessId} ACP harness requires an explicit \`portEndpoint\` when using a basic sandbox session.`,
837
+ });
838
+ }
839
+
762
840
  function openWebSocket({
763
841
  url,
764
842
  headers,