@stablekernel/opencode-cursor 0.4.6 → 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,267 +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 = { ...staticControls.params ?? {} };
270
- if (isRecord(po["params"])) {
271
- for (const [key, value] of Object.entries(po["params"])) {
272
- if (value != null) params[key] = String(value);
273
- }
274
- }
275
- if (typeof po["thinking"] === "string" && params["thinking"] === void 0) {
276
- params["thinking"] = po["thinking"];
277
- }
278
- return { mode, modelSelection: buildModelSelection(modelId, params) };
279
- }
19
+ // src/provider/agent-backend.ts
20
+ import { execSync } from "child_process";
21
+ import { existsSync } from "fs";
22
+ import { fileURLToPath } from "url";
280
23
 
281
24
  // src/cursor-runtime.ts
282
25
  var cached;
@@ -293,11 +36,6 @@ async function loadCursorSdk() {
293
36
  return cached;
294
37
  }
295
38
 
296
- // src/provider/agent-backend.ts
297
- import { execSync } from "child_process";
298
- import { existsSync as existsSync2 } from "fs";
299
- import { fileURLToPath } from "url";
300
-
301
39
  // src/provider/sidecar-client.ts
302
40
  import { spawn } from "child_process";
303
41
  import { createInterface } from "readline";
@@ -305,6 +43,10 @@ function reviveError(error) {
305
43
  const e = error ?? {};
306
44
  const err = new Error(e.message ?? "sidecar error");
307
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;
308
50
  return err;
309
51
  }
310
52
  var SidecarClient = class {
@@ -459,7 +201,8 @@ var SidecarClient = class {
459
201
  agentId,
460
202
  message,
461
203
  ...options?.mode ? { mode: options.mode } : {},
462
- ...options?.local?.force ? { force: true } : {}
204
+ ...options?.local?.force ? { force: true } : {},
205
+ ...options?.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}
463
206
  },
464
207
  {
465
208
  onUpdate: (update) => options?.onDelta?.({ update }),
@@ -490,11 +233,28 @@ var SidecarClient = class {
490
233
  };
491
234
 
492
235
  // src/provider/agent-backend.ts
493
- function resolveBackendKind(env) {
494
- const override = process.env["OPENCODE_CURSOR_SIDECAR"];
495
- if (override === "0" || override === "false") return "in-process";
496
- if (override === "1" || override === "true") return env.nodePath ? "sidecar" : "in-process";
497
- 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";
498
258
  }
499
259
  function detectNode() {
500
260
  try {
@@ -512,15 +272,24 @@ function detectEnvironment() {
512
272
  const needsNode = isBun || process.env["OPENCODE_CURSOR_SIDECAR"] === "1";
513
273
  return { isBun, nodePath: needsNode ? detectNode() : process.execPath };
514
274
  }
515
- 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) {
516
283
  return {
517
284
  kind: "in-process",
518
285
  createAgent: async (options) => {
519
286
  const { Agent } = await loadCursorSdk();
287
+ if (useHttp1) await ensureHttp1Configured();
520
288
  return await Agent.create(options);
521
289
  },
522
290
  resumeAgent: async (agentId, options) => {
523
291
  const { Agent } = await loadCursorSdk();
292
+ if (useHttp1) await ensureHttp1Configured();
524
293
  return await Agent.resume(agentId, options);
525
294
  }
526
295
  };
@@ -536,7 +305,7 @@ function resolveSidecarScript() {
536
305
  ];
537
306
  for (const candidate of candidates) {
538
307
  const path = fileURLToPath(new URL(candidate, import.meta.url));
539
- if (existsSync2(path)) return path;
308
+ if (existsSync(path)) return path;
540
309
  }
541
310
  return void 0;
542
311
  }
@@ -552,29 +321,454 @@ var cached2;
552
321
  function loadAgentBackend() {
553
322
  if (!cached2) {
554
323
  const env = detectEnvironment();
555
- const kind = resolveBackendKind(env);
556
- const scriptPath = kind === "sidecar" ? resolveSidecarScript() : void 0;
557
- const override = process.env["OPENCODE_CURSOR_SIDECAR"];
558
- const optedOut = override === "0" || override === "false";
559
- 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)) {
560
327
  console.error(
561
- `[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.`
562
329
  );
330
+ cached2 = inProcessBackend(true);
331
+ return cached2;
563
332
  }
564
- 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");
565
339
  }
566
340
  return cached2;
567
341
  }
568
342
 
569
- // src/provider/session-store.ts
570
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
571
- import { homedir, tmpdir } from "os";
572
- import { join as join2 } from "path";
573
- var ENTRY_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
574
- var MAX_ENTRIES = 200;
575
- function storeDir() {
576
- const base = process.env.XDG_CACHE_HOME?.trim() || (homedir() ? join2(homedir(), ".cache") : tmpdir());
577
- return join2(base, "opencode-cursor");
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;
768
+ var MAX_ENTRIES = 200;
769
+ function storeDir() {
770
+ const base = process.env.XDG_CACHE_HOME?.trim() || (homedir() ? join2(homedir(), ".cache") : tmpdir());
771
+ return join2(base, "opencode-cursor");
578
772
  }
579
773
  function storeFile() {
580
774
  return join2(storeDir(), "session-pool.json");
@@ -638,7 +832,8 @@ async function acquireAgent(params) {
638
832
  local: {
639
833
  cwd: params.cwd,
640
834
  ...params.settingSources ? { settingSources: params.settingSources } : {},
641
- ...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 } : {}
642
837
  },
643
838
  ...params.mcpServers ? { mcpServers: params.mcpServers } : {},
644
839
  ...params.agents ? { agents: params.agents } : {},
@@ -682,16 +877,19 @@ async function acquireAgent(params) {
682
877
  export {
683
878
  resolveCursorApiKey,
684
879
  fingerprintApiKey,
880
+ loadCursorSdk,
881
+ setPreferredTransport,
685
882
  extractSystemText,
686
883
  removeSystemRule,
687
884
  resolveSystemDelivery,
885
+ classifyError,
886
+ addUsage,
688
887
  streamAgentTurn,
689
888
  sendAgentTurnSilently,
690
889
  buildModelSelection,
691
890
  resolveControls,
692
- loadCursorSdk,
693
891
  getSessionRecord,
694
892
  dropSessionRecord,
695
893
  acquireAgent
696
894
  };
697
- //# sourceMappingURL=chunk-734L3SKU.js.map
895
+ //# sourceMappingURL=chunk-LAOFD3JB.js.map