@stablekernel/opencode-cursor 0.4.7-next.0 → 0.5.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,270 +16,10 @@ function fingerprintApiKey(apiKey) {
16
16
  return createHash("sha256").update(apiKey).digest("hex").slice(0, 16);
17
17
  }
18
18
 
19
- // src/provider/system-rule.ts
20
- import {
21
- mkdirSync,
22
- writeFileSync,
23
- readFileSync,
24
- existsSync,
25
- rmSync
26
- } from "fs";
27
- import { join } from "path";
28
- var RULES_DIR = join(".cursor", "rules");
29
- var RULE_FILE = "opencode.mdc";
30
- var IGNORE_FILE = ".gitignore";
31
- var SENTINEL = "generated: opencode-cursor";
32
- function extractSystemText(prompt) {
33
- const parts = [];
34
- for (const message of prompt) {
35
- if (message.role === "system") parts.push(message.content);
36
- }
37
- return parts.join("\n\n").trim();
38
- }
39
- function isGenerated(content) {
40
- if (!content.startsWith("---")) return false;
41
- const end = content.indexOf("\n---", 3);
42
- const frontmatter = end === -1 ? content : content.slice(0, end);
43
- return frontmatter.split(/\r?\n/).includes(SENTINEL);
44
- }
45
- function writeSystemRule(cwd, systemText) {
46
- if (!systemText) return "empty";
47
- const dir = join(cwd, RULES_DIR);
48
- const path = join(dir, RULE_FILE);
49
- const body = `---
50
- alwaysApply: true
51
- ${SENTINEL}
52
- ---
53
-
54
- ${systemText}
55
- `;
56
- const existing = existsSync(path) ? readFileSync(path, "utf8") : void 0;
57
- if (existing !== void 0) {
58
- if (!isGenerated(existing)) return "blocked";
59
- if (existing === body) return "unchanged";
60
- }
61
- mkdirSync(dir, { recursive: true });
62
- writeFileSync(path, body, "utf8");
63
- ensureGitIgnored(dir);
64
- return "written";
65
- }
66
- function ensureGitIgnored(dir) {
67
- const path = join(dir, IGNORE_FILE);
68
- const existing = existsSync(path) ? readFileSync(path, "utf8") : "";
69
- const lines = existing.split(/\r?\n/);
70
- const missing = [RULE_FILE, IGNORE_FILE].filter(
71
- (entry) => !lines.includes(entry)
72
- );
73
- if (missing.length === 0) return;
74
- const prefix = existing && !existing.endsWith("\n") ? `${existing}
75
- ` : existing;
76
- writeFileSync(path, `${prefix}${missing.join("\n")}
77
- `, "utf8");
78
- }
79
- function removeSystemRule(cwd) {
80
- try {
81
- const path = join(cwd, RULES_DIR, RULE_FILE);
82
- if (isGenerated(readFileSync(path, "utf8"))) rmSync(path);
83
- } catch {
84
- }
85
- }
86
- function resolveSystemDelivery(options) {
87
- const { mode, settingSources, cwd, systemText, warn } = options;
88
- if (mode !== "rules") return { mode, settingSources };
89
- if (settingSources && !settingSources.includes("project")) {
90
- warn(
91
- 'systemPrompt "rules" needs the "project" settings layer, but settingSources was explicitly configured without it; delivering the system prompt inline ("message" mode) instead. Add "project" to settingSources or set systemPrompt: "message" to silence this.'
92
- );
93
- return { mode: "message", settingSources };
94
- }
95
- let result;
96
- try {
97
- result = writeSystemRule(cwd, systemText);
98
- } catch (error) {
99
- warn(
100
- `failed to write .cursor/rules/${RULE_FILE} (${error instanceof Error ? error.message : String(error)}); delivering the system prompt inline ("message" mode) for this turn.`
101
- );
102
- return { mode: "message", settingSources };
103
- }
104
- if (result === "blocked") {
105
- warn(
106
- `.cursor/rules/${RULE_FILE} exists but was not generated by opencode-cursor; leaving it untouched and delivering the system prompt inline ("message" mode).`
107
- );
108
- return { mode: "message", settingSources };
109
- }
110
- if (result === "empty") return { mode, settingSources };
111
- return { mode, settingSources: settingSources ?? ["project"] };
112
- }
113
-
114
- // src/provider/agent-events.ts
115
- function toolDisplayName(toolCall) {
116
- if (!toolCall) return "tool";
117
- if (toolCall.type === "mcp") {
118
- const name = toolCall.args?.toolName;
119
- const server = toolCall.args?.providerIdentifier;
120
- if (name) return server ? `${server}/${name}` : String(name);
121
- return "mcp";
122
- }
123
- return toolCall.type ?? "tool";
124
- }
125
- async function* streamAgentTurn(agent, message, options) {
126
- const queue = [];
127
- let wake;
128
- let finished = false;
129
- let failure;
130
- const debug = process.env.OPENCODE_CURSOR_DEBUG === "1";
131
- const counts = {};
132
- const push = (event) => {
133
- queue.push(event);
134
- wake?.();
135
- wake = void 0;
136
- };
137
- const onDelta = ({ update }) => {
138
- if (debug) counts[update.type] = (counts[update.type] ?? 0) + 1;
139
- switch (update.type) {
140
- case "text-delta":
141
- push({ type: "text-delta", text: update.text });
142
- break;
143
- case "thinking-delta":
144
- push({ type: "reasoning-delta", text: update.text });
145
- break;
146
- case "tool-call-started":
147
- push({
148
- type: "tool-call",
149
- id: String(update.callId),
150
- name: toolDisplayName(update.toolCall),
151
- input: update.toolCall?.args ?? {}
152
- });
153
- break;
154
- case "tool-call-completed": {
155
- const tool = update.toolCall ?? {};
156
- const result = tool.result;
157
- const mcpError = tool.type === "mcp" && result?.value?.isError === true;
158
- push({
159
- type: "tool-result",
160
- id: String(update.callId),
161
- name: toolDisplayName(tool),
162
- result: result ?? null,
163
- isError: result?.status === "error" || mcpError
164
- });
165
- break;
166
- }
167
- case "turn-ended":
168
- if (update.usage) push({ type: "usage", usage: update.usage });
169
- break;
170
- }
171
- };
172
- const runHolder = {};
173
- const onAbort = () => {
174
- void Promise.resolve(runHolder.run?.cancel()).catch(() => {
175
- });
176
- };
177
- options.abortSignal?.addEventListener("abort", onAbort);
178
- const sendTurn = () => sendWithBusyRetry(agent, message, { mode: options.mode, onDelta }, debug);
179
- void sendTurn().then(async (run) => {
180
- runHolder.run = run;
181
- const result = await run.wait();
182
- if (debug) {
183
- console.error(
184
- `[cursor:debug] updates=${JSON.stringify(counts)} status=${result.status} resultLen=${(result.result ?? "").length}`
185
- );
186
- }
187
- if (result.status === "error") {
188
- throw new Error(
189
- `Cursor run ended with status "error"${result.result ? `: ${result.result}` : ""}`
190
- );
191
- }
192
- push({ type: "finish", ...result.status === "cancelled" ? {} : { text: result.result } });
193
- }).catch((err) => {
194
- failure = err;
195
- if (debug) console.error(`[cursor:debug] send failed: ${err instanceof Error ? err.message : String(err)}`);
196
- }).finally(() => {
197
- finished = true;
198
- wake?.();
199
- wake = void 0;
200
- });
201
- try {
202
- while (true) {
203
- if (queue.length > 0) {
204
- yield queue.shift();
205
- continue;
206
- }
207
- if (finished) break;
208
- await new Promise((resolve) => {
209
- wake = resolve;
210
- });
211
- }
212
- while (queue.length > 0) yield queue.shift();
213
- if (failure) throw failure;
214
- } finally {
215
- options.abortSignal?.removeEventListener("abort", onAbort);
216
- }
217
- }
218
- async function sendWithBusyRetry(agent, message, sendOptions, debug) {
219
- try {
220
- return await agent.send(message, sendOptions);
221
- } catch (err) {
222
- if (err instanceof Error && err.name === "AgentBusyError") {
223
- if (debug) console.error("[cursor:debug] agent busy; retrying send with local.force");
224
- return agent.send(message, { ...sendOptions, local: { force: true } });
225
- }
226
- throw err;
227
- }
228
- }
229
- async function sendAgentTurnSilently(agent, message, options) {
230
- if (options.abortSignal?.aborted) return;
231
- const debug = process.env.OPENCODE_CURSOR_DEBUG === "1";
232
- const runHolder = {};
233
- const onAbort = () => {
234
- void Promise.resolve(runHolder.run?.cancel()).catch(() => {
235
- });
236
- };
237
- options.abortSignal?.addEventListener("abort", onAbort);
238
- try {
239
- const run = await sendWithBusyRetry(agent, message, { mode: options.mode }, debug);
240
- runHolder.run = run;
241
- if (options.abortSignal?.aborted) void Promise.resolve(run.cancel()).catch(() => {
242
- });
243
- const result = await run.wait();
244
- if (result.status !== "finished") {
245
- if (options.abortSignal?.aborted) return;
246
- throw new Error(
247
- `Cursor run ended with status "${result.status}"${result.result ? `: ${result.result}` : ""}`
248
- );
249
- }
250
- } finally {
251
- options.abortSignal?.removeEventListener("abort", onAbort);
252
- }
253
- }
254
-
255
- // src/provider/controls.ts
256
- function buildModelSelection(modelId, params) {
257
- const paramList = Object.entries(params ?? {}).map(([id, value]) => ({ id, value }));
258
- return paramList.length > 0 ? { id: modelId, params: paramList } : { id: modelId };
259
- }
260
- function isRecord(value) {
261
- return typeof value === "object" && value !== null && !Array.isArray(value);
262
- }
263
- function isMode(value) {
264
- return value === "agent" || value === "plan";
265
- }
266
- function resolveControls(modelId, staticControls, providerOptions) {
267
- const po = providerOptions ?? {};
268
- const mode = isMode(po["mode"]) ? po["mode"] : staticControls.mode;
269
- const params = {
270
- ...staticControls.defaults ?? {},
271
- ...staticControls.params ?? {}
272
- };
273
- if (isRecord(po["params"])) {
274
- for (const [key, value] of Object.entries(po["params"])) {
275
- if (value != null) params[key] = String(value);
276
- }
277
- }
278
- if (typeof po["thinking"] === "string" && params["thinking"] === void 0) {
279
- params["thinking"] = po["thinking"];
280
- }
281
- return { mode, modelSelection: buildModelSelection(modelId, params) };
282
- }
19
+ // src/provider/agent-backend.ts
20
+ import { execSync } from "child_process";
21
+ import { existsSync } from "fs";
22
+ import { fileURLToPath } from "url";
283
23
 
