@band-ai/sdk 0.1.2 → 0.1.4
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/{CodexAdapter-A1k_LMdZ.d.ts → ClaudeSDKAdapter-BHOkjN3X.d.ts} +222 -222
- package/dist/{CodexAdapter-BbZD-nBd.d.cts → ClaudeSDKAdapter-CRq4R-WB.d.cts} +222 -222
- package/dist/adapters.cjs +375 -275
- package/dist/adapters.d.cts +21 -21
- package/dist/adapters.d.ts +21 -21
- package/dist/adapters.js +1512 -13
- package/dist/{backends-C4n6cKOf.d.ts → backends-CzDbiQar.d.ts} +1 -1
- package/dist/{backends-BnOLsG7w.d.cts → backends-SVMT-7l4.d.cts} +1 -1
- package/dist/chunk-FYVLUGW7.js +59 -0
- package/dist/{chunk-6AJA2OPC.js → chunk-I5EZG52P.js} +11 -4
- package/dist/{chunk-NIAQBANR.js → chunk-MSL6CWHY.js} +1 -1
- package/dist/{chunk-A3TDK6GP.js → chunk-RHVH2GZR.js} +4589 -5993
- package/dist/{chunk-FUODYQRE.js → chunk-W5NDZSYE.js} +2 -59
- package/dist/converters.d.cts +1 -1
- package/dist/converters.d.ts +1 -1
- package/dist/converters.js +5 -3
- package/dist/index.cjs +7161 -7116
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +4 -6
- package/dist/mcp-claude.cjs +4 -6
- package/dist/mcp-claude.d.cts +1 -1
- package/dist/mcp-claude.d.ts +1 -1
- package/dist/mcp-claude.js +47 -5
- package/dist/mcp.d.cts +3 -3
- package/dist/mcp.d.ts +3 -3
- package/dist/mcp.js +1 -1
- package/dist/runtime.cjs +11 -4
- package/dist/runtime.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-5CXIMAH2.js +0 -52
- package/dist/chunk-LY55KYVI.js +0 -0
- package/dist/sdk-HJUVSSWA.js +0 -10
- package/dist/{history-ByessXNq.d.ts → acp-server-B1Da7L6u.d.ts} +4 -4
- package/dist/{history-i6yN7neC.d.cts → acp-server-BdRz9_UN.d.cts} +4 -4
- /package/dist/{claude-CcnQ5OUC.d.ts → sdk-CcnQ5OUC.d.ts} +0 -0
- /package/dist/{claude-CwTAxhwI.d.cts → sdk-CwTAxhwI.d.cts} +0 -0
package/dist/adapters.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import {
|
|
2
|
-
ACPClientAdapter,
|
|
3
|
-
ACPServer,
|
|
4
2
|
AnthropicAdapter,
|
|
5
3
|
AnthropicToolCallingModel,
|
|
6
4
|
CODEX_REASONING_EFFORTS,
|
|
@@ -22,35 +20,1536 @@ import {
|
|
|
22
20
|
OpenAIAdapter,
|
|
23
21
|
OpenAIToolCallingModel,
|
|
24
22
|
OpencodeAdapter,
|
|
25
|
-
ThenvoiACPServerAdapter,
|
|
26
23
|
ToolCallingAdapter,
|
|
27
24
|
VercelAISDKAdapter,
|
|
28
25
|
VercelAISDKToolCallingModel,
|
|
29
26
|
runSingleToolRound
|
|
30
|
-
} from "./chunk-
|
|
31
|
-
import
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
} from "./chunk-RHVH2GZR.js";
|
|
28
|
+
import {
|
|
29
|
+
ThenvoiMcpServer,
|
|
30
|
+
ThenvoiMcpSseServer
|
|
31
|
+
} from "./chunk-MSL6CWHY.js";
|
|
34
32
|
import "./chunk-EDPKUUJL.js";
|
|
33
|
+
import {
|
|
34
|
+
ACPClientHistoryConverter,
|
|
35
|
+
ACPServerHistoryConverter
|
|
36
|
+
} from "./chunk-FYVLUGW7.js";
|
|
35
37
|
import {
|
|
36
38
|
A2AAdapter,
|
|
37
39
|
A2AGatewayAdapter,
|
|
38
40
|
A2AHistoryConverter,
|
|
39
41
|
GatewayHistoryConverter,
|
|
40
42
|
GatewayServer,
|
|
43
|
+
LazyAsyncValue,
|
|
41
44
|
ParlantAdapter,
|
|
42
45
|
ParlantHistoryConverter,
|
|
43
46
|
buildA2AAuthHeaders,
|
|
44
|
-
createGatewayServer
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
createGatewayServer,
|
|
48
|
+
parseToolCall,
|
|
49
|
+
parseToolResult
|
|
50
|
+
} from "./chunk-W5NDZSYE.js";
|
|
51
|
+
import {
|
|
52
|
+
SimpleAdapter
|
|
53
|
+
} from "./chunk-AVVDPXP4.js";
|
|
54
|
+
import {
|
|
55
|
+
ensureHandlePrefix
|
|
56
|
+
} from "./chunk-INA6YZTZ.js";
|
|
48
57
|
import "./chunk-GVEQ3RAH.js";
|
|
49
|
-
import
|
|
58
|
+
import {
|
|
59
|
+
renderSystemPrompt
|
|
60
|
+
} from "./chunk-SFVKOEQH.js";
|
|
50
61
|
import "./chunk-ZZJRRBPQ.js";
|
|
51
62
|
import "./chunk-3BKAC4OZ.js";
|
|
52
63
|
import "./chunk-OD2G5LFQ.js";
|
|
53
|
-
import
|
|
64
|
+
import {
|
|
65
|
+
UnsupportedFeatureError
|
|
66
|
+
} from "./chunk-YR3BOQNW.js";
|
|
67
|
+
|
|
68
|
+
// src/adapters/acp/ACPClientAdapter.ts
|
|
69
|
+
import { spawn } from "child_process";
|
|
70
|
+
import { Readable, Writable } from "stream";
|
|
71
|
+
|
|
72
|
+
// src/adapters/acp/types.ts
|
|
73
|
+
var DEFAULT_ACP_SERVER_MODES = [
|
|
74
|
+
{
|
|
75
|
+
id: "default",
|
|
76
|
+
name: "Default",
|
|
77
|
+
description: "General-purpose chat mode"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: "code",
|
|
81
|
+
name: "Code",
|
|
82
|
+
description: "Route prompts toward coding peers when available"
|
|
83
|
+
}
|
|
84
|
+
];
|
|
85
|
+
function createPendingPrompt(sessionId) {
|
|
86
|
+
let markDone = () => void 0;
|
|
87
|
+
const done = new Promise((resolve) => {
|
|
88
|
+
markDone = resolve;
|
|
89
|
+
});
|
|
90
|
+
return {
|
|
91
|
+
sessionId,
|
|
92
|
+
done,
|
|
93
|
+
markDone,
|
|
94
|
+
terminalMessageSeen: false,
|
|
95
|
+
completionTimer: null
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function choosePermissionOption(options) {
|
|
99
|
+
if (options.length === 0) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
return options.find((option) => option.kind === "allow_once") ?? options.find((option) => option.kind === "allow_always") ?? options[0];
|
|
103
|
+
}
|
|
104
|
+
function asJsonSafe(value) {
|
|
105
|
+
if (value === null || value === void 0 || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
106
|
+
return value;
|
|
107
|
+
}
|
|
108
|
+
if (Array.isArray(value)) {
|
|
109
|
+
return value.map((item) => asJsonSafe(item));
|
|
110
|
+
}
|
|
111
|
+
if (typeof value === "object") {
|
|
112
|
+
if ("model_dump" in value && typeof value.model_dump === "function") {
|
|
113
|
+
return asJsonSafe(value.model_dump());
|
|
114
|
+
}
|
|
115
|
+
if ("toJSON" in value && typeof value.toJSON === "function") {
|
|
116
|
+
return asJsonSafe(value.toJSON());
|
|
117
|
+
}
|
|
118
|
+
return Object.fromEntries(
|
|
119
|
+
Object.entries(value).map(([key, item]) => [key, asJsonSafe(item)])
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
return String(value);
|
|
123
|
+
}
|
|
124
|
+
function normalizeMcpServers(mcpServers) {
|
|
125
|
+
if (!mcpServers) {
|
|
126
|
+
return [];
|
|
127
|
+
}
|
|
128
|
+
return mcpServers.map((server) => asJsonSafe(server)).filter((server) => !!server && typeof server === "object" && !Array.isArray(server));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// src/adapters/acp/client.ts
|
|
132
|
+
var ThenvoiACPClient = class {
|
|
133
|
+
sessionChunks = /* @__PURE__ */ new Map();
|
|
134
|
+
permissionHandlers = /* @__PURE__ */ new Map();
|
|
135
|
+
async sessionUpdate(params) {
|
|
136
|
+
const chunk = toCollectedChunk(params.update);
|
|
137
|
+
if (!chunk) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const existing = this.sessionChunks.get(params.sessionId) ?? [];
|
|
141
|
+
existing.push(chunk);
|
|
142
|
+
this.sessionChunks.set(params.sessionId, existing);
|
|
143
|
+
}
|
|
144
|
+
async requestPermission(params) {
|
|
145
|
+
const handler = this.permissionHandlers.get(params.sessionId);
|
|
146
|
+
if (handler) {
|
|
147
|
+
return handler(params);
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
outcome: {
|
|
151
|
+
outcome: "cancelled"
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
setPermissionHandler(sessionId, handler) {
|
|
156
|
+
if (!handler) {
|
|
157
|
+
this.permissionHandlers.delete(sessionId);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
this.permissionHandlers.set(sessionId, handler);
|
|
161
|
+
}
|
|
162
|
+
resetSession(sessionId) {
|
|
163
|
+
this.sessionChunks.delete(sessionId);
|
|
164
|
+
this.permissionHandlers.delete(sessionId);
|
|
165
|
+
}
|
|
166
|
+
getCollectedText(sessionId) {
|
|
167
|
+
return this.getCollectedChunks(sessionId).filter((chunk) => chunk.chunkType === "text").map((chunk) => chunk.content).join("");
|
|
168
|
+
}
|
|
169
|
+
getCollectedChunks(sessionId) {
|
|
170
|
+
if (sessionId) {
|
|
171
|
+
return [...this.sessionChunks.get(sessionId) ?? []];
|
|
172
|
+
}
|
|
173
|
+
return [...this.sessionChunks.values()].flatMap((chunks) => chunks);
|
|
174
|
+
}
|
|
175
|
+
async extMethod(method, params) {
|
|
176
|
+
if (method === "cursor/ask_question") {
|
|
177
|
+
const options = Array.isArray(params.options) ? params.options : [];
|
|
178
|
+
const selected = choosePermissionOption(
|
|
179
|
+
options.filter((option) => !!option && typeof option === "object")
|
|
180
|
+
);
|
|
181
|
+
if (!selected) {
|
|
182
|
+
return {
|
|
183
|
+
outcome: {
|
|
184
|
+
type: "cancelled"
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
outcome: {
|
|
190
|
+
type: "selected",
|
|
191
|
+
optionId: selected.optionId
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
if (method === "cursor/create_plan") {
|
|
196
|
+
return {
|
|
197
|
+
outcome: {
|
|
198
|
+
type: "approved"
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
return {};
|
|
203
|
+
}
|
|
204
|
+
async extNotification(method, params) {
|
|
205
|
+
const sessionId = toOptionalString(params.sessionId) ?? toOptionalString(params.session_id);
|
|
206
|
+
if (!sessionId) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (method === "cursor/update_todos") {
|
|
210
|
+
const todos = Array.isArray(params.todos) ? params.todos : [];
|
|
211
|
+
const lines = todos.filter((todo) => !!todo && typeof todo === "object").map((todo) => `- [${todo.completed === true ? "x" : " "}] ${String(todo.content ?? "")}`).filter((line) => line.trim().length > 0);
|
|
212
|
+
if (lines.length > 0) {
|
|
213
|
+
this.appendChunk(sessionId, {
|
|
214
|
+
chunkType: "plan",
|
|
215
|
+
content: lines.join("\n"),
|
|
216
|
+
metadata: {}
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
if (method === "cursor/task") {
|
|
222
|
+
const result = toOptionalString(params.result);
|
|
223
|
+
if (result) {
|
|
224
|
+
this.appendChunk(sessionId, {
|
|
225
|
+
chunkType: "text",
|
|
226
|
+
content: `[Task completed] ${result}`,
|
|
227
|
+
metadata: {}
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
appendChunk(sessionId, chunk) {
|
|
233
|
+
const existing = this.sessionChunks.get(sessionId) ?? [];
|
|
234
|
+
existing.push(chunk);
|
|
235
|
+
this.sessionChunks.set(sessionId, existing);
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
function toCollectedChunk(update) {
|
|
239
|
+
switch (update.sessionUpdate) {
|
|
240
|
+
case "agent_message_chunk":
|
|
241
|
+
return {
|
|
242
|
+
chunkType: "text",
|
|
243
|
+
content: extractTextFromContent(update.content),
|
|
244
|
+
metadata: {}
|
|
245
|
+
};
|
|
246
|
+
case "agent_thought_chunk":
|
|
247
|
+
return {
|
|
248
|
+
chunkType: "thought",
|
|
249
|
+
content: extractTextFromContent(update.content),
|
|
250
|
+
metadata: {}
|
|
251
|
+
};
|
|
252
|
+
case "tool_call":
|
|
253
|
+
return {
|
|
254
|
+
chunkType: "tool_call",
|
|
255
|
+
content: update.title,
|
|
256
|
+
metadata: {
|
|
257
|
+
tool_call_id: update.toolCallId,
|
|
258
|
+
raw_input: update.rawInput,
|
|
259
|
+
status: update.status ?? "pending"
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
case "tool_call_update":
|
|
263
|
+
return {
|
|
264
|
+
chunkType: "tool_result",
|
|
265
|
+
content: extractToolOutput(update),
|
|
266
|
+
metadata: {
|
|
267
|
+
tool_call_id: update.toolCallId,
|
|
268
|
+
status: update.status ?? "completed"
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
case "plan":
|
|
272
|
+
return {
|
|
273
|
+
chunkType: "plan",
|
|
274
|
+
content: update.entries.map((entry) => entry.content).join("\n"),
|
|
275
|
+
metadata: {}
|
|
276
|
+
};
|
|
277
|
+
default:
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
function extractToolOutput(update) {
|
|
282
|
+
if (typeof update.rawOutput === "string") {
|
|
283
|
+
return update.rawOutput;
|
|
284
|
+
}
|
|
285
|
+
if (update.rawOutput !== void 0 && update.rawOutput !== null) {
|
|
286
|
+
try {
|
|
287
|
+
return JSON.stringify(update.rawOutput);
|
|
288
|
+
} catch {
|
|
289
|
+
return String(update.rawOutput);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
const parts = (update.content ?? []).map(extractTextFromToolContent).filter((text) => text.length > 0);
|
|
293
|
+
return parts.join("\n");
|
|
294
|
+
}
|
|
295
|
+
function extractTextFromToolContent(content) {
|
|
296
|
+
if (content.type === "content") {
|
|
297
|
+
return extractTextFromContent(content.content);
|
|
298
|
+
}
|
|
299
|
+
if (content.type === "diff") {
|
|
300
|
+
return content.newText;
|
|
301
|
+
}
|
|
302
|
+
if (content.type === "terminal") {
|
|
303
|
+
return `[Terminal: ${content.terminalId}]`;
|
|
304
|
+
}
|
|
305
|
+
return "";
|
|
306
|
+
}
|
|
307
|
+
function extractTextFromContent(content) {
|
|
308
|
+
switch (content.type) {
|
|
309
|
+
case "text":
|
|
310
|
+
return content.text;
|
|
311
|
+
case "resource_link":
|
|
312
|
+
return `[Resource: ${content.title ?? content.name ?? content.uri}]`;
|
|
313
|
+
case "resource": {
|
|
314
|
+
const resource = content.resource;
|
|
315
|
+
if ("text" in resource && typeof resource.text === "string") {
|
|
316
|
+
return resource.text;
|
|
317
|
+
}
|
|
318
|
+
return `[Resource: ${resource.uri ?? "embedded"}]`;
|
|
319
|
+
}
|
|
320
|
+
case "image":
|
|
321
|
+
return `[Image: ${content.mimeType ?? "image"}]`;
|
|
322
|
+
case "audio":
|
|
323
|
+
return `[Audio: ${content.mimeType ?? "audio"}]`;
|
|
324
|
+
default:
|
|
325
|
+
return "";
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
function toOptionalString(value) {
|
|
329
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// src/adapters/acp/loader.ts
|
|
333
|
+
var acpModule = new LazyAsyncValue({
|
|
334
|
+
load: async () => {
|
|
335
|
+
return await import("@agentclientprotocol/sdk").catch((error) => {
|
|
336
|
+
throw new UnsupportedFeatureError(
|
|
337
|
+
`ACP adapters require optional dependency "@agentclientprotocol/sdk". Install it with "pnpm add @agentclientprotocol/sdk". (${error instanceof Error ? error.message : String(error)})`
|
|
338
|
+
);
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
// src/adapters/acp/ACPClientAdapter.ts
|
|
344
|
+
var ACPClientAdapter = class extends SimpleAdapter {
|
|
345
|
+
command;
|
|
346
|
+
cwd;
|
|
347
|
+
env;
|
|
348
|
+
mcpServers;
|
|
349
|
+
authMethod;
|
|
350
|
+
enableMemoryTools;
|
|
351
|
+
enableMcpTools;
|
|
352
|
+
additionalMcpTools;
|
|
353
|
+
clientCapabilities;
|
|
354
|
+
connectionFactory;
|
|
355
|
+
roomToSession = /* @__PURE__ */ new Map();
|
|
356
|
+
roomTools = /* @__PURE__ */ new Map();
|
|
357
|
+
activeSessions = /* @__PURE__ */ new Set();
|
|
358
|
+
bootstrappedSessions = /* @__PURE__ */ new Set();
|
|
359
|
+
backend = null;
|
|
360
|
+
client = null;
|
|
361
|
+
connectionHandle = null;
|
|
362
|
+
connection = null;
|
|
363
|
+
connectionState = null;
|
|
364
|
+
started = false;
|
|
365
|
+
systemPrompt = "";
|
|
366
|
+
spawnPromise = null;
|
|
367
|
+
constructor(options) {
|
|
368
|
+
super({
|
|
369
|
+
historyConverter: new ACPClientHistoryConverter()
|
|
370
|
+
});
|
|
371
|
+
this.command = Array.isArray(options.command) ? [...options.command] : [options.command];
|
|
372
|
+
if (this.command.length === 0 || this.command[0].length === 0) {
|
|
373
|
+
throw new Error("ACPClientAdapter requires a command");
|
|
374
|
+
}
|
|
375
|
+
this.cwd = options.cwd ?? process.cwd();
|
|
376
|
+
this.env = options.env;
|
|
377
|
+
this.mcpServers = [...options.mcpServers ?? []];
|
|
378
|
+
this.authMethod = options.authMethod;
|
|
379
|
+
this.enableMemoryTools = options.enableMemoryTools ?? false;
|
|
380
|
+
this.enableMcpTools = options.enableMcpTools ?? true;
|
|
381
|
+
this.additionalMcpTools = [...options.additionalMcpTools ?? []];
|
|
382
|
+
this.clientCapabilities = options.clientCapabilities;
|
|
383
|
+
this.connectionFactory = options.connectionFactory ?? createSubprocessConnection;
|
|
384
|
+
}
|
|
385
|
+
async onStarted(agentName, agentDescription) {
|
|
386
|
+
await super.onStarted(agentName, agentDescription);
|
|
387
|
+
this.started = true;
|
|
388
|
+
this.systemPrompt = renderSystemPrompt({
|
|
389
|
+
agentName,
|
|
390
|
+
agentDescription,
|
|
391
|
+
includeBaseInstructions: false
|
|
392
|
+
});
|
|
393
|
+
await this.ensureConnection();
|
|
394
|
+
}
|
|
395
|
+
async onMessage(message, tools, history, _participantsMessage, _contactsMessage, context) {
|
|
396
|
+
if (context.isSessionBootstrap) {
|
|
397
|
+
this.rehydrate(history);
|
|
398
|
+
}
|
|
399
|
+
this.roomTools.set(context.roomId, tools);
|
|
400
|
+
const connection = await this.ensureConnection();
|
|
401
|
+
const client = this.client;
|
|
402
|
+
if (!client) {
|
|
403
|
+
throw new Error("ACP client was not initialized");
|
|
404
|
+
}
|
|
405
|
+
const sessionId = await this.getOrCreateSession(context.roomId, connection);
|
|
406
|
+
client.resetSession(sessionId);
|
|
407
|
+
client.setPermissionHandler(
|
|
408
|
+
sessionId,
|
|
409
|
+
(params) => this.handlePermissionRequest(tools, context.roomId, params)
|
|
410
|
+
);
|
|
411
|
+
const promptText = this.bootstrappedSessions.has(sessionId) ? message.content : `${this.buildSystemContext(context.roomId, message)}
|
|
412
|
+
|
|
413
|
+
${message.content}`;
|
|
414
|
+
this.bootstrappedSessions.add(sessionId);
|
|
415
|
+
try {
|
|
416
|
+
await connection.prompt({
|
|
417
|
+
sessionId,
|
|
418
|
+
prompt: [{
|
|
419
|
+
type: "text",
|
|
420
|
+
text: promptText
|
|
421
|
+
}]
|
|
422
|
+
});
|
|
423
|
+
} catch (error) {
|
|
424
|
+
await this.stop();
|
|
425
|
+
await tools.sendEvent(`ACP agent error: ${toErrorMessage(error)}`, "error", {
|
|
426
|
+
acp_error: toErrorMessage(error)
|
|
427
|
+
});
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
await this.flushChunks({
|
|
431
|
+
tools,
|
|
432
|
+
sessionId,
|
|
433
|
+
senderId: message.senderId,
|
|
434
|
+
senderHandle: message.senderName ?? message.senderType
|
|
435
|
+
});
|
|
436
|
+
await tools.sendEvent("ACP client session", "task", {
|
|
437
|
+
acp_client_session_id: sessionId,
|
|
438
|
+
acp_client_room_id: context.roomId
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
async onCleanup(roomId) {
|
|
442
|
+
const sessionId = this.roomToSession.get(roomId);
|
|
443
|
+
this.roomToSession.delete(roomId);
|
|
444
|
+
this.roomTools.delete(roomId);
|
|
445
|
+
if (sessionId) {
|
|
446
|
+
this.activeSessions.delete(sessionId);
|
|
447
|
+
this.bootstrappedSessions.delete(sessionId);
|
|
448
|
+
this.client?.setPermissionHandler(sessionId, void 0);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
async onRuntimeStop() {
|
|
452
|
+
await this.stop();
|
|
453
|
+
}
|
|
454
|
+
async stop() {
|
|
455
|
+
this.spawnPromise = null;
|
|
456
|
+
this.connectionState = null;
|
|
457
|
+
this.activeSessions.clear();
|
|
458
|
+
this.bootstrappedSessions.clear();
|
|
459
|
+
this.roomToSession.clear();
|
|
460
|
+
this.roomTools.clear();
|
|
461
|
+
this.client = null;
|
|
462
|
+
this.connection = null;
|
|
463
|
+
if (this.backend) {
|
|
464
|
+
const backend = this.backend;
|
|
465
|
+
this.backend = null;
|
|
466
|
+
await backend.stop();
|
|
467
|
+
}
|
|
468
|
+
if (this.connectionHandle) {
|
|
469
|
+
const handle = this.connectionHandle;
|
|
470
|
+
this.connectionHandle = null;
|
|
471
|
+
await handle.stop();
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
rehydrate(history) {
|
|
475
|
+
for (const [roomId, sessionId] of Object.entries(history.roomToSession)) {
|
|
476
|
+
if (!this.roomToSession.has(roomId)) {
|
|
477
|
+
this.roomToSession.set(roomId, sessionId);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
async ensureConnection() {
|
|
482
|
+
if (this.connection && !this.connection.signal.aborted) {
|
|
483
|
+
return this.connection;
|
|
484
|
+
}
|
|
485
|
+
if (!this.started) {
|
|
486
|
+
throw new Error("ACPClientAdapter was not started");
|
|
487
|
+
}
|
|
488
|
+
const isCreator = !this.spawnPromise;
|
|
489
|
+
if (isCreator) {
|
|
490
|
+
this.spawnPromise = this.spawnConnection();
|
|
491
|
+
}
|
|
492
|
+
try {
|
|
493
|
+
return await this.spawnPromise;
|
|
494
|
+
} finally {
|
|
495
|
+
if (isCreator) this.spawnPromise = null;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
async spawnConnection() {
|
|
499
|
+
const acp = await acpModule.get();
|
|
500
|
+
const client = new ThenvoiACPClient();
|
|
501
|
+
const handle = await this.connectionFactory(client, {
|
|
502
|
+
command: this.command,
|
|
503
|
+
cwd: this.cwd,
|
|
504
|
+
env: this.env
|
|
505
|
+
});
|
|
506
|
+
const connection = handle.connection;
|
|
507
|
+
const initializeResult = await connection.initialize({
|
|
508
|
+
protocolVersion: acp.PROTOCOL_VERSION,
|
|
509
|
+
clientCapabilities: this.clientCapabilities ?? {}
|
|
510
|
+
});
|
|
511
|
+
if (this.authMethod) {
|
|
512
|
+
await connection.authenticate({
|
|
513
|
+
methodId: this.authMethod
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
this.client = client;
|
|
517
|
+
this.connection = connection;
|
|
518
|
+
this.connectionHandle = handle;
|
|
519
|
+
this.connectionState = initializeResult;
|
|
520
|
+
void connection.closed.finally(() => {
|
|
521
|
+
if (this.connection === connection) {
|
|
522
|
+
this.connection = null;
|
|
523
|
+
this.connectionHandle = null;
|
|
524
|
+
this.connectionState = null;
|
|
525
|
+
this.activeSessions.clear();
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
return connection;
|
|
529
|
+
}
|
|
530
|
+
async getOrCreateSession(roomId, connection) {
|
|
531
|
+
const existingSessionId = this.roomToSession.get(roomId);
|
|
532
|
+
const mcpServers = await this.buildSessionMcpServers();
|
|
533
|
+
if (existingSessionId) {
|
|
534
|
+
if (this.activeSessions.has(existingSessionId)) {
|
|
535
|
+
return existingSessionId;
|
|
536
|
+
}
|
|
537
|
+
const restored = await this.tryRestoreSession(connection, existingSessionId, mcpServers);
|
|
538
|
+
if (restored) {
|
|
539
|
+
this.activeSessions.add(existingSessionId);
|
|
540
|
+
this.bootstrappedSessions.add(existingSessionId);
|
|
541
|
+
return existingSessionId;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
const created = await connection.newSession({
|
|
545
|
+
cwd: this.cwd,
|
|
546
|
+
mcpServers
|
|
547
|
+
});
|
|
548
|
+
this.roomToSession.set(roomId, created.sessionId);
|
|
549
|
+
this.activeSessions.add(created.sessionId);
|
|
550
|
+
return created.sessionId;
|
|
551
|
+
}
|
|
552
|
+
async tryRestoreSession(connection, sessionId, mcpServers) {
|
|
553
|
+
try {
|
|
554
|
+
if (this.connectionState?.agentCapabilities?.loadSession) {
|
|
555
|
+
await connection.loadSession({
|
|
556
|
+
cwd: this.cwd,
|
|
557
|
+
mcpServers,
|
|
558
|
+
sessionId
|
|
559
|
+
});
|
|
560
|
+
return true;
|
|
561
|
+
}
|
|
562
|
+
if (this.connectionState?.agentCapabilities?.sessionCapabilities?.resume) {
|
|
563
|
+
await connection.unstable_resumeSession({
|
|
564
|
+
cwd: this.cwd,
|
|
565
|
+
mcpServers,
|
|
566
|
+
sessionId
|
|
567
|
+
});
|
|
568
|
+
return true;
|
|
569
|
+
}
|
|
570
|
+
} catch {
|
|
571
|
+
return false;
|
|
572
|
+
}
|
|
573
|
+
return false;
|
|
574
|
+
}
|
|
575
|
+
async buildSessionMcpServers() {
|
|
576
|
+
const mcpServers = [...this.mcpServers];
|
|
577
|
+
if (!this.enableMcpTools) {
|
|
578
|
+
return mcpServers;
|
|
579
|
+
}
|
|
580
|
+
const backend = await this.getOrCreateBackend();
|
|
581
|
+
if (backend.kind === "http") {
|
|
582
|
+
const url = backend.server.url;
|
|
583
|
+
if (!url) {
|
|
584
|
+
throw new Error("Thenvoi MCP HTTP backend did not expose a URL");
|
|
585
|
+
}
|
|
586
|
+
mcpServers.push({
|
|
587
|
+
type: "http",
|
|
588
|
+
name: "thenvoi",
|
|
589
|
+
url,
|
|
590
|
+
headers: []
|
|
591
|
+
});
|
|
592
|
+
return mcpServers;
|
|
593
|
+
}
|
|
594
|
+
if (backend.kind === "sse") {
|
|
595
|
+
const url = backend.server.sseUrl;
|
|
596
|
+
if (!url) {
|
|
597
|
+
throw new Error("Thenvoi MCP SSE backend did not expose a URL");
|
|
598
|
+
}
|
|
599
|
+
mcpServers.push({
|
|
600
|
+
type: "sse",
|
|
601
|
+
name: "thenvoi",
|
|
602
|
+
url,
|
|
603
|
+
headers: []
|
|
604
|
+
});
|
|
605
|
+
return mcpServers;
|
|
606
|
+
}
|
|
607
|
+
return mcpServers;
|
|
608
|
+
}
|
|
609
|
+
async getOrCreateBackend() {
|
|
610
|
+
if (this.backend) {
|
|
611
|
+
return this.backend;
|
|
612
|
+
}
|
|
613
|
+
const transport = this.connectionState?.agentCapabilities?.mcpCapabilities?.http ? "http" : this.connectionState?.agentCapabilities?.mcpCapabilities?.sse ? "sse" : "http";
|
|
614
|
+
if (transport === "sse") {
|
|
615
|
+
const server2 = new ThenvoiMcpSseServer({
|
|
616
|
+
tools: (roomId) => this.roomTools.get(roomId),
|
|
617
|
+
enableMemoryTools: this.enableMemoryTools,
|
|
618
|
+
enableContactTools: true,
|
|
619
|
+
additionalTools: this.additionalMcpTools
|
|
620
|
+
});
|
|
621
|
+
await server2.start();
|
|
622
|
+
this.backend = {
|
|
623
|
+
kind: "sse",
|
|
624
|
+
server: server2,
|
|
625
|
+
stop: async () => {
|
|
626
|
+
await server2.stop();
|
|
627
|
+
}
|
|
628
|
+
};
|
|
629
|
+
return this.backend;
|
|
630
|
+
}
|
|
631
|
+
const server = new ThenvoiMcpServer({
|
|
632
|
+
tools: (roomId) => this.roomTools.get(roomId),
|
|
633
|
+
enableMemoryTools: this.enableMemoryTools,
|
|
634
|
+
enableContactTools: true,
|
|
635
|
+
additionalTools: this.additionalMcpTools
|
|
636
|
+
});
|
|
637
|
+
await server.start();
|
|
638
|
+
this.backend = {
|
|
639
|
+
kind: "http",
|
|
640
|
+
server,
|
|
641
|
+
stop: async () => {
|
|
642
|
+
await server.stop();
|
|
643
|
+
}
|
|
644
|
+
};
|
|
645
|
+
return this.backend;
|
|
646
|
+
}
|
|
647
|
+
buildSystemContext(roomId, message) {
|
|
648
|
+
const requesterName = message.senderName ?? message.senderId;
|
|
649
|
+
const requesterId = message.senderId;
|
|
650
|
+
return [
|
|
651
|
+
"[System Context]",
|
|
652
|
+
this.systemPrompt,
|
|
653
|
+
"",
|
|
654
|
+
"## Room Context",
|
|
655
|
+
"You are connected to Thenvoi using Thenvoi MCP tools.",
|
|
656
|
+
"Use the Thenvoi tools for any visible room action. Plain text output is not posted back to the room.",
|
|
657
|
+
"",
|
|
658
|
+
`Current room_id: ${roomId}`,
|
|
659
|
+
`Current requester name: ${requesterName}`,
|
|
660
|
+
`Current requester id: ${requesterId}`,
|
|
661
|
+
"",
|
|
662
|
+
"All Thenvoi MCP tool calls must include room_id."
|
|
663
|
+
].join("\n");
|
|
664
|
+
}
|
|
665
|
+
async handlePermissionRequest(tools, roomId, params) {
|
|
666
|
+
const selected = choosePermissionOption(params.options);
|
|
667
|
+
const toolName = params.toolCall.title ?? "unknown";
|
|
668
|
+
await tools.sendEvent(`Permission requested: ${toolName}`, "tool_call", {
|
|
669
|
+
permission_request: true,
|
|
670
|
+
tool_name: toolName,
|
|
671
|
+
tool_call_id: params.toolCall.toolCallId,
|
|
672
|
+
acp_session_id: params.sessionId,
|
|
673
|
+
auto_allowed: selected !== null
|
|
674
|
+
});
|
|
675
|
+
if (!selected) {
|
|
676
|
+
return {
|
|
677
|
+
outcome: {
|
|
678
|
+
outcome: "cancelled"
|
|
679
|
+
}
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
return {
|
|
683
|
+
outcome: {
|
|
684
|
+
outcome: "selected",
|
|
685
|
+
optionId: selected.optionId
|
|
686
|
+
}
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
async flushChunks(input) {
|
|
690
|
+
const client = this.client;
|
|
691
|
+
if (!client) {
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
for (const chunk of client.getCollectedChunks(input.sessionId)) {
|
|
695
|
+
if (chunk.chunkType === "text") {
|
|
696
|
+
if (chunk.content.length > 0) {
|
|
697
|
+
await input.tools.sendMessage(chunk.content, [{
|
|
698
|
+
id: input.senderId,
|
|
699
|
+
handle: input.senderHandle
|
|
700
|
+
}]);
|
|
701
|
+
}
|
|
702
|
+
continue;
|
|
703
|
+
}
|
|
704
|
+
const messageType = chunk.chunkType === "plan" ? "task" : chunk.chunkType;
|
|
705
|
+
await input.tools.sendEvent(
|
|
706
|
+
chunk.content,
|
|
707
|
+
messageType,
|
|
708
|
+
chunk.metadata
|
|
709
|
+
);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
};
|
|
713
|
+
async function createSubprocessConnection(client, options) {
|
|
714
|
+
const acp = await acpModule.get();
|
|
715
|
+
const child = spawn(options.command[0], options.command.slice(1), {
|
|
716
|
+
cwd: options.cwd,
|
|
717
|
+
env: {
|
|
718
|
+
...process.env,
|
|
719
|
+
...options.env
|
|
720
|
+
},
|
|
721
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
722
|
+
});
|
|
723
|
+
if (!child.stdin || !child.stdout) {
|
|
724
|
+
throw new Error("ACP subprocess did not expose stdio pipes");
|
|
725
|
+
}
|
|
726
|
+
const stream = acp.ndJsonStream(
|
|
727
|
+
Writable.toWeb(child.stdin),
|
|
728
|
+
Readable.toWeb(child.stdout)
|
|
729
|
+
);
|
|
730
|
+
const connection = new acp.ClientSideConnection(() => client, stream);
|
|
731
|
+
return {
|
|
732
|
+
connection,
|
|
733
|
+
stop: async () => {
|
|
734
|
+
await new Promise((resolve) => {
|
|
735
|
+
if (child.exitCode !== null || child.signalCode !== null) {
|
|
736
|
+
resolve();
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
let settled = false;
|
|
740
|
+
const finish = () => {
|
|
741
|
+
if (settled) {
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
settled = true;
|
|
745
|
+
child.off("exit", finish);
|
|
746
|
+
child.off("close", finish);
|
|
747
|
+
resolve();
|
|
748
|
+
};
|
|
749
|
+
child.once("exit", finish);
|
|
750
|
+
child.once("close", finish);
|
|
751
|
+
if (!child.killed) {
|
|
752
|
+
child.kill();
|
|
753
|
+
}
|
|
754
|
+
if (child.exitCode !== null || child.signalCode !== null) {
|
|
755
|
+
finish();
|
|
756
|
+
}
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
function toErrorMessage(error) {
|
|
762
|
+
if (error instanceof Error) {
|
|
763
|
+
return error.message;
|
|
764
|
+
}
|
|
765
|
+
return String(error);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// src/adapters/acp/ACPServer.ts
|
|
769
|
+
import { Readable as Readable2, Writable as Writable2 } from "stream";
|
|
770
|
+
|
|
771
|
+
// src/adapters/acp/ThenvoiACPServerAdapter.ts
|
|
772
|
+
import { randomUUID } from "crypto";
|
|
773
|
+
|
|
774
|
+
// src/adapters/acp/eventConverter.ts
|
|
775
|
+
var EventConverter = class {
|
|
776
|
+
static convert(message) {
|
|
777
|
+
switch (message.messageType) {
|
|
778
|
+
case "text":
|
|
779
|
+
return {
|
|
780
|
+
sessionUpdate: "agent_message_chunk",
|
|
781
|
+
content: {
|
|
782
|
+
type: "text",
|
|
783
|
+
text: message.content
|
|
784
|
+
}
|
|
785
|
+
};
|
|
786
|
+
case "thought":
|
|
787
|
+
return {
|
|
788
|
+
sessionUpdate: "agent_thought_chunk",
|
|
789
|
+
content: {
|
|
790
|
+
type: "text",
|
|
791
|
+
text: message.content
|
|
792
|
+
}
|
|
793
|
+
};
|
|
794
|
+
case "tool_call":
|
|
795
|
+
return this.convertToolCall(message.content);
|
|
796
|
+
case "tool_result":
|
|
797
|
+
return this.convertToolResult(message.content);
|
|
798
|
+
case "error":
|
|
799
|
+
return {
|
|
800
|
+
sessionUpdate: "agent_message_chunk",
|
|
801
|
+
content: {
|
|
802
|
+
type: "text",
|
|
803
|
+
text: `[Error] ${message.content}`
|
|
804
|
+
}
|
|
805
|
+
};
|
|
806
|
+
case "task":
|
|
807
|
+
return {
|
|
808
|
+
sessionUpdate: "plan",
|
|
809
|
+
entries: [{
|
|
810
|
+
content: message.content,
|
|
811
|
+
priority: "medium",
|
|
812
|
+
status: "in_progress"
|
|
813
|
+
}]
|
|
814
|
+
};
|
|
815
|
+
default:
|
|
816
|
+
return null;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
static convertToolCall(value) {
|
|
820
|
+
const parsed = parseToolCall(value);
|
|
821
|
+
if (!parsed) {
|
|
822
|
+
return {
|
|
823
|
+
sessionUpdate: "agent_message_chunk",
|
|
824
|
+
content: {
|
|
825
|
+
type: "text",
|
|
826
|
+
text: value
|
|
827
|
+
}
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
return {
|
|
831
|
+
sessionUpdate: "tool_call",
|
|
832
|
+
toolCallId: parsed.toolCallId,
|
|
833
|
+
title: parsed.name,
|
|
834
|
+
kind: "other",
|
|
835
|
+
status: "in_progress",
|
|
836
|
+
rawInput: parsed.args
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
static convertToolResult(value) {
|
|
840
|
+
const parsed = parseToolResult(value);
|
|
841
|
+
if (!parsed) {
|
|
842
|
+
return {
|
|
843
|
+
sessionUpdate: "agent_message_chunk",
|
|
844
|
+
content: {
|
|
845
|
+
type: "text",
|
|
846
|
+
text: value
|
|
847
|
+
}
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
const textBlock = {
|
|
851
|
+
type: "text",
|
|
852
|
+
text: parsed.output
|
|
853
|
+
};
|
|
854
|
+
const content = [{
|
|
855
|
+
type: "content",
|
|
856
|
+
content: textBlock
|
|
857
|
+
}];
|
|
858
|
+
return {
|
|
859
|
+
sessionUpdate: "tool_call_update",
|
|
860
|
+
toolCallId: parsed.toolCallId,
|
|
861
|
+
status: parsed.isError ? "failed" : "completed",
|
|
862
|
+
rawOutput: parsed.output,
|
|
863
|
+
content
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
};
|
|
867
|
+
|
|
868
|
+
// src/adapters/acp/pushHandler.ts
|
|
869
|
+
var ACPPushHandler = class {
|
|
870
|
+
adapter;
|
|
871
|
+
constructor(adapter) {
|
|
872
|
+
this.adapter = adapter;
|
|
873
|
+
}
|
|
874
|
+
async handlePushEvent(message, roomId) {
|
|
875
|
+
const sessionId = this.adapter.getSessionForRoom(roomId);
|
|
876
|
+
const connection = this.adapter.getConnection();
|
|
877
|
+
if (!sessionId || !connection) {
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
const update = EventConverter.convert(message);
|
|
881
|
+
if (!update) {
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
await connection.sessionUpdate({
|
|
885
|
+
sessionId,
|
|
886
|
+
update
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
};
|
|
890
|
+
|
|
891
|
+
// src/adapters/acp/router.ts
|
|
892
|
+
var AgentRouter = class {
|
|
893
|
+
modeToPeer;
|
|
894
|
+
slashCommands;
|
|
895
|
+
constructor(options) {
|
|
896
|
+
this.modeToPeer = { ...options?.modeToPeer ?? {} };
|
|
897
|
+
this.slashCommands = Object.fromEntries(
|
|
898
|
+
Object.entries(options?.slashCommands ?? {}).map(([command, peer]) => [command.toLowerCase(), peer])
|
|
899
|
+
);
|
|
900
|
+
}
|
|
901
|
+
resolve(text, currentMode) {
|
|
902
|
+
if (text.startsWith("/")) {
|
|
903
|
+
const [command, ...rest] = text.slice(1).split(/\s+/);
|
|
904
|
+
const peer = this.slashCommands[command?.toLowerCase() ?? ""];
|
|
905
|
+
if (peer) {
|
|
906
|
+
return {
|
|
907
|
+
text: rest.join(" ").trim(),
|
|
908
|
+
targetPeer: peer
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
if (currentMode && this.modeToPeer[currentMode]) {
|
|
913
|
+
return {
|
|
914
|
+
text,
|
|
915
|
+
targetPeer: this.modeToPeer[currentMode]
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
return {
|
|
919
|
+
text,
|
|
920
|
+
targetPeer: null
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
};
|
|
924
|
+
|
|
925
|
+
// src/adapters/acp/ThenvoiACPServerAdapter.ts
|
|
926
|
+
var DEFAULT_MAX_SESSIONS = 100;
|
|
927
|
+
var DEFAULT_PROMPT_TIMEOUT_MS = 3e5;
|
|
928
|
+
var DEFAULT_PROMPT_COMPLETION_GRACE_MS = 250;
|
|
929
|
+
var ThenvoiACPServerAdapter = class extends SimpleAdapter {
|
|
930
|
+
thenvoiRest;
|
|
931
|
+
maxSessions;
|
|
932
|
+
responseTimeoutMs;
|
|
933
|
+
promptCompletionGraceMs;
|
|
934
|
+
sessionModes;
|
|
935
|
+
sessionToRoom = /* @__PURE__ */ new Map();
|
|
936
|
+
roomToSession = /* @__PURE__ */ new Map();
|
|
937
|
+
pendingPrompts = /* @__PURE__ */ new Map();
|
|
938
|
+
sessionModeIds = /* @__PURE__ */ new Map();
|
|
939
|
+
sessionModelIds = /* @__PURE__ */ new Map();
|
|
940
|
+
sessionCwds = /* @__PURE__ */ new Map();
|
|
941
|
+
sessionMcpServers = /* @__PURE__ */ new Map();
|
|
942
|
+
connection = null;
|
|
943
|
+
agentId = null;
|
|
944
|
+
sessionsInFlight = 0;
|
|
945
|
+
router;
|
|
946
|
+
pushHandler;
|
|
947
|
+
constructor(options) {
|
|
948
|
+
super({
|
|
949
|
+
historyConverter: new ACPServerHistoryConverter()
|
|
950
|
+
});
|
|
951
|
+
this.thenvoiRest = options.thenvoiRest;
|
|
952
|
+
this.maxSessions = options.maxSessions ?? DEFAULT_MAX_SESSIONS;
|
|
953
|
+
this.responseTimeoutMs = options.responseTimeoutMs ?? DEFAULT_PROMPT_TIMEOUT_MS;
|
|
954
|
+
this.promptCompletionGraceMs = options.promptCompletionGraceMs ?? DEFAULT_PROMPT_COMPLETION_GRACE_MS;
|
|
955
|
+
this.sessionModes = options.sessionModes?.length ? [...options.sessionModes] : [...DEFAULT_ACP_SERVER_MODES];
|
|
956
|
+
this.router = new AgentRouter({
|
|
957
|
+
modeToPeer: options.modeToPeer,
|
|
958
|
+
slashCommands: options.slashCommands
|
|
959
|
+
});
|
|
960
|
+
this.pushHandler = new ACPPushHandler(this);
|
|
961
|
+
}
|
|
962
|
+
bindConnection(connection) {
|
|
963
|
+
this.connection = connection;
|
|
964
|
+
}
|
|
965
|
+
getConnection() {
|
|
966
|
+
return this.connection;
|
|
967
|
+
}
|
|
968
|
+
hasSession(sessionId) {
|
|
969
|
+
return this.sessionToRoom.has(sessionId);
|
|
970
|
+
}
|
|
971
|
+
getSessionIds() {
|
|
972
|
+
return [...this.sessionToRoom.keys()];
|
|
973
|
+
}
|
|
974
|
+
getSessionForRoom(roomId) {
|
|
975
|
+
return this.roomToSession.get(roomId) ?? null;
|
|
976
|
+
}
|
|
977
|
+
get displayName() {
|
|
978
|
+
return this.agentName;
|
|
979
|
+
}
|
|
980
|
+
getSessionCwd(sessionId) {
|
|
981
|
+
return this.sessionCwds.get(sessionId) ?? ".";
|
|
982
|
+
}
|
|
983
|
+
getModeState(sessionId) {
|
|
984
|
+
return {
|
|
985
|
+
availableModes: this.sessionModes,
|
|
986
|
+
currentModeId: this.sessionModeIds.get(sessionId) ?? this.sessionModes[0]?.id ?? "default"
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
setSessionModes(modes) {
|
|
990
|
+
this.sessionModes.splice(0, this.sessionModes.length, ...modes);
|
|
991
|
+
}
|
|
992
|
+
setSessionMode(sessionId, modeId) {
|
|
993
|
+
this.sessionModeIds.set(sessionId, modeId);
|
|
994
|
+
}
|
|
995
|
+
setSessionModel(sessionId, modelId) {
|
|
996
|
+
this.sessionModelIds.set(sessionId, modelId);
|
|
997
|
+
}
|
|
998
|
+
updateSessionContext(sessionId, input) {
|
|
999
|
+
if (input.cwd) {
|
|
1000
|
+
this.sessionCwds.set(sessionId, input.cwd);
|
|
1001
|
+
}
|
|
1002
|
+
if (input.mcpServers) {
|
|
1003
|
+
const normalized = normalizeMcpServers(input.mcpServers);
|
|
1004
|
+
if (normalized.length > 0) {
|
|
1005
|
+
this.sessionMcpServers.set(sessionId, normalized);
|
|
1006
|
+
} else {
|
|
1007
|
+
this.sessionMcpServers.delete(sessionId);
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
async verifyCredentials() {
|
|
1012
|
+
try {
|
|
1013
|
+
await this.thenvoiRest.getAgentMe();
|
|
1014
|
+
return true;
|
|
1015
|
+
} catch {
|
|
1016
|
+
return false;
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
async onStarted(agentName, agentDescription) {
|
|
1020
|
+
await super.onStarted(agentName, agentDescription);
|
|
1021
|
+
try {
|
|
1022
|
+
const identity = await this.thenvoiRest.getAgentMe();
|
|
1023
|
+
this.agentId = identity.id;
|
|
1024
|
+
} catch {
|
|
1025
|
+
this.agentId = null;
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
async createSession(input = {}) {
|
|
1029
|
+
this.sessionsInFlight += 1;
|
|
1030
|
+
const activeCount = this.sessionToRoom.size + this.sessionsInFlight;
|
|
1031
|
+
if (activeCount > this.maxSessions) {
|
|
1032
|
+
this.sessionsInFlight = Math.max(0, this.sessionsInFlight - 1);
|
|
1033
|
+
throw new Error(`Maximum ACP sessions (${this.maxSessions}) reached`);
|
|
1034
|
+
}
|
|
1035
|
+
try {
|
|
1036
|
+
const room = await this.thenvoiRest.createChat();
|
|
1037
|
+
const sessionId = randomUUID();
|
|
1038
|
+
const normalizedMcpServers = normalizeMcpServers(input.mcpServers);
|
|
1039
|
+
this.sessionToRoom.set(sessionId, room.id);
|
|
1040
|
+
this.roomToSession.set(room.id, sessionId);
|
|
1041
|
+
this.sessionCwds.set(sessionId, input.cwd ?? ".");
|
|
1042
|
+
if (normalizedMcpServers.length > 0) {
|
|
1043
|
+
this.sessionMcpServers.set(sessionId, normalizedMcpServers);
|
|
1044
|
+
}
|
|
1045
|
+
try {
|
|
1046
|
+
await this.thenvoiRest.createChatEvent(room.id, {
|
|
1047
|
+
content: "ACP session context",
|
|
1048
|
+
messageType: "task",
|
|
1049
|
+
metadata: {
|
|
1050
|
+
acp_session_id: sessionId,
|
|
1051
|
+
acp_room_id: room.id,
|
|
1052
|
+
acp_cwd: input.cwd ?? ".",
|
|
1053
|
+
acp_mcp_servers: normalizedMcpServers
|
|
1054
|
+
}
|
|
1055
|
+
});
|
|
1056
|
+
} catch (error) {
|
|
1057
|
+
this.sessionToRoom.delete(sessionId);
|
|
1058
|
+
this.roomToSession.delete(room.id);
|
|
1059
|
+
this.sessionCwds.delete(sessionId);
|
|
1060
|
+
this.sessionMcpServers.delete(sessionId);
|
|
1061
|
+
this.sessionModeIds.delete(sessionId);
|
|
1062
|
+
this.sessionModelIds.delete(sessionId);
|
|
1063
|
+
throw error;
|
|
1064
|
+
}
|
|
1065
|
+
return sessionId;
|
|
1066
|
+
} finally {
|
|
1067
|
+
this.sessionsInFlight = Math.max(0, this.sessionsInFlight - 1);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
async handlePrompt(sessionId, text) {
|
|
1071
|
+
const roomId = this.sessionToRoom.get(sessionId);
|
|
1072
|
+
if (!roomId) {
|
|
1073
|
+
throw new Error(`Unknown ACP session: ${sessionId}`);
|
|
1074
|
+
}
|
|
1075
|
+
const pending = createPendingPrompt(sessionId);
|
|
1076
|
+
this.pendingPrompts.set(roomId, pending);
|
|
1077
|
+
const resolved = this.router.resolve(text, this.sessionModeIds.get(sessionId));
|
|
1078
|
+
const promptText = this.prependSessionContext(sessionId, resolved.text);
|
|
1079
|
+
const participants = await this.thenvoiRest.listChatParticipants(roomId);
|
|
1080
|
+
const mentions = this.resolveMentions(participants, resolved.targetPeer);
|
|
1081
|
+
try {
|
|
1082
|
+
await this.thenvoiRest.createChatMessage(roomId, {
|
|
1083
|
+
content: promptText,
|
|
1084
|
+
mentions
|
|
1085
|
+
});
|
|
1086
|
+
await Promise.race([
|
|
1087
|
+
pending.done,
|
|
1088
|
+
new Promise((_, reject) => {
|
|
1089
|
+
const timer = setTimeout(() => {
|
|
1090
|
+
reject(new Error(`ACP prompt timed out after ${this.responseTimeoutMs}ms`));
|
|
1091
|
+
}, this.responseTimeoutMs);
|
|
1092
|
+
pending.done.finally(() => clearTimeout(timer)).catch(() => void 0);
|
|
1093
|
+
})
|
|
1094
|
+
]);
|
|
1095
|
+
} finally {
|
|
1096
|
+
this.finishPendingPrompt(roomId, pending, false);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
async onMessage(message, _tools, history, _participantsMessage, _contactsMessage, context) {
|
|
1100
|
+
if (context.isSessionBootstrap) {
|
|
1101
|
+
this.rehydrate(history);
|
|
1102
|
+
}
|
|
1103
|
+
if (this.agentId && message.senderId === this.agentId) {
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
const pending = this.pendingPrompts.get(context.roomId);
|
|
1107
|
+
if (pending && this.connection) {
|
|
1108
|
+
const update = EventConverter.convert(message);
|
|
1109
|
+
if (update) {
|
|
1110
|
+
await this.connection.sessionUpdate({
|
|
1111
|
+
sessionId: pending.sessionId,
|
|
1112
|
+
update
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
if (message.messageType === "text" || message.messageType === "error") {
|
|
1116
|
+
pending.terminalMessageSeen = true;
|
|
1117
|
+
}
|
|
1118
|
+
if (update || pending.terminalMessageSeen) {
|
|
1119
|
+
this.schedulePromptCompletion(context.roomId, pending);
|
|
1120
|
+
}
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
if (this.connection) {
|
|
1124
|
+
await this.pushHandler.handlePushEvent(message, context.roomId);
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
async onCleanup(roomId) {
|
|
1128
|
+
const sessionId = this.roomToSession.get(roomId);
|
|
1129
|
+
if (sessionId) {
|
|
1130
|
+
this.roomToSession.delete(roomId);
|
|
1131
|
+
this.sessionToRoom.delete(sessionId);
|
|
1132
|
+
this.sessionModeIds.delete(sessionId);
|
|
1133
|
+
this.sessionModelIds.delete(sessionId);
|
|
1134
|
+
this.sessionCwds.delete(sessionId);
|
|
1135
|
+
this.sessionMcpServers.delete(sessionId);
|
|
1136
|
+
}
|
|
1137
|
+
this.finishPendingPrompt(roomId, this.pendingPrompts.get(roomId) ?? null, true);
|
|
1138
|
+
}
|
|
1139
|
+
async onRuntimeStop() {
|
|
1140
|
+
for (const roomId of [...this.roomToSession.keys()]) {
|
|
1141
|
+
await this.onCleanup(roomId);
|
|
1142
|
+
}
|
|
1143
|
+
this.connection = null;
|
|
1144
|
+
}
|
|
1145
|
+
async cancelPrompt(sessionId) {
|
|
1146
|
+
const roomId = this.sessionToRoom.get(sessionId);
|
|
1147
|
+
if (!roomId) {
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
this.finishPendingPrompt(roomId, this.pendingPrompts.get(roomId) ?? null, true);
|
|
1151
|
+
}
|
|
1152
|
+
async closeSession(sessionId) {
|
|
1153
|
+
const roomId = this.sessionToRoom.get(sessionId);
|
|
1154
|
+
if (!roomId) {
|
|
1155
|
+
return;
|
|
1156
|
+
}
|
|
1157
|
+
await this.onCleanup(roomId);
|
|
1158
|
+
}
|
|
1159
|
+
rehydrate(history) {
|
|
1160
|
+
for (const [sessionId, roomId] of Object.entries(history.sessionToRoom)) {
|
|
1161
|
+
if (!this.sessionToRoom.has(sessionId)) {
|
|
1162
|
+
this.sessionToRoom.set(sessionId, roomId);
|
|
1163
|
+
}
|
|
1164
|
+
if (!this.roomToSession.has(roomId)) {
|
|
1165
|
+
this.roomToSession.set(roomId, sessionId);
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
for (const [sessionId, cwd] of Object.entries(history.sessionCwd)) {
|
|
1169
|
+
if (!this.sessionCwds.has(sessionId)) {
|
|
1170
|
+
this.sessionCwds.set(sessionId, cwd);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
for (const [sessionId, mcpServers] of Object.entries(history.sessionMcpServers)) {
|
|
1174
|
+
if (!this.sessionMcpServers.has(sessionId)) {
|
|
1175
|
+
this.sessionMcpServers.set(sessionId, [...mcpServers]);
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
prependSessionContext(sessionId, text) {
|
|
1180
|
+
const cwd = this.sessionCwds.get(sessionId);
|
|
1181
|
+
const mcpServers = this.sessionMcpServers.get(sessionId) ?? [];
|
|
1182
|
+
if (!cwd && mcpServers.length === 0) {
|
|
1183
|
+
return text;
|
|
1184
|
+
}
|
|
1185
|
+
const lines = ["[ACP Session Context]"];
|
|
1186
|
+
if (cwd) {
|
|
1187
|
+
lines.push(`Editor cwd: ${cwd}`);
|
|
1188
|
+
}
|
|
1189
|
+
if (mcpServers.length > 0) {
|
|
1190
|
+
lines.push("Editor MCP servers:");
|
|
1191
|
+
lines.push(...mcpServers.map((server) => formatMcpServerLine(server)));
|
|
1192
|
+
lines.push("These MCP servers belong to the connected editor session.");
|
|
1193
|
+
}
|
|
1194
|
+
lines.push("");
|
|
1195
|
+
lines.push(text);
|
|
1196
|
+
return lines.join("\n");
|
|
1197
|
+
}
|
|
1198
|
+
resolveMentions(participants, targetPeer) {
|
|
1199
|
+
const filtered = participants.filter((participant) => {
|
|
1200
|
+
if (this.agentId && participant.id === this.agentId) {
|
|
1201
|
+
return false;
|
|
1202
|
+
}
|
|
1203
|
+
if (!targetPeer) {
|
|
1204
|
+
return true;
|
|
1205
|
+
}
|
|
1206
|
+
const normalizedTarget = targetPeer.startsWith("@") ? targetPeer.slice(1) : targetPeer;
|
|
1207
|
+
const participantHandles = [
|
|
1208
|
+
participant.handle,
|
|
1209
|
+
ensureHandlePrefix(participant.handle)?.slice(1)
|
|
1210
|
+
].filter((value) => typeof value === "string" && value.length > 0);
|
|
1211
|
+
return participant.name === targetPeer || participant.name === normalizedTarget || participantHandles.includes(targetPeer) || participantHandles.includes(normalizedTarget);
|
|
1212
|
+
});
|
|
1213
|
+
return filtered.map((participant) => ({
|
|
1214
|
+
id: participant.id,
|
|
1215
|
+
handle: participant.handle ?? void 0,
|
|
1216
|
+
name: participant.name
|
|
1217
|
+
}));
|
|
1218
|
+
}
|
|
1219
|
+
schedulePromptCompletion(roomId, pending) {
|
|
1220
|
+
if (this.pendingPrompts.get(roomId) !== pending) {
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
1223
|
+
if (pending.completionTimer) {
|
|
1224
|
+
clearTimeout(pending.completionTimer);
|
|
1225
|
+
}
|
|
1226
|
+
pending.completionTimer = setTimeout(() => {
|
|
1227
|
+
this.finishPendingPrompt(roomId, pending, true);
|
|
1228
|
+
}, this.promptCompletionGraceMs);
|
|
1229
|
+
}
|
|
1230
|
+
finishPendingPrompt(roomId, pending, setDone) {
|
|
1231
|
+
const current = this.pendingPrompts.get(roomId);
|
|
1232
|
+
if (!current || pending && current !== pending) {
|
|
1233
|
+
return;
|
|
1234
|
+
}
|
|
1235
|
+
this.pendingPrompts.delete(roomId);
|
|
1236
|
+
if (current.completionTimer) {
|
|
1237
|
+
clearTimeout(current.completionTimer);
|
|
1238
|
+
current.completionTimer = null;
|
|
1239
|
+
}
|
|
1240
|
+
if (setDone) {
|
|
1241
|
+
current.markDone();
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
};
|
|
1245
|
+
function formatMcpServerLine(server) {
|
|
1246
|
+
const type = typeof server.type === "string" ? server.type : "unknown";
|
|
1247
|
+
const name = typeof server.name === "string" ? server.name : typeof server.command === "string" ? server.command : typeof server.url === "string" ? server.url : "unnamed";
|
|
1248
|
+
const details = [type];
|
|
1249
|
+
if (type === "stdio" && typeof server.command === "string") {
|
|
1250
|
+
details.push(`command=${server.command}`);
|
|
1251
|
+
}
|
|
1252
|
+
if (type !== "stdio" && typeof server.url === "string") {
|
|
1253
|
+
details.push(`url=${server.url}`);
|
|
1254
|
+
}
|
|
1255
|
+
return `- ${name} (${details.join(", ")})`;
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
// src/adapters/acp/cursorExtensions.ts
|
|
1259
|
+
var CursorExtensionHandler = class {
|
|
1260
|
+
async extMethod(method, params) {
|
|
1261
|
+
if (method === "cursor/ask_question") {
|
|
1262
|
+
const options = Array.isArray(params.options) ? params.options : [];
|
|
1263
|
+
const first = options.find((option) => !!option && typeof option === "object");
|
|
1264
|
+
if (!first) {
|
|
1265
|
+
return {
|
|
1266
|
+
outcome: {
|
|
1267
|
+
type: "cancelled"
|
|
1268
|
+
}
|
|
1269
|
+
};
|
|
1270
|
+
}
|
|
1271
|
+
return {
|
|
1272
|
+
outcome: {
|
|
1273
|
+
type: "selected",
|
|
1274
|
+
optionId: String(first.optionId ?? first.id ?? "0")
|
|
1275
|
+
}
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
if (method === "cursor/create_plan") {
|
|
1279
|
+
return {
|
|
1280
|
+
outcome: {
|
|
1281
|
+
type: "approved"
|
|
1282
|
+
}
|
|
1283
|
+
};
|
|
1284
|
+
}
|
|
1285
|
+
return null;
|
|
1286
|
+
}
|
|
1287
|
+
async extNotification(method, params, context) {
|
|
1288
|
+
if (method === "cursor/update_todos") {
|
|
1289
|
+
const todos = Array.isArray(params.todos) ? params.todos : [];
|
|
1290
|
+
const text = todos.filter((todo) => !!todo && typeof todo === "object").map((todo) => `- [${todo.completed === true ? "x" : " "}] ${String(todo.content ?? "")}`).join("\n");
|
|
1291
|
+
if (text.length > 0) {
|
|
1292
|
+
await context.connection.sessionUpdate({
|
|
1293
|
+
sessionId: context.sessionId,
|
|
1294
|
+
update: {
|
|
1295
|
+
sessionUpdate: "agent_message_chunk",
|
|
1296
|
+
content: {
|
|
1297
|
+
type: "text",
|
|
1298
|
+
text
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
if (method === "cursor/task" && typeof params.result === "string" && params.result.length > 0) {
|
|
1306
|
+
await context.connection.sessionUpdate({
|
|
1307
|
+
sessionId: context.sessionId,
|
|
1308
|
+
update: {
|
|
1309
|
+
sessionUpdate: "agent_message_chunk",
|
|
1310
|
+
content: {
|
|
1311
|
+
type: "text",
|
|
1312
|
+
text: `[Task completed] ${params.result}`
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
};
|
|
1319
|
+
|
|
1320
|
+
// src/adapters/acp/ACPServer.ts
|
|
1321
|
+
var ACPServer = class {
|
|
1322
|
+
adapter;
|
|
1323
|
+
authMethods;
|
|
1324
|
+
agentInfo;
|
|
1325
|
+
extensionHandler;
|
|
1326
|
+
connection = null;
|
|
1327
|
+
constructor(adapter, options) {
|
|
1328
|
+
this.adapter = adapter;
|
|
1329
|
+
if (options?.modes?.length) {
|
|
1330
|
+
this.adapter.setSessionModes([...options.modes]);
|
|
1331
|
+
}
|
|
1332
|
+
this.authMethods = options?.authMethods ?? [{
|
|
1333
|
+
id: "api_key",
|
|
1334
|
+
name: "API Key",
|
|
1335
|
+
description: "Authenticate with THENVOI_API_KEY."
|
|
1336
|
+
}];
|
|
1337
|
+
this.agentInfo = options?.agentInfo ?? {};
|
|
1338
|
+
this.extensionHandler = options?.extensionHandler ?? new CursorExtensionHandler();
|
|
1339
|
+
}
|
|
1340
|
+
async connectStream(stream) {
|
|
1341
|
+
if (this.connection && !this.connection.signal.aborted) {
|
|
1342
|
+
return this.connection;
|
|
1343
|
+
}
|
|
1344
|
+
const acp = await acpModule.get();
|
|
1345
|
+
this.connection = new acp.AgentSideConnection((connection) => {
|
|
1346
|
+
this.adapter.bindConnection(connection);
|
|
1347
|
+
return this;
|
|
1348
|
+
}, stream);
|
|
1349
|
+
void this.connection.closed.finally(() => {
|
|
1350
|
+
if (this.connection?.signal.aborted) {
|
|
1351
|
+
this.adapter.bindConnection(null);
|
|
1352
|
+
}
|
|
1353
|
+
});
|
|
1354
|
+
return this.connection;
|
|
1355
|
+
}
|
|
1356
|
+
connectStdio(options) {
|
|
1357
|
+
const stdin = options?.stdin ?? process.stdin;
|
|
1358
|
+
const stdout = options?.stdout ?? process.stdout;
|
|
1359
|
+
return acpModule.get().then((acp) => this.connectStream(
|
|
1360
|
+
acp.ndJsonStream(
|
|
1361
|
+
Writable2.toWeb(stdout),
|
|
1362
|
+
Readable2.toWeb(stdin)
|
|
1363
|
+
)
|
|
1364
|
+
));
|
|
1365
|
+
}
|
|
1366
|
+
get closed() {
|
|
1367
|
+
return this.connection?.closed ?? Promise.resolve();
|
|
1368
|
+
}
|
|
1369
|
+
async initialize(params) {
|
|
1370
|
+
const acp = await acpModule.get();
|
|
1371
|
+
return {
|
|
1372
|
+
protocolVersion: params.protocolVersion === acp.PROTOCOL_VERSION ? params.protocolVersion : acp.PROTOCOL_VERSION,
|
|
1373
|
+
agentCapabilities: {
|
|
1374
|
+
loadSession: true,
|
|
1375
|
+
promptCapabilities: {
|
|
1376
|
+
image: false,
|
|
1377
|
+
audio: false,
|
|
1378
|
+
embeddedContext: true
|
|
1379
|
+
},
|
|
1380
|
+
sessionCapabilities: {
|
|
1381
|
+
list: {},
|
|
1382
|
+
resume: {},
|
|
1383
|
+
fork: {},
|
|
1384
|
+
close: {}
|
|
1385
|
+
}
|
|
1386
|
+
},
|
|
1387
|
+
agentInfo: {
|
|
1388
|
+
name: this.agentInfo.name ?? "thenvoi-agent",
|
|
1389
|
+
title: this.agentInfo.title ?? this.adapter.displayName ?? "Thenvoi Agent",
|
|
1390
|
+
version: this.agentInfo.version ?? "0.1.0"
|
|
1391
|
+
},
|
|
1392
|
+
authMethods: this.authMethods
|
|
1393
|
+
};
|
|
1394
|
+
}
|
|
1395
|
+
async newSession(params) {
|
|
1396
|
+
const sessionId = await this.adapter.createSession({
|
|
1397
|
+
cwd: params.cwd,
|
|
1398
|
+
mcpServers: params.mcpServers
|
|
1399
|
+
});
|
|
1400
|
+
return {
|
|
1401
|
+
sessionId,
|
|
1402
|
+
modes: this.adapter.getModeState(sessionId)
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1405
|
+
async loadSession(params) {
|
|
1406
|
+
if (!this.adapter.hasSession(params.sessionId)) {
|
|
1407
|
+
const acp = await acpModule.get();
|
|
1408
|
+
throw acp.RequestError.resourceNotFound(params.sessionId);
|
|
1409
|
+
}
|
|
1410
|
+
this.adapter.updateSessionContext(params.sessionId, {
|
|
1411
|
+
cwd: params.cwd,
|
|
1412
|
+
mcpServers: params.mcpServers
|
|
1413
|
+
});
|
|
1414
|
+
return {
|
|
1415
|
+
modes: this.adapter.getModeState(params.sessionId)
|
|
1416
|
+
};
|
|
1417
|
+
}
|
|
1418
|
+
async listSessions(params) {
|
|
1419
|
+
const sessions = this.adapter.getSessionIds().filter((sessionId) => !params.cwd || this.adapter.getSessionCwd(sessionId) === params.cwd).map((sessionId) => ({
|
|
1420
|
+
sessionId,
|
|
1421
|
+
cwd: this.adapter.getSessionCwd(sessionId)
|
|
1422
|
+
}));
|
|
1423
|
+
return { sessions };
|
|
1424
|
+
}
|
|
1425
|
+
async unstable_forkSession(params) {
|
|
1426
|
+
if (!this.adapter.hasSession(params.sessionId)) {
|
|
1427
|
+
const acp = await acpModule.get();
|
|
1428
|
+
throw acp.RequestError.resourceNotFound(params.sessionId);
|
|
1429
|
+
}
|
|
1430
|
+
const sessionId = await this.adapter.createSession({
|
|
1431
|
+
cwd: params.cwd,
|
|
1432
|
+
mcpServers: params.mcpServers
|
|
1433
|
+
});
|
|
1434
|
+
return {
|
|
1435
|
+
sessionId
|
|
1436
|
+
};
|
|
1437
|
+
}
|
|
1438
|
+
async unstable_resumeSession(params) {
|
|
1439
|
+
if (!this.adapter.hasSession(params.sessionId)) {
|
|
1440
|
+
const acp = await acpModule.get();
|
|
1441
|
+
throw acp.RequestError.resourceNotFound(params.sessionId);
|
|
1442
|
+
}
|
|
1443
|
+
this.adapter.updateSessionContext(params.sessionId, {
|
|
1444
|
+
cwd: params.cwd,
|
|
1445
|
+
mcpServers: params.mcpServers
|
|
1446
|
+
});
|
|
1447
|
+
return {
|
|
1448
|
+
modes: this.adapter.getModeState(params.sessionId)
|
|
1449
|
+
};
|
|
1450
|
+
}
|
|
1451
|
+
async unstable_closeSession(params) {
|
|
1452
|
+
await this.adapter.closeSession(params.sessionId);
|
|
1453
|
+
return {};
|
|
1454
|
+
}
|
|
1455
|
+
async setSessionMode(params) {
|
|
1456
|
+
if (!this.adapter.hasSession(params.sessionId)) {
|
|
1457
|
+
const acp = await acpModule.get();
|
|
1458
|
+
throw acp.RequestError.resourceNotFound(params.sessionId);
|
|
1459
|
+
}
|
|
1460
|
+
this.adapter.setSessionMode(params.sessionId, params.modeId);
|
|
1461
|
+
return {};
|
|
1462
|
+
}
|
|
1463
|
+
async unstable_setSessionModel(params) {
|
|
1464
|
+
if (!this.adapter.hasSession(params.sessionId)) {
|
|
1465
|
+
const acp = await acpModule.get();
|
|
1466
|
+
throw acp.RequestError.resourceNotFound(params.sessionId);
|
|
1467
|
+
}
|
|
1468
|
+
this.adapter.setSessionModel(params.sessionId, params.modelId);
|
|
1469
|
+
return {};
|
|
1470
|
+
}
|
|
1471
|
+
async setSessionConfigOption(_params) {
|
|
1472
|
+
return {
|
|
1473
|
+
configOptions: []
|
|
1474
|
+
};
|
|
1475
|
+
}
|
|
1476
|
+
async authenticate(params) {
|
|
1477
|
+
if (params.methodId !== "api_key" && params.methodId !== "cursor_login") {
|
|
1478
|
+
const acp = await acpModule.get();
|
|
1479
|
+
throw acp.RequestError.authRequired(void 0, `Unsupported auth method: ${params.methodId}`);
|
|
1480
|
+
}
|
|
1481
|
+
const ok = await this.adapter.verifyCredentials();
|
|
1482
|
+
if (!ok) {
|
|
1483
|
+
const acp = await acpModule.get();
|
|
1484
|
+
throw acp.RequestError.authRequired(void 0, "Authentication failed");
|
|
1485
|
+
}
|
|
1486
|
+
return {};
|
|
1487
|
+
}
|
|
1488
|
+
async prompt(params) {
|
|
1489
|
+
const text = extractPromptText(params.prompt);
|
|
1490
|
+
await this.adapter.handlePrompt(params.sessionId, text);
|
|
1491
|
+
return {
|
|
1492
|
+
stopReason: "end_turn"
|
|
1493
|
+
};
|
|
1494
|
+
}
|
|
1495
|
+
async cancel(params) {
|
|
1496
|
+
await this.adapter.cancelPrompt(params.sessionId);
|
|
1497
|
+
}
|
|
1498
|
+
async extMethod(method, params) {
|
|
1499
|
+
if (this.extensionHandler.extMethod) {
|
|
1500
|
+
const result = await this.extensionHandler.extMethod(method, params);
|
|
1501
|
+
if (result) {
|
|
1502
|
+
return result;
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
return {
|
|
1506
|
+
error: `Unknown extension method: ${method}`
|
|
1507
|
+
};
|
|
1508
|
+
}
|
|
1509
|
+
async extNotification(method, params) {
|
|
1510
|
+
const sessionId = typeof params.sessionId === "string" ? params.sessionId : typeof params.session_id === "string" ? params.session_id : null;
|
|
1511
|
+
if (!sessionId || !this.adapter.hasSession(sessionId)) {
|
|
1512
|
+
return;
|
|
1513
|
+
}
|
|
1514
|
+
const connection = this.adapter.getConnection();
|
|
1515
|
+
if (!connection) {
|
|
1516
|
+
return;
|
|
1517
|
+
}
|
|
1518
|
+
if (this.extensionHandler.extNotification) {
|
|
1519
|
+
await this.extensionHandler.extNotification(method, params, { sessionId, connection });
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
};
|
|
1523
|
+
function extractPromptText(prompt) {
|
|
1524
|
+
const parts = [];
|
|
1525
|
+
for (const block of prompt) {
|
|
1526
|
+
if (block.type === "text") {
|
|
1527
|
+
parts.push(block.text);
|
|
1528
|
+
continue;
|
|
1529
|
+
}
|
|
1530
|
+
if (block.type === "image") {
|
|
1531
|
+
parts.push(block.uri ? `[Image: ${block.uri}]` : "[Image]");
|
|
1532
|
+
continue;
|
|
1533
|
+
}
|
|
1534
|
+
if (block.type === "audio") {
|
|
1535
|
+
parts.push("[Audio]");
|
|
1536
|
+
continue;
|
|
1537
|
+
}
|
|
1538
|
+
if (block.type === "resource_link") {
|
|
1539
|
+
parts.push(`[Resource: ${block.title ?? block.name ?? block.uri}]`);
|
|
1540
|
+
continue;
|
|
1541
|
+
}
|
|
1542
|
+
if (block.type === "resource") {
|
|
1543
|
+
const resource = block.resource;
|
|
1544
|
+
if ("text" in resource && typeof resource.text === "string") {
|
|
1545
|
+
parts.push(resource.text);
|
|
1546
|
+
} else {
|
|
1547
|
+
parts.push(`[Resource: ${resource.uri ?? "embedded"}]`);
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
return parts.join("\n").trim();
|
|
1552
|
+
}
|
|
54
1553
|
export {
|
|
55
1554
|
A2AAdapter,
|
|
56
1555
|
A2AGatewayAdapter,
|