@band-ai/sdk 0.1.2 → 0.1.6

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.
Files changed (37) hide show
  1. package/dist/{CodexAdapter-A1k_LMdZ.d.ts → ClaudeSDKAdapter-BHOkjN3X.d.ts} +222 -222
  2. package/dist/{CodexAdapter-BbZD-nBd.d.cts → ClaudeSDKAdapter-CRq4R-WB.d.cts} +222 -222
  3. package/dist/adapters.cjs +375 -275
  4. package/dist/adapters.d.cts +21 -21
  5. package/dist/adapters.d.ts +21 -21
  6. package/dist/adapters.js +1512 -13
  7. package/dist/{backends-C4n6cKOf.d.ts → backends-CzDbiQar.d.ts} +1 -1
  8. package/dist/{backends-BnOLsG7w.d.cts → backends-SVMT-7l4.d.cts} +1 -1
  9. package/dist/chunk-FYVLUGW7.js +59 -0
  10. package/dist/{chunk-6AJA2OPC.js → chunk-I5EZG52P.js} +11 -4
  11. package/dist/{chunk-NIAQBANR.js → chunk-MSL6CWHY.js} +1 -1
  12. package/dist/{chunk-A3TDK6GP.js → chunk-RHVH2GZR.js} +4589 -5993
  13. package/dist/{chunk-FUODYQRE.js → chunk-W5NDZSYE.js} +2 -59
  14. package/dist/converters.d.cts +1 -1
  15. package/dist/converters.d.ts +1 -1
  16. package/dist/converters.js +5 -3
  17. package/dist/index.cjs +7161 -7116
  18. package/dist/index.d.cts +3 -3
  19. package/dist/index.d.ts +3 -3
  20. package/dist/index.js +4 -6
  21. package/dist/mcp-claude.cjs +4 -6
  22. package/dist/mcp-claude.d.cts +1 -1
  23. package/dist/mcp-claude.d.ts +1 -1
  24. package/dist/mcp-claude.js +47 -5
  25. package/dist/mcp.d.cts +3 -3
  26. package/dist/mcp.d.ts +3 -3
  27. package/dist/mcp.js +1 -1
  28. package/dist/runtime.cjs +11 -4
  29. package/dist/runtime.js +1 -1
  30. package/package.json +2 -2
  31. package/dist/chunk-5CXIMAH2.js +0 -52
  32. package/dist/chunk-LY55KYVI.js +0 -0
  33. package/dist/sdk-HJUVSSWA.js +0 -10
  34. package/dist/{history-ByessXNq.d.ts → acp-server-B1Da7L6u.d.ts} +4 -4
  35. package/dist/{history-i6yN7neC.d.cts → acp-server-BdRz9_UN.d.cts} +4 -4
  36. /package/dist/{claude-CcnQ5OUC.d.ts → sdk-CcnQ5OUC.d.ts} +0 -0
  37. /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-A3TDK6GP.js";
31
- import "./chunk-NIAQBANR.js";
32
- import "./chunk-LY55KYVI.js";
33
- import "./chunk-5CXIMAH2.js";
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
- } from "./chunk-FUODYQRE.js";
46
- import "./chunk-AVVDPXP4.js";
47
- import "./chunk-INA6YZTZ.js";
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 "./chunk-SFVKOEQH.js";
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 "./chunk-YR3BOQNW.js";
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,