@pwrdrvr/agent-acp 0.12.2 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Client } from '@zed-industries/agent-client-protocol';
1
+ import { Client, McpServer } from '@agentclientprotocol/sdk';
2
2
  import { Logger, AgentBackend, NormalizedThreadEvent, Unsubscribe, AgentBackendToolCallHandler, AgentBackendApprovalHandler, AgentStartThreadOptions, AgentBackendStartThreadResult, AgentStartTurnOptions, NormalizedTokenUsage, NormalizedToolCall } from '@pwrdrvr/agent-core';
3
3
  import { JsonRpcId } from '@pwrdrvr/agent-transport';
4
4
 
@@ -24,8 +24,8 @@ interface AcpAgentConnection {
24
24
  setSessionMode(params: Record<string, unknown>): Promise<unknown>;
25
25
  setSessionModel(params: Record<string, unknown>): Promise<unknown>;
26
26
  authenticate(params: Record<string, unknown>): Promise<unknown>;
27
- extMethod(method: string, params: Record<string, unknown>): Promise<Record<string, unknown>>;
28
- extNotification(method: string, params: Record<string, unknown>): Promise<void>;
27
+ request(method: string, params: Record<string, unknown>): Promise<unknown>;
28
+ notify(method: string, params: Record<string, unknown>): Promise<void>;
29
29
  }
30
30
  /** Built connection + a teardown handle (kills the child / closes streams). */