284
24
  // src/cursor-runtime.ts
285
25
  var cached;
@@ -296,11 +36,6 @@ async function loadCursorSdk() {
296
36
  return cached;
297
37
  }
298
38
 
299
- // src/provider/agent-backend.ts
300
- import { execSync } from "child_process";
301
- import { existsSync as existsSync2 } from "fs";
302
- import { fileURLToPath } from "url";
303
-
304
39
  // src/provider/sidecar-client.ts
305
40
  import { spawn } from "child_process";
306
41
  import { createInterface } from "readline";
@@ -308,6 +43,10 @@ function reviveError(error) {
308
43
  const e = error ?? {};
309
44
  const err = new Error(e.message ?? "sidecar error");
310
45
  if (e.name) err.name = e.name;
46
+ if (e.status !== void 0) err.status = e.status;
47
+ if (e.code !== void 0) err.code = e.code;
48
+ if (e.isRetryable !== void 0) err.isRetryable = e.isRetryable;
49
+ if (e.helpUrl !== void 0) err.helpUrl = e.helpUrl;
311
50
  return err;
312
51
  }
313
52
  var SidecarClient = class {
@@ -462,7 +201,8 @@ var SidecarClient = class {
462
201
  agentId,
463
202
  message,
464
203
  ...options?.mode ? { mode: options.mode } : {},
465
- ...options?.local?.force ? { force: true } : {}
204
+ ...options?.local?.force ? { force: true } : {},
205
+ ...options?.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}
466
206
  },
