zidane 5.13.0 → 5.13.2
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/README.md +15 -0
- package/dist/acp-BqIU2mo-.js +1410 -0
- package/dist/acp-BqIU2mo-.js.map +1 -0
- package/dist/acp-cli.d.ts +1 -0
- package/dist/acp-cli.js +713 -0
- package/dist/acp-cli.js.map +1 -0
- package/dist/acp.d.ts +655 -0
- package/dist/acp.d.ts.map +1 -0
- package/dist/acp.js +2 -0
- package/dist/{agent-Db4ojCSV.d.ts → agent-D7ZL8B2X.d.ts} +2 -2
- package/dist/{agent-Db4ojCSV.d.ts.map → agent-D7ZL8B2X.d.ts.map} +1 -1
- package/dist/chat/pure.d.ts +3 -3
- package/dist/chat.d.ts +6 -6
- package/dist/chat.js +3 -2
- package/dist/chat.js.map +1 -1
- package/dist/contexts/daytona.d.ts +3 -3
- package/dist/contexts/docker.d.ts +1 -1
- package/dist/contexts/docker.d.ts.map +1 -1
- package/dist/contexts/docker.js +4 -1
- package/dist/contexts/docker.js.map +1 -1
- package/dist/contexts/e2b.d.ts +2 -2
- package/dist/{contexts-VhV4Af8x.js → contexts-DHi8LPCp.js} +25 -9
- package/dist/contexts-DHi8LPCp.js.map +1 -0
- package/dist/contexts.d.ts +3 -3
- package/dist/contexts.js +1 -1
- package/dist/eval.d.ts +1 -1
- package/dist/eval.js +3 -3
- package/dist/glob-DCWXy_tr.js +128 -0
- package/dist/glob-DCWXy_tr.js.map +1 -0
- package/dist/{headless-tVN-g6IR.js → headless-0O6HMNBQ.js} +6 -6
- package/dist/{headless-tVN-g6IR.js.map → headless-0O6HMNBQ.js.map} +1 -1
- package/dist/headless.d.ts +1 -1
- package/dist/headless.js +1 -1
- package/dist/{index-BEblm0Hu.d.ts → index-BsyPeCSL.d.ts} +3 -3
- package/dist/{index-BEblm0Hu.d.ts.map → index-BsyPeCSL.d.ts.map} +1 -1
- package/dist/{index-CJ-2g7bY.d.ts → index-CDcQW-2S.d.ts} +3 -3
- package/dist/index-CDcQW-2S.d.ts.map +1 -0
- package/dist/{index-CrMb8jCE.d.ts → index-CF15aqlk.d.ts} +3 -3
- package/dist/{index-CrMb8jCE.d.ts.map → index-CF15aqlk.d.ts.map} +1 -1
- package/dist/index.d.ts +7 -7
- package/dist/index.js +7 -7
- package/dist/lazy-DLOurOC_.js +20 -0
- package/dist/lazy-DLOurOC_.js.map +1 -0
- package/dist/{logger-Dcrj48qY.d.ts → logger-DItaCwPw.d.ts} +2 -2
- package/dist/{logger-Dcrj48qY.d.ts.map → logger-DItaCwPw.d.ts.map} +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/{messages-CGazSyTL.js → messages-DEsLGBB9.js} +2 -2
- package/dist/{messages-CGazSyTL.js.map → messages-DEsLGBB9.js.map} +1 -1
- package/dist/output/stream-json.d.ts +2 -2
- package/dist/output/stream-json.js +1 -1
- package/dist/output/terminal.d.ts +2 -2
- package/dist/{presets-kPEMOCmE.js → presets-HDIxliiq.js} +2 -2
- package/dist/{presets-kPEMOCmE.js.map → presets-HDIxliiq.js.map} +1 -1
- package/dist/presets.d.ts +2 -2
- package/dist/presets.js +1 -1
- package/dist/{providers-Bo2biCyT.js → providers-Cz-RNYZO.js} +7 -13
- package/dist/providers-Cz-RNYZO.js.map +1 -0
- package/dist/providers.d.ts +1 -1
- package/dist/providers.js +2 -2
- package/dist/restate.d.ts +2 -2
- package/dist/session/sqlite.d.ts +1 -1
- package/dist/{session-B69BQSn1.js → session-BDWZZaYa.js} +2 -2
- package/dist/{session-B69BQSn1.js.map → session-BDWZZaYa.js.map} +1 -1
- package/dist/session.d.ts +1 -1
- package/dist/session.js +2 -2
- package/dist/skills.d.ts +2 -2
- package/dist/{tool-formatters-CkqBgPH4.d.ts → tool-formatters-CNSMadtp.d.ts} +2 -2
- package/dist/{tool-formatters-CkqBgPH4.d.ts.map → tool-formatters-CNSMadtp.d.ts.map} +1 -1
- package/dist/tools/fetch-url.d.ts +1 -1
- package/dist/tools/web-search.d.ts +1 -1
- package/dist/{tools-5Bnlq68O.js → tools-DhzKzB1y.js} +39 -56
- package/dist/tools-DhzKzB1y.js.map +1 -0
- package/dist/tools.d.ts +2 -2
- package/dist/tools.js +1 -1
- package/dist/{transcript-anchors-D4PwUMyO.js → transcript-anchors-Cq-8gx8u.js} +9 -1417
- package/dist/transcript-anchors-Cq-8gx8u.js.map +1 -0
- package/dist/{transcript-anchors-BnLZmASt.d.ts → transcript-anchors-EG-SmZRu.d.ts} +4 -4
- package/dist/{transcript-anchors-BnLZmASt.d.ts.map → transcript-anchors-EG-SmZRu.d.ts.map} +1 -1
- package/dist/tui.d.ts +3 -3
- package/dist/tui.js +7 -6
- package/dist/tui.js.map +1 -1
- package/dist/{turn-operations-B6FaQAZN.d.ts → turn-operations-DwtWRYr1.d.ts} +3 -3
- package/dist/{turn-operations-B6FaQAZN.d.ts.map → turn-operations-DwtWRYr1.d.ts.map} +1 -1
- package/dist/{types-B39tBba1.d.ts → types-Bs2oY7Ux.d.ts} +27 -4
- package/dist/types-Bs2oY7Ux.d.ts.map +1 -0
- package/dist/types.d.ts +4 -4
- package/dist/xdg-zlSeVBhQ.js +1417 -0
- package/dist/xdg-zlSeVBhQ.js.map +1 -0
- package/docs/ACP.md +221 -0
- package/package.json +11 -1
- package/dist/contexts-VhV4Af8x.js.map +0 -1
- package/dist/index-CJ-2g7bY.d.ts.map +0 -1
- package/dist/providers-Bo2biCyT.js.map +0 -1
- package/dist/tools-5Bnlq68O.js.map +0 -1
- package/dist/transcript-anchors-D4PwUMyO.js.map +0 -1
- package/dist/types-B39tBba1.d.ts.map +0 -1
|
@@ -0,0 +1,1410 @@
|
|
|
1
|
+
import { I as defaultPromptMessage } from "./messages-DEsLGBB9.js";
|
|
2
|
+
import { d as createAgent } from "./tools-DhzKzB1y.js";
|
|
3
|
+
import { r as createProcessContext } from "./contexts-DHi8LPCp.js";
|
|
4
|
+
import { o as toolResultToText } from "./types-DxHDaqN7.js";
|
|
5
|
+
import { o as readStateKey, r as hashContent, s as resolveReadStateMap } from "./read-state-DH2IuQHX.js";
|
|
6
|
+
import { l as errorMessage } from "./errors-BpPfMo_4.js";
|
|
7
|
+
import { t as effectiveInputFromTurn } from "./stats-DAKBEKjc.js";
|
|
8
|
+
import { i as basic_default } from "./presets-HDIxliiq.js";
|
|
9
|
+
import { r as loadSession, t as createSession } from "./session-BDWZZaYa.js";
|
|
10
|
+
import { resolve } from "node:path";
|
|
11
|
+
import { Buffer } from "node:buffer";
|
|
12
|
+
//#region src/acp/json-rpc.ts
|
|
13
|
+
function createJsonRpcConnection(options) {
|
|
14
|
+
let nextId = 1;
|
|
15
|
+
let closed = false;
|
|
16
|
+
let buffer = Buffer.alloc(0);
|
|
17
|
+
const pending = /* @__PURE__ */ new Map();
|
|
18
|
+
const writeMessage = (message) => {
|
|
19
|
+
if (closed) return;
|
|
20
|
+
const json = JSON.stringify({
|
|
21
|
+
...message,
|
|
22
|
+
jsonrpc: "2.0"
|
|
23
|
+
});
|
|
24
|
+
if (options.framing === "content-length") {
|
|
25
|
+
const bytes = Buffer.byteLength(json);
|
|
26
|
+
options.output.write(`Content-Length: ${bytes}\r\n\r\n${json}`);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
options.output.write(`${json}\n`);
|
|
30
|
+
};
|
|
31
|
+
const handleParsed = async (message) => {
|
|
32
|
+
if (!isObject$1(message)) {
|
|
33
|
+
writeMessage(errorResponse(null, -32600, "Invalid JSON-RPC message."));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const msg = message;
|
|
37
|
+
if ("id" in msg && ("result" in msg || "error" in msg)) {
|
|
38
|
+
const request = pending.get(msg.id);
|
|
39
|
+
if (!request) return;
|
|
40
|
+
pending.delete(msg.id);
|
|
41
|
+
if ("error" in msg && msg.error) request.reject(new JsonRpcRemoteError(msg.error.message, msg.error.code, msg.error.data));
|
|
42
|
+
else request.resolve(msg.result);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (typeof msg.method !== "string") {
|
|
46
|
+
writeMessage(errorResponse(("id" in msg ? msg.id : null) ?? null, -32600, "Missing JSON-RPC method."));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if ("id" in msg) {
|
|
50
|
+
try {
|
|
51
|
+
const response = await options.onRequest(msg);
|
|
52
|
+
if (response) writeMessage(response);
|
|
53
|
+
} catch (err) {
|
|
54
|
+
writeMessage(errorResponse(msg.id, -32603, err instanceof Error ? err.message : String(err)));
|
|
55
|
+
}
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
await options.onNotification?.(msg);
|
|
60
|
+
} catch (err) {
|
|
61
|
+
options.onError?.(err instanceof Error ? err : new Error(String(err)));
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
const onData = (chunk) => {
|
|
65
|
+
buffer = Buffer.concat([buffer, typeof chunk === "string" ? Buffer.from(chunk) : chunk]);
|
|
66
|
+
for (;;) {
|
|
67
|
+
let parsed;
|
|
68
|
+
try {
|
|
69
|
+
parsed = readNextMessage(buffer);
|
|
70
|
+
} catch (err) {
|
|
71
|
+
buffer = Buffer.alloc(0);
|
|
72
|
+
writeMessage(errorResponse(null, -32700, err instanceof Error ? err.message : String(err)));
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
if (!parsed) break;
|
|
76
|
+
buffer = parsed.rest;
|
|
77
|
+
handleParsed(parsed.message);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
const onError = (err) => {
|
|
81
|
+
options.onError?.(err);
|
|
82
|
+
};
|
|
83
|
+
return {
|
|
84
|
+
start() {
|
|
85
|
+
options.input.on("data", onData);
|
|
86
|
+
options.input.on("error", onError);
|
|
87
|
+
},
|
|
88
|
+
close() {
|
|
89
|
+
closed = true;
|
|
90
|
+
options.input.off("data", onData);
|
|
91
|
+
options.input.off("error", onError);
|
|
92
|
+
for (const request of pending.values()) request.reject(/* @__PURE__ */ new Error("JSON-RPC connection closed."));
|
|
93
|
+
pending.clear();
|
|
94
|
+
},
|
|
95
|
+
sendNotification(method, params) {
|
|
96
|
+
writeMessage({
|
|
97
|
+
jsonrpc: "2.0",
|
|
98
|
+
method,
|
|
99
|
+
...params !== void 0 ? { params } : {}
|
|
100
|
+
});
|
|
101
|
+
},
|
|
102
|
+
sendRequest(method, params) {
|
|
103
|
+
if (closed) return Promise.reject(/* @__PURE__ */ new Error("JSON-RPC connection is closed."));
|
|
104
|
+
const id = nextId++;
|
|
105
|
+
writeMessage({
|
|
106
|
+
jsonrpc: "2.0",
|
|
107
|
+
id,
|
|
108
|
+
method,
|
|
109
|
+
...params !== void 0 ? { params } : {}
|
|
110
|
+
});
|
|
111
|
+
return new Promise((resolve, reject) => {
|
|
112
|
+
pending.set(id, {
|
|
113
|
+
resolve,
|
|
114
|
+
reject
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
var JsonRpcRemoteError = class extends Error {
|
|
121
|
+
code;
|
|
122
|
+
data;
|
|
123
|
+
constructor(message, code, data) {
|
|
124
|
+
super(message);
|
|
125
|
+
this.code = code;
|
|
126
|
+
this.data = data;
|
|
127
|
+
this.name = "JsonRpcRemoteError";
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
function successResponse(id, result = {}) {
|
|
131
|
+
return {
|
|
132
|
+
jsonrpc: "2.0",
|
|
133
|
+
id,
|
|
134
|
+
result
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function errorResponse(id, code, message, data) {
|
|
138
|
+
return {
|
|
139
|
+
jsonrpc: "2.0",
|
|
140
|
+
id,
|
|
141
|
+
error: {
|
|
142
|
+
code,
|
|
143
|
+
message,
|
|
144
|
+
...data !== void 0 ? { data } : {}
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function readNextMessage(input) {
|
|
149
|
+
if (input.length === 0) return null;
|
|
150
|
+
const headerEnd = input.indexOf("\r\n\r\n");
|
|
151
|
+
if (headerEnd >= 0) {
|
|
152
|
+
const header = input.subarray(0, headerEnd).toString("utf8");
|
|
153
|
+
const match = /^Content-Length:\s*(\d+)/im.exec(header);
|
|
154
|
+
if (!match) return readLineMessage(input);
|
|
155
|
+
const length = Number(match[1]);
|
|
156
|
+
const bodyStart = headerEnd + 4;
|
|
157
|
+
const bodyEnd = bodyStart + length;
|
|
158
|
+
if (input.length < bodyEnd) return null;
|
|
159
|
+
const body = input.subarray(bodyStart, bodyEnd).toString("utf8");
|
|
160
|
+
return {
|
|
161
|
+
message: JSON.parse(body),
|
|
162
|
+
rest: input.subarray(bodyEnd)
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
if (/^Content-Length:/i.test(input.subarray(0, Math.min(input.length, 32)).toString("utf8"))) return null;
|
|
166
|
+
return readLineMessage(input);
|
|
167
|
+
}
|
|
168
|
+
function readLineMessage(input) {
|
|
169
|
+
let rest = input;
|
|
170
|
+
for (;;) {
|
|
171
|
+
const lineEnd = rest.indexOf("\n");
|
|
172
|
+
if (lineEnd < 0) return null;
|
|
173
|
+
const line = rest.subarray(0, lineEnd).toString("utf8").trim();
|
|
174
|
+
rest = rest.subarray(lineEnd + 1);
|
|
175
|
+
if (line.length === 0) continue;
|
|
176
|
+
return {
|
|
177
|
+
message: JSON.parse(line),
|
|
178
|
+
rest
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
function isObject$1(value) {
|
|
183
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
184
|
+
}
|
|
185
|
+
//#endregion
|
|
186
|
+
//#region src/acp/mapping.ts
|
|
187
|
+
function acpMcpServersToZidane(servers) {
|
|
188
|
+
if (!servers) return [];
|
|
189
|
+
return servers.map((server) => {
|
|
190
|
+
if (server.type === "http") return {
|
|
191
|
+
name: server.name,
|
|
192
|
+
transport: "streamable-http",
|
|
193
|
+
url: server.url,
|
|
194
|
+
headers: headersToRecord(server.headers)
|
|
195
|
+
};
|
|
196
|
+
if (server.type === "sse") return {
|
|
197
|
+
name: server.name,
|
|
198
|
+
transport: "sse",
|
|
199
|
+
url: server.url,
|
|
200
|
+
headers: headersToRecord(server.headers)
|
|
201
|
+
};
|
|
202
|
+
const stdio = server;
|
|
203
|
+
return {
|
|
204
|
+
name: server.name,
|
|
205
|
+
transport: "stdio",
|
|
206
|
+
command: stdio.command,
|
|
207
|
+
args: stdio.args ?? [],
|
|
208
|
+
env: envToRecord(stdio.env)
|
|
209
|
+
};
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
function promptCapabilitiesForProvider(capabilities) {
|
|
213
|
+
return {
|
|
214
|
+
image: capabilities?.vision === true,
|
|
215
|
+
audio: capabilities?.audio === true,
|
|
216
|
+
embeddedContext: true
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
function mcpCapabilities() {
|
|
220
|
+
return {
|
|
221
|
+
http: true,
|
|
222
|
+
sse: true
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
function defaultModes(modes) {
|
|
226
|
+
return modes && modes.length > 0 ? [...modes] : [{
|
|
227
|
+
id: "build",
|
|
228
|
+
name: "Build",
|
|
229
|
+
description: "Full tool access for implementation work."
|
|
230
|
+
}, {
|
|
231
|
+
id: "plan",
|
|
232
|
+
name: "Plan",
|
|
233
|
+
description: "Planning-oriented mode; hosts may narrow tools separately."
|
|
234
|
+
}];
|
|
235
|
+
}
|
|
236
|
+
function makeModeState(modes, currentModeId) {
|
|
237
|
+
return {
|
|
238
|
+
currentModeId,
|
|
239
|
+
availableModes: [...modes]
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
function newSessionResponse(sessionId, modes, currentModeId, configOptions) {
|
|
243
|
+
return {
|
|
244
|
+
sessionId,
|
|
245
|
+
modes: makeModeState(modes, currentModeId),
|
|
246
|
+
...configOptions && configOptions.length > 0 ? { configOptions } : {}
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
function acpPromptToPromptParts(prompt) {
|
|
250
|
+
const parts = [];
|
|
251
|
+
const warnings = [];
|
|
252
|
+
for (const block of prompt) switch (block.type) {
|
|
253
|
+
case "text":
|
|
254
|
+
parts.push({
|
|
255
|
+
type: "text",
|
|
256
|
+
text: block.text
|
|
257
|
+
});
|
|
258
|
+
break;
|
|
259
|
+
case "image":
|
|
260
|
+
if (block.data) parts.push({
|
|
261
|
+
type: "image",
|
|
262
|
+
mediaType: block.mimeType || "image/png",
|
|
263
|
+
data: block.data
|
|
264
|
+
});
|
|
265
|
+
else {
|
|
266
|
+
warnings.push(`Image resource linked instead of embedded: ${block.uri ?? "unknown URI"}`);
|
|
267
|
+
parts.push({
|
|
268
|
+
type: "text",
|
|
269
|
+
text: `[image resource: ${block.uri ?? "unavailable"}]`
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
break;
|
|
273
|
+
case "audio":
|
|
274
|
+
if (block.data) parts.push({
|
|
275
|
+
type: "audio",
|
|
276
|
+
mediaType: block.mimeType || "audio/mpeg",
|
|
277
|
+
data: block.data
|
|
278
|
+
});
|
|
279
|
+
else {
|
|
280
|
+
warnings.push("Audio block omitted because it did not include embedded data.");
|
|
281
|
+
parts.push({
|
|
282
|
+
type: "text",
|
|
283
|
+
text: "[audio resource omitted: no embedded data]"
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
break;
|
|
287
|
+
case "resource_link":
|
|
288
|
+
parts.push({
|
|
289
|
+
type: "text",
|
|
290
|
+
text: renderResourceLink(block)
|
|
291
|
+
});
|
|
292
|
+
break;
|
|
293
|
+
case "resource":
|
|
294
|
+
parts.push(resourceToPromptPart(block));
|
|
295
|
+
break;
|
|
296
|
+
default:
|
|
297
|
+
warnings.push("Unknown ACP content block converted to text.");
|
|
298
|
+
parts.push({
|
|
299
|
+
type: "text",
|
|
300
|
+
text: JSON.stringify(block)
|
|
301
|
+
});
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
return {
|
|
305
|
+
prompt: parts,
|
|
306
|
+
warnings
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
function sessionBlocksToAcp(blocks) {
|
|
310
|
+
const out = [];
|
|
311
|
+
for (const block of blocks) if (block.type === "text" || block.type === "thinking") out.push({
|
|
312
|
+
type: "text",
|
|
313
|
+
text: block.text
|
|
314
|
+
});
|
|
315
|
+
else if (block.type === "image") out.push(imageBlockToAcp(block.mediaType, "data" in block ? block.data : void 0, "ref" in block ? block.ref : void 0, block.name));
|
|
316
|
+
else if (block.type === "audio") {
|
|
317
|
+
if (block.data) out.push({
|
|
318
|
+
type: "audio",
|
|
319
|
+
mimeType: block.mediaType,
|
|
320
|
+
data: block.data
|
|
321
|
+
});
|
|
322
|
+
} else if (block.type === "document") out.push(documentBlockToAcp(block.mediaType, "data" in block ? block.data : void 0, "ref" in block ? block.ref : void 0, block.encoding, block.name));
|
|
323
|
+
return out;
|
|
324
|
+
}
|
|
325
|
+
function toolResultToAcpContent(content) {
|
|
326
|
+
if (typeof content === "string") return [{
|
|
327
|
+
type: "content",
|
|
328
|
+
content: {
|
|
329
|
+
type: "text",
|
|
330
|
+
text: content
|
|
331
|
+
}
|
|
332
|
+
}];
|
|
333
|
+
return content.map((block) => {
|
|
334
|
+
if (block.type === "text") return {
|
|
335
|
+
type: "content",
|
|
336
|
+
content: {
|
|
337
|
+
type: "text",
|
|
338
|
+
text: block.text
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
if (block.type === "image") return {
|
|
342
|
+
type: "content",
|
|
343
|
+
content: imageBlockToAcp(block.mediaType, "data" in block ? block.data : void 0, "ref" in block ? block.ref : void 0, "name" in block ? block.name : void 0)
|
|
344
|
+
};
|
|
345
|
+
if (block.type === "audio" && block.data) return {
|
|
346
|
+
type: "content",
|
|
347
|
+
content: {
|
|
348
|
+
type: "audio",
|
|
349
|
+
mimeType: block.mediaType,
|
|
350
|
+
data: block.data
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
if (block.type === "document") return {
|
|
354
|
+
type: "content",
|
|
355
|
+
content: documentBlockToAcp(block.mediaType, "data" in block ? block.data : void 0, "ref" in block ? block.ref : void 0, block.encoding, "name" in block ? block.name : void 0)
|
|
356
|
+
};
|
|
357
|
+
return {
|
|
358
|
+
type: "content",
|
|
359
|
+
content: {
|
|
360
|
+
type: "text",
|
|
361
|
+
text: toolResultToText([block])
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Build a spec-valid ACP image block. `ImageContent` requires `data` +
|
|
368
|
+
* `mimeType`; when we only have a reference (`ref`/uri) we fall back to a
|
|
369
|
+
* `resource_link` (which requires `name` + `uri`) so we never emit an image
|
|
370
|
+
* block missing its required `data`.
|
|
371
|
+
*/
|
|
372
|
+
function imageBlockToAcp(mimeType, data, ref, name) {
|
|
373
|
+
if (data) return {
|
|
374
|
+
type: "image",
|
|
375
|
+
mimeType,
|
|
376
|
+
data,
|
|
377
|
+
...ref ? { uri: ref } : {}
|
|
378
|
+
};
|
|
379
|
+
return {
|
|
380
|
+
type: "resource_link",
|
|
381
|
+
uri: ref ?? acpSyntheticUri("image", name),
|
|
382
|
+
name: name ?? "image",
|
|
383
|
+
mimeType
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Build a spec-valid embedded `resource` (or a `resource_link` fallback for
|
|
388
|
+
* reference-only documents). `EmbeddedResource.resource` requires a `uri`
|
|
389
|
+
* plus either `text` (text payload) or `blob` (base64), so a synthetic URI is
|
|
390
|
+
* minted when the source block has none.
|
|
391
|
+
*/
|
|
392
|
+
function documentBlockToAcp(mimeType, data, ref, encoding, name) {
|
|
393
|
+
const uri = ref ?? acpSyntheticUri("document", name);
|
|
394
|
+
if (typeof data !== "string") return {
|
|
395
|
+
type: "resource_link",
|
|
396
|
+
uri,
|
|
397
|
+
name: name ?? "document",
|
|
398
|
+
mimeType
|
|
399
|
+
};
|
|
400
|
+
return {
|
|
401
|
+
type: "resource",
|
|
402
|
+
resource: encoding === "text" ? {
|
|
403
|
+
uri,
|
|
404
|
+
text: data,
|
|
405
|
+
mimeType
|
|
406
|
+
} : {
|
|
407
|
+
uri,
|
|
408
|
+
blob: data,
|
|
409
|
+
mimeType
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
function acpSyntheticUri(kind, name) {
|
|
414
|
+
return `acp:///${kind}/${(name ?? kind).replace(/[^\w.-]+/g, "_")}`;
|
|
415
|
+
}
|
|
416
|
+
function toolKindForName(name) {
|
|
417
|
+
if (name === "read_file" || name === "list_files") return "read";
|
|
418
|
+
if (name === "write_file" || name === "edit" || name === "multi_edit") return "edit";
|
|
419
|
+
if (name === "grep" || name === "glob" || name === "tool_search") return "search";
|
|
420
|
+
if (name === "shell" || name === "wait_task" || name === "shell_kill") return "execute";
|
|
421
|
+
if (name === "web_search" || name === "fetch_url") return "fetch";
|
|
422
|
+
if (name === "present_plan" || name === "ask_user") return "think";
|
|
423
|
+
return "other";
|
|
424
|
+
}
|
|
425
|
+
function stopReasonFromRun(input) {
|
|
426
|
+
if (input.aborted) return "cancelled";
|
|
427
|
+
if (input.maxTurnsReached) return "max_turn_requests";
|
|
428
|
+
switch (lastFinishReason$1(input.turnUsage)) {
|
|
429
|
+
case "length": return "max_tokens";
|
|
430
|
+
case "content-filter":
|
|
431
|
+
case "error": return "refusal";
|
|
432
|
+
default: return "end_turn";
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
function usageCost(cost) {
|
|
436
|
+
if (typeof cost !== "number" || !Number.isFinite(cost)) return null;
|
|
437
|
+
return {
|
|
438
|
+
amount: cost,
|
|
439
|
+
currency: "USD"
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
function resolveAcpPath(cwd, path) {
|
|
443
|
+
return path.startsWith("/") ? path : resolve(cwd, path);
|
|
444
|
+
}
|
|
445
|
+
function lastFinishReason$1(turnUsage) {
|
|
446
|
+
for (let i = (turnUsage?.length ?? 0) - 1; i >= 0; i--) {
|
|
447
|
+
const reason = turnUsage?.[i]?.finishReason;
|
|
448
|
+
if (reason) return reason;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
function headersToRecord(headers) {
|
|
452
|
+
const out = {};
|
|
453
|
+
for (const header of headers ?? []) out[header.name] = header.value;
|
|
454
|
+
return out;
|
|
455
|
+
}
|
|
456
|
+
function envToRecord(env) {
|
|
457
|
+
const out = {};
|
|
458
|
+
for (const item of env ?? []) out[item.name] = item.value;
|
|
459
|
+
return out;
|
|
460
|
+
}
|
|
461
|
+
function renderResourceLink(block) {
|
|
462
|
+
const label = block.title ?? block.name ?? block.uri;
|
|
463
|
+
return block.mimeType ? `[resource: ${label}](${block.uri}) (${block.mimeType})` : `[resource: ${label}](${block.uri})`;
|
|
464
|
+
}
|
|
465
|
+
function resourceToPromptPart(block) {
|
|
466
|
+
const resource = block.resource;
|
|
467
|
+
const mediaType = resource.mimeType ?? "text/plain";
|
|
468
|
+
const name = nameFromUri(resource.uri);
|
|
469
|
+
if ("text" in resource && typeof resource.text === "string") return {
|
|
470
|
+
type: "document",
|
|
471
|
+
mediaType,
|
|
472
|
+
data: resource.text,
|
|
473
|
+
encoding: "text",
|
|
474
|
+
...name ? { name } : {}
|
|
475
|
+
};
|
|
476
|
+
if ("blob" in resource && typeof resource.blob === "string") return {
|
|
477
|
+
type: "document",
|
|
478
|
+
mediaType,
|
|
479
|
+
data: resource.blob,
|
|
480
|
+
encoding: "base64",
|
|
481
|
+
...name ? { name } : {}
|
|
482
|
+
};
|
|
483
|
+
return {
|
|
484
|
+
type: "text",
|
|
485
|
+
text: `[resource omitted: ${resource.uri}]`
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
function nameFromUri(uri) {
|
|
489
|
+
if (!uri) return void 0;
|
|
490
|
+
const segment = uri.replace(/[/\\]+$/, "").split(/[/\\]/).pop();
|
|
491
|
+
return segment && segment.length > 0 ? segment : void 0;
|
|
492
|
+
}
|
|
493
|
+
//#endregion
|
|
494
|
+
//#region src/acp/client-tools.ts
|
|
495
|
+
function wrapToolsForAcpClient(tools, options) {
|
|
496
|
+
if (!tools) return void 0;
|
|
497
|
+
const wrapped = { ...tools };
|
|
498
|
+
const readFileEntry = findToolEntry(tools, "read_file");
|
|
499
|
+
const writeFileEntry = findToolEntry(tools, "write_file");
|
|
500
|
+
const shellEntry = findToolEntry(tools, "shell");
|
|
501
|
+
if (options.useClientFileSystem !== false && options.capabilities.fs?.readTextFile && readFileEntry) wrapped[readFileEntry.key] = wrapReadFile(readFileEntry.tool, options);
|
|
502
|
+
if (options.useClientFileSystem !== false && options.capabilities.fs?.writeTextFile && writeFileEntry) wrapped[writeFileEntry.key] = wrapWriteFile(writeFileEntry.tool, options);
|
|
503
|
+
if (options.useClientTerminal !== false && options.capabilities.terminal && shellEntry) wrapped[shellEntry.key] = wrapShell(shellEntry.tool, options);
|
|
504
|
+
return wrapped;
|
|
505
|
+
}
|
|
506
|
+
function findToolEntry(tools, canonicalName) {
|
|
507
|
+
for (const [key, tool] of Object.entries(tools)) if (key === canonicalName || tool.spec.name === canonicalName) return {
|
|
508
|
+
key,
|
|
509
|
+
tool
|
|
510
|
+
};
|
|
511
|
+
return null;
|
|
512
|
+
}
|
|
513
|
+
function wrapReadFile(base, options) {
|
|
514
|
+
return {
|
|
515
|
+
...base,
|
|
516
|
+
async execute(input, ctx) {
|
|
517
|
+
const path = typeof input.path === "string" ? input.path : "";
|
|
518
|
+
if (!path) return "Read error: missing path.";
|
|
519
|
+
const absPath = resolveAcpPath(ctx.handle.cwd, path);
|
|
520
|
+
const line = lineOrDefault(input.offset);
|
|
521
|
+
const limit = positiveNumberOrNull(input.limit);
|
|
522
|
+
const response = await options.client.sendRequest("fs/read_text_file", {
|
|
523
|
+
sessionId: options.sessionId,
|
|
524
|
+
path: absPath,
|
|
525
|
+
line,
|
|
526
|
+
...limit !== null ? { limit } : {}
|
|
527
|
+
});
|
|
528
|
+
const content = typeof response.content === "string" ? response.content : "";
|
|
529
|
+
seedReadState(ctx, absPath, content, line ?? 1, limit ?? Number.POSITIVE_INFINITY);
|
|
530
|
+
if (input.lineNumbers === false) return content;
|
|
531
|
+
return withLineNumbers(content, line ?? 1);
|
|
532
|
+
}
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
function wrapWriteFile(base, options) {
|
|
536
|
+
return {
|
|
537
|
+
...base,
|
|
538
|
+
async execute(input, ctx) {
|
|
539
|
+
const path = typeof input.path === "string" ? input.path : "";
|
|
540
|
+
const content = typeof input.content === "string" ? input.content : "";
|
|
541
|
+
if (!path) return "Write error: missing path.";
|
|
542
|
+
const absPath = resolveAcpPath(ctx.handle.cwd, path);
|
|
543
|
+
await options.client.sendRequest("fs/write_text_file", {
|
|
544
|
+
sessionId: options.sessionId,
|
|
545
|
+
path: absPath,
|
|
546
|
+
content
|
|
547
|
+
});
|
|
548
|
+
seedReadState(ctx, absPath, content, 0, Number.POSITIVE_INFINITY);
|
|
549
|
+
ctx.reportOutcome?.("updated");
|
|
550
|
+
return `Wrote ${absPath} (${Buffer.byteLength(content)} bytes).`;
|
|
551
|
+
}
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
function wrapShell(base, options) {
|
|
555
|
+
return {
|
|
556
|
+
...base,
|
|
557
|
+
spec: withoutShellBackgroundInput(base.spec),
|
|
558
|
+
async execute(input, ctx) {
|
|
559
|
+
const command = typeof input.command === "string" ? input.command : "";
|
|
560
|
+
if (!command) return "shell error: missing command.";
|
|
561
|
+
if (input.run_in_background === true) return "shell error: background mode is not supported through ACP terminal callbacks. Run the command in the foreground.";
|
|
562
|
+
const request = {
|
|
563
|
+
sessionId: options.sessionId,
|
|
564
|
+
command,
|
|
565
|
+
cwd: ctx.handle.cwd,
|
|
566
|
+
outputByteLimit: typeof input.maxOutputBytes === "number" ? input.maxOutputBytes : null
|
|
567
|
+
};
|
|
568
|
+
const created = await options.client.sendRequest("terminal/create", request);
|
|
569
|
+
const terminalId = typeof created.terminalId === "string" ? created.terminalId : "";
|
|
570
|
+
if (!terminalId) return "shell error: ACP client did not return a terminal id.";
|
|
571
|
+
try {
|
|
572
|
+
const exit = await options.client.sendRequest("terminal/wait_for_exit", {
|
|
573
|
+
sessionId: options.sessionId,
|
|
574
|
+
terminalId
|
|
575
|
+
});
|
|
576
|
+
return formatTerminalOutput(await options.client.sendRequest("terminal/output", {
|
|
577
|
+
sessionId: options.sessionId,
|
|
578
|
+
terminalId
|
|
579
|
+
}), exit);
|
|
580
|
+
} finally {
|
|
581
|
+
await options.client.sendRequest("terminal/release", {
|
|
582
|
+
sessionId: options.sessionId,
|
|
583
|
+
terminalId
|
|
584
|
+
}).catch(() => void 0);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
function formatTerminalOutput(output, exit) {
|
|
590
|
+
const body = output.output || "(no output)";
|
|
591
|
+
const code = exit.exitCode ?? output.exitStatus?.exitCode;
|
|
592
|
+
const signal = exit.signal ?? output.exitStatus?.signal;
|
|
593
|
+
const suffix = signal ? `\n(signal ${signal})` : typeof code === "number" ? `\n(exit ${code})` : "";
|
|
594
|
+
return `${body}${output.truncated ? "\n(output truncated by ACP client)" : ""}${suffix}`;
|
|
595
|
+
}
|
|
596
|
+
function withoutShellBackgroundInput(spec) {
|
|
597
|
+
const properties = isRecord(spec.inputSchema.properties) ? spec.inputSchema.properties : void 0;
|
|
598
|
+
if (!properties || !Object.hasOwn(properties, "run_in_background")) return spec;
|
|
599
|
+
const { run_in_background: _runInBackground, ...nextProperties } = properties;
|
|
600
|
+
const required = Array.isArray(spec.inputSchema.required) ? spec.inputSchema.required.filter((name) => name !== "run_in_background") : spec.inputSchema.required;
|
|
601
|
+
return {
|
|
602
|
+
...spec,
|
|
603
|
+
description: spec.description.split("\n").filter((line) => !line.includes("run_in_background") && !line.includes("<task-notification>") && !line.includes("background task")).join("\n"),
|
|
604
|
+
inputSchema: {
|
|
605
|
+
...spec.inputSchema,
|
|
606
|
+
properties: nextProperties,
|
|
607
|
+
...required ? { required } : {}
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
function withLineNumbers(content, start) {
|
|
612
|
+
return content.split("\n").map((line, index) => `${start + index}\t${line}`).join("\n");
|
|
613
|
+
}
|
|
614
|
+
function lineOrDefault(value) {
|
|
615
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 1 ? Math.trunc(value) : 1;
|
|
616
|
+
}
|
|
617
|
+
function positiveNumberOrNull(value) {
|
|
618
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.trunc(value) : null;
|
|
619
|
+
}
|
|
620
|
+
function isRecord(value) {
|
|
621
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
622
|
+
}
|
|
623
|
+
function seedReadState(ctx, path, content, offset, limit) {
|
|
624
|
+
const readState = resolveReadStateMap(ctx);
|
|
625
|
+
if (!readState) return;
|
|
626
|
+
readState.set(readStateKey(ctx.handle.cwd, path), {
|
|
627
|
+
contentHash: hashContent(content),
|
|
628
|
+
offset,
|
|
629
|
+
limit,
|
|
630
|
+
maxBytes: Number.POSITIVE_INFINITY,
|
|
631
|
+
mtimeMs: Date.now()
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
//#endregion
|
|
635
|
+
//#region src/acp/server.ts
|
|
636
|
+
const ACP_AGENT_VERSION = "1";
|
|
637
|
+
const MODEL_CONFIG_ID = "model";
|
|
638
|
+
const DEFAULT_PERMISSION_OPTIONS = [
|
|
639
|
+
{
|
|
640
|
+
optionId: "allow_once",
|
|
641
|
+
name: "Allow once",
|
|
642
|
+
kind: "allow_once"
|
|
643
|
+
},
|
|
644
|
+
{
|
|
645
|
+
optionId: "allow_always",
|
|
646
|
+
name: "Always allow",
|
|
647
|
+
kind: "allow_always"
|
|
648
|
+
},
|
|
649
|
+
{
|
|
650
|
+
optionId: "reject_once",
|
|
651
|
+
name: "Reject once",
|
|
652
|
+
kind: "reject_once"
|
|
653
|
+
},
|
|
654
|
+
{
|
|
655
|
+
optionId: "reject_always",
|
|
656
|
+
name: "Always reject",
|
|
657
|
+
kind: "reject_always"
|
|
658
|
+
}
|
|
659
|
+
];
|
|
660
|
+
function createAcpServer(options) {
|
|
661
|
+
return new AcpServerImpl(options);
|
|
662
|
+
}
|
|
663
|
+
var AcpServerImpl = class {
|
|
664
|
+
options;
|
|
665
|
+
peer = null;
|
|
666
|
+
clientCapabilities = {};
|
|
667
|
+
sessions = /* @__PURE__ */ new Map();
|
|
668
|
+
modes;
|
|
669
|
+
defaultModeId;
|
|
670
|
+
defaultConfigOptions;
|
|
671
|
+
modelChoices;
|
|
672
|
+
defaultModelChoiceId;
|
|
673
|
+
builtProviders = /* @__PURE__ */ new Map();
|
|
674
|
+
constructor(options) {
|
|
675
|
+
this.options = options;
|
|
676
|
+
this.modes = defaultModes(options.modes);
|
|
677
|
+
this.defaultModeId = options.defaultModeId ?? this.modes[0]?.id ?? "build";
|
|
678
|
+
this.modelChoices = options.models ?? [];
|
|
679
|
+
if (this.modelChoices.length === 0 && options.provider === void 0) throw new Error("AcpServerOptions requires either `provider` or `models`.");
|
|
680
|
+
this.defaultModelChoiceId = this.resolveDefaultModelChoiceId(options.defaultModelId);
|
|
681
|
+
this.defaultConfigOptions = this.buildDefaultConfigOptions(options.configOptions ?? []);
|
|
682
|
+
}
|
|
683
|
+
resolveDefaultModelChoiceId(requested) {
|
|
684
|
+
if (this.modelChoices.length === 0) return void 0;
|
|
685
|
+
if (requested !== void 0) {
|
|
686
|
+
if (!this.modelChoices.some((choice) => choice.id === requested)) throw new Error(`defaultModelId "${requested}" is not present in the models registry.`);
|
|
687
|
+
return requested;
|
|
688
|
+
}
|
|
689
|
+
return ((this.options.model ? this.modelChoices.find((choice) => choice.id === this.options.model || choice.model === this.options.model) : void 0) ?? this.modelChoices[0]).id;
|
|
690
|
+
}
|
|
691
|
+
buildDefaultConfigOptions(userConfig) {
|
|
692
|
+
if (this.modelChoices.length === 0) return userConfig;
|
|
693
|
+
return [{
|
|
694
|
+
id: MODEL_CONFIG_ID,
|
|
695
|
+
name: "Model",
|
|
696
|
+
type: "select",
|
|
697
|
+
category: "model",
|
|
698
|
+
currentValue: this.defaultModelChoiceId,
|
|
699
|
+
options: this.modelChoices.map((choice) => ({
|
|
700
|
+
value: choice.id,
|
|
701
|
+
name: choice.name ?? choice.model
|
|
702
|
+
}))
|
|
703
|
+
}, ...userConfig.filter((option) => option.id !== MODEL_CONFIG_ID)];
|
|
704
|
+
}
|
|
705
|
+
providerForChoiceId(id) {
|
|
706
|
+
const choice = this.modelChoices.find((candidate) => candidate.id === id);
|
|
707
|
+
if (!choice) throw new AcpProtocolError(-32602, `Unknown model: ${id}`);
|
|
708
|
+
let provider = this.builtProviders.get(id);
|
|
709
|
+
if (!provider) {
|
|
710
|
+
provider = typeof choice.provider === "function" ? choice.provider() : choice.provider;
|
|
711
|
+
this.builtProviders.set(id, provider);
|
|
712
|
+
}
|
|
713
|
+
return provider;
|
|
714
|
+
}
|
|
715
|
+
setPeer(peer) {
|
|
716
|
+
this.peer = peer;
|
|
717
|
+
}
|
|
718
|
+
async handleMessage(message) {
|
|
719
|
+
if (!isObject(message) || typeof message.method !== "string") return errorResponse(normalizeId("id" in message ? message.id : null), -32600, "Invalid JSON-RPC request.");
|
|
720
|
+
if (!("id" in message)) {
|
|
721
|
+
await this.handleNotification(message.method, message.params);
|
|
722
|
+
return null;
|
|
723
|
+
}
|
|
724
|
+
const request = message;
|
|
725
|
+
try {
|
|
726
|
+
const result = await this.handleRequest(request.method, request.params);
|
|
727
|
+
return successResponse(request.id, result);
|
|
728
|
+
} catch (err) {
|
|
729
|
+
const rpcError = toRpcError(err);
|
|
730
|
+
return errorResponse(request.id, rpcError.code, rpcError.message, rpcError.data);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
async handleRequest(method, params) {
|
|
734
|
+
switch (method) {
|
|
735
|
+
case "initialize": return this.initialize(asObject(params));
|
|
736
|
+
case "authenticate": return this.authenticate(asObject(params));
|
|
737
|
+
case "logout": return this.logout();
|
|
738
|
+
case "session/new": return this.newSession(asObject(params));
|
|
739
|
+
case "session/load": return this.loadExistingSession(asObject(params));
|
|
740
|
+
case "session/resume": return this.resumeSession(asObject(params));
|
|
741
|
+
case "session/list": return this.listSessions(asObject(params));
|
|
742
|
+
case "session/delete": return this.deleteSession(asObject(params));
|
|
743
|
+
case "session/close": return this.closeSession(asObject(params));
|
|
744
|
+
case "session/prompt": return this.prompt(asObject(params));
|
|
745
|
+
case "session/set_mode": return this.setMode(asObject(params));
|
|
746
|
+
case "session/set_config_option": return this.setConfigOption(asObject(params));
|
|
747
|
+
default: throw new AcpProtocolError(-32601, `Unknown ACP method: ${method}`);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
async notify(method, params) {
|
|
751
|
+
await this.peer?.sendNotification(method, params);
|
|
752
|
+
}
|
|
753
|
+
async close() {
|
|
754
|
+
const sessions = [...this.sessions.values()];
|
|
755
|
+
this.sessions.clear();
|
|
756
|
+
await Promise.allSettled(sessions.map(async (runtime) => {
|
|
757
|
+
runtime.abortController?.abort(/* @__PURE__ */ new Error("ACP server closing."));
|
|
758
|
+
await runtime.agent.destroy();
|
|
759
|
+
}));
|
|
760
|
+
}
|
|
761
|
+
async handleNotification(method, params) {
|
|
762
|
+
switch (method) {
|
|
763
|
+
case "session/cancel":
|
|
764
|
+
this.cancel(asObject(params));
|
|
765
|
+
break;
|
|
766
|
+
default: break;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
initialize(params) {
|
|
770
|
+
this.clientCapabilities = params.clientCapabilities ?? {};
|
|
771
|
+
const provider = this.resolveProvider();
|
|
772
|
+
return {
|
|
773
|
+
protocolVersion: params.protocolVersion === 1 ? params.protocolVersion : 1,
|
|
774
|
+
agentCapabilities: {
|
|
775
|
+
loadSession: true,
|
|
776
|
+
promptCapabilities: promptCapabilitiesForProvider(provider.meta.capabilities),
|
|
777
|
+
mcpCapabilities: mcpCapabilities(),
|
|
778
|
+
sessionCapabilities: {
|
|
779
|
+
list: {},
|
|
780
|
+
delete: {},
|
|
781
|
+
resume: {},
|
|
782
|
+
close: {},
|
|
783
|
+
additionalDirectories: {}
|
|
784
|
+
},
|
|
785
|
+
auth: { ...this.options.logout ? { logout: {} } : {} }
|
|
786
|
+
},
|
|
787
|
+
agentInfo: this.options.agentInfo ?? {
|
|
788
|
+
name: "zidane",
|
|
789
|
+
title: "Zidane ACP",
|
|
790
|
+
version: ACP_AGENT_VERSION
|
|
791
|
+
},
|
|
792
|
+
authMethods: this.options.authMethods ?? []
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
async authenticate(params) {
|
|
796
|
+
if (!this.options.authenticate) return {};
|
|
797
|
+
return await this.options.authenticate(params);
|
|
798
|
+
}
|
|
799
|
+
async logout() {
|
|
800
|
+
if (!this.options.logout) return {};
|
|
801
|
+
return await this.options.logout();
|
|
802
|
+
}
|
|
803
|
+
async newSession(params) {
|
|
804
|
+
const runtime = await this.createRuntime(params);
|
|
805
|
+
this.deferAvailableCommands(runtime.sessionId);
|
|
806
|
+
return newSessionResponse(runtime.sessionId, this.modes, runtime.modeId, runtime.configOptions);
|
|
807
|
+
}
|
|
808
|
+
async loadExistingSession(params) {
|
|
809
|
+
const runtime = await this.createRuntime(params, params.sessionId, true);
|
|
810
|
+
await this.replayHistory(runtime);
|
|
811
|
+
this.deferAvailableCommands(runtime.sessionId);
|
|
812
|
+
return this.sessionStateResponse(runtime);
|
|
813
|
+
}
|
|
814
|
+
async resumeSession(params) {
|
|
815
|
+
const runtime = await this.createRuntime({
|
|
816
|
+
cwd: params.cwd,
|
|
817
|
+
additionalDirectories: params.additionalDirectories,
|
|
818
|
+
mcpServers: params.mcpServers ?? []
|
|
819
|
+
}, params.sessionId, true);
|
|
820
|
+
this.deferAvailableCommands(runtime.sessionId);
|
|
821
|
+
return this.sessionStateResponse(runtime);
|
|
822
|
+
}
|
|
823
|
+
sessionStateResponse(runtime) {
|
|
824
|
+
return {
|
|
825
|
+
modes: makeModeState(this.modes, runtime.modeId),
|
|
826
|
+
...runtime.configOptions.length > 0 ? { configOptions: runtime.configOptions } : {}
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
deferAvailableCommands(sessionId) {
|
|
830
|
+
if (!this.options.commands || this.options.commands.length === 0) return;
|
|
831
|
+
setTimeout(() => {
|
|
832
|
+
this.sessionUpdate(sessionId, {
|
|
833
|
+
sessionUpdate: "available_commands_update",
|
|
834
|
+
availableCommands: this.options.commands
|
|
835
|
+
});
|
|
836
|
+
}, 0);
|
|
837
|
+
}
|
|
838
|
+
async replayHistory(runtime) {
|
|
839
|
+
for (const turn of runtime.session.turns) {
|
|
840
|
+
const blocks = sessionBlocksToAcp(turn.content);
|
|
841
|
+
if (blocks.length === 0) continue;
|
|
842
|
+
const sessionUpdate = turn.role === "assistant" ? "agent_message_chunk" : "user_message_chunk";
|
|
843
|
+
for (const content of blocks) await this.sessionUpdate(runtime.sessionId, {
|
|
844
|
+
sessionUpdate,
|
|
845
|
+
content
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
async listSessions(params) {
|
|
850
|
+
const store = this.options.store;
|
|
851
|
+
if (!store) return {
|
|
852
|
+
sessions: [...this.sessions.values()].filter((runtime) => !params.cwd || runtime.cwd === params.cwd).map((runtime) => sessionInfo(runtime)),
|
|
853
|
+
nextCursor: null
|
|
854
|
+
};
|
|
855
|
+
const ids = await store.list({
|
|
856
|
+
limit: 100,
|
|
857
|
+
...params.cwd ? { projectRoot: params.cwd } : {}
|
|
858
|
+
});
|
|
859
|
+
const sessions = [];
|
|
860
|
+
for (const id of ids) {
|
|
861
|
+
const loaded = await store.load(id);
|
|
862
|
+
if (!loaded) continue;
|
|
863
|
+
const cwd = loaded.projectRoot ?? params.cwd ?? this.sessions.get(loaded.id)?.cwd;
|
|
864
|
+
if (!cwd) continue;
|
|
865
|
+
sessions.push({
|
|
866
|
+
sessionId: loaded.id,
|
|
867
|
+
cwd,
|
|
868
|
+
title: typeof loaded.metadata.title === "string" ? loaded.metadata.title : null,
|
|
869
|
+
updatedAt: new Date(loaded.updatedAt).toISOString()
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
return {
|
|
873
|
+
sessions,
|
|
874
|
+
nextCursor: null
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
async deleteSession(params) {
|
|
878
|
+
const runtime = this.sessions.get(params.sessionId);
|
|
879
|
+
if (runtime) {
|
|
880
|
+
runtime.abortController?.abort(/* @__PURE__ */ new Error("ACP session deleted."));
|
|
881
|
+
await runtime.agent.destroy();
|
|
882
|
+
this.sessions.delete(params.sessionId);
|
|
883
|
+
}
|
|
884
|
+
await this.options.store?.delete(params.sessionId);
|
|
885
|
+
return {};
|
|
886
|
+
}
|
|
887
|
+
async closeSession(params) {
|
|
888
|
+
const runtime = this.requireSession(params.sessionId);
|
|
889
|
+
runtime.abortController?.abort(/* @__PURE__ */ new Error("ACP session closed."));
|
|
890
|
+
await runtime.agent.destroy();
|
|
891
|
+
this.sessions.delete(params.sessionId);
|
|
892
|
+
return {};
|
|
893
|
+
}
|
|
894
|
+
async prompt(params) {
|
|
895
|
+
const runtime = this.requireSession(params.sessionId);
|
|
896
|
+
if (runtime.inFlight) throw new AcpProtocolError(-32600, `Session ${params.sessionId} already has an in-flight prompt.`);
|
|
897
|
+
const mapped = acpPromptToPromptParts(params.prompt);
|
|
898
|
+
const abortController = new AbortController();
|
|
899
|
+
runtime.abortController = abortController;
|
|
900
|
+
const run = async () => {
|
|
901
|
+
let stats;
|
|
902
|
+
let aborted = false;
|
|
903
|
+
try {
|
|
904
|
+
for (const warning of mapped.warnings) await this.sessionUpdate(runtime.sessionId, {
|
|
905
|
+
sessionUpdate: "agent_thought_chunk",
|
|
906
|
+
content: {
|
|
907
|
+
type: "text",
|
|
908
|
+
text: warning
|
|
909
|
+
}
|
|
910
|
+
});
|
|
911
|
+
stats = await runtime.agent.run({
|
|
912
|
+
prompt: mapped.prompt,
|
|
913
|
+
...runtime.runModel ? { model: runtime.runModel } : {},
|
|
914
|
+
...this.options.system ? { system: this.options.system } : {},
|
|
915
|
+
signal: abortController.signal
|
|
916
|
+
});
|
|
917
|
+
} catch (err) {
|
|
918
|
+
aborted = abortController.signal.aborted;
|
|
919
|
+
if (!aborted) throw err;
|
|
920
|
+
} finally {
|
|
921
|
+
runtime.abortController = void 0;
|
|
922
|
+
runtime.inFlight = void 0;
|
|
923
|
+
}
|
|
924
|
+
return { stopReason: stopReasonFromRun({
|
|
925
|
+
aborted: aborted || abortController.signal.aborted,
|
|
926
|
+
turnUsage: stats?.turnUsage,
|
|
927
|
+
maxTurnsReached: stats ? reachedMaxTurns(stats, this.options.behavior) : false
|
|
928
|
+
}) };
|
|
929
|
+
};
|
|
930
|
+
const inFlight = run();
|
|
931
|
+
runtime.inFlight = inFlight;
|
|
932
|
+
return await inFlight;
|
|
933
|
+
}
|
|
934
|
+
cancel(params) {
|
|
935
|
+
const runtime = this.sessions.get(params.sessionId);
|
|
936
|
+
runtime?.abortController?.abort(/* @__PURE__ */ new Error("Cancelled by ACP client."));
|
|
937
|
+
runtime?.agent.abort();
|
|
938
|
+
}
|
|
939
|
+
async setMode(params) {
|
|
940
|
+
const runtime = this.requireSession(params.sessionId);
|
|
941
|
+
if (!this.modes.some((mode) => mode.id === params.modeId)) throw new AcpProtocolError(-32602, `Unknown mode: ${params.modeId}`);
|
|
942
|
+
runtime.modeId = params.modeId;
|
|
943
|
+
await this.sessionUpdate(params.sessionId, {
|
|
944
|
+
sessionUpdate: "current_mode_update",
|
|
945
|
+
currentModeId: params.modeId
|
|
946
|
+
});
|
|
947
|
+
return {};
|
|
948
|
+
}
|
|
949
|
+
async setConfigOption(params) {
|
|
950
|
+
const runtime = this.requireSession(params.sessionId);
|
|
951
|
+
const isModelSwitch = this.modelChoices.length > 0 && params.configId === MODEL_CONFIG_ID;
|
|
952
|
+
if (isModelSwitch && runtime.inFlight) throw new AcpProtocolError(-32600, "Cannot switch model while a prompt is in flight.");
|
|
953
|
+
const configOptions = this.applyConfigSelection(runtime, params.configId, params.value);
|
|
954
|
+
if (isModelSwitch) this.selectModel(runtime, params.value);
|
|
955
|
+
runtime.config.set(params.configId, params.value);
|
|
956
|
+
await this.sessionUpdate(params.sessionId, {
|
|
957
|
+
sessionUpdate: "config_option_update",
|
|
958
|
+
configOptions
|
|
959
|
+
});
|
|
960
|
+
return { configOptions };
|
|
961
|
+
}
|
|
962
|
+
/**
|
|
963
|
+
* Rebind a live session to the provider + model of the selected registry
|
|
964
|
+
* choice. The agent is NOT rebuilt — only the router's delegate and the run
|
|
965
|
+
* model change — so execution handles and background tasks survive a switch.
|
|
966
|
+
*/
|
|
967
|
+
selectModel(runtime, choiceId) {
|
|
968
|
+
const choice = this.modelChoices.find((candidate) => candidate.id === choiceId);
|
|
969
|
+
if (!choice) throw new AcpProtocolError(-32602, `Unknown model: ${choiceId}`);
|
|
970
|
+
runtime.setProviderDelegate?.(this.providerForChoiceId(choice.id));
|
|
971
|
+
runtime.runModel = choice.model;
|
|
972
|
+
}
|
|
973
|
+
/** Reflect a `select` choice back into the advertised option set's `currentValue`. */
|
|
974
|
+
applyConfigSelection(runtime, configId, value) {
|
|
975
|
+
const option = runtime.configOptions.find((candidate) => candidate.id === configId);
|
|
976
|
+
if (!option) throw new AcpProtocolError(-32602, `Unknown config option: ${configId}`);
|
|
977
|
+
if (!configOptionHasValue(option, value)) throw new AcpProtocolError(-32602, `Invalid value for config option ${configId}: ${value}`);
|
|
978
|
+
runtime.configOptions = runtime.configOptions.map((candidate) => candidate.id === configId ? {
|
|
979
|
+
...candidate,
|
|
980
|
+
currentValue: value
|
|
981
|
+
} : candidate);
|
|
982
|
+
return runtime.configOptions;
|
|
983
|
+
}
|
|
984
|
+
async createRuntime(params, requestedSessionId, mustExist = false) {
|
|
985
|
+
if (requestedSessionId) {
|
|
986
|
+
const existing = this.sessions.get(requestedSessionId);
|
|
987
|
+
if (existing) {
|
|
988
|
+
if (existing.cwd !== params.cwd) throw new AcpProtocolError(-32602, `Session ${requestedSessionId} belongs to cwd ${existing.cwd}, not ${params.cwd}.`);
|
|
989
|
+
const nextAdditionalDirectories = params.additionalDirectories ?? existing.additionalDirectories;
|
|
990
|
+
const nextMcpServers = params.mcpServers === void 0 ? existing.mcpServers : acpMcpServersToZidane(params.mcpServers);
|
|
991
|
+
if (!sameStringArray(existing.additionalDirectories, nextAdditionalDirectories)) throw new AcpProtocolError(-32602, `Session ${requestedSessionId} is already active with different additionalDirectories; close it before resuming with changed roots.`);
|
|
992
|
+
if (!sameJson(existing.mcpServers, nextMcpServers)) throw new AcpProtocolError(-32602, `Session ${requestedSessionId} is already active with different MCP servers; close it before resuming with changed MCP configuration.`);
|
|
993
|
+
return existing;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
const mcpServers = acpMcpServersToZidane(params.mcpServers);
|
|
997
|
+
const session = await this.resolveSession(requestedSessionId, params.cwd, mustExist);
|
|
998
|
+
const execution = this.resolveExecution(params);
|
|
999
|
+
const initialChoiceId = this.defaultModelChoiceId;
|
|
1000
|
+
const router = initialChoiceId !== void 0 ? createProviderRouter(this.providerForChoiceId(initialChoiceId)) : void 0;
|
|
1001
|
+
const provider = router ? router.provider : this.resolveProvider();
|
|
1002
|
+
const runModel = initialChoiceId !== void 0 ? this.modelChoices.find((choice) => choice.id === initialChoiceId).model : this.options.model;
|
|
1003
|
+
const sessionId = session.id;
|
|
1004
|
+
const base = this.options.preset ?? basic_default;
|
|
1005
|
+
const agentOptions = this.options.agentOptions ?? {};
|
|
1006
|
+
const wrappedTools = wrapToolsForAcpClient(mergeTools(base.tools, agentOptions.tools, this.options.tools), {
|
|
1007
|
+
client: this.requirePeer(),
|
|
1008
|
+
sessionId,
|
|
1009
|
+
capabilities: this.clientCapabilities,
|
|
1010
|
+
useClientFileSystem: this.options.useClientFileSystem,
|
|
1011
|
+
useClientTerminal: this.options.useClientTerminal
|
|
1012
|
+
});
|
|
1013
|
+
const behavior = {
|
|
1014
|
+
...base.behavior,
|
|
1015
|
+
...agentOptions.behavior,
|
|
1016
|
+
...this.options.behavior
|
|
1017
|
+
};
|
|
1018
|
+
const agent = createAgent({
|
|
1019
|
+
...base,
|
|
1020
|
+
...agentOptions,
|
|
1021
|
+
provider,
|
|
1022
|
+
execution,
|
|
1023
|
+
session,
|
|
1024
|
+
tools: wrappedTools,
|
|
1025
|
+
behavior,
|
|
1026
|
+
mcpServers: mergeMcpServers(base.mcpServers, agentOptions.mcpServers, this.options.agentOptions?.mcpServers, mcpServers),
|
|
1027
|
+
...this.options.system ? { system: this.options.system } : {}
|
|
1028
|
+
});
|
|
1029
|
+
const runtime = {
|
|
1030
|
+
sessionId,
|
|
1031
|
+
cwd: params.cwd,
|
|
1032
|
+
additionalDirectories: params.additionalDirectories ?? [],
|
|
1033
|
+
mcpServers,
|
|
1034
|
+
session,
|
|
1035
|
+
execution,
|
|
1036
|
+
agent,
|
|
1037
|
+
provider,
|
|
1038
|
+
modeId: this.defaultModeId,
|
|
1039
|
+
configOptions: cloneConfigOptions(this.defaultConfigOptions),
|
|
1040
|
+
config: /* @__PURE__ */ new Map(),
|
|
1041
|
+
...runModel !== void 0 ? { runModel } : {},
|
|
1042
|
+
...router ? { setProviderDelegate: router.setDelegate } : {}
|
|
1043
|
+
};
|
|
1044
|
+
this.installHookAdapter(runtime);
|
|
1045
|
+
this.sessions.set(sessionId, runtime);
|
|
1046
|
+
session.setMeta("acp.cwd", params.cwd);
|
|
1047
|
+
session.setMeta("acp.additionalDirectories", params.additionalDirectories ?? []);
|
|
1048
|
+
if (session.store) await session.save().catch(() => void 0);
|
|
1049
|
+
return runtime;
|
|
1050
|
+
}
|
|
1051
|
+
async resolveSession(sessionId, cwd, mustExist) {
|
|
1052
|
+
const store = this.options.store;
|
|
1053
|
+
if (sessionId && this.sessions.has(sessionId)) return this.sessions.get(sessionId).session;
|
|
1054
|
+
if (sessionId && store) {
|
|
1055
|
+
const existing = await loadSession(store, sessionId);
|
|
1056
|
+
if (existing) return existing;
|
|
1057
|
+
if (mustExist) throw new AcpProtocolError(-32002, `Unknown session: ${sessionId}`);
|
|
1058
|
+
const created = await createSession({
|
|
1059
|
+
id: sessionId,
|
|
1060
|
+
store,
|
|
1061
|
+
projectRoot: cwd,
|
|
1062
|
+
metadata: { title: "ACP session" }
|
|
1063
|
+
});
|
|
1064
|
+
await created.save();
|
|
1065
|
+
return created;
|
|
1066
|
+
}
|
|
1067
|
+
if (sessionId && mustExist) throw new AcpProtocolError(-32002, `Unknown session: ${sessionId}`);
|
|
1068
|
+
const created = await createSession({
|
|
1069
|
+
...sessionId ? { id: sessionId } : {},
|
|
1070
|
+
...store ? { store } : {},
|
|
1071
|
+
projectRoot: cwd,
|
|
1072
|
+
metadata: { title: "ACP session" }
|
|
1073
|
+
});
|
|
1074
|
+
if (store) await created.save();
|
|
1075
|
+
return created;
|
|
1076
|
+
}
|
|
1077
|
+
resolveExecution(params) {
|
|
1078
|
+
if (typeof this.options.execution === "function") return this.options.execution(params);
|
|
1079
|
+
return this.options.execution ?? createProcessContext({ cwd: params.cwd });
|
|
1080
|
+
}
|
|
1081
|
+
resolveProvider() {
|
|
1082
|
+
if (this.defaultModelChoiceId !== void 0) return this.providerForChoiceId(this.defaultModelChoiceId);
|
|
1083
|
+
const provider = this.options.provider;
|
|
1084
|
+
if (provider === void 0) throw new AcpProtocolError(-32603, "No provider configured.");
|
|
1085
|
+
return typeof provider === "function" ? provider() : provider;
|
|
1086
|
+
}
|
|
1087
|
+
installHookAdapter(runtime) {
|
|
1088
|
+
const seenTools = /* @__PURE__ */ new Set();
|
|
1089
|
+
runtime.agent.hooks.hook("stream:text", ({ delta, turnId }) => {
|
|
1090
|
+
this.sessionUpdate(runtime.sessionId, {
|
|
1091
|
+
sessionUpdate: "agent_message_chunk",
|
|
1092
|
+
messageId: turnId,
|
|
1093
|
+
content: {
|
|
1094
|
+
type: "text",
|
|
1095
|
+
text: delta
|
|
1096
|
+
}
|
|
1097
|
+
});
|
|
1098
|
+
});
|
|
1099
|
+
runtime.agent.hooks.hook("stream:thinking", ({ delta, turnId }) => {
|
|
1100
|
+
this.sessionUpdate(runtime.sessionId, {
|
|
1101
|
+
sessionUpdate: "agent_thought_chunk",
|
|
1102
|
+
messageId: turnId,
|
|
1103
|
+
content: {
|
|
1104
|
+
type: "text",
|
|
1105
|
+
text: delta
|
|
1106
|
+
}
|
|
1107
|
+
});
|
|
1108
|
+
});
|
|
1109
|
+
runtime.agent.hooks.hook("tool:gate", async (ctx) => {
|
|
1110
|
+
if (!this.shouldRequestPermission(ctx.name, ctx.input)) return;
|
|
1111
|
+
const key = permissionKey(ctx.name, ctx.input);
|
|
1112
|
+
const cached = runtime.config.get(key);
|
|
1113
|
+
if (cached === "allow") return;
|
|
1114
|
+
if (cached === "reject") {
|
|
1115
|
+
ctx.block = true;
|
|
1116
|
+
ctx.reason = "Rejected by prior ACP permission decision.";
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
1119
|
+
const outcome = await this.requestPermission(runtime, {
|
|
1120
|
+
toolCallId: ctx.callId,
|
|
1121
|
+
title: ctx.name,
|
|
1122
|
+
kind: toolKindForName(ctx.name),
|
|
1123
|
+
status: "pending",
|
|
1124
|
+
rawInput: ctx.input
|
|
1125
|
+
});
|
|
1126
|
+
if (outcome.outcome === "cancelled") {
|
|
1127
|
+
ctx.block = true;
|
|
1128
|
+
ctx.reason = "Cancelled by ACP client.";
|
|
1129
|
+
return;
|
|
1130
|
+
}
|
|
1131
|
+
if (outcome.optionId === "allow_always") runtime.config.set(key, "allow");
|
|
1132
|
+
if (outcome.optionId === "reject_always") runtime.config.set(key, "reject");
|
|
1133
|
+
if (outcome.optionId.startsWith("reject")) {
|
|
1134
|
+
ctx.block = true;
|
|
1135
|
+
ctx.reason = "Rejected by ACP client.";
|
|
1136
|
+
}
|
|
1137
|
+
});
|
|
1138
|
+
runtime.agent.hooks.hook("tool:dispatched", (ctx) => {
|
|
1139
|
+
seenTools.add(ctx.callId);
|
|
1140
|
+
const status = ctx.outcome === "gate-block" || ctx.outcome === "invalid-input" || ctx.outcome === "unknown" ? "failed" : "pending";
|
|
1141
|
+
this.sessionUpdate(runtime.sessionId, {
|
|
1142
|
+
sessionUpdate: "tool_call",
|
|
1143
|
+
toolCallId: ctx.callId,
|
|
1144
|
+
title: ctx.name,
|
|
1145
|
+
kind: toolKindForName(ctx.name),
|
|
1146
|
+
status,
|
|
1147
|
+
rawInput: ctx.input,
|
|
1148
|
+
...status === "failed" ? { rawOutput: ctx.reason ?? ctx.outcome } : {}
|
|
1149
|
+
});
|
|
1150
|
+
});
|
|
1151
|
+
runtime.agent.hooks.hook("tool:before", (ctx) => {
|
|
1152
|
+
if (!seenTools.has(ctx.callId)) {
|
|
1153
|
+
this.sessionUpdate(runtime.sessionId, {
|
|
1154
|
+
sessionUpdate: "tool_call",
|
|
1155
|
+
toolCallId: ctx.callId,
|
|
1156
|
+
title: ctx.name,
|
|
1157
|
+
kind: toolKindForName(ctx.name),
|
|
1158
|
+
status: "in_progress",
|
|
1159
|
+
rawInput: ctx.input
|
|
1160
|
+
});
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
this.sessionUpdate(runtime.sessionId, {
|
|
1164
|
+
sessionUpdate: "tool_call_update",
|
|
1165
|
+
toolCallId: ctx.callId,
|
|
1166
|
+
status: "in_progress"
|
|
1167
|
+
});
|
|
1168
|
+
});
|
|
1169
|
+
runtime.agent.hooks.hook("tool:after", (ctx) => {
|
|
1170
|
+
this.sessionUpdate(runtime.sessionId, {
|
|
1171
|
+
sessionUpdate: "tool_call_update",
|
|
1172
|
+
toolCallId: ctx.callId,
|
|
1173
|
+
status: "completed",
|
|
1174
|
+
content: toolResultToAcpContent(ctx.result),
|
|
1175
|
+
rawOutput: ctx.result
|
|
1176
|
+
});
|
|
1177
|
+
});
|
|
1178
|
+
runtime.agent.hooks.hook("tool:error", (ctx) => {
|
|
1179
|
+
this.sessionUpdate(runtime.sessionId, {
|
|
1180
|
+
sessionUpdate: "tool_call_update",
|
|
1181
|
+
toolCallId: ctx.callId,
|
|
1182
|
+
status: "failed",
|
|
1183
|
+
rawOutput: errorMessage(ctx.error)
|
|
1184
|
+
});
|
|
1185
|
+
});
|
|
1186
|
+
runtime.agent.hooks.hook("tool:cancelled", (ctx) => {
|
|
1187
|
+
this.sessionUpdate(runtime.sessionId, {
|
|
1188
|
+
sessionUpdate: "tool_call_update",
|
|
1189
|
+
toolCallId: ctx.callId,
|
|
1190
|
+
status: "failed",
|
|
1191
|
+
rawOutput: ctx.reason ?? "cancelled"
|
|
1192
|
+
});
|
|
1193
|
+
});
|
|
1194
|
+
runtime.agent.hooks.hook("turn:after", ({ usage, cumulativeUsage }) => {
|
|
1195
|
+
const size = this.resolveContextWindow(runtime);
|
|
1196
|
+
if (size === void 0) return;
|
|
1197
|
+
const used = effectiveInputFromTurn(usage);
|
|
1198
|
+
if (used <= 0) return;
|
|
1199
|
+
const cost = usageCost(cumulativeUsage.cost);
|
|
1200
|
+
this.sessionUpdate(runtime.sessionId, {
|
|
1201
|
+
sessionUpdate: "usage_update",
|
|
1202
|
+
used,
|
|
1203
|
+
size,
|
|
1204
|
+
...cost ? { cost } : {}
|
|
1205
|
+
});
|
|
1206
|
+
});
|
|
1207
|
+
}
|
|
1208
|
+
resolveContextWindow(runtime) {
|
|
1209
|
+
if (typeof this.options.contextWindow === "number" && this.options.contextWindow > 0) return this.options.contextWindow;
|
|
1210
|
+
const fromBehavior = this.options.behavior?.autoCompact;
|
|
1211
|
+
if (fromBehavior && typeof fromBehavior === "object" && typeof fromBehavior.contextWindow === "number" && fromBehavior.contextWindow > 0) return fromBehavior.contextWindow;
|
|
1212
|
+
const meta = runtime.provider.meta;
|
|
1213
|
+
if (typeof meta.contextWindow === "number" && meta.contextWindow > 0) return meta.contextWindow;
|
|
1214
|
+
}
|
|
1215
|
+
shouldRequestPermission(name, input) {
|
|
1216
|
+
const policy = this.options.permission;
|
|
1217
|
+
if (policy?.enabled !== true || !this.peer) return false;
|
|
1218
|
+
if (policy.toolMatcher) return policy.toolMatcher(name, input);
|
|
1219
|
+
return name === "shell" || name === "write_file" || name === "edit" || name === "multi_edit";
|
|
1220
|
+
}
|
|
1221
|
+
async requestPermission(runtime, toolCall) {
|
|
1222
|
+
const request = this.requirePeer().sendRequest("session/request_permission", {
|
|
1223
|
+
sessionId: runtime.sessionId,
|
|
1224
|
+
toolCall,
|
|
1225
|
+
options: this.options.permission?.options ?? DEFAULT_PERMISSION_OPTIONS
|
|
1226
|
+
});
|
|
1227
|
+
const aborted = new Promise((resolve) => {
|
|
1228
|
+
const signal = runtime.abortController?.signal;
|
|
1229
|
+
if (!signal) return;
|
|
1230
|
+
if (signal.aborted) {
|
|
1231
|
+
resolve({ outcome: { outcome: "cancelled" } });
|
|
1232
|
+
return;
|
|
1233
|
+
}
|
|
1234
|
+
signal.addEventListener("abort", () => resolve({ outcome: { outcome: "cancelled" } }), { once: true });
|
|
1235
|
+
});
|
|
1236
|
+
return (await Promise.race([request, aborted])).outcome ?? { outcome: "cancelled" };
|
|
1237
|
+
}
|
|
1238
|
+
async sessionUpdate(sessionId, update) {
|
|
1239
|
+
await this.notify("session/update", {
|
|
1240
|
+
sessionId,
|
|
1241
|
+
update
|
|
1242
|
+
});
|
|
1243
|
+
}
|
|
1244
|
+
requireSession(sessionId) {
|
|
1245
|
+
const runtime = this.sessions.get(sessionId);
|
|
1246
|
+
if (!runtime) throw new AcpProtocolError(-32002, `Unknown session: ${sessionId}`);
|
|
1247
|
+
return runtime;
|
|
1248
|
+
}
|
|
1249
|
+
requirePeer() {
|
|
1250
|
+
if (!this.peer) return {
|
|
1251
|
+
sendNotification: () => void 0,
|
|
1252
|
+
sendRequest: () => Promise.reject(new AcpProtocolError(-32601, "No ACP client peer is attached."))
|
|
1253
|
+
};
|
|
1254
|
+
return this.peer;
|
|
1255
|
+
}
|
|
1256
|
+
};
|
|
1257
|
+
var AcpProtocolError = class extends Error {
|
|
1258
|
+
code;
|
|
1259
|
+
data;
|
|
1260
|
+
constructor(code, message, data) {
|
|
1261
|
+
super(message);
|
|
1262
|
+
this.code = code;
|
|
1263
|
+
this.data = data;
|
|
1264
|
+
this.name = "AcpProtocolError";
|
|
1265
|
+
}
|
|
1266
|
+
};
|
|
1267
|
+
/**
|
|
1268
|
+
* A provider that forwards every call to a swappable delegate. Lets a session
|
|
1269
|
+
* change its underlying provider (and thus credentials) on a model switch
|
|
1270
|
+
* without rebuilding the agent — the loop reads `formatTools` / message builders
|
|
1271
|
+
* / `meta` / `stream` fresh each run, and switches only happen between prompts.
|
|
1272
|
+
*/
|
|
1273
|
+
function createProviderRouter(initial) {
|
|
1274
|
+
let delegate = initial;
|
|
1275
|
+
return {
|
|
1276
|
+
provider: {
|
|
1277
|
+
get name() {
|
|
1278
|
+
return delegate.name;
|
|
1279
|
+
},
|
|
1280
|
+
get meta() {
|
|
1281
|
+
return delegate.meta;
|
|
1282
|
+
},
|
|
1283
|
+
formatTools: (tools) => delegate.formatTools(tools),
|
|
1284
|
+
userMessage: (content) => delegate.userMessage(content),
|
|
1285
|
+
assistantMessage: (content) => delegate.assistantMessage(content),
|
|
1286
|
+
toolResultsMessage: (results) => delegate.toolResultsMessage(results),
|
|
1287
|
+
stream: (options, callbacks) => delegate.stream(options, callbacks),
|
|
1288
|
+
promptMessage: (parts) => routePromptMessage(delegate, parts),
|
|
1289
|
+
classifyError: (err) => delegate.classifyError ? delegate.classifyError(err) : null,
|
|
1290
|
+
countTokens: (payload, signal) => delegate.countTokens ? delegate.countTokens(payload, signal) : Promise.resolve(null)
|
|
1291
|
+
},
|
|
1292
|
+
setDelegate: (next) => {
|
|
1293
|
+
delegate = next;
|
|
1294
|
+
}
|
|
1295
|
+
};
|
|
1296
|
+
}
|
|
1297
|
+
function routePromptMessage(delegate, parts) {
|
|
1298
|
+
if (delegate.promptMessage) return delegate.promptMessage(parts);
|
|
1299
|
+
return defaultPromptMessage(parts);
|
|
1300
|
+
}
|
|
1301
|
+
function mergeTools(...sources) {
|
|
1302
|
+
const out = {};
|
|
1303
|
+
for (const source of sources) if (source) Object.assign(out, source);
|
|
1304
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
1305
|
+
}
|
|
1306
|
+
function mergeMcpServers(...sources) {
|
|
1307
|
+
const byName = /* @__PURE__ */ new Map();
|
|
1308
|
+
for (const source of sources) for (const server of source ?? []) byName.set(server.name, server);
|
|
1309
|
+
return [...byName.values()];
|
|
1310
|
+
}
|
|
1311
|
+
function cloneConfigOptions(options) {
|
|
1312
|
+
return options.map((option) => ({
|
|
1313
|
+
...option,
|
|
1314
|
+
options: option.options.map((item) => "group" in item ? {
|
|
1315
|
+
...item,
|
|
1316
|
+
options: item.options.map((child) => ({ ...child }))
|
|
1317
|
+
} : { ...item })
|
|
1318
|
+
}));
|
|
1319
|
+
}
|
|
1320
|
+
function configOptionHasValue(option, value) {
|
|
1321
|
+
for (const item of option.options) if ("group" in item) {
|
|
1322
|
+
if (item.options.some((child) => child.value === value)) return true;
|
|
1323
|
+
} else if (item.value === value) return true;
|
|
1324
|
+
return false;
|
|
1325
|
+
}
|
|
1326
|
+
function sameStringArray(left, right) {
|
|
1327
|
+
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
1328
|
+
}
|
|
1329
|
+
function sameJson(left, right) {
|
|
1330
|
+
return stableStringify(left) === stableStringify(right);
|
|
1331
|
+
}
|
|
1332
|
+
function asObject(value) {
|
|
1333
|
+
if (!isObject(value)) throw new AcpProtocolError(-32602, "Expected object parameters.");
|
|
1334
|
+
return value;
|
|
1335
|
+
}
|
|
1336
|
+
function isObject(value) {
|
|
1337
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1338
|
+
}
|
|
1339
|
+
function normalizeId(id) {
|
|
1340
|
+
return typeof id === "string" || typeof id === "number" || id === null ? id : null;
|
|
1341
|
+
}
|
|
1342
|
+
function toRpcError(err) {
|
|
1343
|
+
if (err instanceof AcpProtocolError) return {
|
|
1344
|
+
code: err.code,
|
|
1345
|
+
message: err.message,
|
|
1346
|
+
...err.data !== void 0 ? { data: err.data } : {}
|
|
1347
|
+
};
|
|
1348
|
+
return {
|
|
1349
|
+
code: -32603,
|
|
1350
|
+
message: err instanceof Error ? err.message : String(err)
|
|
1351
|
+
};
|
|
1352
|
+
}
|
|
1353
|
+
function sessionInfo(runtime) {
|
|
1354
|
+
return {
|
|
1355
|
+
sessionId: runtime.sessionId,
|
|
1356
|
+
cwd: runtime.cwd,
|
|
1357
|
+
additionalDirectories: runtime.additionalDirectories,
|
|
1358
|
+
title: typeof runtime.session.metadata.title === "string" ? runtime.session.metadata.title : null,
|
|
1359
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1360
|
+
};
|
|
1361
|
+
}
|
|
1362
|
+
function permissionKey(name, input) {
|
|
1363
|
+
return `permission:${name}:${stableStringify(input)}`;
|
|
1364
|
+
}
|
|
1365
|
+
function stableStringify(value) {
|
|
1366
|
+
if (!isObject(value)) return JSON.stringify(value);
|
|
1367
|
+
const keys = Object.keys(value).sort();
|
|
1368
|
+
const out = {};
|
|
1369
|
+
for (const key of keys) out[key] = value[key];
|
|
1370
|
+
return JSON.stringify(out);
|
|
1371
|
+
}
|
|
1372
|
+
function reachedMaxTurns(stats, behavior) {
|
|
1373
|
+
const maxTurns = behavior?.maxTurns;
|
|
1374
|
+
return typeof maxTurns === "number" && stats.turns >= maxTurns && lastFinishReason(stats.turnUsage) !== "length";
|
|
1375
|
+
}
|
|
1376
|
+
function lastFinishReason(turnUsage) {
|
|
1377
|
+
for (let i = (turnUsage?.length ?? 0) - 1; i >= 0; i--) {
|
|
1378
|
+
const reason = turnUsage?.[i]?.finishReason;
|
|
1379
|
+
if (reason) return reason;
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
//#endregion
|
|
1383
|
+
//#region src/acp/index.ts
|
|
1384
|
+
function runAcpStdioServer(options) {
|
|
1385
|
+
const server = createAcpServer(options);
|
|
1386
|
+
const connection = createJsonRpcConnection({
|
|
1387
|
+
input: options.input ?? process.stdin,
|
|
1388
|
+
output: options.output ?? process.stdout,
|
|
1389
|
+
framing: options.framing,
|
|
1390
|
+
onError: options.onError,
|
|
1391
|
+
onRequest: (message) => server.handleMessage(message),
|
|
1392
|
+
onNotification: async (message) => {
|
|
1393
|
+
await server.handleMessage(message);
|
|
1394
|
+
}
|
|
1395
|
+
});
|
|
1396
|
+
server.setPeer(connection);
|
|
1397
|
+
connection.start();
|
|
1398
|
+
return {
|
|
1399
|
+
server,
|
|
1400
|
+
connection,
|
|
1401
|
+
async close() {
|
|
1402
|
+
connection.close();
|
|
1403
|
+
await server.close();
|
|
1404
|
+
}
|
|
1405
|
+
};
|
|
1406
|
+
}
|
|
1407
|
+
//#endregion
|
|
1408
|
+
export { acpMcpServersToZidane as a, stopReasonFromRun as c, createJsonRpcConnection as d, wrapToolsForAcpClient as i, toolResultToAcpContent as l, AcpProtocolError as n, acpPromptToPromptParts as o, createAcpServer as r, sessionBlocksToAcp as s, runAcpStdioServer as t, JsonRpcRemoteError as u };
|
|
1409
|
+
|
|
1410
|
+
//# sourceMappingURL=acp-BqIU2mo-.js.map
|