@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/harness-acp",
3
- "version": "1.0.28",
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.90",
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 { getAiGatewayAuthFromEnv } from '@ai-sdk/harness/utils';
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 ACPAuthOptions = ACPProviderAuthenticationMode;
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?: ACPAuthOptions;
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 ?? 'auto';
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?: ACPProviderAuthenticationMode;
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: env.AI_GATEWAY_BASE_URL ?? DEFAULT_AI_GATEWAY_BASE_URL,
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,
@@ -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 { ACPAuthOptions, ACPClientApp } from './acp-auth';
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,
@@ -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
- skillCatalog = (
380
- await materializeACPSkills({
381
- sandbox: toolSafeSandboxSession,
382
- sandboxHomeDir,
383
- sessionWorkDir: workDir,
384
- harnessId: settings.harnessId,
385
- sessionId: startOptions.sessionId,
386
- skills,
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 !isResume || lifecycleState?.skillsMaterialized !== true;
70
+ return (
71
+ !isResume ||
72
+ lifecycleState?.skillsMaterialized !== true ||
73
+ lifecycleState.skillsDirectory !== skillsDirectory
74
+ );
68
75
  }
69
76
 
70
77
  export function validateACPLifecycleCompatibility({
@@ -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 && skills.length === 0) return [...prompt];
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
- if (hasInstructions) {
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 ACPProviderAuthenticationMode = 'auto' | 'direct' | 'ai-gateway';
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?: ACPProviderAuthenticationMode;
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.
@@ -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
- sandboxHomeDir,
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
- sandboxHomeDir: string;
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
- return {
111
- rootDir,
112
- catalog: skills.map(skill => ({
113
- name: skill.name,
114
- description: skill.description,
115
- path: path.posix.join(rootDir, skill.name, 'SKILL.md'),
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({
@@ -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,