@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
|
@@ -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,
|
package/src/v1/bridge/index.ts
CHANGED
|
@@ -104,6 +104,13 @@ let coldRestorationMethod: ACPSessionRestorationMethod | undefined;
|
|
|
104
104
|
let activePermissionController:
|
|
105
105
|
| ReturnType<typeof createACPPermissionController>
|
|
106
106
|
| undefined;
|
|
107
|
+
let activeQuestionRequest:
|
|
108
|
+
| {
|
|
109
|
+
method: string;
|
|
110
|
+
turn: BridgeTurn;
|
|
111
|
+
emitStreamEvent: ReturnType<typeof createEmitStreamEvent>;
|
|
112
|
+
}
|
|
113
|
+
| undefined;
|
|
107
114
|
|
|
108
115
|
await runBridge<StartMessage>({
|
|
109
116
|
bridgeType,
|
|
@@ -252,15 +259,36 @@ async function runTurn(start: StartMessage, turn: BridgeTurn): Promise<void> {
|
|
|
252
259
|
const emitStreamEvent = createEmitStreamEvent({
|
|
253
260
|
emit: event => turn.emit(event as BridgeEvent),
|
|
254
261
|
emitToolCallCandidate: ({ toolCall }) => {
|
|
262
|
+
const requestId = crypto.randomUUID();
|
|
255
263
|
turn.emit({
|
|
256
264
|
type: 'acp-tool-call-candidate',
|
|
265
|
+
requestId,
|
|
257
266
|
toolCall,
|
|
258
267
|
});
|
|
268
|
+
void turn.requestToolResult(requestId).then(result => {
|
|
269
|
+
if (
|
|
270
|
+
result.output != null &&
|
|
271
|
+
typeof result.output === 'object' &&
|
|
272
|
+
'suppress' in result.output &&
|
|
273
|
+
result.output.suppress === true
|
|
274
|
+
) {
|
|
275
|
+
emitStreamEvent.suppressToolCall({
|
|
276
|
+
toolCallId: toolCall.toolCallId,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
});
|
|
259
280
|
},
|
|
260
281
|
builtinTools: start.builtinTools,
|
|
261
282
|
hostToolServerName: HOST_TOOL_MCP_SERVER_NAME,
|
|
262
283
|
hostTools: start.tools ?? [],
|
|
263
284
|
});
|
|
285
|
+
if (bridgeConfiguration.askUserQuestionsRequestMethod != null) {
|
|
286
|
+
activeQuestionRequest = {
|
|
287
|
+
method: bridgeConfiguration.askUserQuestionsRequestMethod,
|
|
288
|
+
turn,
|
|
289
|
+
emitStreamEvent,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
264
292
|
const permissionController = createACPPermissionController({
|
|
265
293
|
turn,
|
|
266
294
|
sessionId: activeSession.sessionId,
|
|
@@ -353,6 +381,9 @@ async function runTurn(start: StartMessage, turn: BridgeTurn): Promise<void> {
|
|
|
353
381
|
if (emitStreamEvent.message({ message })) return;
|
|
354
382
|
}
|
|
355
383
|
} finally {
|
|
384
|
+
if (activeQuestionRequest?.turn === turn) {
|
|
385
|
+
activeQuestionRequest = undefined;
|
|
386
|
+
}
|
|
356
387
|
permissionController.cancelAll();
|
|
357
388
|
activePermissionController = undefined;
|
|
358
389
|
activeHostToolRelay.unbindTurn({ turn: relayTurn });
|
|
@@ -458,7 +489,7 @@ async function ensureSession({
|
|
|
458
489
|
stream: acp.ndJsonStream(input, output),
|
|
459
490
|
});
|
|
460
491
|
streamCapture = capturedStream.capture;
|
|
461
|
-
|
|
492
|
+
let client = acp
|
|
462
493
|
.client({ name: clientApp.name })
|
|
463
494
|
.onRequest(
|
|
464
495
|
acp.methods.client.session.requestPermission,
|
|
@@ -475,8 +506,58 @@ async function ensureSession({
|
|
|
475
506
|
notification: params,
|
|
476
507
|
update: params.update,
|
|
477
508
|
});
|
|
478
|
-
})
|
|
479
|
-
|
|
509
|
+
});
|
|
510
|
+
if (bridgeConfiguration.askUserQuestionsRequestMethod != null) {
|
|
511
|
+
const method = bridgeConfiguration.askUserQuestionsRequestMethod;
|
|
512
|
+
client = client.onRequest<unknown, unknown>(
|
|
513
|
+
method,
|
|
514
|
+
value => value,
|
|
515
|
+
async ({ params }) => {
|
|
516
|
+
const active = activeQuestionRequest;
|
|
517
|
+
if (active == null || active.method !== method) {
|
|
518
|
+
throw acp.RequestError.methodNotFound(method);
|
|
519
|
+
}
|
|
520
|
+
const requestId = crypto.randomUUID();
|
|
521
|
+
const nativeToolCallId = getNativeQuestionToolCallId({ params });
|
|
522
|
+
active.turn.emit({
|
|
523
|
+
type: 'acp-question-request',
|
|
524
|
+
requestId,
|
|
525
|
+
nativeRequest: params,
|
|
526
|
+
...(nativeToolCallId == null
|
|
527
|
+
? {}
|
|
528
|
+
: {
|
|
529
|
+
nativeToolCall:
|
|
530
|
+
active.emitStreamEvent.getToolCall({
|
|
531
|
+
toolCallId: nativeToolCallId,
|
|
532
|
+
}) ?? undefined,
|
|
533
|
+
}),
|
|
534
|
+
});
|
|
535
|
+
const result = await active.turn.requestToolResult(requestId);
|
|
536
|
+
const classification = result.output as
|
|
537
|
+
| { type: 'unhandled' }
|
|
538
|
+
| { type: 'handled'; toolCallId: string }
|
|
539
|
+
| undefined;
|
|
540
|
+
if (classification?.type !== 'handled') {
|
|
541
|
+
throw acp.RequestError.methodNotFound(method);
|
|
542
|
+
}
|
|
543
|
+
active.emitStreamEvent.suppressToolCall({
|
|
544
|
+
toolCallId: nativeToolCallId ?? classification.toolCallId,
|
|
545
|
+
});
|
|
546
|
+
try {
|
|
547
|
+
const nativeResult = await active.turn.requestToolResult(
|
|
548
|
+
classification.toolCallId,
|
|
549
|
+
);
|
|
550
|
+
return nativeResult.output;
|
|
551
|
+
} finally {
|
|
552
|
+
active.turn.emit({
|
|
553
|
+
type: 'acp-question-resolved',
|
|
554
|
+
requestId,
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
},
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
connection = client.connect(capturedStream.stream);
|
|
480
561
|
|
|
481
562
|
const initializeRequest = createACPInitializeRequest({
|
|
482
563
|
protocolVersion: acp.PROTOCOL_VERSION,
|
|
@@ -627,6 +708,19 @@ async function ensureSession({
|
|
|
627
708
|
return { initialHostToolCatalogRefreshRequired: tools.length > 0 };
|
|
628
709
|
}
|
|
629
710
|
|
|
711
|
+
function getNativeQuestionToolCallId({
|
|
712
|
+
params,
|
|
713
|
+
}: {
|
|
714
|
+
params: unknown;
|
|
715
|
+
}): string | undefined {
|
|
716
|
+
if (!isRecord(params)) return undefined;
|
|
717
|
+
if (typeof params.toolCallId === 'string') return params.toolCallId;
|
|
718
|
+
const nestedParams = params.params;
|
|
719
|
+
return isRecord(nestedParams) && typeof nestedParams.toolCallId === 'string'
|
|
720
|
+
? nestedParams.toolCallId
|
|
721
|
+
: undefined;
|
|
722
|
+
}
|
|
723
|
+
|
|
630
724
|
function createExternalMcpServers({
|
|
631
725
|
mcpServers,
|
|
632
726
|
initialization,
|
|
@@ -68,6 +68,7 @@ export function createACPStreamTranslator({
|
|
|
68
68
|
output: unknown;
|
|
69
69
|
isError?: boolean;
|
|
70
70
|
}) => void;
|
|
71
|
+
getToolCall: (options: { toolCallId: string }) => ACPToolCall | undefined;
|
|
71
72
|
} {
|
|
72
73
|
let openBlock:
|
|
73
74
|
| {
|
|
@@ -215,6 +216,7 @@ export function createACPStreamTranslator({
|
|
|
215
216
|
builtinTools,
|
|
216
217
|
builtinToolsByName,
|
|
217
218
|
});
|
|
219
|
+
emitToolCallCandidate?.({ toolCall: createACPToolCall({ state }) });
|
|
218
220
|
if (
|
|
219
221
|
!forceEmit &&
|
|
220
222
|
builtin?.inputSchema != null &&
|
|
@@ -238,7 +240,6 @@ export function createACPStreamTranslator({
|
|
|
238
240
|
programmaticName,
|
|
239
241
|
toolCallId: state.toolCallId,
|
|
240
242
|
});
|
|
241
|
-
emitToolCallCandidate?.({ toolCall: createACPToolCall({ state }) });
|
|
242
243
|
}
|
|
243
244
|
emit({
|
|
244
245
|
type: 'tool-call',
|
|
@@ -381,6 +382,10 @@ export function createACPStreamTranslator({
|
|
|
381
382
|
close: closeBlock,
|
|
382
383
|
hostToolCall,
|
|
383
384
|
hostToolResult,
|
|
385
|
+
getToolCall: ({ toolCallId }) => {
|
|
386
|
+
const state = toolStates.get(toolCallId);
|
|
387
|
+
return state == null ? undefined : createACPToolCall({ state });
|
|
388
|
+
},
|
|
384
389
|
finish: response => {
|
|
385
390
|
if (finished) return;
|
|
386
391
|
finished = true;
|