@ai-sdk/harness-acp 1.0.10 → 1.0.12

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.10",
3
+ "version": "1.0.12",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -27,7 +27,7 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "ws": "^8.21.0",
30
- "@ai-sdk/harness": "1.0.72",
30
+ "@ai-sdk/harness": "1.0.74",
31
31
  "@ai-sdk/provider-utils": "5.0.27"
32
32
  },
33
33
  "peerDependencies": {
@@ -40,6 +40,7 @@ export type ACPHarnessSettings<TBuiltinTools extends ToolSet = {}> = {
40
40
  readonly providerAuthentication?: ACPV1Settings['providerAuthentication'];
41
41
  readonly modelId?: ACPV1Settings['modelId'];
42
42
  readonly instructionMapping?: ACPV1Settings['instructionMapping'];
43
+ readonly outputSchemaMapping?: ACPV1Settings['outputSchemaMapping'];
43
44
  readonly permissionModeMapping?: ACPV1Settings['permissionModeMapping'];
44
45
  readonly session?: ACPV1Settings['session'];
45
46
  readonly mintBridgeToken?: ACPV1Settings['mintBridgeToken'];
package/src/index.ts CHANGED
@@ -25,6 +25,7 @@ export type {
25
25
  ACPInstructionMapping,
26
26
  ACPNpmLockedSource,
27
27
  ACPNpmSimpleSource,
28
+ ACPOutputSchemaMapping,
28
29
  ACPPermissionModeMapping,
29
30
  ACPPermissionModeTarget,
30
31
  ACPProfileValue,
@@ -100,6 +100,17 @@ const instructionMappingSchema: z.ZodType<ACPInstructionMapping> =
100
100
  }),
101
101
  ]);
102
102
 
