@agentproto/acp 0.2.0 → 0.4.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/client/index.d.ts +49 -5
- package/dist/client/index.mjs +158 -17
- package/dist/client/index.mjs.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/manifest/index.d.ts +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/{types-BPyht7LH.d.ts → types-Sxd1kKk2.d.ts} +41 -1
- package/package.json +1 -1
package/dist/client/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ClientSideConnection, Client } from '@agentclientprotocol/sdk';
|
|
2
|
-
import { S as StreamEvent } from '../types-
|
|
2
|
+
import { S as StreamEvent } from '../types-Sxd1kKk2.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* AIP-44 ACP client — drives a subprocess agent via stdio JSON-RPC.
|
|
@@ -12,14 +12,14 @@ import { S as StreamEvent } from '../types-BPyht7LH.js';
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* Map our internal `AcpMcpServer` (`{ name, transport, ref }`)
|
|
16
|
-
* `@agentclientprotocol/sdk` wire shape expected by
|
|
15
|
+
* Map our internal `AcpMcpServer` (`{ name, transport, ref, headers, credentialRef }`)
|
|
16
|
+
* onto the `@agentclientprotocol/sdk` wire shape expected by
|
|
17
17
|
* `session/new.mcpServers` (and `session/load.mcpServers`).
|
|
18
18
|
*
|
|
19
19
|
* The ACP `McpServer` union is transport-tagged and field-named
|
|
20
20
|
* differently from our compact internal form:
|
|
21
|
-
* - http → `{ type: "http", name, url, headers: [] }`
|
|
22
|
-
* - sse → `{ type: "sse", name, url, headers: [] }`
|
|
21
|
+
* - http → `{ type: "http", name, url, headers: [{ name, value }, …] }`
|
|
22
|
+
* - sse → `{ type: "sse", name, url, headers: [{ name, value }, …] }`
|
|
23
23
|
* - stdio → `{ name, command, args: [], env: [] }` (untagged variant)
|
|
24
24
|
*
|
|
25
25
|
* Without this mapping the raw `{ transport, ref }` entry reaches the
|
|
@@ -55,6 +55,36 @@ interface AcpClientOptions {
|
|
|
55
55
|
handlers?: Partial<Client>;
|
|
56
56
|
/** Protocol version to negotiate. Defaults to the SDK's current. */
|
|
57
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;
|
|
58
88
|
}
|
|
59
89
|
interface AcpClient {
|
|
60
90
|
readonly connection: ClientSideConnection;
|
|
@@ -67,6 +97,11 @@ interface AcpClient {
|
|
|
67
97
|
* Model to select via `session/set_config_option` immediately after
|
|
68
98
|
* `newSession`. The claude-agent-acp wrapper supports this as
|
|
69
99
|
* `configId:"model"`. Omit to keep the agent's own default.
|
|
100
|
+
*
|
|
101
|
+
* Best-effort: a value the agent can't resolve (a stale/gated/typo'd id)
|
|
102
|
+
* is warned about and dropped — the session still starts on the agent's
|
|
103
|
+
* default model rather than crashing the spawn. See the reject handler
|
|
104
|
+
* in `newSession` for the reasoning (agentproto#186).
|
|
70
105
|
*/
|
|
71
106
|
model?: string;
|
|
72
107
|
/**
|
|
@@ -76,6 +111,15 @@ interface AcpClient {
|
|
|
76
111
|
* Omit to keep the model's own default.
|
|
77
112
|
*/
|
|
78
113
|
effort?: string;
|
|
114
|
+
/**
|
|
115
|
+
* Mode to select via `session/set_config_option` after `newSession`,
|
|
116
|
+
* for ACP servers that model mode selection as a session config
|
|
117
|
+
* option (`configId:"mode"`) instead of a CLI flag — e.g. opencode.
|
|
118
|
+
* Best-effort, same as `model`/`effort`: a rejection is warned about
|
|
119
|
+
* and dropped rather than failing the spawn. Omit to keep the
|
|
120
|
+
* agent's own default mode.
|
|
121
|
+
*/
|
|
122
|
+
mode?: string;
|
|
79
123
|
}): Promise<AcpClientSession>;
|
|
80
124
|
/**
|
|
81
125
|
* Reattach to an existing session by id. Available only when the
|
package/dist/client/index.mjs
CHANGED
|
@@ -15,23 +15,32 @@ function toAcpMcpServer(server) {
|
|
|
15
15
|
if ("type" in entry || "url" in entry || "command" in entry) return entry;
|
|
16
16
|
const transport = entry.transport;
|
|
17
17
|
if (typeof transport !== "string") return entry;
|
|
18
|
-
const
|
|
18
|
+
const name = entry.name;
|
|
19
|
+
const ref = entry.ref;
|
|
20
|
+
const headers = entry.headers;
|
|
19
21
|
switch (transport) {
|
|
20
22
|
case "http":
|
|
21
|
-
return { type: "http", name, url: ref ?? "", headers:
|
|
23
|
+
return { type: "http", name, url: ref ?? "", headers: toAcpHeaders(headers) };
|
|
22
24
|
case "sse":
|
|
23
|
-
return { type: "sse", name, url: ref ?? "", headers:
|
|
25
|
+
return { type: "sse", name, url: ref ?? "", headers: toAcpHeaders(headers) };
|
|
24
26
|
case "stdio":
|
|
25
27
|
return { name, command: ref ?? "", args: [], env: [] };
|
|
26
28
|
default:
|
|
27
29
|
return entry;
|
|
28
30
|
}
|
|
29
31
|
}
|
|
32
|
+
function toAcpHeaders(headers) {
|
|
33
|
+
if (!headers || typeof headers !== "object") return [];
|
|
34
|
+
return Object.entries(headers).map(([name, value]) => ({
|
|
35
|
+
name,
|
|
36
|
+
value: typeof value === "string" ? value : String(value)
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
30
39
|
async function createAcpClient(options) {
|
|
31
40
|
const sessions = /* @__PURE__ */ new Map();
|
|
32
41
|
const stream = ndJsonStream(options.output, options.input);
|
|
33
42
|
const connection = new ClientSideConnection(
|
|
34
|
-
() => buildClientHandlers(options.handlers ?? {}, sessions),
|
|
43
|
+
() => buildClientHandlers(options.handlers ?? {}, sessions, options.onActivity),
|
|
35
44
|
stream
|
|
36
45
|
);
|
|
37
46
|
const initResponse = await connection.initialize({
|
|
@@ -51,6 +60,7 @@ async function createAcpClient(options) {
|
|
|
51
60
|
cwd: params.cwd,
|
|
52
61
|
mcpServers: toAcpMcpServers(params.mcpServers ?? [])
|
|
53
62
|
});
|
|
63
|
+
options.onActivity?.();
|
|
54
64
|
const sessionId = response.sessionId;
|
|
55
65
|
const state = {
|
|
56
66
|
events: [],
|
|
@@ -60,11 +70,18 @@ async function createAcpClient(options) {
|
|
|
60
70
|
};
|
|
61
71
|
sessions.set(sessionId, state);
|
|
62
72
|
if (params.model) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
73
|
+
try {
|
|
74
|
+
await connection.setSessionConfigOption({
|
|
75
|
+
configId: "model",
|
|
76
|
+
value: params.model,
|
|
77
|
+
sessionId
|
|
78
|
+
});
|
|
79
|
+
options.onActivity?.();
|
|
80
|
+
} catch (err) {
|
|
81
|
+
console.warn(
|
|
82
|
+
`[acp] set_config_option model="${params.model}" rejected by server \u2014 keeping the agent's default model. Reason: ${configOptionErrorDetail(err)}`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
68
85
|
}
|
|
69
86
|
if (params.effort) {
|
|
70
87
|
try {
|
|
@@ -73,14 +90,35 @@ async function createAcpClient(options) {
|
|
|
73
90
|
value: params.effort,
|
|
74
91
|
sessionId
|
|
75
92
|
});
|
|
93
|
+
options.onActivity?.();
|
|
76
94
|
} catch (err) {
|
|
77
95
|
console.warn(
|
|
78
|
-
`[acp] set_config_option effort="${params.effort}" rejected by server (best-effort)
|
|
79
|
-
err instanceof Error ? err.message : err
|
|
96
|
+
`[acp] set_config_option effort="${params.effort}" rejected by server (best-effort): ${configOptionErrorDetail(err)}`
|
|
80
97
|
);
|
|
81
98
|
}
|
|
82
99
|
}
|
|
83
|
-
|
|
100
|
+
if (params.mode) {
|
|
101
|
+
try {
|
|
102
|
+
await connection.setSessionConfigOption({
|
|
103
|
+
configId: "mode",
|
|
104
|
+
value: params.mode,
|
|
105
|
+
sessionId
|
|
106
|
+
});
|
|
107
|
+
options.onActivity?.();
|
|
108
|
+
} catch (err) {
|
|
109
|
+
console.warn(
|
|
110
|
+
`[acp] set_config_option mode="${params.mode}" rejected by server (best-effort): ${configOptionErrorDetail(err)}`
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return buildSession(
|
|
115
|
+
connection,
|
|
116
|
+
sessionId,
|
|
117
|
+
state,
|
|
118
|
+
sessions,
|
|
119
|
+
options.onActivity,
|
|
120
|
+
options.turnIdleTimeoutMs
|
|
121
|
+
);
|
|
84
122
|
},
|
|
85
123
|
async loadSession(params) {
|
|
86
124
|
await connection.loadSession({
|
|
@@ -88,6 +126,7 @@ async function createAcpClient(options) {
|
|
|
88
126
|
cwd: params.cwd,
|
|
89
127
|
mcpServers: toAcpMcpServers(params.mcpServers ?? [])
|
|
90
128
|
});
|
|
129
|
+
options.onActivity?.();
|
|
91
130
|
const state = {
|
|
92
131
|
events: [],
|
|
93
132
|
resolveNext: null,
|
|
@@ -95,7 +134,14 @@ async function createAcpClient(options) {
|
|
|
95
134
|
active: false
|
|
96
135
|
};
|
|
97
136
|
sessions.set(params.sessionId, state);
|
|
98
|
-
return buildSession(
|
|
137
|
+
return buildSession(
|
|
138
|
+
connection,
|
|
139
|
+
params.sessionId,
|
|
140
|
+
state,
|
|
141
|
+
sessions,
|
|
142
|
+
options.onActivity,
|
|
143
|
+
options.turnIdleTimeoutMs
|
|
144
|
+
);
|
|
99
145
|
},
|
|
100
146
|
async close() {
|
|
101
147
|
sessions.clear();
|
|
@@ -112,7 +158,7 @@ function clientCapabilitiesFromOptions(options) {
|
|
|
112
158
|
terminal: caps.terminal ?? false
|
|
113
159
|
};
|
|
114
160
|
}
|
|
115
|
-
function buildSession(connection, sessionId, state, sessions) {
|
|
161
|
+
function buildSession(connection, sessionId, state, sessions, onActivity, turnIdleTimeoutMs) {
|
|
116
162
|
return {
|
|
117
163
|
sessionId,
|
|
118
164
|
prompt(input) {
|
|
@@ -125,16 +171,50 @@ function buildSession(connection, sessionId, state, sessions) {
|
|
|
125
171
|
state.done = false;
|
|
126
172
|
state.events.length = 0;
|
|
127
173
|
const iter = makeIterator(state);
|
|
174
|
+
let watchdogTimer;
|
|
175
|
+
let watchdogFired = false;
|
|
176
|
+
const clearWatchdogTimer = () => {
|
|
177
|
+
if (watchdogTimer) clearTimeout(watchdogTimer);
|
|
178
|
+
watchdogTimer = void 0;
|
|
179
|
+
};
|
|
180
|
+
const armWatchdogTimer = () => {
|
|
181
|
+
if (turnIdleTimeoutMs === void 0) return;
|
|
182
|
+
clearWatchdogTimer();
|
|
183
|
+
watchdogTimer = setTimeout(() => {
|
|
184
|
+
watchdogFired = true;
|
|
185
|
+
state.done = true;
|
|
186
|
+
enqueue(state, { kind: "turn-end", sessionId, reason: "watchdog-timeout" });
|
|
187
|
+
}, turnIdleTimeoutMs);
|
|
188
|
+
};
|
|
189
|
+
state.resetWatchdogTimer = armWatchdogTimer;
|
|
190
|
+
armWatchdogTimer();
|
|
191
|
+
onActivity?.();
|
|
128
192
|
connection.prompt({
|
|
129
193
|
sessionId,
|
|
130
194
|
prompt: input.messages
|
|
131
195
|
}).then((response) => {
|
|
196
|
+
clearWatchdogTimer();
|
|
197
|
+
onActivity?.();
|
|
198
|
+
if (watchdogFired) {
|
|
199
|
+
console.warn(
|
|
200
|
+
`[acp] session ${sessionId}: connection.prompt() resolved after the turn-idle watchdog already synthesized a turn-end \u2014 ignoring late response.`
|
|
201
|
+
);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
132
204
|
enqueue(state, {
|
|
133
205
|
kind: "turn-end",
|
|
134
206
|
sessionId,
|
|
135
207
|
reason: response.stopReason === "cancelled" && "cancelled" || response.stopReason === "max_turns" && "max_turns" || "completed"
|
|
136
208
|
});
|
|
137
209
|
}).catch((err) => {
|
|
210
|
+
clearWatchdogTimer();
|
|
211
|
+
if (watchdogFired) {
|
|
212
|
+
console.warn(
|
|
213
|
+
`[acp] session ${sessionId}: connection.prompt() rejected after the turn-idle watchdog already synthesized a turn-end \u2014 ignoring late error:`,
|
|
214
|
+
err instanceof Error ? err.message : err
|
|
215
|
+
);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
138
218
|
const message = err instanceof Error ? err.message : String(err);
|
|
139
219
|
enqueue(state, {
|
|
140
220
|
kind: "error",
|
|
@@ -144,6 +224,8 @@ function buildSession(connection, sessionId, state, sessions) {
|
|
|
144
224
|
}).finally(() => {
|
|
145
225
|
state.active = false;
|
|
146
226
|
state.done = true;
|
|
227
|
+
state.resetWatchdogTimer = void 0;
|
|
228
|
+
clearWatchdogTimer();
|
|
147
229
|
flush(state);
|
|
148
230
|
});
|
|
149
231
|
if (input.signal) {
|
|
@@ -160,6 +242,7 @@ function buildSession(connection, sessionId, state, sessions) {
|
|
|
160
242
|
async cancel() {
|
|
161
243
|
if (!state.active) return;
|
|
162
244
|
await connection.cancel({ sessionId });
|
|
245
|
+
onActivity?.();
|
|
163
246
|
},
|
|
164
247
|
async close() {
|
|
165
248
|
sessions.delete(sessionId);
|
|
@@ -204,13 +287,15 @@ function flush(state) {
|
|
|
204
287
|
r(void 0);
|
|
205
288
|
}
|
|
206
289
|
}
|
|
207
|
-
function buildClientHandlers(partial, sessions) {
|
|
290
|
+
function buildClientHandlers(partial, sessions, onActivity) {
|
|
208
291
|
return {
|
|
209
292
|
async sessionUpdate(params) {
|
|
293
|
+
onActivity?.();
|
|
210
294
|
const sid = params.sessionId;
|
|
211
295
|
if (!sid) return;
|
|
212
296
|
const state = sessions.get(sid);
|
|
213
297
|
if (!state) return;
|
|
298
|
+
state.resetWatchdogTimer?.();
|
|
214
299
|
const update = params.update;
|
|
215
300
|
if (!update) return;
|
|
216
301
|
const event = translateSessionUpdate(sid, update);
|
|
@@ -248,12 +333,21 @@ function translateSessionUpdate(sessionId, update) {
|
|
|
248
333
|
};
|
|
249
334
|
case "tool_call": {
|
|
250
335
|
const toolCallId = update.toolCallId ?? "";
|
|
336
|
+
const toolName = update.title ?? update.kind ?? "tool";
|
|
337
|
+
const locations = update.locations;
|
|
338
|
+
let args = update.rawInput ?? update.content ?? {};
|
|
339
|
+
if (update.rawInput == null && locations && locations.length > 0) {
|
|
340
|
+
args = locations.length === 1 ? {
|
|
341
|
+
path: locations[0].path,
|
|
342
|
+
...locations[0].line != null ? { line: locations[0].line } : {}
|
|
343
|
+
} : { paths: locations.map((location) => location.path) };
|
|
344
|
+
}
|
|
251
345
|
return {
|
|
252
346
|
kind: "tool-call",
|
|
253
347
|
sessionId,
|
|
254
348
|
toolCallId,
|
|
255
|
-
toolName
|
|
256
|
-
arguments:
|
|
349
|
+
toolName,
|
|
350
|
+
arguments: args
|
|
257
351
|
};
|
|
258
352
|
}
|
|
259
353
|
case "tool_call_update": {
|
|
@@ -269,12 +363,59 @@ function translateSessionUpdate(sessionId, update) {
|
|
|
269
363
|
}
|
|
270
364
|
return null;
|
|
271
365
|
}
|
|
366
|
+
case "plan": {
|
|
367
|
+
const entries = update.entries ?? [];
|
|
368
|
+
return {
|
|
369
|
+
kind: "plan",
|
|
370
|
+
sessionId,
|
|
371
|
+
entries: entries.map((entry) => ({
|
|
372
|
+
content: entry.content ?? "",
|
|
373
|
+
priority: entry.priority ?? "medium",
|
|
374
|
+
status: entry.status ?? "pending"
|
|
375
|
+
}))
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
case "usage_update": {
|
|
379
|
+
const cost = update.cost;
|
|
380
|
+
const numeric = (...keys) => {
|
|
381
|
+
for (const key of keys) {
|
|
382
|
+
const v = update[key];
|
|
383
|
+
if (typeof v === "number") return v;
|
|
384
|
+
}
|
|
385
|
+
return void 0;
|
|
386
|
+
};
|
|
387
|
+
const tokensIn = numeric("tokensIn", "input_tokens", "inputTokens");
|
|
388
|
+
const tokensOut = numeric("tokensOut", "output_tokens", "outputTokens");
|
|
389
|
+
return {
|
|
390
|
+
kind: "usage_update",
|
|
391
|
+
sessionId,
|
|
392
|
+
size: update.size ?? 0,
|
|
393
|
+
used: update.used ?? 0,
|
|
394
|
+
...cost ? { cost } : {},
|
|
395
|
+
...tokensIn !== void 0 ? { tokensIn } : {},
|
|
396
|
+
...tokensOut !== void 0 ? { tokensOut } : {}
|
|
397
|
+
};
|
|
398
|
+
}
|
|
272
399
|
case "user_message_chunk":
|
|
273
400
|
return null;
|
|
274
401
|
default:
|
|
275
402
|
return null;
|
|
276
403
|
}
|
|
277
404
|
}
|
|
405
|
+
function configOptionErrorDetail(err) {
|
|
406
|
+
if (typeof err === "object" && err !== null) {
|
|
407
|
+
if ("data" in err && typeof err.data === "object" && err.data !== null) {
|
|
408
|
+
const data = err.data;
|
|
409
|
+
if ("details" in data && typeof data.details === "string" && data.details.length > 0) {
|
|
410
|
+
return data.details;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
if ("message" in err && typeof err.message === "string" && err.message.length > 0) {
|
|
414
|
+
return err.message;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return String(err);
|
|
418
|
+
}
|
|
278
419
|
function extractText(content) {
|
|
279
420
|
if (!content) return "";
|
|
280
421
|
if (typeof content === "string") return content;
|
|
@@ -1 +1 @@
|
|
|
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\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\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: toAcpMcpServers(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 // 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 }\n if (params.effort) {\n try {\n await connection.setSessionConfigOption({\n configId: \"effort\",\n value: params.effort,\n sessionId,\n } as never)\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(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: toAcpMcpServers(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, headers, credentialRef }`)\n * onto the `@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: [{ name, value }, …] }`\n * - sse → `{ type: \"sse\", name, url, headers: [{ name, value }, …] }`\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 = entry.name\n const ref = entry.ref\n const headers = entry.headers\n\n switch (transport) {\n case \"http\":\n return { type: \"http\", name, url: ref ?? \"\", headers: toAcpHeaders(headers) }\n case \"sse\":\n return { type: \"sse\", name, url: ref ?? \"\", headers: toAcpHeaders(headers) }\n case \"stdio\":\n return { name, command: ref ?? \"\", args: [], env: [] }\n default:\n return entry\n }\n}\n\nfunction toAcpHeaders(headers: unknown): Array<{ name: string; value: string }> {\n if (!headers || typeof headers !== \"object\") return []\n return Object.entries(headers).map(([name, value]) => ({\n name,\n value: typeof value === \"string\" ? value : String(value),\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 * Best-effort: a value the agent can't resolve (a stale/gated/typo'd id)\n * is warned about and dropped — the session still starts on the agent's\n * default model rather than crashing the spawn. See the reject handler\n * in `newSession` for the reasoning (agentproto#186).\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 /**\n * Mode to select via `session/set_config_option` after `newSession`,\n * for ACP servers that model mode selection as a session config\n * option (`configId:\"mode\"`) instead of a CLI flag — e.g. opencode.\n * Best-effort, same as `model`/`effort`: a rejection is warned about\n * and dropped rather than failing the spawn. Omit to keep the\n * agent's own default mode.\n */\n mode?: 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 try {\n await connection.setSessionConfigOption({\n configId: \"model\",\n value: params.model,\n sessionId,\n } as never)\n options.onActivity?.()\n } catch (err) {\n // Non-fatal by design. claude-agent-acp validates the requested\n // model against the concrete option ids it advertises for THIS\n // session (e.g. \"default\", \"opus[1m]\", \"sonnet\", \"claude-sonnet-5\",\n // \"haiku\") and rejects anything it can't resolve — including stale\n // or gated ids like \"claude-sonnet-4-6\" — with a JSON-RPC -32603\n // whose only useful text is buried in `error.data.details`\n // (\"Invalid value for config option model: <id>\"). Letting that\n // reject `newSession` turned a bad model id into an opaque\n // \"spawn failed — Internal error\" that killed the whole session\n // (agentproto#186). The accepted vocabulary is dynamic (it varies\n // by wrapper version and account entitlements), so there is no\n // version-stable way to know it before the call — a rejected model\n // must therefore never be fatal. This is NOT a silent drop: the\n // requested id AND the server's own reason are logged, and the\n // session continues on the agent's default model.\n console.warn(\n `[acp] set_config_option model=\"${params.model}\" rejected by server ` +\n `— keeping the agent's default model. Reason: ${configOptionErrorDetail(err)}`,\n )\n }\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 ` +\n `(best-effort): ${configOptionErrorDetail(err)}`,\n )\n }\n }\n if (params.mode) {\n try {\n await connection.setSessionConfigOption({\n configId: \"mode\",\n value: params.mode,\n sessionId,\n } as never)\n options.onActivity?.()\n } catch (err) {\n // Best-effort: this ACP server does not support the \"mode\" config\n // option, or doesn't recognize the requested mode id. A rejected\n // set_config_option must never kill the spawn — mode is silently\n // ignored (the session keeps whatever mode the agent defaults to).\n console.warn(\n `[acp] set_config_option mode=\"${params.mode}\" rejected by server ` +\n `(best-effort): ${configOptionErrorDetail(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 // Accept both camelCase and snake_case token fields — agents differ on\n // the convention and this update kind is non-standard ACP.\n const numeric = (...keys: string[]): number | undefined => {\n for (const key of keys) {\n const v = update[key]\n if (typeof v === \"number\") return v\n }\n return undefined\n }\n const tokensIn = numeric(\"tokensIn\", \"input_tokens\", \"inputTokens\")\n const tokensOut = numeric(\"tokensOut\", \"output_tokens\", \"outputTokens\")\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 ...(tokensIn !== undefined ? { tokensIn } : {}),\n ...(tokensOut !== undefined ? { tokensOut } : {}),\n }\n }\n case \"user_message_chunk\":\n return null\n default:\n return null\n }\n}\n\n/**\n * Best human-readable reason from a failed `session/set_config_option`.\n *\n * The ACP SDK surfaces a server-side rejection as a JSON-RPC `RequestError`\n * whose generic `message` is just \"Internal error\" (code -32603) — the useful\n * text lives in `error.data.details` (e.g. claude-agent-acp's\n * \"Invalid value for config option model: claude-sonnet-4-6\"). Prefer that\n * detail, falling back to the error's own message, then a stringified form.\n * Narrows `unknown` with `in`/`typeof` guards — no casts.\n */\nfunction configOptionErrorDetail(err: unknown): string {\n if (typeof err === \"object\" && err !== null) {\n if (\"data\" in err && typeof err.data === \"object\" && err.data !== null) {\n const data = err.data\n if (\n \"details\" in data &&\n typeof data.details === \"string\" &&\n data.details.length > 0\n ) {\n return data.details\n }\n }\n if (\n \"message\" in err &&\n typeof err.message === \"string\" &&\n err.message.length > 0\n ) {\n return err.message\n }\n }\n return String(err)\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-
|
|
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-
|
|
1
|
+
import { A as AcpDefinition } from './types-Sxd1kKk2.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-Sxd1kKk2.js';
|
|
3
3
|
export { A as AcpFrontmatter, a as Aip44ExtensionsSchema, b as acpFrontmatterSchema } from './schema-CxWMGVy0.js';
|
|
4
4
|
import 'zod';
|
|
5
5
|
|
package/dist/manifest/index.d.ts
CHANGED
package/dist/server/index.d.ts
CHANGED
|
@@ -42,6 +42,16 @@ interface AcpMcpServer {
|
|
|
42
42
|
name: string;
|
|
43
43
|
transport: "stdio" | "http" | "sse";
|
|
44
44
|
ref?: string;
|
|
45
|
+
/** Static HTTP headers sent with every request to an `http` or `sse`
|
|
46
|
+
* MCP server. Useful for fixed auth tokens or content-negotiation
|
|
47
|
+
* headers that are safe to embed in config. */
|
|
48
|
+
headers?: Record<string, string>;
|
|
49
|
+
/** Brokered credential path resolved at spawn time into additional
|
|
50
|
+
* `headers` (typically an `Authorization` header). The path is passed
|
|
51
|
+
* to the daemon's credential broker, so the actual secret never lives
|
|
52
|
+
* in env or config. Mutually usable with `headers`; brokered headers
|
|
53
|
+
* win on collision. */
|
|
54
|
+
credentialRef?: string;
|
|
45
55
|
}
|
|
46
56
|
/** AIP-44 extensions on the agentskills.io baseline. Lives under `metadata.aip44`. */
|
|
47
57
|
interface Aip44Extensions {
|
|
@@ -127,7 +137,14 @@ type StreamEvent = {
|
|
|
127
137
|
} | {
|
|
128
138
|
kind: "turn-end";
|
|
129
139
|
sessionId: string;
|
|
130
|
-
|
|
140
|
+
/**
|
|
141
|
+
* `"watchdog-timeout"` is synthesized client-side (never sent by the
|
|
142
|
+
* agent) when `AcpClientOptions.turnIdleTimeoutMs` elapses with no
|
|
143
|
+
* activity signal during a turn and the underlying `prompt()` call
|
|
144
|
+
* still hasn't resolved — distinguishes an inferred completion from
|
|
145
|
+
* a real one so callers that care can tell the difference.
|
|
146
|
+
*/
|
|
147
|
+
reason: "completed" | "cancelled" | "max_turns" | "error" | "watchdog-timeout";
|
|
131
148
|
} | {
|
|
132
149
|
kind: "error";
|
|
133
150
|
sessionId?: string;
|
|
@@ -136,6 +153,29 @@ type StreamEvent = {
|
|
|
136
153
|
message: string;
|
|
137
154
|
data?: unknown;
|
|
138
155
|
};
|
|
156
|
+
} | {
|
|
157
|
+
kind: "plan";
|
|
158
|
+
sessionId: string;
|
|
159
|
+
entries: Array<{
|
|
160
|
+
content: string;
|
|
161
|
+
priority: "high" | "medium" | "low";
|
|
162
|
+
status: "pending" | "in_progress" | "completed";
|
|
163
|
+
}>;
|
|
164
|
+
} | {
|
|
165
|
+
kind: "usage_update";
|
|
166
|
+
sessionId: string;
|
|
167
|
+
size: number;
|
|
168
|
+
used: number;
|
|
169
|
+
cost?: {
|
|
170
|
+
amount: number;
|
|
171
|
+
currency: string;
|
|
172
|
+
};
|
|
173
|
+
/** Cumulative input/output token counts, when the agent reports them
|
|
174
|
+
* (some ACP agents send per-token usage alongside the context-window
|
|
175
|
+
* `size`/`used`). Lets the daemon price a session that has tokens but
|
|
176
|
+
* no adapter-reported `cost`. */
|
|
177
|
+
tokensIn?: number;
|
|
178
|
+
tokensOut?: number;
|
|
139
179
|
};
|
|
140
180
|
|
|
141
181
|
export type { AcpDefinition as A, StreamEvent as S, AcpAuditConfig as a, AcpCapabilities as b, AcpHandle as c, AcpMcpServer as d, AcpRole as e, AcpTier as f, AcpTransport as g, Aip44Extensions as h };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentproto/acp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "@agentproto/acp — AIP-44 ACP.md reference implementation. An agentproto profile of the Agent Client Protocol (agentclientprotocol.com). Wraps @agentclientprotocol/sdk with createAcpClient (drives subprocess agents) and createAcpServer (exposes AIP-9 operators to ACP-speaking IDEs).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agentproto",
|