pi-cursor-sdk 0.1.8 → 0.1.10

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.
@@ -7,7 +7,8 @@ import type {
7
7
  } from "@cursor/sdk";
8
8
  import { AuthStorage, type ProviderModelConfig } from "@earendil-works/pi-coding-agent";
9
9
  import type { ModelThinkingLevel, ThinkingLevelMap } from "@earendil-works/pi-ai";
10
- import { getCachedContextWindow, getCachedContextWindowExact } from "./context-window-cache.js";
10
+ import { loadContextWindowCache } from "./context-window-cache.js";
11
+ import { FALLBACK_MODEL_ITEMS } from "./cursor-fallback-models.generated.js";
11
12
 
12
13
  const CURSOR_PROVIDER_ID = "cursor";
13
14
  const CURSOR_API_KEY_ENV_VAR = "CURSOR_API_KEY";
@@ -17,154 +18,14 @@ const ZERO_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
17
18
  const TEXT_AND_IMAGE_INPUT: ProviderModelConfig["input"] = ["text", "image"];
18
19
  const AUTH_SETUP_HINT = "/login (Use an API key -> Cursor), CURSOR_API_KEY, or --api-key";
19
20
  const CATALOG_REFRESH_HINT =
20
- "After adding auth to an already-started pi session, run /reload or restart pi to refresh the full live Cursor model catalog.";
21
-
22
- const FALLBACK_MODEL_ITEMS: ModelListItem[] = [
23
- {
24
- id: "composer-2",
25
- displayName: "Cursor Composer 2",
26
- parameters: [
27
- {
28
- id: "fast",
29
- displayName: "Fast",
30
- values: [{ value: "false" }, { value: "true" }],
31
- },
32
- ],
33
- variants: [
34
- {
35
- params: [{ id: "fast", value: "true" }],
36
- displayName: "Cursor Composer 2",
37
- isDefault: true,
38
- },
39
- ],
40
- },
41
- {
42
- id: "gpt-5.5",
43
- displayName: "GPT-5.5",
44
- parameters: [
45
- {
46
- id: "context",
47
- displayName: "Context",
48
- values: [{ value: "1m" }, { value: "272k" }],
49
- },
50
- {
51
- id: "reasoning",
52
- displayName: "Reasoning",
53
- values: [
54
- { value: "none" },
55
- { value: "low" },
56
- { value: "medium" },
57
- { value: "high" },
58
- { value: "extra-high" },
59
- ],
60
- },
61
- {
62
- id: "fast",
63
- displayName: "Fast",
64
- values: [{ value: "false" }, { value: "true" }],
65
- },
66
- ],
67
- variants: [
68
- {
69
- params: [
70
- { id: "context", value: "1m" },
71
- { id: "reasoning", value: "medium" },
72
- { id: "fast", value: "false" },
73
- ],
74
- displayName: "GPT-5.5",
75
- isDefault: true,
76
- },
77
- ],
78
- },
79
- {
80
- id: "claude-sonnet-4-6",
81
- displayName: "Sonnet 4.6",
82
- parameters: [
83
- {
84
- id: "thinking",
85
- displayName: "Thinking",
86
- values: [{ value: "false" }, { value: "true" }],
87
- },
88
- {
89
- id: "context",
90
- displayName: "Context",
91
- values: [{ value: "1m" }, { value: "300k" }],
92
- },
93
- {
94
- id: "effort",
95
- displayName: "Effort",
96
- values: [
97
- { value: "low" },
98
- { value: "medium" },
99
- { value: "high" },
100
- { value: "xhigh" },
101
- { value: "max" },
102
- ],
103
- },
104
- {
105
- id: "fast",
106
- displayName: "Fast",
107
- values: [{ value: "false" }, { value: "true" }],
108
- },
109
- ],
110
- variants: [
111
- {
112
- params: [
113
- { id: "thinking", value: "true" },
114
- { id: "context", value: "1m" },
115
- { id: "effort", value: "medium" },
116
- { id: "fast", value: "false" },
117
- ],
118
- displayName: "Sonnet 4.6",
119
- isDefault: true,
120
- },
121
- ],
122
- },
123
- {
124
- id: "claude-opus-4-7",
125
- displayName: "Opus 4.7",
126
- parameters: [
127
- {
128
- id: "thinking",
129
- displayName: "Thinking",
130
- values: [{ value: "false" }, { value: "true" }],
131
- },
132
- {
133
- id: "context",
134
- displayName: "Context",
135
- values: [{ value: "1m" }, { value: "300k" }],
136
- },
137
- {
138
- id: "effort",
139
- displayName: "Effort",
140
- values: [
141
- { value: "low" },
142
- { value: "medium" },
143
- { value: "high" },
144
- { value: "xhigh" },
145
- { value: "max" },
146
- ],
147
- },
148
- ],
149
- variants: [
150
- {
151
- params: [
152
- { id: "thinking", value: "true" },
153
- { id: "context", value: "1m" },
154
- { id: "effort", value: "xhigh" },
155
- ],
156
- displayName: "Opus 4.7",
157
- isDefault: true,
158
- },
159
- ],
160
- },
161
- ];
21
+ "After adding auth to an already-started pi session, run /cursor-refresh-models to refresh the full live Cursor model catalog without restarting pi.";
162
22
 