103
+ type ACPSerializableOutputSchemaMapping = {
104
+ readonly type: 'session-prompt-meta';
105
+ readonly path: string[];
106
+ };
107
+
108
+ const outputSchemaMappingSchema: z.ZodType<ACPSerializableOutputSchemaMapping> =
109
+ z.object({
110
+ type: z.literal('session-prompt-meta'),
111
+ path: z.array(z.string().min(1)).min(1),
112
+ });
113
+
103
114
  export type ACPTurnStartConfig = {
104
115
  readonly version: 1;
105
116
  readonly configurationFingerprint: string;
@@ -109,6 +120,10 @@ export type ACPTurnStartConfig = {
109
120
  readonly permissionMode: HarnessV1PermissionMode;
110
121
  readonly permissionModeMapping?: ACPPermissionModeMapping;
111
122
  readonly debug?: HarnessV1DebugConfig;
123
+ readonly responseFormat?: z.infer<
124
+ typeof harnessV1BridgeStartBaseSchema
125
+ >['responseFormat'];
126
+ readonly outputSchemaMapping?: ACPSerializableOutputSchemaMapping;
112
127
  };
113
128
 
114
129
  export const acpTurnStartConfigSchema = z.object({
@@ -120,6 +135,9 @@ export const acpTurnStartConfigSchema = z.object({
120
135
  permissionMode: harnessV1BridgeStartBaseSchema.shape.permissionMode.unwrap(),
121
136
  permissionModeMapping: permissionModeMappingSchema.optional(),
122
137
  debug: harnessV1BridgeStartBaseSchema.shape.debug.optional(),
138
+ responseFormat:
139
+ harnessV1BridgeStartBaseSchema.shape.responseFormat.optional(),
140
+ outputSchemaMapping: outputSchemaMappingSchema.optional(),
123
141
  }) satisfies z.ZodType<ACPTurnStartConfig>;
124
142
 
125
143
  export type ACPColdSessionState = Omit<
@@ -166,6 +184,7 @@ export const startMessageSchema = harnessV1BridgeStartBaseSchema.extend({
166
184
  prompt: z.array(acpTextContentBlockSchema),
167
185
  instructions: z.string().optional(),
168
186
  instructionMapping: instructionMappingSchema.optional(),
187
+ outputSchemaMapping: outputSchemaMappingSchema.optional(),
169
188
  mcpServers: z.record(z.string(), z.unknown()).optional(),
170
189
  tools: z.array(acpSerializableToolSpecSchema).optional(),
171
190
  builtinTools: z.array(builtinToolSchema).readonly().default([]),
@@ -76,6 +76,7 @@ import {
76
76
  } from './acp-v1-prompt';
77
77
  import type {
78
78
  ACPInstructionMapping,
79
+ ACPOutputSchemaMapping,
79
80
  ACPPermissionModeMapping,
80
81
  ACPPermissionModeTarget,
81
82
  ACPSerializableValue,
@@ -388,6 +389,7 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
388
389
  modelId: settings.modelId,
389
390
  sessionMeta: settings.session?.meta,
390
391
  instructionMapping: settings.instructionMapping,
392
+ outputSchemaMapping: settings.outputSchemaMapping,
391
393
  debug: startOptions.observability?.debug,
392
394
  implementationIdentity,
393
395
  authenticationProfile,
@@ -446,6 +448,7 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
446
448
  authenticationProfile,
447
449
  sessionMeta: settings.session?.meta,
448
450
  instructionMapping: settings.instructionMapping,
451
+ outputSchemaMapping: settings.outputSchemaMapping,
449
452
  builtinTools: builtinToolCatalog,
450
453
  permissionModeMapping,
451
454
  mcpServers: settings.mcpServers,
@@ -477,6 +480,7 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
477
480
  authenticationProfile,
478
481
  sessionMeta: settings.session?.meta,
479
482
  instructionMapping: settings.instructionMapping,
483
+ outputSchemaMapping: settings.outputSchemaMapping,
480
484
  builtinTools: builtinToolCatalog,
481
485
  permissionModeMapping,
482
486
  mcpServers: settings.mcpServers,
@@ -638,6 +642,7 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
638
642
  modelId: settings.modelId,
639
643
  sessionMeta: settings.session?.meta,
640
644
  instructionMapping: settings.instructionMapping,
645
+ outputSchemaMapping: settings.outputSchemaMapping,
641
646
  debug: startOptions.observability?.debug,
642
647
  implementationIdentity,
643
648
  authenticationProfile,
@@ -879,6 +884,7 @@ function createSession({
879
884
  modelId,
880
885
  sessionMeta,
881
886
  instructionMapping,
887
+ outputSchemaMapping,
882
888
  debug,
883
889
  implementationIdentity,
884
890
  authenticationProfile,
@@ -910,6 +916,7 @@ function createSession({
910
916
  modelId: string | undefined;
911
917
  sessionMeta: Readonly<Record<string, ACPSerializableValue>> | undefined;
912
918
  instructionMapping: ACPInstructionMapping | undefined;
919
+ outputSchemaMapping: ACPOutputSchemaMapping | undefined;
913
920
  debug: HarnessV1DebugConfig | undefined;
914
921
  implementationIdentity: string;
915
922
  authenticationProfile: ACPAuthenticationProfileIdentity;
@@ -1211,6 +1218,20 @@ function createSession({
1211
1218
  isResume,
1212
1219
  ...(modelId == null ? {} : { modelId }),
1213
1220
  doPromptTurn: async options => {
1221
+ if (options.responseFormat?.type === 'json') {
1222
+ if (options.responseFormat.schema == null) {
1223
+ throw unsupported({
1224
+ harnessId,
1225
+ message: `${harnessId} requires a JSON schema for structured output.`,
1226
+ });
1227
+ }
1228
+ if (outputSchemaMapping == null) {
1229
+ throw unsupported({
1230
+ harnessId,
1231
+ message: `${harnessId} does not support structured output through ACP.`,
1232
+ });
1233
+ }
1234
+ }
1214
1235
  if (replayOnly) {
1215
1236
  throw new Error(
1216
1237
  `${harnessId} recovered this turn through disk replay only and has no restored ACP process for a subsequent prompt.`,
@@ -1237,6 +1258,8 @@ function createSession({
1237
1258
  authenticationProfile,
1238
1259
  sessionMeta,
1239
1260
  instructionMapping,
1261
+ responseFormat: options.responseFormat,
1262
+ outputSchemaMapping,
1240
1263
  });
1241
1264
  const control = wireTurn({
1242
1265
  emit: options.emit,
@@ -1272,6 +1295,12 @@ function createSession({
1272
1295
  builtinTools,
1273
1296
  permissionMode,
1274
1297
  permissionModeMapping,
1298
+ ...(options.responseFormat == null
1299
+ ? {}
1300
+ : { responseFormat: options.responseFormat }),
1301
+ ...(turnStartConfig.outputSchemaMapping == null
1302
+ ? {}
1303
+ : { outputSchemaMapping: turnStartConfig.outputSchemaMapping }),
1275
1304
  ...(mcpServers == null ? {} : { mcpServers }),
1276
1305
  tools: options.tools == null ? undefined : turnStartConfig.tools,
1277
1306
  turnStartConfig,
@@ -1283,6 +1312,20 @@ function createSession({
1283
1312
  return control;
1284
1313
  },
1285
1314
  doContinueTurn: async options => {
1315
+ if (options.responseFormat?.type === 'json') {
1316
+ if (options.responseFormat.schema == null) {
1317
+ throw unsupported({
1318
+ harnessId,
1319
+ message: `${harnessId} requires a JSON schema for structured output.`,
1320
+ });
1321
+ }
1322
+ if (outputSchemaMapping == null) {
1323
+ throw unsupported({
1324
+ harnessId,
1325
+ message: `${harnessId} does not support structured output through ACP.`,
1326
+ });
1327
+ }
1328
+ }
1286
1329
  if (!turnInFlight) {
1287
1330
  throw new Error(`${harnessId} has no in-flight ACP turn to continue.`);
1288
1331
  }
@@ -1310,6 +1353,12 @@ function createSession({
1310
1353
  builtinTools: turnStartConfig.builtinTools,
1311
1354
  permissionMode: turnStartConfig.permissionMode,
1312
1355
  permissionModeMapping: turnStartConfig.permissionModeMapping,
1356
+ ...(turnStartConfig.responseFormat == null
1357
+ ? {}
1358
+ : { responseFormat: turnStartConfig.responseFormat }),
1359
+ ...(turnStartConfig.outputSchemaMapping == null
1360
+ ? {}
1361
+ : { outputSchemaMapping: turnStartConfig.outputSchemaMapping }),
1313
1362
  ...(instructionMapping == null
1314
1363
  ? {}
1315
1364
  : {
@@ -1482,6 +1531,7 @@ function validateACPTurnStartConfig({
1482
1531
  authenticationProfile,
1483
1532
  sessionMeta,
1484
1533
  instructionMapping,
1534
+ outputSchemaMapping,
1485
1535
  builtinTools,
1486
1536
  permissionModeMapping,
1487
1537
  mcpServers,
@@ -1490,6 +1540,7 @@ function validateACPTurnStartConfig({
1490
1540
  authenticationProfile: ACPAuthenticationProfileIdentity;
1491
1541
  sessionMeta: Readonly<Record<string, ACPSerializableValue>> | undefined;
1492
1542
  instructionMapping: ACPInstructionMapping | undefined;
1543
+ outputSchemaMapping: ACPOutputSchemaMapping | undefined;
1493
1544
  builtinTools: ReadonlyArray<ACPBuiltinToolMapping>;
1494
1545
  permissionModeMapping: ACPPermissionModeMapping | undefined;
1495
1546
  mcpServers: Record<string, unknown> | undefined;
@@ -1505,6 +1556,8 @@ function validateACPTurnStartConfig({
1505
1556
  authenticationProfile,
1506
1557
  sessionMeta,
1507
1558
  instructionMapping,
1559
+ responseFormat: turnStartConfig.responseFormat,
1560
+ outputSchemaMapping,
1508
1561
  });
1509
1562
  if (
1510
1563
  current.configurationFingerprint !==
@@ -1523,6 +1576,7 @@ function validateACPColdSessionConfiguration({
1523
1576
  authenticationProfile,
1524
1577
  sessionMeta,
1525
1578
  instructionMapping,
1579
+ outputSchemaMapping,
1526
1580
  builtinTools,
1527
1581
  permissionModeMapping,
1528
1582
  mcpServers,
@@ -1534,6 +1588,7 @@ function validateACPColdSessionConfiguration({
1534
1588
  authenticationProfile: ACPAuthenticationProfileIdentity;
1535
1589
  sessionMeta: Readonly<Record<string, ACPSerializableValue>> | undefined;
1536
1590
  instructionMapping: ACPInstructionMapping | undefined;
1591
+ outputSchemaMapping: ACPOutputSchemaMapping | undefined;
1537
1592
  builtinTools: ReadonlyArray<ACPBuiltinToolMapping>;
1538
1593
  permissionModeMapping: ACPPermissionModeMapping | undefined;
1539
1594
  mcpServers: Record<string, unknown> | undefined;
@@ -1550,6 +1605,8 @@ function validateACPColdSessionConfiguration({
1550
1605
  authenticationProfile,
1551
1606
  sessionMeta,
1552
1607
  instructionMapping,
1608
+ responseFormat: coldSession.responseFormat,
1609
+ outputSchemaMapping,
1553
1610
  });
1554
1611
  if (
1555
1612
  current.configurationFingerprint !== coldSession.configurationFingerprint ||
@@ -100,6 +100,11 @@ export type ACPInstructionMapping =
100
100
  readonly path: ReadonlyArray<string>;
101
101
  };
102
102
 
103
+ export type ACPOutputSchemaMapping = {
104
+ readonly type: 'session-prompt-meta';
105
+ readonly path: ReadonlyArray<string>;
106
+ };
107
+
103
108
  export type ACPV1Settings = {
104
109
  readonly version?: 'v1';
105
110
  readonly harnessId: string;
@@ -125,6 +130,11 @@ export type ACPV1Settings = {
125
130
  * prompt. When omitted, instructions are prepended to the first user prompt.
126
131
  */
127
132
  readonly instructionMapping?: ACPInstructionMapping;
133
+ /**
134
+ * Maps structured output JSON Schema to an implementation-specific path
135
+ * below the ACP session prompt's `_meta` field.
136
+ */
137
+ readonly outputSchemaMapping?: ACPOutputSchemaMapping;
128
138
  readonly permissionModeMapping?: ACPPermissionModeMapping;
129
139
  readonly session?: {
130
140
  readonly meta?: Readonly<Record<string, ACPSerializableValue>>;
@@ -11,6 +11,7 @@ import {
11
11
  import type { ACPTextContentBlock } from './acp-v1-prompt';
12
12
  import type {
13
13
  ACPInstructionMapping,
14
+ ACPOutputSchemaMapping,
14
15
  ACPPermissionModeMapping,
15
16
  ACPSerializableValue,
16
17
  } from './acp-v1-settings';
@@ -26,6 +27,8 @@ export function createACPTurnStartConfig({
26
27
  authenticationProfile,
27
28
  sessionMeta,
28
29
  instructionMapping,
30
+ responseFormat,
31
+ outputSchemaMapping,
29
32
  }: {
30
33
  prompt: ReadonlyArray<ACPTextContentBlock>;
31
34
  tools: ReadonlyArray<HarnessV1ToolSpec>;
@@ -37,6 +40,8 @@ export function createACPTurnStartConfig({
37
40
  authenticationProfile: ACPAuthenticationProfileIdentity;
38
41
  sessionMeta: Readonly<Record<string, ACPSerializableValue>> | undefined;
39
42
  instructionMapping: ACPInstructionMapping | undefined;
43
+ responseFormat: StartMessage['responseFormat'];
44
+ outputSchemaMapping: ACPOutputSchemaMapping | undefined;
40
45
  }): ACPTurnStartConfig {
41
46
  return {
42
47
  version: 1,
@@ -46,6 +51,7 @@ export function createACPTurnStartConfig({
46
51
  authenticationProfile,
47
52
  sessionMeta: sessionMeta ?? null,
48
53
  ...(instructionMapping == null ? {} : { instructionMapping }),
54
+ ...(outputSchemaMapping == null ? {} : { outputSchemaMapping }),
49
55
  builtinTools,
50
56
  permissionModeMapping: permissionModeMapping ?? null,
51
57
  mcpServers: mcpServers ?? null,
@@ -56,6 +62,15 @@ export function createACPTurnStartConfig({
56
62
  tools: tools.map(tool => acpSerializableToolSpecSchema.parse(tool)),
57
63
  builtinTools: [...builtinTools],
58
64
  permissionMode,
65
+ ...(responseFormat == null ? {} : { responseFormat }),
66
+ ...(outputSchemaMapping == null
67
+ ? {}
68
+ : {
69
+ outputSchemaMapping: {
70
+ type: outputSchemaMapping.type,
71
+ path: [...outputSchemaMapping.path],
72
+ },
73
+ }),
59
74
  ...(permissionModeMapping == null ? {} : { permissionModeMapping }),
60
75
  ...(debug == null ? {} : { debug }),
61
76
  };
@@ -74,6 +89,12 @@ export function createACPColdSessionState({
74
89
  tools: turnStartConfig.tools,
75
90
  builtinTools: turnStartConfig.builtinTools,
76
91
  permissionMode: turnStartConfig.permissionMode,
92
+ ...(turnStartConfig.responseFormat == null
93
+ ? {}
94
+ : { responseFormat: turnStartConfig.responseFormat }),
95
+ ...(turnStartConfig.outputSchemaMapping == null
96
+ ? {}
97
+ : { outputSchemaMapping: turnStartConfig.outputSchemaMapping }),
77
98
  ...(turnStartConfig.permissionModeMapping == null
78
99
  ? {}
79
100
  : { permissionModeMapping: turnStartConfig.permissionModeMapping }),
@@ -261,7 +261,13 @@ async function runTurn(start: StartMessage, turn: BridgeTurn): Promise<void> {
261
261
  };
262
262
  hostToolRelay?.bindTurn({ turn: relayTurn });
263
263
  try {
264
- void activeSession.prompt(start.prompt);
264
+ const promptMeta = createOutputSchemaPromptMeta({ start });
265
+ void promptActiveSession({
266
+ session: activeSession,
267
+ agent: connection!.agent,
268
+ prompt: start.prompt,
269
+ meta: promptMeta,
270
+ });
265
271
  if (turn.abortSignal.aborted) {
266
272
  await cancel();
267
273
  } else {
@@ -706,6 +712,81 @@ function isRecord(value: unknown): value is Readonly<Record<string, unknown>> {
706
712
  return value != null && typeof value === 'object' && !Array.isArray(value);
707
713
  }
708
714
 
715
+ function createOutputSchemaPromptMeta({
716
+ start,
717
+ }: {
718
+ start: StartMessage;
719
+ }): Record<string, unknown> | undefined {
720
+ if (
721
+ start.responseFormat?.type !== 'json' ||
722
+ start.responseFormat.schema == null ||
723
+ start.outputSchemaMapping?.type !== 'session-prompt-meta'
724
+ ) {
725
+ return undefined;
726
+ }
727
+ const root: Record<string, unknown> = {};
728
+ let target = root;
729
+ const path = start.outputSchemaMapping.path;
730
+ for (let index = 0; index < path.length - 1; index++) {
731
+ const child: Record<string, unknown> = {};
732
+ target[path[index]!] = child;
733
+ target = child;
734
+ }
735
+ target[path[path.length - 1]!] = start.responseFormat.schema;
736
+ return root;
737
+ }
738
+
739
+ function promptActiveSession({
740
+ session,
741
+ agent,
742
+ prompt,
743
+ meta,
744
+ }: {
745
+ session: ACPActiveSession;
746
+ agent: acp.ClientContext;
747
+ prompt: Array<acp.ContentBlock>;
748
+ meta: Record<string, unknown> | undefined;
749
+ }): Promise<acp.PromptResponse> {
750
+ if (meta == null) return session.prompt(prompt);
751
+ if (session.promptWithMeta != null) {
752
+ return session.promptWithMeta({ prompt, meta });
753
+ }
754
+ const updates = (
755
+ session as unknown as {
756
+ updates?: {
757
+ clearErrors(): void;
758
+ enqueue(value: acp.ActiveSessionMessage): void;
759
+ reject(error: unknown): void;
760
+ };
761
+ }
762
+ ).updates;
763
+ if (updates == null) {
764
+ throw new Error(
765
+ 'The installed ACP SDK cannot send session prompt metadata while preserving streamed updates.',
766
+ );
767
+ }
768
+ updates.clearErrors();
769
+ const response = agent.request<acp.PromptResponse, acp.PromptRequest>(
770
+ acp.methods.agent.session.prompt,
771
+ {
772
+ sessionId: session.sessionId,
773
+ prompt,
774
+ _meta: meta,
775
+ },
776
+ );
777
+ void response.then(
778
+ value => {
779
+ updates.enqueue({
780
+ kind: 'stop',
781
+ response: value,
782
+ stopReason: value.stopReason,
783
+ });
784
+ },
785
+ error => updates.reject(error),
786
+ );
787
+ return response;
788
+ }
789
+
709
790
  async function readImplementationDescriptor({
710
791
  path,
711
792
  }: {
@@ -6,6 +6,10 @@ export type ACPActiveSession = {
6
6
  readonly sessionId: string;
7
7
  readonly newSessionResponse: acp.NewSessionResponse;
8
8
  prompt(prompt: Array<acp.ContentBlock>): Promise<acp.PromptResponse>;
9
+ promptWithMeta?(options: {
10
+ prompt: Array<acp.ContentBlock>;
11
+ meta: Record<string, unknown>;
12
+ }): Promise<acp.PromptResponse>;
9
13
  nextUpdate(): Promise<acp.ActiveSessionMessage>;
10
14
  dispose(): void;
11
15
  };
@@ -70,6 +74,27 @@ export function createACPRecoveredSession({
70
74
  );
71
75
  return response;
72
76
  },
77
+ promptWithMeta: async ({ prompt, meta }) => {
78
+ if (disposed) {
79
+ throw new Error('Recovered ACP session is disposed.');
80
+ }
81
+ updates.clearErrors();
82
+ const response = agent.request<acp.PromptResponse, acp.PromptRequest>(
83
+ acp.methods.agent.session.prompt,
84
+ { sessionId, prompt, _meta: meta },
85
+ );
86
+ void response.then(
87
+ value => {
88
+ updates.enqueue({
89
+ kind: 'stop',
90
+ response: value,
91
+ stopReason: value.stopReason,
92
+ });
93
+ },
94
+ error => updates.reject(error),
95
+ );
96
+ return response;
97
+ },
73
98
  nextUpdate: () => updates.next(),
74
99
  dispose: () => {
75
100
  if (disposed) return;
package/src/v1/index.ts CHANGED
@@ -5,6 +5,7 @@ export type {
5
5
  ACPInstructionMapping,
6
6
  ACPNpmLockedSource,
7
7
  ACPNpmSimpleSource,
8
+ ACPOutputSchemaMapping,
8
9
  ACPPermissionModeMapping,
9
10
  ACPPermissionModeTarget,
10
11
  ACPProfileValue,