@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.
@@ -230,6 +230,7 @@ async function runBridge(options) {
230
230
  }
231
231
  }
232
232
  const pendingToolResults = /* @__PURE__ */ new Map();
233
+ const bufferedToolResults = [];
233
234
  const pendingToolApprovals = /* @__PURE__ */ new Map();
234
235
  const writeBridgeMeta = async (state) => {
235
236
  try {
@@ -409,9 +410,23 @@ async function runBridge(options) {
409
410
  const userMessages = createBridgeUserMessageQueue({ respond: emit });
410
411
  const turn = {
411
412
  emit,
412
- requestToolResult: (toolCallId) => new Promise((resolve) => {
413
- pendingToolResults.set(toolCallId, resolve);
414
- }),
413
+ requestToolResult: (requestInput) => {
414
+ const request = typeof requestInput === "string" ? { toolCallId: requestInput } : requestInput;
415
+ const bufferedIndex = bufferedToolResults.findIndex(
416
+ (buffered) => buffered.toolCallId === request.toolCallId || request.matches?.(buffered.result) === true
417
+ );
418
+ if (bufferedIndex >= 0) {
419
+ return Promise.resolve(
420
+ bufferedToolResults.splice(bufferedIndex, 1)[0].result
421
+ );
422
+ }
423
+ return new Promise((resolve) => {
424
+ pendingToolResults.set(request.toolCallId, {
425
+ resolve,
426
+ matches: request.matches
427
+ });
428
+ });
429
+ },
415
430
  requestToolApproval: (approvalId) => new Promise((resolve) => {
416
431
  pendingToolApprovals.set(approvalId, resolve);
417
432
  }),
@@ -453,10 +468,25 @@ async function runBridge(options) {
453
468
  return;
454
469
  }
455
470
  case "tool-result": {
456
- const resolver = pendingToolResults.get(msg.toolCallId);
457
- if (resolver) {
458
- pendingToolResults.delete(msg.toolCallId);
459
- resolver({ output: msg.output, isError: msg.isError });
471
+ const result = {
472
+ output: msg.output,
473
+ isError: msg.isError,
474
+ toolResult: msg.toolResult
475
+ };
476
+ const exactPending = pendingToolResults.get(msg.toolCallId);
477
+ const matchingPending = exactPending == null ? Array.from(pendingToolResults.entries()).find(
478
+ ([, pending2]) => pending2.matches?.(result) === true
479
+ ) : void 0;
480
+ const pending = exactPending ?? matchingPending?.[1];
481
+ const pendingId = exactPending != null ? msg.toolCallId : matchingPending?.[0];
482
+ if (pending != null && pendingId != null) {
483
+ pendingToolResults.delete(pendingId);
484
+ pending.resolve(result);
485
+ } else {
486
+ bufferedToolResults.push({
487
+ toolCallId: msg.toolCallId,
488
+ result
489
+ });
460
490
  }
461
491
  return;
462
492
  }
@@ -634,7 +664,6 @@ function serialiseError(err) {
634
664
  import * as acp5 from "@agentclientprotocol/sdk";
635
665
  import { spawn } from "child_process";
636
666
  import { readFile, writeFile as writeFile2 } from "fs/promises";
637
- import { fileURLToPath } from "url";
638
667
  import { Readable, Writable } from "stream";
639
668
  import { argv, env as processEnv } from "process";
640
669
 
@@ -665,6 +694,10 @@ var profileValueSchema = z.lazy(
665
694
  z.record(z.string(), profileValueSchema)
666
695
  ])
667
696
  );
697
+ var hostToolMcpTransportSchema = z.enum([
698
+ "stdio",
699
+ "http"
700
+ ]);
668
701
  var serializableRecordSchema = z.record(z.string(), serializableValueSchema);
669
702
  var profileRecordSchema = z.record(z.string(), profileValueSchema);
670
703
  var authenticationSchema = z.object({
@@ -684,7 +717,9 @@ var bridgeConfigurationSchema = z.object({
684
717
  providerAuthentication: providerAuthenticationSchema.optional(),
685
718
  providerEnvironment: z.record(z.string(), z.string()).optional(),
686
719
  sessionMeta: serializableRecordSchema.optional(),
687
- clientCapabilities: serializableRecordSchema.optional()
720
+ clientCapabilities: serializableRecordSchema.optional(),
721
+ askUserQuestionsRequestMethod: z.string().optional(),
722
+ hostToolMcpTransport: hostToolMcpTransportSchema.optional()
688
723
  });
689
724
  async function readACPBridgeEnvironment({
690
725
  env
@@ -1154,6 +1189,7 @@ function createACPStreamTranslator({
1154
1189
  builtinTools,
1155
1190
  builtinToolsByName
1156
1191
  });
1192
+ emitToolCallCandidate?.({ toolCall: createACPToolCall({ state }) });
1157
1193
  if (!forceEmit && builtin?.inputSchema != null && state.values.status !== "completed" && state.values.status !== "failed" && !hasRequiredBuiltinToolInput({
1158
1194
  rawInput: state.values.rawInput,
1159
1195
  inputSchema: builtin.inputSchema
@@ -1168,7 +1204,6 @@ function createACPStreamTranslator({
1168
1204
  programmaticName,
1169
1205
  toolCallId: state.toolCallId
1170
1206
  });
1171
- emitToolCallCandidate?.({ toolCall: createACPToolCall({ state }) });
1172
1207
  }
1173
1208
  emit({
1174
1209
  type: "tool-call",
@@ -1277,6 +1312,10 @@ function createACPStreamTranslator({
1277
1312
  close: closeBlock,
1278
1313
  hostToolCall,
1279
1314
  hostToolResult,
1315
+ getToolCall: ({ toolCallId }) => {
1316
+ const state = toolStates.get(toolCallId);
1317
+ return state == null ? void 0 : createACPToolCall({ state });
1318
+ },
1280
1319
  finish: (response) => {
1281
1320
  if (finished) return;
1282
1321
  finished = true;
@@ -1382,7 +1421,24 @@ function resolveBuiltinTool({
1382
1421
  const schemaMatches = builtinTools.filter(
1383
1422
  (tool) => matchesBuiltinToolInput({ rawInput, inputSchema: tool.inputSchema })
1384
1423
  );
1385
- return schemaMatches.length === 1 ? schemaMatches[0] : void 0;
1424
+ if (schemaMatches.length <= 1) return schemaMatches[0];
1425
+ return findMostSpecificSchemaMatch({ rawInput, schemaMatches });
1426
+ }
1427
+ function findMostSpecificSchemaMatch({
1428
+ rawInput,
1429
+ schemaMatches
1430
+ }) {
1431
+ if (!isRecord3(rawInput)) return void 0;
1432
+ const rawInputKeys = Object.keys(rawInput);
1433
+ const coverageOf = (tool) => {
1434
+ const properties = isRecord3(tool.inputSchema) ? isRecord3(tool.inputSchema.properties) ? tool.inputSchema.properties : {} : {};
1435
+ return rawInputKeys.filter((key) => key in properties).length;
1436
+ };
1437
+ const highestCoverage = Math.max(...schemaMatches.map(coverageOf));
1438
+ const mostSpecificMatches = schemaMatches.filter(
1439
+ (tool) => coverageOf(tool) === highestCoverage
1440
+ );
1441
+ return mostSpecificMatches.length === 1 ? mostSpecificMatches[0] : void 0;
1386
1442
  }
1387
1443
  function findBuiltinToolByTitle({
1388
1444
  title,
@@ -1731,6 +1787,7 @@ function createHostToolCorrelation({
1731
1787
  const candidates = /* @__PURE__ */ new Map();
1732
1788
  const suppressedToolCallIds = /* @__PURE__ */ new Set();
1733
1789
  const releasedToolCallIds = /* @__PURE__ */ new Set();
1790
+ const observedToolCalls = /* @__PURE__ */ new Map();
1734
1791
  let nextCandidateOrder = 0;
1735
1792
  let buffered = [];
1736
1793
  let flushTimer;
@@ -1807,6 +1864,13 @@ function createHostToolCorrelation({
1807
1864
  return;
1808
1865
  }
1809
1866
  const toolCallId = message.update.toolCallId;
1867
+ observedToolCalls.set(
1868
+ toolCallId,
1869
+ mergeObservedToolCall({
1870
+ previous: observedToolCalls.get(toolCallId),
1871
+ update: message.update
1872
+ })
1873
+ );
1810
1874
  if (suppressedToolCallIds.has(toolCallId)) {
1811
1875
  if (isTerminalToolUpdate(message)) {
1812
1876
  suppressedToolCallIds.delete(toolCallId);
@@ -1890,6 +1954,8 @@ function createHostToolCorrelation({
1890
1954
  suppressToolCallUpdate({ toolCallId: toolCall.toolCallId });
1891
1955
  return true;
1892
1956
  },
1957
+ suppressToolCall: suppressToolCallUpdate,
1958
+ getToolCall: ({ toolCallId }) => observedToolCalls.get(toolCallId),
1893
1959
  flush,
1894
1960
  removeInvocation: ({ token }) => {
1895
1961
  deleteInvocation({ invocations, token });
@@ -1905,9 +1971,26 @@ function createHostToolCorrelation({
1905
1971
  candidates.clear();
1906
1972
  suppressedToolCallIds.clear();
1907
1973
  releasedToolCallIds.clear();
1974
+ observedToolCalls.clear();
1908
1975
  }
1909
1976
  };
1910
1977
  }
1978
+ function mergeObservedToolCall({
1979
+ previous,
1980
+ update
1981
+ }) {
1982
+ return {
1983
+ toolCallId: update.toolCallId,
1984
+ title: update.title ?? previous?.title ?? `Tool ${update.toolCallId}`,
1985
+ ...update.kind == null ? {} : { kind: update.kind },
1986
+ ...update.status == null ? {} : { status: update.status },
1987
+ ...update.content == null ? {} : { content: update.content },
1988
+ ...update.locations == null ? {} : { locations: update.locations },
1989
+ ...update.rawInput === void 0 ? {} : { rawInput: update.rawInput },
1990
+ ...update.rawOutput === void 0 ? {} : { rawOutput: update.rawOutput },
1991
+ ...update._meta === void 0 ? {} : { _meta: update._meta }
1992
+ };
1993
+ }
1911
1994
  function hasPortableEvidence({
1912
1995
  candidate,
1913
1996
  invocation
@@ -2218,7 +2301,9 @@ function createEmitStreamEvent({
2218
2301
  hostToolCall: translator.hostToolCall,
2219
2302
  hostToolResult: translator.hostToolResult,
2220
2303
  registerHostToolCorrelationInvocation: correlation.registerInvocation,
2221
- removeHostToolCorrelationInvocation: correlation.removeInvocation
2304
+ removeHostToolCorrelationInvocation: correlation.removeInvocation,
2305
+ suppressToolCall: correlation.suppressToolCall,
2306
+ getToolCall: ({ toolCallId }) => correlation.getToolCall({ toolCallId }) ?? translator.getToolCall({ toolCallId })
2222
2307
  };
2223
2308
  }
2224
2309
 
@@ -2236,6 +2321,14 @@ async function resolveACPInstructionConfiguration({
2236
2321
  if (instructionMapping == null || instructions == null || instructions.length === 0) {
2237
2322
  return { sessionMeta, environment: resolvedEnvironment };
2238
2323
  }
2324
+ if (instructionMapping.type === "filesystem") {
2325
+ if (typeof instructionMapping.path !== "string" || instructionMapping.path.trim().length === 0) {
2326
+ throw new Error(
2327
+ "ACP instruction mapping filesystem path must be a non-empty string."
2328
+ );
2329
+ }
2330
+ return { sessionMeta, environment: resolvedEnvironment };
2331
+ }
2239
2332
  assertSafePath({ path: instructionMapping.path });
2240
2333
  if (instructionMapping.type === "session-meta") {
2241
2334
  return {
@@ -2307,11 +2400,263 @@ function isRecord5(value) {
2307
2400
  }
2308
2401
 
2309
2402
  // src/v1/bridge/host-tool-relay.ts
2310
- import { randomBytes, timingSafeEqual } from "crypto";
2403
+ import { randomBytes, randomUUID as randomUUID3, timingSafeEqual } from "crypto";
2311
2404
  import { createServer } from "http";
2405
+
2406
+ // src/v1/bridge/host-tool-mcp-http.ts
2407
+ import { randomUUID as randomUUID2 } from "crypto";
2408
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
2409
+ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
2410
+
2411
+ // src/v1/bridge/host-tool-mcp-server.ts
2412
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2413
+ import {
2414
+ CallToolRequestSchema,
2415
+ ErrorCode,
2416
+ ListToolsRequestSchema,
2417
+ McpError
2418
+ } from "@modelcontextprotocol/sdk/types.js";
2419
+ var VERSION = true ? "1.0.40" : "0.0.0-test";
2420
+ function createHostToolMCPServer({
2421
+ tools,
2422
+ revision = 1,
2423
+ invoke,
2424
+ onListTools
2425
+ }) {
2426
+ let catalog = createCatalog({ revision, tools });
2427
+ let catalogAcknowledgmentError;
2428
+ const server = new Server(
2429
+ {
2430
+ name: "@ai-sdk/harness-acp-host-tools",
2431
+ version: VERSION
2432
+ },
2433
+ { capabilities: { tools: { listChanged: true } } }
2434
+ );
2435
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
2436
+ if (catalogAcknowledgmentError != null) {
2437
+ throw catalogAcknowledgmentError;
2438
+ }
2439
+ const current = catalog;
2440
+ if (onListTools != null) {
2441
+ setImmediate(() => {
2442
+ void onListTools({ revision: current.revision }).catch((error) => {
2443
+ catalogAcknowledgmentError = error;
2444
+ });
2445
+ });
2446
+ }
2447
+ return { tools: current.tools.map(toMCPTool) };
2448
+ });
2449
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2450
+ const current = catalog;
2451
+ const tool = current.byName.get(request.params.name);
2452
+ if (tool == null) {
2453
+ throw new McpError(
2454
+ ErrorCode.InvalidParams,
2455
+ `Unknown host tool: ${request.params.name}`
2456
+ );
2457
+ }
2458
+ const input = request.params.arguments ?? {};
2459
+ const result = await invoke({
2460
+ toolName: tool.name,
2461
+ input,
2462
+ catalogRevision: current.revision
2463
+ });
2464
+ return toCallToolResult({ result });
2465
+ });
2466
+ return {
2467
+ server,
2468
+ updateCatalog: async ({ revision: nextRevision, tools: nextTools }) => {
2469
+ catalog = createCatalog({
2470
+ revision: nextRevision,
2471
+ tools: nextTools
2472
+ });
2473
+ await server.sendToolListChanged();
2474
+ }
2475
+ };
2476
+ }
2477
+ function createCatalog({
2478
+ revision,
2479
+ tools
2480
+ }) {
2481
+ return {
2482
+ revision,
2483
+ tools: [...tools],
2484
+ byName: new Map(tools.map((tool) => [tool.name, tool]))
2485
+ };
2486
+ }
2487
+ function toMCPTool(tool) {
2488
+ return {
2489
+ name: tool.name,
2490
+ ...tool.description == null ? {} : { description: tool.description },
2491
+ inputSchema: requireObjectSchema({
2492
+ toolName: tool.name,
2493
+ schema: tool.inputSchema ?? { type: "object" }
2494
+ })
2495
+ };
2496
+ }
2497
+ function requireObjectSchema({
2498
+ toolName,
2499
+ schema
2500
+ }) {
2501
+ if (schema == null || typeof schema !== "object" || Array.isArray(schema) || Reflect.get(schema, "type") !== void 0 && Reflect.get(schema, "type") !== "object") {
2502
+ throw new Error(
2503
+ `Host tool ${toolName} must use an object JSON Schema for MCP.`
2504
+ );
2505
+ }
2506
+ return schema;
2507
+ }
2508
+ function toCallToolResult({
2509
+ result
2510
+ }) {
2511
+ return {
2512
+ content: [
2513
+ {
2514
+ type: "text",
2515
+ text: stringifyOutput({ output: result.output })
2516
+ }
2517
+ ],
2518
+ ...result.isError ? { isError: true } : {},
2519
+ _meta: {
2520
+ "ai-sdk-harness-acp-correlation": result.correlationToken
2521
+ }
2522
+ };
2523
+ }
2524
+ function stringifyOutput({ output }) {
2525
+ if (output === void 0) return "null";
2526
+ try {
2527
+ return JSON.stringify(output);
2528
+ } catch {
2529
+ return JSON.stringify({
2530
+ error: "Host tool output could not be serialized as JSON."
2531
+ });
2532
+ }
2533
+ }
2534
+
2535
+ // src/v1/bridge/host-tool-mcp-http.ts
2536
+ var HOST_TOOL_MCP_ENDPOINT_PATH = "/mcp";
2537
+ function createHostToolMCPHttpEndpoint({
2538
+ tools,
2539
+ revision,
2540
+ invoke,
2541
+ onListTools
2542
+ }) {
2543
+ let catalog = { revision, tools: [...tools] };
2544
+ const sessions = /* @__PURE__ */ new Map();
2545
+ let closed = false;
2546
+ async function openSession({
2547
+ request,
2548
+ response,
2549
+ body
2550
+ }) {
2551
+ const hostToolServer = createHostToolMCPServer({
2552
+ tools: catalog.tools,
2553
+ revision: catalog.revision,
2554
+ invoke,
2555
+ onListTools
2556
+ });
2557
+ const transport = new StreamableHTTPServerTransport({
2558
+ sessionIdGenerator: () => randomUUID2(),
2559
+ onsessioninitialized: (sessionId) => {
2560
+ sessions.set(sessionId, { transport, hostToolServer });
2561
+ },
2562
+ onsessionclosed: (sessionId) => {
2563
+ sessions.delete(sessionId);
2564
+ }
2565
+ });
2566
+ transport.onclose = () => {
2567
+ const { sessionId } = transport;
2568
+ if (sessionId != null) sessions.delete(sessionId);
2569
+ };
2570
+ await hostToolServer.server.connect(transport);
2571
+ await transport.handleRequest(request, response, body);
2572
+ }
2573
+ return {
2574
+ handleRequest: async ({ request, response, body }) => {
2575
+ if (closed) {
2576
+ respondWithJSONRPCError({
2577
+ response,
2578
+ status: 503,
2579
+ code: -32e3,
2580
+ message: "The host tool MCP endpoint is closed."
2581
+ });
2582
+ return;
2583
+ }
2584
+ const sessionId = readSessionId({ request });
2585
+ if (sessionId != null) {
2586
+ const session2 = sessions.get(sessionId);
2587
+ if (session2 == null) {
2588
+ respondWithJSONRPCError({
2589
+ response,
2590
+ status: 404,
2591
+ code: -32001,
2592
+ message: "Unknown host tool MCP session."
2593
+ });
2594
+ return;
2595
+ }
2596
+ await session2.transport.handleRequest(request, response, body);
2597
+ return;
2598
+ }
2599
+ if (request.method !== "POST" || !isInitializeRequest(body)) {
2600
+ respondWithJSONRPCError({
2601
+ response,
2602
+ status: 400,
2603
+ code: -32e3,
2604
+ message: "Host tool MCP requests without a session id must be an initialize request."
2605
+ });
2606
+ return;
2607
+ }
2608
+ await openSession({ request, response, body });
2609
+ },
2610
+ updateCatalog: async ({ revision: nextRevision, tools: nextTools }) => {
2611
+ catalog = { revision: nextRevision, tools: [...nextTools] };
2612
+ await Promise.all(
2613
+ [...sessions.values()].map(
2614
+ (session2) => session2.hostToolServer.updateCatalog({
2615
+ revision: nextRevision,
2616
+ tools: nextTools
2617
+ })
2618
+ )
2619
+ );
2620
+ },
2621
+ close: async () => {
2622
+ if (closed) return;
2623
+ closed = true;
2624
+ const live = [...sessions.values()];
2625
+ sessions.clear();
2626
+ await Promise.all(
2627
+ live.map((session2) => session2.hostToolServer.server.close())
2628
+ );
2629
+ }
2630
+ };
2631
+ }
2632
+ function readSessionId({
2633
+ request
2634
+ }) {
2635
+ const value = request.headers["mcp-session-id"];
2636
+ const sessionId = Array.isArray(value) ? value[0] : value;
2637
+ return sessionId == null || sessionId.length === 0 ? void 0 : sessionId;
2638
+ }
2639
+ function respondWithJSONRPCError({
2640
+ response,
2641
+ status,
2642
+ code,
2643
+ message
2644
+ }) {
2645
+ response.writeHead(status, { "content-type": "application/json" });
2646
+ response.end(
2647
+ JSON.stringify({
2648
+ jsonrpc: "2.0",
2649
+ error: { code, message },
2650
+ id: null
2651
+ })
2652
+ );
2653
+ }
2654
+
2655
+ // src/v1/bridge/host-tool-relay.ts
2312
2656
  async function startHostToolRelay({
2313
2657
  tools,
2314
- serverName
2658
+ serverName,
2659
+ mcpTransport = "stdio"
2315
2660
  }) {
2316
2661
  const state = {
2317
2662
  tools: [...tools],
@@ -2326,8 +2671,44 @@ async function startHostToolRelay({
2326
2671
  let activeTurn;
2327
2672
  let invocationOrder = 0;
2328
2673
  let closePromise;
2674
+ const mcpEndpoint = mcpTransport === "http" ? createHostToolMCPHttpEndpoint({
2675
+ tools: state.tools,
2676
+ revision: state.revision,
2677
+ invoke: ({ toolName, input, catalogRevision }) => handleInvocation({
2678
+ body: {
2679
+ requestId: randomUUID3(),
2680
+ toolName,
2681
+ input,
2682
+ catalogRevision
2683
+ },
2684
+ state,
2685
+ serverName,
2686
+ turn: activeTurn,
2687
+ nextInvocationOrder: () => ++invocationOrder
2688
+ }),
2689
+ onListTools: async ({ revision }) => {
2690
+ acknowledgeCatalog({ state, revision });
2691
+ }
2692
+ }) : void 0;
2329
2693
  const server = createServer(async (request, response) => {
2330
2694
  try {
2695
+ if (mcpEndpoint != null && request.url === HOST_TOOL_MCP_ENDPOINT_PATH) {
2696
+ if (!credentialsMatch({
2697
+ expected: credential,
2698
+ actual: request.headers.authorization
2699
+ })) {
2700
+ throw new RelayRequestError({
2701
+ status: 401,
2702
+ message: "Invalid host tool relay credential."
2703
+ });
2704
+ }
2705
+ await mcpEndpoint.handleRequest({
2706
+ request,
2707
+ response,
2708
+ ...request.method === "POST" ? { body: await readJSONBody({ request }) } : {}
2709
+ });
2710
+ return;
2711
+ }
2331
2712
  const result = await handleRequest({
2332
2713
  request,
2333
2714
  credential,
@@ -2339,6 +2720,10 @@ async function startHostToolRelay({
2339
2720
  response.writeHead(200, { "content-type": "application/json" });
2340
2721
  response.end(JSON.stringify(result));
2341
2722
  } catch (error) {
2723
+ if (response.headersSent) {
2724
+ response.end();
2725
+ return;
2726
+ }
2342
2727
  const status = error instanceof RelayRequestError ? error.status : 500;
2343
2728
  response.writeHead(status, { "content-type": "application/json" });
2344
2729
  response.end(
@@ -2352,6 +2737,9 @@ async function startHostToolRelay({
2352
2737
  const address = server.address();
2353
2738
  return {
2354
2739
  url: `http://127.0.0.1:${address.port}/invoke`,
2740
+ ...mcpEndpoint == null ? {} : {
2741
+ mcpUrl: `http://127.0.0.1:${address.port}${HOST_TOOL_MCP_ENDPOINT_PATH}`
2742
+ },
2355
2743
  credential,
2356
2744
  bindTurn: ({ turn }) => {
2357
2745
  if (activeTurn != null && activeTurn !== turn) {
@@ -2371,6 +2759,10 @@ async function startHostToolRelay({
2371
2759
  state.fingerprint = fingerprint;
2372
2760
  state.revision += 1;
2373
2761
  resolveCatalogChanges({ state });
2762
+ void mcpEndpoint?.updateCatalog({
2763
+ revision: state.revision,
2764
+ tools: state.tools
2765
+ });
2374
2766
  return { changed: true, revision: state.revision };
2375
2767
  },
2376
2768
  waitForCatalogRefresh: ({ revision, timeoutMs }) => waitForCatalogRefresh({ state, revision, timeoutMs }),
@@ -2379,7 +2771,10 @@ async function startHostToolRelay({
2379
2771
  state.closed = true;
2380
2772
  resolveCatalogChanges({ state });
2381
2773
  resolveRefreshWaiters({ state, closing: true });
2382
- closePromise = closeServer({ server });
2774
+ closePromise = (async () => {
2775
+ await mcpEndpoint?.close();
2776
+ await closeServer({ server });
2777
+ })();
2383
2778
  return closePromise;
2384
2779
  }
2385
2780
  };
@@ -2457,13 +2852,16 @@ function handleCatalogSeen({
2457
2852
  message: "Invalid host tool catalog acknowledgment."
2458
2853
  });
2459
2854
  }
2460
- state.servedRevision = Math.max(
2461
- state.servedRevision,
2462
- body.revision
2463
- );
2464
- resolveRefreshWaiters({ state, closing: false });
2855
+ acknowledgeCatalog({ state, revision: body.revision });
2465
2856
  return { acknowledged: true };
2466
2857
  }
2858
+ function acknowledgeCatalog({
2859
+ state,
2860
+ revision
2861
+ }) {
2862
+ state.servedRevision = Math.max(state.servedRevision, revision);
2863
+ resolveRefreshWaiters({ state, closing: false });
2864
+ }
2467
2865
  async function handleInvocation({
2468
2866
  body,
2469
2867
  state,
@@ -2662,6 +3060,55 @@ var RelayRequestError = class extends Error {
2662
3060
  }
2663
3061
  };
2664
3062
 
3063
+ // src/v1/bridge/host-tool-mcp-definition.ts
3064
+ import { fileURLToPath } from "url";
3065
+ import { execPath } from "process";
3066
+ function createHostToolMcpServerDefinition({
3067
+ mcpTransport,
3068
+ relay,
3069
+ serverName,
3070
+ catalogPath,
3071
+ initialization,
3072
+ harnessId
3073
+ }) {
3074
+ if (mcpTransport === "stdio") {
3075
+ return {
3076
+ name: serverName,
3077
+ command: execPath,
3078
+ args: [fileURLToPath(new URL("./host-tool-mcp.mjs", import.meta.url))],
3079
+ env: [
3080
+ {
3081
+ name: "AI_SDK_ACP_HOST_TOOLS_FILE",
3082
+ value: catalogPath
3083
+ },
3084
+ {
3085
+ name: "AI_SDK_ACP_HOST_TOOL_RELAY_URL",
3086
+ value: relay.url
3087
+ },
3088
+ {
3089
+ name: "AI_SDK_ACP_HOST_TOOL_RELAY_CREDENTIAL",
3090
+ value: relay.credential
3091
+ }
3092
+ ]
3093
+ };
3094
+ }
3095
+ if (initialization.agentCapabilities?.mcpCapabilities?.http !== true) {
3096
+ throw new HarnessBridgeCapabilityUnsupportedError({
3097
+ harnessId,
3098
+ message: "This harness exposes host tools through an HTTP MCP server, but the ACP agent does not advertise support for HTTP MCP servers."
3099
+ });
3100
+ }
3101
+ if (relay.mcpUrl == null) {
3102
+ throw new Error("The host tool MCP HTTP endpoint is unavailable.");
3103
+ }
3104
+ return {
3105
+ type: "http",
3106
+ name: serverName,
3107
+ url: relay.mcpUrl,
3108
+ headers: [{ name: "Authorization", value: `Bearer ${relay.credential}` }]
3109
+ };
3110
+ }
3111
+
2665
3112
  // src/v1/bridge/refresh-host-tool-catalog.ts
2666
3113
  async function promptAndRefreshInitialHostToolCatalog({
2667
3114
  startPrompt,
@@ -3150,6 +3597,7 @@ var sessionConfigurationFailure;
3150
3597
  var historicalUpdatesSuppressed = false;
3151
3598
  var coldRestorationMethod;
3152
3599
  var activePermissionController;
3600
+ var activeQuestionRequest;
3153
3601
  await runBridge({
3154
3602
  bridgeType,
3155
3603
  bridgeStateDir,
@@ -3293,15 +3741,31 @@ async function runTurn(start, turn) {
3293
3741
  const emitStreamEvent = createEmitStreamEvent({
3294
3742
  emit: (event) => turn.emit(event),
3295
3743
  emitToolCallCandidate: ({ toolCall }) => {
3744
+ const requestId = crypto.randomUUID();
3296
3745
  turn.emit({
3297
3746
  type: "acp-tool-call-candidate",
3747
+ requestId,
3298
3748
  toolCall
3299
3749
  });
3750
+ void turn.requestToolResult(requestId).then((result) => {
3751
+ if (result.output != null && typeof result.output === "object" && "suppress" in result.output && result.output.suppress === true) {
3752
+ emitStreamEvent.suppressToolCall({
3753
+ toolCallId: toolCall.toolCallId
3754
+ });
3755
+ }
3756
+ });
3300
3757
  },
3301
3758
  builtinTools: start.builtinTools,
3302
3759
  hostToolServerName: HOST_TOOL_MCP_SERVER_NAME,
3303
3760
  hostTools: start.tools ?? []
3304
3761
  });
3762
+ if (bridgeConfiguration.askUserQuestionsRequestMethod != null) {
3763
+ activeQuestionRequest = {
3764
+ method: bridgeConfiguration.askUserQuestionsRequestMethod,
3765
+ turn,
3766
+ emitStreamEvent
3767
+ };
3768
+ }
3305
3769
  const permissionController = createACPPermissionController({
3306
3770
  turn,
3307
3771
  sessionId: activeSession.sessionId,
@@ -3391,6 +3855,9 @@ async function runTurn(start, turn) {
3391
3855
  if (emitStreamEvent.message({ message })) return;
3392
3856
  }
3393
3857
  } finally {
3858
+ if (activeQuestionRequest?.turn === turn) {
3859
+ activeQuestionRequest = void 0;
3860
+ }
3394
3861
  permissionController.cancelAll();
3395
3862
  activePermissionController = void 0;
3396
3863
  activeHostToolRelay.unbindTurn({ turn: relayTurn });
@@ -3483,7 +3950,7 @@ async function ensureSession({
3483
3950
  stream: acp5.ndJsonStream(input, output)
3484
3951
  });
3485
3952
  streamCapture = capturedStream.capture;
3486
- connection = acp5.client({ name: clientApp.name }).onRequest(
3953
+ let client2 = acp5.client({ name: clientApp.name }).onRequest(
3487
3954
  acp5.methods.client.session.requestPermission,
3488
3955
  ({ params }) => activePermissionController?.requestPermission(params) ?? {
3489
3956
  outcome: { outcome: "cancelled" }
@@ -3496,7 +3963,52 @@ async function ensureSession({
3496
3963
  notification: params,
3497
3964
  update: params.update
3498
3965
  });
3499
- }).connect(capturedStream.stream);
3966
+ });
3967
+ if (bridgeConfiguration.askUserQuestionsRequestMethod != null) {
3968
+ const method = bridgeConfiguration.askUserQuestionsRequestMethod;
3969
+ client2 = client2.onRequest(
3970
+ method,
3971
+ (value) => value,
3972
+ async ({ params }) => {
3973
+ const active = activeQuestionRequest;
3974
+ if (active == null || active.method !== method) {
3975
+ throw acp5.RequestError.methodNotFound(method);
3976
+ }
3977
+ const requestId = crypto.randomUUID();
3978
+ const nativeToolCallId = getNativeQuestionToolCallId({ params });
3979
+ active.turn.emit({
3980
+ type: "acp-question-request",
3981
+ requestId,
3982
+ nativeRequest: params,
3983
+ ...nativeToolCallId == null ? {} : {
3984
+ nativeToolCall: active.emitStreamEvent.getToolCall({
3985
+ toolCallId: nativeToolCallId
3986
+ }) ?? void 0
3987
+ }
3988
+ });
3989
+ const result = await active.turn.requestToolResult(requestId);
3990
+ const classification = result.output;
3991
+ if (classification?.type !== "handled") {
3992
+ throw acp5.RequestError.methodNotFound(method);
3993
+ }
3994
+ active.emitStreamEvent.suppressToolCall({
3995
+ toolCallId: nativeToolCallId ?? classification.toolCallId
3996
+ });
3997
+ try {
3998
+ const nativeResult = await active.turn.requestToolResult(
3999
+ classification.toolCallId
4000
+ );
4001
+ return nativeResult.output;
4002
+ } finally {
4003
+ active.turn.emit({
4004
+ type: "acp-question-resolved",
4005
+ requestId
4006
+ });
4007
+ }
4008
+ }
4009
+ );
4010
+ }
4011
+ connection = client2.connect(capturedStream.stream);
3500
4012
  const initializeRequest = createACPInitializeRequest({
3501
4013
  protocolVersion: acp5.PROTOCOL_VERSION,
3502
4014
  clientApp,
@@ -3525,33 +4037,26 @@ async function ensureSession({
3525
4037
  initialization
3526
4038
  });
3527
4039
  const tools = start.tools ?? [];
4040
+ const mcpTransport = bridgeConfiguration.hostToolMcpTransport ?? "stdio";
3528
4041
  const catalogPath = `${bridgeStateDir}/host-tools.json`;
3529
- await writeFile2(catalogPath, JSON.stringify(tools), { mode: 384 });
4042
+ if (mcpTransport === "stdio") {
4043
+ await writeFile2(catalogPath, JSON.stringify(tools), { mode: 384 });
4044
+ }
3530
4045
  hostToolRelay = await startHostToolRelay({
3531
4046
  tools,
3532
- serverName: HOST_TOOL_MCP_SERVER_NAME
4047
+ serverName: HOST_TOOL_MCP_SERVER_NAME,
4048
+ mcpTransport
3533
4049
  });
3534
4050
  const mcpServers = [
3535
4051
  ...externalMcpServers,
3536
- {
3537
- name: HOST_TOOL_MCP_SERVER_NAME,
3538
- command: process.execPath,
3539
- args: [fileURLToPath(new URL("./host-tool-mcp.mjs", import.meta.url))],
3540
- env: [
3541
- {
3542
- name: "AI_SDK_ACP_HOST_TOOLS_FILE",
3543
- value: catalogPath
3544
- },
3545
- {
3546
- name: "AI_SDK_ACP_HOST_TOOL_RELAY_URL",
3547
- value: hostToolRelay.url
3548
- },
3549
- {
3550
- name: "AI_SDK_ACP_HOST_TOOL_RELAY_CREDENTIAL",
3551
- value: hostToolRelay.credential
3552
- }
3553
- ]
3554
- }
4052
+ createHostToolMcpServerDefinition({
4053
+ mcpTransport,
4054
+ relay: hostToolRelay,
4055
+ serverName: HOST_TOOL_MCP_SERVER_NAME,
4056
+ catalogPath,
4057
+ initialization,
4058
+ harnessId: bridgeType
4059
+ })
3555
4060
  ];
3556
4061
  let createdSession;
3557
4062
  if (start.recoveryMode?.type === "lossy-rerun") {
@@ -3629,6 +4134,14 @@ async function ensureSession({
3629
4134
  sessionConfigurationFingerprint = fingerprint;
3630
4135
  return { initialHostToolCatalogRefreshRequired: tools.length > 0 };
3631
4136
  }
4137
+ function getNativeQuestionToolCallId({
4138
+ params
4139
+ }) {
4140
+ if (!isRecord7(params)) return void 0;
4141
+ if (typeof params.toolCallId === "string") return params.toolCallId;
4142
+ const nestedParams = params.params;
4143
+ return isRecord7(nestedParams) && typeof nestedParams.toolCallId === "string" ? nestedParams.toolCallId : void 0;
4144
+ }
3632
4145
  function createExternalMcpServers({
3633
4146
  mcpServers,
3634
4147
  initialization