@ai-sdk/harness-acp 0.0.0 → 1.0.0
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 +13 -0
- package/LICENSE +13 -0
- package/README.md +70 -3
- package/dist/bridge/host-tool-mcp.mjs +302 -0
- package/dist/bridge/host-tool-mcp.mjs.map +1 -0
- package/dist/bridge/index.mjs +3278 -0
- package/dist/bridge/index.mjs.map +1 -0
- package/dist/bridge/package.json +12 -0
- package/dist/bridge/pnpm-lock.yaml +816 -0
- package/dist/index.d.ts +98 -0
- package/dist/index.js +2094 -0
- package/dist/index.js.map +1 -0
- package/package.json +64 -10
- package/src/acp-auth.ts +254 -0
- package/src/acp-bridge-protocol.ts +13 -0
- package/src/acp-harness.ts +160 -0
- package/src/index.ts +19 -0
- package/src/v1/acp-v1-bridge-protocol.ts +151 -0
- package/src/v1/acp-v1-harness.ts +1447 -0
- package/src/v1/acp-v1-lifecycle.ts +115 -0
- package/src/v1/acp-v1-prompt.ts +122 -0
- package/src/v1/acp-v1-settings.ts +98 -0
- package/src/v1/acp-v1-skills.ts +228 -0
- package/src/v1/acp-v1-turn-start-config.ts +93 -0
- package/src/v1/bridge/acp-diagnostics.ts +57 -0
- package/src/v1/bridge/acp-stream-capture.ts +152 -0
- package/src/v1/bridge/acp-v1-bridge-environment.ts +95 -0
- package/src/v1/bridge/create-emit-stream-event.ts +97 -0
- package/src/v1/bridge/host-tool-correlation.ts +640 -0
- package/src/v1/bridge/host-tool-mcp-server.ts +180 -0
- package/src/v1/bridge/host-tool-mcp.ts +185 -0
- package/src/v1/bridge/host-tool-relay-client.ts +53 -0
- package/src/v1/bridge/host-tool-relay.ts +510 -0
- package/src/v1/bridge/index.ts +729 -0
- package/src/v1/bridge/package.json +12 -0
- package/src/v1/bridge/permission-controller.ts +152 -0
- package/src/v1/bridge/permission-mode.ts +163 -0
- package/src/v1/bridge/pnpm-lock.yaml +816 -0
- package/src/v1/bridge/profile-values.ts +97 -0
- package/src/v1/bridge/protocol-configuration.ts +146 -0
- package/src/v1/bridge/recovered-session.ts +145 -0
- package/src/v1/bridge/refresh-host-tool-catalog.ts +33 -0
- package/src/v1/bridge/session-lifecycle.ts +83 -0
- package/src/v1/bridge/stream-translator.ts +901 -0
- package/src/v1/implementation.ts +282 -0
- package/src/v1/index.ts +16 -0
- package/src/version.ts +6 -0
- package/index.js +0 -1
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import type {
|
|
3
|
+
ACPClientApp,
|
|
4
|
+
ACPProviderAuthenticationCompatibility,
|
|
5
|
+
} from '../acp-auth';
|
|
6
|
+
import type {
|
|
7
|
+
ACPNpmImplementation,
|
|
8
|
+
ACPPermissionModeMapping,
|
|
9
|
+
ACPSimpleNpmImplementation,
|
|
10
|
+
} from './acp-v1-settings';
|
|
11
|
+
|
|
12
|
+
const EXACT_SEMVER_REGEXP =
|
|
13
|
+
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
14
|
+
const PACKAGE_NAME_REGEXP =
|
|
15
|
+
/^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/;
|
|
16
|
+
const EXECUTABLE_NAME_REGEXP = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
17
|
+
const ENVIRONMENT_VARIABLE_NAME_REGEXP = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
18
|
+
|
|
19
|
+
export function validateACPV1Implementation(
|
|
20
|
+
implementation: ACPNpmImplementation,
|
|
21
|
+
): void {
|
|
22
|
+
if (isLockedImplementation(implementation)) {
|
|
23
|
+
if (implementation.packageJson.length === 0) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
'ACP locked npm implementation packageJson must not be empty.',
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
if (implementation.pnpmLockYaml.length === 0) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
'ACP locked npm implementation pnpmLockYaml must not be empty.',
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
} else {
|
|
34
|
+
validateSimpleImplementation({ implementation });
|
|
35
|
+
}
|
|
36
|
+
if (!EXECUTABLE_NAME_REGEXP.test(implementation.executable)) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`ACP executable must be a package bin name without a path; received ${JSON.stringify(implementation.executable)}.`,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
validateForwardEnvironment({ forwardEnv: implementation.forwardEnv });
|
|
43
|
+
validateEnvironment({ env: implementation.env });
|
|
44
|
+
const forwardedKeys = new Set(implementation.forwardEnv ?? []);
|
|
45
|
+
for (const key of Object.keys(implementation.env ?? {})) {
|
|
46
|
+
if (forwardedKeys.has(key)) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`ACP runtime environment key ${JSON.stringify(key)} cannot be configured in both forwardEnv and env.`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function createImplementationManifest({
|
|
55
|
+
implementation,
|
|
56
|
+
}: {
|
|
57
|
+
implementation: ACPNpmImplementation;
|
|
58
|
+
}): string {
|
|
59
|
+
if (isLockedImplementation(implementation)) {
|
|
60
|
+
return implementation.packageJson;
|
|
61
|
+
}
|
|
62
|
+
return (
|
|
63
|
+
JSON.stringify(
|
|
64
|
+
{
|
|
65
|
+
name: 'harness-acp-implementation',
|
|
66
|
+
version: '0.0.0',
|
|
67
|
+
private: true,
|
|
68
|
+
type: 'module',
|
|
69
|
+
dependencies: {
|
|
70
|
+
[implementation.packageName]: implementation.version,
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
null,
|
|
74
|
+
2,
|
|
75
|
+
) + '\n'
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function getImplementationLockfile({
|
|
80
|
+
implementation,
|
|
81
|
+
}: {
|
|
82
|
+
implementation: ACPNpmImplementation;
|
|
83
|
+
}): string | undefined {
|
|
84
|
+
return isLockedImplementation(implementation)
|
|
85
|
+
? implementation.pnpmLockYaml
|
|
86
|
+
: undefined;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function createImplementationDescriptor({
|
|
90
|
+
implementation,
|
|
91
|
+
implementationIdentity,
|
|
92
|
+
}: {
|
|
93
|
+
implementation: ACPNpmImplementation;
|
|
94
|
+
implementationIdentity: string;
|
|
95
|
+
}): string {
|
|
96
|
+
return (
|
|
97
|
+
JSON.stringify(
|
|
98
|
+
{
|
|
99
|
+
executable: implementation.executable,
|
|
100
|
+
args: implementation.args ?? [],
|
|
101
|
+
envKeys: getImplementationEnvironmentKeys({ implementation }),
|
|
102
|
+
implementationIdentity,
|
|
103
|
+
},
|
|
104
|
+
null,
|
|
105
|
+
2,
|
|
106
|
+
) + '\n'
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function createImplementationIdentity({
|
|
111
|
+
harnessId,
|
|
112
|
+
acpVersion,
|
|
113
|
+
implementation,
|
|
114
|
+
clientApp,
|
|
115
|
+
providerAuthentication,
|
|
116
|
+
permissionModeMapping,
|
|
117
|
+
}: {
|
|
118
|
+
harnessId: string;
|
|
119
|
+
acpVersion: 'v1';
|
|
120
|
+
implementation: ACPNpmImplementation;
|
|
121
|
+
clientApp: ACPClientApp;
|
|
122
|
+
providerAuthentication: ACPProviderAuthenticationCompatibility | undefined;
|
|
123
|
+
permissionModeMapping?: ACPPermissionModeMapping;
|
|
124
|
+
}): string {
|
|
125
|
+
const acquisition = isLockedImplementation(implementation)
|
|
126
|
+
? {
|
|
127
|
+
mode: 'locked',
|
|
128
|
+
packageJson: implementation.packageJson,
|
|
129
|
+
pnpmLockYaml: implementation.pnpmLockYaml,
|
|
130
|
+
}
|
|
131
|
+
: {
|
|
132
|
+
mode: 'simple',
|
|
133
|
+
packageName: implementation.packageName,
|
|
134
|
+
version: implementation.version,
|
|
135
|
+
};
|
|
136
|
+
const forwardedEnvironment = [
|
|
137
|
+
...new Set(implementation.forwardEnv ?? []),
|
|
138
|
+
].sort();
|
|
139
|
+
const literalEnvironment = Object.fromEntries(
|
|
140
|
+
Object.entries(implementation.env ?? {})
|
|
141
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
142
|
+
.map(([key, value]) => [key, { value }]),
|
|
143
|
+
);
|
|
144
|
+
const payload = {
|
|
145
|
+
harnessId,
|
|
146
|
+
acpVersion,
|
|
147
|
+
acquisition,
|
|
148
|
+
executable: implementation.executable,
|
|
149
|
+
args: implementation.args ?? [],
|
|
150
|
+
clientApp,
|
|
151
|
+
environment: {
|
|
152
|
+
forwarded: forwardedEnvironment,
|
|
153
|
+
literal: literalEnvironment,
|
|
154
|
+
},
|
|
155
|
+
providerAuthentication: providerAuthentication ?? null,
|
|
156
|
+
permissionModeMapping: permissionModeMapping ?? null,
|
|
157
|
+
};
|
|
158
|
+
return createHash('sha256')
|
|
159
|
+
.update(stableStringify({ value: payload }))
|
|
160
|
+
.digest('hex');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function createImplementationInstallCommand({
|
|
164
|
+
implementationDir,
|
|
165
|
+
storeDir,
|
|
166
|
+
implementation,
|
|
167
|
+
}: {
|
|
168
|
+
implementationDir: string;
|
|
169
|
+
storeDir: string;
|
|
170
|
+
implementation: ACPNpmImplementation;
|
|
171
|
+
}): string {
|
|
172
|
+
return (
|
|
173
|
+
`pnpm --dir ${implementationDir} install` +
|
|
174
|
+
(isLockedImplementation(implementation) ? ' --frozen-lockfile' : '') +
|
|
175
|
+
` --prod --store-dir ${storeDir}`
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function resolveImplementationEnvironment({
|
|
180
|
+
implementation,
|
|
181
|
+
env,
|
|
182
|
+
}: {
|
|
183
|
+
implementation: ACPNpmImplementation;
|
|
184
|
+
env: Readonly<Record<string, string | undefined>>;
|
|
185
|
+
}): Record<string, string> {
|
|
186
|
+
const forwardedEnvironment: Record<string, string> = {};
|
|
187
|
+
for (const name of implementation.forwardEnv ?? []) {
|
|
188
|
+
const value = env[name];
|
|
189
|
+
if (value != null && value.length > 0) {
|
|
190
|
+
forwardedEnvironment[name] = value;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return {
|
|
194
|
+
...forwardedEnvironment,
|
|
195
|
+
...implementation.env,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function validateSimpleImplementation({
|
|
200
|
+
implementation,
|
|
201
|
+
}: {
|
|
202
|
+
implementation: ACPSimpleNpmImplementation;
|
|
203
|
+
}): void {
|
|
204
|
+
if (!PACKAGE_NAME_REGEXP.test(implementation.packageName)) {
|
|
205
|
+
throw new Error(
|
|
206
|
+
`ACP npm package name is invalid: ${JSON.stringify(implementation.packageName)}.`,
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
if (!EXACT_SEMVER_REGEXP.test(implementation.version)) {
|
|
210
|
+
throw new Error(
|
|
211
|
+
`ACP npm implementation version must be an exact semantic version; received ${JSON.stringify(implementation.version)}.`,
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function validateEnvironment({
|
|
217
|
+
env,
|
|
218
|
+
}: {
|
|
219
|
+
env: Readonly<Record<string, string>> | undefined;
|
|
220
|
+
}): void {
|
|
221
|
+
for (const [key, value] of Object.entries(env ?? {})) {
|
|
222
|
+
if (!ENVIRONMENT_VARIABLE_NAME_REGEXP.test(key)) {
|
|
223
|
+
throw new Error(
|
|
224
|
+
`ACP environment variable name is invalid: ${JSON.stringify(key)}.`,
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
if (value.includes('\0')) {
|
|
228
|
+
throw new Error(`ACP runtime environment value for ${key} contains NUL.`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function validateForwardEnvironment({
|
|
234
|
+
forwardEnv,
|
|
235
|
+
}: {
|
|
236
|
+
forwardEnv: ReadonlyArray<string> | undefined;
|
|
237
|
+
}): void {
|
|
238
|
+
for (const name of forwardEnv ?? []) {
|
|
239
|
+
if (!ENVIRONMENT_VARIABLE_NAME_REGEXP.test(name)) {
|
|
240
|
+
throw new Error(
|
|
241
|
+
`ACP environment variable name is invalid: ${JSON.stringify(name)}.`,
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function getImplementationEnvironmentKeys({
|
|
248
|
+
implementation,
|
|
249
|
+
}: {
|
|
250
|
+
implementation: ACPNpmImplementation;
|
|
251
|
+
}): string[] {
|
|
252
|
+
return [
|
|
253
|
+
...new Set([
|
|
254
|
+
...(implementation.forwardEnv ?? []),
|
|
255
|
+
...Object.keys(implementation.env ?? {}),
|
|
256
|
+
]),
|
|
257
|
+
].sort();
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function isLockedImplementation(
|
|
261
|
+
implementation: ACPNpmImplementation,
|
|
262
|
+
): implementation is Extract<ACPNpmImplementation, { mode: 'locked' }> {
|
|
263
|
+
return implementation.mode === 'locked';
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function stableStringify({ value }: { value: unknown }): string {
|
|
267
|
+
return JSON.stringify(sortValue({ value }));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function sortValue({ value }: { value: unknown }): unknown {
|
|
271
|
+
if (Array.isArray(value)) {
|
|
272
|
+
return value.map(item => sortValue({ value: item }));
|
|
273
|
+
}
|
|
274
|
+
if (value != null && typeof value === 'object') {
|
|
275
|
+
return Object.fromEntries(
|
|
276
|
+
Object.entries(value)
|
|
277
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
278
|
+
.map(([key, item]) => [key, sortValue({ value: item })]),
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
return value;
|
|
282
|
+
}
|
package/src/v1/index.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export { createACPV1 } from './acp-v1-harness';
|
|
2
|
+
export type {
|
|
3
|
+
ACPAuthentication,
|
|
4
|
+
ACPLockedNpmImplementation,
|
|
5
|
+
ACPNpmImplementation,
|
|
6
|
+
ACPPermissionModeMapping,
|
|
7
|
+
ACPPermissionModeTarget,
|
|
8
|
+
ACPProfileValue,
|
|
9
|
+
ACPProviderAuthentication,
|
|
10
|
+
ACPProviderAuthenticationMode,
|
|
11
|
+
ACPSerializablePrimitive,
|
|
12
|
+
ACPSerializableValue,
|
|
13
|
+
ACPSimpleNpmImplementation,
|
|
14
|
+
ACPV1Settings,
|
|
15
|
+
ACPValueSource,
|
|
16
|
+
} from './acp-v1-settings';
|
package/src/version.ts
ADDED
package/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|