31
31
  type AcpAgentConnectionHandle = {
@@ -178,8 +178,13 @@ type AcpRuntimeAgentInfo = {
178
178
  title?: string;
179
179
  version?: string;
180
180
  };
181
+ type AcpRuntimeMcpCapabilities = {
182
+ http?: boolean;
183
+ sse?: boolean;
184
+ };
181
185
  type AcpRuntimeAgentCapabilities = {
182
186
  loadSession?: boolean;
187
+ mcp?: AcpRuntimeMcpCapabilities;
183
188
  sessionHistoryReplay?: boolean;
184
189
  session?: {
185
190
  close?: boolean;
@@ -212,6 +217,10 @@ declare function normalizeAcpRuntimeCapabilities(params: {
212
217
  initialize?: AcpRuntimeCapabilities;
213
218
  }): AcpRuntimeCapabilities | undefined;
214
219
  declare function acpRuntimeSupportsSessionLoad(capabilities: AcpRuntimeCapabilities | undefined): boolean;
220
+ /** Optional ACP MCP transports are supported only when explicitly advertised. */
221
+ declare function acpRuntimeSupportsHttpMcp(capabilities: AcpRuntimeCapabilities | undefined): boolean;
222
+ /** Optional ACP MCP transports are supported only when explicitly advertised. */
223
+ declare function acpRuntimeSupportsSseMcp(capabilities: AcpRuntimeCapabilities | undefined): boolean;
215
224
  /** The configOption an agent uses to expose model choice (ACP `category:
216
225
  * "model"`, falling back to a literal `model` id). Some agents (e.g. Kimi)
217
226
  * advertise the active model HERE rather than via top-level
@@ -235,6 +244,43 @@ declare function mergeAcpRuntimeState(existing: AcpSessionRuntimeState | undefin
235
244
  /** Resolve the human-facing label for a mode id from the capabilities snapshot. */
236
245
  declare function modeLabelFor(capabilities: AcpRuntimeCapabilities | undefined, modeId: string): string | undefined;
237
246
 
247
+ /** ACP key/value entry used by stdio environments and HTTP headers. */
248
+ type AcpMcpKeyValue = {
249
+ name: string;
250
+ value: string;
251
+ };
252
+ /** Host-friendly collection accepted wherever ACP requires key/value entries. */
253
+ type AcpMcpKeyValueCollection = Readonly<Record<string, string>> | readonly AcpMcpKeyValue[];
254
+ /** Backward-compatible stdio MCP configuration. ACP stdio has no `type` field. */
255
+ type AcpStdioMcpServerConfig = {
256
+ name: string;
257
+ command: string;
258
+ args?: readonly string[];
259
+ env?: AcpMcpKeyValueCollection;
260
+ };
261
+ /** Streamable HTTP MCP configuration from the ACP v1 schema. */
262
+ type AcpHttpMcpServerConfig = {
263
+ name: string;
264
+ type: "http";
265
+ url: string;
266
+ headers: AcpMcpKeyValueCollection;
267
+ };
268
+ /** Legacy HTTP+SSE MCP configuration from the ACP v1 schema. */
269
+ type AcpSseMcpServerConfig = {
270
+ name: string;
271
+ type: "sse";
272
+ url: string;
273
+ headers: AcpMcpKeyValueCollection;
274
+ };
275
+ /** Public MCP input accepted by agent-acp session lifecycle methods. */
276
+ type AcpMcpServerConfig = AcpStdioMcpServerConfig | AcpHttpMcpServerConfig | AcpSseMcpServerConfig;
277
+ /** Exact MCP server wire union from the pinned ACP protocol library. */
278
+ type AcpMcpServerWireConfig = McpServer;
279
+ /** Serialize a host-friendly config into the exact ACP v1 wire representation. */
280
+ declare function normalizeAcpMcpServerConfig(server: AcpMcpServerConfig): AcpMcpServerWireConfig;
281
+ /** Serialize a per-session list without retaining mutable caller collections. */
282
+ declare function normalizeAcpMcpServerConfigs(servers: readonly AcpMcpServerConfig[]): AcpMcpServerWireConfig[];
283
+
238
284
  type AcpPromptContentBlock = {
239
285
  type: "text";
240
286
  text: string;
@@ -243,12 +289,6 @@ type AcpPromptContentBlock = {
243
289
  mimeType: string;
244
290
  data: string;
245
291
  };
246
- type AcpMcpServerConfig = {
247
- name: string;
248
- command: string;
249
- args?: string[];
250
- env?: Record<string, string>;
251
- };
252
292
  type AcpRuntimeOptionSource = "mode" | "model" | "configOption";
253
293
  type AcpAgentClientOptions = {
254
294
  /** The transport (real stdio or a fake). */
@@ -262,7 +302,7 @@ type AcpAgentClientOptions = {
262
302
  /** Default cwd for `session/new` when `startThread` omits one. */
263
303
  cwd?: string;
264
304
  /** MCP servers to attach at `session/new` / `session/load`. */
265
- mcpServers?: AcpMcpServerConfig[];
305
+ mcpServers?: readonly AcpMcpServerConfig[];
266
306
  now?: () => number;
267
307
  logger?: Logger;
268
308
  };
@@ -274,7 +314,15 @@ type AcpAgentClientOptions = {
274
314
  type AcpStartThreadOptions = {
275
315
  /** Working directory for the agent session. */
276
316
  cwd?: string;
277
- mcpServers?: AcpMcpServerConfig[];
317
+ mcpServers?: readonly AcpMcpServerConfig[];
318
+ };
319
+ /** ACP-native `session/load` options. The protocol session id is the id the
320
+ * agent persisted; `threadId` optionally preserves a separate host-facing id. */
321
+ type AcpLoadThreadOptions = {
322
+ sessionId: string;
323
+ threadId?: string;
324
+ cwd?: string;
325
+ mcpServers?: readonly AcpMcpServerConfig[];
278
326
  };
279
327
  /** ACP-NATIVE turn/start options. Internal mapping target; the public `startTurn`
280
328
  * takes neutral `AgentStartTurnOptions`. */
@@ -343,6 +391,10 @@ declare class AcpAgentClient implements AgentBackend {
343
391
  * mapping cwd + dropping Codex-only fields. Exposed for hosts that need
344
392
  * ACP-specific control (per-thread `mcpServers`). */
345
393
  startThreadNative(options?: AcpStartThreadOptions): Promise<AgentBackendStartThreadResult>;
394
+ /** ACP-native `session/load`. The pinned ACP v1 library has no separate
395
+ * `session/resume` method; loading is the protocol-defined history restore
396
+ * path and receives the same per-thread MCP configuration as `session/new`. */
397
+ loadThreadNative(options: AcpLoadThreadOptions): Promise<AgentBackendStartThreadResult>;
346
398
  /**
347
399
  * Re-establish a fresh ACP session for a PERSISTED thread id whose underlying
348
400
  * session is gone (e.g. resuming a chat after an app restart — the agent
@@ -362,7 +414,7 @@ declare class AcpAgentClient implements AgentBackend {
362
414
  * surfaces with different tool sets (library vs sizzle): each surface
363
415
  * passes its own servers, so its threads spawn its tools — overriding the
364
416
  * client-level default. */
365
- mcpServers?: AcpMcpServerConfig[];
417
+ mcpServers?: readonly AcpMcpServerConfig[];
366
418
  }): Promise<void>;
367
419
  /**
368
420
  * Mint a thread id WITHOUT spawning the agent or opening a session. The
@@ -383,6 +435,7 @@ declare class AcpAgentClient implements AgentBackend {
383
435
  * `bindThreadId` is given (resume), in which case the new ACP session is
384
436
  * bound to that existing host thread id. */
385
437
  private establishSession;
438
+ private openSession;
386
439
  /**
387
440
  * Public `AgentBackend.startTurn`: accepts NEUTRAL `AgentStartTurnOptions` and
388
441
  * maps them onto ACP prompt content blocks — a leading `text` block from
@@ -423,10 +476,6 @@ declare class AcpAgentClient implements AgentBackend {
423
476
  private handleNotification;
424
477
  private applySessionUpdate;
425
478
  private handleAcpRequest;
426
- /** Every MCP server name this client knows about — the client-level default
427
- * plus the per-thread servers of every live session. Used to auto-approve a
428
- * configured MCP tool regardless of which session's permission request it is. */
429
- private knownMcpServerNames;
430
479
  private handlePermissionRequest;
431
480
  private setRuntimeOption;
432
481
  private setRuntimeOptionOnTransport;
@@ -448,8 +497,13 @@ declare class AcpAgentClient implements AgentBackend {
448
497
  private notifyRuntimeCapabilities;
449
498
  private emitThreadSettings;
450
499
  private requireSession;
500
+ private assertMcpTransportSupport;
451
501
  /** Whether the agent advertises session/load support (for hosts that resume). */
452
502
  supportsSessionLoad(): boolean;
503
+ /** Whether the initialize result explicitly advertises streamable HTTP MCP. */
504
+ supportsHttpMcp(): boolean;
505
+ /** Whether the initialize result explicitly advertises legacy HTTP+SSE MCP. */
506
+ supportsSseMcp(): boolean;
453
507
  }
454
508
  /** Pick the value to set on a "thinking" config option for a neutral reasoning
455
509
  * token: low-effort → the OFF-like value, high-effort → the ON-like value.
@@ -820,4 +874,4 @@ declare class AcpRegistryService {
820
874
  declare function evaluateAcpDistributionPolicy(distribution: AcpRegistryDistribution, allowlist: AcpAllowlistDecision): AcpDistributionPolicy;
821
875
  declare function normalizeRegistry(raw: unknown): AcpRegistryAgent[];
822
876
 
823
- export { ACP_REGISTRY_URL, AcpAgentAllowlist, type AcpAgentAllowlistRule, AcpAgentClient, type AcpAgentClientFactory, type AcpAgentClientOptions, AcpAgentClientPool, type AcpAgentClientPoolOptions, type AcpAgentConnection, type AcpAgentConnectionHandle, type AcpAgentInstanceSource, type AcpAgentQuirks, type AcpAgentStrategy, type AcpAllowlistDecision, type AcpApplyContext, type AcpBinaryPlatformDistribution, AcpConnection, type AcpConnectionOptions, type AcpDiscoveryProbe, type AcpDistributionKind, type AcpDistributionPolicy, type AcpJsonRpcTransport, type AcpMcpServerConfig, type AcpNormalizeResult, type AcpNormalizerOptions, AcpOneShotClient, type AcpOneShotClientOptions, type AcpOneShotRequest, type AcpOneShotResponse, type AcpPackageDistribution, type AcpPathExecutableLister, type AcpPromptContentBlock, type AcpRegistryAgent, type AcpRegistryAgentWithPolicy, type AcpRegistryAuthDescriptor, type AcpRegistryAuthMethod, type AcpRegistryDistribution, type AcpRegistryFetch, AcpRegistryService, type AcpRegistryServiceOptions, type AcpRegistrySnapshot, type AcpRuntimeAgentCapabilities, type AcpRuntimeAgentInfo, type AcpRuntimeCapabilities, type AcpRuntimeCapabilitiesHandler, type AcpRuntimeCapabilitiesSource, type AcpRuntimeConfigOption, type AcpRuntimeConfigOptionValue, type AcpRuntimeMode, type AcpRuntimeModel, type AcpRuntimeModels, type AcpRuntimeModes, type AcpRuntimeOptionSource, AcpSessionNormalizer, type AcpSessionRuntimeState, type AcpSpawnSpec, type AcpStartThreadOptions, type AcpStartTurnOptions, type AcpTitleHandler, type AcpVerificationStatus, BANNED_ACP_REGISTRY_IDS, BUILT_IN_ACP_STRATEGIES, DEFAULT_ACP_AGENT_ALLOWLIST, type DiscoveredAcpAgent, type DiscoveredAcpAgentGroup, type DiscoveredAcpAgentInstance, type LocalAcpAgentProbe, type LocalAcpDiscoveryOptions, type LocalAcpProbeResult, acpRuntimeSupportsSessionLoad, acpSessionRuntimeStateFromCapabilities, acpSessionRuntimeStateFromUpdate, asRecord, buildAcpBackendId, buildStrategyTable, defaultAcpAgentAllowlist, defaultQuirks, discoverLocalAcpAgentInstances, discoverLocalAcpAgents, evaluateAcpDistributionPolicy, geminiStrategy, grokStrategy, isBannedAcpRegistryId, kimiStrategy, mergeAcpRuntimeState, modeLabelFor, modelConfigOption, modelIdFromCapabilities, modelsFromCapabilities, normalizeAcpRuntimeCapabilities, normalizeRegistry, qwenStrategy, readAcpContentText, readBoolean, readContentText, readFirstLocationPath, readFirstString, readKind, readNonEmptyString, readNumber, readPromptText, readString, readToolCallId, readToolContentText, readToolOutput, readUpdateText, reasoningValueForThoughtLevel, strategyByBackendId, strategyById, toolCallFromUpdate, wellKnownAgentBinDirs };
877
+ export { ACP_REGISTRY_URL, AcpAgentAllowlist, type AcpAgentAllowlistRule, AcpAgentClient, type AcpAgentClientFactory, type AcpAgentClientOptions, AcpAgentClientPool, type AcpAgentClientPoolOptions, type AcpAgentConnection, type AcpAgentConnectionHandle, type AcpAgentInstanceSource, type AcpAgentQuirks, type AcpAgentStrategy, type AcpAllowlistDecision, type AcpApplyContext, type AcpBinaryPlatformDistribution, AcpConnection, type AcpConnectionOptions, type AcpDiscoveryProbe, type AcpDistributionKind, type AcpDistributionPolicy, type AcpHttpMcpServerConfig, type AcpJsonRpcTransport, type AcpLoadThreadOptions, type AcpMcpKeyValue, type AcpMcpKeyValueCollection, type AcpMcpServerConfig, type AcpMcpServerWireConfig, type AcpNormalizeResult, type AcpNormalizerOptions, AcpOneShotClient, type AcpOneShotClientOptions, type AcpOneShotRequest, type AcpOneShotResponse, type AcpPackageDistribution, type AcpPathExecutableLister, type AcpPromptContentBlock, type AcpRegistryAgent, type AcpRegistryAgentWithPolicy, type AcpRegistryAuthDescriptor, type AcpRegistryAuthMethod, type AcpRegistryDistribution, type AcpRegistryFetch, AcpRegistryService, type AcpRegistryServiceOptions, type AcpRegistrySnapshot, type AcpRuntimeAgentCapabilities, type AcpRuntimeAgentInfo, type AcpRuntimeCapabilities, type AcpRuntimeCapabilitiesHandler, type AcpRuntimeCapabilitiesSource, type AcpRuntimeConfigOption, type AcpRuntimeConfigOptionValue, type AcpRuntimeMcpCapabilities, type AcpRuntimeMode, type AcpRuntimeModel, type AcpRuntimeModels, type AcpRuntimeModes, type AcpRuntimeOptionSource, AcpSessionNormalizer, type AcpSessionRuntimeState, type AcpSpawnSpec, type AcpSseMcpServerConfig, type AcpStartThreadOptions, type AcpStartTurnOptions, type AcpStdioMcpServerConfig, type AcpTitleHandler, type AcpVerificationStatus, BANNED_ACP_REGISTRY_IDS, BUILT_IN_ACP_STRATEGIES, DEFAULT_ACP_AGENT_ALLOWLIST, type DiscoveredAcpAgent, type DiscoveredAcpAgentGroup, type DiscoveredAcpAgentInstance, type LocalAcpAgentProbe, type LocalAcpDiscoveryOptions, type LocalAcpProbeResult, acpRuntimeSupportsHttpMcp, acpRuntimeSupportsSessionLoad, acpRuntimeSupportsSseMcp, acpSessionRuntimeStateFromCapabilities, acpSessionRuntimeStateFromUpdate, asRecord, buildAcpBackendId, buildStrategyTable, defaultAcpAgentAllowlist, defaultQuirks, discoverLocalAcpAgentInstances, discoverLocalAcpAgents, evaluateAcpDistributionPolicy, geminiStrategy, grokStrategy, isBannedAcpRegistryId, kimiStrategy, mergeAcpRuntimeState, modeLabelFor, modelConfigOption, modelIdFromCapabilities, modelsFromCapabilities, normalizeAcpMcpServerConfig, normalizeAcpMcpServerConfigs, normalizeAcpRuntimeCapabilities, normalizeRegistry, qwenStrategy, readAcpContentText, readBoolean, readContentText, readFirstLocationPath, readFirstString, readKind, readNonEmptyString, readNumber, readPromptText, readString, readToolCallId, readToolContentText, readToolOutput, readUpdateText, reasoningValueForThoughtLevel, strategyByBackendId, strategyById, toolCallFromUpdate, wellKnownAgentBinDirs };
package/dist/index.js CHANGED
@@ -4,105 +4,8 @@ import { Readable, Writable } from "stream";
4
4
  import {
5
5
  ClientSideConnection,
6
6
  ndJsonStream
7
- } from "@zed-industries/agent-client-protocol";
7
+ } from "@agentclientprotocol/sdk";
8
8
  import { noopLogger } from "@pwrdrvr/agent-core";
9
- function isPlainObject(value) {
10
- return typeof value === "object" && value !== null && !Array.isArray(value);
11
- }
12
- function sanitizeAcpNotificationLine(line) {
13
- if (line.trim().length === 0) return line;
14
- let message;
15
- try {
16
- message = JSON.parse(line);
17
- } catch {
18
- return line;
19
- }
20
- if (!isPlainObject(message) || message.method !== "session/update") return line;
21
- const params = message.params;
22
- if (!isPlainObject(params)) return line;
23
- const update = params.update;
24
- if (!isPlainObject(update)) return line;
25
- if (update.sessionUpdate === "config_option_update") return "";
26
- let changed = false;
27
- for (const key of ["rawInput", "rawOutput"]) {
28
- if (!(key in update)) continue;
29
- const value = update[key];
30
- if (value === void 0 || isPlainObject(value)) continue;
31
- if (value === null) {
32
- delete update[key];
33
- } else {
34
- update[key] = { value };
35
- }
36
- changed = true;
37
- }
38
- return changed ? JSON.stringify(message) : line;
39
- }
40
- function sanitizeInboundStream(source) {
41
- const decoder = new TextDecoder();
42
- const encoder = new TextEncoder();
43
- let buffer = "";
44
- return source.pipeThrough(
45
- new TransformStream({
46
- transform(chunk, controller) {
47
- buffer += decoder.decode(chunk, { stream: true });
48
- let nl = buffer.indexOf("\n");
49
- while (nl !== -1) {
50
- const line = buffer.slice(0, nl);
51
- buffer = buffer.slice(nl + 1);
52
- const sanitized = sanitizeAcpNotificationLine(line);
53
- if (sanitized.length > 0) controller.enqueue(encoder.encode(sanitized + "\n"));
54
- nl = buffer.indexOf("\n");
55
- }
56
- },
57
- flush(controller) {
58
- if (buffer.length > 0) {
59
- const sanitized = sanitizeAcpNotificationLine(buffer);
60
- if (sanitized.length > 0) controller.enqueue(encoder.encode(sanitized));
61
- buffer = "";
62
- }
63
- }
64
- })
65
- );
66
- }
67
- function rewriteOutboundAcpLine(line) {
68
- if (line.trim().length === 0) return line;
69
- let message;
70
- try {
71
- message = JSON.parse(line);
72
- } catch {
73
- return line;
74
- }
75
- if (!isPlainObject(message) || message.method !== "_session/set_config_option") {
76
- return line;
77
- }
78
- message.method = "session/set_config_option";
79
- return JSON.stringify(message);
80
- }
81
- function rewriteOutboundStream(target) {
82
- const decoder = new TextDecoder();
83
- const encoder = new TextEncoder();
84
- let buffer = "";
85
- const transform = new TransformStream({
86
- transform(chunk, controller) {
87
- buffer += decoder.decode(chunk, { stream: true });
88
- let nl = buffer.indexOf("\n");
89
- while (nl !== -1) {
90
- const line = buffer.slice(0, nl);
91
- buffer = buffer.slice(nl + 1);
92
- controller.enqueue(encoder.encode(rewriteOutboundAcpLine(line) + "\n"));
93
- nl = buffer.indexOf("\n");
94
- }
95
- },
96
- flush(controller) {
97
- if (buffer.length > 0) {
98
- controller.enqueue(encoder.encode(rewriteOutboundAcpLine(buffer)));
99
- buffer = "";
100
- }
101
- }
102
- });
103
- void transform.readable.pipeTo(target).catch(() => void 0);
104
- return transform.writable;
105
- }
106
9
  var AcpConnection = class {
107
10
  constructor(options) {
108
11
  this.options = options;
@@ -172,12 +75,8 @@ var AcpConnection = class {
172
75
  const line = chunk.toString("utf8").trimEnd();
173
76
  if (line.length > 0) logger.debug("acp agent stderr", { line });
174
77
  });
175
- const toAgentStdin = rewriteOutboundStream(
176
- Writable.toWeb(child.stdin)
177
- );
178
- const fromAgentStdout = sanitizeInboundStream(
179
- Readable.toWeb(child.stdout)
180
- );
78
+ const toAgentStdin = Writable.toWeb(child.stdin);
79
+ const fromAgentStdout = Readable.toWeb(child.stdout);
181
80
  const stream = ndJsonStream(toAgentStdin, fromAgentStdout);
182
81
  const connection = new ClientSideConnection(
183
82
  (_agent) => client,
@@ -218,7 +117,7 @@ var AcpConnection = class {
218
117
  case "authenticate":
219
118
  return await conn.authenticate(params);
220
119
  default:
221
- return await conn.extMethod(method, params);
120
+ return await conn.request(method, params);
222
121
  }
223
122
  }
224
123
  async notify(method, params = {}) {
@@ -229,7 +128,7 @@ var AcpConnection = class {
229
128
  await conn.cancel(params);
230
129
  return;
231
130
  }
232
- await conn.extNotification(method, params);
131
+ await conn.notify(method, params);
233
132
  }
234
133
  async close() {
235
134
  try {
@@ -760,6 +659,12 @@ function normalizeAcpRuntimeCapabilities(params) {
760
659
  function acpRuntimeSupportsSessionLoad(capabilities) {
761
660
  return capabilities?.agentCapabilities?.loadSession !== false;
762
661
  }
662
+ function acpRuntimeSupportsHttpMcp(capabilities) {
663
+ return capabilities?.agentCapabilities?.mcp?.http === true;
664
+ }
665
+ function acpRuntimeSupportsSseMcp(capabilities) {
666
+ return capabilities?.agentCapabilities?.mcp?.sse === true;
667
+ }
763
668
  function modelConfigOption(capabilities) {
764
669
  return capabilities?.configOptions?.find(
765
670
  (option) => option.category === "model" || option.id === "model"
@@ -947,10 +852,21 @@ function readAgentCapabilities(value, sessionCapabilitiesValue) {
947
852
  const close = readBoolean(asRecord(record?.session), "close");
948
853
  const cancel = readBoolean(asRecord(record?.session), "cancel");
949
854
  const sessionHistoryReplay = readBoolean(kimiMeta, "sessionHistoryReplay") ?? readBoolean(kimiMeta, "session_history_replay");
855
+ const mcpRecord = asRecord(
856
+ record?.mcpCapabilities ?? record?.mcp_capabilities ?? record?.mcp
857
+ );
858
+ const http = readBoolean(mcpRecord, "http");
859
+ const sse = readBoolean(mcpRecord, "sse");
950
860
  const capabilities = {
951
861
  raw: record ?? sessionCapabilities
952
862
  };
953
863
  if (loadSession !== void 0) capabilities.loadSession = loadSession;
864
+ if (http !== void 0 || sse !== void 0) {
865
+ capabilities.mcp = {
866
+ ...http !== void 0 ? { http } : {},
867
+ ...sse !== void 0 ? { sse } : {}
868
+ };
869
+ }
954
870
  if (sessionHistoryReplay !== void 0) {
955
871
  capabilities.sessionHistoryReplay = sessionHistoryReplay;
956
872
  }
@@ -977,6 +893,51 @@ function readAgentInfo(value) {
977
893
  return Object.keys(agentInfo).length > 0 ? agentInfo : void 0;
978
894
  }
979
895
 
896
+ // src/mcp-server-config.ts
897
+ function isKeyValueArray(collection) {
898
+ return Array.isArray(collection);
899
+ }
900
+ function normalizeKeyValues(collection) {
901
+ if (collection === void 0) return [];
902
+ if (isKeyValueArray(collection)) {
903
+ return collection.map(({ name, value }) => ({ name, value }));
904
+ }
905
+ return Object.entries(collection).map(([name, value]) => ({ name, value }));
906
+ }
907
+ function normalizeAcpMcpServerConfig(server) {
908
+ if ("type" in server) {
909
+ return {
910
+ name: server.name,
911
+ type: server.type,
912
+ url: server.url,
913
+ headers: normalizeKeyValues(server.headers)
914
+ };
915
+ }
916
+ return {
917
+ name: server.name,
918
+ command: server.command,
919
+ args: [...server.args ?? []],
920
+ env: normalizeKeyValues(server.env)
921
+ };
922
+ }
923
+ function normalizeAcpMcpServerConfigs(servers) {
924
+ return servers.map(normalizeAcpMcpServerConfig);
925
+ }
926
+ function redactAcpMcpCredentials(message, servers) {
927
+ const secrets = /* @__PURE__ */ new Set();
928
+ for (const server of servers) {
929
+ const values = "type" in server ? [server.url, ...normalizeKeyValues(server.headers).map(({ value }) => value)] : normalizeKeyValues(server.env).map(({ value }) => value);
930
+ for (const value of values) {
931
+ if (value.length > 0) secrets.add(value);
932
+ }
933
+ }
934
+ let redacted = message;
935
+ for (const secret of [...secrets].sort((a, b) => b.length - a.length)) {
936
+ redacted = redacted.split(secret).join("[REDACTED]");
937
+ }
938
+ return redacted;
939
+ }
940
+
980
941
  // src/acp-client.ts
981
942
  var ACP_PROTOCOL_VERSION = 1;
982
943
  var ACP_PROMPT_REQUEST_TIMEOUT_MS = 60 * 6e4;
@@ -1122,6 +1083,18 @@ var AcpAgentClient = class {
1122
1083
  ...options.mcpServers !== void 0 ? { mcpServers: options.mcpServers } : {}
1123
1084
  });
1124
1085
  }
1086
+ /** ACP-native `session/load`. The pinned ACP v1 library has no separate
1087
+ * `session/resume` method; loading is the protocol-defined history restore
1088
+ * path and receives the same per-thread MCP configuration as `session/new`. */
1089
+ async loadThreadNative(options) {
1090
+ return this.openSession({
1091
+ lifecycle: "load",
1092
+ protocolSessionId: options.sessionId,
1093
+ ...options.threadId !== void 0 ? { bindThreadId: options.threadId } : {},
1094
+ ...options.cwd !== void 0 ? { cwd: options.cwd } : {},
1095
+ ...options.mcpServers !== void 0 ? { mcpServers: options.mcpServers } : {}
1096
+ });
1097
+ }
1125
1098
  /**
1126
1099
  * Re-establish a fresh ACP session for a PERSISTED thread id whose underlying
1127
1100
  * session is gone (e.g. resuming a chat after an app restart — the agent
@@ -1167,7 +1140,13 @@ var AcpAgentClient = class {
1167
1140
  * `bindThreadId` is given (resume), in which case the new ACP session is
1168
1141
  * bound to that existing host thread id. */
1169
1142
  async establishSession(options) {
1143
+ return this.openSession({ lifecycle: "new", ...options });
1144
+ }
1145
+ async openSession(options) {
1170
1146
  await this.initialize();
1147
+ if (options.lifecycle === "load" && !acpRuntimeSupportsSessionLoad(this.runtimeCapabilities)) {
1148
+ throw new Error("ACP agent does not advertise session/load support");
1149
+ }
1171
1150
  const cwd = options.cwd ?? this.defaultCwd ?? process.cwd();
1172
1151
  try {
1173
1152
  mkdirSync(cwd, { recursive: true });
@@ -1177,23 +1156,68 @@ var AcpAgentClient = class {
1177
1156
  message: cause instanceof Error ? cause.message : String(cause)
1178
1157
  });
1179
1158
  }
1180
- const mcpServers = (options.mcpServers ?? this.defaultMcpServers).map((server) => ({
1181
- name: server.name,
1182
- command: server.command,
1183
- args: server.args ?? [],
1184
- env: Object.entries(server.env ?? {}).map(([name, value]) => ({ name, value }))
1185
- }));
1186
- const result = await this.transport.request("session/new", {
1187
- cwd,
1188
- mcpServers
1189
- });
1159
+ const configuredMcpServers = options.mcpServers ?? this.defaultMcpServers;
1160
+ this.assertMcpTransportSupport(configuredMcpServers);
1161
+ const mcpServers = normalizeAcpMcpServerConfigs(configuredMcpServers);
1162
+ const method = options.lifecycle === "load" ? "session/load" : "session/new";
1163
+ let session;
1164
+ if (options.lifecycle === "load") {
1165
+ const protocolSessionId2 = options.protocolSessionId;
1166
+ if (!protocolSessionId2) {
1167
+ throw new Error("ACP session/load did not provide a session id");
1168
+ }
1169
+ const threadId2 = options.bindThreadId ?? `acp:${this.strategy.id}:${isUuid(protocolSessionId2) ? protocolSessionId2 : randomUUID()}`;
1170
+ if (this.sessions.has(threadId2)) {
1171
+ throw new Error(`ACP thread is already open: ${threadId2}`);
1172
+ }
1173
+ const mappedThreadId = this.threadIdByProtocolId.get(protocolSessionId2);
1174
+ if (mappedThreadId !== void 0) {
1175
+ throw new Error(
1176
+ `ACP session is already loaded for thread: ${mappedThreadId}`
1177
+ );
1178
+ }
1179
+ session = {
1180
+ threadId: threadId2,
1181
+ protocolSessionId: protocolSessionId2,
1182
+ normalizer: new AcpSessionNormalizer({ quirks: this.strategy.quirks }),
1183
+ turnId: void 0,
1184
+ runtimeState: void 0,
1185
+ pendingTurn: void 0,
1186
+ pendingInstructions: options.instructions,
1187
+ mcpServerNames: configuredMcpServers.map((server) => server.name)
1188
+ };
1189
+ this.sessions.set(threadId2, session);
1190
+ this.threadIdByProtocolId.set(protocolSessionId2, threadId2);
1191
+ }
1192
+ let result;
1193
+ try {
1194
+ result = await this.transport.request(method, {
1195
+ cwd,
1196
+ mcpServers,
1197
+ ...options.lifecycle === "load" ? { sessionId: options.protocolSessionId } : {}
1198
+ });
1199
+ } catch (cause) {
1200
+ const message = redactAcpMcpCredentials(
1201
+ errorMessage(cause),
1202
+ configuredMcpServers
1203
+ );
1204
+ if (session !== void 0) {
1205
+ if (this.sessions.get(session.threadId) === session) {
1206
+ this.sessions.delete(session.threadId);
1207
+ }
1208
+ if (this.threadIdByProtocolId.get(session.protocolSessionId) === session.threadId) {
1209
+ this.threadIdByProtocolId.delete(session.protocolSessionId);
1210
+ }
1211
+ }
1212
+ throw new Error(`ACP ${method} failed: ${message}`);
1213
+ }
1190
1214
  const record = asRecord2(result);
1191
- const protocolSessionId = readString2(record, "sessionId") ?? readString2(record, "session_id");
1215
+ const protocolSessionId = options.lifecycle === "load" ? options.protocolSessionId : readString2(record, "sessionId") ?? readString2(record, "session_id");
1192
1216
  if (!protocolSessionId) {
1193
- throw new Error("ACP session/new did not return a session id");
1217
+ throw new Error(`ACP ${method} did not provide a session id`);
1194
1218
  }
1195
- const threadId = options.bindThreadId ?? `acp:${this.strategy.id}:${isUuid(protocolSessionId) ? protocolSessionId : randomUUID()}`;
1196
- const session = {
1219
+ const threadId = session?.threadId ?? options.bindThreadId ?? `acp:${this.strategy.id}:${isUuid(protocolSessionId) ? protocolSessionId : randomUUID()}`;
1220
+ session ??= {
1197
1221
  threadId,
1198
1222
  protocolSessionId,
1199
1223
  normalizer: new AcpSessionNormalizer({ quirks: this.strategy.quirks }),
@@ -1201,14 +1225,18 @@ var AcpAgentClient = class {
1201
1225
  runtimeState: void 0,
1202
1226
  pendingTurn: void 0,
1203
1227
  pendingInstructions: options.instructions,
1204
- // Remember the MCP servers attached to THIS session so its tools can be
1205
- // auto-approved even when the (pooled, shared) client has no client-level
1206
- // `mcpServers` default.
1207
- mcpServerNames: (options.mcpServers ?? this.defaultMcpServers).map((server) => server.name)
1228
+ // Remember ONLY the MCP servers attached to THIS session so the host's
1229
+ // approval policy never sees another pooled thread's tool set.
1230
+ mcpServerNames: configuredMcpServers.map((server) => server.name)
1208
1231
  };
1209
- this.sessions.set(threadId, session);
1210
- this.threadIdByProtocolId.set(protocolSessionId, threadId);
1211
- const runtimeCapabilities = this.captureRuntimeCapabilities("session-new", result);
1232
+ if (options.lifecycle === "new") {
1233
+ this.sessions.set(threadId, session);
1234
+ this.threadIdByProtocolId.set(protocolSessionId, threadId);
1235
+ }
1236
+ const runtimeCapabilities = this.captureRuntimeCapabilities(
1237
+ options.lifecycle === "load" ? "session-load" : "session-new",
1238
+ result
1239
+ );
1212
1240
  if (runtimeCapabilities) {
1213
1241
  const runtimeState = acpSessionRuntimeStateFromCapabilities(
1214
1242
  runtimeCapabilities,
@@ -1218,10 +1246,11 @@ var AcpAgentClient = class {
1218
1246
  this.notifyRuntimeCapabilities({ threadId, runtimeCapabilities, runtimeState });
1219
1247
  this.emitThreadSettings(session, runtimeCapabilities, runtimeState);
1220
1248
  }
1221
- this.logger.debug("acp thread started", {
1249
+ this.logger.debug("acp thread opened", {
1222
1250
  threadId,
1223
1251
  protocolSessionId,
1224
- resumed: options.bindThreadId !== void 0
1252
+ lifecycle: options.lifecycle,
1253
+ rebound: options.bindThreadId !== void 0
1225
1254
  });
1226
1255
  const out = { threadId };
1227
1256
  const model = modelIdFromCapabilities(runtimeCapabilities);
@@ -1496,16 +1525,6 @@ var AcpAgentClient = class {
1496
1525
  }
1497
1526
  throw new Error(`Unsupported ACP request: ${method}`);
1498
1527
  }
1499
- /** Every MCP server name this client knows about — the client-level default
1500
- * plus the per-thread servers of every live session. Used to auto-approve a
1501
- * configured MCP tool regardless of which session's permission request it is. */
1502
- knownMcpServerNames() {
1503
- const names = new Set(this.defaultMcpServers.map((server) => server.name));
1504
- for (const session of this.sessions.values()) {
1505
- for (const name of session.mcpServerNames) names.add(name);
1506
- }
1507
- return [...names];
1508
- }
1509
1528
  async handlePermissionRequest(params, id) {
1510
1529
  const protocolSessionId = readString2(params, "sessionId") ?? readString2(params, "session_id");
1511
1530
  const threadId = protocolSessionId ? this.threadIdByProtocolId.get(protocolSessionId) : void 0;
@@ -1526,7 +1545,7 @@ var AcpAgentClient = class {
1526
1545
  }
1527
1546
  const handlerParams = {
1528
1547
  ...params,
1529
- mcpServerNames: this.knownMcpServerNames()
1548
+ mcpServerNames: threadId ? this.sessions.get(threadId)?.mcpServerNames ?? [] : []
1530
1549
  };
1531
1550
  if (threadId !== void 0) handlerParams.threadId = threadId;
1532
1551
  const decision = await handler("session/request_permission", handlerParams);
@@ -1661,10 +1680,33 @@ var AcpAgentClient = class {
1661
1680
  }
1662
1681
  return session;
1663
1682
  }
1683
+ assertMcpTransportSupport(servers) {
1684
+ for (const server of servers) {
1685
+ if (!("type" in server)) continue;
1686
+ if (server.type === "http" && !acpRuntimeSupportsHttpMcp(this.runtimeCapabilities)) {
1687
+ throw new Error(
1688
+ `ACP agent does not advertise HTTP MCP support for server "${server.name}"`
1689
+ );
1690
+ }
1691
+ if (server.type === "sse" && !acpRuntimeSupportsSseMcp(this.runtimeCapabilities)) {
1692
+ throw new Error(
1693
+ `ACP agent does not advertise SSE MCP support for server "${server.name}"`
1694
+ );
1695
+ }
1696
+ }
1697
+ }
1664
1698
  /** Whether the agent advertises session/load support (for hosts that resume). */
1665
1699
  supportsSessionLoad() {
1666
1700
  return acpRuntimeSupportsSessionLoad(this.runtimeCapabilities);
1667
1701
  }
1702
+ /** Whether the initialize result explicitly advertises streamable HTTP MCP. */
1703
+ supportsHttpMcp() {
1704
+ return acpRuntimeSupportsHttpMcp(this.runtimeCapabilities);
1705
+ }
1706
+ /** Whether the initialize result explicitly advertises legacy HTTP+SSE MCP. */
1707
+ supportsSseMcp() {
1708
+ return acpRuntimeSupportsSseMcp(this.runtimeCapabilities);
1709
+ }
1668
1710
  };
1669
1711
  var LOW_EFFORT_TOKENS = /* @__PURE__ */ new Set([
1670
1712
  "off",
@@ -2795,7 +2837,9 @@ export {
2795
2837
  BANNED_ACP_REGISTRY_IDS,
2796
2838
  BUILT_IN_ACP_STRATEGIES,
2797
2839
  DEFAULT_ACP_AGENT_ALLOWLIST,
2840
+ acpRuntimeSupportsHttpMcp,
2798
2841
  acpRuntimeSupportsSessionLoad,
2842
+ acpRuntimeSupportsSseMcp,
2799
2843
  acpSessionRuntimeStateFromCapabilities,
2800
2844
  acpSessionRuntimeStateFromUpdate,
2801
2845
  asRecord,
@@ -2815,6 +2859,8 @@ export {
2815
2859
  modelConfigOption,
2816
2860
  modelIdFromCapabilities,
2817
2861
  modelsFromCapabilities,
2862
+ normalizeAcpMcpServerConfig,
2863
+ normalizeAcpMcpServerConfigs,
2818
2864
  normalizeAcpRuntimeCapabilities,
2819
2865
  normalizeRegistry,
2820
2866
  qwenStrategy,