@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/CHANGELOG.md +17 -0
- package/dist/bridge/host-tool-mcp.mjs +1 -1
- package/dist/bridge/index.mjs +149 -12
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/index.d.ts +30 -5
- package/dist/index.js +241 -13
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/acp-harness.ts +43 -9
- package/src/index.ts +1 -0
- package/src/v1/acp-v1-bridge-protocol.ts +15 -0
- package/src/v1/acp-v1-harness.ts +275 -5
- package/src/v1/acp-v1-settings.ts +22 -0
- package/src/v1/bridge/acp-v1-bridge-environment.ts +6 -0
- package/src/v1/bridge/create-emit-stream-event.ts +6 -0
- package/src/v1/bridge/host-tool-correlation.ts +34 -0
- package/src/v1/bridge/index.ts +97 -3
- package/src/v1/bridge/stream-translator.ts +6 -1
- 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.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.
|
|
29
|
+
"@ai-sdk/harness": "1.0.101",
|
|
30
30
|
"@ai-sdk/provider-utils": "5.0.36",
|
|
31
31
|
"ws": "^8.21.0"
|
|
32
32
|
},
|
package/src/acp-harness.ts
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
|
-
import
|
|
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 {
|
|
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<
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
@@ -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
|
|
package/src/v1/acp-v1-harness.ts
CHANGED
|
@@ -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
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
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
|
}
|