@ai-sdk/harness-acp 0.0.0 → 1.0.1

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.
Files changed (49) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/LICENSE +13 -0
  3. package/README.md +69 -3
  4. package/dist/bridge/host-tool-mcp.mjs +302 -0
  5. package/dist/bridge/host-tool-mcp.mjs.map +1 -0
  6. package/dist/bridge/index.mjs +3317 -0
  7. package/dist/bridge/index.mjs.map +1 -0
  8. package/dist/bridge/package.json +12 -0
  9. package/dist/bridge/pnpm-lock.yaml +816 -0
  10. package/dist/index.d.ts +104 -0
  11. package/dist/index.js +2089 -0
  12. package/dist/index.js.map +1 -0
  13. package/package.json +64 -10
  14. package/src/acp-auth.ts +254 -0
  15. package/src/acp-bridge-protocol.ts +13 -0
  16. package/src/acp-harness.ts +107 -0
  17. package/src/index.ts +19 -0
  18. package/src/v1/acp-v1-bridge-protocol.ts +151 -0
  19. package/src/v1/acp-v1-harness.ts +1461 -0
  20. package/src/v1/acp-v1-lifecycle.ts +115 -0
  21. package/src/v1/acp-v1-prompt.ts +122 -0
  22. package/src/v1/acp-v1-settings.ts +97 -0
  23. package/src/v1/acp-v1-skills.ts +228 -0
  24. package/src/v1/acp-v1-turn-start-config.ts +93 -0
  25. package/src/v1/bridge/acp-diagnostics.ts +83 -0
  26. package/src/v1/bridge/acp-stream-capture.ts +152 -0
  27. package/src/v1/bridge/acp-v1-bridge-environment.ts +95 -0
  28. package/src/v1/bridge/agent-stderr-monitor.ts +52 -0
  29. package/src/v1/bridge/create-emit-stream-event.ts +97 -0
  30. package/src/v1/bridge/host-tool-correlation.ts +640 -0
  31. package/src/v1/bridge/host-tool-mcp-server.ts +180 -0
  32. package/src/v1/bridge/host-tool-mcp.ts +185 -0
  33. package/src/v1/bridge/host-tool-relay-client.ts +53 -0
  34. package/src/v1/bridge/host-tool-relay.ts +510 -0
  35. package/src/v1/bridge/index.ts +714 -0
  36. package/src/v1/bridge/package.json +12 -0
  37. package/src/v1/bridge/permission-controller.ts +152 -0
  38. package/src/v1/bridge/permission-mode.ts +163 -0
  39. package/src/v1/bridge/pnpm-lock.yaml +816 -0
  40. package/src/v1/bridge/profile-values.ts +97 -0
  41. package/src/v1/bridge/protocol-configuration.ts +146 -0
  42. package/src/v1/bridge/recovered-session.ts +145 -0
  43. package/src/v1/bridge/refresh-host-tool-catalog.ts +33 -0
  44. package/src/v1/bridge/session-lifecycle.ts +83 -0
  45. package/src/v1/bridge/stream-translator.ts +901 -0
  46. package/src/v1/implementation.ts +308 -0
  47. package/src/v1/index.ts +16 -0
  48. package/src/version.ts +6 -0
  49. package/index.js +0 -1
