@ai-sdk/harness-acp 1.0.25 → 1.0.29
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 +34 -0
- package/README.md +19 -6
- package/dist/bridge/host-tool-mcp.mjs +1 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +84 -113
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/acp-harness.ts +3 -0
- package/src/v1/acp-v1-harness.ts +84 -102
- package/src/v1/acp-v1-lifecycle.ts +9 -1
- package/src/v1/acp-v1-prompt.ts +2 -22
- package/src/v1/acp-v1-settings.ts +7 -0
- package/src/v1/acp-v1-skills.ts +38 -29
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.29",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
}
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@ai-sdk/harness": "1.0.
|
|
30
|
-
"@ai-sdk/provider-utils": "5.0.
|
|
29
|
+
"@ai-sdk/harness": "1.0.91",
|
|
30
|
+
"@ai-sdk/provider-utils": "5.0.32",
|
|
31
31
|
"ws": "^8.21.0"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
package/src/acp-harness.ts
CHANGED
|
@@ -50,6 +50,7 @@ export type ACPHarnessSettings<TBuiltinTools extends ToolSet = {}> = {
|
|
|
50
50
|
readonly authentication?: ACPV1Settings['authentication'];
|
|
51
51
|
readonly providerAuthentication?: ACPV1Settings['providerAuthentication'];
|
|
52
52
|
readonly modelId?: ACPV1Settings['modelId'];
|
|
53
|
+
readonly skillsDirectory?: ACPV1Settings['skillsDirectory'];
|
|
53
54
|
readonly instructionMapping?: ACPV1Settings['instructionMapping'];
|
|
54
55
|
readonly outputSchemaMapping?: ACPV1Settings['outputSchemaMapping'];
|
|
55
56
|
readonly permissionModeMapping?: ACPV1Settings['permissionModeMapping'];
|
|
@@ -85,6 +86,7 @@ const acpResumeStateSchema = z.object({
|
|
|
85
86
|
.optional(),
|
|
86
87
|
})
|
|
87
88
|
.optional(),
|
|
89
|
+
sandboxCredentialEnvironment: z.record(z.string(), z.string()).optional(),
|
|
88
90
|
acpSessionId: z.string().optional(),
|
|
89
91
|
bridge: acpBridgeCoordsSchema.optional(),
|
|
90
92
|
coldSession: acpColdSessionStateSchema.optional(),
|
|
@@ -103,6 +105,7 @@ const acpResumeStateSchema = z.object({
|
|
|
103
105
|
initialGuidanceApplied: z.boolean().optional(),
|
|
104
106
|
skillsMaterialized: z.boolean().optional(),
|
|
105
107
|
skillsFingerprint: z.string().optional(),
|
|
108
|
+
skillsDirectory: z.string().optional(),
|
|
106
109
|
});
|
|
107
110
|
|
|
108
111
|
export function createACP<TBuiltinTools extends ToolSet = {}>(
|
package/src/v1/acp-v1-harness.ts
CHANGED
|
@@ -17,12 +17,12 @@ import {
|
|
|
17
17
|
applyCredentialForwarding,
|
|
18
18
|
createBridgeErrorHandler,
|
|
19
19
|
createBridgeStartupError,
|
|
20
|
+
createSandboxCredentialEnvironment,
|
|
20
21
|
classifyDiskLog,
|
|
21
22
|
drainBridgeProcessStream,
|
|
22
23
|
forwardBridgeProcessStream,
|
|
23
24
|
getRestrictedSandboxSession,
|
|
24
25
|
markBridgeStarting,
|
|
25
|
-
maskSandboxCredentials,
|
|
26
26
|
resolveSandboxDefaultWorkingDirectory,
|
|
27
27
|
resolveSandboxHomeDir,
|
|
28
28
|
SandboxChannel,
|
|
@@ -76,7 +76,6 @@ import {
|
|
|
76
76
|
import {
|
|
77
77
|
convertHarnessPromptToACPTextBlocks,
|
|
78
78
|
prependACPInitialGuidance,
|
|
79
|
-
type ACPSkillCatalogEntry,
|
|
80
79
|
} from './acp-v1-prompt';
|
|
81
80
|
import type {
|
|
82
81
|
ACPInstructionMapping,
|
|
@@ -91,6 +90,7 @@ import {
|
|
|
91
90
|
createACPSkillsFingerprint,
|
|
92
91
|
materializeACPSkills,
|
|
93
92
|
resolveACPPrivateSessionDirectory,
|
|
93
|
+
resolveACPSkillsDirectory,
|
|
94
94
|
} from './acp-v1-skills';
|
|
95
95
|
|
|
96
96
|
const HARNESS_ID_REGEXP = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
@@ -237,6 +237,25 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
237
237
|
sandboxSession,
|
|
238
238
|
abortSignal: startOptions.abortSignal,
|
|
239
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
|
+
}
|
|
240
259
|
const implementationEnvironment = resolveImplementationEnvironment({
|
|
241
260
|
implementation,
|
|
242
261
|
env,
|
|
@@ -261,6 +280,9 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
261
280
|
}),
|
|
262
281
|
]),
|
|
263
282
|
];
|
|
283
|
+
let sandboxCredentialEnvironment:
|
|
284
|
+
| Readonly<Record<string, string>>
|
|
285
|
+
| undefined;
|
|
264
286
|
|
|
265
287
|
if (
|
|
266
288
|
settings.credentialBrokering != null &&
|
|
@@ -271,31 +293,36 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
271
293
|
resolvedProviderAuthentication,
|
|
272
294
|
clientApp,
|
|
273
295
|
});
|
|
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
|
+
};
|
|
274
312
|
const requestTransformations = settings.credentialBrokering({
|
|
275
|
-
env:
|
|
276
|
-
|
|
277
|
-
...providerEnvironment,
|
|
278
|
-
},
|
|
313
|
+
env: brokeringEnvironment,
|
|
314
|
+
sandboxEnv: sandboxImplementationEnvironment,
|
|
279
315
|
});
|
|
280
316
|
if (requestTransformations.length > 0) {
|
|
281
317
|
await sandboxSession.addRequestTransformations(
|
|
282
318
|
requestTransformations,
|
|
283
319
|
);
|
|
284
320
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
/*
|
|
291
|
-
* Each profile environment variable is resolved with its own name as
|
|
292
|
-
* the fake Gateway credential. This preserves profile transformations
|
|
293
|
-
* without exposing the real credential to the sandbox process.
|
|
294
|
-
*/
|
|
295
|
-
sandboxProviderEnvironment = resolveBrokeredProviderEnvironment({
|
|
296
|
-
resolvedProviderAuthentication,
|
|
297
|
-
clientApp,
|
|
298
|
-
});
|
|
321
|
+
sandboxProviderEnvironment =
|
|
322
|
+
resolvedProviderAuthentication.providerAuthentication?.type ===
|
|
323
|
+
'ai-gateway'
|
|
324
|
+
? {}
|
|
325
|
+
: undefined;
|
|
299
326
|
} else if (settings.credentialBrokering != null) {
|
|
300
327
|
warnCredentialBrokeringUnavailable();
|
|
301
328
|
}
|
|
@@ -339,47 +366,32 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
339
366
|
harnessId: settings.harnessId,
|
|
340
367
|
sessionId: startOptions.sessionId,
|
|
341
368
|
});
|
|
369
|
+
const skillsDirectory = resolveACPSkillsDirectory({
|
|
370
|
+
implementationHomeDir:
|
|
371
|
+
implementation.source.type === 'install-command'
|
|
372
|
+
? `${resolvedImplementationDir}/home`
|
|
373
|
+
: sandboxHomeDir,
|
|
374
|
+
skillsDirectory: settings.skillsDirectory,
|
|
375
|
+
sessionWorkDir: workDir,
|
|
376
|
+
});
|
|
342
377
|
const bridgeStateDir = `${privateSessionDir}/bridge`;
|
|
343
|
-
const continueFrom =
|
|
344
|
-
startOptions.continueFrom ?? startOptions.resumeFrom?.continueFrom;
|
|
345
|
-
const lifecycleState = continueFrom ?? startOptions.resumeFrom;
|
|
346
|
-
const isContinue = continueFrom != null;
|
|
347
|
-
const isResume = lifecycleState != null;
|
|
348
|
-
const lifecycleData =
|
|
349
|
-
lifecycleState != null
|
|
350
|
-
? (lifecycleState.data as unknown as ACPLifecycleData)
|
|
351
|
-
: undefined;
|
|
352
|
-
if (lifecycleData != null && lifecycleState != null) {
|
|
353
|
-
validateACPLifecycleCompatibility({
|
|
354
|
-
harnessId: settings.harnessId,
|
|
355
|
-
lifecycleHarnessId: lifecycleState.harnessId,
|
|
356
|
-
implementationIdentity,
|
|
357
|
-
authenticationProfile,
|
|
358
|
-
lifecycleData,
|
|
359
|
-
sandboxId,
|
|
360
|
-
});
|
|
361
|
-
}
|
|
362
378
|
const skills = startOptions.skills ?? [];
|
|
363
379
|
const skillsFingerprint = createACPSkillsFingerprint({ skills });
|
|
364
380
|
const shouldMaterializeSkills = shouldMaterializeACPSkills({
|
|
365
381
|
isResume,
|
|
366
382
|
lifecycleState: lifecycleData,
|
|
367
383
|
skillsFingerprint,
|
|
384
|
+
skillsDirectory,
|
|
368
385
|
});
|
|
369
|
-
let skillCatalog: ReadonlyArray<ACPSkillCatalogEntry> = [];
|
|
370
386
|
if (skills.length > 0) {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
shouldMaterialize: shouldMaterializeSkills,
|
|
380
|
-
abortSignal: startOptions.abortSignal,
|
|
381
|
-
})
|
|
382
|
-
).catalog;
|
|
387
|
+
await materializeACPSkills({
|
|
388
|
+
sandbox: toolSafeSandboxSession,
|
|
389
|
+
rootDir: skillsDirectory,
|
|
390
|
+
sessionWorkDir: workDir,
|
|
391
|
+
skills,
|
|
392
|
+
shouldMaterialize: shouldMaterializeSkills,
|
|
393
|
+
abortSignal: startOptions.abortSignal,
|
|
394
|
+
});
|
|
383
395
|
}
|
|
384
396
|
const report = startOptions.observability?.report;
|
|
385
397
|
const onDiagnostic = report
|
|
@@ -451,8 +463,8 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
451
463
|
isResume: true,
|
|
452
464
|
lifecycleState: lifecycleData,
|
|
453
465
|
}),
|
|
454
|
-
skillCatalog,
|
|
455
466
|
skillsFingerprint,
|
|
467
|
+
skillsDirectory,
|
|
456
468
|
acpSessionId: lifecycleData.acpSessionId,
|
|
457
469
|
bridgePort: coords.port,
|
|
458
470
|
bridgeToken: coords.token,
|
|
@@ -463,6 +475,7 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
463
475
|
turnStartConfig: lifecycleData.turnStartConfig,
|
|
464
476
|
recoveryStatus: lifecycleData.recovery,
|
|
465
477
|
restoration: lifecycleData.restoration,
|
|
478
|
+
sandboxCredentialEnvironment,
|
|
466
479
|
replayOnly: false,
|
|
467
480
|
lossyRerun: false,
|
|
468
481
|
});
|
|
@@ -544,12 +557,14 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
544
557
|
}
|
|
545
558
|
|
|
546
559
|
const forwardedImplementationEnvironment =
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
560
|
+
sandboxCredentialEnvironment == null
|
|
561
|
+
? await applyCredentialForwarding({
|
|
562
|
+
environment: sandboxImplementationEnvironment,
|
|
563
|
+
credentialEnvironmentVariables:
|
|
564
|
+
credentialForwardingEnvironmentVariables,
|
|
565
|
+
credentialForwarding: settings.credentialForwarding,
|
|
566
|
+
})
|
|
567
|
+
: sandboxImplementationEnvironment;
|
|
553
568
|
const port = resolveBridgePort({
|
|
554
569
|
sandboxSession,
|
|
555
570
|
override: portOverride,
|
|
@@ -714,8 +729,8 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
714
729
|
isResume,
|
|
715
730
|
lifecycleState: lifecycleData,
|
|
716
731
|
}),
|
|
717
|
-
skillCatalog,
|
|
718
732
|
skillsFingerprint,
|
|
733
|
+
skillsDirectory,
|
|
719
734
|
acpSessionId: lifecycleData?.acpSessionId,
|
|
720
735
|
bridgePort: boundPort,
|
|
721
736
|
bridgeToken: token,
|
|
@@ -742,6 +757,7 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
742
757
|
coldRestoration == null
|
|
743
758
|
? lifecycleData?.restoration
|
|
744
759
|
: { method: coldRestoration },
|
|
760
|
+
sandboxCredentialEnvironment,
|
|
745
761
|
replayOnly: respawnStrategy?.mode === 'disk-replay',
|
|
746
762
|
lossyRerun: respawnStrategy?.mode === 'lossy-rerun',
|
|
747
763
|
});
|
|
@@ -781,45 +797,6 @@ function resolveProviderEnvironment({
|
|
|
781
797
|
});
|
|
782
798
|
}
|
|
783
799
|
|
|
784
|
-
function resolveBrokeredProviderEnvironment({
|
|
785
|
-
resolvedProviderAuthentication,
|
|
786
|
-
clientApp,
|
|
787
|
-
}: {
|
|
788
|
-
resolvedProviderAuthentication: ReturnType<
|
|
789
|
-
typeof resolveACPProviderAuthentication
|
|
790
|
-
>;
|
|
791
|
-
clientApp: ACPClientApp;
|
|
792
|
-
}): Record<string, string> {
|
|
793
|
-
const providerAuthentication =
|
|
794
|
-
resolvedProviderAuthentication.providerAuthentication;
|
|
795
|
-
if (providerAuthentication?.type !== 'ai-gateway') return {};
|
|
796
|
-
|
|
797
|
-
const baseUrl = requireResolvedEnvironmentValue({
|
|
798
|
-
environment: resolvedProviderAuthentication.env,
|
|
799
|
-
name: 'AI_SDK_ACP_GATEWAY_BASE_URL',
|
|
800
|
-
});
|
|
801
|
-
|
|
802
|
-
return Object.fromEntries(
|
|
803
|
-
Object.entries(providerAuthentication.env).map(
|
|
804
|
-
([environmentVariableName, value]) => {
|
|
805
|
-
const environment = resolveACPLaunchEnvironment({
|
|
806
|
-
providerAuthentication: {
|
|
807
|
-
type: 'ai-gateway',
|
|
808
|
-
env: { [environmentVariableName]: value },
|
|
809
|
-
},
|
|
810
|
-
gateway: {
|
|
811
|
-
apiKey: environmentVariableName,
|
|
812
|
-
baseUrl,
|
|
813
|
-
clientAppName: clientApp.name,
|
|
814
|
-
clientAppVersion: clientApp.version,
|
|
815
|
-
},
|
|
816
|
-
});
|
|
817
|
-
return [environmentVariableName, environment[environmentVariableName]!];
|
|
818
|
-
},
|
|
819
|
-
),
|
|
820
|
-
);
|
|
821
|
-
}
|
|
822
|
-
|
|
823
800
|
function resolveACPProviderCredentialEnvironmentVariables({
|
|
824
801
|
providerAuthentication,
|
|
825
802
|
}: {
|
|
@@ -1071,8 +1048,8 @@ function createSession({
|
|
|
1071
1048
|
mcpServers,
|
|
1072
1049
|
isMcpToolCall,
|
|
1073
1050
|
initialGuidanceApplied: initialGuidanceAppliedAtStart,
|
|
1074
|
-
skillCatalog,
|
|
1075
1051
|
skillsFingerprint,
|
|
1052
|
+
skillsDirectory,
|
|
1076
1053
|
acpSessionId: acpSessionIdAtStart,
|
|
1077
1054
|
bridgePort,
|
|
1078
1055
|
bridgeToken,
|
|
@@ -1083,6 +1060,7 @@ function createSession({
|
|
|
1083
1060
|
turnStartConfig: turnStartConfigAtStart,
|
|
1084
1061
|
recoveryStatus,
|
|
1085
1062
|
restoration,
|
|
1063
|
+
sandboxCredentialEnvironment,
|
|
1086
1064
|
replayOnly,
|
|
1087
1065
|
lossyRerun,
|
|
1088
1066
|
}: {
|
|
@@ -1103,8 +1081,8 @@ function createSession({
|
|
|
1103
1081
|
mcpServers: Record<string, unknown> | undefined;
|
|
1104
1082
|
isMcpToolCall: ((toolCall: ACPToolCall) => boolean) | undefined;
|
|
1105
1083
|
initialGuidanceApplied: boolean;
|
|
1106
|
-
skillCatalog: ReadonlyArray<ACPSkillCatalogEntry>;
|
|
1107
1084
|
skillsFingerprint: string;
|
|
1085
|
+
skillsDirectory: string;
|
|
1108
1086
|
acpSessionId: string | undefined;
|
|
1109
1087
|
bridgePort: number;
|
|
1110
1088
|
bridgeToken: string;
|
|
@@ -1115,6 +1093,7 @@ function createSession({
|
|
|
1115
1093
|
turnStartConfig: ACPTurnStartConfig | undefined;
|
|
1116
1094
|
recoveryStatus: ACPLifecycleData['recovery'];
|
|
1117
1095
|
restoration: ACPLifecycleData['restoration'];
|
|
1096
|
+
sandboxCredentialEnvironment: Readonly<Record<string, string>> | undefined;
|
|
1118
1097
|
replayOnly: boolean;
|
|
1119
1098
|
lossyRerun: boolean;
|
|
1120
1099
|
}): HarnessV1Session {
|
|
@@ -1370,6 +1349,9 @@ function createSession({
|
|
|
1370
1349
|
}): ACPLifecycleData => ({
|
|
1371
1350
|
implementationIdentity,
|
|
1372
1351
|
authenticationProfile,
|
|
1352
|
+
...(sandboxCredentialEnvironment == null
|
|
1353
|
+
? {}
|
|
1354
|
+
: { sandboxCredentialEnvironment }),
|
|
1373
1355
|
...(latestACPSessionId == null ? {} : { acpSessionId: latestACPSessionId }),
|
|
1374
1356
|
...(bridge == null ? {} : { bridge }),
|
|
1375
1357
|
...(latestTurnStartConfig == null
|
|
@@ -1388,6 +1370,7 @@ function createSession({
|
|
|
1388
1370
|
initialGuidanceApplied,
|
|
1389
1371
|
skillsMaterialized: true,
|
|
1390
1372
|
skillsFingerprint,
|
|
1373
|
+
skillsDirectory,
|
|
1391
1374
|
});
|
|
1392
1375
|
|
|
1393
1376
|
return {
|
|
@@ -1458,7 +1441,6 @@ function createSession({
|
|
|
1458
1441
|
instructionMapping == null
|
|
1459
1442
|
? options.instructions
|
|
1460
1443
|
: undefined,
|
|
1461
|
-
skills: skillCatalog,
|
|
1462
1444
|
}),
|
|
1463
1445
|
...(instructionMapping == null
|
|
1464
1446
|
? {}
|
|
@@ -8,6 +8,7 @@ export type ACPPromptGuidanceLifecycleState = {
|
|
|
8
8
|
readonly initialGuidanceApplied?: boolean;
|
|
9
9
|
readonly skillsMaterialized?: boolean;
|
|
10
10
|
readonly skillsFingerprint?: string;
|
|
11
|
+
readonly skillsDirectory?: string;
|
|
11
12
|
};
|
|
12
13
|
|
|
13
14
|
export type ACPBridgeCoordinates = {
|
|
@@ -21,6 +22,7 @@ export type ACPBridgeCoordinates = {
|
|
|
21
22
|
export type ACPLifecycleData = ACPPromptGuidanceLifecycleState & {
|
|
22
23
|
readonly implementationIdentity: string;
|
|
23
24
|
readonly authenticationProfile?: ACPAuthenticationProfileIdentity;
|
|
25
|
+
readonly sandboxCredentialEnvironment?: Readonly<Record<string, string>>;
|
|
24
26
|
readonly acpSessionId?: string;
|
|
25
27
|
readonly bridge?: ACPBridgeCoordinates;
|
|
26
28
|
readonly coldSession?: ACPColdSessionState;
|
|
@@ -49,10 +51,12 @@ export function shouldMaterializeACPSkills({
|
|
|
49
51
|
isResume,
|
|
50
52
|
lifecycleState,
|
|
51
53
|
skillsFingerprint,
|
|
54
|
+
skillsDirectory,
|
|
52
55
|
}: {
|
|
53
56
|
isResume: boolean;
|
|
54
57
|
lifecycleState: ACPPromptGuidanceLifecycleState | undefined;
|
|
55
58
|
skillsFingerprint: string;
|
|
59
|
+
skillsDirectory: string;
|
|
56
60
|
}): boolean {
|
|
57
61
|
if (
|
|
58
62
|
isResume &&
|
|
@@ -63,7 +67,11 @@ export function shouldMaterializeACPSkills({
|
|
|
63
67
|
'ACP lifecycle state was created with a different set of skills.',
|
|
64
68
|
);
|
|
65
69
|
}
|
|
66
|
-
return
|
|
70
|
+
return (
|
|
71
|
+
!isResume ||
|
|
72
|
+
lifecycleState?.skillsMaterialized !== true ||
|
|
73
|
+
lifecycleState.skillsDirectory !== skillsDirectory
|
|
74
|
+
);
|
|
67
75
|
}
|
|
68
76
|
|
|
69
77
|
export function validateACPLifecycleCompatibility({
|
package/src/v1/acp-v1-prompt.ts
CHANGED
|
@@ -11,12 +11,6 @@ export const acpTextContentBlockSchema = z.object({
|
|
|
11
11
|
|
|
12
12
|
export type ACPTextContentBlock = z.infer<typeof acpTextContentBlockSchema>;
|
|
13
13
|
|
|
14
|
-
export type ACPSkillCatalogEntry = {
|
|
15
|
-
readonly name: string;
|
|
16
|
-
readonly description: string;
|
|
17
|
-
readonly path: string;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
14
|
export function convertHarnessPromptToACPTextBlocks({
|
|
21
15
|
prompt,
|
|
22
16
|
harnessId,
|
|
@@ -75,32 +69,18 @@ export function convertHarnessPromptToACPTextBlocks({
|
|
|
75
69
|
export function prependACPInitialGuidance({
|
|
76
70
|
prompt,
|
|
77
71
|
instructions,
|
|
78
|
-
skills,
|
|
79
72
|
}: {
|
|
80
73
|
prompt: ReadonlyArray<ACPTextContentBlock>;
|
|
81
74
|
instructions: string | undefined;
|
|
82
|
-
skills: ReadonlyArray<ACPSkillCatalogEntry>;
|
|
83
75
|
}): ACPTextContentBlock[] {
|
|
84
76
|
const hasInstructions = instructions != null && instructions.length > 0;
|
|
85
|
-
if (!hasInstructions
|
|
77
|
+
if (!hasInstructions) return [...prompt];
|
|
86
78
|
|
|
87
79
|
const lines = [
|
|
88
80
|
'<session-guidance>',
|
|
89
81
|
'This block is operating guidance from the harness, not user-authored content.',
|
|
90
82
|
];
|
|
91
|
-
|
|
92
|
-
lines.push('<instructions>', instructions, '</instructions>');
|
|
93
|
-
}
|
|
94
|
-
if (skills.length > 0) {
|
|
95
|
-
lines.push(
|
|
96
|
-
'<available-skills>',
|
|
97
|
-
'Load a skill only when relevant by reading its SKILL.md file at the listed absolute path.',
|
|
98
|
-
);
|
|
99
|
-
for (const skill of skills) {
|
|
100
|
-
lines.push(`- ${skill.name}: ${skill.description} (${skill.path})`);
|
|
101
|
-
}
|
|
102
|
-
lines.push('</available-skills>');
|
|
103
|
-
}
|
|
83
|
+
lines.push('<instructions>', instructions, '</instructions>');
|
|
104
84
|
lines.push('</session-guidance>');
|
|
105
85
|
|
|
106
86
|
return [{ type: 'text', text: lines.join('\n') }, ...prompt];
|
|
@@ -76,8 +76,10 @@ export type ACPProviderAuthentication = {
|
|
|
76
76
|
|
|
77
77
|
export type ACPCredentialBrokering = ({
|
|
78
78
|
env,
|
|
79
|
+
sandboxEnv,
|
|
79
80
|
}: {
|
|
80
81
|
env: Readonly<Record<string, string>>;
|
|
82
|
+
sandboxEnv?: Readonly<Record<string, string>>;
|
|
81
83
|
}) => ReadonlyArray<HarnessV1RequestTransformation>;
|
|
82
84
|
|
|
83
85
|
export type ACPPermissionModeTarget =
|
|
@@ -142,6 +144,11 @@ export type ACPV1Settings = {
|
|
|
142
144
|
readonly authentication?: ACPAuthentication;
|
|
143
145
|
readonly providerAuthentication?: ACPProviderAuthentication;
|
|
144
146
|
readonly modelId?: string;
|
|
147
|
+
/**
|
|
148
|
+
* Native skills directory relative to the ACP implementation's home
|
|
149
|
+
* directory. Defaults to `.agents/skills`.
|
|
150
|
+
*/
|
|
151
|
+
readonly skillsDirectory?: string;
|
|
145
152
|
/**
|
|
146
153
|
* Routes HarnessAgent instructions to a runtime-native system or developer
|
|
147
154
|
* prompt. When omitted, instructions are prepended to the first user prompt.
|
package/src/v1/acp-v1-skills.ts
CHANGED
|
@@ -3,9 +3,9 @@ import path from 'node:path';
|
|
|
3
3
|
import type { HarnessV1Skill } from '@ai-sdk/harness';
|
|
4
4
|
import { writeSkills } from '@ai-sdk/harness/utils';
|
|
5
5
|
import type { Experimental_SandboxSession } from '@ai-sdk/provider-utils';
|
|
6
|
-
import type { ACPSkillCatalogEntry } from './acp-v1-prompt';
|
|
7
6
|
|
|
8
7
|
const ACP_SKILL_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
8
|
+
export const DEFAULT_ACP_SKILLS_DIRECTORY = '.agents/skills';
|
|
9
9
|
|
|
10
10
|
export function createACPSkillsFingerprint({
|
|
11
11
|
skills,
|
|
@@ -59,37 +59,21 @@ export function resolveACPPrivateSessionDirectory({
|
|
|
59
59
|
|
|
60
60
|
export async function materializeACPSkills({
|
|
61
61
|
sandbox,
|
|
62
|
-
|
|
62
|
+
rootDir,
|
|
63
63
|
sessionWorkDir,
|
|
64
|
-
harnessId,
|
|
65
|
-
sessionId,
|
|
66
64
|
skills,
|
|
67
65
|
shouldMaterialize,
|
|
68
66
|
abortSignal,
|
|
69
67
|
}: {
|
|
70
68
|
sandbox: Experimental_SandboxSession;
|
|
71
|
-
|
|
69
|
+
rootDir: string;
|
|
72
70
|
sessionWorkDir: string;
|
|
73
|
-
harnessId: string;
|
|
74
|
-
sessionId: string;
|
|
75
71
|
skills: ReadonlyArray<HarnessV1Skill>;
|
|
76
72
|
shouldMaterialize: boolean;
|
|
77
73
|
abortSignal?: AbortSignal;
|
|
78
|
-
}): Promise<{
|
|
79
|
-
readonly catalog: ReadonlyArray<ACPSkillCatalogEntry>;
|
|
80
|
-
readonly rootDir: string;
|
|
81
|
-
}> {
|
|
74
|
+
}): Promise<void> {
|
|
82
75
|
validateACPSkills({ skills });
|
|
83
|
-
|
|
84
|
-
const rootDir = path.posix.join(
|
|
85
|
-
resolveACPPrivateSessionDirectory({
|
|
86
|
-
sandboxHomeDir,
|
|
87
|
-
sessionWorkDir,
|
|
88
|
-
harnessId,
|
|
89
|
-
sessionId,
|
|
90
|
-
}),
|
|
91
|
-
'skills',
|
|
92
|
-
);
|
|
76
|
+
assertOutsideSessionWorkDir({ rootDir, sessionWorkDir });
|
|
93
77
|
|
|
94
78
|
if (shouldMaterialize) {
|
|
95
79
|
await writeSkills({
|
|
@@ -106,15 +90,40 @@ export async function materializeACPSkills({
|
|
|
106
90
|
)}: expected a relative POSIX path without traversal.`,
|
|
107
91
|
});
|
|
108
92
|
}
|
|
93
|
+
}
|
|
109
94
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
95
|
+
export function resolveACPSkillsDirectory({
|
|
96
|
+
implementationHomeDir,
|
|
97
|
+
skillsDirectory = DEFAULT_ACP_SKILLS_DIRECTORY,
|
|
98
|
+
sessionWorkDir,
|
|
99
|
+
}: {
|
|
100
|
+
implementationHomeDir: string;
|
|
101
|
+
skillsDirectory?: string;
|
|
102
|
+
sessionWorkDir: string;
|
|
103
|
+
}): string {
|
|
104
|
+
const containsTraversal = skillsDirectory
|
|
105
|
+
.split(/[\\/]/)
|
|
106
|
+
.some(segment => segment === '..');
|
|
107
|
+
const normalizedDirectory = path.posix.normalize(skillsDirectory);
|
|
108
|
+
if (
|
|
109
|
+
skillsDirectory.length === 0 ||
|
|
110
|
+
skillsDirectory.includes('\\') ||
|
|
111
|
+
path.posix.isAbsolute(skillsDirectory) ||
|
|
112
|
+
path.win32.isAbsolute(skillsDirectory) ||
|
|
113
|
+
containsTraversal ||
|
|
114
|
+
normalizedDirectory === '.' ||
|
|
115
|
+
normalizedDirectory.startsWith('../') ||
|
|
116
|
+
normalizedDirectory.includes('/../') ||
|
|
117
|
+
normalizedDirectory.endsWith('/..')
|
|
118
|
+
) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
`ACP skillsDirectory ${JSON.stringify(skillsDirectory)} must be a relative POSIX path without traversal.`,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const rootDir = path.posix.join(implementationHomeDir, normalizedDirectory);
|
|
125
|
+
assertOutsideSessionWorkDir({ rootDir, sessionWorkDir });
|
|
126
|
+
return rootDir;
|
|
118
127
|
}
|
|
119
128
|
|
|
120
129
|
function validateACPSkills({
|