@rama_nigg/open-cursor 2.3.20 → 2.4.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,6 +16,7 @@ export class CliExecutor implements IToolExecutor {
16
16
  const { spawn } = await import("node:child_process");
17
17
  const child = spawn("opencode", ["tool", "run", toolId, "--json", JSON.stringify(args)], {
18
18
  stdio: ["ignore", "pipe", "pipe"],
19
+ shell: process.platform === "win32",
19
20
  });
20
21
 
21
22
  const stdoutChunks: Buffer[] = [];
package/src/usage.ts ADDED
@@ -0,0 +1,112 @@
1
+ import type { StreamJsonResultEvent } from "./streaming/types.js";
2
+
3
+ export type CursorUsageMetrics = {
4
+ inputTokens: number;
5
+ outputTokens: number;
6
+ reasoningTokens: number;
7
+ cacheReadTokens: number;
8
+ cacheWriteTokens: number;
9
+ cost?: number;
10
+ };
11
+
12
+ export type OpenAiUsage = {
13
+ prompt_tokens: number;
14
+ completion_tokens: number;
15
+ total_tokens: number;
16
+ prompt_tokens_details: {
17
+ cached_tokens: number;
18
+ cache_write_tokens: number;
19
+ };
20
+ completion_tokens_details: {
21
+ reasoning_tokens: number;
22
+ };
23
+ cost?: number;
24
+ };
25
+
26
+ function readTokenCount(value: unknown): number {
27
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
28
+ return 0;
29
+ }
30
+ return Math.floor(value);
31
+ }
32
+
33
+ function readOptionalCost(value: unknown): number | undefined {
34
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
35
+ return undefined;
36
+ }
37
+ return value;
38
+ }
39
+
40
+ export function normalizeCursorUsage(value: unknown): CursorUsageMetrics | undefined {
41
+ if (!value || typeof value !== "object") {
42
+ return undefined;
43
+ }
44
+
45
+ const usage = value as Record<string, unknown>;
46
+ const metrics: CursorUsageMetrics = {
47
+ inputTokens: readTokenCount(usage.inputTokens ?? usage.input_tokens ?? usage.prompt_tokens),
48
+ outputTokens: readTokenCount(usage.outputTokens ?? usage.output_tokens ?? usage.completion_tokens),
49
+ reasoningTokens: readTokenCount(usage.reasoningTokens ?? usage.reasoning_tokens),
50
+ cacheReadTokens: readTokenCount(usage.cacheReadTokens ?? usage.cache_read_tokens),
51
+ cacheWriteTokens: readTokenCount(usage.cacheWriteTokens ?? usage.cache_write_tokens),
52
+ };
53
+
54
+ const cost = readOptionalCost(usage.cost ?? usage.totalCost ?? usage.total_cost);
55
+ if (cost !== undefined) {
56
+ metrics.cost = cost;
57
+ }
58
+
59
+ const hasUsage =
60
+ metrics.inputTokens > 0
61
+ || metrics.outputTokens > 0
62
+ || metrics.reasoningTokens > 0
63
+ || metrics.cacheReadTokens > 0
64
+ || metrics.cacheWriteTokens > 0
65
+ || cost !== undefined;
66
+
67
+ return hasUsage ? metrics : undefined;
68
+ }
69
+
70
+ export function createOpenAiUsage(metrics: CursorUsageMetrics): OpenAiUsage {
71
+ const promptTokens = metrics.inputTokens + metrics.cacheReadTokens + metrics.cacheWriteTokens;
72
+ const totalTokens = promptTokens + metrics.outputTokens + metrics.reasoningTokens;
73
+ const usage: OpenAiUsage = {
74
+ prompt_tokens: promptTokens,
75
+ completion_tokens: metrics.outputTokens,
76
+ total_tokens: totalTokens,
77
+ prompt_tokens_details: {
78
+ cached_tokens: metrics.cacheReadTokens,
79
+ cache_write_tokens: metrics.cacheWriteTokens,
80
+ },
81
+ completion_tokens_details: {
82
+ reasoning_tokens: metrics.reasoningTokens,
83
+ },
84
+ };
85
+
86
+ if (metrics.cost !== undefined) {
87
+ usage.cost = metrics.cost;
88
+ }
89
+
90
+ return usage;
91
+ }
92
+
93
+ export function extractOpenAiUsageFromResult(event: StreamJsonResultEvent): OpenAiUsage | undefined {
94
+ const metrics = normalizeCursorUsage(event.usage);
95
+ return metrics ? createOpenAiUsage(metrics) : undefined;
96
+ }
97
+
98
+ export function createChatCompletionUsageChunk(
99
+ id: string,
100
+ created: number,
101
+ model: string,
102
+ usage: OpenAiUsage,
103
+ ) {
104
+ return {
105
+ id,
106
+ object: "chat.completion.chunk",
107
+ created,
108
+ model,
109
+ choices: [],
110
+ usage,
111
+ };
112
+ }
@@ -0,0 +1,57 @@
1
+ // src/utils/binary.ts
2
+ //
3
+ // Resolves the cursor-agent executable path. On Windows the binary is a `.cmd`
4
+ // shim, which Node's spawn cannot execute directly without `shell: true` —
5
+ // callers therefore pair this resolver with `shell: process.platform === "win32"`
6
+ // at every spawn site. That re-enables shell metacharacter interpretation, so
7
+ // any user-controlled string passed as an argument on Windows must be treated
8
+ // as untrusted; never concatenate user input into argv on win32.
9
+ import { existsSync as fsExistsSync } from "fs";
10
+ import * as pathModule from "path";
11
+ import { homedir as osHomedir } from "os";
12
+ import { createLogger } from "./logger.js";
13
+
14
+ const log = createLogger("binary");
15
+
16
+ export type BinaryDeps = {
17
+ platform?: NodeJS.Platform;
18
+ env?: Record<string, string | undefined>;
19
+ existsSync?: (path: string) => boolean;
20
+ homedir?: () => string;
21
+ };
22
+
23
+ export function resolveCursorAgentBinary(deps: BinaryDeps = {}): string {
24
+ const platform = deps.platform ?? process.platform;
25
+ const env = deps.env ?? process.env;
26
+ const checkExists = deps.existsSync ?? fsExistsSync;
27
+ const home = (deps.homedir ?? osHomedir)();
28
+
29
+ const envOverride = env.CURSOR_AGENT_EXECUTABLE;
30
+ if (envOverride && envOverride.length > 0) {
31
+ return envOverride;
32
+ }
33
+
34
+ if (platform === "win32") {
35
+ const pathJoin = pathModule.win32.join;
36
+ const localAppData = env.LOCALAPPDATA ?? pathJoin(home, "AppData", "Local");
37
+ const knownPath = pathJoin(localAppData, "cursor-agent", "cursor-agent.cmd");
38
+ if (checkExists(knownPath)) {
39
+ return knownPath;
40
+ }
41
+ log.warn("cursor-agent not found at known Windows path, falling back to PATH", { checkedPath: knownPath });
42
+ return "cursor-agent.cmd";
43
+ }
44
+
45
+ const knownPaths = [
46
+ pathModule.join(home, ".cursor-agent", "cursor-agent"),
47
+ "/usr/local/bin/cursor-agent",
48
+ ];
49
+ for (const p of knownPaths) {
50
+ if (checkExists(p)) {
51
+ return p;
52
+ }
53
+ }
54
+
55
+ log.warn("cursor-agent not found at known paths, falling back to PATH", { checkedPaths: knownPaths });
56
+ return "cursor-agent";
57
+ }