@@ -0,0 +1,3317 @@
1
+ // ../harness/dist/bridge/index.js
2
+ import { appendFile, mkdir, writeFile } from "fs/promises";
3
+ import { existsSync, readFileSync } from "fs";
4
+ import { env as procEnv, pid, stdout } from "process";
5
+ import { WebSocketServer } from "ws";
6
+ var name = "AI_HarnessBridgeCapabilityUnsupportedError";
7
+ var HarnessBridgeCapabilityUnsupportedError = class extends Error {
8
+ constructor({
9
+ message,
10
+ harnessId,
11
+ cause
12
+ }) {
13
+ super(message);
14
+ Object.defineProperty(this, "name", { value: name });
15
+ this.harnessId = harnessId;
16
+ this.cause = cause;
17
+ }
18
+ static isInstance(error) {
19
+ return error != null && typeof error === "object" && "name" in error && error.name === name && "message" in error && typeof error.message === "string";
20
+ }
21
+ };
22
+ var DEBUG_LEVEL_WEIGHT = {
23
+ error: 0,
24
+ warn: 1,
25
+ info: 2,
26
+ debug: 3,
27
+ trace: 4
28
+ };
29
+ function subsystemMatches(filters, subsystem) {
30
+ if (!filters || filters.length === 0) return true;
31
+ return filters.some(
32
+ (filter) => subsystem === filter || subsystem.startsWith(`${filter}.`)
33
+ );
34
+ }
35
+ function formatBridgeError(err) {
36
+ if (err instanceof Error) {
37
+ return { name: err.name, message: err.message, stack: err.stack };
38
+ }
39
+ if (typeof err === "string") {
40
+ return { message: err };
41
+ }
42
+ if (err !== null && typeof err === "object") {
43
+ try {
44
+ return { message: JSON.stringify(err) };
45
+ } catch {
46
+ }
47
+ }
48
+ return { message: String(err) };
49
+ }
50
+ function parseEnvList(value) {
51
+ if (!value) return void 0;
52
+ const items = value.split(",").map((item) => item.trim()).filter(Boolean);
53
+ return items.length > 0 ? items : void 0;
54
+ }
55
+ var ENV_TRUTHY = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
56
+ var WS_OPEN = 1;
57
+ async function runBridge(options) {
58
+ const { bridgeType: bridgeType2, bridgeStateDir: bridgeStateDir2, onStart, onStop, onDestroy } = options;
59
+ const expectedToken = options.token ?? procEnv.BRIDGE_CHANNEL_TOKEN ?? "";
60
+ const bridgeWsPort = options.port ?? parseInt(procEnv.BRIDGE_WS_PORT ?? "0", 10);
61
+ const bridgeMetaPath = `${bridgeStateDir2}/bridge-meta.json`;
62
+ const startConfigPath = `${bridgeStateDir2}/start-config.json`;
63
+ const rerunStartConfigPath = `${bridgeStateDir2}/rerun-start-config.json`;
64
+ const eventLogPath = `${bridgeStateDir2}/event-log.ndjson`;
65
+ try {
66
+ await mkdir(bridgeStateDir2, { recursive: true });
67
+ } catch {
68
+ }
69
+ let currentBoundPort = 0;
70
+ let currentTurnState = "init";
71
+ let activeSocket;
72
+ let activeSocketReadyForLiveEvents = false;
73
+ let isFirstTurn = true;
74
+ let turnAbort;
75
+ let currentUserMessages;
76
+ let debugConfig;
77
+ let consoleCaptureInstalled = false;
78
+ const envDebugEnabled = ENV_TRUTHY.has(
79
+ (procEnv.HARNESS_DEBUG ?? "").toLowerCase()
80
+ );
81
+ let seqCounter = 0;
82
+ let eventLog = [];
83
+ let diskBuffer = "";
84
+ let flushPromise = null;
85
+ const flushEventsToDisk = async () => {
86
+ while (diskBuffer.length > 0) {
87
+ const buf = diskBuffer;
88
+ diskBuffer = "";
89
+ await appendFile(eventLogPath, buf).catch(() => {
90
+ });
91
+ }
92
+ };
93
+ const scheduleEventFlush = () => {
94
+ if (flushPromise) return;
95
+ flushPromise = new Promise((resolve) => {
96
+ setImmediate(() => {
97
+ void flushEventsToDisk().finally(resolve);
98
+ });
99
+ }).finally(() => {
100
+ flushPromise = null;
101
+ if (diskBuffer.length > 0) {
102
+ scheduleEventFlush();
103
+ }
104
+ });
105
+ };
106
+ const flushPendingEventsToDisk = async () => {
107
+ if (diskBuffer.length > 0 && !flushPromise) {
108
+ scheduleEventFlush();
109
+ }
110
+ let inFlight = flushPromise;
111
+ while (inFlight) {
112
+ await inFlight;
113
+ inFlight = flushPromise;
114
+ }
115
+ };
116
+ const replayFromDisk = procEnv.BRIDGE_REPLAY_FROM_DISK === "1";
117
+ if (replayFromDisk && existsSync(eventLogPath)) {
118
+ try {
119
+ const lines = readFileSync(eventLogPath, "utf8").split("\n").map((line) => line.trim()).filter(Boolean);
120
+ eventLog = lines.map((line) => ({
121
+ seq: JSON.parse(line).seq,
122
+ line
123
+ }));
124
+ seqCounter = eventLog.at(-1)?.seq ?? 0;
125
+ } catch {
126
+ eventLog = [];
127
+ seqCounter = 0;
128
+ }
129
+ }
130
+ const pendingToolResults = /* @__PURE__ */ new Map();
131
+ const pendingToolApprovals = /* @__PURE__ */ new Map();
132
+ const writeBridgeMeta = async (state) => {
133
+ try {
134
+ await writeFile(
135
+ bridgeMetaPath,
136
+ JSON.stringify({
137
+ type: bridgeType2,
138
+ port: currentBoundPort,
139
+ state,
140
+ pid
141
+ })
142
+ );
143
+ } catch {
144
+ }
145
+ };
146
+ const writeStartConfig = async (start) => {
147
+ try {
148
+ const serialized = JSON.stringify(start);
149
+ await writeFile(startConfigPath, serialized);
150
+ if (!existsSync(rerunStartConfigPath)) {
151
+ await writeFile(rerunStartConfigPath, serialized);
152
+ }
153
+ } catch {
154
+ }
155
+ };
156
+ const sendControl = (msg) => {
157
+ if (activeSocket?.readyState === WS_OPEN) {
158
+ try {
159
+ activeSocket.send(JSON.stringify(msg));
160
+ } catch {
161
+ }
162
+ }
163
+ };
164
+ const emit = (event) => {
165
+ const seq = ++seqCounter;
166
+ const line = JSON.stringify({ ...event, seq });
167
+ eventLog.push({ seq, line });
168
+ diskBuffer += `${line}
169
+ `;
170
+ scheduleEventFlush();
171
+ if (activeSocketReadyForLiveEvents && activeSocket?.readyState === WS_OPEN) {
172
+ try {
173
+ activeSocket.send(line);
174
+ } catch {
175
+ }
176
+ }
177
+ };
178
+ const replay = (ws, afterSeq) => {
179
+ for (const entry of eventLog) {
180
+ if (entry.seq > afterSeq && ws.readyState === WS_OPEN) {
181
+ ws.send(entry.line);
182
+ }
183
+ }
184
+ };
185
+ const shouldEmitDebugEvent = (level, subsystem) => {
186
+ if (!debugConfig?.enabled) return false;
187
+ const threshold = debugConfig.level ?? "debug";
188
+ if (DEBUG_LEVEL_WEIGHT[level] > DEBUG_LEVEL_WEIGHT[threshold]) return false;
189
+ return subsystemMatches(debugConfig.subsystems, subsystem);
190
+ };
191
+ const rawStdoutWrite = process.stdout.write.bind(process.stdout);
192
+ const rawStderrWrite = process.stderr.write.bind(process.stderr);
193
+ const writeErrorToStderr = (input) => {
194
+ try {
195
+ const formatted = formatBridgeError(input.error);
196
+ rawStderrWrite(
197
+ `[harness:${bridgeType2}:error] ${input.message}: ${formatted.message}
198
+ `
199
+ );
200
+ if (formatted.stack) {
201
+ rawStderrWrite(`${formatted.stack}
202
+ `);
203
+ }
204
+ } catch {
205
+ }
206
+ };
207
+ const emitWarning = (input) => {
208
+ try {
209
+ for (const line of input.message.split("\n")) {
210
+ if (line.trim().length > 0) {
211
+ rawStderrWrite(`[harness:${bridgeType2}:warn] ${line}
212
+ `);
213
+ }
214
+ }
215
+ } catch {
216
+ }
217
+ };
218
+ const emitError = (input) => {
219
+ writeErrorToStderr({
220
+ message: input.message ?? "bridge error",
221
+ error: input.error
222
+ });
223
+ emit({ type: "error", error: serialiseError(input.error) });
224
+ };
225
+ const installConsoleCapture = () => {
226
+ if (consoleCaptureInstalled) return;
227
+ consoleCaptureInstalled = true;
228
+ const buffers = {
229
+ stdout: "",
230
+ stderr: ""
231
+ };
232
+ const patch = (stream, raw) => (chunk, encoding, cb) => {
233
+ if (debugConfig?.enabled) {
234
+ try {
235
+ const enc = typeof encoding === "string" ? encoding : "utf8";
236
+ const text = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString(
237
+ enc
238
+ );
239
+ const combined = buffers[stream] + text.replace(/\r\n/g, "\n");
240
+ const parts = combined.split("\n");
241
+ buffers[stream] = parts.pop() ?? "";
242
+ for (const line of parts) {
243
+ const trimmed = line.replace(/\s+$/, "");
244
+ if (trimmed) {
245
+ emit({
246
+ type: "sandbox-log",
247
+ source: bridgeType2,
248
+ stream,
249
+ line: trimmed
250
+ });
251
+ }
252
+ }
253
+ } catch {
254
+ }
255
+ }
256
+ return raw(
257
+ chunk,
258
+ encoding,
259
+ cb
260
+ );
261
+ };
262
+ process.stdout.write = patch(
263
+ "stdout",
264
+ rawStdoutWrite
265
+ );
266
+ process.stderr.write = patch(
267
+ "stderr",
268
+ rawStderrWrite
269
+ );
270
+ };
271
+ const handleInbound = async (msg, ws) => {
272
+ switch (msg.type) {
273
+ case "start": {
274
+ if (activeSocket !== ws) return;
275
+ activeSocketReadyForLiveEvents = true;
276
+ const firstTurn = isFirstTurn;
277
+ isFirstTurn = false;
278
+ eventLog = [];
279
+ diskBuffer = "";
280
+ void writeFile(eventLogPath, "").catch(() => {
281
+ });
282
+ turnAbort = new AbortController();
283
+ currentTurnState = "running";
284
+ void writeStartConfig(msg);
285
+ void writeBridgeMeta("running");
286
+ const startDebug = msg.debug;
287
+ debugConfig = {
288
+ enabled: startDebug?.enabled ?? envDebugEnabled,
289
+ level: startDebug?.level ?? procEnv.HARNESS_DEBUG_LEVEL,
290
+ subsystems: startDebug?.subsystems ?? parseEnvList(procEnv.HARNESS_DEBUG_SUBSYSTEMS)
291
+ };
292
+ if (debugConfig.enabled) {
293
+ installConsoleCapture();
294
+ }
295
+ const turn = {
296
+ emit,
297
+ requestToolResult: (toolCallId) => new Promise((resolve) => {
298
+ pendingToolResults.set(toolCallId, resolve);
299
+ }),
300
+ requestToolApproval: (approvalId) => new Promise((resolve) => {
301
+ pendingToolApprovals.set(approvalId, resolve);
302
+ }),
303
+ pendingUserMessages: [],
304
+ abortSignal: turnAbort.signal,
305
+ firstTurn,
306
+ bridgeLog: (input) => {
307
+ const level = input.level ?? "debug";
308
+ if (!shouldEmitDebugEvent(level, input.subsystem)) return;
309
+ emit({
310
+ type: "debug-event",
311
+ level,
312
+ subsystem: input.subsystem,
313
+ message: input.message,
314
+ ...input.attrs ? { attrs: input.attrs } : {},
315
+ ...input.error !== void 0 ? { error: formatBridgeError(input.error) } : {}
316
+ });
317
+ },
318
+ emitWarning,
319
+ emitError
320
+ };
321
+ currentUserMessages = turn.pendingUserMessages;
322
+ try {
323
+ await onStart(msg, turn);
324
+ } catch (err) {
325
+ emitError({ error: err, message: "bridge turn failed" });
326
+ } finally {
327
+ currentTurnState = "waiting";
328
+ void writeBridgeMeta("waiting");
329
+ }
330
+ return;
331
+ }
332
+ case "tool-result": {
333
+ const resolver = pendingToolResults.get(msg.toolCallId);
334
+ if (resolver) {
335
+ pendingToolResults.delete(msg.toolCallId);
336
+ resolver({ output: msg.output, isError: msg.isError });
337
+ }
338
+ return;
339
+ }
340
+ case "tool-approval-response": {
341
+ const resolver = pendingToolApprovals.get(msg.approvalId);
342
+ if (resolver) {
343
+ pendingToolApprovals.delete(msg.approvalId);
344
+ resolver({ approved: msg.approved, reason: msg.reason });
345
+ }
346
+ return;
347
+ }
348
+ case "user-message":
349
+ currentUserMessages?.push(msg.text);
350
+ return;
351
+ case "abort":
352
+ turnAbort?.abort();
353
+ return;
354
+ case "resume":
355
+ if (activeSocket !== ws) return;
356
+ replay(ws, msg.lastSeenEventId);
357
+ activeSocketReadyForLiveEvents = true;
358
+ return;
359
+ case "destroy":
360
+ currentTurnState = "done";
361
+ void writeBridgeMeta("done");
362
+ await onDestroy?.();
363
+ drainThenExit(ws, 1e3, "destroy");
364
+ return;
365
+ case "stop": {
366
+ currentTurnState = "done";
367
+ void writeBridgeMeta("done");
368
+ const data = await onStop?.() ?? {};
369
+ sendControl({ type: "bridge-stop", data });
370
+ drainThenExit(ws, 1e3, "stop");
371
+ return;
372
+ }
373
+ }
374
+ };
375
+ void writeBridgeMeta("init");
376
+ const wss = new WebSocketServer({ port: bridgeWsPort, host: "0.0.0.0" });
377
+ const exit = () => {
378
+ if (options.onExit) {
379
+ options.onExit();
380
+ return;
381
+ }
382
+ wss.close(() => process.exit(0));
383
+ setTimeout(() => process.exit(0), 1e3).unref();
384
+ };
385
+ const drainThenExit = (ws, code, reason) => {
386
+ const start = Date.now();
387
+ const tick = () => {
388
+ const drained = ws.bufferedAmount === 0 || ws.readyState !== WS_OPEN;
389
+ if (drained || Date.now() - start >= 5e3) {
390
+ void flushPendingEventsToDisk().finally(() => {
391
+ try {
392
+ ws.close(code, reason);
393
+ } finally {
394
+ exit();
395
+ }
396
+ });
397
+ return;
398
+ }
399
+ setTimeout(tick, 10).unref();
400
+ };
401
+ tick();
402
+ };
403
+ wss.on("listening", () => {
404
+ const addr = wss.address();
405
+ currentBoundPort = typeof addr === "object" && addr ? addr.port : 0;
406
+ currentTurnState = "waiting";
407
+ void writeBridgeMeta("waiting");
408
+ stdout.write(
409
+ JSON.stringify({
410
+ type: "bridge-ready",
411
+ port: currentBoundPort
412
+ }) + "\n"
413
+ );
414
+ options.onListening?.(currentBoundPort);
415
+ });
416
+ wss.on("connection", (ws, req) => {
417
+ const url = new URL(req.url ?? "/", "http://localhost");
418
+ if (url.searchParams.get("agent_bridge_token") !== expectedToken) {
419
+ ws.close(1008, "unauthorized");
420
+ return;
421
+ }
422
+ activeSocket = ws;
423
+ activeSocketReadyForLiveEvents = false;
424
+ sendControl({
425
+ type: "bridge-hello",
426
+ state: currentTurnState,
427
+ lastSeq: seqCounter
428
+ });
429
+ ws.on("message", (raw) => {
430
+ let parsed;
431
+ try {
432
+ const text = typeof raw === "string" ? raw : Buffer.from(raw).toString("utf8");
433
+ parsed = JSON.parse(text);
434
+ } catch (err) {
435
+ sendControl({
436
+ type: "error",
437
+ error: `protocol parse error: ${err.message}`
438
+ });
439
+ return;
440
+ }
441
+ void handleInbound(parsed, ws);
442
+ });
443
+ ws.on("close", () => {
444
+ if (activeSocket === ws) {
445
+ activeSocket = void 0;
446
+ activeSocketReadyForLiveEvents = false;
447
+ }
448
+ });
449
+ ws.on("error", () => {
450
+ });
451
+ });
452
+ process.on("uncaughtException", (err) => {
453
+ emitError({ error: err, message: "uncaught exception" });
454
+ });
455
+ process.on("unhandledRejection", (err) => {
456
+ emitError({ error: err, message: "unhandled rejection" });
457
+ });
458
+ await new Promise((resolve, reject) => {
459
+ if (wss.address() != null) {
460
+ resolve();
461
+ return;
462
+ }
463
+ wss.once("listening", resolve);
464
+ wss.once("error", reject);
465
+ });
466
+ return {
467
+ port: currentBoundPort,
468
+ close: () => new Promise((resolve) => {
469
+ wss.close(() => resolve());
470
+ })
471
+ };
472
+ }
473
+ function serialiseError(err) {
474
+ if (err instanceof Error) {
475
+ return { name: err.name, message: err.message, stack: err.stack };
476
+ }
477
+ return err;
478
+ }
479
+
480
+ // src/v1/bridge/index.ts
481
+ import * as acp4 from "@agentclientprotocol/sdk";
482
+ import { spawn } from "child_process";
483
+ import { readFile, writeFile as writeFile2 } from "fs/promises";
484
+ import { fileURLToPath } from "url";
485
+ import { Readable, Writable } from "stream";
486
+ import { argv, env as processEnv } from "process";
487
+
488
+ // src/v1/bridge/acp-v1-bridge-environment.ts
489
+ import { z } from "zod/v4";
490
+ var ACP_BRIDGE_CONFIGURATION_ENV = "AI_SDK_ACP_BRIDGE_CONFIGURATION";
491
+ var serializableValueSchema = z.json();
492
+ var profileValueSchema = z.lazy(
493
+ () => z.union([
494
+ z.string(),
495
+ z.number(),
496
+ z.boolean(),
497
+ z.null(),
498
+ z.object({
499
+ $source: z.enum([
500
+ "gateway-api-key",
501
+ "gateway-base-url",
502
+ "gateway-authorization",
503
+ "client-app",
504
+ "client-app-name",
505
+ "client-app-version"
506
+ ]),
507
+ prefix: z.string().optional(),
508
+ suffix: z.string().optional(),
509
+ ensureSuffix: z.string().optional()
510
+ }),
511
+ z.array(profileValueSchema),
512
+ z.record(z.string(), profileValueSchema)
513
+ ])
514
+ );
515
+ var serializableRecordSchema = z.record(z.string(), serializableValueSchema);
516
+ var profileRecordSchema = z.record(z.string(), profileValueSchema);
517
+ var authenticationSchema = z.object({
518
+ methodId: z.string(),
519
+ meta: serializableRecordSchema.optional(),
520
+ clientCapabilities: serializableRecordSchema.optional()
521
+ });
522
+ var providerAuthenticationSchema = z.discriminatedUnion("type", [
523
+ z.object({ type: z.literal("direct") }),
524
+ z.object({
525
+ type: z.literal("ai-gateway"),
526
+ env: profileRecordSchema
527
+ })
528
+ ]);
529
+ var bridgeConfigurationSchema = z.object({
530
+ authentication: authenticationSchema.optional(),
531
+ providerAuthentication: providerAuthenticationSchema.optional(),
532
+ sessionMeta: serializableRecordSchema.optional()
533
+ });
534
+ async function readACPBridgeEnvironment({
535
+ env
536
+ }) {
537
+ const serialized = env[ACP_BRIDGE_CONFIGURATION_ENV];
538
+ if (serialized == null) return {};
539
+ try {
540
+ const result = bridgeConfigurationSchema.safeParse(JSON.parse(serialized));
541
+ if (result.success) return result.data;
542
+ } catch {
543
+ }
544
+ throw new Error("ACP bridge configuration environment is invalid.");
545
+ }
546
+
547
+ // src/v1/bridge/profile-values.ts
548
+ function resolveACPProfileValue({
549
+ value,
550
+ gateway
551
+ }) {
552
+ if (Array.isArray(value)) {
553
+ return value.map((item) => resolveACPProfileValue({ value: item, gateway }));
554
+ }
555
+ if (value !== null && typeof value === "object") {
556
+ if (isValueSource(value)) {
557
+ const sourceValue = resolveSource({ source: value.$source, gateway });
558
+ const resolved = `${value.prefix ?? ""}${sourceValue}`;
559
+ return value.ensureSuffix == null ? `${resolved}${value.suffix ?? ""}` : ensureSuffix({
560
+ value: resolved,
561
+ suffix: value.ensureSuffix
562
+ });
563
+ }
564
+ return Object.fromEntries(
565
+ Object.entries(value).map(([key, item]) => [
566
+ key,
567
+ resolveACPProfileValue({ value: item, gateway })
568
+ ])
569
+ );
570
+ }
571
+ return value;
572
+ }
573
+ function isValueSource(value) {
574
+ return "$source" in value && typeof value.$source === "string";
575
+ }
576
+ function ensureSuffix({
577
+ value,
578
+ suffix
579
+ }) {
580
+ const normalized = value.replace(/\/+$/, "");
581
+ return normalized.endsWith(suffix) ? normalized : `${normalized}${suffix}`;
582
+ }
583
+ function resolveSource({
584
+ source,
585
+ gateway
586
+ }) {
587
+ switch (source) {
588
+ case "gateway-api-key":
589
+ return gateway.apiKey;
590
+ case "gateway-base-url":
591
+ return gateway.baseUrl;
592
+ case "gateway-authorization":
593
+ return `Bearer ${gateway.apiKey}`;
594
+ case "client-app":
595
+ return `${gateway.clientAppName}/${gateway.clientAppVersion}`;
596
+ case "client-app-name":
597
+ return gateway.clientAppName;
598
+ case "client-app-version":
599
+ return gateway.clientAppVersion;
600
+ }
601
+ }
602
+
603
+ // src/v1/bridge/protocol-configuration.ts
604
+ function createACPInitializeRequest({
605
+ protocolVersion,
606
+ clientApp,
607
+ authentication,
608
+ supportsBooleanSessionConfigOptions = false
609
+ }) {
610
+ let clientCapabilities = authentication?.clientCapabilities ?? {};
611
+ if (supportsBooleanSessionConfigOptions) {
612
+ clientCapabilities = mergeRecords({
613
+ left: clientCapabilities,
614
+ right: {
615
+ session: {
616
+ configOptions: {
617
+ boolean: {}
618
+ }
619
+ }
620
+ }
621
+ });
622
+ }
623
+ return {
624
+ protocolVersion,
625
+ clientInfo: clientApp,
626
+ clientCapabilities
627
+ };
628
+ }
629
+ function resolveACPLaunchEnvironment({
630
+ providerAuthentication,
631
+ gateway
632
+ }) {
633
+ if (providerAuthentication?.type !== "ai-gateway") {
634
+ return {};
635
+ }
636
+ const resolved = asRecord(
637
+ resolveACPProfileValue({
638
+ value: providerAuthentication.env,
639
+ gateway: requireGateway({ gateway })
640
+ })
641
+ );
642
+ return Object.fromEntries(
643
+ Object.entries(resolved).map(([key, value]) => [
644
+ key,
645
+ typeof value === "string" ? value : JSON.stringify(value)
646
+ ])
647
+ );
648
+ }
649
+ function validateACPProtocolVersion({
650
+ requested,
651
+ initialization
652
+ }) {
653
+ if (initialization.protocolVersion !== requested) {
654
+ throw new Error(
655
+ `ACP protocol negotiation failed: requested v${requested}, agent selected v${initialization.protocolVersion}.`
656
+ );
657
+ }
658
+ }
659
+ function assertACPAuthenticationMethod({
660
+ initialization,
661
+ methodId
662
+ }) {
663
+ if (!initialization.authMethods?.some((method) => method.id === methodId)) {
664
+ const advertised = initialization.authMethods?.map((method) => method.id).join(", ") || "none";
665
+ throw new Error(
666
+ `ACP authentication method ${JSON.stringify(methodId)} is not advertised by the agent. Advertised methods: ${advertised}.`
667
+ );
668
+ }
669
+ }
670
+ function mergeRecords({
671
+ left,
672
+ right
673
+ }) {
674
+ const result = { ...left };
675
+ for (const [key, value] of Object.entries(right)) {
676
+ const previous = result[key];
677
+ result[key] = isRecord(previous) && isRecord(value) ? mergeRecords({ left: previous, right: value }) : value;
678
+ }
679
+ return result;
680
+ }
681
+ function requireGateway({
682
+ gateway
683
+ }) {
684
+ if (gateway == null) {
685
+ throw new Error("ACP Gateway profile values are unavailable.");
686
+ }
687
+ return gateway;
688
+ }
689
+ function asRecord(value) {
690
+ if (!isRecord(value)) {
691
+ throw new Error("ACP profile data must resolve to an object.");
692
+ }
693
+ return value;
694
+ }
695
+ function isRecord(value) {
696
+ return value != null && typeof value === "object" && !Array.isArray(value);
697
+ }
698
+
699
+ // src/v1/bridge/acp-stream-capture.ts
700
+ var KNOWN_SESSION_UPDATES = /* @__PURE__ */ new Set([
701
+ "user_message_chunk",
702
+ "agent_message_chunk",
703
+ "agent_thought_chunk",
704
+ "tool_call",
705
+ "tool_call_update",
706
+ "plan",
707
+ "plan_update",
708
+ "plan_removed",
709
+ "available_commands_update",
710
+ "current_mode_update",
711
+ "config_option_update",
712
+ "session_info_update",
713
+ "usage_update"
714
+ ]);
715
+ function captureACPStream({ stream }) {
716
+ const updates = [];
717
+ const readable = stream.readable.pipeThrough(
718
+ new TransformStream({
719
+ transform(message, controller) {
720
+ const rawUpdate = getRawSessionUpdate({ message });
721
+ if (!rawUpdate.isSessionUpdate) {
722
+ controller.enqueue(message);
723
+ return;
724
+ }
725
+ const sessionUpdate = getStringProperty({
726
+ value: rawUpdate.value,
727
+ property: "sessionUpdate"
728
+ });
729
+ const forwarded = !rawUpdate.canFilterUnknown || sessionUpdate == null || KNOWN_SESSION_UPDATES.has(sessionUpdate);
730
+ updates.push({ rawUpdate: rawUpdate.value, forwarded });
731
+ if (forwarded) controller.enqueue(message);
732
+ }
733
+ })
734
+ );
735
+ return {
736
+ stream: { ...stream, readable },
737
+ capture: {
738
+ takeForUpdate: ({ update }) => {
739
+ const index = updates.findIndex(
740
+ (candidate) => candidate.forwarded && sessionUpdatesMatch({
741
+ rawUpdate: candidate.rawUpdate,
742
+ update
743
+ })
744
+ );
745
+ if (index === -1) {
746
+ return { precedingRawValues: [], rawUpdate: update };
747
+ }
748
+ const consumed = updates.splice(0, index + 1);
749
+ return {
750
+ precedingRawValues: consumed.slice(0, -1).map((candidate) => candidate.rawUpdate),
751
+ rawUpdate: consumed.at(-1).rawUpdate
752
+ };
753
+ },
754
+ drainRawValues: () => updates.splice(0).map((candidate) => candidate.rawUpdate)
755
+ }
756
+ };
757
+ }
758
+ function getRawSessionUpdate({ message }) {
759
+ const record = message;
760
+ if (record.method !== "session/update" || !isRecord2(record.params)) {
761
+ return { isSessionUpdate: false };
762
+ }
763
+ const params = record.params;
764
+ return {
765
+ isSessionUpdate: true,
766
+ value: params.update,
767
+ canFilterUnknown: typeof params.sessionId === "string" && isRecord2(params.update)
768
+ };
769
+ }
770
+ function sessionUpdatesMatch({
771
+ rawUpdate,
772
+ update
773
+ }) {
774
+ if (!isRecord2(rawUpdate)) return false;
775
+ if (rawUpdate.sessionUpdate !== update.sessionUpdate) return false;
776
+ if (update.sessionUpdate === "tool_call" || update.sessionUpdate === "tool_call_update") {
777
+ return rawUpdate.toolCallId === update.toolCallId;
778
+ }
779
+ if (update.sessionUpdate === "user_message_chunk" || update.sessionUpdate === "agent_message_chunk" || update.sessionUpdate === "agent_thought_chunk") {
780
+ return rawUpdate.messageId === update.messageId;
781
+ }
782
+ return true;
783
+ }
784
+ function getStringProperty({
785
+ value,
786
+ property
787
+ }) {
788
+ if (!isRecord2(value)) return void 0;
789
+ const propertyValue = value[property];
790
+ return typeof propertyValue === "string" ? propertyValue : void 0;
791
+ }
792
+ function isRecord2(value) {
793
+ return value != null && typeof value === "object" && !Array.isArray(value);
794
+ }
795
+
796
+ // src/v1/bridge/acp-diagnostics.ts
797
+ function createACPInitializationDiagnostic({
798
+ initialization,
799
+ sessionId
800
+ }) {
801
+ const agentInfo = initialization.agentInfo;
802
+ return {
803
+ protocolVersion: initialization.protocolVersion,
804
+ sessionId,
805
+ agent: agentInfo == null ? null : {
806
+ name: agentInfo.name,
807
+ version: agentInfo.version,
808
+ ...agentInfo.title == null ? {} : { title: agentInfo.title }
809
+ },
810
+ capabilities: stripMetadata({
811
+ value: initialization.agentCapabilities ?? {}
812
+ }),
813
+ authMethods: (initialization.authMethods ?? []).map((method) => ({
814
+ id: method.id,
815
+ type: "type" in method ? method.type : "agent"
816
+ }))
817
+ };
818
+ }
819
+ function createACPBridgeError({
820
+ stage,
821
+ cause
822
+ }) {
823
+ const causeMessage = getErrorMessage({ error: cause });
824
+ const error = new Error(
825
+ causeMessage == null ? `ACP ${stage} failed.` : `ACP ${stage} failed: ${causeMessage}`
826
+ );
827
+ error.cause = cause;
828
+ return error;
829
+ }
830
+ function getErrorMessage({ error }) {
831
+ if (error instanceof Error) {
832
+ return error.message;
833
+ }
834
+ if (error != null && typeof error === "object" && "message" in error && typeof error.message === "string") {
835
+ return error.message;
836
+ }
837
+ if (typeof error === "string") {
838
+ return error;
839
+ }
840
+ return void 0;
841
+ }
842
+ function stripMetadata({ value }) {
843
+ if (Array.isArray(value)) {
844
+ return value.map((item) => stripMetadata({ value: item }));
845
+ }
846
+ if (value == null || typeof value !== "object") return value;
847
+ const result = {};
848
+ for (const [key, item] of Object.entries(value)) {
849
+ if (key !== "_meta") result[key] = stripMetadata({ value: item });
850
+ }
851
+ return result;
852
+ }
853
+
854
+ // src/v1/bridge/agent-stderr-monitor.ts
855
+ import { stripVTControlCharacters } from "util";
856
+ function monitorACPAgentStderr({
857
+ stderr,
858
+ onStderrLine
859
+ }) {
860
+ let rejectFailure;
861
+ const failure = new Promise((_, reject) => {
862
+ rejectFailure = reject;
863
+ });
864
+ void failure.catch(() => {
865
+ });
866
+ void (async () => {
867
+ stderr.setEncoding("utf8");
868
+ let pending = "";
869
+ for await (const chunk of stderr) {
870
+ pending += chunk;
871
+ const lines = pending.split("\n");
872
+ pending = lines.pop() ?? "";
873
+ for (const line of lines) handleStderrLine({ line });
874
+ }
875
+ if (pending.length > 0) handleStderrLine({ line: pending });
876
+ })().catch((error) => rejectFailure(error));
877
+ return failure;
878
+ function handleStderrLine({ line }) {
879
+ if (line.length === 0) return;
880
+ onStderrLine(line);
881
+ const normalizedLine = stripVTControlCharacters(line);
882
+ if (normalizedLine.toLowerCase().includes("failed to deserialize responsestreamevent from stream")) {
883
+ rejectFailure(
884
+ new Error("ACP agent failed to deserialize a streamed response.")
885
+ );
886
+ }
887
+ }
888
+ }
889
+
890
+ // src/v1/bridge/stream-translator.ts
891
+ function createACPStreamTranslator({
892
+ emit,
893
+ builtinTools = []
894
+ }) {
895
+ let openBlock;
896
+ let blockCounter = 0;
897
+ let stepOpen = false;
898
+ let completedToolStepEligible = false;
899
+ let finished = false;
900
+ const blockIdCounts = /* @__PURE__ */ new Map();
901
+ const toolStates = /* @__PURE__ */ new Map();
902
+ const pendingToolCallIds = /* @__PURE__ */ new Set();
903
+ const builtinToolsByName = indexBuiltinTools({ builtinTools });
904
+ const closeBlock = () => {
905
+ if (openBlock == null) return;
906
+ emit({
907
+ type: openBlock.type === "text" ? "text-end" : "reasoning-end",
908
+ id: openBlock.id
909
+ });
910
+ openBlock = void 0;
911
+ };
912
+ const createBlockId = ({
913
+ type,
914
+ messageId
915
+ }) => {
916
+ const base = messageId ?? `acp-${type}-${++blockCounter}`;
917
+ const count = (blockIdCounts.get(base) ?? 0) + 1;
918
+ blockIdCounts.set(base, count);
919
+ return count === 1 ? base : `${base}-${count}`;
920
+ };
921
+ const emitContent = ({
922
+ type,
923
+ text,
924
+ messageId
925
+ }) => {
926
+ const normalizedMessageId = messageId ?? null;
927
+ if (openBlock == null || openBlock.type !== type || openBlock.messageId !== normalizedMessageId) {
928
+ closeBlock();
929
+ const id = createBlockId({ type, messageId: normalizedMessageId });
930
+ emit({
931
+ type: type === "text" ? "text-start" : "reasoning-start",
932
+ id
933
+ });
934
+ openBlock = { type, id, messageId: normalizedMessageId };
935
+ }
936
+ emit({
937
+ type: type === "text" ? "text-delta" : "reasoning-delta",
938
+ id: openBlock.id,
939
+ delta: text
940
+ });
941
+ stepOpen = true;
942
+ };
943
+ const emitInferredStep = ({
944
+ finishReason
945
+ }) => {
946
+ closeBlock();
947
+ emit({
948
+ type: "finish-step",
949
+ finishReason,
950
+ usage: unknownUsage(),
951
+ harnessMetadata: { acp: { inferredStep: true } }
952
+ });
953
+ stepOpen = false;
954
+ completedToolStepEligible = false;
955
+ };
956
+ const flushCompletedToolStep = () => {
957
+ if (!stepOpen || !completedToolStepEligible || pendingToolCallIds.size > 0) {
958
+ return;
959
+ }
960
+ emitInferredStep({
961
+ finishReason: { unified: "tool-calls", raw: "tool-calls" }
962
+ });
963
+ };
964
+ const emitToolUpdate = ({
965
+ update,
966
+ rawUpdate,
967
+ forceEmit = false
968
+ }) => {
969
+ closeBlock();
970
+ const state = toolStates.get(update.toolCallId) ?? {
971
+ toolCallId: update.toolCallId,
972
+ values: {},
973
+ emittedFilePaths: /* @__PURE__ */ new Set(),
974
+ emittedCall: false,
975
+ emittedResult: false
976
+ };
977
+ toolStates.set(update.toolCallId, state);
978
+ mergeToolUpdate({ state, update, rawUpdate });
979
+ if (!forceEmit && !state.emittedCall && update.sessionUpdate === "tool_call" && update.status === "pending") {
980
+ return;
981
+ }
982
+ if (!state.emittedCall) {
983
+ const programmaticName = getStringProperty2({
984
+ value: state.values,
985
+ property: "name"
986
+ });
987
+ const builtin = resolveBuiltinTool({
988
+ programmaticName,
989
+ metadata: state.values._meta,
990
+ rawInput: state.values.rawInput,
991
+ builtinTools,
992
+ builtinToolsByName
993
+ });
994
+ if (!forceEmit && builtin?.inputSchema != null && state.values.status !== "completed" && state.values.status !== "failed" && !hasRequiredBuiltinToolInput({
995
+ rawInput: state.values.rawInput,
996
+ inputSchema: builtin.inputSchema
997
+ })) {
998
+ return;
999
+ }
1000
+ if (builtin != null) {
1001
+ state.toolName = builtin.toolName;
1002
+ state.nativeName = builtin.nativeName != null && builtin.toolName !== builtin.nativeName ? builtin.nativeName : void 0;
1003
+ state.dynamic = false;
1004
+ } else {
1005
+ state.toolName = createDynamicToolName({
1006
+ programmaticName,
1007
+ toolCallId: state.toolCallId
1008
+ });
1009
+ state.dynamic = true;
1010
+ }
1011
+ emit({
1012
+ type: "tool-call",
1013
+ toolCallId: state.toolCallId,
1014
+ toolName: state.toolName,
1015
+ input: stringifyToolInput({ input: state.values.rawInput }),
1016
+ providerExecuted: true,
1017
+ ...state.dynamic ? { dynamic: true } : {},
1018
+ ...state.nativeName == null ? {} : { nativeName: state.nativeName }
1019
+ });
1020
+ state.emittedCall = true;
1021
+ stepOpen = true;
1022
+ pendingToolCallIds.add(state.toolCallId);
1023
+ completedToolStepEligible = false;
1024
+ }
1025
+ emitFileChanges({ state, emit });
1026
+ if (!state.emittedResult && (state.values.status === "completed" || state.values.status === "failed")) {
1027
+ emit({
1028
+ type: "tool-result",
1029
+ toolCallId: state.toolCallId,
1030
+ toolName: state.toolName,
1031
+ result: createToolResult({ state }),
1032
+ ...state.values.status === "failed" ? { isError: true } : {},
1033
+ ...state.dynamic ? { dynamic: true } : {}
1034
+ });
1035
+ state.emittedResult = true;
1036
+ pendingToolCallIds.delete(state.toolCallId);
1037
+ completedToolStepEligible = pendingToolCallIds.size === 0;
1038
+ }
1039
+ };
1040
+ const hostToolCall = ({
1041
+ toolCallId,
1042
+ toolName,
1043
+ input
1044
+ }) => {
1045
+ closeBlock();
1046
+ emit({
1047
+ type: "tool-call",
1048
+ toolCallId,
1049
+ toolName,
1050
+ input: stringifyToolInput({ input }),
1051
+ providerExecuted: false
1052
+ });
1053
+ stepOpen = true;
1054
+ pendingToolCallIds.add(toolCallId);
1055
+ completedToolStepEligible = false;
1056
+ };
1057
+ const hostToolResult = ({
1058
+ toolCallId,
1059
+ toolName,
1060
+ output,
1061
+ isError
1062
+ }) => {
1063
+ closeBlock();
1064
+ emit({
1065
+ type: "tool-result",
1066
+ toolCallId,
1067
+ toolName,
1068
+ result: toSafeJSONValue({ value: output, fallback: {} }) ?? {},
1069
+ ...isError ? { isError: true } : {}
1070
+ });
1071
+ pendingToolCallIds.delete(toolCallId);
1072
+ completedToolStepEligible = pendingToolCallIds.size === 0;
1073
+ };
1074
+ return {
1075
+ update: (options) => {
1076
+ const { update, rawUpdate, preserveRaw } = "update" in options ? {
1077
+ update: options.update,
1078
+ rawUpdate: options.rawUpdate,
1079
+ preserveRaw: options.preserveRaw
1080
+ } : { update: options, rawUpdate: options, preserveRaw: true };
1081
+ const preservedUpdate = rawUpdate === void 0 ? update : rawUpdate;
1082
+ if (preserveRaw !== false) {
1083
+ emit({ type: "raw", rawValue: preservedUpdate });
1084
+ }
1085
+ if (update.sessionUpdate === "agent_message_chunk" && update.content?.type === "text" && typeof update.content.text === "string") {
1086
+ flushCompletedToolStep();
1087
+ emitContent({
1088
+ type: "text",
1089
+ text: update.content.text,
1090
+ messageId: update.messageId
1091
+ });
1092
+ return;
1093
+ }
1094
+ if (update.sessionUpdate === "agent_thought_chunk" && update.content?.type === "text" && typeof update.content.text === "string") {
1095
+ flushCompletedToolStep();
1096
+ emitContent({
1097
+ type: "reasoning",
1098
+ text: update.content.text,
1099
+ messageId: update.messageId
1100
+ });
1101
+ return;
1102
+ }
1103
+ if (update.sessionUpdate === "agent_message_chunk" || update.sessionUpdate === "agent_thought_chunk") {
1104
+ closeBlock();
1105
+ return;
1106
+ }
1107
+ if (update.sessionUpdate === "tool_call" || update.sessionUpdate === "tool_call_update") {
1108
+ emitToolUpdate({ update, rawUpdate: preservedUpdate });
1109
+ }
1110
+ },
1111
+ permissionToolCall: ({ toolCall }) => {
1112
+ emitToolUpdate({
1113
+ update: toolCall,
1114
+ rawUpdate: toolCall,
1115
+ forceEmit: true
1116
+ });
1117
+ },
1118
+ raw: ({ rawValue }) => {
1119
+ emit({ type: "raw", rawValue });
1120
+ },
1121
+ close: closeBlock,
1122
+ hostToolCall,
1123
+ hostToolResult,
1124
+ finish: (response) => {
1125
+ if (finished) return;
1126
+ finished = true;
1127
+ emit({ type: "raw", rawValue: response });
1128
+ const finishReason = mapACPFinishReason({
1129
+ stopReason: response.stopReason
1130
+ });
1131
+ const usageMetadata = response.usage == null ? void 0 : mapACPUsageMetadata({ usage: response.usage });
1132
+ if (stepOpen) {
1133
+ emitInferredStep({ finishReason });
1134
+ } else {
1135
+ closeBlock();
1136
+ }
1137
+ emit({
1138
+ type: "finish",
1139
+ finishReason,
1140
+ totalUsage: mapACPUsage({ usage: response.usage }),
1141
+ harnessMetadata: {
1142
+ acp: {
1143
+ stopReason: response.stopReason,
1144
+ ...usageMetadata == null ? {} : { usage: usageMetadata }
1145
+ }
1146
+ }
1147
+ });
1148
+ }
1149
+ };
1150
+ }
1151
+ function mapACPFinishReason({ stopReason }) {
1152
+ switch (stopReason) {
1153
+ case "end_turn":
1154
+ return { unified: "stop", raw: stopReason };
1155
+ case "max_tokens":
1156
+ case "max_turn_requests":
1157
+ return { unified: "length", raw: stopReason };
1158
+ case "refusal":
1159
+ return { unified: "content-filter", raw: stopReason };
1160
+ default:
1161
+ return { unified: "other", raw: stopReason };
1162
+ }
1163
+ }
1164
+ function indexBuiltinTools({
1165
+ builtinTools
1166
+ }) {
1167
+ const matches = /* @__PURE__ */ new Map();
1168
+ for (const builtinTool of builtinTools) {
1169
+ addBuiltinToolMatch({
1170
+ matches,
1171
+ name: builtinTool.toolName,
1172
+ builtinTool
1173
+ });
1174
+ if (builtinTool.nativeName != null) {
1175
+ addBuiltinToolMatch({
1176
+ matches,
1177
+ name: builtinTool.nativeName,
1178
+ builtinTool
1179
+ });
1180
+ }
1181
+ }
1182
+ return new Map(
1183
+ [...matches].filter(
1184
+ (entry) => entry[1] != null
1185
+ )
1186
+ );
1187
+ }
1188
+ function addBuiltinToolMatch({
1189
+ matches,
1190
+ name: name2,
1191
+ builtinTool
1192
+ }) {
1193
+ if (!matches.has(name2)) {
1194
+ matches.set(name2, builtinTool);
1195
+ return;
1196
+ }
1197
+ if (matches.get(name2)?.toolName !== builtinTool.toolName) {
1198
+ matches.set(name2, null);
1199
+ }
1200
+ }
1201
+ function resolveBuiltinTool({
1202
+ programmaticName,
1203
+ metadata,
1204
+ rawInput,
1205
+ builtinTools,
1206
+ builtinToolsByName
1207
+ }) {
1208
+ if (programmaticName != null) {
1209
+ return builtinToolsByName.get(programmaticName);
1210
+ }
1211
+ const metadataMatch = findBuiltinToolsInMetadata({
1212
+ metadata,
1213
+ builtinToolsByName
1214
+ });
1215
+ if (metadataMatch.hasProgrammaticName) {
1216
+ return metadataMatch.matches.length === 1 ? metadataMatch.matches[0] : void 0;
1217
+ }
1218
+ const schemaMatches = builtinTools.filter(
1219
+ (tool) => matchesBuiltinToolInput({ rawInput, inputSchema: tool.inputSchema })
1220
+ );
1221
+ return schemaMatches.length === 1 ? schemaMatches[0] : void 0;
1222
+ }
1223
+ function findBuiltinToolsInMetadata({
1224
+ metadata,
1225
+ builtinToolsByName
1226
+ }) {
1227
+ const matches = /* @__PURE__ */ new Map();
1228
+ let hasProgrammaticName = false;
1229
+ const seen = /* @__PURE__ */ new Set();
1230
+ const visit = (value) => {
1231
+ if (value == null || typeof value !== "object" || seen.has(value)) return;
1232
+ seen.add(value);
1233
+ for (const [property, child2] of Array.isArray(value) ? value.entries() : Object.entries(value)) {
1234
+ if ((property === "name" || property === "toolName") && typeof child2 === "string") {
1235
+ hasProgrammaticName = true;
1236
+ const builtin = builtinToolsByName.get(child2);
1237
+ if (builtin != null) matches.set(builtin.toolName, builtin);
1238
+ }
1239
+ visit(child2);
1240
+ }
1241
+ };
1242
+ visit(metadata);
1243
+ return { hasProgrammaticName, matches: [...matches.values()] };
1244
+ }
1245
+ function matchesBuiltinToolInput({
1246
+ rawInput,
1247
+ inputSchema
1248
+ }) {
1249
+ if (!isRecord3(rawInput) || !isRecord3(inputSchema)) return false;
1250
+ if (inputSchema.type !== "object") return false;
1251
+ const required = inputSchema.required;
1252
+ if (!Array.isArray(required) || required.length === 0 || !required.every((property) => typeof property === "string")) {
1253
+ return false;
1254
+ }
1255
+ if (!hasRequiredBuiltinToolInput({ rawInput, inputSchema })) return false;
1256
+ const properties = isRecord3(inputSchema.properties) ? inputSchema.properties : {};
1257
+ return Object.entries(properties).filter(([, schema]) => isJSONSchemaDiscriminator({ schema })).every(([property, schema]) => {
1258
+ if (!Object.prototype.hasOwnProperty.call(rawInput, property)) {
1259
+ return false;
1260
+ }
1261
+ return matchesJSONSchemaValue({ value: rawInput[property], schema });
1262
+ });
1263
+ }
1264
+ function hasRequiredBuiltinToolInput({
1265
+ rawInput,
1266
+ inputSchema
1267
+ }) {
1268
+ if (!isRecord3(inputSchema) || inputSchema.type !== "object") return true;
1269
+ if (!isRecord3(rawInput)) return false;
1270
+ const required = inputSchema.required;
1271
+ if (!Array.isArray(required) || required.length === 0) return true;
1272
+ if (!required.every((property) => typeof property === "string")) return true;
1273
+ const properties = isRecord3(inputSchema.properties) ? inputSchema.properties : {};
1274
+ return required.every((property) => {
1275
+ if (!Object.prototype.hasOwnProperty.call(rawInput, property)) return false;
1276
+ return matchesJSONSchemaValue({
1277
+ value: rawInput[property],
1278
+ schema: properties[property]
1279
+ });
1280
+ });
1281
+ }
1282
+ function isJSONSchemaDiscriminator({ schema }) {
1283
+ return isRecord3(schema) && ("const" in schema || Array.isArray(schema.enum) && schema.enum.length === 1);
1284
+ }
1285
+ function matchesJSONSchemaValue({
1286
+ value,
1287
+ schema
1288
+ }) {
1289
+ if (schema === true) return true;
1290
+ if (schema === false || !isRecord3(schema)) return false;
1291
+ if ("const" in schema && !Object.is(value, schema.const)) return false;
1292
+ if (Array.isArray(schema.enum) && !schema.enum.some((candidate) => Object.is(value, candidate))) {
1293
+ return false;
1294
+ }
1295
+ for (const alternatives of [schema.anyOf, schema.oneOf]) {
1296
+ if (Array.isArray(alternatives) && !alternatives.some(
1297
+ (alternative) => matchesJSONSchemaValue({ value, schema: alternative })
1298
+ )) {
1299
+ return false;
1300
+ }
1301
+ }
1302
+ if (typeof schema.type !== "string") return true;
1303
+ switch (schema.type) {
1304
+ case "null":
1305
+ return value === null;
1306
+ case "boolean":
1307
+ return typeof value === "boolean";
1308
+ case "integer":
1309
+ return typeof value === "number" && Number.isInteger(value);
1310
+ case "number":
1311
+ return typeof value === "number" && Number.isFinite(value);
1312
+ case "string":
1313
+ return typeof value === "string";
1314
+ case "array":
1315
+ return Array.isArray(value);
1316
+ case "object":
1317
+ return isRecord3(value);
1318
+ default:
1319
+ return true;
1320
+ }
1321
+ }
1322
+ function mergeToolUpdate({
1323
+ state,
1324
+ update,
1325
+ rawUpdate
1326
+ }) {
1327
+ const parsed = update;
1328
+ for (const property of [
1329
+ "title",
1330
+ "kind",
1331
+ "status",
1332
+ "content",
1333
+ "locations",
1334
+ "rawInput",
1335
+ "rawOutput",
1336
+ "_meta"
1337
+ ]) {
1338
+ if (Object.prototype.hasOwnProperty.call(parsed, property) && parsed[property] !== void 0) {
1339
+ state.values[property] = parsed[property];
1340
+ }
1341
+ }
1342
+ const programmaticName = getStringProperty2({
1343
+ value: rawUpdate,
1344
+ property: "name"
1345
+ });
1346
+ if (programmaticName != null) state.values.name = programmaticName;
1347
+ }
1348
+ function emitFileChanges({
1349
+ state,
1350
+ emit
1351
+ }) {
1352
+ const content = Array.isArray(state.values.content) ? state.values.content : [];
1353
+ for (const item of content) {
1354
+ if (!isRecord3(item) || item.type !== "diff") continue;
1355
+ const path = getStringProperty2({ value: item, property: "path" });
1356
+ const newText = getStringProperty2({ value: item, property: "newText" });
1357
+ if (path == null || newText == null) continue;
1358
+ const oldText = item.oldText;
1359
+ const event = oldText == null ? "create" : "modify";
1360
+ emitFileChange({ state, emit, path, event });
1361
+ }
1362
+ if (state.values.kind !== "edit") return;
1363
+ const locations = Array.isArray(state.values.locations) ? state.values.locations : [];
1364
+ for (const location of locations) {
1365
+ const path = getStringProperty2({ value: location, property: "path" });
1366
+ if (path != null) {
1367
+ emitFileChange({ state, emit, path, event: "modify" });
1368
+ }
1369
+ }
1370
+ }
1371
+ function emitFileChange({
1372
+ state,
1373
+ emit,
1374
+ path,
1375
+ event
1376
+ }) {
1377
+ if (state.emittedFilePaths.has(path)) return;
1378
+ state.emittedFilePaths.add(path);
1379
+ emit({
1380
+ type: "file-change",
1381
+ path,
1382
+ event,
1383
+ harnessMetadata: {
1384
+ acp: {
1385
+ toolCallId: state.toolCallId
1386
+ }
1387
+ }
1388
+ });
1389
+ }
1390
+ function createDynamicToolName({
1391
+ programmaticName,
1392
+ toolCallId
1393
+ }) {
1394
+ const source = programmaticName ?? `acp_tool_${toolCallId}`;
1395
+ const normalized = source.replace(/[^A-Za-z0-9_-]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 128);
1396
+ if (normalized.length === 0) return "acp_tool";
1397
+ return /^[A-Za-z_]/.test(normalized) ? normalized : `acp_${normalized}`;
1398
+ }
1399
+ function stringifyToolInput({ input }) {
1400
+ return JSON.stringify(toSafeJSONValue({ value: input, fallback: {} }));
1401
+ }
1402
+ function createToolResult({ state }) {
1403
+ const value = state.values.rawOutput != null ? state.values.rawOutput : state.values.content != null ? state.values.content : {};
1404
+ return toSafeJSONValue({ value, fallback: {} }) ?? {};
1405
+ }
1406
+ function toSafeJSONValue({
1407
+ value,
1408
+ fallback,
1409
+ seen = /* @__PURE__ */ new Set()
1410
+ }) {
1411
+ if (value == null) return fallback;
1412
+ if (typeof value === "string" || typeof value === "boolean") return value;
1413
+ if (typeof value === "number") return Number.isFinite(value) ? value : null;
1414
+ if (typeof value === "bigint") return value.toString();
1415
+ if (typeof value !== "object") return fallback;
1416
+ if (seen.has(value)) return "[Circular]";
1417
+ seen.add(value);
1418
+ if (Array.isArray(value)) {
1419
+ const result2 = value.map(
1420
+ (item) => toSafeJSONValue({ value: item, fallback: null, seen })
1421
+ );
1422
+ seen.delete(value);
1423
+ return result2;
1424
+ }
1425
+ const result = /* @__PURE__ */ Object.create(null);
1426
+ try {
1427
+ for (const [key, item] of Object.entries(value)) {
1428
+ result[key] = toSafeJSONValue({
1429
+ value: item,
1430
+ fallback: null,
1431
+ seen
1432
+ });
1433
+ }
1434
+ } catch {
1435
+ seen.delete(value);
1436
+ return fallback;
1437
+ }
1438
+ seen.delete(value);
1439
+ return result;
1440
+ }
1441
+ function getStringProperty2({
1442
+ value,
1443
+ property
1444
+ }) {
1445
+ if (!isRecord3(value)) return void 0;
1446
+ const propertyValue = value[property];
1447
+ return typeof propertyValue === "string" ? propertyValue : void 0;
1448
+ }
1449
+ function isRecord3(value) {
1450
+ return value != null && typeof value === "object" && !Array.isArray(value);
1451
+ }
1452
+ function mapACPUsage({
1453
+ usage
1454
+ }) {
1455
+ if (usage == null) return unknownUsage();
1456
+ const raw = mapACPUsageMetadata({ usage });
1457
+ return {
1458
+ inputTokens: {
1459
+ total: usage.inputTokens,
1460
+ noCache: void 0,
1461
+ cacheRead: usage.cachedReadTokens ?? void 0,
1462
+ cacheWrite: usage.cachedWriteTokens ?? void 0
1463
+ },
1464
+ outputTokens: {
1465
+ total: usage.outputTokens,
1466
+ text: void 0,
1467
+ reasoning: usage.thoughtTokens ?? void 0
1468
+ },
1469
+ raw
1470
+ };
1471
+ }
1472
+ function mapACPUsageMetadata({
1473
+ usage
1474
+ }) {
1475
+ return {
1476
+ totalTokens: usage.totalTokens,
1477
+ inputTokens: usage.inputTokens,
1478
+ outputTokens: usage.outputTokens,
1479
+ ...usage.thoughtTokens == null ? {} : { thoughtTokens: usage.thoughtTokens },
1480
+ ...usage.cachedReadTokens == null ? {} : { cachedReadTokens: usage.cachedReadTokens },
1481
+ ...usage.cachedWriteTokens == null ? {} : { cachedWriteTokens: usage.cachedWriteTokens }
1482
+ };
1483
+ }
1484
+ function unknownUsage() {
1485
+ return {
1486
+ inputTokens: {
1487
+ total: void 0,
1488
+ noCache: void 0,
1489
+ cacheRead: void 0,
1490
+ cacheWrite: void 0
1491
+ },
1492
+ outputTokens: {
1493
+ total: void 0,
1494
+ text: void 0,
1495
+ reasoning: void 0
1496
+ }
1497
+ };
1498
+ }
1499
+
1500
+ // src/v1/bridge/host-tool-correlation.ts
1501
+ var CORRELATION_WINDOW_MS = 1e3;
1502
+ var MAX_BUFFERED_UPDATES = 128;
1503
+ function createHostToolCorrelation({
1504
+ emitSemanticUpdate,
1505
+ emitRawUpdate,
1506
+ hostToolServerName,
1507
+ hostTools
1508
+ }) {
1509
+ const invocations = /* @__PURE__ */ new Map();
1510
+ const candidates = /* @__PURE__ */ new Map();
1511
+ const suppressedToolCallIds = /* @__PURE__ */ new Set();
1512
+ const releasedToolCallIds = /* @__PURE__ */ new Set();
1513
+ let nextCandidateOrder = 0;
1514
+ let buffered = [];
1515
+ let flushTimer;
1516
+ const flush = () => {
1517
+ if (flushTimer != null) {
1518
+ clearTimeout(flushTimer);
1519
+ flushTimer = void 0;
1520
+ }
1521
+ if (buffered.length === 0) return;
1522
+ const updates = buffered;
1523
+ buffered = [];
1524
+ for (const update of updates) {
1525
+ if (isToolUpdate(update.message) && suppressedToolCallIds.has(update.message.update.toolCallId)) {
1526
+ continue;
1527
+ }
1528
+ if (isToolUpdate(update.message)) {
1529
+ releasedToolCallIds.add(update.message.update.toolCallId);
1530
+ }
1531
+ emitSemanticUpdate(update);
1532
+ }
1533
+ for (const toolCallId of candidates.keys()) {
1534
+ if (!suppressedToolCallIds.has(toolCallId)) {
1535
+ releasedToolCallIds.add(toolCallId);
1536
+ }
1537
+ }
1538
+ candidates.clear();
1539
+ };
1540
+ const scheduleFlush = () => {
1541
+ if (flushTimer != null) return;
1542
+ flushTimer = setTimeout(flush, CORRELATION_WINDOW_MS);
1543
+ flushTimer.unref?.();
1544
+ };
1545
+ const suppressCandidate = ({
1546
+ candidate,
1547
+ invocation
1548
+ }) => {
1549
+ invocation.toolCallId = candidate.toolCallId;
1550
+ suppressedToolCallIds.add(candidate.toolCallId);
1551
+ candidates.delete(candidate.toolCallId);
1552
+ deleteInvocation({ invocations, token: invocation.token });
1553
+ };
1554
+ const suppressToolCallUpdate = ({ toolCallId }) => {
1555
+ suppressedToolCallIds.add(toolCallId);
1556
+ candidates.delete(toolCallId);
1557
+ if (candidates.size === 0) flush();
1558
+ };
1559
+ const reconcile = () => {
1560
+ const orderedInvocations = [...invocations.values()].sort(
1561
+ (left, right) => left.order - right.order
1562
+ );
1563
+ const orderedCandidates = [...candidates.values()].sort(
1564
+ (left, right) => left.order - right.order
1565
+ );
1566
+ for (const invocation of orderedInvocations) {
1567
+ if (invocation.toolCallId != null) continue;
1568
+ const candidate = orderedCandidates.find(
1569
+ (item) => candidates.has(item.toolCallId) && hasPortableEvidence({ candidate: item, invocation })
1570
+ );
1571
+ if (candidate != null) suppressCandidate({ candidate, invocation });
1572
+ }
1573
+ if (candidates.size === 0) flush();
1574
+ };
1575
+ return {
1576
+ update: ({ message, rawUpdate }) => {
1577
+ const preservedUpdate = rawUpdate === void 0 ? message.update : rawUpdate;
1578
+ emitRawUpdate({ rawUpdate: preservedUpdate });
1579
+ if (!isToolUpdate(message)) {
1580
+ if (buffered.length === 0) {
1581
+ emitSemanticUpdate({ message, rawUpdate: preservedUpdate });
1582
+ } else {
1583
+ buffered.push({ message, rawUpdate: preservedUpdate });
1584
+ if (buffered.length >= MAX_BUFFERED_UPDATES) flush();
1585
+ }
1586
+ return;
1587
+ }
1588
+ const toolCallId = message.update.toolCallId;
1589
+ if (suppressedToolCallIds.has(toolCallId)) {
1590
+ if (isTerminalToolUpdate(message)) {
1591
+ suppressedToolCallIds.delete(toolCallId);
1592
+ removeInvocationForToolCall({ invocations, toolCallId });
1593
+ }
1594
+ return;
1595
+ }
1596
+ if (releasedToolCallIds.has(toolCallId)) {
1597
+ emitSemanticUpdate({ message, rawUpdate: preservedUpdate });
1598
+ if (isTerminalToolUpdate(message))
1599
+ releasedToolCallIds.delete(toolCallId);
1600
+ return;
1601
+ }
1602
+ buffered.push({ message, rawUpdate: preservedUpdate });
1603
+ const candidate = candidates.get(toolCallId) ?? {
1604
+ toolCallId,
1605
+ order: ++nextCandidateOrder,
1606
+ evidence: []
1607
+ };
1608
+ candidate.evidence.push(message.update, preservedUpdate);
1609
+ candidates.set(toolCallId, candidate);
1610
+ const tokenMatch = findTokenInvocation({
1611
+ value: preservedUpdate,
1612
+ invocations
1613
+ });
1614
+ if (tokenMatch != null) {
1615
+ suppressCandidate({ candidate, invocation: tokenMatch });
1616
+ }
1617
+ reconcile();
1618
+ if (buffered.length >= MAX_BUFFERED_UPDATES) flush();
1619
+ else if (buffered.length > 0) scheduleFlush();
1620
+ },
1621
+ registerInvocation: ({ token, serverName, toolName, input, order }) => {
1622
+ const invocation = {
1623
+ token,
1624
+ serverName,
1625
+ toolName,
1626
+ inputFingerprint: canonicalFingerprint({ value: input }),
1627
+ order
1628
+ };
1629
+ invocations.set(token, invocation);
1630
+ invocation.expiryTimer = setTimeout(
1631
+ () => deleteInvocation({ invocations, token }),
1632
+ CORRELATION_WINDOW_MS
1633
+ );
1634
+ invocation.expiryTimer.unref?.();
1635
+ const tokenCandidate = [...candidates.values()].find(
1636
+ (candidate) => candidate.evidence.some(
1637
+ (value) => containsExactValue({ value, target: token })
1638
+ )
1639
+ );
1640
+ if (tokenCandidate != null) {
1641
+ suppressCandidate({ candidate: tokenCandidate, invocation });
1642
+ }
1643
+ reconcile();
1644
+ },
1645
+ claimHostToolPermission: ({ toolCall }) => {
1646
+ const candidate = candidates.get(toolCall.toolCallId);
1647
+ const matches = hostTools.filter(({ name: name2 }) => {
1648
+ const permission = resolvePermissionHostTool({
1649
+ toolCall,
1650
+ serverName: hostToolServerName,
1651
+ toolName: name2
1652
+ });
1653
+ if (permission == null) return false;
1654
+ if (candidate == null) return permission.hasRequestIdentity;
1655
+ return hasPortableEvidence({
1656
+ candidate,
1657
+ invocation: {
1658
+ token: "",
1659
+ serverName: hostToolServerName,
1660
+ toolName: name2,
1661
+ inputFingerprint: canonicalFingerprint({
1662
+ value: permission.input
1663
+ }),
1664
+ order: 0
1665
+ }
1666
+ });
1667
+ });
1668
+ if (matches.length !== 1) return false;
1669
+ suppressToolCallUpdate({ toolCallId: toolCall.toolCallId });
1670
+ return true;
1671
+ },
1672
+ flush,
1673
+ removeInvocation: ({ token }) => {
1674
+ deleteInvocation({ invocations, token });
1675
+ },
1676
+ close: () => {
1677
+ flush();
1678
+ for (const invocation of invocations.values()) {
1679
+ if (invocation.expiryTimer != null) {
1680
+ clearTimeout(invocation.expiryTimer);
1681
+ }
1682
+ }
1683
+ invocations.clear();
1684
+ candidates.clear();
1685
+ suppressedToolCallIds.clear();
1686
+ releasedToolCallIds.clear();
1687
+ }
1688
+ };
1689
+ }
1690
+ function hasPortableEvidence({
1691
+ candidate,
1692
+ invocation
1693
+ }) {
1694
+ let hasServerIdentity = false;
1695
+ let hasToolName = false;
1696
+ let hasInput = false;
1697
+ for (const evidence of candidate.evidence) {
1698
+ const metadata = getProperty({ value: evidence, property: "_meta" });
1699
+ const rawInput = getProperty({ value: evidence, property: "rawInput" });
1700
+ const programmaticName = getProperty({
1701
+ value: evidence,
1702
+ property: "name"
1703
+ });
1704
+ const deferredToolName = getProperty({
1705
+ value: rawInput,
1706
+ property: "tool_name"
1707
+ });
1708
+ const deferredToolInput = getProperty({
1709
+ value: rawInput,
1710
+ property: "tool_input"
1711
+ });
1712
+ if (containsCombinedIdentity({
1713
+ value: [metadata, programmaticName],
1714
+ serverName: invocation.serverName,
1715
+ toolName: invocation.toolName
1716
+ })) {
1717
+ hasServerIdentity = true;
1718
+ hasToolName = true;
1719
+ }
1720
+ if (typeof deferredToolName === "string" && isRecord4(deferredToolInput) && hasDelimitedPair({
1721
+ value: deferredToolName,
1722
+ serverName: invocation.serverName,
1723
+ toolName: invocation.toolName
1724
+ })) {
1725
+ hasServerIdentity = true;
1726
+ hasToolName = true;
1727
+ if (canonicalFingerprint({ value: deferredToolInput }) === invocation.inputFingerprint) {
1728
+ hasInput = true;
1729
+ }
1730
+ }
1731
+ if (containsExactValue({
1732
+ value: [metadata, rawInput],
1733
+ target: invocation.serverName
1734
+ })) {
1735
+ hasServerIdentity = true;
1736
+ }
1737
+ if (programmaticName === invocation.toolName || containsExactValue({
1738
+ value: [metadata, rawInput],
1739
+ target: invocation.toolName
1740
+ })) {
1741
+ hasToolName = true;
1742
+ }
1743
+ if (containsFingerprint({
1744
+ value: [metadata, rawInput],
1745
+ target: invocation.inputFingerprint
1746
+ })) {
1747
+ hasInput = true;
1748
+ }
1749
+ }
1750
+ return hasServerIdentity && hasToolName && hasInput;
1751
+ }
1752
+ function resolvePermissionHostTool({
1753
+ toolCall,
1754
+ serverName,
1755
+ toolName
1756
+ }) {
1757
+ if (!isRecord4(toolCall.rawInput)) return void 0;
1758
+ const deferredToolName = getProperty({
1759
+ value: toolCall.rawInput,
1760
+ property: "tool_name"
1761
+ });
1762
+ const deferredToolInput = getProperty({
1763
+ value: toolCall.rawInput,
1764
+ property: "tool_input"
1765
+ });
1766
+ if (hasOwnProperty({ value: toolCall.rawInput, property: "tool_name" }) && hasOwnProperty({ value: toolCall.rawInput, property: "tool_input" })) {
1767
+ if (typeof deferredToolName !== "string" || !isRecord4(deferredToolInput) || !hasDelimitedPair({
1768
+ value: deferredToolName,
1769
+ serverName,
1770
+ toolName
1771
+ })) {
1772
+ return void 0;
1773
+ }
1774
+ return {
1775
+ input: deferredToolInput,
1776
+ hasRequestIdentity: true
1777
+ };
1778
+ }
1779
+ return {
1780
+ input: toolCall.rawInput,
1781
+ hasRequestIdentity: containsCombinedIdentity({
1782
+ value: [
1783
+ getProperty({ value: toolCall, property: "title" }),
1784
+ getProperty({ value: toolCall, property: "name" }),
1785
+ getProperty({ value: toolCall, property: "_meta" })
1786
+ ],
1787
+ serverName,
1788
+ toolName
1789
+ })
1790
+ };
1791
+ }
1792
+ function containsCombinedIdentity({
1793
+ value,
1794
+ serverName,
1795
+ toolName
1796
+ }) {
1797
+ const seen = /* @__PURE__ */ new Set();
1798
+ const visit = (candidate) => {
1799
+ if (typeof candidate === "string") {
1800
+ return hasDelimitedPair({ value: candidate, serverName, toolName });
1801
+ }
1802
+ if (candidate == null || typeof candidate !== "object") return false;
1803
+ if (seen.has(candidate)) return false;
1804
+ seen.add(candidate);
1805
+ const values = Array.isArray(candidate) ? candidate : Object.values(candidate);
1806
+ for (const item of values) {
1807
+ if (visit(item)) return true;
1808
+ }
1809
+ return false;
1810
+ };
1811
+ return visit(value);
1812
+ }
1813
+ function hasDelimitedPair({
1814
+ value,
1815
+ serverName,
1816
+ toolName
1817
+ }) {
1818
+ if (serverName.length === 0 || toolName.length === 0) return false;
1819
+ let serverIndex = value.indexOf(serverName);
1820
+ while (serverIndex !== -1) {
1821
+ const beforeServer = serverIndex === 0 ? void 0 : value[serverIndex - 1];
1822
+ const serverHasLeadingBoundary = beforeServer == null || value.slice(0, serverIndex).endsWith("__") || !/[A-Za-z0-9_-]/.test(beforeServer);
1823
+ const delimiterStart = serverIndex + serverName.length;
1824
+ let toolIndex = delimiterStart;
1825
+ while (toolIndex < value.length && !/[A-Za-z0-9-]/.test(value[toolIndex])) {
1826
+ toolIndex += 1;
1827
+ }
1828
+ const toolEnd = toolIndex + toolName.length;
1829
+ const afterTool = toolEnd === value.length ? void 0 : value[toolEnd];
1830
+ const toolHasTrailingBoundary = afterTool == null || value.startsWith("__", toolEnd) || !/[A-Za-z0-9_-]/.test(afterTool);
1831
+ if (serverHasLeadingBoundary && toolIndex > delimiterStart && value.startsWith(toolName, toolIndex) && toolHasTrailingBoundary) {
1832
+ return true;
1833
+ }
1834
+ serverIndex = value.indexOf(serverName, serverIndex + 1);
1835
+ }
1836
+ return false;
1837
+ }
1838
+ function findTokenInvocation({
1839
+ value,
1840
+ invocations
1841
+ }) {
1842
+ for (const invocation of invocations.values()) {
1843
+ if (containsExactValue({ value, target: invocation.token })) {
1844
+ return invocation;
1845
+ }
1846
+ }
1847
+ return void 0;
1848
+ }
1849
+ function removeInvocationForToolCall({
1850
+ invocations,
1851
+ toolCallId
1852
+ }) {
1853
+ for (const [token, invocation] of invocations) {
1854
+ if (invocation.toolCallId === toolCallId) {
1855
+ deleteInvocation({ invocations, token });
1856
+ }
1857
+ }
1858
+ }
1859
+ function deleteInvocation({
1860
+ invocations,
1861
+ token
1862
+ }) {
1863
+ const invocation = invocations.get(token);
1864
+ if (invocation?.expiryTimer != null) clearTimeout(invocation.expiryTimer);
1865
+ invocations.delete(token);
1866
+ }
1867
+ function isToolUpdate(message) {
1868
+ return message.update.sessionUpdate === "tool_call" || message.update.sessionUpdate === "tool_call_update";
1869
+ }
1870
+ function containsExactValue({
1871
+ value,
1872
+ target
1873
+ }) {
1874
+ const seen = /* @__PURE__ */ new Set();
1875
+ const visit = (candidate) => {
1876
+ if (candidate === target) return true;
1877
+ if (candidate == null || typeof candidate !== "object") return false;
1878
+ if (seen.has(candidate)) return false;
1879
+ seen.add(candidate);
1880
+ const values = Array.isArray(candidate) ? candidate : Object.values(candidate);
1881
+ for (const item of values) {
1882
+ if (visit(item)) return true;
1883
+ }
1884
+ return false;
1885
+ };
1886
+ return visit(value);
1887
+ }
1888
+ function containsFingerprint({
1889
+ value,
1890
+ target
1891
+ }) {
1892
+ const seen = /* @__PURE__ */ new Set();
1893
+ const visit = (candidate) => {
1894
+ if (candidate != null && typeof candidate === "object") {
1895
+ if (canonicalFingerprint({ value: candidate }) === target) return true;
1896
+ if (seen.has(candidate)) return false;
1897
+ seen.add(candidate);
1898
+ const values = Array.isArray(candidate) ? candidate : Object.values(candidate);
1899
+ for (const item of values) {
1900
+ if (visit(item)) return true;
1901
+ }
1902
+ }
1903
+ return false;
1904
+ };
1905
+ return visit(value);
1906
+ }
1907
+ function canonicalFingerprint({ value }) {
1908
+ return JSON.stringify(canonicalizeJSON({ value }));
1909
+ }
1910
+ function canonicalizeJSON({ value }) {
1911
+ if (Array.isArray(value)) {
1912
+ return value.map((item) => canonicalizeJSON({ value: item }));
1913
+ }
1914
+ if (value == null || typeof value !== "object") return value;
1915
+ return Object.fromEntries(
1916
+ Object.keys(value).sort().filter((key) => value[key] !== void 0).map((key) => [
1917
+ key,
1918
+ canonicalizeJSON({
1919
+ value: value[key]
1920
+ })
1921
+ ])
1922
+ );
1923
+ }
1924
+ function getProperty({
1925
+ value,
1926
+ property
1927
+ }) {
1928
+ if (value == null || typeof value !== "object" || Array.isArray(value)) {
1929
+ return void 0;
1930
+ }
1931
+ return Reflect.get(value, property);
1932
+ }
1933
+ function hasOwnProperty({
1934
+ value,
1935
+ property
1936
+ }) {
1937
+ return Object.prototype.hasOwnProperty.call(value, property);
1938
+ }
1939
+ function isRecord4(value) {
1940
+ return value != null && typeof value === "object" && !Array.isArray(value);
1941
+ }
1942
+ function isTerminalToolUpdate(message) {
1943
+ return isToolUpdate(message) && (message.update.status === "completed" || message.update.status === "failed");
1944
+ }
1945
+
1946
+ // src/v1/bridge/create-emit-stream-event.ts
1947
+ function createEmitStreamEvent({
1948
+ emit,
1949
+ builtinTools,
1950
+ hostToolServerName,
1951
+ hostTools
1952
+ }) {
1953
+ const translator = createACPStreamTranslator({ emit, builtinTools });
1954
+ const correlation = createHostToolCorrelation({
1955
+ emitSemanticUpdate: ({ message, rawUpdate }) => {
1956
+ translator.update({
1957
+ update: message.update,
1958
+ rawUpdate,
1959
+ preserveRaw: false
1960
+ });
1961
+ },
1962
+ emitRawUpdate: ({ rawUpdate }) => {
1963
+ translator.raw({ rawValue: rawUpdate });
1964
+ },
1965
+ hostToolServerName,
1966
+ hostTools
1967
+ });
1968
+ return {
1969
+ message: ({ message, rawUpdate }) => {
1970
+ if (message.kind === "stop") {
1971
+ correlation.close();
1972
+ translator.finish(message.response);
1973
+ return true;
1974
+ }
1975
+ correlation.update({ message, rawUpdate });
1976
+ return false;
1977
+ },
1978
+ raw: translator.raw,
1979
+ close: () => {
1980
+ correlation.close();
1981
+ translator.close();
1982
+ },
1983
+ permissionToolCall: ({ toolCall }) => {
1984
+ translator.permissionToolCall({
1985
+ toolCall: {
1986
+ sessionUpdate: "tool_call_update",
1987
+ ...toolCall
1988
+ }
1989
+ });
1990
+ },
1991
+ claimHostToolPermission: correlation.claimHostToolPermission,
1992
+ hostToolCall: translator.hostToolCall,
1993
+ hostToolResult: translator.hostToolResult,
1994
+ registerHostToolCorrelationInvocation: correlation.registerInvocation,
1995
+ removeHostToolCorrelationInvocation: correlation.removeInvocation
1996
+ };
1997
+ }
1998
+
1999
+ // src/v1/bridge/host-tool-relay.ts
2000
+ import { randomBytes, timingSafeEqual } from "crypto";
2001
+ import { createServer } from "http";
2002
+ async function startHostToolRelay({
2003
+ tools,
2004
+ serverName
2005
+ }) {
2006
+ const state = {
2007
+ tools: [...tools],
2008
+ fingerprint: catalogFingerprint({ tools }),
2009
+ revision: 1,
2010
+ servedRevision: 0,
2011
+ closed: false,
2012
+ changeWaiters: /* @__PURE__ */ new Set(),
2013
+ refreshWaiters: /* @__PURE__ */ new Set()
2014
+ };
2015
+ const credential = randomBytes(32).toString("hex");
2016
+ let activeTurn;
2017
+ let invocationOrder = 0;
2018
+ let closePromise;
2019
+ const server = createServer(async (request, response) => {
2020
+ try {
2021
+ const result = await handleRequest({
2022
+ request,
2023
+ credential,
2024
+ state,
2025
+ serverName,
2026
+ turn: activeTurn,
2027
+ nextInvocationOrder: () => ++invocationOrder
2028
+ });
2029
+ response.writeHead(200, { "content-type": "application/json" });
2030
+ response.end(JSON.stringify(result));
2031
+ } catch (error) {
2032
+ const status = error instanceof RelayRequestError ? error.status : 500;
2033
+ response.writeHead(status, { "content-type": "application/json" });
2034
+ response.end(
2035
+ JSON.stringify({
2036
+ error: error instanceof Error ? error.message : String(error)
2037
+ })
2038
+ );
2039
+ }
2040
+ });
2041
+ await listen({ server });
2042
+ const address = server.address();
2043
+ return {
2044
+ url: `http://127.0.0.1:${address.port}/invoke`,
2045
+ credential,
2046
+ bindTurn: ({ turn }) => {
2047
+ if (activeTurn != null && activeTurn !== turn) {
2048
+ throw new Error("A host tool relay turn is already active.");
2049
+ }
2050
+ activeTurn = turn;
2051
+ },
2052
+ unbindTurn: ({ turn }) => {
2053
+ if (activeTurn === turn) activeTurn = void 0;
2054
+ },
2055
+ updateCatalog: ({ tools: nextTools }) => {
2056
+ const fingerprint = catalogFingerprint({ tools: nextTools });
2057
+ if (fingerprint === state.fingerprint) {
2058
+ return { changed: false, revision: state.revision };
2059
+ }
2060
+ state.tools = [...nextTools];
2061
+ state.fingerprint = fingerprint;
2062
+ state.revision += 1;
2063
+ resolveCatalogChanges({ state });
2064
+ return { changed: true, revision: state.revision };
2065
+ },
2066
+ waitForCatalogRefresh: ({ revision, timeoutMs }) => waitForCatalogRefresh({ state, revision, timeoutMs }),
2067
+ close: () => {
2068
+ if (closePromise != null) return closePromise;
2069
+ state.closed = true;
2070
+ resolveCatalogChanges({ state });
2071
+ resolveRefreshWaiters({ state, closing: true });
2072
+ closePromise = closeServer({ server });
2073
+ return closePromise;
2074
+ }
2075
+ };
2076
+ }
2077
+ async function handleRequest({
2078
+ request,
2079
+ credential,
2080
+ state,
2081
+ serverName,
2082
+ turn,
2083
+ nextInvocationOrder
2084
+ }) {
2085
+ if (request.method !== "POST") {
2086
+ throw new RelayRequestError({
2087
+ status: 404,
2088
+ message: "Unknown host tool relay endpoint."
2089
+ });
2090
+ }
2091
+ if (!credentialsMatch({
2092
+ expected: credential,
2093
+ actual: request.headers.authorization
2094
+ })) {
2095
+ throw new RelayRequestError({
2096
+ status: 401,
2097
+ message: "Invalid host tool relay credential."
2098
+ });
2099
+ }
2100
+ const body = await readJSONBody({ request });
2101
+ if (request.url === "/catalog/next") {
2102
+ return handleCatalogNext({ body, state });
2103
+ }
2104
+ if (request.url === "/catalog/seen") {
2105
+ return handleCatalogSeen({ body, state });
2106
+ }
2107
+ if (request.url === "/invoke") {
2108
+ return handleInvocation({
2109
+ body,
2110
+ state,
2111
+ serverName,
2112
+ turn,
2113
+ nextInvocationOrder
2114
+ });
2115
+ }
2116
+ throw new RelayRequestError({
2117
+ status: 404,
2118
+ message: "Unknown host tool relay endpoint."
2119
+ });
2120
+ }
2121
+ async function handleCatalogNext({
2122
+ body,
2123
+ state
2124
+ }) {
2125
+ if (!isRecord5(body) || !Number.isSafeInteger(body.afterRevision) || body.afterRevision < 0) {
2126
+ throw new RelayRequestError({
2127
+ status: 400,
2128
+ message: "Invalid host tool catalog poll request."
2129
+ });
2130
+ }
2131
+ const afterRevision = body.afterRevision;
2132
+ if (!state.closed && afterRevision >= state.revision) {
2133
+ await waitForCatalogChange({ state });
2134
+ }
2135
+ if (state.closed) {
2136
+ return { closed: true, revision: state.revision };
2137
+ }
2138
+ return afterRevision < state.revision ? { revision: state.revision, tools: state.tools } : { revision: state.revision };
2139
+ }
2140
+ function handleCatalogSeen({
2141
+ body,
2142
+ state
2143
+ }) {
2144
+ if (!isRecord5(body) || !Number.isSafeInteger(body.revision) || body.revision < 1 || body.revision > state.revision) {
2145
+ throw new RelayRequestError({
2146
+ status: 400,
2147
+ message: "Invalid host tool catalog acknowledgment."
2148
+ });
2149
+ }
2150
+ state.servedRevision = Math.max(
2151
+ state.servedRevision,
2152
+ body.revision
2153
+ );
2154
+ resolveRefreshWaiters({ state, closing: false });
2155
+ return { acknowledged: true };
2156
+ }
2157
+ async function handleInvocation({
2158
+ body,
2159
+ state,
2160
+ serverName,
2161
+ turn,
2162
+ nextInvocationOrder
2163
+ }) {
2164
+ if (turn == null) {
2165
+ throw new RelayRequestError({
2166
+ status: 409,
2167
+ message: "No ACP prompt turn is active."
2168
+ });
2169
+ }
2170
+ if (!isRecord5(body) || typeof body.requestId !== "string" || typeof body.toolName !== "string" || !isRecord5(body.input) || !Number.isSafeInteger(body.catalogRevision)) {
2171
+ throw new RelayRequestError({
2172
+ status: 400,
2173
+ message: "Invalid host tool relay request."
2174
+ });
2175
+ }
2176
+ if (body.catalogRevision !== state.revision) {
2177
+ throw new RelayRequestError({
2178
+ status: 409,
2179
+ message: `Host tool ${body.toolName} was invoked from stale catalog revision ${body.catalogRevision}; the active revision is ${state.revision}.`
2180
+ });
2181
+ }
2182
+ const tool = state.tools.find((item) => item.name === body.toolName);
2183
+ if (tool == null) {
2184
+ throw new RelayRequestError({
2185
+ status: 404,
2186
+ message: `Host tool ${body.toolName} is not active in catalog revision ${state.revision}.`
2187
+ });
2188
+ }
2189
+ const correlationToken = randomBytes(32).toString("hex");
2190
+ turn.registerCorrelationInvocation({
2191
+ token: correlationToken,
2192
+ serverName,
2193
+ toolName: tool.name,
2194
+ input: body.input,
2195
+ order: nextInvocationOrder()
2196
+ });
2197
+ turn.emitToolCall({
2198
+ toolCallId: body.requestId,
2199
+ toolName: tool.name,
2200
+ input: body.input
2201
+ });
2202
+ let result;
2203
+ try {
2204
+ result = await turn.requestToolResult(body.requestId);
2205
+ } catch (error) {
2206
+ turn.removeCorrelationInvocation({ token: correlationToken });
2207
+ throw error;
2208
+ }
2209
+ turn.emitToolResult({
2210
+ toolCallId: body.requestId,
2211
+ toolName: tool.name,
2212
+ output: result.output,
2213
+ ...result.isError ? { isError: true } : {}
2214
+ });
2215
+ return {
2216
+ output: result.output,
2217
+ ...result.isError ? { isError: true } : {},
2218
+ correlationToken
2219
+ };
2220
+ }
2221
+ function waitForCatalogChange({
2222
+ state
2223
+ }) {
2224
+ let changeWaiter;
2225
+ const change = new Promise((resolve) => {
2226
+ changeWaiter = resolve;
2227
+ state.changeWaiters.add(changeWaiter);
2228
+ });
2229
+ let timer;
2230
+ const timeout = new Promise((resolve) => {
2231
+ timer = setTimeout(resolve, 2e4);
2232
+ timer.unref?.();
2233
+ });
2234
+ return Promise.race([change, timeout]).finally(() => {
2235
+ state.changeWaiters.delete(changeWaiter);
2236
+ if (timer != null) clearTimeout(timer);
2237
+ });
2238
+ }
2239
+ function resolveCatalogChanges({ state }) {
2240
+ for (const resolve of [...state.changeWaiters]) resolve();
2241
+ }
2242
+ function waitForCatalogRefresh({
2243
+ state,
2244
+ revision,
2245
+ timeoutMs
2246
+ }) {
2247
+ if (state.servedRevision >= revision) return Promise.resolve(true);
2248
+ if (state.closed) return Promise.resolve(false);
2249
+ let waiter;
2250
+ const refresh = new Promise((resolve) => {
2251
+ waiter = {
2252
+ revision,
2253
+ resolve
2254
+ };
2255
+ state.refreshWaiters.add(waiter);
2256
+ });
2257
+ let timer;
2258
+ const timeout = new Promise((resolve) => {
2259
+ timer = setTimeout(() => resolve(false), timeoutMs);
2260
+ timer.unref?.();
2261
+ });
2262
+ return Promise.race([refresh, timeout]).finally(() => {
2263
+ state.refreshWaiters.delete(waiter);
2264
+ if (timer != null) clearTimeout(timer);
2265
+ });
2266
+ }
2267
+ function resolveRefreshWaiters({
2268
+ state,
2269
+ closing
2270
+ }) {
2271
+ for (const waiter of [...state.refreshWaiters]) {
2272
+ if (closing || state.servedRevision >= waiter.revision) {
2273
+ waiter.resolve(!closing);
2274
+ }
2275
+ }
2276
+ }
2277
+ function catalogFingerprint({
2278
+ tools
2279
+ }) {
2280
+ return JSON.stringify(canonicalizeJSON2({ value: tools }));
2281
+ }
2282
+ function canonicalizeJSON2({ value }) {
2283
+ if (Array.isArray(value)) {
2284
+ return value.map((item) => canonicalizeJSON2({ value: item }));
2285
+ }
2286
+ if (!isRecord5(value)) return value;
2287
+ return Object.fromEntries(
2288
+ Object.keys(value).sort().filter((key) => value[key] !== void 0).map((key) => [key, canonicalizeJSON2({ value: value[key] })])
2289
+ );
2290
+ }
2291
+ async function readJSONBody({
2292
+ request
2293
+ }) {
2294
+ const chunks = [];
2295
+ let size = 0;
2296
+ for await (const chunk of request) {
2297
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
2298
+ size += buffer.length;
2299
+ if (size > 16 * 1024 * 1024) {
2300
+ throw new RelayRequestError({
2301
+ status: 413,
2302
+ message: "Host tool relay request is too large."
2303
+ });
2304
+ }
2305
+ chunks.push(buffer);
2306
+ }
2307
+ const text = Buffer.concat(chunks).toString("utf8");
2308
+ try {
2309
+ return await new Response(text, {
2310
+ headers: { "content-type": "application/json" }
2311
+ }).json();
2312
+ } catch {
2313
+ throw new RelayRequestError({
2314
+ status: 400,
2315
+ message: "Host tool relay request is not valid JSON."
2316
+ });
2317
+ }
2318
+ }
2319
+ function credentialsMatch({
2320
+ expected,
2321
+ actual
2322
+ }) {
2323
+ const expectedValue = Buffer.from(`Bearer ${expected}`);
2324
+ const actualValue = Buffer.from(actual ?? "");
2325
+ return expectedValue.length === actualValue.length && timingSafeEqual(expectedValue, actualValue);
2326
+ }
2327
+ function listen({ server }) {
2328
+ return new Promise((resolve, reject) => {
2329
+ server.once("error", reject);
2330
+ server.listen(0, "127.0.0.1", () => {
2331
+ server.off("error", reject);
2332
+ resolve();
2333
+ });
2334
+ });
2335
+ }
2336
+ function closeServer({ server }) {
2337
+ return new Promise((resolve, reject) => {
2338
+ server.close((error) => {
2339
+ if (error == null) resolve();
2340
+ else reject(error);
2341
+ });
2342
+ });
2343
+ }
2344
+ function isRecord5(value) {
2345
+ return value != null && typeof value === "object" && !Array.isArray(value);
2346
+ }
2347
+ var RelayRequestError = class extends Error {
2348
+ constructor({ status, message }) {
2349
+ super(message);
2350
+ this.name = "HostToolRelayRequestError";
2351
+ this.status = status;
2352
+ }
2353
+ };
2354
+
2355
+ // src/v1/bridge/refresh-host-tool-catalog.ts
2356
+ async function refreshHostToolCatalog({
2357
+ relay,
2358
+ tools,
2359
+ harnessId,
2360
+ timeoutMs
2361
+ }) {
2362
+ const catalog = relay.updateCatalog({ tools });
2363
+ if (!catalog.changed && tools.length === 0) return;
2364
+ if (await relay.waitForCatalogRefresh({
2365
+ revision: catalog.revision,
2366
+ timeoutMs
2367
+ })) {
2368
+ return;
2369
+ }
2370
+ throw new HarnessBridgeCapabilityUnsupportedError({
2371
+ harnessId,
2372
+ message: `The ACP implementation did not load the active harness-owned MCP tool catalog to revision ${catalog.revision} before the next prompt, so host tools cannot be exposed safely.`
2373
+ });
2374
+ }
2375
+
2376
+ // src/v1/bridge/permission-controller.ts
2377
+ var permissionRequestCounter = 0;
2378
+ function createACPPermissionController({
2379
+ turn,
2380
+ sessionId,
2381
+ permissionMode,
2382
+ hasPermissionModeMapping,
2383
+ emitToolCall,
2384
+ claimHostToolPermission
2385
+ }) {
2386
+ const pendingPermissions = /* @__PURE__ */ new Map();
2387
+ return {
2388
+ async requestPermission(request) {
2389
+ if (request.sessionId !== sessionId) {
2390
+ turn.emitWarning({
2391
+ message: `ACP permission request for session ${JSON.stringify(request.sessionId)} was cancelled because the active session is ${JSON.stringify(sessionId)}.`
2392
+ });
2393
+ return cancelled();
2394
+ }
2395
+ const allowOnce = request.options.find(
2396
+ (option) => option.kind === "allow_once"
2397
+ );
2398
+ const rejectOnce = request.options.find(
2399
+ (option) => option.kind === "reject_once"
2400
+ );
2401
+ if (allowOnce == null || rejectOnce == null) {
2402
+ const missing = [
2403
+ ...allowOnce == null ? ["allow_once"] : [],
2404
+ ...rejectOnce == null ? ["reject_once"] : []
2405
+ ];
2406
+ turn.emitWarning({
2407
+ message: `ACP permission request for tool call ${JSON.stringify(request.toolCall.toolCallId)} was cancelled because it did not advertise ${missing.join(" and ")}.`
2408
+ });
2409
+ return cancelled();
2410
+ }
2411
+ if (claimHostToolPermission({ toolCall: request.toolCall })) {
2412
+ return {
2413
+ outcome: {
2414
+ outcome: "selected",
2415
+ optionId: allowOnce.optionId
2416
+ }
2417
+ };
2418
+ }
2419
+ if (!hasPermissionModeMapping && shouldAutoApprove({
2420
+ permissionMode,
2421
+ kind: request.toolCall.kind
2422
+ })) {
2423
+ return {
2424
+ outcome: {
2425
+ outcome: "selected",
2426
+ optionId: allowOnce.optionId
2427
+ }
2428
+ };
2429
+ }
2430
+ const approvalId = `acp-permission-${++permissionRequestCounter}`;
2431
+ let cancelPermission;
2432
+ const cancellation = new Promise((resolve) => {
2433
+ cancelPermission = () => resolve(void 0);
2434
+ });
2435
+ pendingPermissions.set(approvalId, {
2436
+ cancel: cancelPermission
2437
+ });
2438
+ const approval = turn.requestToolApproval(approvalId);
2439
+ emitToolCall({ toolCall: request.toolCall });
2440
+ turn.emit({
2441
+ type: "tool-approval-request",
2442
+ approvalId,
2443
+ toolCallId: request.toolCall.toolCallId
2444
+ });
2445
+ try {
2446
+ const response = await Promise.race([approval, cancellation]);
2447
+ if (response == null) return cancelled();
2448
+ return {
2449
+ outcome: {
2450
+ outcome: "selected",
2451
+ optionId: response.approved ? allowOnce.optionId : rejectOnce.optionId
2452
+ }
2453
+ };
2454
+ } finally {
2455
+ pendingPermissions.delete(approvalId);
2456
+ }
2457
+ },
2458
+ cancelAll() {
2459
+ for (const pending of pendingPermissions.values()) {
2460
+ pending.cancel();
2461
+ }
2462
+ pendingPermissions.clear();
2463
+ }
2464
+ };
2465
+ }
2466
+ function shouldAutoApprove({
2467
+ permissionMode,
2468
+ kind
2469
+ }) {
2470
+ if (permissionMode === "allow-all") return true;
2471
+ if (kind === "read" || kind === "search" || kind === "think" || kind === "fetch") {
2472
+ return true;
2473
+ }
2474
+ return permissionMode === "allow-edits" && (kind === "edit" || kind === "delete" || kind === "move");
2475
+ }
2476
+ function cancelled() {
2477
+ return { outcome: { outcome: "cancelled" } };
2478
+ }
2479
+
2480
+ // src/v1/bridge/permission-mode.ts
2481
+ import * as acp from "@agentclientprotocol/sdk";
2482
+ var PERMISSION_MODES = [
2483
+ "allow-reads",
2484
+ "allow-edits",
2485
+ "allow-all"
2486
+ ];
2487
+ async function configureACPPermissionMode({
2488
+ agent,
2489
+ sessionId,
2490
+ sessionConfiguration,
2491
+ permissionModeMapping,
2492
+ permissionMode,
2493
+ harnessId
2494
+ }) {
2495
+ const target = permissionModeMapping[permissionMode];
2496
+ if (target == null) {
2497
+ throw unsupported({
2498
+ harnessId,
2499
+ message: `Permission mode ${JSON.stringify(permissionMode)} is not supported by this ACP harness.` + (permissionModeMapping["allow-all"] == null ? "" : ` Use permissionMode: 'allow-all'.`)
2500
+ });
2501
+ }
2502
+ for (const mappedPermissionMode of PERMISSION_MODES) {
2503
+ const mappedTarget = permissionModeMapping[mappedPermissionMode];
2504
+ if (mappedTarget != null) {
2505
+ validateTarget({
2506
+ target: mappedTarget,
2507
+ permissionMode: mappedPermissionMode,
2508
+ sessionConfiguration,
2509
+ harnessId
2510
+ });
2511
+ }
2512
+ }
2513
+ if (target.type === "session-mode") {
2514
+ await agent.request(acp.methods.agent.session.setMode, {
2515
+ sessionId,
2516
+ modeId: target.modeId
2517
+ });
2518
+ return;
2519
+ }
2520
+ await agent.request(acp.methods.agent.session.setConfigOption, {
2521
+ sessionId,
2522
+ configId: target.configId,
2523
+ value: target.value,
2524
+ ...typeof target.value === "boolean" ? { type: "boolean" } : {}
2525
+ });
2526
+ }
2527
+ function validateTarget({
2528
+ target,
2529
+ permissionMode,
2530
+ sessionConfiguration,
2531
+ harnessId
2532
+ }) {
2533
+ if (target.type === "session-mode") {
2534
+ const availableModeIds = sessionConfiguration.modes?.availableModes.map((mode) => mode.id) ?? [];
2535
+ if (!availableModeIds.includes(target.modeId)) {
2536
+ throw unsupported({
2537
+ harnessId,
2538
+ message: `ACP permission mapping for ${JSON.stringify(permissionMode)} requires session mode ${JSON.stringify(target.modeId)}, but the agent advertised ${formatChoices({ values: availableModeIds })}.`
2539
+ });
2540
+ }
2541
+ return;
2542
+ }
2543
+ const configOption = sessionConfiguration.configOptions?.find(
2544
+ (option) => option.id === target.configId
2545
+ );
2546
+ if (configOption == null) {
2547
+ const availableConfigIds = sessionConfiguration.configOptions?.map((option) => option.id) ?? [];
2548
+ throw unsupported({
2549
+ harnessId,
2550
+ message: `ACP permission mapping for ${JSON.stringify(permissionMode)} requires session configuration ${JSON.stringify(target.configId)}, but the agent advertised ${formatChoices({ values: availableConfigIds })}.`
2551
+ });
2552
+ }
2553
+ if (configOption.type === "boolean") {
2554
+ if (typeof target.value !== "boolean") {
2555
+ throw unsupported({
2556
+ harnessId,
2557
+ message: `ACP permission mapping for ${JSON.stringify(permissionMode)} assigns string value ${JSON.stringify(target.value)} to boolean session configuration ${JSON.stringify(target.configId)}.`
2558
+ });
2559
+ }
2560
+ return;
2561
+ }
2562
+ if (typeof target.value !== "string") {
2563
+ throw unsupported({
2564
+ harnessId,
2565
+ message: `ACP permission mapping for ${JSON.stringify(permissionMode)} assigns boolean value ${JSON.stringify(target.value)} to select session configuration ${JSON.stringify(target.configId)}.`
2566
+ });
2567
+ }
2568
+ const availableValues = configOption.options.flatMap(
2569
+ (option) => "options" in option ? option.options.map((value) => value.value) : [option.value]
2570
+ );
2571
+ if (!availableValues.includes(target.value)) {
2572
+ throw unsupported({
2573
+ harnessId,
2574
+ message: `ACP permission mapping for ${JSON.stringify(permissionMode)} requires value ${JSON.stringify(target.value)} for session configuration ${JSON.stringify(target.configId)}, but the agent advertised ${formatChoices({ values: availableValues })}.`
2575
+ });
2576
+ }
2577
+ }
2578
+ function formatChoices({ values }) {
2579
+ return values.length === 0 ? "no matching choices" : values.map((value) => JSON.stringify(value)).join(", ");
2580
+ }
2581
+ function unsupported({
2582
+ harnessId,
2583
+ message
2584
+ }) {
2585
+ return new HarnessBridgeCapabilityUnsupportedError({ harnessId, message });
2586
+ }
2587
+
2588
+ // src/v1/bridge/recovered-session.ts
2589
+ import * as acp2 from "@agentclientprotocol/sdk";
2590
+ function assertACPResumeCapability({
2591
+ initialization,
2592
+ harnessId
2593
+ }) {
2594
+ const sessionCapabilities = initialization.agentCapabilities?.sessionCapabilities;
2595
+ if (sessionCapabilities?.resume == null || sessionCapabilities.resume === false) {
2596
+ throw new HarnessBridgeCapabilityUnsupportedError({
2597
+ harnessId,
2598
+ message: "ACP process-loss rerun requires the agent to advertise sessionCapabilities.resume; a fresh unrelated ACP session will not be created."
2599
+ });
2600
+ }
2601
+ }
2602
+ function createACPRecoveredSession({
2603
+ agent,
2604
+ sessionId,
2605
+ restorationResponse,
2606
+ updates
2607
+ }) {
2608
+ let disposed = false;
2609
+ return {
2610
+ sessionId,
2611
+ newSessionResponse: {
2612
+ sessionId,
2613
+ ...restorationResponse
2614
+ },
2615
+ prompt: async (prompt) => {
2616
+ if (disposed) {
2617
+ throw new Error("Recovered ACP session is disposed.");
2618
+ }
2619
+ updates.clearErrors();
2620
+ const response = agent.request(
2621
+ acp2.methods.agent.session.prompt,
2622
+ { sessionId, prompt }
2623
+ );
2624
+ void response.then(
2625
+ (value) => {
2626
+ updates.enqueue({
2627
+ kind: "stop",
2628
+ response: value,
2629
+ stopReason: value.stopReason
2630
+ });
2631
+ },
2632
+ (error) => updates.reject(error)
2633
+ );
2634
+ return response;
2635
+ },
2636
+ nextUpdate: () => updates.next(),
2637
+ dispose: () => {
2638
+ if (disposed) return;
2639
+ disposed = true;
2640
+ updates.fail(new Error("Recovered ACP session disposed."));
2641
+ }
2642
+ };
2643
+ }
2644
+ function createACPRecoveredSessionUpdates() {
2645
+ const values = [];
2646
+ const waiters = [];
2647
+ let failure;
2648
+ let failed = false;
2649
+ return {
2650
+ enqueue(value) {
2651
+ if (failed) return;
2652
+ const waiter = waiters.shift();
2653
+ if (waiter == null) values.push({ type: "value", value });
2654
+ else waiter.resolve(value);
2655
+ },
2656
+ reject(error) {
2657
+ if (failed) return;
2658
+ const waiter = waiters.shift();
2659
+ if (waiter == null) values.push({ type: "error", error });
2660
+ else waiter.reject(error);
2661
+ },
2662
+ clearErrors() {
2663
+ for (let index = values.length - 1; index >= 0; index--) {
2664
+ if (values[index]?.type === "error") values.splice(index, 1);
2665
+ }
2666
+ },
2667
+ fail(error) {
2668
+ if (failed) return;
2669
+ failed = true;
2670
+ failure = error;
2671
+ for (const waiter of waiters.splice(0)) waiter.reject(error);
2672
+ },
2673
+ next() {
2674
+ const entry = values.shift();
2675
+ if (entry?.type === "value") return Promise.resolve(entry.value);
2676
+ if (entry?.type === "error") {
2677
+ return Promise.reject(
2678
+ entry.error instanceof Error ? entry.error : new Error("Recovered ACP session update failed.")
2679
+ );
2680
+ }
2681
+ if (failed) {
2682
+ return Promise.reject(
2683
+ failure instanceof Error ? failure : new Error("Recovered ACP session update stream failed.")
2684
+ );
2685
+ }
2686
+ return new Promise((resolve, reject) => {
2687
+ waiters.push({ resolve, reject });
2688
+ });
2689
+ }
2690
+ };
2691
+ }
2692
+
2693
+ // src/v1/bridge/session-lifecycle.ts
2694
+ import * as acp3 from "@agentclientprotocol/sdk";
2695
+ function resolveACPSessionRestorationMethod({
2696
+ initialization,
2697
+ harnessId
2698
+ }) {
2699
+ const sessionCapabilities = initialization.agentCapabilities?.sessionCapabilities;
2700
+ if (sessionCapabilities?.resume != null && sessionCapabilities.resume !== false) {
2701
+ return "resume";
2702
+ }
2703
+ if (initialization.agentCapabilities?.loadSession === true) {
2704
+ return "load";
2705
+ }
2706
+ throw new HarnessBridgeCapabilityUnsupportedError({
2707
+ harnessId,
2708
+ message: "Cold ACP session restoration requires the agent to advertise sessionCapabilities.resume or loadSession; a fresh unrelated ACP session will not be created."
2709
+ });
2710
+ }
2711
+ async function restoreACPBridgeSession({
2712
+ agent,
2713
+ initialization,
2714
+ sessionId,
2715
+ cwd,
2716
+ mcpServers,
2717
+ meta,
2718
+ harnessId,
2719
+ setHistoricalUpdatesSuppressed,
2720
+ discardCapturedHistory
2721
+ }) {
2722
+ const method = resolveACPSessionRestorationMethod({
2723
+ initialization,
2724
+ harnessId
2725
+ });
2726
+ setHistoricalUpdatesSuppressed({ suppressed: true });
2727
+ try {
2728
+ const request = {
2729
+ sessionId,
2730
+ cwd,
2731
+ mcpServers: [...mcpServers],
2732
+ ...meta == null ? {} : { _meta: meta }
2733
+ };
2734
+ const response = method === "resume" ? await agent.request(acp3.methods.agent.session.resume, request) : await agent.request(
2735
+ acp3.methods.agent.session.load,
2736
+ request
2737
+ );
2738
+ discardCapturedHistory();
2739
+ return { method, response };
2740
+ } finally {
2741
+ setHistoricalUpdatesSuppressed({ suppressed: false });
2742
+ }
2743
+ }
2744
+
2745
+ // src/v1/bridge/index.ts
2746
+ var HOST_TOOL_MCP_SERVER_NAME = "ai-sdk-harness-tools";
2747
+ var CATALOG_REFRESH_TIMEOUT_MS = 1e4;
2748
+ var args = parseArgs({ args: argv.slice(2) });
2749
+ var workDir = requireArg({ value: args.workDir, name: "--workdir" });
2750
+ var bridgeStateDir = requireArg({
2751
+ value: args.bridgeStateDir,
2752
+ name: "--bridge-state-dir"
2753
+ });
2754
+ var implementationDir = requireArg({
2755
+ value: args.implementationDir,
2756
+ name: "--implementation-dir"
2757
+ });
2758
+ var bridgeType = requireArg({
2759
+ value: args.bridgeType,
2760
+ name: "--bridge-type"
2761
+ });
2762
+ var implementation = await readImplementationDescriptor({
2763
+ path: `${implementationDir}/implementation.json`
2764
+ });
2765
+ var bridgeConfiguration = await readACPBridgeEnvironment({ env: processEnv });
2766
+ var child;
2767
+ var agentResponseStreamFailure;
2768
+ var connection;
2769
+ var session;
2770
+ var recoveredSessionUpdates;
2771
+ var recoveredSessionId;
2772
+ var sessionConfigurationFingerprint;
2773
+ var streamCapture;
2774
+ var initializationDiagnostic;
2775
+ var hostToolRelay;
2776
+ var catalogRefreshError;
2777
+ var sessionConfigurationFailure;
2778
+ var historicalUpdatesSuppressed = false;
2779
+ var coldRestorationMethod;
2780
+ var activePermissionController;
2781
+ await runBridge({
2782
+ bridgeType,
2783
+ bridgeStateDir,
2784
+ onStart: runTurn,
2785
+ onStop: () => session == null ? {} : { sessionId: session.sessionId },
2786
+ onExit: () => {
2787
+ let exited = false;
2788
+ const finish = () => {
2789
+ if (exited) return;
2790
+ exited = true;
2791
+ session?.dispose();
2792
+ connection?.close();
2793
+ child?.kill();
2794
+ process.exit(0);
2795
+ };
2796
+ if (hostToolRelay == null) {
2797
+ finish();
2798
+ return;
2799
+ }
2800
+ const timer = setTimeout(finish, 1e3);
2801
+ timer.unref();
2802
+ void hostToolRelay.close().finally(() => {
2803
+ clearTimeout(timer);
2804
+ finish();
2805
+ });
2806
+ }
2807
+ });
2808
+ async function runTurn(start, turn) {
2809
+ try {
2810
+ await ensureSession({ start, turn });
2811
+ } catch (error) {
2812
+ if (HarnessBridgeCapabilityUnsupportedError.isInstance(error)) throw error;
2813
+ throw createACPBridgeError({
2814
+ stage: "session initialization",
2815
+ cause: error
2816
+ });
2817
+ }
2818
+ const activeSession = session;
2819
+ if (activeSession == null) {
2820
+ throw new Error("ACP session initialization did not produce a session.");
2821
+ }
2822
+ const activeAgentResponseStreamFailure = agentResponseStreamFailure;
2823
+ if (activeAgentResponseStreamFailure == null) {
2824
+ throw new Error(
2825
+ "ACP session initialization did not start stderr monitoring."
2826
+ );
2827
+ }
2828
+ if (start.recoveryMode?.type === "lossy-rerun") {
2829
+ const marker = {
2830
+ type: "acp-recovery",
2831
+ mode: "lossy-rerun",
2832
+ reason: start.recoveryMode.reason
2833
+ };
2834
+ turn.emit({ type: "raw", rawValue: marker });
2835
+ turn.bridgeLog({
2836
+ level: "warn",
2837
+ subsystem: "acp.recovery",
2838
+ message: "The ACP process was replaced; the original prompt is being rerun against the resumed ACP session.",
2839
+ attrs: marker
2840
+ });
2841
+ turn.emitWarning({
2842
+ message: "ACP process-loss recovery is rerunning the interrupted prompt and may repeat work."
2843
+ });
2844
+ }
2845
+ turn.emit({
2846
+ type: "bridge-thread",
2847
+ threadId: activeSession.sessionId
2848
+ });
2849
+ if (initializationDiagnostic != null) {
2850
+ turn.bridgeLog({
2851
+ level: "info",
2852
+ subsystem: "acp.protocol",
2853
+ message: "ACP session initialized.",
2854
+ attrs: initializationDiagnostic
2855
+ });
2856
+ }
2857
+ if (start.recoveryMode?.type === "cold-restore") {
2858
+ const marker = {
2859
+ type: "acp-session-restored",
2860
+ method: coldRestorationMethod
2861
+ };
2862
+ turn.emit({ type: "raw", rawValue: marker });
2863
+ turn.bridgeLog({
2864
+ level: "info",
2865
+ subsystem: "acp.lifecycle",
2866
+ message: "ACP session restored in a replacement bridge process.",
2867
+ attrs: marker
2868
+ });
2869
+ turn.emit({
2870
+ type: "finish",
2871
+ finishReason: {
2872
+ unified: "stop",
2873
+ raw: "acp-session-restored"
2874
+ },
2875
+ totalUsage: unknownUsage2()
2876
+ });
2877
+ return;
2878
+ }
2879
+ let rejectCancellationFailure;
2880
+ const cancellationFailure = new Promise((_, reject) => {
2881
+ rejectCancellationFailure = reject;
2882
+ });
2883
+ void cancellationFailure.catch(() => {
2884
+ });
2885
+ let cancellationRequested = false;
2886
+ let cancellationFailureError;
2887
+ const cancel = async () => {
2888
+ if (cancellationRequested) return;
2889
+ cancellationRequested = true;
2890
+ activePermissionController?.cancelAll();
2891
+ try {
2892
+ if (connection == null) {
2893
+ throw new Error("ACP connection closed before cancellation.");
2894
+ }
2895
+ await connection.agent.notify(acp4.methods.agent.session.cancel, {
2896
+ sessionId: activeSession.sessionId
2897
+ });
2898
+ } catch (error) {
2899
+ cancellationFailureError = createACPBridgeError({
2900
+ stage: "session cancellation",
2901
+ cause: error
2902
+ });
2903
+ rejectCancellationFailure(cancellationFailureError);
2904
+ }
2905
+ };
2906
+ turn.emit({ type: "stream-start" });
2907
+ const emitStreamEvent = createEmitStreamEvent({
2908
+ emit: (event) => turn.emit(event),
2909
+ builtinTools: start.builtinTools,
2910
+ hostToolServerName: HOST_TOOL_MCP_SERVER_NAME,
2911
+ hostTools: start.tools ?? []
2912
+ });
2913
+ const permissionController = createACPPermissionController({
2914
+ turn,
2915
+ sessionId: activeSession.sessionId,
2916
+ permissionMode: start.permissionMode ?? "allow-all",
2917
+ hasPermissionModeMapping: start.permissionModeMapping != null,
2918
+ emitToolCall: emitStreamEvent.permissionToolCall,
2919
+ claimHostToolPermission: emitStreamEvent.claimHostToolPermission
2920
+ });
2921
+ activePermissionController = permissionController;
2922
+ const relayTurn = {
2923
+ emitToolCall: emitStreamEvent.hostToolCall,
2924
+ emitToolResult: emitStreamEvent.hostToolResult,
2925
+ requestToolResult: (toolCallId) => turn.requestToolResult(toolCallId),
2926
+ registerCorrelationInvocation: emitStreamEvent.registerHostToolCorrelationInvocation,
2927
+ removeCorrelationInvocation: emitStreamEvent.removeHostToolCorrelationInvocation
2928
+ };
2929
+ hostToolRelay?.bindTurn({ turn: relayTurn });
2930
+ try {
2931
+ void activeSession.prompt(start.prompt);
2932
+ if (turn.abortSignal.aborted) {
2933
+ await cancel();
2934
+ } else {
2935
+ turn.abortSignal.addEventListener("abort", () => void cancel(), {
2936
+ once: true
2937
+ });
2938
+ }
2939
+ for (; ; ) {
2940
+ let message;
2941
+ try {
2942
+ message = await Promise.race([
2943
+ activeSession.nextUpdate(),
2944
+ cancellationFailure,
2945
+ activeAgentResponseStreamFailure
2946
+ ]);
2947
+ } catch (error) {
2948
+ for (const rawValue of streamCapture?.drainRawValues() ?? []) {
2949
+ emitStreamEvent.raw({ rawValue });
2950
+ }
2951
+ emitStreamEvent.close();
2952
+ if (error === cancellationFailureError) throw error;
2953
+ throw createACPBridgeError({
2954
+ stage: "prompt update stream",
2955
+ cause: error
2956
+ });
2957
+ }
2958
+ if (message.kind === "session_update") {
2959
+ const captured = streamCapture?.takeForUpdate({
2960
+ update: message.update
2961
+ });
2962
+ for (const rawValue of captured?.precedingRawValues ?? []) {
2963
+ emitStreamEvent.raw({ rawValue });
2964
+ }
2965
+ emitStreamEvent.message({
2966
+ message,
2967
+ rawUpdate: captured?.rawUpdate
2968
+ });
2969
+ continue;
2970
+ }
2971
+ for (const rawValue of streamCapture?.drainRawValues() ?? []) {
2972
+ emitStreamEvent.raw({ rawValue });
2973
+ }
2974
+ if (emitStreamEvent.message({ message })) return;
2975
+ }
2976
+ } finally {
2977
+ permissionController.cancelAll();
2978
+ activePermissionController = void 0;
2979
+ hostToolRelay?.unbindTurn({ turn: relayTurn });
2980
+ }
2981
+ }
2982
+ async function ensureSession({
2983
+ start,
2984
+ turn
2985
+ }) {
2986
+ if (sessionConfigurationFailure != null) {
2987
+ throw sessionConfigurationFailure.error;
2988
+ }
2989
+ const fingerprint = JSON.stringify({
2990
+ authentication: bridgeConfiguration.authentication,
2991
+ providerAuthentication: bridgeConfiguration.providerAuthentication,
2992
+ sessionMeta: bridgeConfiguration.sessionMeta,
2993
+ permissionMode: start.permissionMode,
2994
+ permissionModeMapping: start.permissionModeMapping
2995
+ });
2996
+ if (session != null) {
2997
+ if (catalogRefreshError != null) throw catalogRefreshError;
2998
+ if (sessionConfigurationFingerprint !== fingerprint) {
2999
+ throw new Error(
3000
+ "ACP authentication and session profile settings cannot change after the ACP session has started."
3001
+ );
3002
+ }
3003
+ const relay = hostToolRelay;
3004
+ if (relay == null) {
3005
+ throw new Error("The host tool MCP relay is unavailable.");
3006
+ }
3007
+ try {
3008
+ await refreshHostToolCatalog({
3009
+ relay,
3010
+ tools: start.tools ?? [],
3011
+ harnessId: bridgeType,
3012
+ timeoutMs: CATALOG_REFRESH_TIMEOUT_MS
3013
+ });
3014
+ } catch (error) {
3015
+ if (HarnessBridgeCapabilityUnsupportedError.isInstance(error)) {
3016
+ catalogRefreshError = error;
3017
+ }
3018
+ throw error;
3019
+ }
3020
+ return;
3021
+ }
3022
+ const clientApp = resolveClientApp();
3023
+ const gateway = bridgeConfiguration.providerAuthentication?.type === "ai-gateway" ? resolveGatewayValues({ clientApp }) : void 0;
3024
+ const authentication = bridgeConfiguration.authentication;
3025
+ const launchEnv = resolveACPLaunchEnvironment({
3026
+ providerAuthentication: bridgeConfiguration.providerAuthentication,
3027
+ gateway
3028
+ });
3029
+ child = spawn(
3030
+ `${implementationDir}/node_modules/.bin/${implementation.executable}`,
3031
+ [...implementation.args],
3032
+ {
3033
+ cwd: workDir,
3034
+ env: createChildEnvironment({ launchEnv }),
3035
+ shell: false,
3036
+ stdio: ["pipe", "pipe", "pipe"]
3037
+ }
3038
+ );
3039
+ agentResponseStreamFailure = monitorACPAgentStderr({
3040
+ stderr: child.stderr,
3041
+ onStderrLine: (line) => {
3042
+ turn.bridgeLog({
3043
+ level: "warn",
3044
+ subsystem: "acp.agent.stderr",
3045
+ message: line
3046
+ });
3047
+ }
3048
+ });
3049
+ const input = Writable.toWeb(child.stdin);
3050
+ const output = Readable.toWeb(child.stdout);
3051
+ const capturedStream = captureACPStream({
3052
+ stream: acp4.ndJsonStream(input, output)
3053
+ });
3054
+ streamCapture = capturedStream.capture;
3055
+ connection = acp4.client({ name: clientApp.name }).onRequest(
3056
+ acp4.methods.client.session.requestPermission,
3057
+ ({ params }) => activePermissionController?.requestPermission(params) ?? {
3058
+ outcome: { outcome: "cancelled" }
3059
+ }
3060
+ ).onNotification(acp4.methods.client.session.update, ({ params }) => {
3061
+ if (params.sessionId !== recoveredSessionId) return;
3062
+ if (historicalUpdatesSuppressed) return;
3063
+ recoveredSessionUpdates?.enqueue({
3064
+ kind: "session_update",
3065
+ notification: params,
3066
+ update: params.update
3067
+ });
3068
+ }).connect(capturedStream.stream);
3069
+ const initializeRequest = createACPInitializeRequest({
3070
+ protocolVersion: acp4.PROTOCOL_VERSION,
3071
+ clientApp,
3072
+ authentication,
3073
+ supportsBooleanSessionConfigOptions: Object.values(
3074
+ start.permissionModeMapping ?? {}
3075
+ ).some(
3076
+ (target) => target?.type === "session-config-option" && typeof target.value === "boolean"
3077
+ )
3078
+ });
3079
+ const initialization = await connection.agent.request(acp4.methods.agent.initialize, initializeRequest);
3080
+ validateACPProtocolVersion({
3081
+ requested: acp4.PROTOCOL_VERSION,
3082
+ initialization
3083
+ });
3084
+ if (authentication != null) {
3085
+ await authenticate({
3086
+ agent: connection.agent,
3087
+ initialization,
3088
+ authentication
3089
+ });
3090
+ }
3091
+ const tools = start.tools ?? [];
3092
+ const catalogPath = `${bridgeStateDir}/host-tools.json`;
3093
+ await writeFile2(catalogPath, JSON.stringify(tools), { mode: 384 });
3094
+ hostToolRelay = await startHostToolRelay({
3095
+ tools,
3096
+ serverName: HOST_TOOL_MCP_SERVER_NAME
3097
+ });
3098
+ const mcpServers = [
3099
+ {
3100
+ name: HOST_TOOL_MCP_SERVER_NAME,
3101
+ command: process.execPath,
3102
+ args: [fileURLToPath(new URL("./host-tool-mcp.mjs", import.meta.url))],
3103
+ env: [
3104
+ {
3105
+ name: "AI_SDK_ACP_HOST_TOOLS_FILE",
3106
+ value: catalogPath
3107
+ },
3108
+ {
3109
+ name: "AI_SDK_ACP_HOST_TOOL_RELAY_URL",
3110
+ value: hostToolRelay.url
3111
+ },
3112
+ {
3113
+ name: "AI_SDK_ACP_HOST_TOOL_RELAY_CREDENTIAL",
3114
+ value: hostToolRelay.credential
3115
+ }
3116
+ ]
3117
+ }
3118
+ ];
3119
+ let createdSession;
3120
+ if (start.recoveryMode?.type === "lossy-rerun") {
3121
+ assertACPResumeCapability({ initialization, harnessId: bridgeType });
3122
+ recoveredSessionId = start.recoveryMode.acpSessionId;
3123
+ recoveredSessionUpdates = createACPRecoveredSessionUpdates();
3124
+ const resumeResponse = await connection.agent.request(acp4.methods.agent.session.resume, {
3125
+ sessionId: recoveredSessionId,
3126
+ cwd: workDir,
3127
+ mcpServers,
3128
+ ...bridgeConfiguration.sessionMeta == null ? {} : { _meta: bridgeConfiguration.sessionMeta }
3129
+ });
3130
+ createdSession = createACPRecoveredSession({
3131
+ agent: connection.agent,
3132
+ sessionId: recoveredSessionId,
3133
+ restorationResponse: resumeResponse,
3134
+ updates: recoveredSessionUpdates
3135
+ });
3136
+ } else if (start.recoveryMode?.type === "cold-restore") {
3137
+ recoveredSessionId = start.recoveryMode.acpSessionId;
3138
+ recoveredSessionUpdates = createACPRecoveredSessionUpdates();
3139
+ const restored = await restoreACPBridgeSession({
3140
+ agent: connection.agent,
3141
+ initialization,
3142
+ sessionId: recoveredSessionId,
3143
+ cwd: workDir,
3144
+ mcpServers,
3145
+ meta: bridgeConfiguration.sessionMeta,
3146
+ harnessId: bridgeType,
3147
+ setHistoricalUpdatesSuppressed: ({ suppressed }) => {
3148
+ historicalUpdatesSuppressed = suppressed;
3149
+ },
3150
+ discardCapturedHistory: () => {
3151
+ streamCapture?.drainRawValues();
3152
+ }
3153
+ });
3154
+ coldRestorationMethod = restored.method;
3155
+ createdSession = createACPRecoveredSession({
3156
+ agent: connection.agent,
3157
+ sessionId: recoveredSessionId,
3158
+ restorationResponse: restored.response,
3159
+ updates: recoveredSessionUpdates
3160
+ });
3161
+ } else {
3162
+ createdSession = await connection.agent.buildSession({
3163
+ cwd: workDir,
3164
+ mcpServers,
3165
+ ...bridgeConfiguration.sessionMeta == null ? {} : { _meta: bridgeConfiguration.sessionMeta }
3166
+ }).start();
3167
+ }
3168
+ try {
3169
+ await refreshHostToolCatalog({
3170
+ relay: hostToolRelay,
3171
+ tools,
3172
+ harnessId: bridgeType,
3173
+ timeoutMs: CATALOG_REFRESH_TIMEOUT_MS
3174
+ });
3175
+ if (start.permissionModeMapping != null) {
3176
+ await configureACPPermissionMode({
3177
+ agent: connection.agent,
3178
+ sessionId: createdSession.sessionId,
3179
+ sessionConfiguration: createdSession.newSessionResponse,
3180
+ permissionModeMapping: start.permissionModeMapping,
3181
+ permissionMode: start.permissionMode ?? "allow-all",
3182
+ harnessId: bridgeType
3183
+ });
3184
+ }
3185
+ } catch (error) {
3186
+ createdSession.dispose();
3187
+ if (HarnessBridgeCapabilityUnsupportedError.isInstance(error)) {
3188
+ catalogRefreshError = error;
3189
+ }
3190
+ sessionConfigurationFailure = { error };
3191
+ throw error;
3192
+ }
3193
+ session = createdSession;
3194
+ initializationDiagnostic = createACPInitializationDiagnostic({
3195
+ initialization,
3196
+ sessionId: createdSession.sessionId
3197
+ });
3198
+ sessionConfigurationFingerprint = fingerprint;
3199
+ }
3200
+ function unknownUsage2() {
3201
+ return {
3202
+ inputTokens: {
3203
+ total: void 0,
3204
+ noCache: void 0,
3205
+ cacheRead: void 0,
3206
+ cacheWrite: void 0
3207
+ },
3208
+ outputTokens: {
3209
+ total: void 0,
3210
+ text: void 0,
3211
+ reasoning: void 0
3212
+ }
3213
+ };
3214
+ }
3215
+ async function authenticate({
3216
+ agent,
3217
+ initialization,
3218
+ authentication
3219
+ }) {
3220
+ assertACPAuthenticationMethod({
3221
+ initialization,
3222
+ methodId: authentication.methodId
3223
+ });
3224
+ await agent.request(acp4.methods.agent.authenticate, {
3225
+ methodId: authentication.methodId,
3226
+ ...authentication.meta == null ? {} : { _meta: authentication.meta }
3227
+ });
3228
+ }
3229
+ function resolveClientApp() {
3230
+ const name2 = processEnv.AI_SDK_ACP_CLIENT_APP_NAME;
3231
+ const version = processEnv.AI_SDK_ACP_CLIENT_APP_VERSION;
3232
+ if (name2 == null || version == null) {
3233
+ throw new Error("ACP client app values were not supplied to the bridge.");
3234
+ }
3235
+ return { name: name2, version };
3236
+ }
3237
+ function resolveGatewayValues({
3238
+ clientApp
3239
+ }) {
3240
+ const apiKey = processEnv.AI_SDK_ACP_GATEWAY_API_KEY;
3241
+ const baseUrl = processEnv.AI_SDK_ACP_GATEWAY_BASE_URL;
3242
+ if (apiKey == null || baseUrl == null) {
3243
+ throw new Error(
3244
+ "AI Gateway profile values were not supplied to the ACP bridge."
3245
+ );
3246
+ }
3247
+ return {
3248
+ apiKey,
3249
+ baseUrl,
3250
+ clientAppName: clientApp.name,
3251
+ clientAppVersion: clientApp.version
3252
+ };
3253
+ }
3254
+ function createChildEnvironment({
3255
+ launchEnv
3256
+ }) {
3257
+ const blocked = /* @__PURE__ */ new Set([
3258
+ "BRIDGE_CHANNEL_TOKEN",
3259
+ "BRIDGE_WS_PORT",
3260
+ "AI_SDK_ACP_GATEWAY_API_KEY",
3261
+ "AI_SDK_ACP_GATEWAY_BASE_URL",
3262
+ "AI_SDK_ACP_CLIENT_APP_NAME",
3263
+ "AI_SDK_ACP_CLIENT_APP_VERSION",
3264
+ ACP_BRIDGE_CONFIGURATION_ENV
3265
+ ]);
3266
+ return {
3267
+ ...Object.fromEntries(
3268
+ Object.entries(processEnv).filter(
3269
+ ([key, value]) => !blocked.has(key) && value != null
3270
+ )
3271
+ ),
3272
+ ...launchEnv
3273
+ };
3274
+ }
3275
+ function isRecord6(value) {
3276
+ return value != null && typeof value === "object" && !Array.isArray(value);
3277
+ }
3278
+ async function readImplementationDescriptor({
3279
+ path
3280
+ }) {
3281
+ const text = await readFile(path, "utf8");
3282
+ const value = await new Response(text, {
3283
+ headers: { "content-type": "application/json" }
3284
+ }).json();
3285
+ if (!isRecord6(value) || typeof value.executable !== "string" || !Array.isArray(value.args) || !value.args.every((item) => typeof item === "string") || !Array.isArray(value.envKeys) || !value.envKeys.every((item) => typeof item === "string")) {
3286
+ throw new Error("Invalid ACP implementation descriptor.");
3287
+ }
3288
+ return {
3289
+ executable: value.executable,
3290
+ args: value.args,
3291
+ envKeys: value.envKeys
3292
+ };
3293
+ }
3294
+ function parseArgs({ args: args2 }) {
3295
+ const result = {};
3296
+ for (let index = 0; index < args2.length; index++) {
3297
+ const value = args2[index + 1];
3298
+ if (value == null) continue;
3299
+ if (args2[index] === "--workdir") result.workDir = value;
3300
+ else if (args2[index] === "--bridge-state-dir")
3301
+ result.bridgeStateDir = value;
3302
+ else if (args2[index] === "--implementation-dir")
3303
+ result.implementationDir = value;
3304
+ else if (args2[index] === "--bridge-type") result.bridgeType = value;
3305
+ }
3306
+ return result;
3307
+ }
3308
+ function requireArg({
3309
+ value,
3310
+ name: name2
3311
+ }) {
3312
+ if (value == null || value.length === 0) {
3313
+ throw new Error(`Missing ${name2} argument.`);
3314
+ }
3315
+ return value;
3316
+ }
3317
+ //# sourceMappingURL=index.mjs.map