163
23
  export type CursorModelFallbackReason = "missing-api-key" | "discovery-failed" | "empty-model-list";
164
24
 
165
25
  export interface CursorModelFallbackIssue {
166
26
  reason: CursorModelFallbackReason;
167
27
  message: string;
28
+ errorMessage?: string;
168
29
  }
169
30
 
170
31
  export interface DiscoverModelsOptions {
@@ -351,9 +212,14 @@ function getModelName(item: ModelListItem, context?: string, alias?: string): st
351
212
  return context ? `${baseName} @ ${context}` : baseName;
352
213
  }
353
214
 
354
- function getContextWindow(piModelId: string, context?: string, baseModelId?: string): number {
355
- if (context) return parseContextWindow(context) ?? FALLBACK_CONTEXT_WINDOW;
356
- return getCachedContextWindowExact(piModelId) ?? (baseModelId ? getCachedContextWindow(baseModelId) : undefined) ?? FALLBACK_CONTEXT_WINDOW;
215
+ function getContextWindow(contextWindowCache: Map<string, number>, piModelId: string, context?: string, baseModelId?: string): number {
216
+ return (
217
+ contextWindowCache.get(piModelId) ??
218
+ (context ? parseContextWindow(context) : undefined) ??
219
+ (baseModelId ? contextWindowCache.get(baseModelId) : undefined) ??
220
+ contextWindowCache.get("default") ??
221
+ FALLBACK_CONTEXT_WINDOW
222
+ );
357
223
  }
358
224
 
359
225
  function toMetadata(
@@ -362,6 +228,7 @@ function toMetadata(
362
228
  selectionModelId: string,
363
229
  defaultParams: ModelParameterValue[],
364
230
  context: string | undefined,
231
+ contextWindowCache: Map<string, number>,
365
232
  ): CursorModelMetadata {
366
233
  const thinkingLevelMap = getThinkingLevelMap(item);
367
234
  const fastValue = getParamValue(defaultParams, "fast")?.toLowerCase();
@@ -372,7 +239,7 @@ function toMetadata(
372
239
  displayName: item.displayName || item.id,
373
240
  defaultParams: cloneParams(defaultParams),
374
241
  ...(context ? { context } : {}),
375
- contextWindow: getContextWindow(piModelId, context, item.id),
242
+ contextWindow: getContextWindow(contextWindowCache, piModelId, context, item.id),
376
243
  supportsFast: getParameter(item, "fast") !== undefined,
377
244
  defaultFast: fastValue === "true",
378
245
  supportsReasoning: thinkingLevelMap !== undefined,
@@ -404,11 +271,25 @@ function getContextValues(item: ModelListItem): string[] {
404
271
  return getParameter(item, "context")?.values.map((value) => value.value) ?? [];
405
272
  }
406
273
 
407
- function getModelIds(item: ModelListItem, reservedBaseModelIds: Set<string>): string[] {
274
+ function getAmbiguousAliases(items: ModelListItem[]): Set<string> {
275
+ const aliasOwners = new Map<string, Set<string>>();
276
+ for (const item of items) {
277
+ for (const rawAlias of item.aliases ?? []) {
278
+ const alias = rawAlias.trim();
279
+ if (!alias || alias === item.id) continue;
280
+ const owners = aliasOwners.get(alias) ?? new Set<string>();
281
+ owners.add(item.id);
282
+ aliasOwners.set(alias, owners);
283
+ }
284
+ }
285
+ return new Set([...aliasOwners.entries()].filter(([, owners]) => owners.size > 1).map(([alias]) => alias));
286
+ }
287
+
288
+ function getModelIds(item: ModelListItem, reservedBaseModelIds: Set<string>, ambiguousAliases: Set<string>): string[] {
408
289
  const ids = [item.id];
409
290
  for (const rawAlias of item.aliases ?? []) {
410
291
  const alias = rawAlias.trim();
411
- if (!alias || alias === item.id || ids.includes(alias) || reservedBaseModelIds.has(alias)) continue;
292
+ if (!alias || alias === item.id || ids.includes(alias) || reservedBaseModelIds.has(alias) || ambiguousAliases.has(alias)) continue;
412
293
  ids.push(alias);
413
294
  }
414
295
  return ids;
@@ -418,20 +299,22 @@ function toModelConfigs(
418
299
  item: ModelListItem,
419
300
  usedPiModelIds: Set<string>,
420
301
  reservedBaseModelIds: Set<string>,
302
+ ambiguousAliases: Set<string>,
303
+ contextWindowCache: Map<string, number>,
421
304
  ): ProviderModelConfig[] {
422
305
  const defaultParams = getDefaultParams(item);
423
306
  const contextValues = getContextValues(item);
424
307
  const contexts = contextValues.length > 0 ? contextValues : [undefined];
425
308
  const configs: ProviderModelConfig[] = [];
426
309
 
427
- for (const selectionModelId of getModelIds(item, reservedBaseModelIds)) {
310
+ for (const selectionModelId of getModelIds(item, reservedBaseModelIds, ambiguousAliases)) {
428
311
  const alias = selectionModelId === item.id ? undefined : selectionModelId;
429
312
  for (const context of contexts) {
430
313
  const params = context ? replaceParam(defaultParams, "context", context) : defaultParams;
431
314
  const piModelId = encodePiModelId(selectionModelId, context);
432
315
  if (usedPiModelIds.has(piModelId)) continue;
433
316
  usedPiModelIds.add(piModelId);
434
- const metadata = toMetadata(item, piModelId, selectionModelId, params, context);
317
+ const metadata = toMetadata(item, piModelId, selectionModelId, params, context, contextWindowCache);
435
318
  metadataByPiModelId.set(piModelId, metadata);
436
319
  configs.push(toModelConfig(metadata, getModelName(item, context, alias)));
437
320
  }
@@ -448,7 +331,9 @@ function registerModelItems(items: ModelListItem[]): ProviderModelConfig[] {
448
331
  metadataByPiModelId.clear();
449
332
  const usedPiModelIds = new Set<string>();
450
333
  const reservedBaseModelIds = new Set(items.map((item) => item.id));
451
- return sortModelsByBaseId(items).flatMap((item) => toModelConfigs(item, usedPiModelIds, reservedBaseModelIds));
334
+ const ambiguousAliases = getAmbiguousAliases(items);
335
+ const contextWindowCache = loadContextWindowCache();
336
+ return sortModelsByBaseId(items).flatMap((item) => toModelConfigs(item, usedPiModelIds, reservedBaseModelIds, ambiguousAliases, contextWindowCache));
452
337
  }
453
338
 
454
339
  export function getCursorModelMetadata(modelId: string): CursorModelMetadata | undefined {
@@ -532,6 +417,24 @@ export function buildCursorModelSelection(
532
417
  return params.length > 0 ? { id: metadata.selectionModelId, params } : { id: metadata.selectionModelId };
533
418
  }
534
419
 
420
+ function scrubDiscoveryErrorText(text: string, apiKey: string): string {
421
+ let scrubbed = text.replace(new RegExp(apiKey.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"), "[redacted]");
422
+ return scrubbed
423
+ .replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [redacted]")
424
+ .replace(/((?:^|[\s,{])cookie["']?\s*[:=]\s*["']?)[^\n]+/gi, "$1[redacted]")
425
+ .replace(
426
+ /((?:authorization|api[_-]?key|apiKey|token|session(?:[_-]?id)?)["']?\s*[:=]\s*["']?)[^"'\s,;}]+/gi,
427
+ "$1[redacted]",
428
+ )
429
+ .trim();
430
+ }
431
+
432
+ function sanitizeDiscoveryError(error: unknown, apiKey: string): string | undefined {
433
+ const message = error instanceof Error ? error.message : typeof error === "string" ? error : "";
434
+ const scrubbed = scrubDiscoveryErrorText(message, apiKey);
435
+ return scrubbed || undefined;
436
+ }
437
+
535
438
  function useFallbackModels(options: DiscoverModelsOptions, issue: CursorModelFallbackIssue): ProviderModelConfig[] {
536
439
  options.onFallback?.(issue);
537
440
  return registerModelItems(FALLBACK_MODEL_ITEMS);
@@ -555,10 +458,12 @@ export async function discoverModels(options: DiscoverModelsOptions = {}): Promi
555
458
  reason: "empty-model-list",
556
459
  message: `Cursor model discovery returned no models. Using fallback Cursor models; verify ${AUTH_SETUP_HINT}. ${CATALOG_REFRESH_HINT}`,
557
460
  });
558
- } catch {
461
+ } catch (error) {
462
+ const errorMessage = sanitizeDiscoveryError(error, apiKey);
559
463
  return useFallbackModels(options, {
560
464
  reason: "discovery-failed",
561
- message: `Cursor model discovery failed. Using fallback Cursor models; verify ${AUTH_SETUP_HINT}. ${CATALOG_REFRESH_HINT}`,
465
+ message: `Cursor model discovery failed${errorMessage ? `: ${errorMessage}` : ""}. Using fallback Cursor models; verify ${AUTH_SETUP_HINT}. ${CATALOG_REFRESH_HINT}`,
466
+ ...(errorMessage ? { errorMessage } : {}),
562
467
  });
563
468
  }
564
469
  }