@ai-sdk/harness-acp 1.0.38 → 1.0.40
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 +24 -0
- package/README.md +4 -3
- package/dist/bridge/host-tool-mcp.mjs +1 -1
- package/dist/bridge/index.mjs +556 -43
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/index.d.ts +56 -6
- package/dist/index.js +280 -72
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/acp-harness.ts +51 -9
- package/src/index.ts +2 -0
- package/src/v1/acp-v1-bridge-protocol.ts +19 -0
- package/src/v1/acp-v1-harness.ts +307 -19
- package/src/v1/acp-v1-settings.ts +42 -0
- package/src/v1/acp-v1-skills.ts +4 -65
- package/src/v1/bridge/acp-v1-bridge-environment.ts +16 -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/host-tool-mcp-definition.ts +69 -0
- package/src/v1/bridge/host-tool-mcp-http.ts +182 -0
- package/src/v1/bridge/host-tool-relay.ts +87 -7
- package/src/v1/bridge/index.ts +111 -24
- package/src/v1/bridge/instruction-mapping.ts +12 -0
- package/src/v1/bridge/stream-translator.ts +41 -2
- package/src/v1/index.ts +2 -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.40",
|
|
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.102",
|
|
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,15 @@ 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
|
+
/**
|
|
78
|
+
* Transport used for the harness-owned MCP server that exposes host tools to
|
|
79
|
+
* the ACP implementation. Defaults to `stdio`. Set this to `http` for
|
|
80
|
+
* implementations that only accept HTTP or SSE MCP servers from the client,
|
|
81
|
+
* which requires the implementation to advertise
|
|
82
|
+
* `agentCapabilities.mcpCapabilities.http`.
|
|
83
|
+
*/
|
|
84
|
+
readonly hostToolMcpTransport?: ACPV1Settings['hostToolMcpTransport'];
|
|
85
|
+
readonly askUserQuestions?: TAskUserQuestions;
|
|
64
86
|
readonly permissionModeMapping?: ACPV1Settings['permissionModeMapping'];
|
|
65
87
|
readonly session?: ACPV1Settings['session'];
|
|
66
88
|
readonly mintBridgeToken?: ACPV1Settings['mintBridgeToken'];
|
|
@@ -117,9 +139,22 @@ const acpResumeStateSchema = z.object({
|
|
|
117
139
|
|
|
118
140
|
type ACPBridgeCoords = z.infer<typeof acpBridgeCoordsSchema>;
|
|
119
141
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
142
|
+
type ACPHarnessTools<
|
|
143
|
+
TBuiltinTools extends ToolSet,
|
|
144
|
+
TAskUserQuestions,
|
|
145
|
+
> = TBuiltinTools &
|
|
146
|
+
([TAskUserQuestions] extends [undefined]
|
|
147
|
+
? {}
|
|
148
|
+
: {
|
|
149
|
+
askUserQuestions: typeof HARNESS_V1_BUILTIN_TOOLS.askUserQuestions;
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
export function createACP<
|
|
153
|
+
TBuiltinTools extends ToolSet = {},
|
|
154
|
+
TAskUserQuestions extends ACPAskUserQuestionsSettings | undefined = undefined,
|
|
155
|
+
>(
|
|
156
|
+
settings: ACPHarnessSettings<TBuiltinTools, TAskUserQuestions>,
|
|
157
|
+
): HarnessV1<ACPHarnessTools<TBuiltinTools, TAskUserQuestions>> {
|
|
123
158
|
if (
|
|
124
159
|
(settings.credentialEnv == null) !==
|
|
125
160
|
(settings.credentialBrokering == null)
|
|
@@ -143,10 +178,17 @@ export function createACP<TBuiltinTools extends ToolSet = {}>(
|
|
|
143
178
|
switch (version) {
|
|
144
179
|
case 'v1': {
|
|
145
180
|
const clientApp = settings.clientApp ?? ACP_CLIENT_APP;
|
|
181
|
+
const builtinTools =
|
|
182
|
+
settings.askUserQuestions == null
|
|
183
|
+
? (settings.builtinTools ?? (ACP_BUILTIN_TOOLS as TBuiltinTools))
|
|
184
|
+
: {
|
|
185
|
+
...(settings.builtinTools ??
|
|
186
|
+
(ACP_BUILTIN_TOOLS as TBuiltinTools)),
|
|
187
|
+
askUserQuestions: HARNESS_V1_BUILTIN_TOOLS.askUserQuestions,
|
|
188
|
+
};
|
|
146
189
|
return createACPV1({
|
|
147
190
|
settings,
|
|
148
|
-
builtinTools
|
|
149
|
-
settings.builtinTools ?? (ACP_BUILTIN_TOOLS as TBuiltinTools),
|
|
191
|
+
builtinTools,
|
|
150
192
|
port: settings.port,
|
|
151
193
|
portEndpoint: settings.portEndpoint,
|
|
152
194
|
startupTimeoutMs: settings.startupTimeoutMs,
|
|
@@ -154,7 +196,7 @@ export function createACP<TBuiltinTools extends ToolSet = {}>(
|
|
|
154
196
|
lifecycleStateSchema: acpResumeStateSchema satisfies z.ZodType<{
|
|
155
197
|
bridge?: ACPBridgeCoords;
|
|
156
198
|
}>,
|
|
157
|
-
})
|
|
199
|
+
}) as HarnessV1<ACPHarnessTools<TBuiltinTools, TAskUserQuestions>>;
|
|
158
200
|
}
|
|
159
201
|
default:
|
|
160
202
|
throw new Error(
|
package/src/index.ts
CHANGED
|
@@ -20,8 +20,10 @@ export type {
|
|
|
20
20
|
ACPToolKind,
|
|
21
21
|
} from './acp-tool-call';
|
|
22
22
|
export type {
|
|
23
|
+
ACPAskUserQuestionsSettings,
|
|
23
24
|
ACPCredentialBrokering,
|
|
24
25
|
ACPAuthentication,
|
|
26
|
+
ACPHostToolMCPTransport,
|
|
25
27
|
ACPInstallCommandSource,
|
|
26
28
|
ACPInstructionMapping,
|
|
27
29
|
ACPModelMapping,
|
|
@@ -113,6 +113,10 @@ const instructionMappingSchema: z.ZodType<ACPInstructionMapping> =
|
|
|
113
113
|
variable: z.string().min(1),
|
|
114
114
|
path: instructionMappingPathSchema,
|
|
115
115
|
}),
|
|
116
|
+
z.object({
|
|
117
|
+
type: z.literal('filesystem'),
|
|
118
|
+
path: z.string().min(1),
|
|
119
|
+
}),
|
|
116
120
|
]);
|
|
117
121
|
|
|
118
122
|
type ACPSerializableOutputSchemaMapping = {
|
|
@@ -199,6 +203,7 @@ const coldRestoreSchema = z.object({
|
|
|
199
203
|
|
|
200
204
|
const acpToolCallCandidateSchema = z.object({
|
|
201
205
|
type: z.literal('acp-tool-call-candidate'),
|
|
206
|
+
requestId: z.string(),
|
|
202
207
|
toolCall: z.custom<ACPToolCall>(
|
|
203
208
|
value =>
|
|
204
209
|
value != null &&
|
|
@@ -208,9 +213,23 @@ const acpToolCallCandidateSchema = z.object({
|
|
|
208
213
|
),
|
|
209
214
|
});
|
|
210
215
|
|
|
216
|
+
const acpQuestionRequestSchema = z.object({
|
|
217
|
+
type: z.literal('acp-question-request'),
|
|
218
|
+
requestId: z.string(),
|
|
219
|
+
nativeRequest: z.unknown(),
|
|
220
|
+
nativeToolCall: acpToolCallCandidateSchema.shape.toolCall.optional(),
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
const acpQuestionResolvedSchema = z.object({
|
|
224
|
+
type: z.literal('acp-question-resolved'),
|
|
225
|
+
requestId: z.string(),
|
|
226
|
+
});
|
|
227
|
+
|
|
211
228
|
export const outboundMessageSchema = z.union([
|
|
212
229
|
harnessV1BridgeOutboundMessageSchema,
|
|
213
230
|
acpToolCallCandidateSchema,
|
|
231
|
+
acpQuestionRequestSchema,
|
|
232
|
+
acpQuestionResolvedSchema,
|
|
214
233
|
]);
|
|
215
234
|
export type OutboundMessage = z.infer<typeof outboundMessageSchema>;
|
|
216
235
|
|
package/src/v1/acp-v1-harness.ts
CHANGED
|
@@ -32,11 +32,14 @@ import {
|
|
|
32
32
|
warnCredentialBrokeringUnavailable,
|
|
33
33
|
waitForBridgeReady,
|
|
34
34
|
withBridgeToken,
|
|
35
|
+
writeInstructions,
|
|
36
|
+
writeSkills,
|
|
35
37
|
} from '@ai-sdk/harness/utils';
|
|
36
38
|
import {
|
|
37
39
|
asSchema,
|
|
38
40
|
type Experimental_SandboxProcess,
|
|
39
41
|
type Experimental_SandboxSession as SandboxSession,
|
|
42
|
+
type ToolResultPart,
|
|
40
43
|
type ToolSet,
|
|
41
44
|
} from '@ai-sdk/provider-utils';
|
|
42
45
|
import { WebSocket } from 'ws';
|
|
@@ -81,6 +84,7 @@ import {
|
|
|
81
84
|
prependACPInstructionGuidance,
|
|
82
85
|
} from './acp-v1-prompt';
|
|
83
86
|
import type {
|
|
87
|
+
ACPAskUserQuestionsSettings,
|
|
84
88
|
ACPInstructionMapping,
|
|
85
89
|
ACPModelMapping,
|
|
86
90
|
ACPOutputSchemaMapping,
|
|
@@ -91,9 +95,11 @@ import type {
|
|
|
91
95
|
ACPV1Settings,
|
|
92
96
|
} from './acp-v1-settings';
|
|
93
97
|
import {
|
|
94
|
-
|
|
98
|
+
ACP_SKILL_NAME_PATTERN,
|
|
99
|
+
DEFAULT_ACP_SKILLS_DIRECTORY,
|
|
95
100
|
resolveACPPrivateSessionDirectory,
|
|
96
101
|
resolveACPSkillsDirectory,
|
|
102
|
+
validateACPSkills,
|
|
97
103
|
} from './acp-v1-skills';
|
|
98
104
|
|
|
99
105
|
const HARNESS_ID_REGEXP = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
@@ -322,6 +328,9 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
322
328
|
const requestTransformations = settings.credentialBrokering({
|
|
323
329
|
env: brokeringEnvironment,
|
|
324
330
|
sandboxEnv: sandboxImplementationEnvironment,
|
|
331
|
+
...(startOptions.headers == null
|
|
332
|
+
? {}
|
|
333
|
+
: { headers: startOptions.headers }),
|
|
325
334
|
});
|
|
326
335
|
if (requestTransformations.length > 0) {
|
|
327
336
|
await sandboxSession.addRequestTransformations(
|
|
@@ -370,17 +379,18 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
370
379
|
});
|
|
371
380
|
const privateSessionDir = resolveACPPrivateSessionDirectory({
|
|
372
381
|
sandboxHomeDir,
|
|
373
|
-
sessionWorkDir: workDir,
|
|
374
382
|
harnessId: settings.harnessId,
|
|
375
383
|
sessionId: startOptions.sessionId,
|
|
376
384
|
});
|
|
385
|
+
const implementationHomeDir =
|
|
386
|
+
implementation.source.type === 'install-command'
|
|
387
|
+
? `${resolvedImplementationDir}/home`
|
|
388
|
+
: sandboxHomeDir;
|
|
389
|
+
const skillsDir =
|
|
390
|
+
settings.skillsDirectory ?? DEFAULT_ACP_SKILLS_DIRECTORY;
|
|
377
391
|
const skillsDirectory = resolveACPSkillsDirectory({
|
|
378
|
-
implementationHomeDir
|
|
379
|
-
implementation.source.type === 'install-command'
|
|
380
|
-
? `${resolvedImplementationDir}/home`
|
|
381
|
-
: sandboxHomeDir,
|
|
392
|
+
implementationHomeDir,
|
|
382
393
|
skillsDirectory: settings.skillsDirectory,
|
|
383
|
-
sessionWorkDir: workDir,
|
|
384
394
|
});
|
|
385
395
|
const bridgeStateDir = `${privateSessionDir}/bridge`;
|
|
386
396
|
const report = startOptions.observability?.report;
|
|
@@ -442,6 +452,7 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
442
452
|
sessionMeta: settings.session?.meta,
|
|
443
453
|
instructionMapping: settings.instructionMapping,
|
|
444
454
|
outputSchemaMapping: settings.outputSchemaMapping,
|
|
455
|
+
askUserQuestions: settings.askUserQuestions,
|
|
445
456
|
debug: startOptions.observability?.debug,
|
|
446
457
|
implementationIdentity,
|
|
447
458
|
authenticationProfile,
|
|
@@ -456,7 +467,8 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
456
467
|
}),
|
|
457
468
|
instructionsFingerprint: lifecycleData.instructionsFingerprint,
|
|
458
469
|
sandbox: toolSafeSandboxSession,
|
|
459
|
-
|
|
470
|
+
homePath: implementationHomeDir,
|
|
471
|
+
skillsDir,
|
|
460
472
|
skillsDirectory,
|
|
461
473
|
acpSessionId: lifecycleData.acpSessionId,
|
|
462
474
|
bridgePort: coords.port,
|
|
@@ -615,6 +627,9 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
615
627
|
sandboxProviderEnvironment == null ? undefined : {},
|
|
616
628
|
sessionMeta: settings.session?.meta,
|
|
617
629
|
clientCapabilities: settings.clientCapabilities,
|
|
630
|
+
askUserQuestionsRequestMethod:
|
|
631
|
+
settings.askUserQuestions?.requestMethod,
|
|
632
|
+
hostToolMcpTransport: settings.hostToolMcpTransport,
|
|
618
633
|
}),
|
|
619
634
|
...sandboxProviderAuthenticationEnvironment,
|
|
620
635
|
BRIDGE_CHANNEL_TOKEN: token,
|
|
@@ -732,6 +747,7 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
732
747
|
sessionMeta: settings.session?.meta,
|
|
733
748
|
instructionMapping: settings.instructionMapping,
|
|
734
749
|
outputSchemaMapping: settings.outputSchemaMapping,
|
|
750
|
+
askUserQuestions: settings.askUserQuestions,
|
|
735
751
|
debug: startOptions.observability?.debug,
|
|
736
752
|
implementationIdentity,
|
|
737
753
|
authenticationProfile,
|
|
@@ -746,7 +762,8 @@ export function createACPV1<TBuiltinTools extends ToolSet = {}>({
|
|
|
746
762
|
}),
|
|
747
763
|
instructionsFingerprint: lifecycleData?.instructionsFingerprint,
|
|
748
764
|
sandbox: toolSafeSandboxSession,
|
|
749
|
-
|
|
765
|
+
homePath: implementationHomeDir,
|
|
766
|
+
skillsDir,
|
|
750
767
|
skillsDirectory,
|
|
751
768
|
acpSessionId: lifecycleData?.acpSessionId,
|
|
752
769
|
bridgePort: boundPort,
|
|
@@ -1045,6 +1062,7 @@ function createSession({
|
|
|
1045
1062
|
sessionMeta,
|
|
1046
1063
|
instructionMapping,
|
|
1047
1064
|
outputSchemaMapping,
|
|
1065
|
+
askUserQuestions,
|
|
1048
1066
|
debug,
|
|
1049
1067
|
implementationIdentity,
|
|
1050
1068
|
authenticationProfile,
|
|
@@ -1056,7 +1074,8 @@ function createSession({
|
|
|
1056
1074
|
initialGuidanceApplied: initialGuidanceAppliedAtStart,
|
|
1057
1075
|
instructionsFingerprint: instructionsFingerprintAtStart,
|
|
1058
1076
|
sandbox,
|
|
1059
|
-
|
|
1077
|
+
homePath,
|
|
1078
|
+
skillsDir,
|
|
1060
1079
|
skillsDirectory,
|
|
1061
1080
|
acpSessionId: acpSessionIdAtStart,
|
|
1062
1081
|
bridgePort,
|
|
@@ -1081,6 +1100,7 @@ function createSession({
|
|
|
1081
1100
|
sessionMeta: Readonly<Record<string, ACPSerializableValue>> | undefined;
|
|
1082
1101
|
instructionMapping: ACPInstructionMapping | undefined;
|
|
1083
1102
|
outputSchemaMapping: ACPOutputSchemaMapping | undefined;
|
|
1103
|
+
askUserQuestions: ACPAskUserQuestionsSettings | undefined;
|
|
1084
1104
|
debug: HarnessV1DebugConfig | undefined;
|
|
1085
1105
|
implementationIdentity: string;
|
|
1086
1106
|
authenticationProfile: ACPAuthenticationProfileIdentity;
|
|
@@ -1092,7 +1112,8 @@ function createSession({
|
|
|
1092
1112
|
initialGuidanceApplied: boolean;
|
|
1093
1113
|
instructionsFingerprint: string | undefined;
|
|
1094
1114
|
sandbox: SandboxSession;
|
|
1095
|
-
|
|
1115
|
+
homePath: string;
|
|
1116
|
+
skillsDir: string;
|
|
1096
1117
|
skillsDirectory: string;
|
|
1097
1118
|
acpSessionId: string | undefined;
|
|
1098
1119
|
bridgePort: number;
|
|
@@ -1114,6 +1135,14 @@ function createSession({
|
|
|
1114
1135
|
let instructionsFingerprint = instructionsFingerprintAtStart;
|
|
1115
1136
|
let latestACPSessionId = acpSessionIdAtStart;
|
|
1116
1137
|
let latestTurnStartConfig = turnStartConfigAtStart;
|
|
1138
|
+
const bufferedQuestionResults = new Map<
|
|
1139
|
+
string,
|
|
1140
|
+
{
|
|
1141
|
+
readonly output: unknown;
|
|
1142
|
+
readonly isError?: boolean;
|
|
1143
|
+
readonly toolResult: ToolResultPart;
|
|
1144
|
+
}
|
|
1145
|
+
>();
|
|
1117
1146
|
|
|
1118
1147
|
const markTurnFinished = () => {
|
|
1119
1148
|
turnInFlight = false;
|
|
@@ -1154,6 +1183,14 @@ function createSession({
|
|
|
1154
1183
|
| undefined;
|
|
1155
1184
|
const dynamicToolCalls = new Map<string, boolean>();
|
|
1156
1185
|
const toolCallClassificationErrors = new Map<string, unknown>();
|
|
1186
|
+
const activeQuestionRequests = new Map<
|
|
1187
|
+
string,
|
|
1188
|
+
{
|
|
1189
|
+
readonly requestId: string;
|
|
1190
|
+
readonly nativeRequest: unknown;
|
|
1191
|
+
}
|
|
1192
|
+
>();
|
|
1193
|
+
const questionToolCallIdsByRequestId = new Map<string, string>();
|
|
1157
1194
|
const subscriptions: Array<() => void> = [];
|
|
1158
1195
|
const forward = (event: HarnessV1StreamPart) => {
|
|
1159
1196
|
if (event.type === 'text-start' || event.type === 'reasoning-start') {
|
|
@@ -1220,12 +1257,120 @@ function createSession({
|
|
|
1220
1257
|
subscriptions.push(
|
|
1221
1258
|
channel.on('acp-tool-call-candidate', event => {
|
|
1222
1259
|
try {
|
|
1260
|
+
const suppress =
|
|
1261
|
+
askUserQuestions?.isNativeToolCall?.({
|
|
1262
|
+
nativeToolCall: event.toolCall,
|
|
1263
|
+
}) === true;
|
|
1223
1264
|
dynamicToolCalls.set(
|
|
1224
1265
|
event.toolCall.toolCallId,
|
|
1225
1266
|
isMcpToolCall?.(event.toolCall) === true,
|
|
1226
1267
|
);
|
|
1268
|
+
channel.send({
|
|
1269
|
+
type: 'tool-result',
|
|
1270
|
+
toolCallId: event.requestId,
|
|
1271
|
+
output: { suppress },
|
|
1272
|
+
});
|
|
1227
1273
|
} catch (error) {
|
|
1228
1274
|
toolCallClassificationErrors.set(event.toolCall.toolCallId, error);
|
|
1275
|
+
channel.send({
|
|
1276
|
+
type: 'tool-result',
|
|
1277
|
+
toolCallId: event.requestId,
|
|
1278
|
+
output: { suppress: false },
|
|
1279
|
+
});
|
|
1280
|
+
}
|
|
1281
|
+
}),
|
|
1282
|
+
);
|
|
1283
|
+
subscriptions.push(
|
|
1284
|
+
channel.on('acp-question-request', event => {
|
|
1285
|
+
if (askUserQuestions == null) {
|
|
1286
|
+
channel.send({
|
|
1287
|
+
type: 'tool-result',
|
|
1288
|
+
toolCallId: event.requestId,
|
|
1289
|
+
output: { type: 'unhandled' },
|
|
1290
|
+
});
|
|
1291
|
+
return;
|
|
1292
|
+
}
|
|
1293
|
+
try {
|
|
1294
|
+
const nativeToolCall = askUserQuestions.fromNativeRequest({
|
|
1295
|
+
nativeRequest: event.nativeRequest,
|
|
1296
|
+
nativeToolCall: event.nativeToolCall,
|
|
1297
|
+
});
|
|
1298
|
+
if (nativeToolCall == null) {
|
|
1299
|
+
channel.send({
|
|
1300
|
+
type: 'tool-result',
|
|
1301
|
+
toolCallId: event.requestId,
|
|
1302
|
+
output: { type: 'unhandled' },
|
|
1303
|
+
});
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1306
|
+
if (
|
|
1307
|
+
nativeToolCall.toolName !== 'askUserQuestions' ||
|
|
1308
|
+
nativeToolCall.providerExecuted !== false
|
|
1309
|
+
) {
|
|
1310
|
+
throw new Error(
|
|
1311
|
+
`${harnessId} ACP askUserQuestions.fromNativeRequest must return a client-executed askUserQuestions tool call.`,
|
|
1312
|
+
);
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
const toolCall = withNativeQuestionRequest({
|
|
1316
|
+
harnessId,
|
|
1317
|
+
nativeRequest: event.nativeRequest,
|
|
1318
|
+
toolCall: nativeToolCall,
|
|
1319
|
+
});
|
|
1320
|
+
const bufferedResult = takeBufferedQuestionResult({
|
|
1321
|
+
bufferedQuestionResults,
|
|
1322
|
+
toolCallId: toolCall.toolCallId,
|
|
1323
|
+
nativeRequest: event.nativeRequest,
|
|
1324
|
+
matchesNativeRequest: askUserQuestions.matchesNativeRequest,
|
|
1325
|
+
harnessId,
|
|
1326
|
+
});
|
|
1327
|
+
activeQuestionRequests.set(toolCall.toolCallId, {
|
|
1328
|
+
requestId: event.requestId,
|
|
1329
|
+
nativeRequest: event.nativeRequest,
|
|
1330
|
+
});
|
|
1331
|
+
questionToolCallIdsByRequestId.set(
|
|
1332
|
+
event.requestId,
|
|
1333
|
+
toolCall.toolCallId,
|
|
1334
|
+
);
|
|
1335
|
+
channel.send({
|
|
1336
|
+
type: 'tool-result',
|
|
1337
|
+
toolCallId: event.requestId,
|
|
1338
|
+
output: {
|
|
1339
|
+
type: 'handled',
|
|
1340
|
+
toolCallId: toolCall.toolCallId,
|
|
1341
|
+
},
|
|
1342
|
+
});
|
|
1343
|
+
|
|
1344
|
+
if (bufferedResult == null) {
|
|
1345
|
+
forward(toolCall);
|
|
1346
|
+
return;
|
|
1347
|
+
}
|
|
1348
|
+
channel.send({
|
|
1349
|
+
type: 'tool-result',
|
|
1350
|
+
toolCallId: toolCall.toolCallId,
|
|
1351
|
+
output: askUserQuestions.toNativeResponse({
|
|
1352
|
+
nativeRequest: event.nativeRequest,
|
|
1353
|
+
toolResult: bufferedResult.toolResult,
|
|
1354
|
+
}),
|
|
1355
|
+
isError: bufferedResult.isError,
|
|
1356
|
+
toolResult: bufferedResult.toolResult,
|
|
1357
|
+
});
|
|
1358
|
+
} catch (error) {
|
|
1359
|
+
closeForwardedBlock();
|
|
1360
|
+
forward({ type: 'error', error });
|
|
1361
|
+
try {
|
|
1362
|
+
channel.send({ type: 'abort' });
|
|
1363
|
+
} catch {}
|
|
1364
|
+
settle({ error });
|
|
1365
|
+
}
|
|
1366
|
+
}),
|
|
1367
|
+
);
|
|
1368
|
+
subscriptions.push(
|
|
1369
|
+
channel.on('acp-question-resolved', event => {
|
|
1370
|
+
const toolCallId = questionToolCallIdsByRequestId.get(event.requestId);
|
|
1371
|
+
if (toolCallId != null) {
|
|
1372
|
+
activeQuestionRequests.delete(toolCallId);
|
|
1373
|
+
questionToolCallIdsByRequestId.delete(event.requestId);
|
|
1229
1374
|
}
|
|
1230
1375
|
}),
|
|
1231
1376
|
);
|
|
@@ -1315,11 +1460,52 @@ function createSession({
|
|
|
1315
1460
|
});
|
|
1316
1461
|
},
|
|
1317
1462
|
submitToolResult: async input => {
|
|
1463
|
+
if (
|
|
1464
|
+
askUserQuestions != null &&
|
|
1465
|
+
input.toolResult?.toolName === 'askUserQuestions'
|
|
1466
|
+
) {
|
|
1467
|
+
const activeRequest = activeQuestionRequests.get(input.toolCallId);
|
|
1468
|
+
if (activeRequest == null) {
|
|
1469
|
+
const previousNativeRequest =
|
|
1470
|
+
input.toolResult.providerOptions?.[harnessId]?.nativeRequest;
|
|
1471
|
+
if (!lossyRerun && previousNativeRequest !== undefined) {
|
|
1472
|
+
channel.send({
|
|
1473
|
+
type: 'tool-result',
|
|
1474
|
+
toolCallId: input.toolCallId,
|
|
1475
|
+
output: askUserQuestions.toNativeResponse({
|
|
1476
|
+
nativeRequest: previousNativeRequest,
|
|
1477
|
+
toolResult: input.toolResult,
|
|
1478
|
+
}),
|
|
1479
|
+
isError: input.isError,
|
|
1480
|
+
toolResult: input.toolResult,
|
|
1481
|
+
});
|
|
1482
|
+
return;
|
|
1483
|
+
}
|
|
1484
|
+
bufferedQuestionResults.set(input.toolCallId, {
|
|
1485
|
+
output: input.output,
|
|
1486
|
+
...(input.isError == null ? {} : { isError: input.isError }),
|
|
1487
|
+
toolResult: input.toolResult,
|
|
1488
|
+
});
|
|
1489
|
+
return;
|
|
1490
|
+
}
|
|
1491
|
+
channel.send({
|
|
1492
|
+
type: 'tool-result',
|
|
1493
|
+
toolCallId: input.toolCallId,
|
|
1494
|
+
output: askUserQuestions.toNativeResponse({
|
|
1495
|
+
nativeRequest: activeRequest.nativeRequest,
|
|
1496
|
+
toolResult: input.toolResult,
|
|
1497
|
+
}),
|
|
1498
|
+
isError: input.isError,
|
|
1499
|
+
toolResult: input.toolResult,
|
|
1500
|
+
});
|
|
1501
|
+
return;
|
|
1502
|
+
}
|
|
1318
1503
|
channel.send({
|
|
1319
1504
|
type: 'tool-result',
|
|
1320
1505
|
toolCallId: input.toolCallId,
|
|
1321
1506
|
output: input.output,
|
|
1322
1507
|
isError: input.isError,
|
|
1508
|
+
toolResult: input.toolResult,
|
|
1323
1509
|
});
|
|
1324
1510
|
},
|
|
1325
1511
|
done,
|
|
@@ -1398,15 +1584,41 @@ function createSession({
|
|
|
1398
1584
|
skills: ReadonlyArray<HarnessV1Skill>;
|
|
1399
1585
|
abortSignal?: AbortSignal;
|
|
1400
1586
|
}): Promise<void> => {
|
|
1401
|
-
|
|
1587
|
+
validateACPSkills({ skills });
|
|
1588
|
+
await writeSkills({
|
|
1402
1589
|
sandbox,
|
|
1403
|
-
|
|
1404
|
-
|
|
1590
|
+
homePath,
|
|
1591
|
+
skillsDir,
|
|
1405
1592
|
skills,
|
|
1406
1593
|
abortSignal,
|
|
1594
|
+
skillNamePattern: ACP_SKILL_NAME_PATTERN,
|
|
1595
|
+
invalidSkillNameMessage: ({ name }) =>
|
|
1596
|
+
`Invalid ACP skill name ${JSON.stringify(name)}: expected a kebab-case slug.`,
|
|
1597
|
+
invalidSkillFilePathMessage: ({ skillName, filePath }) =>
|
|
1598
|
+
`Invalid ACP skill file path ${JSON.stringify(filePath)} for skill ${JSON.stringify(
|
|
1599
|
+
skillName,
|
|
1600
|
+
)}: expected a relative POSIX path without traversal.`,
|
|
1407
1601
|
});
|
|
1408
1602
|
};
|
|
1409
1603
|
|
|
1604
|
+
const synchronizeInstructions = async ({
|
|
1605
|
+
instructions,
|
|
1606
|
+
abortSignal,
|
|
1607
|
+
}: {
|
|
1608
|
+
instructions: string | undefined;
|
|
1609
|
+
abortSignal?: AbortSignal;
|
|
1610
|
+
}): Promise<void> => {
|
|
1611
|
+
if (instructionMapping?.type === 'filesystem') {
|
|
1612
|
+
await writeInstructions({
|
|
1613
|
+
sandbox,
|
|
1614
|
+
homePath,
|
|
1615
|
+
instructionsFile: instructionMapping.path,
|
|
1616
|
+
instructions,
|
|
1617
|
+
abortSignal,
|
|
1618
|
+
});
|
|
1619
|
+
}
|
|
1620
|
+
};
|
|
1621
|
+
|
|
1410
1622
|
return {
|
|
1411
1623
|
sessionId,
|
|
1412
1624
|
isResume,
|
|
@@ -1415,6 +1627,10 @@ function createSession({
|
|
|
1415
1627
|
skills: options.skills,
|
|
1416
1628
|
abortSignal: options.abortSignal,
|
|
1417
1629
|
});
|
|
1630
|
+
await synchronizeInstructions({
|
|
1631
|
+
instructions: options.instructions,
|
|
1632
|
+
abortSignal: options.abortSignal,
|
|
1633
|
+
});
|
|
1418
1634
|
if (options.responseFormat?.type === 'json') {
|
|
1419
1635
|
if (options.responseFormat.schema == null) {
|
|
1420
1636
|
throw unsupported({
|
|
@@ -1477,6 +1693,7 @@ function createSession({
|
|
|
1477
1693
|
channel.send({
|
|
1478
1694
|
type: 'start',
|
|
1479
1695
|
prompt:
|
|
1696
|
+
instructionMapping?.type !== 'filesystem' &&
|
|
1480
1697
|
instructionsFingerprint !== nextInstructionsFingerprint &&
|
|
1481
1698
|
(instructionMapping == null || initialGuidanceApplied)
|
|
1482
1699
|
? prependACPInstructionGuidance({
|
|
@@ -1519,6 +1736,10 @@ function createSession({
|
|
|
1519
1736
|
skills: options.skills,
|
|
1520
1737
|
abortSignal: options.abortSignal,
|
|
1521
1738
|
});
|
|
1739
|
+
await synchronizeInstructions({
|
|
1740
|
+
instructions: options.instructions,
|
|
1741
|
+
abortSignal: options.abortSignal,
|
|
1742
|
+
});
|
|
1522
1743
|
if (options.responseFormat?.type === 'json') {
|
|
1523
1744
|
if (options.responseFormat.schema == null) {
|
|
1524
1745
|
throw unsupported({
|
|
@@ -1604,11 +1825,6 @@ function createSession({
|
|
|
1604
1825
|
`${harnessId} ACP session ${sessionId} is stopped; cannot suspend.`,
|
|
1605
1826
|
);
|
|
1606
1827
|
}
|
|
1607
|
-
if (!turnInFlight) {
|
|
1608
|
-
throw new Error(
|
|
1609
|
-
`${harnessId} ACP session ${sessionId} has no in-flight turn to suspend.`,
|
|
1610
|
-
);
|
|
1611
|
-
}
|
|
1612
1828
|
stopped = true;
|
|
1613
1829
|
const lastSeenEventId = await channel.suspend();
|
|
1614
1830
|
return {
|
|
@@ -1682,6 +1898,78 @@ function isCompletePermissionModeMapping({
|
|
|
1682
1898
|
);
|
|
1683
1899
|
}
|
|
1684
1900
|
|
|
1901
|
+
function withNativeQuestionRequest({
|
|
1902
|
+
harnessId,
|
|
1903
|
+
nativeRequest,
|
|
1904
|
+
toolCall,
|
|
1905
|
+
}: {
|
|
1906
|
+
harnessId: string;
|
|
1907
|
+
nativeRequest: unknown;
|
|
1908
|
+
toolCall: Extract<HarnessV1StreamPart, { type: 'tool-call' }>;
|
|
1909
|
+
}): Extract<HarnessV1StreamPart, { type: 'tool-call' }> {
|
|
1910
|
+
const harnessMetadata = toolCall.providerMetadata?.[harnessId];
|
|
1911
|
+
return {
|
|
1912
|
+
...toolCall,
|
|
1913
|
+
providerMetadata: {
|
|
1914
|
+
...toolCall.providerMetadata,
|
|
1915
|
+
[harnessId]: {
|
|
1916
|
+
...(harnessMetadata ?? {}),
|
|
1917
|
+
nativeRequest,
|
|
1918
|
+
} as NonNullable<
|
|
1919
|
+
Extract<HarnessV1StreamPart, { type: 'tool-call' }>['providerMetadata']
|
|
1920
|
+
>[string],
|
|
1921
|
+
},
|
|
1922
|
+
};
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
function takeBufferedQuestionResult({
|
|
1926
|
+
bufferedQuestionResults,
|
|
1927
|
+
toolCallId,
|
|
1928
|
+
nativeRequest,
|
|
1929
|
+
matchesNativeRequest,
|
|
1930
|
+
harnessId,
|
|
1931
|
+
}: {
|
|
1932
|
+
bufferedQuestionResults: Map<
|
|
1933
|
+
string,
|
|
1934
|
+
{
|
|
1935
|
+
readonly output: unknown;
|
|
1936
|
+
readonly isError?: boolean;
|
|
1937
|
+
readonly toolResult: ToolResultPart;
|
|
1938
|
+
}
|
|
1939
|
+
>;
|
|
1940
|
+
toolCallId: string;
|
|
1941
|
+
nativeRequest: unknown;
|
|
1942
|
+
matchesNativeRequest:
|
|
1943
|
+
| ACPAskUserQuestionsSettings['matchesNativeRequest']
|
|
1944
|
+
| undefined;
|
|
1945
|
+
harnessId: string;
|
|
1946
|
+
}):
|
|
1947
|
+
| {
|
|
1948
|
+
readonly output: unknown;
|
|
1949
|
+
readonly isError?: boolean;
|
|
1950
|
+
readonly toolResult: ToolResultPart;
|
|
1951
|
+
}
|
|
1952
|
+
| undefined {
|
|
1953
|
+
const exact = bufferedQuestionResults.get(toolCallId);
|
|
1954
|
+
if (exact != null) {
|
|
1955
|
+
bufferedQuestionResults.delete(toolCallId);
|
|
1956
|
+
return exact;
|
|
1957
|
+
}
|
|
1958
|
+
if (matchesNativeRequest == null) return undefined;
|
|
1959
|
+
for (const [bufferedToolCallId, buffered] of bufferedQuestionResults) {
|
|
1960
|
+
const previousNativeRequest =
|
|
1961
|
+
buffered.toolResult.providerOptions?.[harnessId]?.nativeRequest;
|
|
1962
|
+
if (
|
|
1963
|
+
previousNativeRequest !== undefined &&
|
|
1964
|
+
matchesNativeRequest({ previousNativeRequest, nativeRequest })
|
|
1965
|
+
) {
|
|
1966
|
+
bufferedQuestionResults.delete(bufferedToolCallId);
|
|
1967
|
+
return buffered;
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
return undefined;
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1685
1973
|
function isPermissionModeMappingValue({
|
|
1686
1974
|
value,
|
|
1687
1975
|
}: {
|