@ai-sdk/harness-acp 1.0.15 → 1.0.17
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/CHANGELOG.md +16 -0
- package/dist/bridge/host-tool-mcp.mjs +1 -1
- package/dist/bridge/index.mjs +140 -5
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +82 -20
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/acp-harness.ts +7 -1
- package/src/v1/acp-v1-harness.ts +97 -19
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/harness-acp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.17",
|
|
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.
|
|
30
|
+
"@ai-sdk/harness": "1.0.79",
|
|
31
31
|
"@ai-sdk/provider-utils": "5.0.28"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
package/src/acp-harness.ts
CHANGED
|
@@ -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,
|
package/src/v1/acp-v1-harness.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
393
|
+
const endpoint = await resolveBridgeEndpoint({
|
|
394
|
+
sandboxSession,
|
|
395
|
+
override: portEndpointOverride,
|
|
369
396
|
port: coords.port,
|
|
370
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
506
|
-
await
|
|
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
|
|
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
|
|
605
|
+
const endpoint = await resolveBridgeEndpoint({
|
|
606
|
+
sandboxSession,
|
|
607
|
+
override: portEndpointOverride,
|
|
579
608
|
port: boundPort,
|
|
580
|
-
|
|
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
|
|
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)
|
|
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,
|