@rama_nigg/open-cursor 2.4.4 → 2.4.5

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.
@@ -520,6 +520,15 @@ function resolveCursorAgentBinary(deps = {}) {
520
520
  log.warn("cursor-agent not found at known paths, falling back to PATH", { checkedPaths: knownPaths });
521
521
  return "cursor-agent";
522
522
  }
523
+ function formatShellCommandForPlatform(command, platform = process.platform) {
524
+ if (platform !== "win32") {
525
+ return command;
526
+ }
527
+ if (command.startsWith('"') && command.endsWith('"')) {
528
+ return command;
529
+ }
530
+ return `"${command}"`;
531
+ }
523
532
  var log;
524
533
  var init_binary = __esm(() => {
525
534
  init_logger();
@@ -569,7 +578,7 @@ async function pollForAuthFile(timeoutMs = AUTH_POLL_TIMEOUT, intervalMs = AUTH_
569
578
  async function startCursorOAuth() {
570
579
  return new Promise((resolve2, reject) => {
571
580
  log2.info("Starting cursor-cli login process");
572
- const proc = spawn(resolveCursorAgentBinary(), ["login"], {
581
+ const proc = spawn(formatShellCommandForPlatform(resolveCursorAgentBinary()), ["login"], {
573
582
  stdio: ["pipe", "pipe", "pipe"],
574
583
  shell: process.platform === "win32"
575
584
  });
@@ -763,29 +772,6 @@ class LineBuffer {
763
772
  }
764
773
  }
765
774
 
766
- // src/streaming/types.ts
767
- var hasTextContent = (event) => event.message.content.some((content) => content.type === "text"), hasThinkingContent = (event) => event.message.content.some((content) => content.type === "thinking"), isAssistantText = (event) => event.type === "assistant" && hasTextContent(event), isThinking = (event) => {
768
- if (event.type === "thinking") {
769
- return true;
770
- }
771
- return event.type === "assistant" && hasThinkingContent(event);
772
- }, isToolCall = (event) => event.type === "tool_call", isResult = (event) => event.type === "result", extractText = (event) => event.message.content.filter((content) => content.type === "text").map((content) => content.text).join(""), extractThinking = (event) => {
773
- if (event.type === "thinking") {
774
- return event.text ?? "";
775
- }
776
- return event.message.content.filter((content) => content.type === "thinking").map((content) => content.thinking).join("");
777
- }, inferToolName = (event) => {
778
- const [key] = Object.keys(event.tool_call ?? {});
779
- if (!key) {
780
- return "";
781
- }
782
- if (key.endsWith("ToolCall")) {
783
- const base = key.slice(0, -"ToolCall".length);
784
- return base.charAt(0).toLowerCase() + base.slice(1);
785
- }
786
- return key;
787
- };
788
-
789
775
  // src/streaming/delta-tracker.ts
790
776
  class DeltaTracker {
791
777
  lastText = "";
@@ -823,14 +809,70 @@ class DeltaTracker {
823
809
  }
824
810
  }
825
811
 
812
+ class MixedDeltaTracker {
813
+ emittedText = "";
814
+ emittedThinking = "";
815
+ nextText(value) {
816
+ const delta = this.diff(this.emittedText, value);
817
+ if (delta) {
818
+ this.emittedText += delta;
819
+ }
820
+ return delta;
821
+ }
822
+ nextThinking(value) {
823
+ const delta = this.diff(this.emittedThinking, value);
824
+ if (delta) {
825
+ this.emittedThinking += delta;
826
+ }
827
+ return delta;
828
+ }
829
+ reset() {
830
+ this.emittedText = "";
831
+ this.emittedThinking = "";
832
+ }
833
+ diff(emitted, current) {
834
+ if (!emitted) {
835
+ return current;
836
+ }
837
+ if (current.startsWith(emitted)) {
838
+ return current.slice(emitted.length);
839
+ }
840
+ if (emitted.startsWith(current)) {
841
+ return "";
842
+ }
843
+ return current;
844
+ }
845
+ }
846
+
847
+ // src/streaming/types.ts
848
+ var hasTextContent = (event) => event.message.content.some((content) => content.type === "text"), hasThinkingContent = (event) => event.message.content.some((content) => content.type === "thinking"), isAssistantText = (event) => event.type === "assistant" && hasTextContent(event), isThinking = (event) => {
849
+ if (event.type === "thinking") {
850
+ return true;
851
+ }
852
+ return event.type === "assistant" && hasThinkingContent(event);
853
+ }, isToolCall = (event) => event.type === "tool_call", isResult = (event) => event.type === "result", extractText = (event) => event.message.content.filter((content) => content.type === "text").map((content) => content.text).join(""), extractThinking = (event) => {
854
+ if (event.type === "thinking") {
855
+ return event.text ?? "";
856
+ }
857
+ return event.message.content.filter((content) => content.type === "thinking").map((content) => content.thinking).join("");
858
+ }, inferToolName = (event) => {
859
+ const [key] = Object.keys(event.tool_call ?? {});
860
+ if (!key) {
861
+ return "";
862
+ }
863
+ if (key.endsWith("ToolCall")) {
864
+ const base = key.slice(0, -"ToolCall".length);
865
+ return base.charAt(0).toLowerCase() + base.slice(1);
866
+ }
867
+ return key;
868
+ };
869
+
826
870
  // src/streaming/openai-sse.ts
827
871
  class StreamToSseConverter {
828
872
  id;
829
873
  created;
830
874
  model;
831
- tracker = new DeltaTracker;
832
- sawAssistantPartials = false;
833
- sawThinkingPartials = false;
875
+ tracker = new MixedDeltaTracker;
834
876
  constructor(model, options) {
835
877
  this.model = model;
836
878
  this.id = options?.id ?? `cursor-acp-${Date.now()}`;
@@ -838,35 +880,17 @@ class StreamToSseConverter {
838
880
  }
839
881
  handleEvent(event) {
840
882
  if (isAssistantText(event)) {
841
- const isPartial = typeof event.timestamp_ms === "number";
842
- if (isPartial) {
843
- const text = extractText(event);
844
- if (text) {
845
- this.sawAssistantPartials = true;
846
- return [this.chunkWith({ content: text })];
847
- }
848
- return [];
849
- }
850
- if (this.sawAssistantPartials) {
883
+ const text = extractText(event);
884
+ if (!text)
851
885
  return [];
852
- }
853
- const delta = this.tracker.nextText(extractText(event));
886
+ const delta = this.tracker.nextText(text);
854
887
  return delta ? [this.chunkWith({ content: delta })] : [];
855
888
  }
856
889
  if (isThinking(event)) {
857
- const isPartial = typeof event.timestamp_ms === "number";
858
- if (isPartial) {
859
- const text = extractThinking(event);
860
- if (text) {
861
- this.sawThinkingPartials = true;
862
- return [this.chunkWith({ reasoning_content: text })];
863
- }
864
- return [];
865
- }
866
- if (this.sawThinkingPartials) {
890
+ const text = extractThinking(event);
891
+ if (!text)
867
892
  return [];
868
- }
869
- const delta = this.tracker.nextThinking(extractThinking(event));
893
+ const delta = this.tracker.nextThinking(text);
870
894
  return delta ? [this.chunkWith({ reasoning_content: delta })] : [];
871
895
  }
872
896
  if (isToolCall(event)) {
@@ -16110,6 +16134,7 @@ function extractCompletionFromStream(output) {
16110
16134
  let usage;
16111
16135
  let sawAssistantPartials = false;
16112
16136
  let sawThinkingPartials = false;
16137
+ const tracker = new MixedDeltaTracker;
16113
16138
  for (const line of lines) {
16114
16139
  const event = parseStreamJsonLine(line);
16115
16140
  if (!event) {
@@ -16121,8 +16146,8 @@ function extractCompletionFromStream(output) {
16121
16146
  continue;
16122
16147
  const isPartial = typeof event.timestamp_ms === "number";
16123
16148
  if (isPartial) {
16124
- assistantText += text;
16125
16149
  sawAssistantPartials = true;
16150
+ assistantText += tracker.nextText(text);
16126
16151
  } else if (!sawAssistantPartials) {
16127
16152
  assistantText = text;
16128
16153
  }
@@ -16132,8 +16157,8 @@ function extractCompletionFromStream(output) {
16132
16157
  if (thinking) {
16133
16158
  const isPartial = typeof event.timestamp_ms === "number";
16134
16159
  if (isPartial) {
16135
- reasoningText += thinking;
16136
16160
  sawThinkingPartials = true;
16161
+ reasoningText += tracker.nextThinking(thinking);
16137
16162
  } else if (!sawThinkingPartials) {
16138
16163
  reasoningText = thinking;
16139
16164
  }
@@ -16820,7 +16845,7 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
16820
16845
  if (FORCE_TOOL_MODE) {
16821
16846
  cmd.push("--force");
16822
16847
  }
16823
- const child = spawn3(cmd[0], cmd.slice(1), {
16848
+ const child = spawn3(formatShellCommandForPlatform(cmd[0]), cmd.slice(1), {
16824
16849
  stdio: ["pipe", "pipe", "pipe"],
16825
16850
  shell: process.platform === "win32"
16826
16851
  });
@@ -16967,22 +16992,18 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
16967
16992
  child.kill();
16968
16993
  } catch {}
16969
16994
  };
16970
- child.stdout.on("data", async (chunk) => {
16971
- if (streamTerminated || res.writableEnded) {
16972
- return;
16973
- }
16974
- if (!firstTokenReceived) {
16975
- perf.mark("first-token");
16976
- firstTokenReceived = true;
16977
- }
16978
- for (const line of lineBuffer.push(chunk)) {
16979
- if (streamTerminated || res.writableEnded) {
16995
+ const chunkQueue = [];
16996
+ let draining = false;
16997
+ let childClosed = false;
16998
+ let childCloseHandled = false;
16999
+ let childExitCode = null;
17000
+ const processLines = async (lines) => {
17001
+ for (const line of lines) {
17002
+ if (streamTerminated || res.writableEnded)
16980
17003
  break;
16981
- }
16982
17004
  const event = parseStreamJsonLine(line);
16983
- if (!event) {
17005
+ if (!event)
16984
17006
  continue;
16985
- }
16986
17007
  if (isResult(event)) {
16987
17008
  usage = extractOpenAiUsageFromResult(event) ?? usage;
16988
17009
  }
@@ -17031,148 +17052,100 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
17031
17052
  }
17032
17053
  break;
17033
17054
  }
17034
- if (result.intercepted) {
17055
+ if (result.intercepted)
17035
17056
  break;
17036
- }
17037
- if (result.skipConverter) {
17057
+ if (result.skipConverter)
17038
17058
  continue;
17039
- }
17040
17059
  }
17041
- if (streamTerminated || res.writableEnded) {
17060
+ if (streamTerminated || res.writableEnded)
17042
17061
  break;
17043
- }
17044
17062
  for (const sse of converter.handleEvent(event)) {
17045
17063
  res.write(sse);
17046
17064
  }
17047
17065
  }
17048
- });
17049
- child.on("close", async (code) => {
17050
- if (streamTerminated || res.writableEnded) {
17066
+ };
17067
+ const drainQueue = async () => {
17068
+ if (draining)
17051
17069
  return;
17052
- }
17053
- for (const line of lineBuffer.flush()) {
17054
- if (streamTerminated || res.writableEnded) {
17055
- break;
17056
- }
17057
- const event = parseStreamJsonLine(line);
17058
- if (!event) {
17059
- continue;
17060
- }
17061
- if (isResult(event)) {
17062
- usage = extractOpenAiUsageFromResult(event) ?? usage;
17063
- }
17064
- if (event.type === "tool_call") {
17065
- const result = await handleToolLoopEventWithFallback({
17066
- event,
17067
- boundary: boundaryContext.getBoundary(),
17068
- boundaryMode: boundaryContext.getBoundary().mode,
17069
- autoFallbackToLegacy: ENABLE_PROVIDER_BOUNDARY_AUTOFALLBACK,
17070
- toolLoopMode: TOOL_LOOP_MODE,
17071
- allowedToolNames,
17072
- toolSchemaMap,
17073
- toolLoopGuard,
17074
- toolMapper,
17075
- toolSessionId,
17076
- shouldEmitToolUpdates: SHOULD_EMIT_TOOL_UPDATES,
17077
- proxyExecuteToolCalls: PROXY_EXECUTE_TOOL_CALLS,
17078
- suppressConverterToolEvents: SUPPRESS_CONVERTER_TOOL_EVENTS,
17079
- toolRouter,
17080
- responseMeta: { id, created, model },
17081
- passThroughTracker,
17082
- onToolUpdate: (update) => {
17083
- res.write(formatToolUpdateEvent(update));
17084
- },
17085
- onToolResult: (toolResult) => {
17086
- res.write(`data: ${JSON.stringify(toolResult)}
17087
-
17088
- `);
17089
- },
17090
- onInterceptedToolCall: (toolCall) => {
17091
- emitToolCallAndTerminate(toolCall);
17092
- },
17093
- onFallbackToLegacy: (error) => {
17094
- boundaryContext.activateLegacyFallback("handleToolLoopEvent.close", error);
17095
- }
17096
- });
17097
- if (result.terminate) {
17098
- if (!result.terminate.silent) {
17099
- emitTerminalAssistantErrorAndTerminate(result.terminate.message);
17100
- } else {
17101
- streamTerminated = true;
17102
- try {
17103
- child.kill();
17104
- } catch {}
17105
- }
17106
- break;
17107
- }
17108
- if (result.intercepted) {
17070
+ draining = true;
17071
+ try {
17072
+ while (chunkQueue.length > 0) {
17073
+ if (streamTerminated || res.writableEnded)
17109
17074
  break;
17075
+ const chunk = chunkQueue.shift();
17076
+ if (!firstTokenReceived) {
17077
+ perf.mark("first-token");
17078
+ firstTokenReceived = true;
17110
17079
  }
17111
- if (result.skipConverter) {
17112
- continue;
17113
- }
17114
- }
17115
- if (streamTerminated || res.writableEnded) {
17116
- break;
17080
+ await processLines(lineBuffer.push(chunk));
17117
17081
  }
17118
- for (const sse of converter.handleEvent(event)) {
17119
- res.write(sse);
17120
- }
17121
- }
17122
- if (streamTerminated || res.writableEnded) {
17123
- return;
17124
- }
17125
- perf.mark("request:done");
17126
- perf.summarize();
17127
- const stderrText = Buffer.concat(stderrChunks).toString().trim();
17128
- log20.debug("cursor-agent completed (node stream)", {
17129
- code,
17130
- stderrChars: stderrText.length
17131
- });
17132
- if (code !== 0) {
17133
- const errSource = stderrText || `cursor-agent exited with code ${String(code ?? "unknown")} and no output`;
17134
- const parsed = parseAgentError(errSource);
17135
- const msg = formatErrorForUser(parsed);
17136
- const errChunk = createChatCompletionChunk(id, created, model, msg, true);
17137
- res.write(`data: ${JSON.stringify(errChunk)}
17082
+ if (childClosed && !childCloseHandled && !streamTerminated && !res.writableEnded) {
17083
+ childCloseHandled = true;
17084
+ await processLines(lineBuffer.flush());
17085
+ if (streamTerminated || res.writableEnded)
17086
+ return;
17087
+ perf.mark("request:done");
17088
+ perf.summarize();
17089
+ const stderrText = Buffer.concat(stderrChunks).toString().trim();
17090
+ log20.debug("cursor-agent completed (node stream)", {
17091
+ code: childExitCode,
17092
+ stderrChars: stderrText.length
17093
+ });
17094
+ if (childExitCode !== 0) {
17095
+ const errSource = stderrText || `cursor-agent exited with code ${String(childExitCode ?? "unknown")} and no output`;
17096
+ const parsed = parseAgentError(errSource);
17097
+ const msg = formatErrorForUser(parsed);
17098
+ const errChunk = createChatCompletionChunk(id, created, model, msg, true);
17099
+ res.write(`data: ${JSON.stringify(errChunk)}
17138
17100
 
17139
17101
  `);
17140
- res.write(formatSseDone());
17141
- streamTerminated = true;
17142
- res.end();
17143
- return;
17144
- }
17145
- const passThroughSummary = passThroughTracker.getSummary();
17146
- if (passThroughSummary.hasActivity) {
17147
- await toastService.showPassThroughSummary(passThroughSummary.tools);
17148
- }
17149
- if (passThroughSummary.errors.length > 0) {
17150
- await toastService.showErrorSummary(passThroughSummary.errors);
17151
- }
17152
- const doneChunk = {
17153
- id,
17154
- object: "chat.completion.chunk",
17155
- created,
17156
- model,
17157
- choices: [
17158
- {
17159
- index: 0,
17160
- delta: {},
17161
- finish_reason: "stop"
17102
+ res.write(formatSseDone());
17103
+ streamTerminated = true;
17104
+ res.end();
17105
+ return;
17162
17106
  }
17163
- ]
17164
- };
17165
- res.write(`data: ${JSON.stringify(doneChunk)}
17107
+ const passThroughSummary = passThroughTracker.getSummary();
17108
+ if (passThroughSummary.hasActivity) {
17109
+ await toastService.showPassThroughSummary(passThroughSummary.tools);
17110
+ }
17111
+ if (passThroughSummary.errors.length > 0) {
17112
+ await toastService.showErrorSummary(passThroughSummary.errors);
17113
+ }
17114
+ const doneChunk = {
17115
+ id,
17116
+ object: "chat.completion.chunk",
17117
+ created,
17118
+ model,
17119
+ choices: [{ index: 0, delta: {}, finish_reason: "stop" }]
17120
+ };
17121
+ res.write(`data: ${JSON.stringify(doneChunk)}
17166
17122
 
17167
17123
  `);
17168
- if (usage) {
17169
- const usageChunk = createChatCompletionUsageChunk(id, created, model, usage);
17170
- res.write(`data: ${JSON.stringify(usageChunk)}
17124
+ if (usage) {
17125
+ const usageChunk = createChatCompletionUsageChunk(id, created, model, usage);
17126
+ res.write(`data: ${JSON.stringify(usageChunk)}
17171
17127
 
17172
17128
  `);
17129
+ }
17130
+ res.write(formatSseDone());
17131
+ streamTerminated = true;
17132
+ res.end();
17133
+ }
17134
+ } finally {
17135
+ draining = false;
17136
+ if (!streamTerminated && !res.writableEnded && (chunkQueue.length > 0 || childClosed && !childCloseHandled)) {
17137
+ drainQueue();
17138
+ }
17173
17139
  }
17174
- res.write(formatSseDone());
17175
- res.end();
17140
+ };
17141
+ child.stdout.on("data", (chunk) => {
17142
+ chunkQueue.push(Buffer.from(chunk));
17143
+ drainQueue();
17144
+ });
17145
+ child.on("close", (code) => {
17146
+ childClosed = true;
17147
+ childExitCode = code;
17148
+ drainQueue();
17176
17149
  });
17177
17150
  }
17178
17151
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rama_nigg/open-cursor",
3
- "version": "2.4.4",
3
+ "version": "2.4.5",
4
4
  "description": "No prompt limits. No broken streams. Full thinking + tool support. Your Cursor subscription, properly integrated.",
5
5
  "type": "module",
6
6
  "main": "dist/plugin-entry.js",
package/src/auth.ts CHANGED
@@ -6,7 +6,7 @@ import { homedir, platform } from "os";
6
6
  import { join } from "path";
7
7
  import { createLogger } from "./utils/logger";
8
8
  import { stripAnsi } from "./utils/errors";
9
- import { resolveCursorAgentBinary } from "./utils/binary.js";
9
+ import { formatShellCommandForPlatform, resolveCursorAgentBinary } from "./utils/binary.js";
10
10
 
11
11
  const log = createLogger("auth");
12
12
 
@@ -76,7 +76,7 @@ export async function startCursorOAuth(): Promise<{
76
76
  return new Promise((resolve, reject) => {
77
77
  log.info("Starting cursor-cli login process");
78
78
 
79
- const proc = spawn(resolveCursorAgentBinary(), ["login"], {
79
+ const proc = spawn(formatShellCommandForPlatform(resolveCursorAgentBinary()), ["login"], {
80
80
  stdio: ["pipe", "pipe", "pipe"],
81
81
  shell: process.platform === "win32",
82
82
  });
@@ -7,7 +7,7 @@ import {
7
7
  type StreamJsonEvent,
8
8
  } from '../streaming/types.js';
9
9
  import { createLogger } from '../utils/logger.js';
10
- import { resolveCursorAgentBinary } from '../utils/binary.js';
10
+ import { formatShellCommandForPlatform, resolveCursorAgentBinary } from '../utils/binary.js';
11
11
 
12
12
  export interface CursorClientConfig {
13
13
  timeout?: number;
@@ -77,7 +77,7 @@ export class SimpleCursorClient {
77
77
 
78
78
  this.log.debug('Executing prompt stream', { promptLength: prompt.length, mode, model });
79
79
 
80
- const child = spawn(this.config.cursorAgentPath, args, {
80
+ const child = spawn(formatShellCommandForPlatform(this.config.cursorAgentPath), args, {
81
81
  cwd,
82
82
  stdio: ['pipe', 'pipe', 'pipe'],
83
83
  shell: process.platform === 'win32',
@@ -189,7 +189,7 @@ export class SimpleCursorClient {
189
189
  this.log.debug('Executing prompt', { promptLength: prompt.length, mode, model });
190
190
 
191
191
  return new Promise((resolve, reject) => {
192
- const child = spawn(this.config.cursorAgentPath, args, {
192
+ const child = spawn(formatShellCommandForPlatform(this.config.cursorAgentPath), args, {
193
193
  cwd,
194
194
  stdio: ['pipe', 'pipe', 'pipe'],
195
195
  shell: process.platform === 'win32',