@agentproto/acp 0.1.0 → 0.3.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.
@@ -1,5 +1,5 @@
1
1
  import { ClientSideConnection, Client } from '@agentclientprotocol/sdk';
2
- import { S as StreamEvent } from '../types-BPyht7LH.js';
2
+ import { S as StreamEvent } from '../types-DC2phPNG.js';
3
3
 
4
4
  /**
5
5
  * AIP-44 ACP client — drives a subprocess agent via stdio JSON-RPC.
@@ -11,6 +11,27 @@ import { S as StreamEvent } from '../types-BPyht7LH.js';
11
11
  * notification handler.
12
12
  */
13
13
 
14
+ /**
15
+ * Map our internal `AcpMcpServer` (`{ name, transport, ref }`) onto the
16
+ * `@agentclientprotocol/sdk` wire shape expected by
17
+ * `session/new.mcpServers` (and `session/load.mcpServers`).
18
+ *
19
+ * The ACP `McpServer` union is transport-tagged and field-named
20
+ * differently from our compact internal form:
21
+ * - http → `{ type: "http", name, url, headers: [] }`
22
+ * - sse → `{ type: "sse", name, url, headers: [] }`
23
+ * - stdio → `{ name, command, args: [], env: [] }` (untagged variant)
24
+ *
25
+ * Without this mapping the raw `{ transport, ref }` entry reaches the
26
+ * agent verbatim and `session/new` rejects with `Invalid params` — the
27
+ * symptom that broke orchestrator spawns (the auto-injected scoped
28
+ * gateway is an `http` entry).
29
+ *
30
+ * Retro-compat: an entry that already carries an ACP-native discriminator
31
+ * (`type`/`url`/`command`) is passed through untouched, so callers that
32
+ * pre-shape their entries keep working.
33
+ */
34
+ declare function toAcpMcpServers(servers: readonly unknown[]): unknown[];
14
35
  interface AcpClientOptions {
15
36
  /** Output bytes go here (subprocess stdin). */
16
37
  output: WritableStream<Uint8Array>;
@@ -34,6 +55,36 @@ interface AcpClientOptions {
34
55
  handlers?: Partial<Client>;
35
56
  /** Protocol version to negotiate. Defaults to the SDK's current. */
36
57
  protocolVersion?: number;
58
+ /**
59
+ * Called on ANY JSON-RPC traffic with the child agent process — every
60
+ * incoming `session/update` notification (even ones that
61
+ * `translateSessionUpdate` maps to `null`, e.g. a `tool_call_update`
62
+ * still in progress) and every outbound RPC call this client makes
63
+ * (`newSession`, `loadSession`, `setSessionConfigOption`, `prompt`,
64
+ * `cancel`). Distinct from the `StreamEvent` stream: fires even
65
+ * during gaps where no event is produced, so a host can track
66
+ * liveness independent of what the agent actually says.
67
+ */
68
+ onActivity?: () => void;
69
+ /**
70
+ * Turn-idle watchdog: if a `prompt()` call goes this many ms with NO
71
+ * activity signal (see `onActivity` above — incoming `session/update`
72
+ * notifications, outbound RPCs) and the underlying `connection.prompt()`
73
+ * promise still hasn't resolved, synthesize a `turn-end` event with
74
+ * `reason: "watchdog-timeout"` so the caller's async iterator completes
75
+ * instead of hanging forever. The timer resets on every activity signal
76
+ * observed DURING that turn, so a legitimately long tool-call chain
77
+ * (still producing activity, just no user-visible output) never
78
+ * false-positives — this is "N ms of true silence," not "N ms since the
79
+ * turn started."
80
+ *
81
+ * Undefined (the default) disables the watchdog entirely — existing
82
+ * callers see no behavior change. If the real `connection.prompt()`
83
+ * eventually settles after the synthetic turn-end was already emitted,
84
+ * the late result is logged and discarded (no crash, no duplicate
85
+ * turn-end for the same logical turn).
86
+ */
87
+ turnIdleTimeoutMs?: number;
37
88
  }
38
89
  interface AcpClient {
39
90
  readonly connection: ClientSideConnection;
@@ -42,6 +93,19 @@ interface AcpClient {
42
93
  newSession(params: {
43
94
  cwd: string;
44
95
  mcpServers?: unknown[];
96
+ /**
97
+ * Model to select via `session/set_config_option` immediately after
98
+ * `newSession`. The claude-agent-acp wrapper supports this as
99
+ * `configId:"model"`. Omit to keep the agent's own default.
100
+ */
101
+ model?: string;
102
+ /**
103
+ * Effort level to select via `session/set_config_option` after
104
+ * `newSession`. Effort is model-dependent — same label ≠ same
105
+ * budget across models; defaults differ too (see AgentCliConnectOptions).
106
+ * Omit to keep the model's own default.
107
+ */
108
+ effort?: string;
45
109
  }): Promise<AcpClientSession>;
46
110
  /**
47
111
  * Reattach to an existing session by id. Available only when the
@@ -73,4 +137,4 @@ interface AcpClientSession {
73
137
  }
74
138
  declare function createAcpClient(options: AcpClientOptions): Promise<AcpClient>;
75
139
 
76
- export { type AcpClient, type AcpClientOptions, type AcpClientSession, createAcpClient };
140
+ export { type AcpClient, type AcpClientOptions, type AcpClientSession, createAcpClient, toAcpMcpServers };
@@ -6,11 +6,32 @@ import { ndJsonStream, ClientSideConnection } from '@agentclientprotocol/sdk';
6
6
  */
7
7
 
8
8
  var PROTOCOL_VERSION_DEFAULT = 1;
9
+ function toAcpMcpServers(servers) {
10
+ return servers.map(toAcpMcpServer);
11
+ }
12
+ function toAcpMcpServer(server) {
13
+ if (!server || typeof server !== "object") return server;
14
+ const entry = server;
15
+ if ("type" in entry || "url" in entry || "command" in entry) return entry;
16
+ const transport = entry.transport;
17
+ if (typeof transport !== "string") return entry;
18
+ const { name, ref } = entry;
19
+ switch (transport) {
20
+ case "http":
21
+ return { type: "http", name, url: ref ?? "", headers: [] };
22
+ case "sse":
23
+ return { type: "sse", name, url: ref ?? "", headers: [] };
24
+ case "stdio":
25
+ return { name, command: ref ?? "", args: [], env: [] };
26
+ default:
27
+ return entry;
28
+ }
29
+ }
9
30
  async function createAcpClient(options) {
10
31
  const sessions = /* @__PURE__ */ new Map();
11
32
  const stream = ndJsonStream(options.output, options.input);
12
33
  const connection = new ClientSideConnection(
13
- () => buildClientHandlers(options.handlers ?? {}, sessions),
34
+ () => buildClientHandlers(options.handlers ?? {}, sessions, options.onActivity),
14
35
  stream
15
36
  );
16
37
  const initResponse = await connection.initialize({
@@ -28,8 +49,9 @@ async function createAcpClient(options) {
28
49
  async newSession(params) {
29
50
  const response = await connection.newSession({
30
51
  cwd: params.cwd,
31
- mcpServers: params.mcpServers ?? []
52
+ mcpServers: toAcpMcpServers(params.mcpServers ?? [])
32
53
  });
54
+ options.onActivity?.();
33
55
  const sessionId = response.sessionId;
34
56
  const state = {
35
57
  events: [],
@@ -38,14 +60,45 @@ async function createAcpClient(options) {
38
60
  active: false
39
61
  };
40
62
  sessions.set(sessionId, state);
41
- return buildSession(connection, sessionId, state, sessions);
63
+ if (params.model) {
64
+ await connection.setSessionConfigOption({
65
+ configId: "model",
66
+ value: params.model,
67
+ sessionId
68
+ });
69
+ options.onActivity?.();
70
+ }
71
+ if (params.effort) {
72
+ try {
73
+ await connection.setSessionConfigOption({
74
+ configId: "effort",
75
+ value: params.effort,
76
+ sessionId
77
+ });
78
+ options.onActivity?.();
79
+ } catch (err) {
80
+ console.warn(
81
+ `[acp] set_config_option effort="${params.effort}" rejected by server (best-effort):`,
82
+ err instanceof Error ? err.message : err
83
+ );
84
+ }
85
+ }
86
+ return buildSession(
87
+ connection,
88
+ sessionId,
89
+ state,
90
+ sessions,
91
+ options.onActivity,
92
+ options.turnIdleTimeoutMs
93
+ );
42
94
  },
43
95
  async loadSession(params) {
44
96
  await connection.loadSession({
45
97
  sessionId: params.sessionId,
46
98
  cwd: params.cwd,
47
- mcpServers: params.mcpServers ?? []
99
+ mcpServers: toAcpMcpServers(params.mcpServers ?? [])
48
100
  });
101
+ options.onActivity?.();
49
102
  const state = {
50
103
  events: [],
51
104
  resolveNext: null,
@@ -53,7 +106,14 @@ async function createAcpClient(options) {
53
106
  active: false
54
107
  };
55
108
  sessions.set(params.sessionId, state);
56
- return buildSession(connection, params.sessionId, state, sessions);
109
+ return buildSession(
110
+ connection,
111
+ params.sessionId,
112
+ state,
113
+ sessions,
114
+ options.onActivity,
115
+ options.turnIdleTimeoutMs
116
+ );
57
117
  },
58
118
  async close() {
59
119
  sessions.clear();
@@ -70,7 +130,7 @@ function clientCapabilitiesFromOptions(options) {
70
130
  terminal: caps.terminal ?? false
71
131
  };
72
132
  }
73
- function buildSession(connection, sessionId, state, sessions) {
133
+ function buildSession(connection, sessionId, state, sessions, onActivity, turnIdleTimeoutMs) {
74
134
  return {
75
135
  sessionId,
76
136
  prompt(input) {
@@ -83,16 +143,50 @@ function buildSession(connection, sessionId, state, sessions) {
83
143
  state.done = false;
84
144
  state.events.length = 0;
85
145
  const iter = makeIterator(state);
146
+ let watchdogTimer;
147
+ let watchdogFired = false;
148
+ const clearWatchdogTimer = () => {
149
+ if (watchdogTimer) clearTimeout(watchdogTimer);
150
+ watchdogTimer = void 0;
151
+ };
152
+ const armWatchdogTimer = () => {
153
+ if (turnIdleTimeoutMs === void 0) return;
154
+ clearWatchdogTimer();
155
+ watchdogTimer = setTimeout(() => {
156
+ watchdogFired = true;
157
+ state.done = true;
158
+ enqueue(state, { kind: "turn-end", sessionId, reason: "watchdog-timeout" });
159
+ }, turnIdleTimeoutMs);
160
+ };
161
+ state.resetWatchdogTimer = armWatchdogTimer;
162
+ armWatchdogTimer();
163
+ onActivity?.();
86
164
  connection.prompt({
87
165
  sessionId,
88
166
  prompt: input.messages
89
167
  }).then((response) => {
168
+ clearWatchdogTimer();
169
+ onActivity?.();
170
+ if (watchdogFired) {
171
+ console.warn(
172
+ `[acp] session ${sessionId}: connection.prompt() resolved after the turn-idle watchdog already synthesized a turn-end \u2014 ignoring late response.`
173
+ );
174
+ return;
175
+ }
90
176
  enqueue(state, {
91
177
  kind: "turn-end",
92
178
  sessionId,
93
179
  reason: response.stopReason === "cancelled" && "cancelled" || response.stopReason === "max_turns" && "max_turns" || "completed"
94
180
  });
95
181
  }).catch((err) => {
182
+ clearWatchdogTimer();
183
+ if (watchdogFired) {
184
+ console.warn(
185
+ `[acp] session ${sessionId}: connection.prompt() rejected after the turn-idle watchdog already synthesized a turn-end \u2014 ignoring late error:`,
186
+ err instanceof Error ? err.message : err
187
+ );
188
+ return;
189
+ }
96
190
  const message = err instanceof Error ? err.message : String(err);
97
191
  enqueue(state, {
98
192
  kind: "error",
@@ -102,6 +196,8 @@ function buildSession(connection, sessionId, state, sessions) {
102
196
  }).finally(() => {
103
197
  state.active = false;
104
198
  state.done = true;
199
+ state.resetWatchdogTimer = void 0;
200
+ clearWatchdogTimer();
105
201
  flush(state);
106
202
  });
107
203
  if (input.signal) {
@@ -118,6 +214,7 @@ function buildSession(connection, sessionId, state, sessions) {
118
214
  async cancel() {
119
215
  if (!state.active) return;
120
216
  await connection.cancel({ sessionId });
217
+ onActivity?.();
121
218
  },
122
219
  async close() {
123
220
  sessions.delete(sessionId);
@@ -162,13 +259,15 @@ function flush(state) {
162
259
  r(void 0);
163
260
  }
164
261
  }
165
- function buildClientHandlers(partial, sessions) {
262
+ function buildClientHandlers(partial, sessions, onActivity) {
166
263
  return {
167
264
  async sessionUpdate(params) {
265
+ onActivity?.();
168
266
  const sid = params.sessionId;
169
267
  if (!sid) return;
170
268
  const state = sessions.get(sid);
171
269
  if (!state) return;
270
+ state.resetWatchdogTimer?.();
172
271
  const update = params.update;
173
272
  if (!update) return;
174
273
  const event = translateSessionUpdate(sid, update);
@@ -206,12 +305,21 @@ function translateSessionUpdate(sessionId, update) {
206
305
  };
207
306
  case "tool_call": {
208
307
  const toolCallId = update.toolCallId ?? "";
308
+ const toolName = update.title ?? update.kind ?? "tool";
309
+ const locations = update.locations;
310
+ let args = update.rawInput ?? update.content ?? {};
311
+ if (update.rawInput == null && locations && locations.length > 0) {
312
+ args = locations.length === 1 ? {
313
+ path: locations[0].path,
314
+ ...locations[0].line != null ? { line: locations[0].line } : {}
315
+ } : { paths: locations.map((location) => location.path) };
316
+ }
209
317
  return {
210
318
  kind: "tool-call",
211
319
  sessionId,
212
320
  toolCallId,
213
- toolName: update.kind ?? update.title ?? "tool",
214
- arguments: update.rawInput ?? update.content ?? {}
321
+ toolName,
322
+ arguments: args
215
323
  };
216
324
  }
217
325
  case "tool_call_update": {
@@ -227,6 +335,28 @@ function translateSessionUpdate(sessionId, update) {
227
335
  }
228
336
  return null;
229
337
  }
338
+ case "plan": {
339
+ const entries = update.entries ?? [];
340
+ return {
341
+ kind: "plan",
342
+ sessionId,
343
+ entries: entries.map((entry) => ({
344
+ content: entry.content ?? "",
345
+ priority: entry.priority ?? "medium",
346
+ status: entry.status ?? "pending"
347
+ }))
348
+ };
349
+ }
350
+ case "usage_update": {
351
+ const cost = update.cost;
352
+ return {
353
+ kind: "usage_update",
354
+ sessionId,
355
+ size: update.size ?? 0,
356
+ used: update.used ?? 0,
357
+ ...cost ? { cost } : {}
358
+ };
359
+ }
230
360
  case "user_message_chunk":
231
361
  return null;
232
362
  default:
@@ -243,6 +373,6 @@ function extractText(content) {
243
373
  return "";
244
374
  }
245
375
 
246
- export { createAcpClient };
376
+ export { createAcpClient, toAcpMcpServers };
247
377
  //# sourceMappingURL=index.mjs.map
248
378
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/client/index.ts"],"names":["value"],"mappings":";;;;;;;AAkBA,IAAM,wBAAA,GAA2B,CAAA;AAqEjC,eAAsB,gBACpB,OAAA,EACoB;AACpB,EAAA,MAAM,QAAA,uBAAe,GAAA,EAA0B;AAE/C,EAAA,MAAM,MAAA,GAAiB,YAAA,CAAa,OAAA,CAAQ,MAAA,EAAQ,QAAQ,KAAK,CAAA;AAEjE,EAAA,MAAM,aAAmC,IAAI,oBAAA;AAAA,IAC3C,MAAM,mBAAA,CAAoB,OAAA,CAAQ,QAAA,IAAY,IAAI,QAAQ,CAAA;AAAA,IAC1D;AAAA,GACF;AAEA,EAAA,MAAM,YAAA,GAAe,MAAM,UAAA,CAAW,UAAA,CAAW;AAAA,IAC/C,eAAA,EAAiB,QAAQ,eAAA,IAAmB,wBAAA;AAAA,IAC5C,kBAAA,EAAoB,8BAA8B,OAAO,CAAA;AAAA,IACzD,UAAA,EAAY,QAAQ,UAAA,GAChB;AAAA,MACE,IAAA,EAAM,QAAQ,UAAA,CAAW,IAAA;AAAA,MACzB,KAAA,EAAO,OAAA,CAAQ,UAAA,CAAW,KAAA,IAAS,QAAQ,UAAA,CAAW,IAAA;AAAA,MACtD,OAAA,EAAS,OAAA,CAAQ,UAAA,CAAW,OAAA,IAAW;AAAA,KACzC,GACA;AAAA,GACI,CAAA;AAEV,EAAA,OAAO;AAAA,IACL,UAAA;AAAA,IACA,mBAAoB,YAAA,CACjB,iBAAA;AAAA,IACH,MAAM,WAAW,MAAA,EAAQ;AACvB,MAAA,MAAM,QAAA,GAAW,MAAM,UAAA,CAAW,UAAA,CAAW;AAAA,QAC3C,KAAK,MAAA,CAAO,GAAA;AAAA,QACZ,UAAA,EAAa,MAAA,CAAO,UAAA,IAAc;AAAC,OAC3B,CAAA;AACV,MAAA,MAAM,YAAa,QAAA,CAAmC,SAAA;AACtD,MAAA,MAAM,KAAA,GAAsB;AAAA,QAC1B,QAAQ,EAAC;AAAA,QACT,WAAA,EAAa,IAAA;AAAA,QACb,IAAA,EAAM,KAAA;AAAA,QACN,MAAA,EAAQ;AAAA,OACV;AACA,MAAA,QAAA,CAAS,GAAA,CAAI,WAAW,KAAK,CAAA;AAC7B,MAAA,OAAO,YAAA,CAAa,UAAA,EAAY,SAAA,EAAW,KAAA,EAAO,QAAQ,CAAA;AAAA,IAC5D,CAAA;AAAA,IACA,MAAM,YAAY,MAAA,EAAQ;AAMxB,MAAA,MAAM,WAAW,WAAA,CAAY;AAAA,QAC3B,WAAW,MAAA,CAAO,SAAA;AAAA,QAClB,KAAK,MAAA,CAAO,GAAA;AAAA,QACZ,UAAA,EAAa,MAAA,CAAO,UAAA,IAAc;AAAC,OAC3B,CAAA;AACV,MAAA,MAAM,KAAA,GAAsB;AAAA,QAC1B,QAAQ,EAAC;AAAA,QACT,WAAA,EAAa,IAAA;AAAA,QACb,IAAA,EAAM,KAAA;AAAA,QACN,MAAA,EAAQ;AAAA,OACV;AACA,MAAA,QAAA,CAAS,GAAA,CAAI,MAAA,CAAO,SAAA,EAAW,KAAK,CAAA;AACpC,MAAA,OAAO,YAAA,CAAa,UAAA,EAAY,MAAA,CAAO,SAAA,EAAW,OAAO,QAAQ,CAAA;AAAA,IACnE,CAAA;AAAA,IACA,MAAM,KAAA,GAAQ;AACZ,MAAA,QAAA,CAAS,KAAA,EAAM;AAAA,IACjB;AAAA,GACF;AACF;AAEA,SAAS,8BACP,OAAA,EACyB;AACzB,EAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,YAAA,IAAgB,EAAC;AACtC,EAAA,OAAO;AAAA,IACL,EAAA,EAAI;AAAA,MACF,YAAA,EAAc,IAAA,CAAK,EAAA,EAAI,YAAA,IAAgB,KAAA;AAAA,MACvC,aAAA,EAAe,IAAA,CAAK,EAAA,EAAI,aAAA,IAAiB;AAAA,KAC3C;AAAA,IACA,QAAA,EAAU,KAAK,QAAA,IAAY;AAAA,GAC7B;AACF;AAEA,SAAS,YAAA,CACP,UAAA,EACA,SAAA,EACA,KAAA,EACA,QAAA,EACkB;AAClB,EAAA,OAAO;AAAA,IACL,SAAA;AAAA,IACA,OAAO,KAAA,EAAO;AACZ,MAAA,IAAI,MAAM,MAAA,EAAQ;AAChB,QAAA,MAAM,IAAI,KAAA;AAAA,UACR,oCAAoC,SAAS,CAAA,gCAAA;AAAA,SAC/C;AAAA,MACF;AACA,MAAA,KAAA,CAAM,MAAA,GAAS,IAAA;AACf,MAAA,KAAA,CAAM,IAAA,GAAO,KAAA;AACb,MAAA,KAAA,CAAM,OAAO,MAAA,GAAS,CAAA;AAEtB,MAAA,MAAM,IAAA,GAAO,aAAa,KAAK,CAAA;AAE/B,MAAgB,WACb,MAAA,CAAO;AAAA,QACN,SAAA;AAAA,QACA,QAAQ,KAAA,CAAM;AAAA,OACN,CAAA,CACT,IAAA,CAAK,CAAC,QAAA,KAAa;AAClB,QAAA,OAAA,CAAQ,KAAA,EAAO;AAAA,UACb,IAAA,EAAM,UAAA;AAAA,UACN,SAAA;AAAA,UACA,MAAA,EACI,SAAqC,UAAA,KACrC,WAAA,IAAe,eACf,QAAA,CAAqC,UAAA,KACrC,eAAe,WAAA,IACjB;AAAA,SACH,CAAA;AAAA,MACH,CAAC,CAAA,CACA,KAAA,CAAM,CAAC,GAAA,KAAiB;AACvB,QAAA,MAAM,UACJ,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,OAAO,GAAG,CAAA;AACjD,QAAA,OAAA,CAAQ,KAAA,EAAO;AAAA,UACb,IAAA,EAAM,OAAA;AAAA,UACN,SAAA;AAAA,UACA,KAAA,EAAO,EAAE,OAAA;AAAQ,SAClB,CAAA;AAAA,MACH,CAAC,CAAA,CACA,OAAA,CAAQ,MAAM;AACb,QAAA,KAAA,CAAM,MAAA,GAAS,KAAA;AACf,QAAA,KAAA,CAAM,IAAA,GAAO,IAAA;AACb,QAAA,KAAA,CAAM,KAAK,CAAA;AAAA,MACb,CAAC;AAEH,MAAA,IAAI,MAAM,MAAA,EAAQ;AAChB,QAAA,KAAA,CAAM,MAAA,CAAO,gBAAA;AAAA,UACX,OAAA;AAAA,UACA,MAAM;AACJ,YAAA,KAAK,UAAA,CAAW,MAAA,CAAO,EAAE,SAAA,EAAoB,CAAA;AAAA,UAC/C,CAAA;AAAA,UACA,EAAE,MAAM,IAAA;AAAK,SACf;AAAA,MACF;AAGA,MAAA,OAAO,IAAA;AAAA,IACT,CAAA;AAAA,IACA,MAAM,MAAA,GAAS;AACb,MAAA,IAAI,CAAC,MAAM,MAAA,EAAQ;AACnB,MAAA,MAAM,UAAA,CAAW,MAAA,CAAO,EAAE,SAAA,EAAoB,CAAA;AAAA,IAChD,CAAA;AAAA,IACA,MAAM,KAAA,GAAQ;AACZ,MAAA,QAAA,CAAS,OAAO,SAAS,CAAA;AAAA,IAC3B;AAAA,GACF;AACF;AAEA,SAAS,aAAa,KAAA,EAAiD;AACrE,EAAA,OAAO;AAAA,IACL,CAAC,MAAA,CAAO,aAAa,CAAA,GAAI;AACvB,MAAA,OAAO;AAAA,QACL,MAAM,IAAA,GAAO;AACX,UAAA,IAAI,KAAA,CAAM,MAAA,CAAO,MAAA,GAAS,CAAA,EAAG;AAC3B,YAAA,MAAMA,MAAAA,GAAQ,KAAA,CAAM,MAAA,CAAO,KAAA,EAAM;AACjC,YAAA,OAAO,EAAE,KAAA,EAAAA,MAAAA,EAAO,IAAA,EAAM,KAAA,EAAM;AAAA,UAC9B;AACA,UAAA,IAAI,MAAM,IAAA,EAAM,OAAO,EAAE,KAAA,EAAO,MAAA,EAAW,MAAM,IAAA,EAAK;AACtD,UAAA,MAAM,KAAA,GAAQ,MAAM,IAAI,OAAA;AAAA,YACtB,CAAC,OAAA,KAAY;AACX,cAAA,KAAA,CAAM,WAAA,GAAc,OAAA;AAAA,YACtB;AAAA,WACF;AACA,UAAA,IAAI,UAAU,MAAA,EAAW,OAAO,EAAE,KAAA,EAAO,MAAA,EAAW,MAAM,IAAA,EAAK;AAC/D,UAAA,OAAO,EAAE,KAAA,EAAO,IAAA,EAAM,KAAA,EAAM;AAAA,QAC9B;AAAA,OACF;AAAA,IACF;AAAA,GACF;AACF;AAEA,SAAS,OAAA,CAAQ,OAAqB,KAAA,EAAoB;AACxD,EAAA,IAAI,MAAM,WAAA,EAAa;AACrB,IAAA,MAAM,IAAI,KAAA,CAAM,WAAA;AAChB,IAAA,KAAA,CAAM,WAAA,GAAc,IAAA;AACpB,IAAA,CAAA,CAAE,KAAK,CAAA;AACP,IAAA;AAAA,EACF;AACA,EAAA,KAAA,CAAM,MAAA,CAAO,KAAK,KAAK,CAAA;AACzB;AAEA,SAAS,MAAM,KAAA,EAAqB;AAClC,EAAA,IAAI,MAAM,WAAA,EAAa;AACrB,IAAA,MAAM,IAAI,KAAA,CAAM,WAAA;AAChB,IAAA,KAAA,CAAM,WAAA,GAAc,IAAA;AACpB,IAAA,CAAA,CAAE,MAAS,CAAA;AAAA,EACb;AACF;AAEA,SAAS,mBAAA,CACP,SACA,QAAA,EACmB;AACnB,EAAA,OAAO;AAAA,IACL,MAAM,cAAc,MAAA,EAAQ;AAC1B,MAAA,MAAM,MAAO,MAAA,CAAkC,SAAA;AAC/C,MAAA,IAAI,CAAC,GAAA,EAAK;AACV,MAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,GAAA,CAAI,GAAG,CAAA;AAC9B,MAAA,IAAI,CAAC,KAAA,EAAO;AAEZ,MAAA,MAAM,SAAU,MAAA,CAAgD,MAAA;AAChE,MAAA,IAAI,CAAC,MAAA,EAAQ;AACb,MAAA,MAAM,KAAA,GAAQ,sBAAA,CAAuB,GAAA,EAAK,MAAM,CAAA;AAChD,MAAA,IAAI,KAAA,EAAO,OAAA,CAAQ,KAAA,EAAO,KAAK,CAAA;AAE/B,MAAA,IAAI,OAAA,CAAQ,aAAA,EAAe,MAAM,OAAA,CAAQ,cAAc,MAAM,CAAA;AAAA,IAC/D,CAAA;AAAA,IACA,MAAM,kBAAkB,MAAA,EAAQ;AAC9B,MAAA,IAAI,OAAA,CAAQ,iBAAA,EAAmB,OAAO,OAAA,CAAQ,kBAAkB,MAAM,CAAA;AACtE,MAAA,MAAM,IAAI,MAAM,oDAAoD,CAAA;AAAA,IACtE,CAAA;AAAA,IACA,MAAM,aAAa,MAAA,EAAQ;AACzB,MAAA,IAAI,OAAA,CAAQ,YAAA,EAAc,OAAO,OAAA,CAAQ,aAAa,MAAM,CAAA;AAC5D,MAAA,MAAM,IAAI,MAAM,mDAAmD,CAAA;AAAA,IACrE,CAAA;AAAA,IACA,MAAM,cAAc,MAAA,EAAQ;AAC1B,MAAA,IAAI,OAAA,CAAQ,aAAA,EAAe,OAAO,OAAA,CAAQ,cAAc,MAAM,CAAA;AAC9D,MAAA,MAAM,IAAI,MAAM,oDAAoD,CAAA;AAAA,IACtE,CAAA;AAAA,IACA,GAAI;AAAA,GACN;AACF;AAEA,SAAS,sBAAA,CACP,WACA,MAAA,EACoB;AACpB,EAAA,QAAQ,OAAO,aAAA;AAAe,IAC5B,KAAK,qBAAA;AACH,MAAA,OAAO;AAAA,QACL,IAAA,EAAM,YAAA;AAAA,QACN,SAAA;AAAA,QACA,IAAA,EAAM,WAAA,CAAY,MAAA,CAAO,OAAO;AAAA,OAClC;AAAA,IACF,KAAK,qBAAA;AACH,MAAA,OAAO;AAAA,QACL,IAAA,EAAM,SAAA;AAAA,QACN,SAAA;AAAA,QACA,IAAA,EAAM,WAAA,CAAY,MAAA,CAAO,OAAO;AAAA,OAClC;AAAA,IACF,KAAK,WAAA,EAAa;AAChB,MAAA,MAAM,UAAA,GAAc,OAAO,UAAA,IAAyB,EAAA;AACpD,MAAA,OAAO;AAAA,QACL,IAAA,EAAM,WAAA;AAAA,QACN,SAAA;AAAA,QACA,UAAA;AAAA,QACA,QAAA,EAAW,MAAA,CAAO,IAAA,IAAoB,MAAA,CAAO,KAAA,IAAoB,MAAA;AAAA,QACjE,SAAA,EAAW,MAAA,CAAO,QAAA,IAAY,MAAA,CAAO,WAAW;AAAC,OACnD;AAAA,IACF;AAAA,IACA,KAAK,kBAAA,EAAoB;AACvB,MAAA,MAAM,SAAS,MAAA,CAAO,MAAA;AACtB,MAAA,IAAI,MAAA,KAAW,WAAA,IAAe,MAAA,KAAW,QAAA,EAAU;AACjD,QAAA,OAAO;AAAA,UACL,IAAA,EAAM,aAAA;AAAA,UACN,SAAA;AAAA,UACA,UAAA,EAAa,OAAO,UAAA,IAAyB,EAAA;AAAA,UAC7C,MAAA,EAAQ,MAAA,CAAO,SAAA,IAAa,MAAA,CAAO,OAAA,IAAW,IAAA;AAAA,UAC9C,SAAS,MAAA,KAAW;AAAA,SACtB;AAAA,MACF;AACA,MAAA,OAAO,IAAA;AAAA,IACT;AAAA,IACA,KAAK,oBAAA;AACH,MAAA,OAAO,IAAA;AAAA,IACT;AACE,MAAA,OAAO,IAAA;AAAA;AAEb;AAEA,SAAS,YAAY,OAAA,EAA0B;AAC7C,EAAA,IAAI,CAAC,SAAS,OAAO,EAAA;AACrB,EAAA,IAAI,OAAO,OAAA,KAAY,QAAA,EAAU,OAAO,OAAA;AACxC,EAAA,IAAI,OAAO,OAAA,KAAY,QAAA,IAAY,OAAA,KAAY,IAAA,EAAM;AACnD,IAAA,MAAM,CAAA,GAAI,OAAA;AACV,IAAA,IAAI,CAAA,CAAE,SAAS,MAAA,IAAU,OAAO,EAAE,IAAA,KAAS,QAAA,SAAiB,CAAA,CAAE,IAAA;AAAA,EAChE;AACA,EAAA,OAAO,EAAA;AACT","file":"index.mjs","sourcesContent":["/**\n * AIP-44 ACP client — drives a subprocess agent via stdio JSON-RPC.\n *\n * Wraps `@agentclientprotocol/sdk`'s `ClientSideConnection`. Owns the\n * connection lifecycle, runs `initialize` on construction, and exposes\n * a session API where each `prompt()` call returns an\n * `AsyncIterable<StreamEvent>` fed by the SDK's `session/update`\n * notification handler.\n */\n\nimport {\n ClientSideConnection,\n ndJsonStream,\n type Client as AcpClientHandlers,\n type Stream,\n} from \"@agentclientprotocol/sdk\"\nimport type { StreamEvent } from \"../types.js\"\n\nconst PROTOCOL_VERSION_DEFAULT = 1\n\nexport interface AcpClientOptions {\n /** Output bytes go here (subprocess stdin). */\n output: WritableStream<Uint8Array>\n /** Input bytes come from here (subprocess stdout). */\n input: ReadableStream<Uint8Array>\n /** Client identity advertised during `initialize`. */\n clientInfo?: {\n name: string\n title?: string\n version?: string\n }\n /** Client capabilities advertised during `initialize`. */\n capabilities?: {\n fs?: { readTextFile?: boolean; writeTextFile?: boolean }\n terminal?: boolean\n }\n /** Optional handlers for agent-initiated requests beyond fs / terminal. */\n handlers?: Partial<AcpClientHandlers>\n /** Protocol version to negotiate. Defaults to the SDK's current. */\n protocolVersion?: number\n}\n\nexport interface AcpClient {\n readonly connection: ClientSideConnection\n /** Negotiated agent capabilities returned from `initialize`. */\n readonly agentCapabilities: Record<string, unknown> | undefined\n newSession(params: {\n cwd: string\n mcpServers?: unknown[]\n }): Promise<AcpClientSession>\n /**\n * Reattach to an existing session by id. Available only when the\n * agent advertised `loadSession: true` in its capabilities (callers\n * should check `agentCapabilities` first; we forward the SDK error\n * verbatim if they don't).\n *\n * Drives the AIP-45 native-resume continuation strategy: the host\n * persists the `sessionId` returned by `newSession`, then on a\n * cold start (process restart, fresh sandbox, different machine)\n * spawns a new subprocess and calls `loadSession` to pick up\n * the conversation from the stored history.\n */\n loadSession(params: {\n sessionId: string\n cwd: string\n mcpServers?: unknown[]\n }): Promise<AcpClientSession>\n close(): Promise<void>\n}\n\nexport interface AcpClientSession {\n readonly sessionId: string\n prompt(input: {\n messages: unknown[]\n signal?: AbortSignal\n }): AsyncIterable<StreamEvent>\n cancel(): Promise<void>\n close(): Promise<void>\n}\n\ninterface SessionState {\n events: StreamEvent[]\n resolveNext: ((evt: StreamEvent | undefined) => void) | null\n done: boolean\n active: boolean\n}\n\nexport async function createAcpClient(\n options: AcpClientOptions,\n): Promise<AcpClient> {\n const sessions = new Map<string, SessionState>()\n\n const stream: Stream = ndJsonStream(options.output, options.input)\n\n const connection: ClientSideConnection = new ClientSideConnection(\n () => buildClientHandlers(options.handlers ?? {}, sessions),\n stream,\n )\n\n const initResponse = await connection.initialize({\n protocolVersion: options.protocolVersion ?? PROTOCOL_VERSION_DEFAULT,\n clientCapabilities: clientCapabilitiesFromOptions(options),\n clientInfo: options.clientInfo\n ? {\n name: options.clientInfo.name,\n title: options.clientInfo.title ?? options.clientInfo.name,\n version: options.clientInfo.version ?? \"0.0.0\",\n }\n : undefined,\n } as never)\n\n return {\n connection,\n agentCapabilities: (initResponse as { agentCapabilities?: Record<string, unknown> })\n .agentCapabilities,\n async newSession(params) {\n const response = await connection.newSession({\n cwd: params.cwd,\n mcpServers: (params.mcpServers ?? []) as never,\n } as never)\n const sessionId = (response as { sessionId: string }).sessionId\n const state: SessionState = {\n events: [],\n resolveNext: null,\n done: false,\n active: false,\n }\n sessions.set(sessionId, state)\n return buildSession(connection, sessionId, state, sessions)\n },\n async loadSession(params) {\n // SDK returns `LoadSessionResponse` (no body fields we need); the\n // sessionId is what the caller already provided. We register a\n // fresh `SessionState` so subsequent `prompt` calls have a slot\n // to flush events into — same lifecycle shape as a brand-new\n // session.\n await connection.loadSession({\n sessionId: params.sessionId,\n cwd: params.cwd,\n mcpServers: (params.mcpServers ?? []) as never,\n } as never)\n const state: SessionState = {\n events: [],\n resolveNext: null,\n done: false,\n active: false,\n }\n sessions.set(params.sessionId, state)\n return buildSession(connection, params.sessionId, state, sessions)\n },\n async close() {\n sessions.clear()\n },\n }\n}\n\nfunction clientCapabilitiesFromOptions(\n options: AcpClientOptions,\n): Record<string, unknown> {\n const caps = options.capabilities ?? {}\n return {\n fs: {\n readTextFile: caps.fs?.readTextFile ?? false,\n writeTextFile: caps.fs?.writeTextFile ?? false,\n },\n terminal: caps.terminal ?? false,\n }\n}\n\nfunction buildSession(\n connection: ClientSideConnection,\n sessionId: string,\n state: SessionState,\n sessions: Map<string, SessionState>,\n): AcpClientSession {\n return {\n sessionId,\n prompt(input) {\n if (state.active) {\n throw new Error(\n `AcpClientSession.prompt: session ${sessionId} already has an in-flight prompt`,\n )\n }\n state.active = true\n state.done = false\n state.events.length = 0\n\n const iter = makeIterator(state)\n\n const promise = connection\n .prompt({\n sessionId,\n prompt: input.messages as never,\n } as never)\n .then((response) => {\n enqueue(state, {\n kind: \"turn-end\",\n sessionId,\n reason:\n ((response as { stopReason?: string }).stopReason ===\n \"cancelled\" && \"cancelled\") ||\n ((response as { stopReason?: string }).stopReason ===\n \"max_turns\" && \"max_turns\") ||\n \"completed\",\n })\n })\n .catch((err: unknown) => {\n const message =\n err instanceof Error ? err.message : String(err)\n enqueue(state, {\n kind: \"error\",\n sessionId,\n error: { message },\n })\n })\n .finally(() => {\n state.active = false\n state.done = true\n flush(state)\n })\n\n if (input.signal) {\n input.signal.addEventListener(\n \"abort\",\n () => {\n void connection.cancel({ sessionId } as never)\n },\n { once: true },\n )\n }\n\n void promise\n return iter\n },\n async cancel() {\n if (!state.active) return\n await connection.cancel({ sessionId } as never)\n },\n async close() {\n sessions.delete(sessionId)\n },\n }\n}\n\nfunction makeIterator(state: SessionState): AsyncIterable<StreamEvent> {\n return {\n [Symbol.asyncIterator]() {\n return {\n async next() {\n if (state.events.length > 0) {\n const value = state.events.shift() as StreamEvent\n return { value, done: false }\n }\n if (state.done) return { value: undefined, done: true }\n const value = await new Promise<StreamEvent | undefined>(\n (resolve) => {\n state.resolveNext = resolve\n },\n )\n if (value === undefined) return { value: undefined, done: true }\n return { value, done: false }\n },\n }\n },\n }\n}\n\nfunction enqueue(state: SessionState, event: StreamEvent) {\n if (state.resolveNext) {\n const r = state.resolveNext\n state.resolveNext = null\n r(event)\n return\n }\n state.events.push(event)\n}\n\nfunction flush(state: SessionState) {\n if (state.resolveNext) {\n const r = state.resolveNext\n state.resolveNext = null\n r(undefined)\n }\n}\n\nfunction buildClientHandlers(\n partial: Partial<AcpClientHandlers>,\n sessions: Map<string, SessionState>,\n): AcpClientHandlers {\n return {\n async sessionUpdate(params) {\n const sid = (params as { sessionId?: string }).sessionId\n if (!sid) return\n const state = sessions.get(sid)\n if (!state) return\n\n const update = (params as { update?: Record<string, unknown> }).update\n if (!update) return\n const event = translateSessionUpdate(sid, update)\n if (event) enqueue(state, event)\n\n if (partial.sessionUpdate) await partial.sessionUpdate(params)\n },\n async requestPermission(params) {\n if (partial.requestPermission) return partial.requestPermission(params)\n throw new Error(\"AcpClient.requestPermission: no handler configured\")\n },\n async readTextFile(params) {\n if (partial.readTextFile) return partial.readTextFile(params)\n throw new Error(\"AcpClient.readTextFile: capability not advertised\")\n },\n async writeTextFile(params) {\n if (partial.writeTextFile) return partial.writeTextFile(params)\n throw new Error(\"AcpClient.writeTextFile: capability not advertised\")\n },\n ...(partial as object),\n } as AcpClientHandlers\n}\n\nfunction translateSessionUpdate(\n sessionId: string,\n update: Record<string, unknown>,\n): StreamEvent | null {\n switch (update.sessionUpdate) {\n case \"agent_message_chunk\":\n return {\n kind: \"text-delta\",\n sessionId,\n text: extractText(update.content),\n }\n case \"agent_thought_chunk\":\n return {\n kind: \"thought\",\n sessionId,\n text: extractText(update.content),\n }\n case \"tool_call\": {\n const toolCallId = (update.toolCallId as string) ?? \"\"\n return {\n kind: \"tool-call\",\n sessionId,\n toolCallId,\n toolName: (update.kind as string) ?? (update.title as string) ?? \"tool\",\n arguments: update.rawInput ?? update.content ?? {},\n }\n }\n case \"tool_call_update\": {\n const status = update.status as string | undefined\n if (status === \"completed\" || status === \"failed\") {\n return {\n kind: \"tool-result\",\n sessionId,\n toolCallId: (update.toolCallId as string) ?? \"\",\n result: update.rawOutput ?? update.content ?? null,\n isError: status === \"failed\",\n }\n }\n return null\n }\n case \"user_message_chunk\":\n return null\n default:\n return null\n }\n}\n\nfunction extractText(content: unknown): string {\n if (!content) return \"\"\n if (typeof content === \"string\") return content\n if (typeof content === \"object\" && content !== null) {\n const c = content as { type?: string; text?: string }\n if (c.type === \"text\" && typeof c.text === \"string\") return c.text\n }\n return \"\"\n}\n"]}
1
+ {"version":3,"sources":["../../src/client/index.ts"],"names":["value"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * AIP-44 ACP client — drives a subprocess agent via stdio JSON-RPC.\n *\n * Wraps `@agentclientprotocol/sdk`'s `ClientSideConnection`. Owns the\n * connection lifecycle, runs `initialize` on construction, and exposes\n * a session API where each `prompt()` call returns an\n * `AsyncIterable<StreamEvent>` fed by the SDK's `session/update`\n * notification handler.\n */\n\nimport {\n ClientSideConnection,\n ndJsonStream,\n type Client as AcpClientHandlers,\n type Stream,\n} from \"@agentclientprotocol/sdk\"\nimport type { AcpMcpServer, StreamEvent } from \"../types.js\"\n\nconst PROTOCOL_VERSION_DEFAULT = 1\n\n/**\n * Map our internal `AcpMcpServer` (`{ name, transport, ref }`) onto the\n * `@agentclientprotocol/sdk` wire shape expected by\n * `session/new.mcpServers` (and `session/load.mcpServers`).\n *\n * The ACP `McpServer` union is transport-tagged and field-named\n * differently from our compact internal form:\n * - http → `{ type: \"http\", name, url, headers: [] }`\n * - sse → `{ type: \"sse\", name, url, headers: [] }`\n * - stdio → `{ name, command, args: [], env: [] }` (untagged variant)\n *\n * Without this mapping the raw `{ transport, ref }` entry reaches the\n * agent verbatim and `session/new` rejects with `Invalid params` — the\n * symptom that broke orchestrator spawns (the auto-injected scoped\n * gateway is an `http` entry).\n *\n * Retro-compat: an entry that already carries an ACP-native discriminator\n * (`type`/`url`/`command`) is passed through untouched, so callers that\n * pre-shape their entries keep working.\n */\nexport function toAcpMcpServers(servers: readonly unknown[]): unknown[] {\n return servers.map(toAcpMcpServer)\n}\n\nfunction toAcpMcpServer(server: unknown): unknown {\n if (!server || typeof server !== \"object\") return server\n const entry = server as Record<string, unknown>\n\n // Already ACP-native (has a wire discriminator) → leave as-is.\n if (\"type\" in entry || \"url\" in entry || \"command\" in entry) return entry\n\n const transport = entry.transport\n // Not our `{ name, transport, ref }` shape → nothing to map.\n if (typeof transport !== \"string\") return entry\n\n const { name, ref } = entry as Pick<AcpMcpServer, \"name\" | \"ref\"> & {\n transport: string\n }\n\n switch (transport) {\n case \"http\":\n return { type: \"http\", name, url: ref ?? \"\", headers: [] }\n case \"sse\":\n return { type: \"sse\", name, url: ref ?? \"\", headers: [] }\n case \"stdio\":\n return { name, command: ref ?? \"\", args: [], env: [] }\n default:\n return entry\n }\n}\n\nexport interface AcpClientOptions {\n /** Output bytes go here (subprocess stdin). */\n output: WritableStream<Uint8Array>\n /** Input bytes come from here (subprocess stdout). */\n input: ReadableStream<Uint8Array>\n /** Client identity advertised during `initialize`. */\n clientInfo?: {\n name: string\n title?: string\n version?: string\n }\n /** Client capabilities advertised during `initialize`. */\n capabilities?: {\n fs?: { readTextFile?: boolean; writeTextFile?: boolean }\n terminal?: boolean\n }\n /** Optional handlers for agent-initiated requests beyond fs / terminal. */\n handlers?: Partial<AcpClientHandlers>\n /** Protocol version to negotiate. Defaults to the SDK's current. */\n protocolVersion?: number\n /**\n * Called on ANY JSON-RPC traffic with the child agent process — every\n * incoming `session/update` notification (even ones that\n * `translateSessionUpdate` maps to `null`, e.g. a `tool_call_update`\n * still in progress) and every outbound RPC call this client makes\n * (`newSession`, `loadSession`, `setSessionConfigOption`, `prompt`,\n * `cancel`). Distinct from the `StreamEvent` stream: fires even\n * during gaps where no event is produced, so a host can track\n * liveness independent of what the agent actually says.\n */\n onActivity?: () => void\n /**\n * Turn-idle watchdog: if a `prompt()` call goes this many ms with NO\n * activity signal (see `onActivity` above — incoming `session/update`\n * notifications, outbound RPCs) and the underlying `connection.prompt()`\n * promise still hasn't resolved, synthesize a `turn-end` event with\n * `reason: \"watchdog-timeout\"` so the caller's async iterator completes\n * instead of hanging forever. The timer resets on every activity signal\n * observed DURING that turn, so a legitimately long tool-call chain\n * (still producing activity, just no user-visible output) never\n * false-positives — this is \"N ms of true silence,\" not \"N ms since the\n * turn started.\"\n *\n * Undefined (the default) disables the watchdog entirely — existing\n * callers see no behavior change. If the real `connection.prompt()`\n * eventually settles after the synthetic turn-end was already emitted,\n * the late result is logged and discarded (no crash, no duplicate\n * turn-end for the same logical turn).\n */\n turnIdleTimeoutMs?: number\n}\n\nexport interface AcpClient {\n readonly connection: ClientSideConnection\n /** Negotiated agent capabilities returned from `initialize`. */\n readonly agentCapabilities: Record<string, unknown> | undefined\n newSession(params: {\n cwd: string\n mcpServers?: unknown[]\n /**\n * Model to select via `session/set_config_option` immediately after\n * `newSession`. The claude-agent-acp wrapper supports this as\n * `configId:\"model\"`. Omit to keep the agent's own default.\n */\n model?: string\n /**\n * Effort level to select via `session/set_config_option` after\n * `newSession`. Effort is model-dependent — same label ≠ same\n * budget across models; defaults differ too (see AgentCliConnectOptions).\n * Omit to keep the model's own default.\n */\n effort?: string\n }): Promise<AcpClientSession>\n /**\n * Reattach to an existing session by id. Available only when the\n * agent advertised `loadSession: true` in its capabilities (callers\n * should check `agentCapabilities` first; we forward the SDK error\n * verbatim if they don't).\n *\n * Drives the AIP-45 native-resume continuation strategy: the host\n * persists the `sessionId` returned by `newSession`, then on a\n * cold start (process restart, fresh sandbox, different machine)\n * spawns a new subprocess and calls `loadSession` to pick up\n * the conversation from the stored history.\n */\n loadSession(params: {\n sessionId: string\n cwd: string\n mcpServers?: unknown[]\n }): Promise<AcpClientSession>\n close(): Promise<void>\n}\n\nexport interface AcpClientSession {\n readonly sessionId: string\n prompt(input: {\n messages: unknown[]\n signal?: AbortSignal\n }): AsyncIterable<StreamEvent>\n cancel(): Promise<void>\n close(): Promise<void>\n}\n\ninterface SessionState {\n events: StreamEvent[]\n resolveNext: ((evt: StreamEvent | undefined) => void) | null\n done: boolean\n active: boolean\n /**\n * Set by `buildSession.prompt()` for the duration of an in-flight turn\n * when a watchdog timer is armed; cleared once that turn settles. Lets\n * the `sessionUpdate` handler (which only has a `sessionId`, not a\n * closure over the turn's timer) bump the SAME timer an incoming\n * notification is liveness evidence for.\n */\n resetWatchdogTimer?: () => void\n}\n\nexport async function createAcpClient(\n options: AcpClientOptions,\n): Promise<AcpClient> {\n const sessions = new Map<string, SessionState>()\n\n const stream: Stream = ndJsonStream(options.output, options.input)\n\n const connection: ClientSideConnection = new ClientSideConnection(\n () => buildClientHandlers(options.handlers ?? {}, sessions, options.onActivity),\n stream,\n )\n\n const initResponse = await connection.initialize({\n protocolVersion: options.protocolVersion ?? PROTOCOL_VERSION_DEFAULT,\n clientCapabilities: clientCapabilitiesFromOptions(options),\n clientInfo: options.clientInfo\n ? {\n name: options.clientInfo.name,\n title: options.clientInfo.title ?? options.clientInfo.name,\n version: options.clientInfo.version ?? \"0.0.0\",\n }\n : undefined,\n } as never)\n\n return {\n connection,\n agentCapabilities: (initResponse as { agentCapabilities?: Record<string, unknown> })\n .agentCapabilities,\n async newSession(params) {\n const response = await connection.newSession({\n cwd: params.cwd,\n mcpServers: toAcpMcpServers(params.mcpServers ?? []) as never,\n } as never)\n options.onActivity?.()\n const sessionId = (response as { sessionId: string }).sessionId\n const state: SessionState = {\n events: [],\n resolveNext: null,\n done: false,\n active: false,\n }\n sessions.set(sessionId, state)\n // Apply model + effort via session/set_config_option immediately\n // after newSession. The claude-agent-acp wrapper handles these as\n // configId:\"model\" and configId:\"effort\". Both are optional — when\n // omitted the agent keeps its own defaults (which vary by model).\n // We call these sequentially so a model switch (which rebuilds the\n // effort options) always precedes the effort set.\n if (params.model) {\n await connection.setSessionConfigOption({\n configId: \"model\",\n value: params.model,\n sessionId,\n } as never)\n options.onActivity?.()\n }\n if (params.effort) {\n try {\n await connection.setSessionConfigOption({\n configId: \"effort\",\n value: params.effort,\n sessionId,\n } as never)\n options.onActivity?.()\n } catch (err) {\n // Best-effort: this ACP server does not support the \"effort\" config\n // option (e.g. claude-agent-acp ignores unknown config keys and\n // some versions reject them outright). A rejected set_config_option\n // must never kill the spawn — effort is silently ignored instead.\n console.warn(\n `[acp] set_config_option effort=\"${params.effort}\" rejected by server (best-effort):`,\n err instanceof Error ? err.message : err,\n )\n }\n }\n return buildSession(\n connection,\n sessionId,\n state,\n sessions,\n options.onActivity,\n options.turnIdleTimeoutMs,\n )\n },\n async loadSession(params) {\n // SDK returns `LoadSessionResponse` (no body fields we need); the\n // sessionId is what the caller already provided. We register a\n // fresh `SessionState` so subsequent `prompt` calls have a slot\n // to flush events into — same lifecycle shape as a brand-new\n // session.\n await connection.loadSession({\n sessionId: params.sessionId,\n cwd: params.cwd,\n mcpServers: toAcpMcpServers(params.mcpServers ?? []) as never,\n } as never)\n options.onActivity?.()\n const state: SessionState = {\n events: [],\n resolveNext: null,\n done: false,\n active: false,\n }\n sessions.set(params.sessionId, state)\n return buildSession(\n connection,\n params.sessionId,\n state,\n sessions,\n options.onActivity,\n options.turnIdleTimeoutMs,\n )\n },\n async close() {\n sessions.clear()\n },\n }\n}\n\nfunction clientCapabilitiesFromOptions(\n options: AcpClientOptions,\n): Record<string, unknown> {\n const caps = options.capabilities ?? {}\n return {\n fs: {\n readTextFile: caps.fs?.readTextFile ?? false,\n writeTextFile: caps.fs?.writeTextFile ?? false,\n },\n terminal: caps.terminal ?? false,\n }\n}\n\nfunction buildSession(\n connection: ClientSideConnection,\n sessionId: string,\n state: SessionState,\n sessions: Map<string, SessionState>,\n onActivity?: () => void,\n turnIdleTimeoutMs?: number,\n): AcpClientSession {\n return {\n sessionId,\n prompt(input) {\n if (state.active) {\n throw new Error(\n `AcpClientSession.prompt: session ${sessionId} already has an in-flight prompt`,\n )\n }\n state.active = true\n state.done = false\n state.events.length = 0\n\n const iter = makeIterator(state)\n\n // Turn-idle watchdog — undefined turnIdleTimeoutMs disables it\n // entirely (existing callers see no behavior change). Armed below\n // and re-armed on every activity signal observed during this turn\n // (outbound send, resolve, and incoming session/update via\n // `state.resetWatchdogTimer`); cleared once the turn settles.\n let watchdogTimer: ReturnType<typeof setTimeout> | undefined\n let watchdogFired = false\n const clearWatchdogTimer = () => {\n if (watchdogTimer) clearTimeout(watchdogTimer)\n watchdogTimer = undefined\n }\n const armWatchdogTimer = () => {\n if (turnIdleTimeoutMs === undefined) return\n clearWatchdogTimer()\n watchdogTimer = setTimeout(() => {\n watchdogFired = true\n state.done = true\n enqueue(state, { kind: \"turn-end\", sessionId, reason: \"watchdog-timeout\" })\n }, turnIdleTimeoutMs)\n }\n state.resetWatchdogTimer = armWatchdogTimer\n\n // Outbound send — proves the daemon is still driving this turn,\n // independent of whatever StreamEvents come back. Also the initial\n // arm of the watchdog timer, so the timeout is measured from \"we\n // just sent this\" rather than some earlier idle point.\n armWatchdogTimer()\n onActivity?.()\n const promise = connection\n .prompt({\n sessionId,\n prompt: input.messages as never,\n } as never)\n .then((response) => {\n clearWatchdogTimer()\n onActivity?.()\n if (watchdogFired) {\n console.warn(\n `[acp] session ${sessionId}: connection.prompt() resolved after the ` +\n `turn-idle watchdog already synthesized a turn-end — ignoring late response.`,\n )\n return\n }\n enqueue(state, {\n kind: \"turn-end\",\n sessionId,\n reason:\n ((response as { stopReason?: string }).stopReason ===\n \"cancelled\" && \"cancelled\") ||\n ((response as { stopReason?: string }).stopReason ===\n \"max_turns\" && \"max_turns\") ||\n \"completed\",\n })\n })\n .catch((err: unknown) => {\n clearWatchdogTimer()\n if (watchdogFired) {\n console.warn(\n `[acp] session ${sessionId}: connection.prompt() rejected after the ` +\n `turn-idle watchdog already synthesized a turn-end — ignoring late error:`,\n err instanceof Error ? err.message : err,\n )\n return\n }\n const message =\n err instanceof Error ? err.message : String(err)\n enqueue(state, {\n kind: \"error\",\n sessionId,\n error: { message },\n })\n })\n .finally(() => {\n state.active = false\n state.done = true\n state.resetWatchdogTimer = undefined\n clearWatchdogTimer()\n flush(state)\n })\n\n if (input.signal) {\n input.signal.addEventListener(\n \"abort\",\n () => {\n void connection.cancel({ sessionId } as never)\n },\n { once: true },\n )\n }\n\n void promise\n return iter\n },\n async cancel() {\n if (!state.active) return\n await connection.cancel({ sessionId } as never)\n onActivity?.()\n },\n async close() {\n sessions.delete(sessionId)\n },\n }\n}\n\nfunction makeIterator(state: SessionState): AsyncIterable<StreamEvent> {\n return {\n [Symbol.asyncIterator]() {\n return {\n async next() {\n if (state.events.length > 0) {\n const value = state.events.shift() as StreamEvent\n return { value, done: false }\n }\n if (state.done) return { value: undefined, done: true }\n const value = await new Promise<StreamEvent | undefined>(\n (resolve) => {\n state.resolveNext = resolve\n },\n )\n if (value === undefined) return { value: undefined, done: true }\n return { value, done: false }\n },\n }\n },\n }\n}\n\nfunction enqueue(state: SessionState, event: StreamEvent) {\n if (state.resolveNext) {\n const r = state.resolveNext\n state.resolveNext = null\n r(event)\n return\n }\n state.events.push(event)\n}\n\nfunction flush(state: SessionState) {\n if (state.resolveNext) {\n const r = state.resolveNext\n state.resolveNext = null\n r(undefined)\n }\n}\n\nfunction buildClientHandlers(\n partial: Partial<AcpClientHandlers>,\n sessions: Map<string, SessionState>,\n onActivity?: () => void,\n): AcpClientHandlers {\n return {\n async sessionUpdate(params) {\n // Every notification is a liveness signal, even ones that don't\n // translate into a StreamEvent below (e.g. an in-progress\n // tool_call_update) — this is the gap that leaves lastOutputAt\n // stale during a long internal tool-call chain.\n onActivity?.()\n\n const sid = (params as { sessionId?: string }).sessionId\n if (!sid) return\n const state = sessions.get(sid)\n if (!state) return\n\n // Reset this session's in-flight turn watchdog (if any) — an\n // incoming notification is exactly the \"not silent\" signal the\n // watchdog exists to detect the absence of.\n state.resetWatchdogTimer?.()\n\n const update = (params as { update?: Record<string, unknown> }).update\n if (!update) return\n const event = translateSessionUpdate(sid, update)\n if (event) enqueue(state, event)\n\n if (partial.sessionUpdate) await partial.sessionUpdate(params)\n },\n async requestPermission(params) {\n if (partial.requestPermission) return partial.requestPermission(params)\n throw new Error(\"AcpClient.requestPermission: no handler configured\")\n },\n async readTextFile(params) {\n if (partial.readTextFile) return partial.readTextFile(params)\n throw new Error(\"AcpClient.readTextFile: capability not advertised\")\n },\n async writeTextFile(params) {\n if (partial.writeTextFile) return partial.writeTextFile(params)\n throw new Error(\"AcpClient.writeTextFile: capability not advertised\")\n },\n ...(partial as object),\n } as AcpClientHandlers\n}\n\nfunction translateSessionUpdate(\n sessionId: string,\n update: Record<string, unknown>,\n): StreamEvent | null {\n switch (update.sessionUpdate) {\n case \"agent_message_chunk\":\n return {\n kind: \"text-delta\",\n sessionId,\n text: extractText(update.content),\n }\n case \"agent_thought_chunk\":\n return {\n kind: \"thought\",\n sessionId,\n text: extractText(update.content),\n }\n case \"tool_call\": {\n const toolCallId = (update.toolCallId as string) ?? \"\"\n // `title` is the descriptive label an agent gives a tool call\n // (\"Reading src/foo.ts\") — prefer it over the coarse `kind`\n // bucket (read/edit/search/execute/…) so rendered lines are\n // informative instead of generic.\n const toolName =\n (update.title as string) ?? (update.kind as string) ?? \"tool\"\n const locations = update.locations as\n | Array<{ path: string; line?: number | null }>\n | undefined\n let args: unknown = update.rawInput ?? update.content ?? {}\n // Some tool calls (e.g. a bare \"read\" with no structured input)\n // carry only `locations` — fold the file paths into `arguments`\n // so they survive downstream instead of being dropped.\n if (update.rawInput == null && locations && locations.length > 0) {\n args =\n locations.length === 1\n ? {\n path: locations[0]!.path,\n ...(locations[0]!.line != null\n ? { line: locations[0]!.line }\n : {}),\n }\n : { paths: locations.map((location) => location.path) }\n }\n return {\n kind: \"tool-call\",\n sessionId,\n toolCallId,\n toolName,\n arguments: args,\n }\n }\n case \"tool_call_update\": {\n const status = update.status as string | undefined\n if (status === \"completed\" || status === \"failed\") {\n return {\n kind: \"tool-result\",\n sessionId,\n toolCallId: (update.toolCallId as string) ?? \"\",\n result: update.rawOutput ?? update.content ?? null,\n isError: status === \"failed\",\n }\n }\n return null\n }\n case \"plan\": {\n const entries = (update.entries as Array<Record<string, unknown>>) ?? []\n return {\n kind: \"plan\",\n sessionId,\n entries: entries.map((entry) => ({\n content: (entry.content as string) ?? \"\",\n priority: (entry.priority as \"high\" | \"medium\" | \"low\") ?? \"medium\",\n status:\n (entry.status as \"pending\" | \"in_progress\" | \"completed\") ??\n \"pending\",\n })),\n }\n }\n case \"usage_update\": {\n const cost = update.cost as { amount: number; currency: string } | null | undefined\n return {\n kind: \"usage_update\",\n sessionId,\n size: (update.size as number) ?? 0,\n used: (update.used as number) ?? 0,\n ...(cost ? { cost } : {}),\n }\n }\n case \"user_message_chunk\":\n return null\n default:\n return null\n }\n}\n\nfunction extractText(content: unknown): string {\n if (!content) return \"\"\n if (typeof content === \"string\") return content\n if (typeof content === \"object\" && content !== null) {\n const c = content as { type?: string; text?: string }\n if (c.type === \"text\" && typeof c.text === \"string\") return c.text\n }\n return \"\"\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { A as AcpDefinition } from './types-BPyht7LH.js';
2
- export { a as AcpAuditConfig, b as AcpCapabilities, c as AcpHandle, d as AcpMcpServer, e as AcpRole, f as AcpTier, g as AcpTransport, h as Aip44Extensions, S as StreamEvent } from './types-BPyht7LH.js';
1
+ import { A as AcpDefinition } from './types-DC2phPNG.js';
2
+ export { a as AcpAuditConfig, b as AcpCapabilities, c as AcpHandle, d as AcpMcpServer, e as AcpRole, f as AcpTier, g as AcpTransport, h as Aip44Extensions, S as StreamEvent } from './types-DC2phPNG.js';
3
3
  export { A as AcpFrontmatter, a as Aip44ExtensionsSchema, b as acpFrontmatterSchema } from './schema-CxWMGVy0.js';
4
4
  import 'zod';
5
5
 
@@ -1,6 +1,6 @@
1
1
  import { A as AcpFrontmatter } from '../schema-CxWMGVy0.js';
2
2
  export { b as acpFrontmatterSchema } from '../schema-CxWMGVy0.js';
3
- import { c as AcpHandle } from '../types-BPyht7LH.js';
3
+ import { c as AcpHandle } from '../types-DC2phPNG.js';
4
4
  import 'zod';
5
5
 
6
6
  /**
@@ -1,5 +1,5 @@
1
1
  import { AgentSideConnection } from '@agentclientprotocol/sdk';
2
- import { c as AcpHandle } from '../types-BPyht7LH.js';
2
+ import { c as AcpHandle } from '../types-DC2phPNG.js';
3
3
 
4
4
  /**
5
5
  * AIP-44 ACP server — exposes an AIP-9 operator to ACP-speaking clients.
@@ -214,6 +214,15 @@ interface HelloFrame {
214
214
  * `ws_open` / `ws_message` / `ws_close`. Hosts MUST gate
215
215
  * `forwardWebSocket()` on this — older daemons ignore the frames. */
216
216
  wsForward?: boolean;
217
+ /**
218
+ * Identifiers of the capabilities the daemon serves — its registered
219
+ * agent adapters / tool surfaces. Lets a host that fronts several
220
+ * daemons enumerate and route by what each one can do, without a
221
+ * round-trip. Omitted by older daemons (host treats as "unknown",
222
+ * not "none"). Advisory: the authoritative surface is still the
223
+ * daemon's live `tools/list` over the HTTP relay.
224
+ */
225
+ tools?: ReadonlyArray<string>;
217
226
  }>;
218
227
  /** User-friendly daemon label, surfaced in host UIs. */
219
228
  label?: string;
@@ -304,6 +313,12 @@ interface WsOpenFrame {
304
313
  /** Path + querystring on the daemon-local upstream, e.g.
305
314
  * `/sessions/abc/pty?cols=80&rows=24`. */
306
315
  path: string;
316
+ /** Optional registered upstream to dial instead of the daemon's
317
+ * default gateway — a NAME the daemon resolves to a base URL (never
318
+ * a URL itself, so the host can't point the daemon at an arbitrary
319
+ * origin). Used to reach an imported local service (e.g. a browser
320
+ * capability server) by alias. Absent ⇒ the daemon's own gateway. */
321
+ upstream?: string;
307
322
  /** Headers to set on the upstream WS request. Hop-by-hop headers
308
323
  * (Connection, Upgrade, Sec-WebSocket-*) MUST be stripped — the
309
324
  * daemon's `ws` client manages those. */
@@ -421,6 +436,19 @@ interface FrameSink {
421
436
  * and can mutate it (e.g. force `cwd` into a workspace) or reject.
422
437
  */
423
438
 
439
+ /**
440
+ * Default ceiling for an upstream WS dial. An upstream that accepts the TCP
441
+ * connection but never completes (or rejects) the WebSocket upgrade would
442
+ * otherwise leave the `ws_open` pending forever — the host never gets an ack or
443
+ * a reject. Bounded so a hung upstream surfaces as a 504 instead.
444
+ */
445
+ declare const DEFAULT_WS_DIAL_TIMEOUT_MS = 10000;
446
+ /**
447
+ * Default ceiling for an `http_request` forward when the frame carries no
448
+ * `timeoutMs`. Covers connect + buffered body (or connect + headers for a
449
+ * stream, after which the bound is handed to `httpStreamIdleTimeoutMs`).
450
+ */
451
+ declare const DEFAULT_HTTP_FORWARD_TIMEOUT_MS = 30000;
424
452
  interface TunnelServerOptions {
425
453
  sink: FrameSink;
426
454
  /**
@@ -441,8 +469,31 @@ interface TunnelServerOptions {
441
469
  * `error: { code: "http_upstream_not_configured" }`.
442
470
  */
443
471
  httpUpstream?: string;
472
+ /**
473
+ * Default ceiling for an `http_request` forward (connect + buffered body, or
474
+ * connect + headers for a stream) when the frame carries no `timeoutMs`.
475
+ * Defaults to DEFAULT_HTTP_FORWARD_TIMEOUT_MS. Disarmed once a streaming
476
+ * response is detected — see `httpStreamIdleTimeoutMs` for the stream phase.
477
+ */
478
+ httpForwardTimeoutMs?: number;
479
+ /**
480
+ * Idle ceiling between chunks of a STREAMING response (SSE / ndjson). A
481
+ * stalled upstream that sends headers then hangs forever between chunks would
482
+ * otherwise block indefinitely (the regular timeout is disarmed for streams,
483
+ * since they're long-lived by design). Counts time since the last byte and
484
+ * resets on every chunk, so a heartbeating stream is never cut. Omitted ⇒ no
485
+ * idle bound (current behaviour); set it to guard against silent stalls.
486
+ */
487
+ httpStreamIdleTimeoutMs?: number;
444
488
  /** User-friendly label sent in the hello frame. */
445
489
  label?: string;
490
+ /**
491
+ * Capability identifiers advertised in the hello frame — the agent
492
+ * adapters / tool surfaces this daemon serves (e.g. the gateway's
493
+ * registered adapter ids). Lets a multi-daemon host enumerate and
494
+ * route by capability without a round-trip. Omit to advertise none.
495
+ */
496
+ tools?: ReadonlyArray<string>;
446
497
  /**
447
498
  * Whether this daemon advertises PTY support. Set to true only when
448
499
  * `spawnPty` is also provided.
@@ -506,7 +557,28 @@ interface TunnelServerOptions {
506
557
  url: string;
507
558
  protocols?: readonly string[];
508
559
  headers?: Readonly<Record<string, string>>;
560
+ /**
561
+ * Aborted when the dial exceeds `wsDialTimeoutMs` (or the tunnel tears down
562
+ * mid-dial). A cooperating implementation SHOULD abort the in-flight upgrade
563
+ * and reject so the socket isn't left half-open; even if it doesn't, the
564
+ * server bounds the wait independently and rejects the `ws_open`.
565
+ */
566
+ signal?: AbortSignal;
509
567
  }) => Promise<UpstreamWebSocket>;
568
+ /**
569
+ * Ceiling for `dialUpstreamWs` to resolve before the `ws_open` is rejected
570
+ * with 504 `upstream_ws_timeout`. Defaults to DEFAULT_WS_DIAL_TIMEOUT_MS.
571
+ */
572
+ wsDialTimeoutMs?: number;
573
+ /**
574
+ * Resolve a named upstream (a `ws_open` frame's `upstream` field) to an
575
+ * HTTP base URL the daemon should dial instead of `httpUpstream`. Lets a
576
+ * host reach an imported local service by alias (e.g. a browser capability
577
+ * server) without ever naming a raw origin — the daemon owns the name→URL
578
+ * mapping. Return undefined for an unknown alias (the open is rejected).
579
+ * Omitted ⇒ every `ws_open` dials the default `httpUpstream`.
580
+ */
581
+ resolveWsUpstream?: (upstream: string) => string | undefined | Promise<string | undefined>;
510
582
  }
511
583
  /**
512
584
  * Minimal upstream WS surface — satisfied by the `ws` library's
@@ -598,6 +670,10 @@ interface TunnelWebSocketOpenRequest {
598
670
  /** Path + querystring on the daemon-local upstream. Daemon prepends
599
671
  * its `ws://<upstream>` base; absolute URLs are rejected. */
600
672
  path: string;
673
+ /** Optional registered upstream to dial instead of the daemon's default
674
+ * gateway — a NAME the daemon resolves to a base URL (e.g. an imported
675
+ * service alias). The host never names a raw origin. */
676
+ upstream?: string;
601
677
  /** Headers forwarded to the upstream upgrade. Hop-by-hop / Sec-* are
602
678
  * stripped by the daemon. Cookies are NOT forwarded unless the
603
679
  * daemon was started with `--upstream-cookies`. */
@@ -740,4 +816,4 @@ interface WebSocketLike {
740
816
  }
741
817
  declare function wrapWebSocket(ws: WebSocketLike): FrameSink;
742
818
 
743
- export { type DaemonToHostFrame, type ErrorFrame, type ExitFrame, type FrameSink, type HelloFrame, type HostToDaemonFrame, type KillFrame, type PingFrame, type PongFrame, type PtyProcess, type ReconnectSoonFrame, type ResizeFrame, type SpawnFrame, type SpawnedFrame, type StderrFrame, type StdinFrame, type StdoutFrame, TUNNEL_VERSION, type TunnelChildProcess, type TunnelClient, type TunnelClientOptions, type TunnelFrame, type TunnelHttpRequest, type TunnelHttpResponse, type TunnelHttpStreamResponse, type TunnelServer, type TunnelServerOptions, type TunnelSpawnOptions, type TunnelWebSocket, type TunnelWebSocketOpenRequest, type UpstreamWebSocket, type WebSocketLike, type WsCloseFrame, type WsMessageFrame, type WsOpenAckFrame, type WsOpenFrame, createTunnelClient, createTunnelServer, decodeData, encodeData, encodeFrame, parseFrame, wrapWebSocket };
819
+ export { DEFAULT_HTTP_FORWARD_TIMEOUT_MS, DEFAULT_WS_DIAL_TIMEOUT_MS, type DaemonToHostFrame, type ErrorFrame, type ExitFrame, type FrameSink, type HelloFrame, type HostToDaemonFrame, type KillFrame, type PingFrame, type PongFrame, type PtyProcess, type ReconnectSoonFrame, type ResizeFrame, type SpawnFrame, type SpawnedFrame, type StderrFrame, type StdinFrame, type StdoutFrame, TUNNEL_VERSION, type TunnelChildProcess, type TunnelClient, type TunnelClientOptions, type TunnelFrame, type TunnelHttpRequest, type TunnelHttpResponse, type TunnelHttpStreamResponse, type TunnelServer, type TunnelServerOptions, type TunnelSpawnOptions, type TunnelWebSocket, type TunnelWebSocketOpenRequest, type UpstreamWebSocket, type WebSocketLike, type WsCloseFrame, type WsMessageFrame, type WsOpenAckFrame, type WsOpenFrame, createTunnelClient, createTunnelServer, decodeData, encodeData, encodeFrame, parseFrame, wrapWebSocket };