@rama_nigg/open-cursor 2.3.19 → 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.
package/src/mcp/config.ts CHANGED
@@ -93,6 +93,55 @@ export function readMcpConfigs(deps: ReadMcpConfigsDeps = {}): McpServerConfig[]
93
93
  return configs;
94
94
  }
95
95
 
96
+ interface ReadSubagentNamesDeps {
97
+ configJson?: string;
98
+ existsSync?: (path: string) => boolean;
99
+ readFileSync?: (path: string, enc: BufferEncoding) => string;
100
+ env?: NodeJS.ProcessEnv;
101
+ }
102
+
103
+ export function readSubagentNames(deps: ReadSubagentNamesDeps = {}): string[] {
104
+ let raw: string;
105
+
106
+ if (deps.configJson != null) {
107
+ raw = deps.configJson;
108
+ } else {
109
+ const exists = deps.existsSync ?? nodeExistsSync;
110
+ const readFile = deps.readFileSync ?? nodeReadFileSync;
111
+ const configPath = resolveOpenCodeConfigPath(deps.env ?? process.env);
112
+ if (!exists(configPath)) return ["general-purpose"];
113
+ try {
114
+ raw = readFile(configPath, "utf8");
115
+ } catch {
116
+ return ["general-purpose"];
117
+ }
118
+ }
119
+
120
+ let parsed: Record<string, unknown>;
121
+ try {
122
+ parsed = JSON.parse(raw);
123
+ } catch {
124
+ return ["general-purpose"];
125
+ }
126
+
127
+ const agentSection = parsed.agent;
128
+ if (!agentSection || typeof agentSection !== "object" || Array.isArray(agentSection)) {
129
+ return ["general-purpose"];
130
+ }
131
+
132
+ const agents = agentSection as Record<string, unknown>;
133
+ const names = Object.keys(agents);
134
+ if (names.length === 0) return ["general-purpose"];
135
+
136
+ const subagentNames = names.filter((name) => {
137
+ const entry = agents[name];
138
+ return entry && typeof entry === "object" && !Array.isArray(entry)
139
+ && (entry as Record<string, unknown>).mode === "subagent";
140
+ });
141
+
142
+ return subagentNames.length > 0 ? subagentNames : names;
143
+ }
144
+
96
145
  function isStringRecord(v: unknown): v is Record<string, string> {
97
146
  return typeof v === "object" && v !== null && !Array.isArray(v);
98
147
  }
