@ai-sdk/harness-acp 1.0.28 → 1.0.30
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 +15 -0
- package/README.md +5 -0
- package/dist/bridge/host-tool-mcp.mjs +1 -1
- package/dist/index.d.ts +11 -5
- package/dist/index.js +86 -68
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/acp-auth.ts +38 -13
- package/src/acp-harness.ts +2 -0
- package/src/index.ts +1 -2
- package/src/v1/acp-v1-harness.ts +29 -19
- package/src/v1/acp-v1-lifecycle.ts +8 -1
- package/src/v1/acp-v1-prompt.ts +2 -22
- package/src/v1/acp-v1-settings.ts +8 -2
- package/src/v1/acp-v1-skills.ts +38 -29
- package/src/v1/implementation.ts +9 -4
- package/src/v1/index.ts +1 -1
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.30",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
}
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@ai-sdk/harness": "1.0.
|
|
29
|
+
"@ai-sdk/harness": "1.0.92",
|
|
30
30
|
"@ai-sdk/provider-utils": "5.0.32",
|
|
31
31
|
"ws": "^8.21.0"
|
|
32
32
|
},
|
package/src/acp-auth.ts
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
getAiGatewayAuthFromEnv,
|
|
4
|
+
isHarnessAuthenticationEnvironment,
|
|
5
|
+
} from '@ai-sdk/harness/utils';
|
|
3
6
|
import type {
|
|
4
7
|
ACPAuthentication,
|
|
8
|
+
ACPAuthenticationMode as ACPV1AuthenticationMode,
|
|
5
9
|
ACPProviderAuthentication,
|
|
6
|
-
ACPProviderAuthenticationMode,
|
|
7
10
|
} from './v1';
|
|
8
11
|
import type { ACPResolvedProviderAuthentication } from './v1/bridge/acp-v1-bridge-environment';
|
|
9
12
|
|
|
10
13
|
const DEFAULT_AI_GATEWAY_BASE_URL = 'https://ai-gateway.vercel.sh';
|
|
11
14
|
|
|
12
|
-
export type
|
|
15
|
+
export type ACPAuthenticationMode = ACPV1AuthenticationMode;
|
|
13
16
|
|
|
14
17
|
export type ACPClientApp = {
|
|
15
18
|
readonly name: string;
|
|
@@ -17,7 +20,7 @@ export type ACPClientApp = {
|
|
|
17
20
|
};
|
|
18
21
|
|
|
19
22
|
type ACPAuthResolutionOptions = {
|
|
20
|
-
readonly mode?:
|
|
23
|
+
readonly mode?: ACPAuthenticationMode;
|
|
21
24
|
readonly providerAuthentication: ACPProviderAuthentication | undefined;
|
|
22
25
|
readonly clientApp: ACPClientApp;
|
|
23
26
|
};
|
|
@@ -102,6 +105,10 @@ export function resolveACPProviderAuthentication({
|
|
|
102
105
|
AI_SDK_ACP_CLIENT_APP_NAME: auth.clientApp.name,
|
|
103
106
|
AI_SDK_ACP_CLIENT_APP_VERSION: auth.clientApp.version,
|
|
104
107
|
};
|
|
108
|
+
const resolvedEnv = resolveACPAuthenticationEnvironment({
|
|
109
|
+
auth: auth.mode,
|
|
110
|
+
env,
|
|
111
|
+
});
|
|
105
112
|
const providerAuthentication = auth.providerAuthentication;
|
|
106
113
|
if (providerAuthentication == null) {
|
|
107
114
|
return { providerAuthentication: undefined, env: clientAppEnv };
|
|
@@ -114,7 +121,7 @@ export function resolveACPProviderAuthentication({
|
|
|
114
121
|
};
|
|
115
122
|
}
|
|
116
123
|
|
|
117
|
-
const mode = auth.mode
|
|
124
|
+
const mode = resolveACPProviderAuthenticationMode(auth.mode);
|
|
118
125
|
if (mode === 'direct') {
|
|
119
126
|
return {
|
|
120
127
|
providerAuthentication: { type: 'direct' },
|
|
@@ -126,12 +133,12 @@ export function resolveACPProviderAuthentication({
|
|
|
126
133
|
compatibility?.type === 'ai-gateway'
|
|
127
134
|
? {
|
|
128
135
|
apiKey: resolveGatewayCredential({
|
|
129
|
-
env,
|
|
136
|
+
env: resolvedEnv,
|
|
130
137
|
credentialSource: compatibility.credentialSource,
|
|
131
138
|
}),
|
|
132
139
|
baseUrl: compatibility.baseUrl,
|
|
133
140
|
}
|
|
134
|
-
: getAiGatewayAuthFromEnv({ env });
|
|
141
|
+
: getAiGatewayAuthFromEnv({ env: resolvedEnv });
|
|
135
142
|
const apiKey = gateway.apiKey;
|
|
136
143
|
if (compatibility == null && mode === 'auto' && apiKey == null) {
|
|
137
144
|
return {
|
|
@@ -166,20 +173,22 @@ export function resolveACPProviderAuthenticationCompatibility({
|
|
|
166
173
|
providerAuthentication,
|
|
167
174
|
env,
|
|
168
175
|
}: {
|
|
169
|
-
auth?:
|
|
176
|
+
auth?: ACPAuthenticationMode;
|
|
170
177
|
providerAuthentication: ACPProviderAuthentication | undefined;
|
|
171
178
|
env: Record<string, string | undefined>;
|
|
172
179
|
}): ACPProviderAuthenticationCompatibility | undefined {
|
|
180
|
+
const resolvedEnv = resolveACPAuthenticationEnvironment({
|
|
181
|
+
auth,
|
|
182
|
+
env,
|
|
183
|
+
});
|
|
173
184
|
if (providerAuthentication == null) return undefined;
|
|
174
185
|
|
|
175
|
-
const mode = auth;
|
|
186
|
+
const mode = resolveACPProviderAuthenticationMode(auth);
|
|
176
187
|
if (mode === 'direct') {
|
|
177
188
|
return { type: 'direct', mode };
|
|
178
189
|
}
|
|
179
190
|
|
|
180
|
-
const credentialSource = resolveGatewayCredentialSource({
|
|
181
|
-
env,
|
|
182
|
-
});
|
|
191
|
+
const credentialSource = resolveGatewayCredentialSource({ env: resolvedEnv });
|
|
183
192
|
if (mode === 'auto' && credentialSource == null) {
|
|
184
193
|
return { type: 'direct', mode };
|
|
185
194
|
}
|
|
@@ -189,10 +198,26 @@ export function resolveACPProviderAuthenticationCompatibility({
|
|
|
189
198
|
mode,
|
|
190
199
|
env: providerAuthentication.gateway.env,
|
|
191
200
|
credentialSource,
|
|
192
|
-
baseUrl:
|
|
201
|
+
baseUrl: resolvedEnv.AI_GATEWAY_BASE_URL ?? DEFAULT_AI_GATEWAY_BASE_URL,
|
|
193
202
|
};
|
|
194
203
|
}
|
|
195
204
|
|
|
205
|
+
function resolveACPProviderAuthenticationMode(
|
|
206
|
+
auth: ACPAuthenticationMode | undefined,
|
|
207
|
+
): Extract<ACPAuthenticationMode, string> {
|
|
208
|
+
return typeof auth === 'string' || auth == null ? (auth ?? 'auto') : 'auto';
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function resolveACPAuthenticationEnvironment({
|
|
212
|
+
auth,
|
|
213
|
+
env,
|
|
214
|
+
}: {
|
|
215
|
+
auth: ACPAuthenticationMode | undefined;
|
|
216
|
+
env: Record<string, string | undefined>;
|
|
217
|
+
}): Record<string, string | undefined> {
|
|
218
|
+
return isHarnessAuthenticationEnvironment(auth) ? auth : env;
|
|
219
|
+
}
|
|
220
|
+
|
|
196
221
|
export function resolveACPEnv({
|
|
197
222
|
auth,
|
|
198
223
|
env,
|
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'];
|
|
@@ -104,6 +105,7 @@ const acpResumeStateSchema = z.object({
|
|
|
104
105
|
initialGuidanceApplied: z.boolean().optional(),
|
|
105
106
|
skillsMaterialized: z.boolean().optional(),
|
|
106
107
|
skillsFingerprint: z.string().optional(),
|
|
108
|
+
skillsDirectory: z.string().optional(),
|
|
107
109
|
});
|
|
108
110
|
|
|
109
111
|
export function createACP<TBuiltinTools extends ToolSet = {}>(
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { createACP } from './acp-harness';
|
|
2
|
-
export type {
|
|
2
|
+
export type { ACPAuthenticationMode, ACPClientApp } from './acp-auth';
|
|
3
3
|
export type { ACPHarnessSettings } from './acp-harness';
|
|
4
4
|
export type {
|
|
5
5
|
ACPAnnotations,
|
|
@@ -31,7 +31,6 @@ export type {
|
|
|
31
31
|
ACPPermissionModeTarget,
|
|
32
32
|
ACPProfileValue,
|
|
33
33
|
ACPProviderAuthentication,
|
|
34
|
-
ACPProviderAuthenticationMode,
|
|
35
34
|
ACPSerializablePrimitive,
|
|
36
35
|
ACPSerializableValue,
|
|
37
36
|
ACPSource,
|
package/src/v1/acp-v1-harness.ts
CHANGED
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
import { WebSocket } from 'ws';
|
|
40
40
|
import {
|
|
41
41
|
createACPAuthenticationProfileIdentity,
|
|
42
|
+
resolveACPAuthenticationEnvironment,
|
|
42
43
|
resolveACPProviderAuthentication,
|
|
43
44
|
resolveACPProviderAuthenticationCompatibility,
|
|
44
45
|
type ACPAuthenticationProfileIdentity,
|
|
@@ -76,7 +77,6 @@ import {
|
|
|
76
77
|
import {
|
|
77
78
|
convertHarnessPromptToACPTextBlocks,
|
|
78
79
|
prependACPInitialGuidance,
|
|
79
|
-
type ACPSkillCatalogEntry,
|
|
80
80
|
} from './acp-v1-prompt';
|
|
81
81
|
import type {
|
|
82
82
|
ACPInstructionMapping,
|
|
@@ -91,6 +91,7 @@ import {
|
|
|
91
91
|
createACPSkillsFingerprint,
|
|
92
92
|
materializeACPSkills,
|
|
93
93
|
resolveACPPrivateSessionDirectory,
|
|
94
|
+
resolveACPSkillsDirectory,
|
|
94
95
|
} from './acp-v1-skills';
|
|
95
96
|
|
|
96
97
|
const HARNESS_ID_REGEXP = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
@@ -188,6 +189,10 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
188
189
|
}
|
|
189
190
|
const permissionMode = startOptions.permissionMode ?? 'allow-all';
|
|
190
191
|
const env = { ...process.env };
|
|
192
|
+
const authenticationEnvironment = resolveACPAuthenticationEnvironment({
|
|
193
|
+
auth: settings.auth,
|
|
194
|
+
env,
|
|
195
|
+
});
|
|
191
196
|
const providerAuthenticationCompatibility =
|
|
192
197
|
resolveACPProviderAuthenticationCompatibility({
|
|
193
198
|
auth: settings.auth,
|
|
@@ -259,6 +264,7 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
259
264
|
const implementationEnvironment = resolveImplementationEnvironment({
|
|
260
265
|
implementation,
|
|
261
266
|
env,
|
|
267
|
+
credentialEnv: authenticationEnvironment,
|
|
262
268
|
});
|
|
263
269
|
let sandboxImplementationEnvironment = implementationEnvironment;
|
|
264
270
|
let sandboxProviderAuthenticationEnvironment =
|
|
@@ -366,6 +372,14 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
366
372
|
harnessId: settings.harnessId,
|
|
367
373
|
sessionId: startOptions.sessionId,
|
|
368
374
|
});
|
|
375
|
+
const skillsDirectory = resolveACPSkillsDirectory({
|
|
376
|
+
implementationHomeDir:
|
|
377
|
+
implementation.source.type === 'install-command'
|
|
378
|
+
? `${resolvedImplementationDir}/home`
|
|
379
|
+
: sandboxHomeDir,
|
|
380
|
+
skillsDirectory: settings.skillsDirectory,
|
|
381
|
+
sessionWorkDir: workDir,
|
|
382
|
+
});
|
|
369
383
|
const bridgeStateDir = `${privateSessionDir}/bridge`;
|
|
370
384
|
const skills = startOptions.skills ?? [];
|
|
371
385
|
const skillsFingerprint = createACPSkillsFingerprint({ skills });
|
|
@@ -373,21 +387,17 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
373
387
|
isResume,
|
|
374
388
|
lifecycleState: lifecycleData,
|
|
375
389
|
skillsFingerprint,
|
|
390
|
+
skillsDirectory,
|
|
376
391
|
});
|
|
377
|
-
let skillCatalog: ReadonlyArray<ACPSkillCatalogEntry> = [];
|
|
378
392
|
if (skills.length > 0) {
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
shouldMaterialize: shouldMaterializeSkills,
|
|
388
|
-
abortSignal: startOptions.abortSignal,
|
|
389
|
-
})
|
|
390
|
-
).catalog;
|
|
393
|
+
await materializeACPSkills({
|
|
394
|
+
sandbox: toolSafeSandboxSession,
|
|
395
|
+
rootDir: skillsDirectory,
|
|
396
|
+
sessionWorkDir: workDir,
|
|
397
|
+
skills,
|
|
398
|
+
shouldMaterialize: shouldMaterializeSkills,
|
|
399
|
+
abortSignal: startOptions.abortSignal,
|
|
400
|
+
});
|
|
391
401
|
}
|
|
392
402
|
const report = startOptions.observability?.report;
|
|
393
403
|
const onDiagnostic = report
|
|
@@ -459,8 +469,8 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
459
469
|
isResume: true,
|
|
460
470
|
lifecycleState: lifecycleData,
|
|
461
471
|
}),
|
|
462
|
-
skillCatalog,
|
|
463
472
|
skillsFingerprint,
|
|
473
|
+
skillsDirectory,
|
|
464
474
|
acpSessionId: lifecycleData.acpSessionId,
|
|
465
475
|
bridgePort: coords.port,
|
|
466
476
|
bridgeToken: coords.token,
|
|
@@ -725,8 +735,8 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
725
735
|
isResume,
|
|
726
736
|
lifecycleState: lifecycleData,
|
|
727
737
|
}),
|
|
728
|
-
skillCatalog,
|
|
729
738
|
skillsFingerprint,
|
|
739
|
+
skillsDirectory,
|
|
730
740
|
acpSessionId: lifecycleData?.acpSessionId,
|
|
731
741
|
bridgePort: boundPort,
|
|
732
742
|
bridgeToken: token,
|
|
@@ -1044,8 +1054,8 @@ function createSession({
|
|
|
1044
1054
|
mcpServers,
|
|
1045
1055
|
isMcpToolCall,
|
|
1046
1056
|
initialGuidanceApplied: initialGuidanceAppliedAtStart,
|
|
1047
|
-
skillCatalog,
|
|
1048
1057
|
skillsFingerprint,
|
|
1058
|
+
skillsDirectory,
|
|
1049
1059
|
acpSessionId: acpSessionIdAtStart,
|
|
1050
1060
|
bridgePort,
|
|
1051
1061
|
bridgeToken,
|
|
@@ -1077,8 +1087,8 @@ function createSession({
|
|
|
1077
1087
|
mcpServers: Record<string, unknown> | undefined;
|
|
1078
1088
|
isMcpToolCall: ((toolCall: ACPToolCall) => boolean) | undefined;
|
|
1079
1089
|
initialGuidanceApplied: boolean;
|
|
1080
|
-
skillCatalog: ReadonlyArray<ACPSkillCatalogEntry>;
|
|
1081
1090
|
skillsFingerprint: string;
|
|
1091
|
+
skillsDirectory: string;
|
|
1082
1092
|
acpSessionId: string | undefined;
|
|
1083
1093
|
bridgePort: number;
|
|
1084
1094
|
bridgeToken: string;
|
|
@@ -1366,6 +1376,7 @@ function createSession({
|
|
|
1366
1376
|
initialGuidanceApplied,
|
|
1367
1377
|
skillsMaterialized: true,
|
|
1368
1378
|
skillsFingerprint,
|
|
1379
|
+
skillsDirectory,
|
|
1369
1380
|
});
|
|
1370
1381
|
|
|
1371
1382
|
return {
|
|
@@ -1436,7 +1447,6 @@ function createSession({
|
|
|
1436
1447
|
instructionMapping == null
|
|
1437
1448
|
? options.instructions
|
|
1438
1449
|
: undefined,
|
|
1439
|
-
skills: skillCatalog,
|
|
1440
1450
|
}),
|
|
1441
1451
|
...(instructionMapping == null
|
|
1442
1452
|
? {}
|
|
@@ -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 = {
|
|
@@ -50,10 +51,12 @@ export function shouldMaterializeACPSkills({
|
|
|
50
51
|
isResume,
|
|
51
52
|
lifecycleState,
|
|
52
53
|
skillsFingerprint,
|
|
54
|
+
skillsDirectory,
|
|
53
55
|
}: {
|
|
54
56
|
isResume: boolean;
|
|
55
57
|
lifecycleState: ACPPromptGuidanceLifecycleState | undefined;
|
|
56
58
|
skillsFingerprint: string;
|
|
59
|
+
skillsDirectory: string;
|
|
57
60
|
}): boolean {
|
|
58
61
|
if (
|
|
59
62
|
isResume &&
|
|
@@ -64,7 +67,11 @@ export function shouldMaterializeACPSkills({
|
|
|
64
67
|
'ACP lifecycle state was created with a different set of skills.',
|
|
65
68
|
);
|
|
66
69
|
}
|
|
67
|
-
return
|
|
70
|
+
return (
|
|
71
|
+
!isResume ||
|
|
72
|
+
lifecycleState?.skillsMaterialized !== true ||
|
|
73
|
+
lifecycleState.skillsDirectory !== skillsDirectory
|
|
74
|
+
);
|
|
68
75
|
}
|
|
69
76
|
|
|
70
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];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
HarnessV1Authentication,
|
|
2
3
|
HarnessV1CredentialForwarding,
|
|
3
4
|
HarnessV1PermissionMode,
|
|
4
5
|
HarnessV1RequestTransformation,
|
|
@@ -66,7 +67,7 @@ export type ACPAuthentication = {
|
|
|
66
67
|
readonly clientCapabilities?: Readonly<Record<string, ACPSerializableValue>>;
|
|
67
68
|
};
|
|
68
69
|
|
|
69
|
-
export type
|
|
70
|
+
export type ACPAuthenticationMode = HarnessV1Authentication;
|
|
70
71
|
|
|
71
72
|
export type ACPProviderAuthentication = {
|
|
72
73
|
readonly gateway: {
|
|
@@ -123,7 +124,7 @@ export type ACPV1Settings = {
|
|
|
123
124
|
readonly harnessId: string;
|
|
124
125
|
readonly mcpServers?: Record<string, unknown>;
|
|
125
126
|
readonly isMcpToolCall?: (toolCall: ACPToolCall) => boolean;
|
|
126
|
-
readonly auth?:
|
|
127
|
+
readonly auth?: ACPAuthenticationMode;
|
|
127
128
|
readonly source: ACPSource;
|
|
128
129
|
readonly executable: string;
|
|
129
130
|
readonly args?: ReadonlyArray<string>;
|
|
@@ -144,6 +145,11 @@ export type ACPV1Settings = {
|
|
|
144
145
|
readonly authentication?: ACPAuthentication;
|
|
145
146
|
readonly providerAuthentication?: ACPProviderAuthentication;
|
|
146
147
|
readonly modelId?: string;
|
|
148
|
+
/**
|
|
149
|
+
* Native skills directory relative to the ACP implementation's home
|
|
150
|
+
* directory. Defaults to `.agents/skills`.
|
|
151
|
+
*/
|
|
152
|
+
readonly skillsDirectory?: string;
|
|
147
153
|
/**
|
|
148
154
|
* Routes HarnessAgent instructions to a runtime-native system or developer
|
|
149
155
|
* 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({
|
package/src/v1/implementation.ts
CHANGED
|
@@ -277,20 +277,25 @@ ${implementation.source.command}
|
|
|
277
277
|
export function resolveImplementationEnvironment({
|
|
278
278
|
implementation,
|
|
279
279
|
env,
|
|
280
|
+
credentialEnv = env,
|
|
280
281
|
}: {
|
|
281
282
|
implementation: ACPImplementation;
|
|
282
283
|
env: Readonly<Record<string, string | undefined>>;
|
|
284
|
+
credentialEnv?: Readonly<Record<string, string | undefined>>;
|
|
283
285
|
}): Record<string, string> {
|
|
284
286
|
const forwardedEnvironment: Record<string, string> = {};
|
|
285
|
-
for (const name of [
|
|
286
|
-
...(implementation.forwardEnv ?? []),
|
|
287
|
-
...(implementation.credentialEnv ?? []),
|
|
288
|
-
]) {
|
|
287
|
+
for (const name of implementation.forwardEnv ?? []) {
|
|
289
288
|
const value = env[name];
|
|
290
289
|
if (value != null && value.length > 0) {
|
|
291
290
|
forwardedEnvironment[name] = value;
|
|
292
291
|
}
|
|
293
292
|
}
|
|
293
|
+
for (const name of implementation.credentialEnv ?? []) {
|
|
294
|
+
const value = credentialEnv[name];
|
|
295
|
+
if (value != null && value.length > 0) {
|
|
296
|
+
forwardedEnvironment[name] = value;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
294
299
|
return {
|
|
295
300
|
...forwardedEnvironment,
|
|
296
301
|
...implementation.env,
|
package/src/v1/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ export { createACPV1 } from './acp-v1-harness';
|
|
|
2
2
|
export type {
|
|
3
3
|
ACPCredentialBrokering,
|
|
4
4
|
ACPAuthentication,
|
|
5
|
+
ACPAuthenticationMode,
|
|
5
6
|
ACPInstallCommandSource,
|
|
6
7
|
ACPInstructionMapping,
|
|
7
8
|
ACPNpmLockedSource,
|
|
@@ -11,7 +12,6 @@ export type {
|
|
|
11
12
|
ACPPermissionModeTarget,
|
|
12
13
|
ACPProfileValue,
|
|
13
14
|
ACPProviderAuthentication,
|
|
14
|
-
ACPProviderAuthenticationMode,
|
|
15
15
|
ACPSerializablePrimitive,
|
|
16
16
|
ACPSerializableValue,
|
|
17
17
|
ACPSource,
|