@ai-sdk/harness-acp 1.0.2 → 1.0.4
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 +16 -0
- package/README.md +13 -0
- package/dist/bridge/host-tool-mcp.mjs +1 -1
- package/dist/bridge/index.mjs +103 -12
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/index.d.ts +20 -1
- package/dist/index.js +41 -6
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/acp-harness.ts +1 -0
- package/src/index.ts +1 -0
- package/src/v1/acp-v1-bridge-protocol.ts +21 -1
- package/src/v1/acp-v1-harness.ts +37 -1
- package/src/v1/acp-v1-settings.ts +21 -0
- package/src/v1/acp-v1-turn-start-config.ts +4 -0
- package/src/v1/bridge/index.ts +14 -6
- package/src/v1/bridge/instruction-mapping.ts +132 -0
- package/src/v1/index.ts +1 -0
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.4",
|
|
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.
|
|
31
|
-
"@ai-sdk/provider-utils": "5.0.
|
|
30
|
+
"@ai-sdk/harness": "1.0.67",
|
|
31
|
+
"@ai-sdk/provider-utils": "5.0.27"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"zod": "^3.25.76 || ^4.1.8"
|
package/src/acp-harness.ts
CHANGED
|
@@ -37,6 +37,7 @@ export type ACPHarnessSettings<TBuiltinTools extends ToolSet = {}> = {
|
|
|
37
37
|
readonly authentication?: ACPV1Settings['authentication'];
|
|
38
38
|
readonly providerAuthentication?: ACPV1Settings['providerAuthentication'];
|
|
39
39
|
readonly modelId?: ACPV1Settings['modelId'];
|
|
40
|
+
readonly instructionMapping?: ACPV1Settings['instructionMapping'];
|
|
40
41
|
readonly permissionModeMapping?: ACPV1Settings['permissionModeMapping'];
|
|
41
42
|
readonly session?: ACPV1Settings['session'];
|
|
42
43
|
readonly mintBridgeToken?: ACPV1Settings['mintBridgeToken'];
|
package/src/index.ts
CHANGED
|
@@ -12,7 +12,10 @@ import {
|
|
|
12
12
|
} from '@ai-sdk/harness';
|
|
13
13
|
import { z } from 'zod/v4';
|
|
14
14
|
import type { ACPToolCall } from '../acp-tool-call';
|
|
15
|
-
import type {
|
|
15
|
+
import type {
|
|
16
|
+
ACPInstructionMapping,
|
|
17
|
+
ACPPermissionModeMapping,
|
|
18
|
+
} from './acp-v1-settings';
|
|
16
19
|
import { acpTextContentBlockSchema } from './acp-v1-prompt';
|
|
17
20
|
|
|
18
21
|
export type ACPBuiltinToolMapping = {
|
|
@@ -82,6 +85,21 @@ const permissionModeMappingSchema: z.ZodType<ACPPermissionModeMapping> =
|
|
|
82
85
|
'allow-all': permissionModeTargetSchema.nullable(),
|
|
83
86
|
});
|
|
84
87
|
|
|
88
|
+
const instructionMappingPathSchema = z.array(z.string().min(1)).min(1);
|
|
89
|
+
|
|
90
|
+
const instructionMappingSchema: z.ZodType<ACPInstructionMapping> =
|
|
91
|
+
z.discriminatedUnion('type', [
|
|
92
|
+
z.object({
|
|
93
|
+
type: z.literal('session-meta'),
|
|
94
|
+
path: instructionMappingPathSchema,
|
|
95
|
+
}),
|
|
96
|
+
z.object({
|
|
97
|
+
type: z.literal('launch-env-json'),
|
|
98
|
+
variable: z.string().min(1),
|
|
99
|
+
path: instructionMappingPathSchema,
|
|
100
|
+
}),
|
|
101
|
+
]);
|
|
102
|
+
|
|
85
103
|
export type ACPTurnStartConfig = {
|
|
86
104
|
readonly version: 1;
|
|
87
105
|
readonly configurationFingerprint: string;
|
|
@@ -146,6 +164,8 @@ export type OutboundMessage = z.infer<typeof outboundMessageSchema>;
|
|
|
146
164
|
|
|
147
165
|
export const startMessageSchema = harnessV1BridgeStartBaseSchema.extend({
|
|
148
166
|
prompt: z.array(acpTextContentBlockSchema),
|
|
167
|
+
instructions: z.string().optional(),
|
|
168
|
+
instructionMapping: instructionMappingSchema.optional(),
|
|
149
169
|
mcpServers: z.record(z.string(), z.unknown()).optional(),
|
|
150
170
|
tools: z.array(acpSerializableToolSpecSchema).optional(),
|
|
151
171
|
builtinTools: z.array(builtinToolSchema).readonly().default([]),
|
package/src/v1/acp-v1-harness.ts
CHANGED
|
@@ -77,6 +77,7 @@ import {
|
|
|
77
77
|
type ACPSkillCatalogEntry,
|
|
78
78
|
} from './acp-v1-prompt';
|
|
79
79
|
import type {
|
|
80
|
+
ACPInstructionMapping,
|
|
80
81
|
ACPPermissionModeMapping,
|
|
81
82
|
ACPPermissionModeTarget,
|
|
82
83
|
ACPSerializableValue,
|
|
@@ -376,6 +377,7 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
376
377
|
proc: undefined,
|
|
377
378
|
modelId: settings.modelId,
|
|
378
379
|
sessionMeta: settings.session?.meta,
|
|
380
|
+
instructionMapping: settings.instructionMapping,
|
|
379
381
|
debug: startOptions.observability?.debug,
|
|
380
382
|
implementationIdentity,
|
|
381
383
|
authenticationProfile,
|
|
@@ -433,6 +435,7 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
433
435
|
turnStartConfig,
|
|
434
436
|
authenticationProfile,
|
|
435
437
|
sessionMeta: settings.session?.meta,
|
|
438
|
+
instructionMapping: settings.instructionMapping,
|
|
436
439
|
builtinTools: builtinToolCatalog,
|
|
437
440
|
permissionModeMapping,
|
|
438
441
|
mcpServers: settings.mcpServers,
|
|
@@ -463,6 +466,7 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
463
466
|
permissionMode,
|
|
464
467
|
authenticationProfile,
|
|
465
468
|
sessionMeta: settings.session?.meta,
|
|
469
|
+
instructionMapping: settings.instructionMapping,
|
|
466
470
|
builtinTools: builtinToolCatalog,
|
|
467
471
|
permissionModeMapping,
|
|
468
472
|
mcpServers: settings.mcpServers,
|
|
@@ -589,6 +593,9 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
589
593
|
builtinTools: builtinToolCatalog,
|
|
590
594
|
permissionMode,
|
|
591
595
|
permissionModeMapping,
|
|
596
|
+
...(settings.instructionMapping == null
|
|
597
|
+
? {}
|
|
598
|
+
: { instructionMapping: settings.instructionMapping }),
|
|
592
599
|
...(settings.mcpServers == null
|
|
593
600
|
? {}
|
|
594
601
|
: { mcpServers: settings.mcpServers }),
|
|
@@ -622,6 +629,7 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
622
629
|
proc,
|
|
623
630
|
modelId: settings.modelId,
|
|
624
631
|
sessionMeta: settings.session?.meta,
|
|
632
|
+
instructionMapping: settings.instructionMapping,
|
|
625
633
|
debug: startOptions.observability?.debug,
|
|
626
634
|
implementationIdentity,
|
|
627
635
|
authenticationProfile,
|
|
@@ -825,6 +833,7 @@ function createSession({
|
|
|
825
833
|
proc,
|
|
826
834
|
modelId,
|
|
827
835
|
sessionMeta,
|
|
836
|
+
instructionMapping,
|
|
828
837
|
debug,
|
|
829
838
|
implementationIdentity,
|
|
830
839
|
authenticationProfile,
|
|
@@ -855,6 +864,7 @@ function createSession({
|
|
|
855
864
|
proc: Experimental_SandboxProcess | undefined;
|
|
856
865
|
modelId: string | undefined;
|
|
857
866
|
sessionMeta: Readonly<Record<string, ACPSerializableValue>> | undefined;
|
|
867
|
+
instructionMapping: ACPInstructionMapping | undefined;
|
|
858
868
|
debug: HarnessV1DebugConfig | undefined;
|
|
859
869
|
implementationIdentity: string;
|
|
860
870
|
authenticationProfile: ACPAuthenticationProfileIdentity;
|
|
@@ -1181,6 +1191,7 @@ function createSession({
|
|
|
1181
1191
|
debug,
|
|
1182
1192
|
authenticationProfile,
|
|
1183
1193
|
sessionMeta,
|
|
1194
|
+
instructionMapping,
|
|
1184
1195
|
});
|
|
1185
1196
|
const control = wireTurn({
|
|
1186
1197
|
emit: options.emit,
|
|
@@ -1198,9 +1209,20 @@ function createSession({
|
|
|
1198
1209
|
? prompt
|
|
1199
1210
|
: prependACPInitialGuidance({
|
|
1200
1211
|
prompt,
|
|
1201
|
-
instructions:
|
|
1212
|
+
instructions:
|
|
1213
|
+
instructionMapping == null
|
|
1214
|
+
? options.instructions
|
|
1215
|
+
: undefined,
|
|
1202
1216
|
skills: skillCatalog,
|
|
1203
1217
|
}),
|
|
1218
|
+
...(instructionMapping == null
|
|
1219
|
+
? {}
|
|
1220
|
+
: {
|
|
1221
|
+
instructionMapping,
|
|
1222
|
+
...(options.instructions == null
|
|
1223
|
+
? {}
|
|
1224
|
+
: { instructions: options.instructions }),
|
|
1225
|
+
}),
|
|
1204
1226
|
debug,
|
|
1205
1227
|
builtinTools,
|
|
1206
1228
|
permissionMode,
|
|
@@ -1243,6 +1265,14 @@ function createSession({
|
|
|
1243
1265
|
builtinTools: turnStartConfig.builtinTools,
|
|
1244
1266
|
permissionMode: turnStartConfig.permissionMode,
|
|
1245
1267
|
permissionModeMapping: turnStartConfig.permissionModeMapping,
|
|
1268
|
+
...(instructionMapping == null
|
|
1269
|
+
? {}
|
|
1270
|
+
: {
|
|
1271
|
+
instructionMapping,
|
|
1272
|
+
...(options.instructions == null
|
|
1273
|
+
? {}
|
|
1274
|
+
: { instructions: options.instructions }),
|
|
1275
|
+
}),
|
|
1246
1276
|
...(mcpServers == null ? {} : { mcpServers }),
|
|
1247
1277
|
tools: turnStartConfig.tools,
|
|
1248
1278
|
turnStartConfig,
|
|
@@ -1406,6 +1436,7 @@ function validateACPTurnStartConfig({
|
|
|
1406
1436
|
turnStartConfig,
|
|
1407
1437
|
authenticationProfile,
|
|
1408
1438
|
sessionMeta,
|
|
1439
|
+
instructionMapping,
|
|
1409
1440
|
builtinTools,
|
|
1410
1441
|
permissionModeMapping,
|
|
1411
1442
|
mcpServers,
|
|
@@ -1413,6 +1444,7 @@ function validateACPTurnStartConfig({
|
|
|
1413
1444
|
turnStartConfig: ACPTurnStartConfig;
|
|
1414
1445
|
authenticationProfile: ACPAuthenticationProfileIdentity;
|
|
1415
1446
|
sessionMeta: Readonly<Record<string, ACPSerializableValue>> | undefined;
|
|
1447
|
+
instructionMapping: ACPInstructionMapping | undefined;
|
|
1416
1448
|
builtinTools: ReadonlyArray<ACPBuiltinToolMapping>;
|
|
1417
1449
|
permissionModeMapping: ACPPermissionModeMapping | undefined;
|
|
1418
1450
|
mcpServers: Record<string, unknown> | undefined;
|
|
@@ -1427,6 +1459,7 @@ function validateACPTurnStartConfig({
|
|
|
1427
1459
|
debug: turnStartConfig.debug,
|
|
1428
1460
|
authenticationProfile,
|
|
1429
1461
|
sessionMeta,
|
|
1462
|
+
instructionMapping,
|
|
1430
1463
|
});
|
|
1431
1464
|
if (
|
|
1432
1465
|
current.configurationFingerprint !==
|
|
@@ -1444,6 +1477,7 @@ function validateACPColdSessionConfiguration({
|
|
|
1444
1477
|
permissionMode,
|
|
1445
1478
|
authenticationProfile,
|
|
1446
1479
|
sessionMeta,
|
|
1480
|
+
instructionMapping,
|
|
1447
1481
|
builtinTools,
|
|
1448
1482
|
permissionModeMapping,
|
|
1449
1483
|
mcpServers,
|
|
@@ -1454,6 +1488,7 @@ function validateACPColdSessionConfiguration({
|
|
|
1454
1488
|
permissionMode: NonNullable<StartMessage['permissionMode']>;
|
|
1455
1489
|
authenticationProfile: ACPAuthenticationProfileIdentity;
|
|
1456
1490
|
sessionMeta: Readonly<Record<string, ACPSerializableValue>> | undefined;
|
|
1491
|
+
instructionMapping: ACPInstructionMapping | undefined;
|
|
1457
1492
|
builtinTools: ReadonlyArray<ACPBuiltinToolMapping>;
|
|
1458
1493
|
permissionModeMapping: ACPPermissionModeMapping | undefined;
|
|
1459
1494
|
mcpServers: Record<string, unknown> | undefined;
|
|
@@ -1469,6 +1504,7 @@ function validateACPColdSessionConfiguration({
|
|
|
1469
1504
|
debug,
|
|
1470
1505
|
authenticationProfile,
|
|
1471
1506
|
sessionMeta,
|
|
1507
|
+
instructionMapping,
|
|
1472
1508
|
});
|
|
1473
1509
|
if (
|
|
1474
1510
|
current.configurationFingerprint !== coldSession.configurationFingerprint ||
|
|
@@ -75,6 +75,22 @@ export type ACPPermissionModeMapping = Readonly<
|
|
|
75
75
|
Record<HarnessV1PermissionMode, ACPPermissionModeTarget | null>
|
|
76
76
|
>;
|
|
77
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Describes where an ACP implementation accepts native session instructions.
|
|
80
|
+
* Session metadata paths are relative to `_meta`; launch environment paths are
|
|
81
|
+
* relative to the parsed JSON object stored in `variable`.
|
|
82
|
+
*/
|
|
83
|
+
export type ACPInstructionMapping =
|
|
84
|
+
| {
|
|
85
|
+
readonly type: 'session-meta';
|
|
86
|
+
readonly path: ReadonlyArray<string>;
|
|
87
|
+
}
|
|
88
|
+
| {
|
|
89
|
+
readonly type: 'launch-env-json';
|
|
90
|
+
readonly variable: string;
|
|
91
|
+
readonly path: ReadonlyArray<string>;
|
|
92
|
+
};
|
|
93
|
+
|
|
78
94
|
export type ACPV1Settings = {
|
|
79
95
|
readonly version?: 'v1';
|
|
80
96
|
readonly harnessId: string;
|
|
@@ -93,6 +109,11 @@ export type ACPV1Settings = {
|
|
|
93
109
|
readonly authentication?: ACPAuthentication;
|
|
94
110
|
readonly providerAuthentication?: ACPProviderAuthentication;
|
|
95
111
|
readonly modelId?: string;
|
|
112
|
+
/**
|
|
113
|
+
* Routes HarnessAgent instructions to a runtime-native system or developer
|
|
114
|
+
* prompt. When omitted, instructions are prepended to the first user prompt.
|
|
115
|
+
*/
|
|
116
|
+
readonly instructionMapping?: ACPInstructionMapping;
|
|
96
117
|
readonly permissionModeMapping?: ACPPermissionModeMapping;
|
|
97
118
|
readonly session?: {
|
|
98
119
|
readonly meta?: Readonly<Record<string, ACPSerializableValue>>;
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from './acp-v1-bridge-protocol';
|
|
11
11
|
import type { ACPTextContentBlock } from './acp-v1-prompt';
|
|
12
12
|
import type {
|
|
13
|
+
ACPInstructionMapping,
|
|
13
14
|
ACPPermissionModeMapping,
|
|
14
15
|
ACPSerializableValue,
|
|
15
16
|
} from './acp-v1-settings';
|
|
@@ -24,6 +25,7 @@ export function createACPTurnStartConfig({
|
|
|
24
25
|
debug,
|
|
25
26
|
authenticationProfile,
|
|
26
27
|
sessionMeta,
|
|
28
|
+
instructionMapping,
|
|
27
29
|
}: {
|
|
28
30
|
prompt: ReadonlyArray<ACPTextContentBlock>;
|
|
29
31
|
tools: ReadonlyArray<HarnessV1ToolSpec>;
|
|
@@ -34,6 +36,7 @@ export function createACPTurnStartConfig({
|
|
|
34
36
|
debug: HarnessV1DebugConfig | undefined;
|
|
35
37
|
authenticationProfile: ACPAuthenticationProfileIdentity;
|
|
36
38
|
sessionMeta: Readonly<Record<string, ACPSerializableValue>> | undefined;
|
|
39
|
+
instructionMapping: ACPInstructionMapping | undefined;
|
|
37
40
|
}): ACPTurnStartConfig {
|
|
38
41
|
return {
|
|
39
42
|
version: 1,
|
|
@@ -42,6 +45,7 @@ export function createACPTurnStartConfig({
|
|
|
42
45
|
stableStringify({
|
|
43
46
|
authenticationProfile,
|
|
44
47
|
sessionMeta: sessionMeta ?? null,
|
|
48
|
+
...(instructionMapping == null ? {} : { instructionMapping }),
|
|
45
49
|
builtinTools,
|
|
46
50
|
permissionModeMapping: permissionModeMapping ?? null,
|
|
47
51
|
mcpServers: mcpServers ?? null,
|
package/src/v1/bridge/index.ts
CHANGED
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
} from './acp-diagnostics';
|
|
33
33
|
import { monitorACPAgentStderr } from './agent-stderr-monitor';
|
|
34
34
|
import { createEmitStreamEvent } from './create-emit-stream-event';
|
|
35
|
+
import { resolveACPInstructionConfiguration } from './instruction-mapping';
|
|
35
36
|
import {
|
|
36
37
|
startHostToolRelay,
|
|
37
38
|
type HostToolRelay,
|
|
@@ -326,6 +327,7 @@ async function ensureSession({
|
|
|
326
327
|
authentication: bridgeConfiguration.authentication,
|
|
327
328
|
providerAuthentication: bridgeConfiguration.providerAuthentication,
|
|
328
329
|
sessionMeta: bridgeConfiguration.sessionMeta,
|
|
330
|
+
instructionMapping: start.instructionMapping,
|
|
329
331
|
permissionMode: start.permissionMode,
|
|
330
332
|
permissionModeMapping: start.permissionModeMapping,
|
|
331
333
|
mcpServers: start.mcpServers,
|
|
@@ -367,12 +369,18 @@ async function ensureSession({
|
|
|
367
369
|
providerAuthentication: bridgeConfiguration.providerAuthentication,
|
|
368
370
|
gateway,
|
|
369
371
|
});
|
|
372
|
+
const instructionConfiguration = await resolveACPInstructionConfiguration({
|
|
373
|
+
instructions: start.instructions,
|
|
374
|
+
instructionMapping: start.instructionMapping,
|
|
375
|
+
sessionMeta: bridgeConfiguration.sessionMeta,
|
|
376
|
+
environment: createChildEnvironment({ launchEnv }),
|
|
377
|
+
});
|
|
370
378
|
child = spawn(
|
|
371
379
|
`${implementationDir}/node_modules/.bin/${implementation.executable}`,
|
|
372
380
|
[...implementation.args],
|
|
373
381
|
{
|
|
374
382
|
cwd: workDir,
|
|
375
|
-
env:
|
|
383
|
+
env: instructionConfiguration.environment,
|
|
376
384
|
shell: false,
|
|
377
385
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
378
386
|
},
|
|
@@ -490,9 +498,9 @@ async function ensureSession({
|
|
|
490
498
|
sessionId: recoveredSessionId,
|
|
491
499
|
cwd: workDir,
|
|
492
500
|
mcpServers,
|
|
493
|
-
...(
|
|
501
|
+
...(instructionConfiguration.sessionMeta == null
|
|
494
502
|
? {}
|
|
495
|
-
: { _meta:
|
|
503
|
+
: { _meta: instructionConfiguration.sessionMeta }),
|
|
496
504
|
});
|
|
497
505
|
createdSession = createACPRecoveredSession({
|
|
498
506
|
agent: connection.agent,
|
|
@@ -509,7 +517,7 @@ async function ensureSession({
|
|
|
509
517
|
sessionId: recoveredSessionId,
|
|
510
518
|
cwd: workDir,
|
|
511
519
|
mcpServers,
|
|
512
|
-
meta:
|
|
520
|
+
meta: instructionConfiguration.sessionMeta,
|
|
513
521
|
harnessId: bridgeType,
|
|
514
522
|
setHistoricalUpdatesSuppressed: ({ suppressed }) => {
|
|
515
523
|
historicalUpdatesSuppressed = suppressed;
|
|
@@ -530,9 +538,9 @@ async function ensureSession({
|
|
|
530
538
|
.buildSession({
|
|
531
539
|
cwd: workDir,
|
|
532
540
|
mcpServers,
|
|
533
|
-
...(
|
|
541
|
+
...(instructionConfiguration.sessionMeta == null
|
|
534
542
|
? {}
|
|
535
|
-
: { _meta:
|
|
543
|
+
: { _meta: instructionConfiguration.sessionMeta }),
|
|
536
544
|
})
|
|
537
545
|
.start();
|
|
538
546
|
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { z } from 'zod/v4';
|
|
2
|
+
import type {
|
|
3
|
+
ACPInstructionMapping,
|
|
4
|
+
ACPSerializableValue,
|
|
5
|
+
} from '../acp-v1-settings';
|
|
6
|
+
|
|
7
|
+
const UNSAFE_PATH_SEGMENTS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
8
|
+
|
|
9
|
+
const serializableRecordSchema: z.ZodType<
|
|
10
|
+
Readonly<Record<string, ACPSerializableValue>>
|
|
11
|
+
> = z.record(z.string(), z.json());
|
|
12
|
+
|
|
13
|
+
export async function resolveACPInstructionConfiguration({
|
|
14
|
+
instructions,
|
|
15
|
+
instructionMapping,
|
|
16
|
+
sessionMeta,
|
|
17
|
+
environment,
|
|
18
|
+
}: {
|
|
19
|
+
instructions: string | undefined;
|
|
20
|
+
instructionMapping: ACPInstructionMapping | undefined;
|
|
21
|
+
sessionMeta: Readonly<Record<string, ACPSerializableValue>> | undefined;
|
|
22
|
+
environment: Readonly<Record<string, string | undefined>>;
|
|
23
|
+
}): Promise<{
|
|
24
|
+
sessionMeta: Readonly<Record<string, ACPSerializableValue>> | undefined;
|
|
25
|
+
environment: Record<string, string | undefined>;
|
|
26
|
+
}> {
|
|
27
|
+
const resolvedEnvironment = { ...environment };
|
|
28
|
+
if (
|
|
29
|
+
instructionMapping == null ||
|
|
30
|
+
instructions == null ||
|
|
31
|
+
instructions.length === 0
|
|
32
|
+
) {
|
|
33
|
+
return { sessionMeta, environment: resolvedEnvironment };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
assertSafePath({ path: instructionMapping.path });
|
|
37
|
+
|
|
38
|
+
if (instructionMapping.type === 'session-meta') {
|
|
39
|
+
return {
|
|
40
|
+
sessionMeta: setStringAtPath({
|
|
41
|
+
record: sessionMeta ?? {},
|
|
42
|
+
path: instructionMapping.path,
|
|
43
|
+
value: instructions,
|
|
44
|
+
}),
|
|
45
|
+
environment: resolvedEnvironment,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const serialized = resolvedEnvironment[instructionMapping.variable];
|
|
50
|
+
let configuration: Readonly<Record<string, ACPSerializableValue>> = {};
|
|
51
|
+
if (serialized != null && serialized.length > 0) {
|
|
52
|
+
configuration = parseSerializableRecord({
|
|
53
|
+
serialized,
|
|
54
|
+
variable: instructionMapping.variable,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
resolvedEnvironment[instructionMapping.variable] = JSON.stringify(
|
|
59
|
+
setStringAtPath({
|
|
60
|
+
record: configuration,
|
|
61
|
+
path: instructionMapping.path,
|
|
62
|
+
value: instructions,
|
|
63
|
+
}),
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
return { sessionMeta, environment: resolvedEnvironment };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/*
|
|
70
|
+
* The bridge runs in an isolated runtime whose dependencies are limited to the
|
|
71
|
+
* ones declared in this directory's package.json, so AI SDK JSON helpers are
|
|
72
|
+
* unavailable here. Schema validation plus a guarded `JSON.parse` provides the
|
|
73
|
+
* same guarantee: nothing but a validated serializable record escapes.
|
|
74
|
+
*/
|
|
75
|
+
function parseSerializableRecord({
|
|
76
|
+
serialized,
|
|
77
|
+
variable,
|
|
78
|
+
}: {
|
|
79
|
+
serialized: string;
|
|
80
|
+
variable: string;
|
|
81
|
+
}): Readonly<Record<string, ACPSerializableValue>> {
|
|
82
|
+
try {
|
|
83
|
+
const result = serializableRecordSchema.safeParse(JSON.parse(serialized));
|
|
84
|
+
if (result.success) return result.data;
|
|
85
|
+
} catch {}
|
|
86
|
+
throw new Error(
|
|
87
|
+
`ACP instruction mapping environment variable ${JSON.stringify(variable)} must contain a JSON object.`,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function setStringAtPath({
|
|
92
|
+
record,
|
|
93
|
+
path,
|
|
94
|
+
value,
|
|
95
|
+
}: {
|
|
96
|
+
record: Readonly<Record<string, ACPSerializableValue>>;
|
|
97
|
+
path: ReadonlyArray<string>;
|
|
98
|
+
value: string;
|
|
99
|
+
}): Record<string, ACPSerializableValue> {
|
|
100
|
+
const [key, ...remainingPath] = path;
|
|
101
|
+
if (key == null) return { ...record };
|
|
102
|
+
return {
|
|
103
|
+
...record,
|
|
104
|
+
[key]:
|
|
105
|
+
remainingPath.length === 0
|
|
106
|
+
? value
|
|
107
|
+
: setStringAtPath({
|
|
108
|
+
record: isRecord(record[key]) ? record[key] : {},
|
|
109
|
+
path: remainingPath,
|
|
110
|
+
value,
|
|
111
|
+
}),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function assertSafePath({ path }: { path: ReadonlyArray<string> }): void {
|
|
116
|
+
if (
|
|
117
|
+
path.length === 0 ||
|
|
118
|
+
path.some(
|
|
119
|
+
segment => segment.length === 0 || UNSAFE_PATH_SEGMENTS.has(segment),
|
|
120
|
+
)
|
|
121
|
+
) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
'ACP instruction mapping path must contain only safe, non-empty property names.',
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function isRecord(
|
|
129
|
+
value: ACPSerializableValue | undefined,
|
|
130
|
+
): value is Readonly<Record<string, ACPSerializableValue>> {
|
|
131
|
+
return value != null && typeof value === 'object' && !Array.isArray(value);
|
|
132
|
+
}
|