@@ -109,7 +109,7 @@ function mcpSchemaToZod(inputSchema: Record<string, unknown> | undefined, z: any
109
109
  zodType = z.array(z.any());
110
110
  break;
111
111
  case "object":
112
- zodType = z.record(z.any());
112
+ zodType = z.record(z.string(), z.any());
113
113
  break;
114
114
  default:
115
115
  zodType = z.any();
@@ -1,4 +1,5 @@
1
1
  import type { ModelInfo, DiscoveryConfig } from "./types.js";
2
+ import { resolveCursorAgentBinary } from "../utils/binary.js";
2
3
 
3
4
  interface CacheEntry {
4
5
  models: ModelInfo[];
@@ -51,7 +52,7 @@ export class ModelDiscoveryService {
51
52
  private async queryViaCLI(): Promise<ModelInfo[]> {
52
53
  try {
53
54
  const bunAny = (globalThis as any).Bun;
54
- const proc = bunAny.spawn(["cursor-agent", "models", "--json"], {
55
+ const proc = bunAny.spawn([resolveCursorAgentBinary(), "models", "--json"], {
55
56
  timeout: 5000,
56
57
  stdout: "pipe",
57
58
  stderr: "pipe"
@@ -78,7 +79,7 @@ export class ModelDiscoveryService {
78
79
  private async queryViaHelp(): Promise<ModelInfo[]> {
79
80
  try {
80
81
  const bunAny = (globalThis as any).Bun;
81
- const proc = bunAny.spawn(["cursor-agent", "--help"], {
82
+ const proc = bunAny.spawn([resolveCursorAgentBinary(), "--help"], {
82
83
  timeout: 5000,
83
84
  stdout: "pipe",
84
85
  stderr: "pipe"
@@ -0,0 +1,196 @@
1
+ export type OpenCodeModelCost = {
2
+ input: number;
3
+ output: number;
4
+ cache_read?: number;
5
+ cache_write?: number;
6
+ context_over_200k?: OpenCodeModelCost;
7
+ };
8
+
9
+ export type CursorPricingCoverage = {
10
+ priced: string[];
11
+ missing: string[];
12
+ };
13
+
14
+ export type OpenCodeModelCostValidation = {
15
+ valid: boolean;
16
+ errors: string[];
17
+ };
18
+
19
+ export const CURSOR_PRICING_DOC_URL = "https://cursor.com/docs/models-and-pricing";
20
+
21
+ export const CURSOR_PRICING_DOC_MARKERS = [
22
+ "Auto",
23
+ "Composer 2",
24
+ "Composer 1.5",
25
+ "Claude 4.6",
26
+ "Claude 4.6 Sonnet",
27
+ "Claude Opus 4.7",
28
+ "Gemini 3.1 Pro",
29
+ "Gemini 3 Flash",
30
+ "GPT-5.3 Codex",
31
+ "GPT-5.4",
32
+ "GPT-5.5",
33
+ "Grok 4.20",
34
+ "Kimi K2.5",
35
+ ];
36
+
37
+ // Official Cursor prices per 1M tokens from https://cursor.com/docs/models-and-pricing.
38
+ const AUTO_COST = cost(1.25, 6, 0.25, 1.25);
39
+ const COMPOSER_2_COST = cost(0.5, 2.5, 0.2, 0.5);
40
+ const COMPOSER_2_FAST_COST = cost(1.5, 7.5, 0.35, 1.5);
41
+ const COMPOSER_1_5_COST = cost(3.5, 17.5, 0.35, 3.5);
42
+
43
+ const CLAUDE_SONNET_COST = cost(3, 15, 0.3, 3.75);
44
+ const CLAUDE_SONNET_LONG_CONTEXT_COST = cost(6, 22.5, 0.6, 7.5);
45
+ const CLAUDE_SONNET_WITH_LONG_CONTEXT_COST = withLongContext(
46
+ CLAUDE_SONNET_COST,
47
+ CLAUDE_SONNET_LONG_CONTEXT_COST,
48
+ );
49
+ const CLAUDE_OPUS_COST = cost(5, 25, 0.5, 6.25);
50
+ const CLAUDE_OPUS_FAST_COST = cost(30, 150, 3, 37.5);
51
+
52
+ const GEMINI_3_PRO_COST = withLongContext(cost(2, 12, 0.2, 2), cost(4, 18, 0.4, 4));
53
+ const GEMINI_3_FLASH_COST = cost(0.5, 3, 0.05, 0.5);
54
+
55
+ const GPT_5_1_COST = cost(1.25, 10, 0.125, 1.25);
56
+ const GPT_5_2_COST = cost(1.75, 14, 0.175, 1.75);
57
+ const GPT_5_3_CODEX_COST = cost(1.75, 14, 0.175, 1.75);
58
+ const GPT_5_4_COST = withLongContext(cost(2.5, 15, 0.25, 2.5), cost(5, 22.5, 0.5, 5));
59
+ const GPT_5_4_FAST_COST = cost(5, 30, 0.5, 5);
60
+ const GPT_5_4_MINI_COST = cost(0.75, 4.5, 0.075, 0.75);
61
+ const GPT_5_4_NANO_COST = cost(0.2, 1.25, 0.02, 0.2);
62
+ const GPT_5_5_COST = withLongContext(cost(5, 30, 0.5, 5), cost(10, 45, 1, 10));
63
+ const GPT_5_MINI_COST = cost(0.25, 2, 0.025, 0.25);
64
+
65
+ const GROK_4_20_COST = withLongContext(cost(2, 6, 0.2, 2), cost(4, 12, 0.4, 4));
66
+ const KIMI_K2_5_COST = cost(0.6, 3, 0.1, 0.6);
67
+
68
+ export function getCursorModelCost(modelId: string): OpenCodeModelCost | undefined {
69
+ if (modelId === "auto") return AUTO_COST;
70
+ if (modelId === "composer-2-fast") return COMPOSER_2_FAST_COST;
71
+ if (modelId === "composer-2") return COMPOSER_2_COST;
72
+ if (modelId === "composer-1.5") return COMPOSER_1_5_COST;
73
+
74
+ if (modelId.startsWith("claude-opus-4-7")) return CLAUDE_OPUS_COST;
75
+ if (modelId.startsWith("claude-4.6-opus")) {
76
+ return modelId.endsWith("-fast") ? CLAUDE_OPUS_FAST_COST : CLAUDE_OPUS_COST;
77
+ }
78
+ if (modelId.startsWith("claude-4.5-opus")) return CLAUDE_OPUS_COST;
79
+ if (modelId.startsWith("claude-4.6-sonnet")) return CLAUDE_SONNET_WITH_LONG_CONTEXT_COST;
80
+ if (modelId.startsWith("claude-4.5-sonnet")) return CLAUDE_SONNET_WITH_LONG_CONTEXT_COST;
81
+ if (modelId.startsWith("claude-4-sonnet")) return CLAUDE_SONNET_COST;
82
+
83
+ if (modelId === "gemini-3.1-pro") return GEMINI_3_PRO_COST;
84
+ if (modelId === "gemini-3-flash") return GEMINI_3_FLASH_COST;
85
+
86
+ if (modelId.startsWith("gpt-5.5")) return GPT_5_5_COST;
87
+ if (modelId.startsWith("gpt-5.4-mini")) return GPT_5_4_MINI_COST;
88
+ if (modelId.startsWith("gpt-5.4-nano")) return GPT_5_4_NANO_COST;
89
+ if (modelId.startsWith("gpt-5.4")) {
90
+ return modelId.endsWith("-fast") ? GPT_5_4_FAST_COST : GPT_5_4_COST;
91
+ }
92
+ if (modelId.startsWith("gpt-5.3-codex")) return GPT_5_3_CODEX_COST;
93
+ if (modelId.startsWith("gpt-5.2-codex")) return GPT_5_2_COST;
94
+ if (modelId.startsWith("gpt-5.2")) return GPT_5_2_COST;
95
+ if (modelId.startsWith("gpt-5.1-codex-mini")) return GPT_5_MINI_COST;
96
+ if (modelId.startsWith("gpt-5.1-codex-max")) return GPT_5_1_COST;
97
+ if (modelId.startsWith("gpt-5.1")) return GPT_5_1_COST;
98
+ if (modelId === "gpt-5-mini") return GPT_5_MINI_COST;
99
+
100
+ if (modelId.startsWith("grok-4-20")) return GROK_4_20_COST;
101
+ if (modelId === "kimi-k2.5") return KIMI_K2_5_COST;
102
+
103
+ return undefined;
104
+ }
105
+
106
+ export function applyCursorModelCost<T extends Record<string, unknown>>(
107
+ modelId: string,
108
+ entry: T,
109
+ ): T & { cost?: OpenCodeModelCost } {
110
+ const modelCost = getCursorModelCost(modelId);
111
+ if (!modelCost) return entry;
112
+ return { ...entry, cost: modelCost };
113
+ }
114
+
115
+ export function checkCursorPricingCoverage(modelIds: string[]): CursorPricingCoverage {
116
+ const priced: string[] = [];
117
+ const missing: string[] = [];
118
+
119
+ for (const modelId of modelIds) {
120
+ if (getCursorModelCost(modelId)) {
121
+ priced.push(modelId);
122
+ } else {
123
+ missing.push(modelId);
124
+ }
125
+ }
126
+
127
+ return { priced, missing };
128
+ }
129
+
130
+ export function validateOpenCodeModelCost(
131
+ value: unknown,
132
+ path = "cost",
133
+ ): OpenCodeModelCostValidation {
134
+ const errors: string[] = [];
135
+ collectCostValidationErrors(value, path, errors);
136
+ return { valid: errors.length === 0, errors };
137
+ }
138
+
139
+ export function isOpenCodeModelCost(value: unknown): value is OpenCodeModelCost {
140
+ return validateOpenCodeModelCost(value).valid;
141
+ }
142
+
143
+ function cost(input: number, output: number, cacheRead: number, cacheWrite: number): OpenCodeModelCost {
144
+ return {
145
+ input,
146
+ output,
147
+ cache_read: cacheRead,
148
+ cache_write: cacheWrite,
149
+ };
150
+ }
151
+
152
+ function withLongContext(
153
+ base: OpenCodeModelCost,
154
+ longContext: OpenCodeModelCost,
155
+ ): OpenCodeModelCost {
156
+ return {
157
+ ...base,
158
+ context_over_200k: longContext,
159
+ };
160
+ }
161
+
162
+ function collectCostValidationErrors(value: unknown, path: string, errors: string[]): void {
163
+ if (!isRecord(value)) {
164
+ errors.push(`${path} must be an object`);
165
+ return;
166
+ }
167
+
168
+ validateRequiredPrice(value.input, `${path}.input`, errors);
169
+ validateRequiredPrice(value.output, `${path}.output`, errors);
170
+ validateOptionalPrice(value.cache_read, `${path}.cache_read`, errors);
171
+ validateOptionalPrice(value.cache_write, `${path}.cache_write`, errors);
172
+
173
+ if (value.context_over_200k !== undefined) {
174
+ collectCostValidationErrors(value.context_over_200k, `${path}.context_over_200k`, errors);
175
+ }
176
+ }
177
+
178
+ function validateRequiredPrice(value: unknown, path: string, errors: string[]): void {
179
+ if (!isNonNegativeFiniteNumber(value)) {
180
+ errors.push(`${path} must be a non-negative finite number`);
181
+ }
182
+ }
183
+
184
+ function validateOptionalPrice(value: unknown, path: string, errors: string[]): void {
185
+ if (value !== undefined && !isNonNegativeFiniteNumber(value)) {
186
+ errors.push(`${path} must be a non-negative finite number`);
187
+ }
188
+ }
189
+
190
+ function isNonNegativeFiniteNumber(value: unknown): value is number {
191
+ return typeof value === "number" && Number.isFinite(value) && value >= 0;
192
+ }
193
+
194
+ function isRecord(value: unknown): value is Record<string, unknown> {
195
+ return typeof value === "object" && value !== null && !Array.isArray(value);
196
+ }