@ai-sdk/harness-acp 1.0.37 → 1.0.39

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.37",
3
+ "version": "1.0.39",
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.99",
29
+ "@ai-sdk/harness": "1.0.101",
30
30
  "@ai-sdk/provider-utils": "5.0.36",
31
31
  "ws": "^8.21.0"
32
32
  },
@@ -1,9 +1,17 @@
1
- import type { HarnessV1, HarnessV1PortEndpoint } from '@ai-sdk/harness';
1
+ import {
2
+ HARNESS_V1_BUILTIN_TOOLS,
3
+ type HarnessV1,
4
+ type HarnessV1PortEndpoint,
5
+ } from '@ai-sdk/harness';
2
6
  import type { ToolSet } from '@ai-sdk/provider-utils';
3
7
  import { z } from 'zod/v4';
4
8
  import type { ACPClientApp } from './acp-auth';
5
9
  import type { ACPToolCall } from './acp-tool-call';
6
- import { createACPV1, type ACPV1Settings } from './v1';
10
+ import {
11
+ createACPV1,
12
+ type ACPAskUserQuestionsSettings,
13
+ type ACPV1Settings,
14
+ } from './v1';
7
15
  import {
8
16
  acpColdSessionStateSchema,
9
17
  acpTurnStartConfigSchema,
@@ -15,7 +23,12 @@ const ACP_CLIENT_APP = {
15
23
  version: VERSION,
16
24
  } as const satisfies ACPClientApp;
17
25
 
18
- export type ACPHarnessSettings<TBuiltinTools extends ToolSet = {}> = {
26
+ export type ACPHarnessSettings<
27
+ TBuiltinTools extends ToolSet = {},
28
+ TAskUserQuestions extends ACPAskUserQuestionsSettings | undefined =
29
+ | ACPAskUserQuestionsSettings
30
+ | undefined,
31
+ > = {
19
32
  readonly builtinTools?: TBuiltinTools;
20
33
  /**
21
34
  * MCP server definitions keyed by server name. Each definition uses the
@@ -61,6 +74,7 @@ export type ACPHarnessSettings<TBuiltinTools extends ToolSet = {}> = {
61
74
  readonly skillsDirectory?: ACPV1Settings['skillsDirectory'];
62
75
  readonly instructionMapping?: ACPV1Settings['instructionMapping'];
63
76
  readonly outputSchemaMapping?: ACPV1Settings['outputSchemaMapping'];
77
+ readonly askUserQuestions?: TAskUserQuestions;
64
78
  readonly permissionModeMapping?: ACPV1Settings['permissionModeMapping'];
65
79
  readonly session?: ACPV1Settings['session'];
66
80
  readonly mintBridgeToken?: ACPV1Settings['mintBridgeToken'];
@@ -117,9 +131,22 @@ const acpResumeStateSchema = z.object({
117
131
 
118
132
  type ACPBridgeCoords = z.infer<typeof acpBridgeCoordsSchema>;
119
133
 
120
- export function createACP<TBuiltinTools extends ToolSet = {}>(
121
- settings: ACPHarnessSettings<TBuiltinTools>,
122
- ): HarnessV1<TBuiltinTools> {
134
+ type ACPHarnessTools<
135
+ TBuiltinTools extends ToolSet,
136
+ TAskUserQuestions,
137
+ > = TBuiltinTools &
138
+ ([TAskUserQuestions] extends [undefined]
139
+ ? {}
140
+ : {
141
+ askUserQuestions: typeof HARNESS_V1_BUILTIN_TOOLS.askUserQuestions;
142
+ });
143
+
144
+ export function createACP<
145
+ TBuiltinTools extends ToolSet = {},
146
+ TAskUserQuestions extends ACPAskUserQuestionsSettings | undefined = undefined,
147
+ >(
148
+ settings: ACPHarnessSettings<TBuiltinTools, TAskUserQuestions>,
149
+ ): HarnessV1<ACPHarnessTools<TBuiltinTools, TAskUserQuestions>> {
123
150
  if (
124
151
  (settings.credentialEnv == null) !==
125
152
  (settings.credentialBrokering == null)
@@ -143,10 +170,17 @@ export function createACP<TBuiltinTools extends ToolSet = {}>(
143
170
  switch (version) {
144
171
  case 'v1': {
145
172
  const clientApp = settings.clientApp ?? ACP_CLIENT_APP;
173
+ const builtinTools =
174
+ settings.askUserQuestions == null
175
+ ? (settings.builtinTools ?? (ACP_BUILTIN_TOOLS as TBuiltinTools))
176
+ : {
177
+ ...(settings.builtinTools ??
178
+ (ACP_BUILTIN_TOOLS as TBuiltinTools)),
179
+ askUserQuestions: HARNESS_V1_BUILTIN_TOOLS.askUserQuestions,
180
+ };
146
181
  return createACPV1({
147
182
  settings,
148
- builtinTools:
149
- settings.builtinTools ?? (ACP_BUILTIN_TOOLS as TBuiltinTools),
183
+ builtinTools,
150
184
  port: settings.port,
151
185
  portEndpoint: settings.portEndpoint,
152
186
  startupTimeoutMs: settings.startupTimeoutMs,
@@ -154,7 +188,7 @@ export function createACP<TBuiltinTools extends ToolSet = {}>(
154
188
  lifecycleStateSchema: acpResumeStateSchema satisfies z.ZodType<{
155
189
  bridge?: ACPBridgeCoords;
156
190
  }>,
157
- });
191
+ }) as HarnessV1<ACPHarnessTools<TBuiltinTools, TAskUserQuestions>>;
158
192
  }
159
193
  default:
160
194
  throw new Error(
package/src/index.ts CHANGED
@@ -20,6 +20,7 @@ export type {
20
20
  ACPToolKind,
21
21
  } from './acp-tool-call';
22
22
  export type {
23
+ ACPAskUserQuestionsSettings,
23
24
  ACPCredentialBrokering,
24
25
  ACPAuthentication,
25
26
  ACPInstallCommandSource,
@@ -199,6 +199,7 @@ const coldRestoreSchema = z.object({
199
199
 
200
200
  const acpToolCallCandidateSchema = z.object({
201
201
  type: z.literal('acp-tool-call-candidate'),
202
+ requestId: z.string(),
202
203
  toolCall: z.custom<ACPToolCall>(
203
204
  value =>
204
205
  value != null &&
@@ -208,9 +209,23 @@ const acpToolCallCandidateSchema = z.object({
208
209
  ),
209
210
  });
210
211
 
212
+ const acpQuestionRequestSchema = z.object({
213
+ type: z.literal('acp-question-request'),
214
+ requestId: z.string(),
215
+ nativeRequest: z.unknown(),
216
+ nativeToolCall: acpToolCallCandidateSchema.shape.toolCall.optional(),
217
+ });
218
+
219
+ const acpQuestionResolvedSchema = z.object({
220
+ type: z.literal('acp-question-resolved'),
221
+ requestId: z.string(),
222
+ });
223
+
211
224
  export const outboundMessageSchema = z.union([
212
225
  harnessV1BridgeOutboundMessageSchema,
213
226
  acpToolCallCandidateSchema,
227
+ acpQuestionRequestSchema,
228
+ acpQuestionResolvedSchema,
214
229
  ]);
215
230
  export type OutboundMessage = z.infer<typeof outboundMessageSchema>;
216
231
 
@@ -37,6 +37,7 @@ import {
37
37
  asSchema,
38
38
  type Experimental_SandboxProcess,
39
39
  type Experimental_SandboxSession as SandboxSession,
40
+ type ToolResultPart,
40
41
  type ToolSet,
41
42
  } from '@ai-sdk/provider-utils';
42
43
  import { WebSocket } from 'ws';
@@ -81,6 +82,7 @@ import {
81
82
  prependACPInstructionGuidance,
82
83
  } from './acp-v1-prompt';
83
84
  import type {
85
+ ACPAskUserQuestionsSettings,
84
86
  ACPInstructionMapping,
85
87
  ACPModelMapping,
86
88
  ACPOutputSchemaMapping,
@@ -333,8 +335,6 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
333
335
  'ai-gateway'
334
336
  ? {}
335
337
  : undefined;
336
- } else if (settings.credentialBrokering != null) {
337
- warnCredentialBrokeringUnavailable();
338
338
  }
339
339
  if (
340
340
  settings.credentialForwarding != null &&
@@ -444,6 +444,7 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
444
444
  sessionMeta: settings.session?.meta,
445
445
  instructionMapping: settings.instructionMapping,
446
446
  outputSchemaMapping: settings.outputSchemaMapping,
447
+ askUserQuestions: settings.askUserQuestions,
447
448
  debug: startOptions.observability?.debug,
448
449
  implementationIdentity,
449
450
  authenticationProfile,
@@ -561,6 +562,25 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
561
562
  credentialForwarding: settings.credentialForwarding,
562
563
  })
563
564
  : sandboxImplementationEnvironment;
565
+ if (
566
+ settings.credentialBrokering != null &&
567
+ sandboxCredentialEnvironment == null
568
+ ) {
569
+ warnCredentialBrokeringUnavailable({
570
+ environment: {
571
+ ...sandboxImplementationEnvironment,
572
+ ...resolvedProviderAuthentication.env,
573
+ },
574
+ forwardedEnvironment: {
575
+ ...forwardedImplementationEnvironment,
576
+ ...sandboxProviderAuthenticationEnvironment,
577
+ },
578
+ credentialEnvironmentVariables: [
579
+ ...credentialForwardingEnvironmentVariables,
580
+ 'AI_SDK_ACP_GATEWAY_API_KEY',
581
+ ],
582
+ });
583
+ }
564
584
  const port = resolveBridgePort({
565
585
  sandboxSession,
566
586
  override: portOverride,
@@ -598,6 +618,8 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
598
618
  sandboxProviderEnvironment == null ? undefined : {},
599
619
  sessionMeta: settings.session?.meta,
600
620
  clientCapabilities: settings.clientCapabilities,
621
+ askUserQuestionsRequestMethod:
622
+ settings.askUserQuestions?.requestMethod,
601
623
  }),
602
624
  ...sandboxProviderAuthenticationEnvironment,
603
625
  BRIDGE_CHANNEL_TOKEN: token,
@@ -715,6 +737,7 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
715
737
  sessionMeta: settings.session?.meta,
716
738
  instructionMapping: settings.instructionMapping,
717
739
  outputSchemaMapping: settings.outputSchemaMapping,
740
+ askUserQuestions: settings.askUserQuestions,
718
741
  debug: startOptions.observability?.debug,
719
742
  implementationIdentity,
720
743
  authenticationProfile,
@@ -1028,6 +1051,7 @@ function createSession({
1028
1051
  sessionMeta,
1029
1052
  instructionMapping,
1030
1053
  outputSchemaMapping,
1054
+ askUserQuestions,
1031
1055
  debug,
1032
1056
  implementationIdentity,
1033
1057
  authenticationProfile,
@@ -1064,6 +1088,7 @@ function createSession({
1064
1088
  sessionMeta: Readonly<Record<string, ACPSerializableValue>> | undefined;
1065
1089
  instructionMapping: ACPInstructionMapping | undefined;
1066
1090
  outputSchemaMapping: ACPOutputSchemaMapping | undefined;
1091
+ askUserQuestions: ACPAskUserQuestionsSettings | undefined;
1067
1092
  debug: HarnessV1DebugConfig | undefined;
1068
1093
  implementationIdentity: string;
1069
1094
  authenticationProfile: ACPAuthenticationProfileIdentity;
@@ -1097,6 +1122,14 @@ function createSession({
1097
1122
  let instructionsFingerprint = instructionsFingerprintAtStart;
1098
1123
  let latestACPSessionId = acpSessionIdAtStart;
1099
1124
  let latestTurnStartConfig = turnStartConfigAtStart;
1125
+ const bufferedQuestionResults = new Map<
1126
+ string,
1127
+ {
1128
+ readonly output: unknown;
1129
+ readonly isError?: boolean;
1130
+ readonly toolResult: ToolResultPart;
1131
+ }
1132
+ >();
1100
1133
 
1101
1134
  const markTurnFinished = () => {
1102
1135
  turnInFlight = false;
@@ -1104,9 +1137,14 @@ function createSession({
1104
1137
  channel.on('bridge-thread', event => {
1105
1138
  latestACPSessionId = event.threadId;
1106
1139
  });
1107
- channel.on('finish', markTurnFinished);
1108
- channel.on('error', markTurnFinished);
1109
- channel.onClose(markTurnFinished);
1140
+ // A resumed continuation replays its buffered events after the new session
1141
+ // is created. Let `wireTurn` consume those events so it can forward a
1142
+ // terminal replay instead of rejecting the continuation before it starts.
1143
+ if (!turnInFlightAtStart) {
1144
+ channel.on('finish', markTurnFinished);
1145
+ channel.on('error', markTurnFinished);
1146
+ channel.onClose(markTurnFinished);
1147
+ }
1110
1148
 
1111
1149
  const wireTurn = ({
1112
1150
  emit,
@@ -1132,6 +1170,14 @@ function createSession({
1132
1170
  | undefined;
1133
1171
  const dynamicToolCalls = new Map<string, boolean>();
1134
1172
  const toolCallClassificationErrors = new Map<string, unknown>();
1173
+ const activeQuestionRequests = new Map<
1174
+ string,
1175
+ {
1176
+ readonly requestId: string;
1177
+ readonly nativeRequest: unknown;
1178
+ }
1179
+ >();
1180
+ const questionToolCallIdsByRequestId = new Map<string, string>();
1135
1181
  const subscriptions: Array<() => void> = [];
1136
1182
  const forward = (event: HarnessV1StreamPart) => {
1137
1183
  if (event.type === 'text-start' || event.type === 'reasoning-start') {
@@ -1198,12 +1244,120 @@ function createSession({
1198
1244
  subscriptions.push(
1199
1245
  channel.on('acp-tool-call-candidate', event => {
1200
1246
  try {
1247
+ const suppress =
1248
+ askUserQuestions?.isNativeToolCall?.({
1249
+ nativeToolCall: event.toolCall,
1250
+ }) === true;
1201
1251
  dynamicToolCalls.set(
1202
1252
  event.toolCall.toolCallId,
1203
1253
  isMcpToolCall?.(event.toolCall) === true,
1204
1254
  );
1255
+ channel.send({
1256
+ type: 'tool-result',
1257
+ toolCallId: event.requestId,
1258
+ output: { suppress },
1259
+ });
1205
1260
  } catch (error) {
1206
1261
  toolCallClassificationErrors.set(event.toolCall.toolCallId, error);
1262
+ channel.send({
1263
+ type: 'tool-result',
1264
+ toolCallId: event.requestId,
1265
+ output: { suppress: false },
1266
+ });
1267
+ }
1268
+ }),
1269
+ );
1270
+ subscriptions.push(
1271
+ channel.on('acp-question-request', event => {
1272
+ if (askUserQuestions == null) {
1273
+ channel.send({
1274
+ type: 'tool-result',
1275
+ toolCallId: event.requestId,
1276
+ output: { type: 'unhandled' },
1277
+ });
1278
+ return;
1279
+ }
1280
+ try {
1281
+ const nativeToolCall = askUserQuestions.fromNativeRequest({
1282
+ nativeRequest: event.nativeRequest,
1283
+ nativeToolCall: event.nativeToolCall,
1284
+ });
1285
+ if (nativeToolCall == null) {
1286
+ channel.send({
1287
+ type: 'tool-result',
1288
+ toolCallId: event.requestId,
1289
+ output: { type: 'unhandled' },
1290
+ });
1291
+ return;
1292
+ }
1293
+ if (
1294
+ nativeToolCall.toolName !== 'askUserQuestions' ||
1295
+ nativeToolCall.providerExecuted !== false
1296
+ ) {
1297
+ throw new Error(
1298
+ `${harnessId} ACP askUserQuestions.fromNativeRequest must return a client-executed askUserQuestions tool call.`,
1299
+ );
1300
+ }
1301
+
1302
+ const toolCall = withNativeQuestionRequest({
1303
+ harnessId,
1304
+ nativeRequest: event.nativeRequest,
1305
+ toolCall: nativeToolCall,
1306
+ });
1307
+ const bufferedResult = takeBufferedQuestionResult({
1308
+ bufferedQuestionResults,
1309
+ toolCallId: toolCall.toolCallId,
1310
+ nativeRequest: event.nativeRequest,
1311
+ matchesNativeRequest: askUserQuestions.matchesNativeRequest,
1312
+ harnessId,
1313
+ });
1314
+ activeQuestionRequests.set(toolCall.toolCallId, {
1315
+ requestId: event.requestId,
1316
+ nativeRequest: event.nativeRequest,
1317
+ });
1318
+ questionToolCallIdsByRequestId.set(
1319
+ event.requestId,
1320
+ toolCall.toolCallId,
1321
+ );
1322
+ channel.send({
1323
+ type: 'tool-result',
1324
+ toolCallId: event.requestId,
1325
+ output: {
1326
+ type: 'handled',
1327
+ toolCallId: toolCall.toolCallId,
1328
+ },
1329
+ });
1330
+
1331
+ if (bufferedResult == null) {
1332
+ forward(toolCall);
1333
+ return;
1334
+ }
1335
+ channel.send({
1336
+ type: 'tool-result',
1337
+ toolCallId: toolCall.toolCallId,
1338
+ output: askUserQuestions.toNativeResponse({
1339
+ nativeRequest: event.nativeRequest,
1340
+ toolResult: bufferedResult.toolResult,
1341
+ }),
1342
+ isError: bufferedResult.isError,
1343
+ toolResult: bufferedResult.toolResult,
1344
+ });
1345
+ } catch (error) {
1346
+ closeForwardedBlock();
1347
+ forward({ type: 'error', error });
1348
+ try {
1349
+ channel.send({ type: 'abort' });
1350
+ } catch {}
1351
+ settle({ error });
1352
+ }
1353
+ }),
1354
+ );
1355
+ subscriptions.push(
1356
+ channel.on('acp-question-resolved', event => {
1357
+ const toolCallId = questionToolCallIdsByRequestId.get(event.requestId);
1358
+ if (toolCallId != null) {
1359
+ activeQuestionRequests.delete(toolCallId);
1360
+ questionToolCallIdsByRequestId.delete(event.requestId);
1207
1361
  }
1208
1362
  }),
1209
1363
  );
@@ -1239,6 +1393,7 @@ function createSession({
1239
1393
  }
1240
1394
  subscriptions.push(
1241
1395
  channel.on('finish', event => {
1396
+ markTurnFinished();
1242
1397
  closeForwardedBlock();
1243
1398
  forward(event);
1244
1399
  settle(abortRequested ? { error: abortError } : {});
@@ -1246,6 +1401,7 @@ function createSession({
1246
1401
  );
1247
1402
  subscriptions.push(
1248
1403
  channel.on('error', event => {
1404
+ markTurnFinished();
1249
1405
  closeForwardedBlock();
1250
1406
  const error = deserializeBridgeError({
1251
1407
  error: event.error,
@@ -1256,6 +1412,7 @@ function createSession({
1256
1412
  }),
1257
1413
  );
1258
1414
  channel.onClose((_code, reason) => {
1415
+ markTurnFinished();
1259
1416
  if (reason === 'suspended') {
1260
1417
  settle({});
1261
1418
  return;
@@ -1290,11 +1447,52 @@ function createSession({
1290
1447
  });
1291
1448
  },
1292
1449
  submitToolResult: async input => {
1450
+ if (
1451
+ askUserQuestions != null &&
1452
+ input.toolResult?.toolName === 'askUserQuestions'
1453
+ ) {
1454
+ const activeRequest = activeQuestionRequests.get(input.toolCallId);
1455
+ if (activeRequest == null) {
1456
+ const previousNativeRequest =
1457
+ input.toolResult.providerOptions?.[harnessId]?.nativeRequest;
1458
+ if (!lossyRerun && previousNativeRequest !== undefined) {
1459
+ channel.send({
1460
+ type: 'tool-result',
1461
+ toolCallId: input.toolCallId,
1462
+ output: askUserQuestions.toNativeResponse({
1463
+ nativeRequest: previousNativeRequest,
1464
+ toolResult: input.toolResult,
1465
+ }),
1466
+ isError: input.isError,
1467
+ toolResult: input.toolResult,
1468
+ });
1469
+ return;
1470
+ }
1471
+ bufferedQuestionResults.set(input.toolCallId, {
1472
+ output: input.output,
1473
+ ...(input.isError == null ? {} : { isError: input.isError }),
1474
+ toolResult: input.toolResult,
1475
+ });
1476
+ return;
1477
+ }
1478
+ channel.send({
1479
+ type: 'tool-result',
1480
+ toolCallId: input.toolCallId,
1481
+ output: askUserQuestions.toNativeResponse({
1482
+ nativeRequest: activeRequest.nativeRequest,
1483
+ toolResult: input.toolResult,
1484
+ }),
1485
+ isError: input.isError,
1486
+ toolResult: input.toolResult,
1487
+ });
1488
+ return;
1489
+ }
1293
1490
  channel.send({
1294
1491
  type: 'tool-result',
1295
1492
  toolCallId: input.toolCallId,
1296
1493
  output: input.output,
1297
1494
  isError: input.isError,
1495
+ toolResult: input.toolResult,
1298
1496
  });
1299
1497
  },
1300
1498
  done,
@@ -1657,6 +1855,78 @@ function isCompletePermissionModeMapping({
1657
1855
  );
1658
1856
  }
1659
1857
 
1858
+ function withNativeQuestionRequest({
1859
+ harnessId,
1860
+ nativeRequest,
1861
+ toolCall,
1862
+ }: {
1863
+ harnessId: string;
1864
+ nativeRequest: unknown;
1865
+ toolCall: Extract<HarnessV1StreamPart, { type: 'tool-call' }>;
1866
+ }): Extract<HarnessV1StreamPart, { type: 'tool-call' }> {
1867
+ const harnessMetadata = toolCall.providerMetadata?.[harnessId];
1868
+ return {
1869
+ ...toolCall,
1870
+ providerMetadata: {
1871
+ ...toolCall.providerMetadata,
1872
+ [harnessId]: {
1873
+ ...(harnessMetadata ?? {}),
1874
+ nativeRequest,
1875
+ } as NonNullable<
1876
+ Extract<HarnessV1StreamPart, { type: 'tool-call' }>['providerMetadata']
1877
+ >[string],
1878
+ },
1879
+ };
1880
+ }
1881
+
1882
+ function takeBufferedQuestionResult({
1883
+ bufferedQuestionResults,
1884
+ toolCallId,
1885
+ nativeRequest,
1886
+ matchesNativeRequest,
1887
+ harnessId,
1888
+ }: {
1889
+ bufferedQuestionResults: Map<
1890
+ string,
1891
+ {
1892
+ readonly output: unknown;
1893
+ readonly isError?: boolean;
1894
+ readonly toolResult: ToolResultPart;
1895
+ }
1896
+ >;
1897
+ toolCallId: string;
1898
+ nativeRequest: unknown;
1899
+ matchesNativeRequest:
1900
+ | ACPAskUserQuestionsSettings['matchesNativeRequest']
1901
+ | undefined;
1902
+ harnessId: string;
1903
+ }):
1904
+ | {
1905
+ readonly output: unknown;
1906
+ readonly isError?: boolean;
1907
+ readonly toolResult: ToolResultPart;
1908
+ }
1909
+ | undefined {
1910
+ const exact = bufferedQuestionResults.get(toolCallId);
1911
+ if (exact != null) {
1912
+ bufferedQuestionResults.delete(toolCallId);
1913
+ return exact;
1914
+ }
1915
+ if (matchesNativeRequest == null) return undefined;
1916
+ for (const [bufferedToolCallId, buffered] of bufferedQuestionResults) {
1917
+ const previousNativeRequest =
1918
+ buffered.toolResult.providerOptions?.[harnessId]?.nativeRequest;
1919
+ if (
1920
+ previousNativeRequest !== undefined &&
1921
+ matchesNativeRequest({ previousNativeRequest, nativeRequest })
1922
+ ) {
1923
+ bufferedQuestionResults.delete(bufferedToolCallId);
1924
+ return buffered;
1925
+ }
1926
+ }
1927
+ return undefined;
1928
+ }
1929
+
1660
1930
  function isPermissionModeMappingValue({
1661
1931
  value,
1662
1932
  }: {
@@ -3,7 +3,9 @@ import type {
3
3
  HarnessV1CredentialForwarding,
4
4
  HarnessV1PermissionMode,
5
5
  HarnessV1RequestTransformation,
6
+ HarnessV1StreamPart,
6
7
  } from '@ai-sdk/harness';
8
+ import type { ToolResultPart } from '@ai-sdk/provider-utils';
7
9
  import type { ACPToolCall } from '../acp-tool-call';
8
10
 
9
11
  export type ACPSerializablePrimitive = string | number | boolean | null;
@@ -129,6 +131,25 @@ export type ACPOutputSchemaMapping = {
129
131
  readonly path: ReadonlyArray<string>;
130
132
  };
131
133
 
134
+ export type ACPAskUserQuestionsSettings = {
135
+ readonly requestMethod: string;
136
+ readonly isNativeToolCall?: (options: {
137
+ nativeToolCall: ACPToolCall;
138
+ }) => boolean;
139
+ readonly fromNativeRequest: (options: {
140
+ nativeRequest: unknown;
141
+ nativeToolCall?: ACPToolCall;
142
+ }) => Extract<HarnessV1StreamPart, { type: 'tool-call' }> | null;
143
+ readonly toNativeResponse: (options: {
144
+ nativeRequest: unknown;
145
+ toolResult: ToolResultPart;
146
+ }) => unknown;
147
+ readonly matchesNativeRequest?: (options: {
148
+ previousNativeRequest: unknown;
149
+ nativeRequest: unknown;
150
+ }) => boolean;
151
+ };
152
+
132
153
  export type ACPV1Settings = {
133
154
  readonly version?: 'v1';
134
155
  readonly harnessId: string;
@@ -179,6 +200,7 @@ export type ACPV1Settings = {
179
200
  * below the ACP session prompt's `_meta` field.
180
201
  */
181
202
  readonly outputSchemaMapping?: ACPOutputSchemaMapping;
203
+ readonly askUserQuestions?: ACPAskUserQuestionsSettings;
182
204
  readonly permissionModeMapping?: ACPPermissionModeMapping;
183
205
  readonly session?: {
184
206
  readonly meta?: Readonly<Record<string, ACPSerializableValue>>;
@@ -60,6 +60,7 @@ export type ACPBridgeConfiguration = {
60
60
  readonly providerEnvironment?: Readonly<Record<string, string>>;
61
61
  readonly sessionMeta?: Readonly<Record<string, ACPSerializableValue>>;
62
62
  readonly clientCapabilities?: Readonly<Record<string, ACPSerializableValue>>;
63
+ readonly askUserQuestionsRequestMethod?: string;
63
64
  };
64
65
 
65
66
  const bridgeConfigurationSchema: z.ZodType<ACPBridgeConfiguration> = z.object({
@@ -68,6 +69,7 @@ const bridgeConfigurationSchema: z.ZodType<ACPBridgeConfiguration> = z.object({
68
69
  providerEnvironment: z.record(z.string(), z.string()).optional(),
69
70
  sessionMeta: serializableRecordSchema.optional(),
70
71
  clientCapabilities: serializableRecordSchema.optional(),
72
+ askUserQuestionsRequestMethod: z.string().optional(),
71
73
  });
72
74
 
73
75
  export function createACPBridgeEnvironment({
@@ -76,6 +78,7 @@ export function createACPBridgeEnvironment({
76
78
  providerEnvironment,
77
79
  sessionMeta,
78
80
  clientCapabilities,
81
+ askUserQuestionsRequestMethod,
79
82
  }: ACPBridgeConfiguration): Record<string, string> {
80
83
  return {
81
84
  [ACP_BRIDGE_CONFIGURATION_ENV]: JSON.stringify({
@@ -84,6 +87,9 @@ export function createACPBridgeEnvironment({
84
87
  ...(providerEnvironment == null ? {} : { providerEnvironment }),
85
88
  ...(sessionMeta == null ? {} : { sessionMeta }),
86
89
  ...(clientCapabilities == null ? {} : { clientCapabilities }),
90
+ ...(askUserQuestionsRequestMethod == null
91
+ ? {}
92
+ : { askUserQuestionsRequestMethod }),
87
93
  }),
88
94
  };
89
95
  }
@@ -51,6 +51,8 @@ export function createEmitStreamEvent({
51
51
  order: number;
52
52
  }) => void;
53
53
  removeHostToolCorrelationInvocation: (options: { token: string }) => void;
54
+ suppressToolCall: (options: { toolCallId: string }) => void;
55
+ getToolCall: (options: { toolCallId: string }) => ACPToolCall | undefined;
54
56
  } {
55
57
  const translator = createACPStreamTranslator({
56
58
  emit,
@@ -100,5 +102,9 @@ export function createEmitStreamEvent({
100
102
  hostToolResult: translator.hostToolResult,
101
103
  registerHostToolCorrelationInvocation: correlation.registerInvocation,
102
104
  removeHostToolCorrelationInvocation: correlation.removeInvocation,
105
+ suppressToolCall: correlation.suppressToolCall,
106
+ getToolCall: ({ toolCallId }) =>
107
+ correlation.getToolCall({ toolCallId }) ??
108
+ translator.getToolCall({ toolCallId }),
103
109
  };
104
110
  }