@ai-sdk/harness-acp 1.0.38 → 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 +8 -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 +217 -8
- 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 +245 -0
- 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,
|
|
@@ -442,6 +444,7 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
442
444
|
sessionMeta: settings.session?.meta,
|
|
443
445
|
instructionMapping: settings.instructionMapping,
|
|
444
446
|
outputSchemaMapping: settings.outputSchemaMapping,
|
|
447
|
+
askUserQuestions: settings.askUserQuestions,
|
|
445
448
|
debug: startOptions.observability?.debug,
|
|
446
449
|
implementationIdentity,
|
|
447
450
|
authenticationProfile,
|
|
@@ -615,6 +618,8 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
615
618
|
sandboxProviderEnvironment == null ? undefined : {},
|
|
616
619
|
sessionMeta: settings.session?.meta,
|
|
617
620
|
clientCapabilities: settings.clientCapabilities,
|
|
621
|
+
askUserQuestionsRequestMethod:
|
|
622
|
+
settings.askUserQuestions?.requestMethod,
|
|
618
623
|
}),
|
|
619
624
|
...sandboxProviderAuthenticationEnvironment,
|
|
620
625
|
BRIDGE_CHANNEL_TOKEN: token,
|
|
@@ -732,6 +737,7 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
732
737
|
sessionMeta: settings.session?.meta,
|
|
733
738
|
instructionMapping: settings.instructionMapping,
|
|
734
739
|
outputSchemaMapping: settings.outputSchemaMapping,
|
|
740
|
+
askUserQuestions: settings.askUserQuestions,
|
|
735
741
|
debug: startOptions.observability?.debug,
|
|
736
742
|
implementationIdentity,
|
|
737
743
|
authenticationProfile,
|
|
@@ -1045,6 +1051,7 @@ function createSession({
|
|
|
1045
1051
|
sessionMeta,
|
|
1046
1052
|
instructionMapping,
|
|
1047
1053
|
outputSchemaMapping,
|
|
1054
|
+
askUserQuestions,
|
|
1048
1055
|
debug,
|
|
1049
1056
|
implementationIdentity,
|
|
1050
1057
|
authenticationProfile,
|
|
@@ -1081,6 +1088,7 @@ function createSession({
|
|
|
1081
1088
|
sessionMeta: Readonly<Record<string, ACPSerializableValue>> | undefined;
|
|
1082
1089
|
instructionMapping: ACPInstructionMapping | undefined;
|
|
1083
1090
|
outputSchemaMapping: ACPOutputSchemaMapping | undefined;
|
|
1091
|
+
askUserQuestions: ACPAskUserQuestionsSettings | undefined;
|
|
1084
1092
|
debug: HarnessV1DebugConfig | undefined;
|
|
1085
1093
|
implementationIdentity: string;
|
|
1086
1094
|
authenticationProfile: ACPAuthenticationProfileIdentity;
|
|
@@ -1114,6 +1122,14 @@ function createSession({
|
|
|
1114
1122
|
let instructionsFingerprint = instructionsFingerprintAtStart;
|
|
1115
1123
|
let latestACPSessionId = acpSessionIdAtStart;
|
|
1116
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
|
+
>();
|
|
1117
1133
|
|
|
1118
1134
|
const markTurnFinished = () => {
|
|
1119
1135
|
turnInFlight = false;
|
|
@@ -1154,6 +1170,14 @@ function createSession({
|
|
|
1154
1170
|
| undefined;
|
|
1155
1171
|
const dynamicToolCalls = new Map<string, boolean>();
|
|
1156
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>();
|
|
1157
1181
|
const subscriptions: Array<() => void> = [];
|
|
1158
1182
|
const forward = (event: HarnessV1StreamPart) => {
|
|
1159
1183
|
if (event.type === 'text-start' || event.type === 'reasoning-start') {
|
|
@@ -1220,12 +1244,120 @@ function createSession({
|
|
|
1220
1244
|
subscriptions.push(
|
|
1221
1245
|
channel.on('acp-tool-call-candidate', event => {
|
|
1222
1246
|
try {
|
|
1247
|
+
const suppress =
|
|
1248
|
+
askUserQuestions?.isNativeToolCall?.({
|
|
1249
|
+
nativeToolCall: event.toolCall,
|
|
1250
|
+
}) === true;
|
|
1223
1251
|
dynamicToolCalls.set(
|
|
1224
1252
|
event.toolCall.toolCallId,
|
|
1225
1253
|
isMcpToolCall?.(event.toolCall) === true,
|
|
1226
1254
|
);
|
|
1255
|
+
channel.send({
|
|
1256
|
+
type: 'tool-result',
|
|
1257
|
+
toolCallId: event.requestId,
|
|
1258
|
+
output: { suppress },
|
|
1259
|
+
});
|
|
1227
1260
|
} catch (error) {
|
|
1228
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);
|
|
1229
1361
|
}
|
|
1230
1362
|
}),
|
|
1231
1363
|
);
|
|
@@ -1315,11 +1447,52 @@ function createSession({
|
|
|
1315
1447
|
});
|
|
1316
1448
|
},
|
|
1317
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
|
+
}
|
|
1318
1490
|
channel.send({
|
|
1319
1491
|
type: 'tool-result',
|
|
1320
1492
|
toolCallId: input.toolCallId,
|
|
1321
1493
|
output: input.output,
|
|
1322
1494
|
isError: input.isError,
|
|
1495
|
+
toolResult: input.toolResult,
|
|
1323
1496
|
});
|
|
1324
1497
|
},
|
|
1325
1498
|
done,
|
|
@@ -1682,6 +1855,78 @@ function isCompletePermissionModeMapping({
|
|
|
1682
1855
|
);
|
|
1683
1856
|
}
|
|
1684
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
|
+
|
|
1685
1930
|
function isPermissionModeMappingValue({
|
|
1686
1931
|
value,
|
|
1687
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
|
}
|
|
@@ -2,6 +2,7 @@ import type {
|
|
|
2
2
|
ActiveSessionMessage,
|
|
3
3
|
ToolCallUpdate,
|
|
4
4
|
} from '@agentclientprotocol/sdk';
|
|
5
|
+
import type { ACPToolCall } from '../../acp-tool-call';
|
|
5
6
|
|
|
6
7
|
type SessionUpdateMessage = Extract<
|
|
7
8
|
ActiveSessionMessage,
|
|
@@ -55,6 +56,8 @@ export function createHostToolCorrelation({
|
|
|
55
56
|
order: number;
|
|
56
57
|
}): void;
|
|
57
58
|
claimHostToolPermission(options: { toolCall: ToolCallUpdate }): boolean;
|
|
59
|
+
suppressToolCall(options: { toolCallId: string }): void;
|
|
60
|
+
getToolCall(options: { toolCallId: string }): ACPToolCall | undefined;
|
|
58
61
|
flush(): void;
|
|
59
62
|
removeInvocation(options: { token: string }): void;
|
|
60
63
|
close(): void;
|
|
@@ -63,6 +66,7 @@ export function createHostToolCorrelation({
|
|
|
63
66
|
const candidates = new Map<string, CorrelationCandidate>();
|
|
64
67
|
const suppressedToolCallIds = new Set<string>();
|
|
65
68
|
const releasedToolCallIds = new Set<string>();
|
|
69
|
+
const observedToolCalls = new Map<string, ACPToolCall>();
|
|
66
70
|
let nextCandidateOrder = 0;
|
|
67
71
|
let buffered: BufferedUpdate[] = [];
|
|
68
72
|
let flushTimer: ReturnType<typeof setTimeout> | undefined;
|
|
@@ -155,6 +159,13 @@ export function createHostToolCorrelation({
|
|
|
155
159
|
}
|
|
156
160
|
|
|
157
161
|
const toolCallId = message.update.toolCallId;
|
|
162
|
+
observedToolCalls.set(
|
|
163
|
+
toolCallId,
|
|
164
|
+
mergeObservedToolCall({
|
|
165
|
+
previous: observedToolCalls.get(toolCallId),
|
|
166
|
+
update: message.update,
|
|
167
|
+
}),
|
|
168
|
+
);
|
|
158
169
|
if (suppressedToolCallIds.has(toolCallId)) {
|
|
159
170
|
if (isTerminalToolUpdate(message)) {
|
|
160
171
|
suppressedToolCallIds.delete(toolCallId);
|
|
@@ -242,6 +253,8 @@ export function createHostToolCorrelation({
|
|
|
242
253
|
suppressToolCallUpdate({ toolCallId: toolCall.toolCallId });
|
|
243
254
|
return true;
|
|
244
255
|
},
|
|
256
|
+
suppressToolCall: suppressToolCallUpdate,
|
|
257
|
+
getToolCall: ({ toolCallId }) => observedToolCalls.get(toolCallId),
|
|
245
258
|
flush,
|
|
246
259
|
removeInvocation: ({ token }) => {
|
|
247
260
|
deleteInvocation({ invocations, token });
|
|
@@ -257,10 +270,31 @@ export function createHostToolCorrelation({
|
|
|
257
270
|
candidates.clear();
|
|
258
271
|
suppressedToolCallIds.clear();
|
|
259
272
|
releasedToolCallIds.clear();
|
|
273
|
+
observedToolCalls.clear();
|
|
260
274
|
},
|
|
261
275
|
};
|
|
262
276
|
}
|
|
263
277
|
|
|
278
|
+
function mergeObservedToolCall({
|
|
279
|
+
previous,
|
|
280
|
+
update,
|
|
281
|
+
}: {
|
|
282
|
+
previous: ACPToolCall | undefined;
|
|
283
|
+
update: ToolCallUpdate;
|
|
284
|
+
}): ACPToolCall {
|
|
285
|
+
return {
|
|
286
|
+
toolCallId: update.toolCallId,
|
|
287
|
+
title: update.title ?? previous?.title ?? `Tool ${update.toolCallId}`,
|
|
288
|
+
...(update.kind == null ? {} : { kind: update.kind }),
|
|
289
|
+
...(update.status == null ? {} : { status: update.status }),
|
|
290
|
+
...(update.content == null ? {} : { content: update.content }),
|
|
291
|
+
...(update.locations == null ? {} : { locations: update.locations }),
|
|
292
|
+
...(update.rawInput === undefined ? {} : { rawInput: update.rawInput }),
|
|
293
|
+
...(update.rawOutput === undefined ? {} : { rawOutput: update.rawOutput }),
|
|
294
|
+
...(update._meta === undefined ? {} : { _meta: update._meta }),
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
264
298
|
function hasPortableEvidence({
|
|
265
299
|
candidate,
|
|
266
300
|
invocation,
|