@band-ai/sdk 0.4.2 → 0.4.3
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/{CopilotACPAdapter-Bj6NRdqd.d.ts → CursorACPAdapter-7-Jtl1LV.d.ts} +90 -1
- package/dist/{CopilotACPAdapter-HRvat7CP.d.cts → CursorACPAdapter-_YhrXRWq.d.cts} +90 -1
- package/dist/adapters.cjs +468 -125
- package/dist/adapters.d.cts +1 -1
- package/dist/adapters.d.ts +1 -1
- package/dist/adapters.js +5 -1
- package/dist/{chunk-KXLV645N.js → chunk-VHYPQAQ5.js} +455 -114
- package/dist/index.cjs +405 -62
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5 -1
- package/package.json +1 -1
package/dist/adapters.cjs
CHANGED
|
@@ -990,7 +990,9 @@ __export(adapters_exports, {
|
|
|
990
990
|
CodexAppServerStdioClient: () => CodexAppServerStdioClient,
|
|
991
991
|
CodexJsonRpcError: () => CodexJsonRpcError,
|
|
992
992
|
CopilotACPAdapter: () => CopilotACPAdapter,
|
|
993
|
+
CursorACPAdapter: () => CursorACPAdapter,
|
|
993
994
|
DEFAULT_COPILOT_ACP_COMMAND: () => DEFAULT_COPILOT_ACP_COMMAND,
|
|
995
|
+
DEFAULT_CURSOR_ACP_COMMAND: () => DEFAULT_CURSOR_ACP_COMMAND,
|
|
994
996
|
DEFAULT_OMP_ACP_COMMAND: () => DEFAULT_OMP_ACP_COMMAND,
|
|
995
997
|
FAILURE_CODE_SESSION_CONFIG: () => FAILURE_CODE_SESSION_CONFIG,
|
|
996
998
|
GatewayHistoryConverter: () => GatewayHistoryConverter,
|
|
@@ -2338,74 +2340,17 @@ function checkPort2(http, port) {
|
|
|
2338
2340
|
});
|
|
2339
2341
|
}
|
|
2340
2342
|
|
|
2341
|
-
// src/adapters/acp/types.ts
|
|
2342
|
-
var DEFAULT_ACP_SERVER_MODES = [
|
|
2343
|
-
{
|
|
2344
|
-
id: "default",
|
|
2345
|
-
name: "Default",
|
|
2346
|
-
description: "General-purpose chat mode"
|
|
2347
|
-
},
|
|
2348
|
-
{
|
|
2349
|
-
id: "code",
|
|
2350
|
-
name: "Code",
|
|
2351
|
-
description: "Route prompts toward coding peers when available"
|
|
2352
|
-
}
|
|
2353
|
-
];
|
|
2354
|
-
function createPendingPrompt(sessionId) {
|
|
2355
|
-
let markDone = () => void 0;
|
|
2356
|
-
const done = new Promise((resolve) => {
|
|
2357
|
-
markDone = resolve;
|
|
2358
|
-
});
|
|
2359
|
-
return {
|
|
2360
|
-
sessionId,
|
|
2361
|
-
done,
|
|
2362
|
-
markDone,
|
|
2363
|
-
terminalMessageSeen: false,
|
|
2364
|
-
completionTimer: null
|
|
2365
|
-
};
|
|
2366
|
-
}
|
|
2367
|
-
function choosePermissionOption(options) {
|
|
2368
|
-
if (options.length === 0) {
|
|
2369
|
-
return null;
|
|
2370
|
-
}
|
|
2371
|
-
return options.find((option) => option.kind === "allow_once") ?? options.find((option) => option.kind === "allow_always") ?? options[0];
|
|
2372
|
-
}
|
|
2373
|
-
function asJsonSafe(value) {
|
|
2374
|
-
if (value === null || value === void 0 || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
2375
|
-
return value;
|
|
2376
|
-
}
|
|
2377
|
-
if (Array.isArray(value)) {
|
|
2378
|
-
return value.map((item) => asJsonSafe(item));
|
|
2379
|
-
}
|
|
2380
|
-
if (typeof value === "object") {
|
|
2381
|
-
if ("model_dump" in value && typeof value.model_dump === "function") {
|
|
2382
|
-
return asJsonSafe(value.model_dump());
|
|
2383
|
-
}
|
|
2384
|
-
if ("toJSON" in value && typeof value.toJSON === "function") {
|
|
2385
|
-
return asJsonSafe(value.toJSON());
|
|
2386
|
-
}
|
|
2387
|
-
return Object.fromEntries(
|
|
2388
|
-
Object.entries(value).map(([key, item]) => [key, asJsonSafe(item)])
|
|
2389
|
-
);
|
|
2390
|
-
}
|
|
2391
|
-
return String(value);
|
|
2392
|
-
}
|
|
2393
|
-
function normalizeMcpServers(mcpServers) {
|
|
2394
|
-
if (!mcpServers) {
|
|
2395
|
-
return [];
|
|
2396
|
-
}
|
|
2397
|
-
return mcpServers.map((server) => asJsonSafe(server)).filter((server) => !!server && typeof server === "object" && !Array.isArray(server));
|
|
2398
|
-
}
|
|
2399
|
-
|
|
2400
2343
|
// src/adapters/acp/client.ts
|
|
2401
2344
|
var BandACPClient = class {
|
|
2402
2345
|
sessionChunks = /* @__PURE__ */ new Map();
|
|
2403
2346
|
permissionHandler;
|
|
2347
|
+
extensionHandler;
|
|
2404
2348
|
// The handler is connection-scoped and required at construction, so it is
|
|
2405
2349
|
// already in place before the agent process is spawned: there is no window
|
|
2406
2350
|
// in which a `session/request_permission` has nowhere to go.
|
|
2407
|
-
constructor(permissionHandler) {
|
|
2351
|
+
constructor(permissionHandler, extensionHandler) {
|
|
2408
2352
|
this.permissionHandler = permissionHandler;
|
|
2353
|
+
this.extensionHandler = extensionHandler;
|
|
2409
2354
|
}
|
|
2410
2355
|
beginSession(sessionId) {
|
|
2411
2356
|
this.sessionChunks.set(sessionId, []);
|
|
@@ -2450,62 +2395,26 @@ var BandACPClient = class {
|
|
|
2450
2395
|
return chunks;
|
|
2451
2396
|
}
|
|
2452
2397
|
async extMethod(method, params) {
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
return {
|
|
2460
|
-
outcome: {
|
|
2461
|
-
type: "cancelled"
|
|
2462
|
-
}
|
|
2463
|
-
};
|
|
2464
|
-
}
|
|
2465
|
-
return {
|
|
2466
|
-
outcome: {
|
|
2467
|
-
type: "selected",
|
|
2468
|
-
optionId: selected.optionId
|
|
2469
|
-
}
|
|
2470
|
-
};
|
|
2471
|
-
}
|
|
2472
|
-
if (method === "cursor/create_plan") {
|
|
2473
|
-
return {
|
|
2474
|
-
outcome: {
|
|
2475
|
-
type: "approved"
|
|
2476
|
-
}
|
|
2477
|
-
};
|
|
2478
|
-
}
|
|
2479
|
-
return {};
|
|
2398
|
+
const result = await this.extensionHandler?.extMethod?.(
|
|
2399
|
+
method,
|
|
2400
|
+
params,
|
|
2401
|
+
{ sessionId: sessionIdFrom(params) }
|
|
2402
|
+
);
|
|
2403
|
+
return result ?? {};
|
|
2480
2404
|
}
|
|
2481
2405
|
async extNotification(method, params) {
|
|
2482
|
-
const sessionId =
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
this.appendChunk(sessionId, {
|
|
2491
|
-
chunkType: "plan",
|
|
2492
|
-
content: lines.join("\n"),
|
|
2493
|
-
metadata: {},
|
|
2494
|
-
streamed: false
|
|
2495
|
-
});
|
|
2496
|
-
}
|
|
2406
|
+
const sessionId = sessionIdFrom(params);
|
|
2407
|
+
const chunks = await this.extensionHandler?.extNotification?.(
|
|
2408
|
+
method,
|
|
2409
|
+
params,
|
|
2410
|
+
{ sessionId }
|
|
2411
|
+
);
|
|
2412
|
+
const targetSessionId = sessionId ?? this.extensionHandler?.extensionSessionId?.() ?? null;
|
|
2413
|
+
if (!targetSessionId || !chunks) {
|
|
2497
2414
|
return;
|
|
2498
2415
|
}
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
if (result) {
|
|
2502
|
-
this.appendChunk(sessionId, {
|
|
2503
|
-
chunkType: "text",
|
|
2504
|
-
content: `[Task completed] ${result}`,
|
|
2505
|
-
metadata: {},
|
|
2506
|
-
streamed: false
|
|
2507
|
-
});
|
|
2508
|
-
}
|
|
2416
|
+
for (const chunk of chunks) {
|
|
2417
|
+
this.appendChunk(targetSessionId, chunk);
|
|
2509
2418
|
}
|
|
2510
2419
|
}
|
|
2511
2420
|
appendChunk(sessionId, chunk) {
|
|
@@ -2630,6 +2539,68 @@ function extractTextFromContent(content) {
|
|
|
2630
2539
|
function toOptionalString(value) {
|
|
2631
2540
|
return typeof value === "string" && value.length > 0 ? value : null;
|
|
2632
2541
|
}
|
|
2542
|
+
function sessionIdFrom(params) {
|
|
2543
|
+
return toOptionalString(params.sessionId) ?? toOptionalString(params.session_id);
|
|
2544
|
+
}
|
|
2545
|
+
|
|
2546
|
+
// src/adapters/acp/types.ts
|
|
2547
|
+
var DEFAULT_ACP_SERVER_MODES = [
|
|
2548
|
+
{
|
|
2549
|
+
id: "default",
|
|
2550
|
+
name: "Default",
|
|
2551
|
+
description: "General-purpose chat mode"
|
|
2552
|
+
},
|
|
2553
|
+
{
|
|
2554
|
+
id: "code",
|
|
2555
|
+
name: "Code",
|
|
2556
|
+
description: "Route prompts toward coding peers when available"
|
|
2557
|
+
}
|
|
2558
|
+
];
|
|
2559
|
+
function createPendingPrompt(sessionId) {
|
|
2560
|
+
let markDone = () => void 0;
|
|
2561
|
+
const done = new Promise((resolve) => {
|
|
2562
|
+
markDone = resolve;
|
|
2563
|
+
});
|
|
2564
|
+
return {
|
|
2565
|
+
sessionId,
|
|
2566
|
+
done,
|
|
2567
|
+
markDone,
|
|
2568
|
+
terminalMessageSeen: false,
|
|
2569
|
+
completionTimer: null
|
|
2570
|
+
};
|
|
2571
|
+
}
|
|
2572
|
+
function choosePermissionOption(options) {
|
|
2573
|
+
if (options.length === 0) {
|
|
2574
|
+
return null;
|
|
2575
|
+
}
|
|
2576
|
+
return options.find((option) => option.kind === "allow_once") ?? options.find((option) => option.kind === "allow_always") ?? options[0];
|
|
2577
|
+
}
|
|
2578
|
+
function asJsonSafe(value) {
|
|
2579
|
+
if (value === null || value === void 0 || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
2580
|
+
return value;
|
|
2581
|
+
}
|
|
2582
|
+
if (Array.isArray(value)) {
|
|
2583
|
+
return value.map((item) => asJsonSafe(item));
|
|
2584
|
+
}
|
|
2585
|
+
if (typeof value === "object") {
|
|
2586
|
+
if ("model_dump" in value && typeof value.model_dump === "function") {
|
|
2587
|
+
return asJsonSafe(value.model_dump());
|
|
2588
|
+
}
|
|
2589
|
+
if ("toJSON" in value && typeof value.toJSON === "function") {
|
|
2590
|
+
return asJsonSafe(value.toJSON());
|
|
2591
|
+
}
|
|
2592
|
+
return Object.fromEntries(
|
|
2593
|
+
Object.entries(value).map(([key, item]) => [key, asJsonSafe(item)])
|
|
2594
|
+
);
|
|
2595
|
+
}
|
|
2596
|
+
return String(value);
|
|
2597
|
+
}
|
|
2598
|
+
function normalizeMcpServers(mcpServers) {
|
|
2599
|
+
if (!mcpServers) {
|
|
2600
|
+
return [];
|
|
2601
|
+
}
|
|
2602
|
+
return mcpServers.map((server) => asJsonSafe(server)).filter((server) => !!server && typeof server === "object" && !Array.isArray(server));
|
|
2603
|
+
}
|
|
2633
2604
|
|
|
2634
2605
|
// src/adapters/acp/loader.ts
|
|
2635
2606
|
init_errors();
|
|
@@ -2725,6 +2696,7 @@ var ACPClientAdapter = class extends SimpleAdapter {
|
|
|
2725
2696
|
additionalMcpTools;
|
|
2726
2697
|
clientCapabilities;
|
|
2727
2698
|
connectionFactory;
|
|
2699
|
+
extensionHandler;
|
|
2728
2700
|
tcpEndpoint;
|
|
2729
2701
|
// The value's `generation` is the connection generation the session was
|
|
2730
2702
|
// last established/restored against. `client` is the exact BandACPClient
|
|
@@ -2789,6 +2761,7 @@ var ACPClientAdapter = class extends SimpleAdapter {
|
|
|
2789
2761
|
this.additionalMcpTools = [...options.additionalMcpTools ?? []];
|
|
2790
2762
|
this.clientCapabilities = options.clientCapabilities;
|
|
2791
2763
|
this.connectionFactory = options.connectionFactory;
|
|
2764
|
+
this.extensionHandler = options.extensionHandler;
|
|
2792
2765
|
this.tcpEndpoint = tcpEndpoint;
|
|
2793
2766
|
this.resolvePermission = options.resolvePermission;
|
|
2794
2767
|
this.resolveSessionMode = options.resolveSessionMode;
|
|
@@ -2844,6 +2817,7 @@ var ACPClientAdapter = class extends SimpleAdapter {
|
|
|
2844
2817
|
let client = null;
|
|
2845
2818
|
let sessionId;
|
|
2846
2819
|
let generation = 0;
|
|
2820
|
+
await this.onAcpTurnStarted(message, tools, context);
|
|
2847
2821
|
try {
|
|
2848
2822
|
const ensured = await this.ensureConnection();
|
|
2849
2823
|
connection = ensured.connection;
|
|
@@ -2854,6 +2828,7 @@ var ACPClientAdapter = class extends SimpleAdapter {
|
|
|
2854
2828
|
}
|
|
2855
2829
|
sessionId = await this.getOrCreateSession(context.roomId, connection, generation, client);
|
|
2856
2830
|
const sessionKey = this.sessionKey(generation, sessionId);
|
|
2831
|
+
await this.onAcpSessionReady(message, tools, context, sessionId);
|
|
2857
2832
|
client.beginSession(sessionId);
|
|
2858
2833
|
const content = replaceUuidMentions(message.content, mentionSubjectsFromMetadata(message.metadata));
|
|
2859
2834
|
const messageWithContext = [...systemUpdateParts(participantsMessage, contactsMessage), content].join("\n\n");
|
|
@@ -2924,8 +2899,16 @@ ${messageWithContext}`;
|
|
|
2924
2899
|
...configError ? { optionId: configError.optionId, selectedValue: configError.selectedValue } : {}
|
|
2925
2900
|
}
|
|
2926
2901
|
);
|
|
2902
|
+
} finally {
|
|
2903
|
+
await this.onAcpTurnFinished(message, tools, context);
|
|
2927
2904
|
}
|
|
2928
2905
|
}
|
|
2906
|
+
async onAcpTurnStarted(_message, _tools, _context) {
|
|
2907
|
+
}
|
|
2908
|
+
async onAcpTurnFinished(_message, _tools, _context) {
|
|
2909
|
+
}
|
|
2910
|
+
async onAcpSessionReady(_message, _tools, _context, _sessionId) {
|
|
2911
|
+
}
|
|
2929
2912
|
// Best-effort: tells the agent to stop working on a turn Band has already
|
|
2930
2913
|
// given up waiting for (the ACP client has no way to force it), evicts the
|
|
2931
2914
|
// session so the room's next turn re-establishes rather than reuses it
|
|
@@ -3095,6 +3078,9 @@ ${messageWithContext}`;
|
|
|
3095
3078
|
sessionKey(generation, sessionId) {
|
|
3096
3079
|
return `${generation}:${sessionId}`;
|
|
3097
3080
|
}
|
|
3081
|
+
roomIdForSession(sessionId) {
|
|
3082
|
+
return [...this.roomToSession.entries()].find(([, owner]) => owner.sessionId === sessionId)?.[0];
|
|
3083
|
+
}
|
|
3098
3084
|
async ensureConnection() {
|
|
3099
3085
|
if (this.connection && !this.connection.signal.aborted) {
|
|
3100
3086
|
return { connection: this.connection, generation: this.connectionGeneration };
|
|
@@ -3131,7 +3117,10 @@ ${messageWithContext}`;
|
|
|
3131
3117
|
throw new Error(CONNECTION_ATTEMPT_SUPERSEDED_ERROR);
|
|
3132
3118
|
}
|
|
3133
3119
|
const owner = { generation: -1 };
|
|
3134
|
-
const client = new BandACPClient(
|
|
3120
|
+
const client = new BandACPClient(
|
|
3121
|
+
(params) => this.routePermissionRequest(params, owner.generation),
|
|
3122
|
+
this.extensionHandler
|
|
3123
|
+
);
|
|
3135
3124
|
handle = await (this.connectionFactory ? this.connectionFactory(client, {
|
|
3136
3125
|
command: this.command,
|
|
3137
3126
|
cwd: this.cwd,
|
|
@@ -4856,6 +4845,358 @@ var CopilotACPAdapter = class extends ACPClientAdapter {
|
|
|
4856
4845
|
}
|
|
4857
4846
|
};
|
|
4858
4847
|
|
|
4848
|
+
// src/adapters/cursor-acp/CursorACPAdapter.ts
|
|
4849
|
+
var DEFAULT_CURSOR_ACP_COMMAND = ["agent", "acp"];
|
|
4850
|
+
var DEFAULT_CURSOR_DECISION_TIMEOUT_MS = 3e5;
|
|
4851
|
+
var DEFAULT_CURSOR_MAX_PENDING_DECISIONS = 10;
|
|
4852
|
+
var CursorExtensions = class {
|
|
4853
|
+
adapter = null;
|
|
4854
|
+
todosBySession = /* @__PURE__ */ new Map();
|
|
4855
|
+
bind(adapter) {
|
|
4856
|
+
this.adapter = adapter;
|
|
4857
|
+
}
|
|
4858
|
+
async resolvePermission(request, signal) {
|
|
4859
|
+
return this.adapter?.resolveCursorPermission(request, signal);
|
|
4860
|
+
}
|
|
4861
|
+
extensionSessionId() {
|
|
4862
|
+
return this.adapter?.extensionSessionId() ?? null;
|
|
4863
|
+
}
|
|
4864
|
+
async extMethod(method, params, context) {
|
|
4865
|
+
return this.adapter?.resolveExtension(method, params, context.sessionId) ?? null;
|
|
4866
|
+
}
|
|
4867
|
+
async extNotification(method, params, context) {
|
|
4868
|
+
const sessionId = context.sessionId ?? this.extensionSessionId();
|
|
4869
|
+
if (!sessionId) {
|
|
4870
|
+
return;
|
|
4871
|
+
}
|
|
4872
|
+
if (method === "cursor/update_todos") {
|
|
4873
|
+
const content = this.updateTodos(sessionId, params);
|
|
4874
|
+
if (!content) {
|
|
4875
|
+
return;
|
|
4876
|
+
}
|
|
4877
|
+
return [{ chunkType: "plan", content, metadata: { cursor_todos: true }, streamed: false }];
|
|
4878
|
+
}
|
|
4879
|
+
if (method === "cursor/task") {
|
|
4880
|
+
const description = stringValue(params.description);
|
|
4881
|
+
if (!description) {
|
|
4882
|
+
return [];
|
|
4883
|
+
}
|
|
4884
|
+
const subagentType = stringValue(params.subagentType) ?? "unspecified";
|
|
4885
|
+
const model = stringValue(params.model);
|
|
4886
|
+
const suffix = model ? ` (${model})` : "";
|
|
4887
|
+
return [{ chunkType: "plan", content: `[Cursor ${subagentType} task] ${description}${suffix}`, metadata: {}, streamed: false }];
|
|
4888
|
+
}
|
|
4889
|
+
if (method === "cursor/generate_image") {
|
|
4890
|
+
const description = stringValue(params.description);
|
|
4891
|
+
if (!description) {
|
|
4892
|
+
return [];
|
|
4893
|
+
}
|
|
4894
|
+
const filePath = stringValue(params.filePath);
|
|
4895
|
+
return [{ chunkType: "plan", content: `[Cursor generated image] ${description}${filePath ? ` \u2192 ${filePath}` : ""}`, metadata: {}, streamed: false }];
|
|
4896
|
+
}
|
|
4897
|
+
}
|
|
4898
|
+
forgetSession(sessionId) {
|
|
4899
|
+
this.todosBySession.delete(sessionId);
|
|
4900
|
+
}
|
|
4901
|
+
clearSessions() {
|
|
4902
|
+
this.todosBySession.clear();
|
|
4903
|
+
}
|
|
4904
|
+
updateTodos(sessionId, params) {
|
|
4905
|
+
const todos = parseTodos(params.todos);
|
|
4906
|
+
if (params.merge === true) {
|
|
4907
|
+
const current2 = this.todosBySession.get(sessionId) ?? /* @__PURE__ */ new Map();
|
|
4908
|
+
for (const todo of todos) {
|
|
4909
|
+
current2.set(todo.id, todo);
|
|
4910
|
+
}
|
|
4911
|
+
this.todosBySession.set(sessionId, current2);
|
|
4912
|
+
} else {
|
|
4913
|
+
this.todosBySession.set(sessionId, new Map(todos.map((todo) => [todo.id, todo])));
|
|
4914
|
+
}
|
|
4915
|
+
const current = this.todosBySession.get(sessionId);
|
|
4916
|
+
return current && current.size > 0 ? [...current.values()].map((todo) => `- [${todoMark(todo.status)}] ${todo.content}`).join("\n") : void 0;
|
|
4917
|
+
}
|
|
4918
|
+
};
|
|
4919
|
+
var CursorACPAdapter = class extends ACPClientAdapter {
|
|
4920
|
+
provider = "cursor-acp";
|
|
4921
|
+
approvalMode;
|
|
4922
|
+
questionMode;
|
|
4923
|
+
planMode;
|
|
4924
|
+
decisionTimeoutMs;
|
|
4925
|
+
maxPendingDecisions;
|
|
4926
|
+
authorizedSenders;
|
|
4927
|
+
decisionLogger;
|
|
4928
|
+
extensions;
|
|
4929
|
+
turns = /* @__PURE__ */ new Map();
|
|
4930
|
+
pending = /* @__PURE__ */ new Map();
|
|
4931
|
+
activeTurn = null;
|
|
4932
|
+
turnTail = Promise.resolve();
|
|
4933
|
+
constructor(options = {}) {
|
|
4934
|
+
const extensions = new CursorExtensions();
|
|
4935
|
+
validateOptions(options);
|
|
4936
|
+
const env = cursorEnv(options);
|
|
4937
|
+
super({
|
|
4938
|
+
...options,
|
|
4939
|
+
env,
|
|
4940
|
+
command: options.command ?? [...DEFAULT_CURSOR_ACP_COMMAND],
|
|
4941
|
+
authMethod: "cursor_login",
|
|
4942
|
+
extensionHandler: extensions,
|
|
4943
|
+
resolvePermission: (request, signal) => extensions.resolvePermission(request, signal)
|
|
4944
|
+
});
|
|
4945
|
+
extensions.bind(this);
|
|
4946
|
+
this.extensions = extensions;
|
|
4947
|
+
this.approvalMode = options.approvalMode ?? "manual";
|
|
4948
|
+
this.questionMode = options.questionMode ?? "manual";
|
|
4949
|
+
this.planMode = options.planMode ?? "manual";
|
|
4950
|
+
this.decisionTimeoutMs = options.decisionTimeoutMs ?? DEFAULT_CURSOR_DECISION_TIMEOUT_MS;
|
|
4951
|
+
this.maxPendingDecisions = options.maxPendingDecisions ?? DEFAULT_CURSOR_MAX_PENDING_DECISIONS;
|
|
4952
|
+
this.authorizedSenders = options.decisionAuthorizedSenders ? new Set(options.decisionAuthorizedSenders) : null;
|
|
4953
|
+
this.decisionLogger = resolveLogger(options.logger);
|
|
4954
|
+
}
|
|
4955
|
+
async onMessage(message, tools, history, participantsMessage, contactsMessage, context) {
|
|
4956
|
+
if (await this.handleControl(message, tools, context.roomId)) {
|
|
4957
|
+
return;
|
|
4958
|
+
}
|
|
4959
|
+
await this.withCursorTurnLock(() => super.onMessage(message, tools, history, participantsMessage, contactsMessage, context));
|
|
4960
|
+
}
|
|
4961
|
+
async onAcpTurnStarted(message, tools, context) {
|
|
4962
|
+
const turn = { messageId: message.id, roomId: context.roomId, tools, requesterId: message.senderId };
|
|
4963
|
+
this.turns.set(context.roomId, turn);
|
|
4964
|
+
this.activeTurn = turn;
|
|
4965
|
+
}
|
|
4966
|
+
async onAcpSessionReady(message, _tools, context, sessionId) {
|
|
4967
|
+
const turn = this.turns.get(context.roomId);
|
|
4968
|
+
if (turn?.messageId === message.id) {
|
|
4969
|
+
turn.sessionId = sessionId;
|
|
4970
|
+
}
|
|
4971
|
+
}
|
|
4972
|
+
async onAcpTurnFinished(message, _tools, context) {
|
|
4973
|
+
const turn = this.turns.get(context.roomId);
|
|
4974
|
+
if (turn?.messageId === message.id) {
|
|
4975
|
+
this.turns.delete(context.roomId);
|
|
4976
|
+
this.cancelRoom(context.roomId);
|
|
4977
|
+
}
|
|
4978
|
+
if (this.activeTurn?.messageId === message.id) {
|
|
4979
|
+
this.activeTurn = null;
|
|
4980
|
+
}
|
|
4981
|
+
}
|
|
4982
|
+
async onCleanup(roomId) {
|
|
4983
|
+
const sessionId = this.turns.get(roomId)?.sessionId;
|
|
4984
|
+
this.cancelRoom(roomId);
|
|
4985
|
+
this.turns.delete(roomId);
|
|
4986
|
+
if (this.activeTurn?.roomId === roomId) {
|
|
4987
|
+
this.activeTurn = null;
|
|
4988
|
+
}
|
|
4989
|
+
await super.onCleanup(roomId);
|
|
4990
|
+
if (sessionId) {
|
|
4991
|
+
this.extensions.forgetSession(sessionId);
|
|
4992
|
+
}
|
|
4993
|
+
}
|
|
4994
|
+
async stop() {
|
|
4995
|
+
for (const decision of this.pending.values()) {
|
|
4996
|
+
decision.resolve(void 0);
|
|
4997
|
+
}
|
|
4998
|
+
this.pending.clear();
|
|
4999
|
+
this.turns.clear();
|
|
5000
|
+
this.activeTurn = null;
|
|
5001
|
+
this.extensions.clearSessions();
|
|
5002
|
+
await super.stop();
|
|
5003
|
+
}
|
|
5004
|
+
async resolveExtension(method, params, sessionId) {
|
|
5005
|
+
const roomId = sessionId ? this.roomIdForSession(sessionId) : this.activeTurn?.roomId;
|
|
5006
|
+
const turn = roomId ? this.turns.get(roomId) : void 0;
|
|
5007
|
+
if (!turn || sessionId && turn.sessionId !== sessionId) {
|
|
5008
|
+
return { outcome: { outcome: "cancelled" } };
|
|
5009
|
+
}
|
|
5010
|
+
if (method === "cursor/ask_question") {
|
|
5011
|
+
return this.resolveQuestion(turn.roomId, turn, params);
|
|
5012
|
+
}
|
|
5013
|
+
if (method === "cursor/create_plan") {
|
|
5014
|
+
return this.resolvePlan(turn.roomId, turn, params);
|
|
5015
|
+
}
|
|
5016
|
+
return {};
|
|
5017
|
+
}
|
|
5018
|
+
async resolveCursorPermission(request, signal) {
|
|
5019
|
+
if (this.approvalMode === "autoAccept") {
|
|
5020
|
+
return allowOption(request.options)?.optionId;
|
|
5021
|
+
}
|
|
5022
|
+
if (this.approvalMode === "autoDecline") {
|
|
5023
|
+
return void 0;
|
|
5024
|
+
}
|
|
5025
|
+
const turn = this.turns.get(request.roomId);
|
|
5026
|
+
if (!turn) {
|
|
5027
|
+
return void 0;
|
|
5028
|
+
}
|
|
5029
|
+
const options = request.options.map((option) => option.optionId);
|
|
5030
|
+
const token = await this.waitForDecision("permission", request.roomId, turn, /* @__PURE__ */ new Map([["permission", options]]), /* @__PURE__ */ new Set(), `Cursor needs permission. Reply \`/cursor select {token} option-id\` or \`/cursor deny {token}\`.`, signal);
|
|
5031
|
+
return typeof token === "string" && options.includes(token) ? token : void 0;
|
|
5032
|
+
}
|
|
5033
|
+
extensionSessionId() {
|
|
5034
|
+
return this.activeTurn?.sessionId ?? null;
|
|
5035
|
+
}
|
|
5036
|
+
async resolveQuestion(roomId, turn, params) {
|
|
5037
|
+
const questions = questionChoices(params.questions);
|
|
5038
|
+
if (questions.choices.size === 0) {
|
|
5039
|
+
return { outcome: { outcome: "cancelled" } };
|
|
5040
|
+
}
|
|
5041
|
+
if (this.questionMode === "autoCancel") {
|
|
5042
|
+
return { outcome: { outcome: "cancelled" } };
|
|
5043
|
+
}
|
|
5044
|
+
if (this.questionMode === "autoFirst") {
|
|
5045
|
+
return answered(Object.fromEntries([...questions.choices].map(([id, options]) => [id, [options[0]]])));
|
|
5046
|
+
}
|
|
5047
|
+
const result = await this.waitForDecision("question", roomId, turn, questions.choices, questions.multiSelect, "Cursor needs input. Reply `/cursor answer {token} question-id=option-id[,option-id] ...`.");
|
|
5048
|
+
return isRecord(result) ? result : { outcome: { outcome: "cancelled" } };
|
|
5049
|
+
}
|
|
5050
|
+
async resolvePlan(roomId, turn, params) {
|
|
5051
|
+
if (this.planMode === "autoAccept") {
|
|
5052
|
+
return { outcome: { outcome: "accepted" } };
|
|
5053
|
+
}
|
|
5054
|
+
if (this.planMode === "autoDecline") {
|
|
5055
|
+
return { outcome: { outcome: "rejected" } };
|
|
5056
|
+
}
|
|
5057
|
+
const title = stringValue(params.title) ?? "Cursor plan";
|
|
5058
|
+
const result = await this.waitForDecision("plan", roomId, turn, /* @__PURE__ */ new Map(), /* @__PURE__ */ new Set(), `${title} needs approval. Reply \`/cursor accept {token}\` or \`/cursor reject {token}\`.`);
|
|
5059
|
+
return isRecord(result) ? result : { outcome: { outcome: "cancelled" } };
|
|
5060
|
+
}
|
|
5061
|
+
async waitForDecision(kind, roomId, turn, choices, multiSelect, prompt, signal) {
|
|
5062
|
+
if (this.pending.size >= this.maxPendingDecisions) {
|
|
5063
|
+
this.pending.values().next().value?.resolve(void 0);
|
|
5064
|
+
}
|
|
5065
|
+
const token = crypto.randomUUID().slice(0, 8);
|
|
5066
|
+
return new Promise((resolve) => {
|
|
5067
|
+
const timer = setTimeout(() => settle(void 0), this.decisionTimeoutMs);
|
|
5068
|
+
const abort = () => settle(void 0);
|
|
5069
|
+
const settle = (value) => {
|
|
5070
|
+
clearTimeout(timer);
|
|
5071
|
+
signal?.removeEventListener("abort", abort);
|
|
5072
|
+
this.pending.delete(token);
|
|
5073
|
+
resolve(value);
|
|
5074
|
+
};
|
|
5075
|
+
this.pending.set(token, { kind, roomId, choices, multiSelect, resolve: settle });
|
|
5076
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
5077
|
+
void turn.tools.sendMessage(prompt.replaceAll("{token}", token), [turn.requesterId]).catch((error) => {
|
|
5078
|
+
this.decisionLogger.warn("cursor_acp.decision_prompt_delivery_failed", { roomId, kind, error: String(error) });
|
|
5079
|
+
settle(void 0);
|
|
5080
|
+
});
|
|
5081
|
+
});
|
|
5082
|
+
}
|
|
5083
|
+
async handleControl(message, tools, roomId) {
|
|
5084
|
+
const words = message.content.trim().split(/\s+/);
|
|
5085
|
+
if (words[0]?.toLowerCase() !== "/cursor") {
|
|
5086
|
+
return false;
|
|
5087
|
+
}
|
|
5088
|
+
if (words.length === 1 || words[1]?.toLowerCase() === "decisions") {
|
|
5089
|
+
const entries = [...this.pending.entries()].filter(([, decision2]) => decision2.roomId === roomId).map(([token2, decision2]) => `\`${token2}\` (${decision2.kind})`);
|
|
5090
|
+
await tools.sendMessage(`Pending Cursor decisions: ${entries.join(", ") || "none"}`);
|
|
5091
|
+
return true;
|
|
5092
|
+
}
|
|
5093
|
+
const [_, action, token, ...args] = words;
|
|
5094
|
+
const decision = token ? this.pending.get(token) : void 0;
|
|
5095
|
+
if (!decision || decision.roomId !== roomId) {
|
|
5096
|
+
await tools.sendMessage(`Cursor decision \`${token ?? ""}\` is not pending.`);
|
|
5097
|
+
return true;
|
|
5098
|
+
}
|
|
5099
|
+
if (this.authorizedSenders && !this.authorizedSenders.has(message.senderId)) {
|
|
5100
|
+
await tools.sendMessage("You are not authorized to resolve Cursor decisions.");
|
|
5101
|
+
return true;
|
|
5102
|
+
}
|
|
5103
|
+
const result = commandResult(action ?? "", args, decision);
|
|
5104
|
+
if (result === null) {
|
|
5105
|
+
await tools.sendMessage(`That command is not valid for Cursor ${decision.kind} decision \`${token}\`.`);
|
|
5106
|
+
return true;
|
|
5107
|
+
}
|
|
5108
|
+
decision.resolve(result);
|
|
5109
|
+
await tools.sendMessage(`Cursor ${decision.kind} decision \`${token}\` resolved.`);
|
|
5110
|
+
return true;
|
|
5111
|
+
}
|
|
5112
|
+
cancelRoom(roomId) {
|
|
5113
|
+
for (const [token, decision] of this.pending) {
|
|
5114
|
+
if (decision.roomId === roomId) {
|
|
5115
|
+
decision.resolve(void 0);
|
|
5116
|
+
this.pending.delete(token);
|
|
5117
|
+
}
|
|
5118
|
+
}
|
|
5119
|
+
}
|
|
5120
|
+
async withCursorTurnLock(run) {
|
|
5121
|
+
const queued = this.turnTail.then(run, run);
|
|
5122
|
+
this.turnTail = queued.then(() => void 0, () => void 0);
|
|
5123
|
+
return queued;
|
|
5124
|
+
}
|
|
5125
|
+
};
|
|
5126
|
+
function cursorEnv(options) {
|
|
5127
|
+
const env = { ...options.env };
|
|
5128
|
+
if (options.apiKey) env.CURSOR_API_KEY ??= options.apiKey;
|
|
5129
|
+
if (options.authToken) env.CURSOR_AUTH_TOKEN ??= options.authToken;
|
|
5130
|
+
return Object.keys(env).length > 0 ? env : void 0;
|
|
5131
|
+
}
|
|
5132
|
+
function validateOptions(options) {
|
|
5133
|
+
if (options.apiKey && options.authToken) throw new Error("set either apiKey or authToken, not both");
|
|
5134
|
+
if (Array.isArray(options.command) && options.command.length === 0) throw new Error("Cursor ACP command must not be empty");
|
|
5135
|
+
if (options.decisionTimeoutMs !== void 0 && (!Number.isFinite(options.decisionTimeoutMs) || options.decisionTimeoutMs <= 0)) throw new Error("decisionTimeoutMs must be a positive finite number");
|
|
5136
|
+
if (options.maxPendingDecisions !== void 0 && (!Number.isInteger(options.maxPendingDecisions) || options.maxPendingDecisions <= 0)) throw new Error("maxPendingDecisions must be a positive integer");
|
|
5137
|
+
}
|
|
5138
|
+
function allowOption(options) {
|
|
5139
|
+
return options.find((option) => option.kind === "allow_once") ?? options.find((option) => option.kind === "allow_always");
|
|
5140
|
+
}
|
|
5141
|
+
function questionChoices(value) {
|
|
5142
|
+
const choices = /* @__PURE__ */ new Map();
|
|
5143
|
+
const multiSelect = /* @__PURE__ */ new Set();
|
|
5144
|
+
if (!Array.isArray(value)) return { choices, multiSelect };
|
|
5145
|
+
for (const question of value) {
|
|
5146
|
+
if (!isRecord(question) || typeof question.id !== "string" || !Array.isArray(question.options)) continue;
|
|
5147
|
+
const options = question.options.filter(isRecord).map((option) => stringValue(option.id)).filter((id) => !!id);
|
|
5148
|
+
if (options.length === 0) continue;
|
|
5149
|
+
choices.set(question.id, options);
|
|
5150
|
+
if (question.allowMultiple === true) multiSelect.add(question.id);
|
|
5151
|
+
}
|
|
5152
|
+
return { choices, multiSelect };
|
|
5153
|
+
}
|
|
5154
|
+
function commandResult(action, args, decision) {
|
|
5155
|
+
if (decision.kind === "permission") return action === "deny" ? void 0 : action === "select" && args.length === 1 && decision.choices.get("permission")?.includes(args[0] ?? "") ? args[0] : null;
|
|
5156
|
+
if (decision.kind === "plan") return action === "accept" ? { outcome: { outcome: "accepted" } } : action === "reject" ? { outcome: { outcome: "rejected" } } : null;
|
|
5157
|
+
if (action !== "answer") return null;
|
|
5158
|
+
const selected = {};
|
|
5159
|
+
for (const argument of args) {
|
|
5160
|
+
const [id, raw] = argument.split("=", 2);
|
|
5161
|
+
const values = raw?.split(",") ?? [];
|
|
5162
|
+
const offered = id ? decision.choices.get(id) : void 0;
|
|
5163
|
+
if (!id || !offered || selected[id] || values.length === 0 || values.length > 1 && !decision.multiSelect.has(id) || values.some((value) => !offered.includes(value))) return null;
|
|
5164
|
+
selected[id] = values;
|
|
5165
|
+
}
|
|
5166
|
+
return Object.keys(selected).length === decision.choices.size ? answered(selected) : null;
|
|
5167
|
+
}
|
|
5168
|
+
function answered(selected) {
|
|
5169
|
+
return { outcome: { outcome: "answered", answers: Object.entries(selected).map(([questionId, selectedOptionIds]) => ({ questionId, selectedOptionIds })) } };
|
|
5170
|
+
}
|
|
5171
|
+
function parseTodos(value) {
|
|
5172
|
+
if (!Array.isArray(value)) return [];
|
|
5173
|
+
return value.flatMap((todo) => {
|
|
5174
|
+
if (!isRecord(todo)) return [];
|
|
5175
|
+
const id = stringValue(todo.id);
|
|
5176
|
+
const content = stringValue(todo.content);
|
|
5177
|
+
const status = stringValue(todo.status);
|
|
5178
|
+
return id && content && status ? [{ id, content, status }] : [];
|
|
5179
|
+
});
|
|
5180
|
+
}
|
|
5181
|
+
function todoMark(status) {
|
|
5182
|
+
switch (status) {
|
|
5183
|
+
case "completed":
|
|
5184
|
+
return "x";
|
|
5185
|
+
case "in_progress":
|
|
5186
|
+
return "~";
|
|
5187
|
+
case "cancelled":
|
|
5188
|
+
return "-";
|
|
5189
|
+
default:
|
|
5190
|
+
return " ";
|
|
5191
|
+
}
|
|
5192
|
+
}
|
|
5193
|
+
function isRecord(value) {
|
|
5194
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
5195
|
+
}
|
|
5196
|
+
function stringValue(value) {
|
|
5197
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
5198
|
+
}
|
|
5199
|
+
|
|
4859
5200
|
// src/adapters/tool-calling/ToolCallingAdapter.ts
|
|
4860
5201
|
init_protocols();
|
|
4861
5202
|
|
|
@@ -7283,20 +7624,20 @@ function unwrapResult(value, depth = 0) {
|
|
|
7283
7624
|
}
|
|
7284
7625
|
return event;
|
|
7285
7626
|
}
|
|
7286
|
-
function
|
|
7627
|
+
function isRecord2(value) {
|
|
7287
7628
|
return typeof value === "object" && value !== null;
|
|
7288
7629
|
}
|
|
7289
7630
|
function isOptionalString(value) {
|
|
7290
7631
|
return value === void 0 || typeof value === "string";
|
|
7291
7632
|
}
|
|
7292
7633
|
function isMessagePart(value) {
|
|
7293
|
-
if (!
|
|
7634
|
+
if (!isRecord2(value)) {
|
|
7294
7635
|
return false;
|
|
7295
7636
|
}
|
|
7296
7637
|
return isOptionalString(value.kind) && isOptionalString(value.type) && isOptionalString(value.text) && (value.root === void 0 || isMessagePart(value.root));
|
|
7297
7638
|
}
|
|
7298
7639
|
function isMessageLike(value) {
|
|
7299
|
-
if (!
|
|
7640
|
+
if (!isRecord2(value)) {
|
|
7300
7641
|
return false;
|
|
7301
7642
|
}
|
|
7302
7643
|
if (!isOptionalString(value.kind) || !isOptionalString(value.role)) {
|
|
@@ -7311,7 +7652,7 @@ function isMessageLike(value) {
|
|
|
7311
7652
|
return Array.isArray(value.parts) && value.parts.every((part) => isMessagePart(part));
|
|
7312
7653
|
}
|
|
7313
7654
|
function isStatusLike(value) {
|
|
7314
|
-
if (!
|
|
7655
|
+
if (!isRecord2(value)) {
|
|
7315
7656
|
return false;
|
|
7316
7657
|
}
|
|
7317
7658
|
if (!isOptionalString(value.state)) {
|
|
@@ -7320,7 +7661,7 @@ function isStatusLike(value) {
|
|
|
7320
7661
|
return value.message === void 0 || isMessageLike(value.message);
|
|
7321
7662
|
}
|
|
7322
7663
|
function isArtifactLike(value) {
|
|
7323
|
-
if (!
|
|
7664
|
+
if (!isRecord2(value)) {
|
|
7324
7665
|
return false;
|
|
7325
7666
|
}
|
|
7326
7667
|
if (value.parts === void 0) {
|
|
@@ -7329,19 +7670,19 @@ function isArtifactLike(value) {
|
|
|
7329
7670
|
return Array.isArray(value.parts) && value.parts.every((part) => isMessagePart(part));
|
|
7330
7671
|
}
|
|
7331
7672
|
function isMessageEvent(event) {
|
|
7332
|
-
return
|
|
7673
|
+
return isRecord2(event) && event.kind === "message" && isMessageLike(event) && Array.isArray(event.parts);
|
|
7333
7674
|
}
|
|
7334
7675
|
function isTaskEvent(event) {
|
|
7335
|
-
if (!
|
|
7676
|
+
if (!isRecord2(event) || event.kind !== "task") {
|
|
7336
7677
|
return false;
|
|
7337
7678
|
}
|
|
7338
7679
|
return typeof event.id === "string" && isOptionalString(event.contextId) && isStatusLike(event.status) && (event.artifacts === void 0 || Array.isArray(event.artifacts) && event.artifacts.every((item) => isArtifactLike(item))) && (event.history === void 0 || Array.isArray(event.history) && event.history.every((item) => isMessageLike(item)));
|
|
7339
7680
|
}
|
|
7340
7681
|
function isStatusUpdateEvent(event) {
|
|
7341
|
-
return
|
|
7682
|
+
return isRecord2(event) && event.kind === "status-update" && typeof event.taskId === "string" && typeof event.contextId === "string" && isStatusLike(event.status);
|
|
7342
7683
|
}
|
|
7343
7684
|
function isArtifactUpdateEvent(event) {
|
|
7344
|
-
return
|
|
7685
|
+
return isRecord2(event) && event.kind === "artifact-update" && typeof event.taskId === "string" && typeof event.contextId === "string" && isArtifactLike(event.artifact);
|
|
7345
7686
|
}
|
|
7346
7687
|
async function loadDefaultA2AClientFactory() {
|
|
7347
7688
|
let module2;
|
|
@@ -9981,7 +10322,7 @@ var SdkOpencodeClientBase = class {
|
|
|
9981
10322
|
{ signal: this.eventsAbortController.signal }
|
|
9982
10323
|
);
|
|
9983
10324
|
for await (const event of events.stream) {
|
|
9984
|
-
if (
|
|
10325
|
+
if (isRecord3(event)) {
|
|
9985
10326
|
yield event;
|
|
9986
10327
|
}
|
|
9987
10328
|
}
|
|
@@ -10099,7 +10440,7 @@ function optionalString(value) {
|
|
|
10099
10440
|
const trimmed = value.trim();
|
|
10100
10441
|
return trimmed.length > 0 ? trimmed : null;
|
|
10101
10442
|
}
|
|
10102
|
-
function
|
|
10443
|
+
function isRecord3(value) {
|
|
10103
10444
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
10104
10445
|
}
|
|
10105
10446
|
function getResultBody(result) {
|
|
@@ -10109,7 +10450,7 @@ function expectRecord(result) {
|
|
|
10109
10450
|
if (!result.response.ok) {
|
|
10110
10451
|
throw new HttpStatusError(result.response.status, getResultBody(result));
|
|
10111
10452
|
}
|
|
10112
|
-
return
|
|
10453
|
+
return isRecord3(result.data) ? result.data : {};
|
|
10113
10454
|
}
|
|
10114
10455
|
async function expectVoid(resultPromise) {
|
|
10115
10456
|
const result = await resultPromise;
|
|
@@ -13057,7 +13398,9 @@ function parseModelListResponse(value) {
|
|
|
13057
13398
|
CodexAppServerStdioClient,
|
|
13058
13399
|
CodexJsonRpcError,
|
|
13059
13400
|
CopilotACPAdapter,
|
|
13401
|
+
CursorACPAdapter,
|
|
13060
13402
|
DEFAULT_COPILOT_ACP_COMMAND,
|
|
13403
|
+
DEFAULT_CURSOR_ACP_COMMAND,
|
|
13061
13404
|
DEFAULT_OMP_ACP_COMMAND,
|
|
13062
13405
|
FAILURE_CODE_SESSION_CONFIG,
|
|
13063
13406
|
GatewayHistoryConverter,
|