467
207
  {
468
208
  onUpdate: (update) => options?.onDelta?.({ update }),
@@ -493,11 +233,28 @@ var SidecarClient = class {
493
233
  };
494
234
 
495
235
  // src/provider/agent-backend.ts
496
- function resolveBackendKind(env) {
497
- const override = process.env["OPENCODE_CURSOR_SIDECAR"];
498
- if (override === "0" || override === "false") return "in-process";
499
- if (override === "1" || override === "true") return env.nodePath ? "sidecar" : "in-process";
500
- return env.isBun && env.nodePath ? "sidecar" : "in-process";
236
+ var DEFAULT_BUN_TRANSPORT = "http1";
237
+ var preferredTransport;
238
+ function setPreferredTransport(t) {
239
+ preferredTransport = t;
240
+ }
241
+ function isTransportKind(v) {
242
+ return v === "http1" || v === "http2-direct" || v === "sidecar";
243
+ }
244
+ function resolveTransport(env) {
245
+ const requested = preferredTransport ?? (isTransportKind(process.env["OPENCODE_CURSOR_TRANSPORT"]) ? process.env["OPENCODE_CURSOR_TRANSPORT"] : void 0);
246
+ if (requested) {
247
+ if (requested === "sidecar" && !env.nodePath) {
248
+ return env.isBun ? "http1" : "http2-direct";
249
+ }
250
+ return requested;
251
+ }
252
+ const legacy = process.env["OPENCODE_CURSOR_SIDECAR"];
253
+ if (legacy === "1" || legacy === "true") {
254
+ return env.nodePath ? "sidecar" : env.isBun ? "http1" : "http2-direct";
255
+ }
256
+ if (legacy === "0" || legacy === "false") return env.isBun ? "http1" : "http2-direct";
257
+ return env.isBun ? DEFAULT_BUN_TRANSPORT : "http2-direct";
501
258
  }
502
259
  function detectNode() {
503
260
  try {
@@ -515,15 +272,24 @@ function detectEnvironment() {
515
272
  const needsNode = isBun || process.env["OPENCODE_CURSOR_SIDECAR"] === "1";
516
273
  return { isBun, nodePath: needsNode ? detectNode() : process.execPath };
517
274
  }
518
- function inProcessBackend() {
275
+ var http1Configured = false;
276
+ async function ensureHttp1Configured() {
277
+ if (http1Configured) return;
278
+ const { Cursor } = await loadCursorSdk();
279
+ Cursor.configure({ local: { useHttp1ForAgent: true } });
280
+ http1Configured = true;
281
+ }
282
+ function inProcessBackend(useHttp1) {
519
283
  return {
520
284
  kind: "in-process",
521
285
  createAgent: async (options) => {
522
286
  const { Agent } = await loadCursorSdk();
287
+ if (useHttp1) await ensureHttp1Configured();
523
288
  return await Agent.create(options);
524
289
  },
525
290
  resumeAgent: async (agentId, options) => {
526
291
  const { Agent } = await loadCursorSdk();
292
+ if (useHttp1) await ensureHttp1Configured();
527
293
  return await Agent.resume(agentId, options);
528
294
  }
529
295
  };
@@ -539,7 +305,7 @@ function resolveSidecarScript() {
539
305
  ];
540
306
  for (const candidate of candidates) {
541
307
  const path = fileURLToPath(new URL(candidate, import.meta.url));
542
- if (existsSync2(path)) return path;
308
+ if (existsSync(path)) return path;
543
309
  }
544
310
  return void 0;
545
311
  }
@@ -555,25 +321,450 @@ var cached2;
555
321
  function loadAgentBackend() {
556
322
  if (!cached2) {
557
323
  const env = detectEnvironment();
558
- const kind = resolveBackendKind(env);
559
- const scriptPath = kind === "sidecar" ? resolveSidecarScript() : void 0;
560
- const override = process.env["OPENCODE_CURSOR_SIDECAR"];
561
- const optedOut = override === "0" || override === "false";
562
- if (env.isBun && !optedOut && (kind === "in-process" || !scriptPath)) {
324
+ const transport = resolveTransport(env);
325
+ const scriptPath = transport === "sidecar" ? resolveSidecarScript() : void 0;
326
+ if (transport === "sidecar" && (!env.nodePath || !scriptPath)) {
563
327
  console.error(
564
- `[opencode-cursor] Running under Bun without a usable Node sidecar (node: ${env.nodePath ?? "not found"}, script: ${scriptPath ?? "not found"}): Cursor native tool calls may fail (Bun node:http2 incompatibility). Install Node.js to enable the sidecar, or set OPENCODE_CURSOR_SIDECAR=0 to silence this warning.`
328
+ `[opencode-cursor] Node sidecar requested but unavailable (node: ${env.nodePath ?? "not found"}, script: ${scriptPath ?? "not found"}); falling back to in-process HTTP/1.1 transport.`
565
329
  );
330
+ cached2 = inProcessBackend(true);
331
+ return cached2;
566
332
  }
567
- cached2 = kind === "sidecar" && env.nodePath && scriptPath ? sidecarBackend(env.nodePath, scriptPath) : inProcessBackend();
333
+ if (transport === "http2-direct" && env.isBun) {
334
+ console.error(
335
+ "[opencode-cursor] http2-direct under Bun: Cursor streams may fail (Bun node:http2 incompatibility, oven-sh/bun#31499). Set OPENCODE_CURSOR_TRANSPORT=http1 (recommended) or sidecar."
336
+ );
337
+ }
338
+ cached2 = transport === "sidecar" && env.nodePath && scriptPath ? sidecarBackend(env.nodePath, scriptPath) : inProcessBackend(transport === "http1");
568
339
  }
569
340
  return cached2;
570
341
  }
571
342
 
572
- // src/provider/session-store.ts
573
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
574
- import { homedir, tmpdir } from "os";
575
- import { join as join2 } from "path";
576
- var ENTRY_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
343
+ // src/provider/system-rule.ts
344
+ import {
345
+ mkdirSync,
346
+ writeFileSync,
347
+ readFileSync,
348
+ existsSync as existsSync2,
349
+ rmSync
350
+ } from "fs";
351
+ import { join } from "path";
352
+ var RULES_DIR = join(".cursor", "rules");
353
+ var RULE_FILE = "opencode.mdc";
354
+ var IGNORE_FILE = ".gitignore";
355
+ var SENTINEL = "generated: opencode-cursor";
356
+ function extractSystemText(prompt) {
357
+ const parts = [];
358
+ for (const message of prompt) {
359
+ if (message.role === "system") parts.push(message.content);
360
+ }
361
+ return parts.join("\n\n").trim();
362
+ }
363
+ function isGenerated(content) {
364
+ if (!content.startsWith("---")) return false;
365
+ const end = content.indexOf("\n---", 3);
366
+ const frontmatter = end === -1 ? content : content.slice(0, end);
367
+ return frontmatter.split(/\r?\n/).includes(SENTINEL);
368
+ }
369
+ function writeSystemRule(cwd, systemText) {
370
+ if (!systemText) return "empty";
371
+ const dir = join(cwd, RULES_DIR);
372
+ const path = join(dir, RULE_FILE);
373
+ const body = `---
374
+ alwaysApply: true
375
+ ${SENTINEL}
376
+ ---
377
+
378
+ ${systemText}
379
+ `;
380
+ const existing = existsSync2(path) ? readFileSync(path, "utf8") : void 0;
381
+ if (existing !== void 0) {
382
+ if (!isGenerated(existing)) return "blocked";
383
+ if (existing === body) return "unchanged";
384
+ }
385
+ mkdirSync(dir, { recursive: true });
386
+ writeFileSync(path, body, "utf8");
387
+ ensureGitIgnored(dir);
388
+ return "written";
389
+ }
390
+ function ensureGitIgnored(dir) {
391
+ const path = join(dir, IGNORE_FILE);
392
+ const existing = existsSync2(path) ? readFileSync(path, "utf8") : "";
393
+ const lines = existing.split(/\r?\n/);
394
+ const missing = [RULE_FILE, IGNORE_FILE].filter(
395
+ (entry) => !lines.includes(entry)
396
+ );
397
+ if (missing.length === 0) return;
398
+ const prefix = existing && !existing.endsWith("\n") ? `${existing}
399
+ ` : existing;
400
+ writeFileSync(path, `${prefix}${missing.join("\n")}
401
+ `, "utf8");
402
+ }
403
+ function removeSystemRule(cwd) {
404
+ try {
405
+ const path = join(cwd, RULES_DIR, RULE_FILE);
406
+ if (isGenerated(readFileSync(path, "utf8"))) rmSync(path);
407
+ } catch {
408
+ }
409
+ }
410
+ function resolveSystemDelivery(options) {
411
+ const { mode, settingSources, cwd, systemText, warn } = options;
412
+ if (mode !== "rules") return { mode, settingSources };
413
+ if (settingSources && !settingSources.includes("project")) {
414
+ warn(
415
+ 'systemPrompt "rules" needs the "project" settings layer, but settingSources was explicitly configured without it; delivering the system prompt inline ("message" mode) instead. Add "project" to settingSources or set systemPrompt: "message" to silence this.'
416
+ );
417
+ return { mode: "message", settingSources };
418
+ }
419
+ let result;
420
+ try {
421
+ result = writeSystemRule(cwd, systemText);
422
+ } catch (error) {
423
+ warn(
424
+ `failed to write .cursor/rules/${RULE_FILE} (${error instanceof Error ? error.message : String(error)}); delivering the system prompt inline ("message" mode) for this turn.`
425
+ );
426
+ return { mode: "message", settingSources };
427
+ }
428
+ if (result === "blocked") {
429
+ warn(
430
+ `.cursor/rules/${RULE_FILE} exists but was not generated by opencode-cursor; leaving it untouched and delivering the system prompt inline ("message" mode).`
431
+ );
432
+ return { mode: "message", settingSources };
433
+ }
434
+ if (result === "empty") return { mode, settingSources };
435
+ return { mode, settingSources: settingSources ?? ["project"] };
436
+ }
437
+
438
+ // src/provider/error-classify.ts
439
+ function classifyError(err) {
440
+ const e = err ?? {};
441
+ const name = typeof e.name === "string" ? e.name : "Error";
442
+ const message = typeof e.message === "string" ? e.message : String(err);
443
+ const status = typeof e.status === "number" ? e.status : void 0;
444
+ const code = typeof e.code === "string" ? e.code : void 0;
445
+ const helpUrl = typeof e.helpUrl === "string" ? e.helpUrl : void 0;
446
+ const base = { ...status !== void 0 ? { status } : {}, ...helpUrl ? { helpUrl } : {}, message };
447
+ switch (name) {
448
+ case "AgentNotFoundError":
449
+ return { kind: "agent-not-found", retryable: false, ...base };
450
+ case "AgentBusyError":
451
+ return { kind: "agent-busy", retryable: false, ...base };
452
+ case "RateLimitError":
453
+ return { kind: "rate-limit", retryable: true, ...base };
454
+ case "NetworkError":
455
+ return { kind: "network", retryable: true, ...base };
456
+ case "AuthenticationError":
457
+ return { kind: "auth", retryable: false, ...base };
458
+ case "ConfigurationError":
459
+ case "IntegrationNotConnectedError":
460
+ case "UnsupportedRunOperationError":
461
+ return { kind: "config", retryable: false, ...base };
462
+ }
463
+ if (status === 401) return { kind: "auth", retryable: false, ...base };
464
+ if (status === 429) return { kind: "rate-limit", retryable: true, ...base };
465
+ if (status === 409) return { kind: "agent-busy", retryable: false, ...base };
466
+ if (status === 503 || status === 504) return { kind: "network", retryable: true, ...base };
467
+ if (code === "agent_not_found") return { kind: "agent-not-found", retryable: false, ...base };
468
+ return { kind: "unknown", retryable: e.isRetryable === true, ...base };
469
+ }
470
+
471
+ // src/provider/agent-events.ts
472
+ function addUsage(a, b) {
473
+ if (!a) return b;
474
+ if (!b) return a;
475
+ return {
476
+ inputTokens: a.inputTokens + b.inputTokens,
477
+ outputTokens: a.outputTokens + b.outputTokens,
478
+ cacheReadTokens: a.cacheReadTokens + b.cacheReadTokens,
479
+ cacheWriteTokens: a.cacheWriteTokens + b.cacheWriteTokens
480
+ };
481
+ }
482
+ function toolDisplayName(toolCall) {
483
+ if (!toolCall) return "tool";
484
+ if (toolCall.type === "mcp") {
485
+ const name = toolCall.args?.toolName;
486
+ const server = toolCall.args?.providerIdentifier;
487
+ if (name) return server ? `${server}/${name}` : String(name);
488
+ return "mcp";
489
+ }
490
+ return toolCall.type ?? "tool";
491
+ }
492
+ async function* streamAgentTurn(agent, message, options) {
493
+ const queue = [];
494
+ let wake;
495
+ let finished = false;
496
+ let failure;
497
+ const debug = process.env.OPENCODE_CURSOR_DEBUG === "1";
498
+ const counts = {};
499
+ const stallMs = Number(process.env.OPENCODE_CURSOR_STALL_MS ?? 6e4);
500
+ let stallTimer;
501
+ let forced = false;
502
+ let anyEvent = false;
503
+ const push = (event) => {
504
+ anyEvent = true;
505
+ queue.push(event);
506
+ wake?.();
507
+ wake = void 0;
508
+ armWatchdog();
509
+ };
510
+ const armWatchdog = () => {
511
+ if (stallMs <= 0 || finished) return;
512
+ if (stallTimer) clearTimeout(stallTimer);
513
+ stallTimer = setTimeout(() => {
514
+ void onStall();
515
+ }, stallMs);
516
+ stallTimer.unref?.();
517
+ };
518
+ const onDelta = ({ update }) => {
519
+ if (debug) counts[update.type] = (counts[update.type] ?? 0) + 1;
520
+ switch (update.type) {
521
+ case "text-delta":
522
+ push({ type: "text-delta", text: update.text });
523
+ break;
524
+ case "thinking-delta":
525
+ push({ type: "reasoning-delta", text: update.text });
526
+ break;
527
+ case "thinking-completed":
528
+ push({ type: "reasoning-complete", durationMs: update.thinkingDurationMs });
529
+ break;
530
+ case "summary-started":
531
+ case "summary":
532
+ case "summary-completed":
533
+ push({ type: "compaction" });
534
+ break;
535
+ case "partial-tool-call":
536
+ push({
537
+ type: "tool-input-partial",
538
+ id: String(update.callId),
539
+ name: toolDisplayName(update.toolCall),
540
+ input: update.toolCall?.args ?? {}
541
+ });
542
+ break;
543
+ case "tool-call-started":
544
+ push({
545
+ type: "tool-call",
546
+ id: String(update.callId),
547
+ name: toolDisplayName(update.toolCall),
548
+ input: update.toolCall?.args ?? {}
549
+ });
550
+ break;
551
+ case "tool-call-completed": {
552
+ const tool = update.toolCall ?? {};
553
+ const result = tool.result;
554
+ const mcpError = tool.type === "mcp" && result?.value?.isError === true;
555
+ push({
556
+ type: "tool-result",
557
+ id: String(update.callId),
558
+ name: toolDisplayName(tool),
559
+ result: result ?? null,
560
+ isError: result?.status === "error" || mcpError
561
+ });
562
+ break;
563
+ }
564
+ case "turn-ended":
565
+ if (update.usage) {
566
+ const summed = addUsage(options.usageBase, update.usage);
567
+ if (summed) push({ type: "usage", usage: summed });
568
+ }
569
+ break;
570
+ }
571
+ };
572
+ const runHolder = {};
573
+ const onAbort = () => {
574
+ if (stallTimer) clearTimeout(stallTimer);
575
+ stallTimer = void 0;
576
+ void Promise.resolve(runHolder.run?.cancel()).catch(() => {
577
+ });
578
+ };
579
+ options.abortSignal?.addEventListener("abort", onAbort);
580
+ let runGen = 0;
581
+ const startRun = (force) => {
582
+ const gen = ++runGen;
583
+ void sendWithRecovery(
584
+ agent,
585
+ message,
586
+ {
587
+ mode: options.mode,
588
+ onDelta,
589
+ ...options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {},
590
+ ...force ? { local: { force: true } } : {}
591
+ },
592
+ debug
593
+ ).then(async (run) => {
594
+ runHolder.run = run;
595
+ if (options.abortSignal?.aborted) void Promise.resolve(run.cancel()).catch(() => {
596
+ });
597
+ const result = await run.wait();
598
+ if (debug) {
599
+ console.error(
600
+ `[cursor:debug] updates=${JSON.stringify(counts)} status=${result.status} resultLen=${(result.result ?? "").length}`
601
+ );
602
+ }
603
+ if (gen !== runGen || finished) return;
604
+ if (result.status === "error") {
605
+ throw new Error(
606
+ `Cursor run ended with status "error"${result.result ? `: ${result.result}` : ""}`
607
+ );
608
+ }
609
+ push({ type: "finish", ...result.status === "cancelled" ? {} : { text: result.result } });
610
+ }).catch((err) => {
611
+ if (gen !== runGen) return;
612
+ failure = err;
613
+ if (debug) console.error(`[cursor:debug] send failed: ${err instanceof Error ? err.message : String(err)}`);
614
+ }).finally(() => {
615
+ if (gen !== runGen) return;
616
+ finished = true;
617
+ if (stallTimer) clearTimeout(stallTimer);
618
+ wake?.();
619
+ wake = void 0;
620
+ });
621
+ };
622
+ const onStall = async () => {
623
+ if (finished) return;
624
+ if (options.abortSignal?.aborted) return;
625
+ const failTerminal = async (message2) => {
626
+ try {
627
+ await runHolder.run?.cancel();
628
+ } catch {
629
+ }
630
+ failure = new Error(message2);
631
+ finished = true;
632
+ if (stallTimer) clearTimeout(stallTimer);
633
+ stallTimer = void 0;
634
+ wake?.();
635
+ wake = void 0;
636
+ };
637
+ if (anyEvent) {
638
+ await failTerminal(`Cursor run stalled (no events for ${stallMs}ms)`);
639
+ return;
640
+ }
641
+ if (forced) {
642
+ await failTerminal(`Cursor run stalled twice (no events for ${stallMs}ms)`);
643
+ return;
644
+ }
645
+ forced = true;
646
+ if (debug) console.error("[cursor:debug] stream stalled; cancelling and resending with local.force");
647
+ try {
648
+ await runHolder.run?.cancel();
649
+ } catch {
650
+ }
651
+ armWatchdog();
652
+ startRun(true);
653
+ };
654
+ armWatchdog();
655
+ startRun(false);
656
+ try {
657
+ while (true) {
658
+ if (queue.length > 0) {
659
+ yield queue.shift();
660
+ continue;
661
+ }
662
+ if (finished) break;
663
+ await new Promise((resolve) => {
664
+ wake = resolve;
665
+ });
666
+ }
667
+ while (queue.length > 0) yield queue.shift();
668
+ if (failure) throw failure;
669
+ } finally {
670
+ if (stallTimer) clearTimeout(stallTimer);
671
+ options.abortSignal?.removeEventListener("abort", onAbort);
672
+ }
673
+ }
674
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
675
+ var RETRY_BACKOFF_MS = [500, 1500];
676
+ async function sendWithRecovery(agent, message, sendOptions, debug) {
677
+ for (let attempt = 0; ; attempt++) {
678
+ try {
679
+ return await agent.send(message, sendOptions);
680
+ } catch (err) {
681
+ const classified = classifyError(err);
682
+ if (classified.kind === "agent-busy") {
683
+ if (debug) console.error("[cursor:debug] agent busy; retrying send with local.force");
684
+ return agent.send(message, { ...sendOptions, local: { force: true } });
685
+ }
686
+ if ((classified.kind === "rate-limit" || classified.kind === "network") && attempt < RETRY_BACKOFF_MS.length) {
687
+ if (debug)
688
+ console.error(
689
+ `[cursor:debug] ${classified.kind}; retrying send in ${RETRY_BACKOFF_MS[attempt]}ms`
690
+ );
691
+ await sleep(RETRY_BACKOFF_MS[attempt]);
692
+ continue;
693
+ }
694
+ throw err;
695
+ }
696
+ }
697
+ }
698
+ async function sendAgentTurnSilently(agent, message, options) {
699
+ if (options.abortSignal?.aborted) return void 0;
700
+ const debug = process.env.OPENCODE_CURSOR_DEBUG === "1";
701
+ const runHolder = {};
702
+ let usage;
703
+ const onDelta = ({ update }) => {
704
+ if (update.type === "turn-ended" && update.usage) usage = update.usage;
705
+ };
706
+ const onAbort = () => {
707
+ void Promise.resolve(runHolder.run?.cancel()).catch(() => {
708
+ });
709
+ };
710
+ options.abortSignal?.addEventListener("abort", onAbort);
711
+ try {
712
+ const run = await sendWithRecovery(
713
+ agent,
714
+ message,
715
+ { mode: options.mode, onDelta, ...options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {} },
716
+ debug
717
+ );
718
+ runHolder.run = run;
719
+ if (options.abortSignal?.aborted) void Promise.resolve(run.cancel()).catch(() => {
720
+ });
721
+ const result = await run.wait();
722
+ if (result.status !== "finished") {
723
+ if (options.abortSignal?.aborted) return void 0;
724
+ throw new Error(
725
+ `Cursor run ended with status "${result.status}"${result.result ? `: ${result.result}` : ""}`
726
+ );
727
+ }
728
+ return usage;
729
+ } finally {
730
+ options.abortSignal?.removeEventListener("abort", onAbort);
731
+ }
732
+ }
733
+
734
+ // src/provider/controls.ts
735
+ function buildModelSelection(modelId, params) {
736
+ const paramList = Object.entries(params ?? {}).map(([id, value]) => ({ id, value }));
737
+ return paramList.length > 0 ? { id: modelId, params: paramList } : { id: modelId };
738
+ }
739
+ function isRecord(value) {
740
+ return typeof value === "object" && value !== null && !Array.isArray(value);
741
+ }
742
+ function isMode(value) {
743
+ return value === "agent" || value === "plan";
744
+ }
745
+ function resolveControls(modelId, staticControls, providerOptions) {
746
+ const po = providerOptions ?? {};
747
+ const mode = isMode(po["mode"]) ? po["mode"] : staticControls.mode;
748
+ const params = {
749
+ ...staticControls.defaults ?? {},
750
+ ...staticControls.params ?? {}
751
+ };
752
+ if (isRecord(po["params"])) {
753
+ for (const [key, value] of Object.entries(po["params"])) {
754
+ if (value != null) params[key] = String(value);
755
+ }
756
+ }
757
+ if (typeof po["thinking"] === "string" && params["thinking"] === void 0) {
758
+ params["thinking"] = po["thinking"];
759
+ }
760
+ return { mode, modelSelection: buildModelSelection(modelId, params) };
761
+ }
762
+
763
+ // src/provider/session-store.ts
764
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
765
+ import { homedir, tmpdir } from "os";
766
+ import { join as join2 } from "path";
767
+ var ENTRY_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
577
768
  var MAX_ENTRIES = 200;
578
769
  function storeDir() {
579
770
  const base = process.env.XDG_CACHE_HOME?.trim() || (homedir() ? join2(homedir(), ".cache") : tmpdir());
@@ -641,7 +832,8 @@ async function acquireAgent(params) {
641
832
  local: {
642
833
  cwd: params.cwd,
643
834
  ...params.settingSources ? { settingSources: params.settingSources } : {},
644
- ...params.sandbox !== void 0 ? { sandboxOptions: { enabled: params.sandbox } } : {}
835
+ ...params.sandbox !== void 0 ? { sandboxOptions: { enabled: params.sandbox } } : {},
836
+ ...params.autoReview !== void 0 ? { autoReview: params.autoReview } : {}
645
837
  },
646
838
  ...params.mcpServers ? { mcpServers: params.mcpServers } : {},
647
839
  ...params.agents ? { agents: params.agents } : {},
@@ -685,16 +877,19 @@ async function acquireAgent(params) {
685
877
  export {
686
878
  resolveCursorApiKey,
687
879
  fingerprintApiKey,
880
+ loadCursorSdk,
881
+ setPreferredTransport,
688
882
  extractSystemText,
689
883
  removeSystemRule,
690
884
  resolveSystemDelivery,
885
+ classifyError,
886
+ addUsage,
691
887
  streamAgentTurn,
692
888
  sendAgentTurnSilently,
693
889
  buildModelSelection,
694
890
  resolveControls,
695
- loadCursorSdk,
696
891
  getSessionRecord,
697
892
  dropSessionRecord,
698
893
  acquireAgent
699
894
  };
700
- //# sourceMappingURL=chunk-SVIXYMHP.js.map
895
+ //# sourceMappingURL=chunk-LAOFD3JB.js.map