@ai-sdk/harness-acp 1.0.22 → 1.0.24

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/harness-acp",
3
- "version": "1.0.22",
3
+ "version": "1.0.24",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -27,8 +27,8 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "ws": "^8.21.0",
30
- "@ai-sdk/harness": "1.0.84",
31
- "@ai-sdk/provider-utils": "5.0.28"
30
+ "@ai-sdk/harness": "1.0.86",
31
+ "@ai-sdk/provider-utils": "5.0.29"
32
32
  },
33
33
  "peerDependencies": {
34
34
  "zod": "^3.25.76 || ^4.1.8"
package/src/index.ts CHANGED
@@ -22,6 +22,7 @@ export type {
22
22
  export type {
23
23
  ACPCredentialBrokering,
24
24
  ACPAuthentication,
25
+ ACPInstallCommandSource,
25
26
  ACPInstructionMapping,
26
27
  ACPNpmLockedSource,
27
28
  ACPNpmSimpleSource,
@@ -5,8 +5,9 @@ import {
5
5
  createImplementationDescriptor,
6
6
  createImplementationInstallCommand,
7
7
  createImplementationManifest,
8
+ getImplementationInstallScript,
8
9
  getImplementationLockfile,
9
- type ACPNpmImplementation,
10
+ type ACPImplementation,
10
11
  } from './implementation';
11
12
 
12
13
  export function createACPBootstrap({
@@ -14,7 +15,7 @@ export function createACPBootstrap({
14
15
  implementation,
15
16
  }: {
16
17
  readonly harnessId: string;
17
- readonly implementation: ACPNpmImplementation;
18
+ readonly implementation: ACPImplementation;
18
19
  }): {
19
20
  readonly bootstrapDir: string;
20
21
  readonly getBootstrap: () => Promise<HarnessV1Bootstrap>;
@@ -33,9 +34,15 @@ export function createACPBootstrap({
33
34
  readBridgeAsset({ name: 'index.mjs' }),
34
35
  readBridgeAsset({ name: 'host-tool-mcp.mjs' }),
35
36
  ]);
37
+ const implementationManifest = createImplementationManifest({
38
+ implementation,
39
+ });
36
40
  const implementationLock = getImplementationLockfile({
37
41
  implementation,
38
42
  });
43
+ const implementationInstallScript = getImplementationInstallScript({
44
+ implementation,
45
+ });
39
46
  cachedBootstrap = {
40
47
  harnessId,
41
48
  bootstrapDir,
@@ -53,14 +60,18 @@ export function createACPBootstrap({
53
60
  path: `${bootstrapDir}/host-tool-mcp.mjs`,
54
61
  content: hostToolMCP,
55
62
  },
56
- {
57
- path: `${bootstrapDir}/implementation/package.json`,
58
- content: createImplementationManifest({ implementation }),
59
- },
60
63
  {
61
64
  path: `${bootstrapDir}/implementation/implementation.json`,
62
65
  content: createImplementationDescriptor({ implementation }),
63
66
  },
67
+ ...(implementationManifest == null
68
+ ? []
69
+ : [
70
+ {
71
+ path: `${bootstrapDir}/implementation/package.json`,
72
+ content: implementationManifest,
73
+ },
74
+ ]),
64
75
  ...(implementationLock == null
65
76
  ? []
66
77
  : [
@@ -69,6 +80,14 @@ export function createACPBootstrap({
69
80
  content: implementationLock,
70
81
  },
71
82
  ]),
83
+ ...(implementationInstallScript == null
84
+ ? []
85
+ : [
86
+ {
87
+ path: `${bootstrapDir}/implementation/install.sh`,
88
+ content: implementationInstallScript,
89
+ },
90
+ ]),
72
91
  ],
73
92
  commands: [
74
93
  {
@@ -47,7 +47,15 @@ export type ACPNpmLockedSource = {
47
47
  readonly pnpmLockYaml: string;
48
48
  };
49
49
 
50
- export type ACPSource = ACPNpmSimpleSource | ACPNpmLockedSource;
50
+ export type ACPInstallCommandSource = {
51
+ readonly type: 'install-command';
52
+ readonly command: string;
53
+ };
54
+
55
+ export type ACPSource =
56
+ | ACPNpmSimpleSource
57
+ | ACPNpmLockedSource
58
+ | ACPInstallCommandSource;
51
59
 
52
60
  export type ACPAuthentication = {
53
61
  readonly methodId: string;
@@ -53,7 +53,8 @@ import {
53
53
  } from './session-lifecycle';
54
54
 
55
55
  type ImplementationDescriptor = {
56
- readonly executable: string;
56
+ readonly executablePath: string;
57
+ readonly privateHome: boolean;
57
58
  readonly args: ReadonlyArray<string>;
58
59
  readonly envKeys: ReadonlyArray<string>;
59
60
  };
@@ -381,10 +382,14 @@ async function ensureSession({
381
382
  instructions: start.instructions,
382
383
  instructionMapping: start.instructionMapping,
383
384
  sessionMeta: bridgeConfiguration.sessionMeta,
384
- environment: createChildEnvironment({ launchEnv }),
385
+ environment: createChildEnvironment({
386
+ launchEnv,
387
+ implementationDir,
388
+ privateHome: implementation.privateHome,
389
+ }),
385
390
  });
386
391
  child = spawn(
387
- `${implementationDir}/node_modules/.bin/${implementation.executable}`,
392
+ `${implementationDir}/${implementation.executablePath}`,
388
393
  [...implementation.args],
389
394
  {
390
395
  cwd: workDir,
@@ -686,8 +691,12 @@ function resolveGatewayValues({
686
691
 
687
692
  function createChildEnvironment({
688
693
  launchEnv,
694
+ implementationDir,
695
+ privateHome,
689
696
  }: {
690
697
  launchEnv: Readonly<Record<string, string>>;
698
+ implementationDir: string;
699
+ privateHome: boolean;
691
700
  }): NodeJS.ProcessEnv {
692
701
  const blocked = new Set([
693
702
  'BRIDGE_CHANNEL_TOKEN',
@@ -698,7 +707,7 @@ function createChildEnvironment({
698
707
  'AI_SDK_ACP_CLIENT_APP_VERSION',
699
708
  ACP_BRIDGE_CONFIGURATION_ENV,
700
709
  ]);
701
- return {
710
+ const environment = {
702
711
  ...Object.fromEntries(
703
712
  Object.entries(processEnv).filter(
704
713
  ([key, value]) => !blocked.has(key) && value != null,
@@ -706,6 +715,20 @@ function createChildEnvironment({
706
715
  ),
707
716
  ...launchEnv,
708
717
  };
718
+ if (!privateHome) return environment;
719
+
720
+ const home = `${implementationDir}/home`;
721
+ const inheritedPath = environment.PATH;
722
+ return {
723
+ ...environment,
724
+ HOME: home,
725
+ PATH: [
726
+ `${home}/.local/bin`,
727
+ ...(inheritedPath == null || inheritedPath.length === 0
728
+ ? []
729
+ : [inheritedPath]),
730
+ ].join(':'),
731
+ };
709
732
  }
710
733
 
711
734
  function isRecord(value: unknown): value is Readonly<Record<string, unknown>> {
@@ -798,7 +821,8 @@ async function readImplementationDescriptor({
798
821
  }).json();
799
822
  if (
800
823
  !isRecord(value) ||
801
- typeof value.executable !== 'string' ||
824
+ typeof value.executablePath !== 'string' ||
825
+ typeof value.privateHome !== 'boolean' ||
802
826
  !Array.isArray(value.args) ||
803
827
  !value.args.every(item => typeof item === 'string') ||
804
828
  !Array.isArray(value.envKeys) ||
@@ -807,7 +831,8 @@ async function readImplementationDescriptor({
807
831
  throw new Error('Invalid ACP implementation descriptor.');
808
832
  }
809
833
  return {
810
- executable: value.executable,
834
+ executablePath: value.executablePath,
835
+ privateHome: value.privateHome,
811
836
  args: value.args as string[],
812
837
  envKeys: value.envKeys as string[],
813
838
  };
@@ -18,7 +18,7 @@ import type {
18
18
  * two shapes are required to agree, and createACPV1Implementation is the only
19
19
  * place that maps between them.
20
20
  */
21
- export type ACPNpmImplementation = {
21
+ export type ACPImplementation = {
22
22
  readonly source: ACPSource;
23
23
  readonly executable: string;
24
24
  readonly args?: ReadonlyArray<string>;
@@ -31,7 +31,7 @@ export function createACPV1Implementation({
31
31
  settings,
32
32
  }: {
33
33
  settings: ACPV1Settings;
34
- }): ACPNpmImplementation {
34
+ }): ACPImplementation {
35
35
  return {
36
36
  source: settings.source,
37
37
  executable: settings.executable,
@@ -50,7 +50,7 @@ const EXECUTABLE_NAME_REGEXP = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
50
50
  const ENVIRONMENT_VARIABLE_NAME_REGEXP = /^[A-Za-z_][A-Za-z0-9_]*$/;
51
51
 
52
52
  export function validateACPV1Implementation(
53
- implementation: ACPNpmImplementation,
53
+ implementation: ACPImplementation,
54
54
  ): void {
55
55
  const { source } = implementation;
56
56
  if (source.type === 'npm-locked') {
@@ -60,12 +60,14 @@ export function validateACPV1Implementation(
60
60
  if (source.pnpmLockYaml.length === 0) {
61
61
  throw new Error('ACP source.pnpmLockYaml must not be empty.');
62
62
  }
63
- } else {
63
+ } else if (source.type === 'npm-simple') {
64
64
  validateNpmSimpleSource({ source });
65
+ } else if (source.command.trim().length === 0) {
66
+ throw new Error('ACP source.command must not be empty.');
65
67
  }
66
68
  if (!EXECUTABLE_NAME_REGEXP.test(implementation.executable)) {
67
69
  throw new Error(
68
- `ACP executable must be a package bin name without a path; received ${JSON.stringify(implementation.executable)}.`,
70
+ `ACP executable must be a bare command name without a path; received ${JSON.stringify(implementation.executable)}.`,
69
71
  );
70
72
  }
71
73
 
@@ -98,9 +100,10 @@ export function validateACPV1Implementation(
98
100
  export function createImplementationManifest({
99
101
  implementation,
100
102
  }: {
101
- implementation: ACPNpmImplementation;
102
- }): string {
103
+ implementation: ACPImplementation;
104
+ }): string | undefined {
103
105
  const { source } = implementation;
106
+ if (source.type === 'install-command') return undefined;
104
107
  if (source.type === 'npm-locked') {
105
108
  return source.packageJson;
106
109
  }
@@ -124,7 +127,7 @@ export function createImplementationManifest({
124
127
  export function getImplementationLockfile({
125
128
  implementation,
126
129
  }: {
127
- implementation: ACPNpmImplementation;
130
+ implementation: ACPImplementation;
128
131
  }): string | undefined {
129
132
  const { source } = implementation;
130
133
  return source.type === 'npm-locked' ? source.pnpmLockYaml : undefined;
@@ -133,12 +136,16 @@ export function getImplementationLockfile({
133
136
  export function createImplementationDescriptor({
134
137
  implementation,
135
138
  }: {
136
- implementation: ACPNpmImplementation;
139
+ implementation: ACPImplementation;
137
140
  }): string {
138
141
  return (
139
142
  JSON.stringify(
140
143
  {
141
- executable: implementation.executable,
144
+ executablePath:
145
+ implementation.source.type === 'install-command'
146
+ ? `home/.local/bin/${implementation.executable}`
147
+ : `node_modules/.bin/${implementation.executable}`,
148
+ privateHome: implementation.source.type === 'install-command',
142
149
  args: implementation.args ?? [],
143
150
  envKeys: getImplementationEnvironmentKeys({ implementation }),
144
151
  },
@@ -158,7 +165,7 @@ export function createImplementationIdentity({
158
165
  }: {
159
166
  harnessId: string;
160
167
  acpVersion: 'v1';
161
- implementation: ACPNpmImplementation;
168
+ implementation: ACPImplementation;
162
169
  clientApp: ACPClientApp;
163
170
  providerAuthentication: ACPProviderAuthenticationCompatibility | undefined;
164
171
  permissionModeMapping?: ACPPermissionModeMapping;
@@ -171,13 +178,18 @@ export function createImplementationIdentity({
171
178
  packageJson: source.packageJson,
172
179
  pnpmLockYaml: source.pnpmLockYaml,
173
180
  }
174
- : {
175
- type: source.type,
176
- packageName: source.packageName,
177
- ...(source.packageVersion == null
178
- ? {}
179
- : { packageVersion: source.packageVersion }),
180
- };
181
+ : source.type === 'npm-simple'
182
+ ? {
183
+ type: source.type,
184
+ packageName: source.packageName,
185
+ ...(source.packageVersion == null
186
+ ? {}
187
+ : { packageVersion: source.packageVersion }),
188
+ }
189
+ : {
190
+ type: source.type,
191
+ command: source.command,
192
+ };
181
193
  const forwardedEnvironment = [
182
194
  ...new Set(implementation.forwardEnv ?? []),
183
195
  ].sort();
@@ -216,8 +228,11 @@ export function createImplementationInstallCommand({
216
228
  }: {
217
229
  implementationDir: string;
218
230
  storeDir: string;
219
- implementation: ACPNpmImplementation;
231
+ implementation: ACPImplementation;
220
232
  }): string {
233
+ if (implementation.source.type === 'install-command') {
234
+ return 'bash implementation/install.sh';
235
+ }
221
236
  return (
222
237
  `pnpm --dir ${implementationDir} install` +
223
238
  (implementation.source.type === 'npm-locked' ? ' --frozen-lockfile' : '') +
@@ -225,11 +240,32 @@ export function createImplementationInstallCommand({
225
240
  );
226
241
  }
227
242
 
243
+ export function getImplementationInstallScript({
244
+ implementation,
245
+ }: {
246
+ implementation: ACPImplementation;
247
+ }): string | undefined {
248
+ if (implementation.source.type !== 'install-command') return undefined;
249
+ return `#!/usr/bin/env bash
250
+ set -euo pipefail
251
+
252
+ ACP_IMPLEMENTATION_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
253
+ ACP_INSTALL_HOME="$ACP_IMPLEMENTATION_DIR/home"
254
+ export HOME="$ACP_INSTALL_HOME"
255
+ export PATH="$ACP_INSTALL_HOME/.local/bin:$PATH"
256
+
257
+ mkdir -p "$ACP_INSTALL_HOME/.local/bin"
258
+ cd "$ACP_IMPLEMENTATION_DIR"
259
+
260
+ ${implementation.source.command}
261
+ `;
262
+ }
263
+
228
264
  export function resolveImplementationEnvironment({
229
265
  implementation,
230
266
  env,
231
267
  }: {
232
- implementation: ACPNpmImplementation;
268
+ implementation: ACPImplementation;
233
269
  env: Readonly<Record<string, string | undefined>>;
234
270
  }): Record<string, string> {
235
271
  const forwardedEnvironment: Record<string, string> = {};
@@ -302,7 +338,7 @@ function validateForwardEnvironment({
302
338
  function getImplementationEnvironmentKeys({
303
339
  implementation,
304
340
  }: {
305
- implementation: ACPNpmImplementation;
341
+ implementation: ACPImplementation;
306
342
  }): string[] {
307
343
  return [
308
344
  ...new Set([
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
+ ACPInstallCommandSource,
5
6
  ACPInstructionMapping,
6
7
  ACPNpmLockedSource,
7
8
  ACPNpmSimpleSource,