@ai-sdk/harness-acp 1.0.24 → 1.0.28
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 +38 -0
- package/README.md +14 -6
- package/dist/bridge/host-tool-mcp.mjs +1 -1
- package/dist/bridge/index.mjs +93 -11
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/index.d.ts +17 -2
- package/dist/index.js +123 -47
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
- package/src/acp-harness.ts +7 -0
- package/src/v1/acp-bootstrap.ts +12 -0
- package/src/v1/acp-v1-bridge-protocol.ts +14 -0
- package/src/v1/acp-v1-harness.ts +157 -62
- package/src/v1/acp-v1-lifecycle.ts +1 -0
- package/src/v1/acp-v1-settings.ts +11 -0
- package/src/v1/bridge/index.ts +44 -18
- package/src/v1/bridge/refresh-host-tool-catalog.ts +28 -0
- package/src/v1/bridge/stream-translator.ts +65 -0
- package/src/v1/implementation.ts +13 -0
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.28",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -26,9 +26,9 @@
|
|
|
26
26
|
}
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"
|
|
30
|
-
"@ai-sdk/
|
|
31
|
-
"
|
|
29
|
+
"@ai-sdk/harness": "1.0.90",
|
|
30
|
+
"@ai-sdk/provider-utils": "5.0.32",
|
|
31
|
+
"ws": "^8.21.0"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"zod": "^3.25.76 || ^4.1.8"
|
|
@@ -38,10 +38,10 @@
|
|
|
38
38
|
"@modelcontextprotocol/sdk": "1.29.0",
|
|
39
39
|
"@types/node": "22.19.19",
|
|
40
40
|
"@types/ws": "^8.5.13",
|
|
41
|
+
"@vercel/ai-tsconfig": "0.0.0",
|
|
41
42
|
"tsup": "^8.5.1",
|
|
42
43
|
"typescript": "5.8.3",
|
|
43
|
-
"zod": "3.25.76"
|
|
44
|
-
"@vercel/ai-tsconfig": "0.0.0"
|
|
44
|
+
"zod": "3.25.76"
|
|
45
45
|
},
|
|
46
46
|
"engines": {
|
|
47
47
|
"node": ">=22"
|
package/src/acp-harness.ts
CHANGED
|
@@ -40,6 +40,12 @@ export type ACPHarnessSettings<TBuiltinTools extends ToolSet = {}> = {
|
|
|
40
40
|
readonly forwardEnv?: ACPV1Settings['forwardEnv'];
|
|
41
41
|
readonly credentialEnv?: ACPV1Settings['credentialEnv'];
|
|
42
42
|
readonly credentialBrokering?: ACPV1Settings['credentialBrokering'];
|
|
43
|
+
/**
|
|
44
|
+
* Customizes each credential value before it is forwarded into a sandbox
|
|
45
|
+
* process. This does not restrict which credentials the harness adapter can
|
|
46
|
+
* discover, read, or otherwise access in the host process.
|
|
47
|
+
*/
|
|
48
|
+
readonly credentialForwarding?: ACPV1Settings['credentialForwarding'];
|
|
43
49
|
readonly env?: ACPV1Settings['env'];
|
|
44
50
|
readonly authentication?: ACPV1Settings['authentication'];
|
|
45
51
|
readonly providerAuthentication?: ACPV1Settings['providerAuthentication'];
|
|
@@ -79,6 +85,7 @@ const acpResumeStateSchema = z.object({
|
|
|
79
85
|
.optional(),
|
|
80
86
|
})
|
|
81
87
|
.optional(),
|
|
88
|
+
sandboxCredentialEnvironment: z.record(z.string(), z.string()).optional(),
|
|
82
89
|
acpSessionId: z.string().optional(),
|
|
83
90
|
bridge: acpBridgeCoordsSchema.optional(),
|
|
84
91
|
coldSession: acpColdSessionStateSchema.optional(),
|
package/src/v1/acp-bootstrap.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
createImplementationManifest,
|
|
8
8
|
getImplementationInstallScript,
|
|
9
9
|
getImplementationLockfile,
|
|
10
|
+
getImplementationWorkspaceFile,
|
|
10
11
|
type ACPImplementation,
|
|
11
12
|
} from './implementation';
|
|
12
13
|
|
|
@@ -40,6 +41,9 @@ export function createACPBootstrap({
|
|
|
40
41
|
const implementationLock = getImplementationLockfile({
|
|
41
42
|
implementation,
|
|
42
43
|
});
|
|
44
|
+
const implementationWorkspace = getImplementationWorkspaceFile({
|
|
45
|
+
implementation,
|
|
46
|
+
});
|
|
43
47
|
const implementationInstallScript = getImplementationInstallScript({
|
|
44
48
|
implementation,
|
|
45
49
|
});
|
|
@@ -80,6 +84,14 @@ export function createACPBootstrap({
|
|
|
80
84
|
content: implementationLock,
|
|
81
85
|
},
|
|
82
86
|
]),
|
|
87
|
+
...(implementationWorkspace == null
|
|
88
|
+
? []
|
|
89
|
+
: [
|
|
90
|
+
{
|
|
91
|
+
path: `${bootstrapDir}/implementation/pnpm-workspace.yaml`,
|
|
92
|
+
content: implementationWorkspace,
|
|
93
|
+
},
|
|
94
|
+
]),
|
|
83
95
|
...(implementationInstallScript == null
|
|
84
96
|
? []
|
|
85
97
|
: [
|
|
@@ -32,6 +32,18 @@ export type ACPBuiltinToolMapping = {
|
|
|
32
32
|
*/
|
|
33
33
|
readonly nativeName?: HarnessV1BuiltinTool['nativeName'];
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Stable prefix of the display title emitted by an ACP implementation.
|
|
37
|
+
* Used only when the implementation omits a programmatic tool name.
|
|
38
|
+
*/
|
|
39
|
+
readonly title?: HarnessV1BuiltinTool['title'];
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Broad capability category used to compare a built-in with the ACP tool
|
|
43
|
+
* kind when resolving unnamed calls.
|
|
44
|
+
*/
|
|
45
|
+
readonly toolUseKind?: HarnessV1BuiltinTool['toolUseKind'];
|
|
46
|
+
|
|
35
47
|
/**
|
|
36
48
|
* JSON Schema used to identify tool calls when an ACP implementation does
|
|
37
49
|
* not provide a programmatic tool name. It is optional because built-in
|
|
@@ -43,6 +55,8 @@ export type ACPBuiltinToolMapping = {
|
|
|
43
55
|
const builtinToolSchema: z.ZodType<ACPBuiltinToolMapping> = z.object({
|
|
44
56
|
toolName: z.string(),
|
|
45
57
|
nativeName: z.string().optional(),
|
|
58
|
+
title: z.string().optional(),
|
|
59
|
+
toolUseKind: z.enum(['readonly', 'edit', 'bash']).optional(),
|
|
46
60
|
inputSchema: z.json().optional(),
|
|
47
61
|
});
|
|
48
62
|
|
package/src/v1/acp-v1-harness.ts
CHANGED
|
@@ -14,14 +14,15 @@ import {
|
|
|
14
14
|
} from '@ai-sdk/harness';
|
|
15
15
|
import { HarnessBridgeCapabilityUnsupportedError } from '@ai-sdk/harness/bridge';
|
|
16
16
|
import {
|
|
17
|
+
applyCredentialForwarding,
|
|
17
18
|
createBridgeErrorHandler,
|
|
18
19
|
createBridgeStartupError,
|
|
20
|
+
createSandboxCredentialEnvironment,
|
|
19
21
|
classifyDiskLog,
|
|
20
22
|
drainBridgeProcessStream,
|
|
21
23
|
forwardBridgeProcessStream,
|
|
22
24
|
getRestrictedSandboxSession,
|
|
23
25
|
markBridgeStarting,
|
|
24
|
-
maskSandboxCredentials,
|
|
25
26
|
resolveSandboxDefaultWorkingDirectory,
|
|
26
27
|
resolveSandboxHomeDir,
|
|
27
28
|
SandboxChannel,
|
|
@@ -82,6 +83,7 @@ import type {
|
|
|
82
83
|
ACPOutputSchemaMapping,
|
|
83
84
|
ACPPermissionModeMapping,
|
|
84
85
|
ACPPermissionModeTarget,
|
|
86
|
+
ACPProfileValue,
|
|
85
87
|
ACPSerializableValue,
|
|
86
88
|
ACPV1Settings,
|
|
87
89
|
} from './acp-v1-settings';
|
|
@@ -235,6 +237,25 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
235
237
|
sandboxSession,
|
|
236
238
|
abortSignal: startOptions.abortSignal,
|
|
237
239
|
});
|
|
240
|
+
const continueFrom =
|
|
241
|
+
startOptions.continueFrom ?? startOptions.resumeFrom?.continueFrom;
|
|
242
|
+
const lifecycleState = continueFrom ?? startOptions.resumeFrom;
|
|
243
|
+
const isContinue = continueFrom != null;
|
|
244
|
+
const isResume = lifecycleState != null;
|
|
245
|
+
const lifecycleData =
|
|
246
|
+
lifecycleState != null
|
|
247
|
+
? (lifecycleState.data as unknown as ACPLifecycleData)
|
|
248
|
+
: undefined;
|
|
249
|
+
if (lifecycleData != null && lifecycleState != null) {
|
|
250
|
+
validateACPLifecycleCompatibility({
|
|
251
|
+
harnessId: settings.harnessId,
|
|
252
|
+
lifecycleHarnessId: lifecycleState.harnessId,
|
|
253
|
+
implementationIdentity,
|
|
254
|
+
authenticationProfile,
|
|
255
|
+
lifecycleData,
|
|
256
|
+
sandboxId,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
238
259
|
const implementationEnvironment = resolveImplementationEnvironment({
|
|
239
260
|
implementation,
|
|
240
261
|
env,
|
|
@@ -243,6 +264,25 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
243
264
|
let sandboxProviderAuthenticationEnvironment =
|
|
244
265
|
resolvedProviderAuthentication.env;
|
|
245
266
|
let sandboxProviderEnvironment: Record<string, string> | undefined;
|
|
267
|
+
const credentialEnvironmentVariables = [
|
|
268
|
+
...new Set([
|
|
269
|
+
...(settings.credentialEnv ?? []),
|
|
270
|
+
'AI_GATEWAY_API_KEY',
|
|
271
|
+
'VERCEL_OIDC_TOKEN',
|
|
272
|
+
]),
|
|
273
|
+
];
|
|
274
|
+
const credentialForwardingEnvironmentVariables = [
|
|
275
|
+
...new Set([
|
|
276
|
+
...credentialEnvironmentVariables,
|
|
277
|
+
...resolveACPProviderCredentialEnvironmentVariables({
|
|
278
|
+
providerAuthentication:
|
|
279
|
+
resolvedProviderAuthentication.providerAuthentication,
|
|
280
|
+
}),
|
|
281
|
+
]),
|
|
282
|
+
];
|
|
283
|
+
let sandboxCredentialEnvironment:
|
|
284
|
+
| Readonly<Record<string, string>>
|
|
285
|
+
| undefined;
|
|
246
286
|
|
|
247
287
|
if (
|
|
248
288
|
settings.credentialBrokering != null &&
|
|
@@ -253,44 +293,55 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
253
293
|
resolvedProviderAuthentication,
|
|
254
294
|
clientApp,
|
|
255
295
|
});
|
|
256
|
-
const
|
|
257
|
-
...
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
296
|
+
const brokeringEnvironment = {
|
|
297
|
+
...implementationEnvironment,
|
|
298
|
+
...providerEnvironment,
|
|
299
|
+
};
|
|
300
|
+
sandboxCredentialEnvironment =
|
|
301
|
+
lifecycleData?.sandboxCredentialEnvironment ??
|
|
302
|
+
(await createSandboxCredentialEnvironment({
|
|
303
|
+
environment: brokeringEnvironment,
|
|
304
|
+
credentialEnvironmentVariables:
|
|
305
|
+
credentialForwardingEnvironmentVariables,
|
|
306
|
+
credentialForwarding: settings.credentialForwarding,
|
|
307
|
+
}));
|
|
308
|
+
sandboxImplementationEnvironment = {
|
|
309
|
+
...brokeringEnvironment,
|
|
310
|
+
...sandboxCredentialEnvironment,
|
|
311
|
+
};
|
|
263
312
|
const requestTransformations = settings.credentialBrokering({
|
|
264
|
-
env:
|
|
265
|
-
|
|
266
|
-
...providerEnvironment,
|
|
267
|
-
},
|
|
313
|
+
env: brokeringEnvironment,
|
|
314
|
+
sandboxEnv: sandboxImplementationEnvironment,
|
|
268
315
|
});
|
|
269
316
|
if (requestTransformations.length > 0) {
|
|
270
317
|
await sandboxSession.addRequestTransformations(
|
|
271
318
|
requestTransformations,
|
|
272
319
|
);
|
|
273
320
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
}),
|
|
292
|
-
credentialEnvironmentVariables,
|
|
321
|
+
sandboxProviderEnvironment =
|
|
322
|
+
resolvedProviderAuthentication.providerAuthentication?.type ===
|
|
323
|
+
'ai-gateway'
|
|
324
|
+
? {}
|
|
325
|
+
: undefined;
|
|
326
|
+
} else if (settings.credentialBrokering != null) {
|
|
327
|
+
warnCredentialBrokeringUnavailable();
|
|
328
|
+
}
|
|
329
|
+
if (
|
|
330
|
+
settings.credentialForwarding != null &&
|
|
331
|
+
sandboxProviderEnvironment == null &&
|
|
332
|
+
resolvedProviderAuthentication.providerAuthentication?.type ===
|
|
333
|
+
'ai-gateway'
|
|
334
|
+
) {
|
|
335
|
+
sandboxProviderEnvironment = resolveProviderEnvironment({
|
|
336
|
+
resolvedProviderAuthentication,
|
|
337
|
+
clientApp,
|
|
293
338
|
});
|
|
339
|
+
}
|
|
340
|
+
if (sandboxProviderEnvironment != null) {
|
|
341
|
+
sandboxImplementationEnvironment = {
|
|
342
|
+
...sandboxImplementationEnvironment,
|
|
343
|
+
...sandboxProviderEnvironment,
|
|
344
|
+
};
|
|
294
345
|
sandboxProviderAuthenticationEnvironment = Object.fromEntries(
|
|
295
346
|
Object.entries(resolvedProviderAuthentication.env).filter(
|
|
296
347
|
([key]) =>
|
|
@@ -298,8 +349,6 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
298
349
|
key !== 'AI_SDK_ACP_GATEWAY_BASE_URL',
|
|
299
350
|
),
|
|
300
351
|
);
|
|
301
|
-
} else if (settings.credentialBrokering != null) {
|
|
302
|
-
warnCredentialBrokeringUnavailable();
|
|
303
352
|
}
|
|
304
353
|
const resolvedBridgeDir = posix.resolve(
|
|
305
354
|
defaultWorkingDirectory,
|
|
@@ -318,25 +367,6 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
318
367
|
sessionId: startOptions.sessionId,
|
|
319
368
|
});
|
|
320
369
|
const bridgeStateDir = `${privateSessionDir}/bridge`;
|
|
321
|
-
const continueFrom =
|
|
322
|
-
startOptions.continueFrom ?? startOptions.resumeFrom?.continueFrom;
|
|
323
|
-
const lifecycleState = continueFrom ?? startOptions.resumeFrom;
|
|
324
|
-
const isContinue = continueFrom != null;
|
|
325
|
-
const isResume = lifecycleState != null;
|
|
326
|
-
const lifecycleData =
|
|
327
|
-
lifecycleState != null
|
|
328
|
-
? (lifecycleState.data as unknown as ACPLifecycleData)
|
|
329
|
-
: undefined;
|
|
330
|
-
if (lifecycleData != null && lifecycleState != null) {
|
|
331
|
-
validateACPLifecycleCompatibility({
|
|
332
|
-
harnessId: settings.harnessId,
|
|
333
|
-
lifecycleHarnessId: lifecycleState.harnessId,
|
|
334
|
-
implementationIdentity,
|
|
335
|
-
authenticationProfile,
|
|
336
|
-
lifecycleData,
|
|
337
|
-
sandboxId,
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
370
|
const skills = startOptions.skills ?? [];
|
|
341
371
|
const skillsFingerprint = createACPSkillsFingerprint({ skills });
|
|
342
372
|
const shouldMaterializeSkills = shouldMaterializeACPSkills({
|
|
@@ -441,6 +471,7 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
441
471
|
turnStartConfig: lifecycleData.turnStartConfig,
|
|
442
472
|
recoveryStatus: lifecycleData.recovery,
|
|
443
473
|
restoration: lifecycleData.restoration,
|
|
474
|
+
sandboxCredentialEnvironment,
|
|
444
475
|
replayOnly: false,
|
|
445
476
|
lossyRerun: false,
|
|
446
477
|
});
|
|
@@ -521,6 +552,15 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
521
552
|
}
|
|
522
553
|
}
|
|
523
554
|
|
|
555
|
+
const forwardedImplementationEnvironment =
|
|
556
|
+
sandboxCredentialEnvironment == null
|
|
557
|
+
? await applyCredentialForwarding({
|
|
558
|
+
environment: sandboxImplementationEnvironment,
|
|
559
|
+
credentialEnvironmentVariables:
|
|
560
|
+
credentialForwardingEnvironmentVariables,
|
|
561
|
+
credentialForwarding: settings.credentialForwarding,
|
|
562
|
+
})
|
|
563
|
+
: sandboxImplementationEnvironment;
|
|
524
564
|
const port = resolveBridgePort({
|
|
525
565
|
sandboxSession,
|
|
526
566
|
override: portOverride,
|
|
@@ -549,12 +589,13 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
549
589
|
` --implementation-dir ${shellQuote(resolvedImplementationDir)}` +
|
|
550
590
|
` --bridge-type ${shellQuote(settings.harnessId)}`,
|
|
551
591
|
env: {
|
|
552
|
-
...
|
|
592
|
+
...forwardedImplementationEnvironment,
|
|
553
593
|
...createACPBridgeEnvironment({
|
|
554
594
|
authentication: settings.authentication,
|
|
555
595
|
providerAuthentication:
|
|
556
596
|
resolvedProviderAuthentication.providerAuthentication,
|
|
557
|
-
providerEnvironment:
|
|
597
|
+
providerEnvironment:
|
|
598
|
+
sandboxProviderEnvironment == null ? undefined : {},
|
|
558
599
|
sessionMeta: settings.session?.meta,
|
|
559
600
|
}),
|
|
560
601
|
...sandboxProviderAuthenticationEnvironment,
|
|
@@ -712,6 +753,7 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
712
753
|
coldRestoration == null
|
|
713
754
|
? lifecycleData?.restoration
|
|
714
755
|
: { method: coldRestoration },
|
|
756
|
+
sandboxCredentialEnvironment,
|
|
715
757
|
replayOnly: respawnStrategy?.mode === 'disk-replay',
|
|
716
758
|
lossyRerun: respawnStrategy?.mode === 'lossy-rerun',
|
|
717
759
|
});
|
|
@@ -722,13 +764,11 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
722
764
|
function resolveProviderEnvironment({
|
|
723
765
|
resolvedProviderAuthentication,
|
|
724
766
|
clientApp,
|
|
725
|
-
gatewayApiKey,
|
|
726
767
|
}: {
|
|
727
768
|
resolvedProviderAuthentication: ReturnType<
|
|
728
769
|
typeof resolveACPProviderAuthentication
|
|
729
770
|
>;
|
|
730
771
|
clientApp: ACPClientApp;
|
|
731
|
-
gatewayApiKey?: string;
|
|
732
772
|
}): Record<string, string> {
|
|
733
773
|
if (
|
|
734
774
|
resolvedProviderAuthentication.providerAuthentication?.type !== 'ai-gateway'
|
|
@@ -739,12 +779,10 @@ function resolveProviderEnvironment({
|
|
|
739
779
|
providerAuthentication:
|
|
740
780
|
resolvedProviderAuthentication.providerAuthentication,
|
|
741
781
|
gateway: {
|
|
742
|
-
apiKey:
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
name: 'AI_SDK_ACP_GATEWAY_API_KEY',
|
|
747
|
-
}),
|
|
782
|
+
apiKey: requireResolvedEnvironmentValue({
|
|
783
|
+
environment: resolvedProviderAuthentication.env,
|
|
784
|
+
name: 'AI_SDK_ACP_GATEWAY_API_KEY',
|
|
785
|
+
}),
|
|
748
786
|
baseUrl: requireResolvedEnvironmentValue({
|
|
749
787
|
environment: resolvedProviderAuthentication.env,
|
|
750
788
|
name: 'AI_SDK_ACP_GATEWAY_BASE_URL',
|
|
@@ -755,6 +793,40 @@ function resolveProviderEnvironment({
|
|
|
755
793
|
});
|
|
756
794
|
}
|
|
757
795
|
|
|
796
|
+
function resolveACPProviderCredentialEnvironmentVariables({
|
|
797
|
+
providerAuthentication,
|
|
798
|
+
}: {
|
|
799
|
+
providerAuthentication: ReturnType<
|
|
800
|
+
typeof resolveACPProviderAuthentication
|
|
801
|
+
>['providerAuthentication'];
|
|
802
|
+
}): string[] {
|
|
803
|
+
if (providerAuthentication?.type !== 'ai-gateway') return [];
|
|
804
|
+
|
|
805
|
+
return Object.entries(providerAuthentication.env)
|
|
806
|
+
.filter(([, value]) => containsACPProviderCredential({ value }))
|
|
807
|
+
.map(([name]) => name);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
function containsACPProviderCredential({
|
|
811
|
+
value,
|
|
812
|
+
}: {
|
|
813
|
+
value: ACPProfileValue;
|
|
814
|
+
}): boolean {
|
|
815
|
+
if (Array.isArray(value)) {
|
|
816
|
+
return value.some(item => containsACPProviderCredential({ value: item }));
|
|
817
|
+
}
|
|
818
|
+
if (value == null || typeof value !== 'object') return false;
|
|
819
|
+
if ('$source' in value) {
|
|
820
|
+
return (
|
|
821
|
+
value.$source === 'gateway-api-key' ||
|
|
822
|
+
value.$source === 'gateway-authorization'
|
|
823
|
+
);
|
|
824
|
+
}
|
|
825
|
+
return Object.values(value).some(item =>
|
|
826
|
+
containsACPProviderCredential({ value: item }),
|
|
827
|
+
);
|
|
828
|
+
}
|
|
829
|
+
|
|
758
830
|
function requireResolvedEnvironmentValue({
|
|
759
831
|
environment,
|
|
760
832
|
name,
|
|
@@ -984,6 +1056,7 @@ function createSession({
|
|
|
984
1056
|
turnStartConfig: turnStartConfigAtStart,
|
|
985
1057
|
recoveryStatus,
|
|
986
1058
|
restoration,
|
|
1059
|
+
sandboxCredentialEnvironment,
|
|
987
1060
|
replayOnly,
|
|
988
1061
|
lossyRerun,
|
|
989
1062
|
}: {
|
|
@@ -1016,6 +1089,7 @@ function createSession({
|
|
|
1016
1089
|
turnStartConfig: ACPTurnStartConfig | undefined;
|
|
1017
1090
|
recoveryStatus: ACPLifecycleData['recovery'];
|
|
1018
1091
|
restoration: ACPLifecycleData['restoration'];
|
|
1092
|
+
sandboxCredentialEnvironment: Readonly<Record<string, string>> | undefined;
|
|
1019
1093
|
replayOnly: boolean;
|
|
1020
1094
|
lossyRerun: boolean;
|
|
1021
1095
|
}): HarnessV1Session {
|
|
@@ -1271,6 +1345,9 @@ function createSession({
|
|
|
1271
1345
|
}): ACPLifecycleData => ({
|
|
1272
1346
|
implementationIdentity,
|
|
1273
1347
|
authenticationProfile,
|
|
1348
|
+
...(sandboxCredentialEnvironment == null
|
|
1349
|
+
? {}
|
|
1350
|
+
: { sandboxCredentialEnvironment }),
|
|
1274
1351
|
...(latestACPSessionId == null ? {} : { acpSessionId: latestACPSessionId }),
|
|
1275
1352
|
...(bridge == null ? {} : { bridge }),
|
|
1276
1353
|
...(latestTurnStartConfig == null
|
|
@@ -1583,6 +1660,22 @@ export function serializeBuiltinTools({
|
|
|
1583
1660
|
typeof tool.nativeName === 'string'
|
|
1584
1661
|
? tool.nativeName
|
|
1585
1662
|
: undefined;
|
|
1663
|
+
const title =
|
|
1664
|
+
tool != null &&
|
|
1665
|
+
typeof tool === 'object' &&
|
|
1666
|
+
'title' in tool &&
|
|
1667
|
+
typeof tool.title === 'string'
|
|
1668
|
+
? tool.title
|
|
1669
|
+
: undefined;
|
|
1670
|
+
const toolUseKind =
|
|
1671
|
+
tool != null &&
|
|
1672
|
+
typeof tool === 'object' &&
|
|
1673
|
+
'toolUseKind' in tool &&
|
|
1674
|
+
(tool.toolUseKind === 'readonly' ||
|
|
1675
|
+
tool.toolUseKind === 'edit' ||
|
|
1676
|
+
tool.toolUseKind === 'bash')
|
|
1677
|
+
? tool.toolUseKind
|
|
1678
|
+
: undefined;
|
|
1586
1679
|
let inputSchema: ACPBuiltinToolMapping['inputSchema'];
|
|
1587
1680
|
if (
|
|
1588
1681
|
tool != null &&
|
|
@@ -1599,6 +1692,8 @@ export function serializeBuiltinTools({
|
|
|
1599
1692
|
return {
|
|
1600
1693
|
toolName,
|
|
1601
1694
|
...(nativeName == null ? {} : { nativeName }),
|
|
1695
|
+
...(title == null ? {} : { title }),
|
|
1696
|
+
...(toolUseKind == null ? {} : { toolUseKind }),
|
|
1602
1697
|
...(inputSchema == null ? {} : { inputSchema }),
|
|
1603
1698
|
};
|
|
1604
1699
|
});
|
|
@@ -21,6 +21,7 @@ export type ACPBridgeCoordinates = {
|
|
|
21
21
|
export type ACPLifecycleData = ACPPromptGuidanceLifecycleState & {
|
|
22
22
|
readonly implementationIdentity: string;
|
|
23
23
|
readonly authenticationProfile?: ACPAuthenticationProfileIdentity;
|
|
24
|
+
readonly sandboxCredentialEnvironment?: Readonly<Record<string, string>>;
|
|
24
25
|
readonly acpSessionId?: string;
|
|
25
26
|
readonly bridge?: ACPBridgeCoordinates;
|
|
26
27
|
readonly coldSession?: ACPColdSessionState;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
HarnessV1CredentialForwarding,
|
|
2
3
|
HarnessV1PermissionMode,
|
|
3
4
|
HarnessV1RequestTransformation,
|
|
4
5
|
} from '@ai-sdk/harness';
|
|
@@ -45,6 +46,8 @@ export type ACPNpmLockedSource = {
|
|
|
45
46
|
readonly type: 'npm-locked';
|
|
46
47
|
readonly packageJson: string;
|
|
47
48
|
readonly pnpmLockYaml: string;
|
|
49
|
+
/** Optional pnpm workspace configuration required by the locked install. */
|
|
50
|
+
readonly pnpmWorkspaceYaml?: string;
|
|
48
51
|
};
|
|
49
52
|
|
|
50
53
|
export type ACPInstallCommandSource = {
|
|
@@ -73,8 +76,10 @@ export type ACPProviderAuthentication = {
|
|
|
73
76
|
|
|
74
77
|
export type ACPCredentialBrokering = ({
|
|
75
78
|
env,
|
|
79
|
+
sandboxEnv,
|
|
76
80
|
}: {
|
|
77
81
|
env: Readonly<Record<string, string>>;
|
|
82
|
+
sandboxEnv?: Readonly<Record<string, string>>;
|
|
78
83
|
}) => ReadonlyArray<HarnessV1RequestTransformation>;
|
|
79
84
|
|
|
80
85
|
export type ACPPermissionModeTarget =
|
|
@@ -125,6 +130,12 @@ export type ACPV1Settings = {
|
|
|
125
130
|
readonly forwardEnv?: ReadonlyArray<string>;
|
|
126
131
|
readonly credentialEnv?: ReadonlyArray<string>;
|
|
127
132
|
readonly credentialBrokering?: ACPCredentialBrokering;
|
|
133
|
+
/**
|
|
134
|
+
* Customizes each credential value before it is forwarded into a sandbox
|
|
135
|
+
* process. This does not restrict which credentials the harness adapter can
|
|
136
|
+
* discover, read, or otherwise access in the host process.
|
|
137
|
+
*/
|
|
138
|
+
readonly credentialForwarding?: HarnessV1CredentialForwarding;
|
|
128
139
|
/**
|
|
129
140
|
* Runtime environment values that are safe to persist in bootstrap and
|
|
130
141
|
* lifecycle compatibility identity.
|
package/src/v1/bridge/index.ts
CHANGED
|
@@ -38,7 +38,10 @@ import {
|
|
|
38
38
|
type HostToolRelay,
|
|
39
39
|
type HostToolRelayTurn,
|
|
40
40
|
} from './host-tool-relay';
|
|
41
|
-
import {
|
|
41
|
+
import {
|
|
42
|
+
promptAndRefreshInitialHostToolCatalog,
|
|
43
|
+
refreshHostToolCatalog,
|
|
44
|
+
} from './refresh-host-tool-catalog';
|
|
42
45
|
import { createACPPermissionController } from './permission-controller';
|
|
43
46
|
import { configureACPPermissionMode } from './permission-mode';
|
|
44
47
|
import {
|
|
@@ -130,8 +133,12 @@ await runBridge<StartMessage>({
|
|
|
130
133
|
});
|
|
131
134
|
|
|
132
135
|
async function runTurn(start: StartMessage, turn: BridgeTurn): Promise<void> {
|
|
136
|
+
let initialHostToolCatalogRefreshRequired: boolean;
|
|
133
137
|
try {
|
|
134
|
-
await ensureSession({
|
|
138
|
+
({ initialHostToolCatalogRefreshRequired } = await ensureSession({
|
|
139
|
+
start,
|
|
140
|
+
turn,
|
|
141
|
+
}));
|
|
135
142
|
} catch (error) {
|
|
136
143
|
if (HarnessBridgeCapabilityUnsupportedError.isInstance(error)) throw error;
|
|
137
144
|
throw createACPBridgeError({
|
|
@@ -149,6 +156,10 @@ async function runTurn(start: StartMessage, turn: BridgeTurn): Promise<void> {
|
|
|
149
156
|
'ACP session initialization did not start stderr monitoring.',
|
|
150
157
|
);
|
|
151
158
|
}
|
|
159
|
+
const activeHostToolRelay = hostToolRelay;
|
|
160
|
+
if (activeHostToolRelay == null) {
|
|
161
|
+
throw new Error('The host tool MCP relay is unavailable.');
|
|
162
|
+
}
|
|
152
163
|
if (start.recoveryMode?.type === 'lossy-rerun') {
|
|
153
164
|
const marker = {
|
|
154
165
|
type: 'acp-recovery',
|
|
@@ -260,15 +271,35 @@ async function runTurn(start: StartMessage, turn: BridgeTurn): Promise<void> {
|
|
|
260
271
|
removeCorrelationInvocation:
|
|
261
272
|
emitStreamEvent.removeHostToolCorrelationInvocation,
|
|
262
273
|
};
|
|
263
|
-
|
|
274
|
+
activeHostToolRelay.bindTurn({ turn: relayTurn });
|
|
264
275
|
try {
|
|
265
276
|
const promptMeta = createOutputSchemaPromptMeta({ start });
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
277
|
+
const startPrompt = () =>
|
|
278
|
+
promptActiveSession({
|
|
279
|
+
session: activeSession,
|
|
280
|
+
agent: connection!.agent,
|
|
281
|
+
prompt: start.prompt,
|
|
282
|
+
meta: promptMeta,
|
|
283
|
+
});
|
|
284
|
+
if (initialHostToolCatalogRefreshRequired) {
|
|
285
|
+
try {
|
|
286
|
+
await promptAndRefreshInitialHostToolCatalog({
|
|
287
|
+
startPrompt,
|
|
288
|
+
relay: activeHostToolRelay,
|
|
289
|
+
tools: start.tools ?? [],
|
|
290
|
+
harnessId: bridgeType,
|
|
291
|
+
timeoutMs: CATALOG_REFRESH_TIMEOUT_MS,
|
|
292
|
+
});
|
|
293
|
+
} catch (error) {
|
|
294
|
+
if (HarnessBridgeCapabilityUnsupportedError.isInstance(error)) {
|
|
295
|
+
catalogRefreshError = error;
|
|
296
|
+
}
|
|
297
|
+
sessionConfigurationFailure = { error };
|
|
298
|
+
throw error;
|
|
299
|
+
}
|
|
300
|
+
} else {
|
|
301
|
+
void startPrompt();
|
|
302
|
+
}
|
|
272
303
|
if (turn.abortSignal.aborted) {
|
|
273
304
|
await cancel();
|
|
274
305
|
} else {
|
|
@@ -316,7 +347,7 @@ async function runTurn(start: StartMessage, turn: BridgeTurn): Promise<void> {
|
|
|
316
347
|
} finally {
|
|
317
348
|
permissionController.cancelAll();
|
|
318
349
|
activePermissionController = undefined;
|
|
319
|
-
|
|
350
|
+
activeHostToolRelay.unbindTurn({ turn: relayTurn });
|
|
320
351
|
}
|
|
321
352
|
}
|
|
322
353
|
|
|
@@ -326,7 +357,7 @@ async function ensureSession({
|
|
|
326
357
|
}: {
|
|
327
358
|
start: StartMessage;
|
|
328
359
|
turn: BridgeTurn;
|
|
329
|
-
}): Promise<
|
|
360
|
+
}): Promise<{ initialHostToolCatalogRefreshRequired: boolean }> {
|
|
330
361
|
if (sessionConfigurationFailure != null) {
|
|
331
362
|
throw sessionConfigurationFailure.error;
|
|
332
363
|
}
|
|
@@ -364,7 +395,7 @@ async function ensureSession({
|
|
|
364
395
|
}
|
|
365
396
|
throw error;
|
|
366
397
|
}
|
|
367
|
-
return;
|
|
398
|
+
return { initialHostToolCatalogRefreshRequired: false };
|
|
368
399
|
}
|
|
369
400
|
|
|
370
401
|
const clientApp = resolveClientApp();
|
|
@@ -558,12 +589,6 @@ async function ensureSession({
|
|
|
558
589
|
.start();
|
|
559
590
|
}
|
|
560
591
|
try {
|
|
561
|
-
await refreshHostToolCatalog({
|
|
562
|
-
relay: hostToolRelay,
|
|
563
|
-
tools,
|
|
564
|
-
harnessId: bridgeType,
|
|
565
|
-
timeoutMs: CATALOG_REFRESH_TIMEOUT_MS,
|
|
566
|
-
});
|
|
567
592
|
if (start.permissionModeMapping != null) {
|
|
568
593
|
await configureACPPermissionMode({
|
|
569
594
|
agent: connection.agent,
|
|
@@ -588,6 +613,7 @@ async function ensureSession({
|
|
|
588
613
|
sessionId: createdSession.sessionId,
|
|
589
614
|
});
|
|
590
615
|
sessionConfigurationFingerprint = fingerprint;
|
|
616
|
+
return { initialHostToolCatalogRefreshRequired: tools.length > 0 };
|
|
591
617
|
}
|
|
592
618
|
|
|
593
619
|
function createExternalMcpServers({
|