pi-cursor-sdk 0.1.62 → 0.2.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.
Files changed (128) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +5 -5
  3. package/docs/cursor-live-smoke-checklist.md +4 -4
  4. package/docs/cursor-model-ux-spec.md +5 -3
  5. package/docs/cursor-native-tool-visual-audit.md +1 -1
  6. package/docs/cursor-testing-lessons.md +1 -1
  7. package/docs/platform-smoke.md +3 -3
  8. package/node_modules/fast-uri/index.js +37 -1
  9. package/node_modules/fast-uri/package.json +1 -1
  10. package/node_modules/fast-uri/test/security.test.js +136 -0
  11. package/node_modules/hono/dist/cjs/context.js +28 -13
  12. package/node_modules/hono/dist/cjs/helper/proxy/index.js +4 -0
  13. package/node_modules/hono/dist/cjs/helper/streaming/sse.js +1 -1
  14. package/node_modules/hono/dist/cjs/hono-base.js +3 -2
  15. package/node_modules/hono/dist/cjs/jsx/base.js +27 -23
  16. package/node_modules/hono/dist/cjs/jsx/hooks/index.js +16 -13
  17. package/node_modules/hono/dist/cjs/middleware/cache/index.js +103 -8
  18. package/node_modules/hono/dist/cjs/middleware/compress/index.js +5 -0
  19. package/node_modules/hono/dist/cjs/middleware/cors/index.js +2 -2
  20. package/node_modules/hono/dist/cjs/middleware/etag/index.js +1 -1
  21. package/node_modules/hono/dist/cjs/middleware/jwk/jwk.js +9 -4
  22. package/node_modules/hono/dist/cjs/middleware/jwt/jwt.js +9 -4
  23. package/node_modules/hono/dist/cjs/middleware/language/language.js +10 -6
  24. package/node_modules/hono/dist/cjs/middleware/method-not-allowed/index.js +90 -0
  25. package/node_modules/hono/dist/cjs/middleware/secure-headers/secure-headers.js +10 -4
  26. package/node_modules/hono/dist/cjs/request.js +7 -9
  27. package/node_modules/hono/dist/cjs/router/reg-exp-router/node.js +55 -56
  28. package/node_modules/hono/dist/cjs/router/reg-exp-router/router.js +64 -85
  29. package/node_modules/hono/dist/cjs/router/reg-exp-router/trie.js +13 -5
  30. package/node_modules/hono/dist/cjs/router.js +1 -1
  31. package/node_modules/hono/dist/cjs/utils/accept.js +1 -1
  32. package/node_modules/hono/dist/cjs/utils/cookie.js +3 -2
  33. package/node_modules/hono/dist/cjs/utils/url.js +8 -8
  34. package/node_modules/hono/dist/context.js +28 -13
  35. package/node_modules/hono/dist/helper/proxy/index.js +4 -0
  36. package/node_modules/hono/dist/helper/streaming/sse.js +1 -1
  37. package/node_modules/hono/dist/hono-base.js +3 -2
  38. package/node_modules/hono/dist/jsx/base.js +27 -23
  39. package/node_modules/hono/dist/jsx/hooks/index.js +16 -13
  40. package/node_modules/hono/dist/middleware/cache/index.js +103 -8
  41. package/node_modules/hono/dist/middleware/compress/index.js +5 -0
  42. package/node_modules/hono/dist/middleware/cors/index.js +2 -2
  43. package/node_modules/hono/dist/middleware/etag/index.js +1 -1
  44. package/node_modules/hono/dist/middleware/jwk/jwk.js +9 -4
  45. package/node_modules/hono/dist/middleware/jwt/jwt.js +9 -4
  46. package/node_modules/hono/dist/middleware/language/language.js +10 -6
  47. package/node_modules/hono/dist/middleware/method-not-allowed/index.js +68 -0
  48. package/node_modules/hono/dist/middleware/secure-headers/secure-headers.js +10 -4
  49. package/node_modules/hono/dist/request.js +8 -10
  50. package/node_modules/hono/dist/router/reg-exp-router/node.js +55 -56
  51. package/node_modules/hono/dist/router/reg-exp-router/router.js +64 -85
  52. package/node_modules/hono/dist/router/reg-exp-router/trie.js +13 -5
  53. package/node_modules/hono/dist/router.js +1 -1
  54. package/node_modules/hono/dist/types/adapter/aws-lambda/types.d.ts +9 -0
  55. package/node_modules/hono/dist/types/client/types.d.ts +1 -1
  56. package/node_modules/hono/dist/types/hono-base.d.ts +4 -3
  57. package/node_modules/hono/dist/types/jsx/base.d.ts +4 -2
  58. package/node_modules/hono/dist/types/jsx/dom/index.d.ts +5 -5
  59. package/node_modules/hono/dist/types/jsx/dom/intrinsic-element/components.d.ts +2 -2
  60. package/node_modules/hono/dist/types/jsx/dom/server.d.ts +5 -5
  61. package/node_modules/hono/dist/types/jsx/hooks/index.d.ts +8 -6
  62. package/node_modules/hono/dist/types/jsx/index.d.ts +5 -5
  63. package/node_modules/hono/dist/types/middleware/cache/index.d.ts +6 -4
  64. package/node_modules/hono/dist/types/middleware/cors/index.d.ts +1 -1
  65. package/node_modules/hono/dist/types/middleware/jsx-renderer/index.d.ts +2 -2
  66. package/node_modules/hono/dist/types/middleware/jwk/jwk.d.ts +2 -0
  67. package/node_modules/hono/dist/types/middleware/jwt/jwt.d.ts +2 -0
  68. package/node_modules/hono/dist/types/middleware/method-not-allowed/index.d.ts +49 -0
  69. package/node_modules/hono/dist/types/router/reg-exp-router/node.d.ts +1 -1
  70. package/node_modules/hono/dist/types/router/reg-exp-router/trie.d.ts +2 -1
  71. package/node_modules/hono/dist/types/router.d.ts +1 -1
  72. package/node_modules/hono/dist/types/utils/headers.d.ts +2 -2
  73. package/node_modules/hono/dist/types/utils/url.d.ts +1 -0
  74. package/node_modules/hono/dist/utils/accept.js +1 -1
  75. package/node_modules/hono/dist/utils/cookie.js +4 -3
  76. package/node_modules/hono/dist/utils/url.js +6 -7
  77. package/node_modules/hono/package.json +10 -3
  78. package/node_modules/ip-address/README.md +146 -126
  79. package/node_modules/ip-address/dist/common.d.ts +29 -0
  80. package/node_modules/ip-address/dist/common.js +44 -5
  81. package/node_modules/ip-address/dist/common.js.map +1 -1
  82. package/node_modules/ip-address/dist/ipv4.d.ts +8 -1
  83. package/node_modules/ip-address/dist/ipv4.js +24 -20
  84. package/node_modules/ip-address/dist/ipv4.js.map +1 -1
  85. package/node_modules/ip-address/dist/ipv6.d.ts +68 -3
  86. package/node_modules/ip-address/dist/ipv6.js +180 -53
  87. package/node_modules/ip-address/dist/ipv6.js.map +1 -1
  88. package/node_modules/ip-address/dist/v4/constants.js +5 -1
  89. package/node_modules/ip-address/dist/v4/constants.js.map +1 -1
  90. package/node_modules/ip-address/dist/v6/constants.js +3 -2
  91. package/node_modules/ip-address/dist/v6/constants.js.map +1 -1
  92. package/node_modules/ip-address/package.json +6 -4
  93. package/package.json +5 -5
  94. package/scripts/platform-smoke/live-suite-runner.mjs +15 -39
  95. package/scripts/platform-smoke/platform-build-windows.ps1 +1 -1
  96. package/scripts/platform-smoke/scenarios.mjs +1 -1
  97. package/scripts/platform-smoke.mjs +5 -5
  98. package/scripts/refresh-cursor-model-snapshots.mjs +16 -7
  99. package/shared/cursor-model-selection-identities.d.mts +21 -0
  100. package/shared/cursor-model-selection-identities.mjs +95 -0
  101. package/src/bundled-context-windows.ts +18 -12
  102. package/src/context.ts +1 -1
  103. package/src/cursor-context-tools.ts +1 -1
  104. package/src/cursor-display-only-trace.ts +1 -1
  105. package/src/cursor-fallback-models.generated.ts +875 -361
  106. package/src/cursor-live-run-accounting.ts +1 -1
  107. package/src/cursor-live-run-coordinator.ts +1 -1
  108. package/src/cursor-native-replay-routing.ts +1 -1
  109. package/src/cursor-partial-content-emitter.ts +1 -1
  110. package/src/cursor-pi-tool-bridge-mcp.ts +1 -1
  111. package/src/cursor-pi-tool-bridge-run.ts +1 -1
  112. package/src/cursor-pi-tool-bridge-types.ts +1 -1
  113. package/src/cursor-provider-lazy.ts +1 -1
  114. package/src/cursor-provider-live-run-drain.ts +1 -1
  115. package/src/cursor-provider-overflow.ts +1 -1
  116. package/src/cursor-provider-run-finalizer.ts +1 -1
  117. package/src/cursor-provider-turn-coordinator.ts +1 -1
  118. package/src/cursor-provider-turn-lifecycle-emitter.ts +1 -1
  119. package/src/cursor-provider-turn-prepare.ts +1 -1
  120. package/src/cursor-provider-turn-types.ts +1 -1
  121. package/src/cursor-provider.ts +1 -1
  122. package/src/cursor-run-final-text.ts +1 -1
  123. package/src/cursor-sdk-event-debug.ts +1 -1
  124. package/src/cursor-session-agent.ts +1 -1
  125. package/src/cursor-session-send-policy.ts +1 -1
  126. package/src/cursor-usage-accounting.ts +1 -1
  127. package/src/model-discovery.ts +32 -89
  128. package/node_modules/cross-spawn/node_modules/which/CHANGELOG.md +0 -166
@@ -19,7 +19,6 @@ import { getScenario, renderPrompt } from "./scenarios.mjs";
19
19
 
20
20
  const DEFAULT_MODEL = "cursor/composer-2-5";
21
21
  const DEFAULT_WAIT_MS = 240_000;
22
- const READY_WAIT_MS = 45_000;
23
22
  const SESSION_JSONL_WAIT_MS = 60_000;
24
23
  const COLS = 150;
25
24
  const ROWS = 45;
@@ -242,25 +241,10 @@ function stripANSI(text) {
242
241
  return text.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, "").replace(/\x1b\][^\x07]*(?:\x07|\x1b\\)/g, "");
243
242
  }
244
243
 
245
- function psQuote(value) {
246
- return `'${String(value).replace(/'/g, "''")}'`;
247
- }
248
-
249
- function shellQuote(value) {
250
- return `'${String(value).replace(/'/g, `'\\''`)}'`;
251
- }
252
-
253
- function ptySpawnCommand(piCli, args) {
254
- if (process.platform !== "win32") {
255
- return {
256
- file: "/bin/bash",
257
- args: ["-lc", ["exec", shellQuote(piCli), ...args.map(shellQuote)].join(" ")],
258
- };
259
- }
260
- return {
261
- file: "powershell.exe",
262
- args: ["-NoLogo", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", ["&", psQuote(piCli), ...args.map(psQuote)].join(" ")],
263
- };
244
+ function ptySpawnCommand(args) {
245
+ const cliEntry = resolve(process.cwd(), "node_modules", "@earendil-works", "pi-coding-agent", "dist", "cli.js");
246
+ if (!existsSync(cliEntry)) throw new Error(`Pi CLI entry not found: ${cliEntry}`);
247
+ return { file: process.execPath, args: [cliEntry, ...args] };
264
248
  }
265
249
 
266
250
  async function waitForSessionJsonl(sessionDir, finalMarker, startedAt, events) {
@@ -285,7 +269,7 @@ async function waitForSessionJsonl(sessionDir, finalMarker, startedAt, events) {
285
269
  return files;
286
270
  }
287
271
 
288
- async function runPtyPi({ artifactDir, piCli, piArgs, env, cwd, sessionDir, prompt, finalMarker, waitMs, abortMode, scenario }) {
272
+ async function runPtyPi({ artifactDir, ptyCommand, env, cwd, sessionDir, finalMarker, waitMs, abortMode, scenario }) {
289
273
  let pty;
290
274
  try {
291
275
  pty = await import("node-pty");
@@ -297,8 +281,7 @@ async function runPtyPi({ artifactDir, piCli, piArgs, env, cwd, sessionDir, prom
297
281
  let plain = "";
298
282
  const events = [];
299
283
  const startedAt = Date.now();
300
- const { file, args } = ptySpawnCommand(piCli, piArgs);
301
- writeFileSync(join(artifactDir, "pty-spawn-command.json"), JSON.stringify({ file, args, cwd, fileExists: existsSync(file), cwdExists: existsSync(cwd) }, null, 2));
284
+ const { file, args } = ptyCommand;
302
285
  let child;
303
286
  try {
304
287
  child = pty.spawn(file, args, {
@@ -322,15 +305,7 @@ async function runPtyPi({ artifactDir, piCli, piArgs, env, cwd, sessionDir, prom
322
305
  events.push({ type: "exit", elapsedMs: Date.now() - startedAt, code: event.exitCode, signal: event.signal });
323
306
  });
324
307
 
325
- const readyStartedAt = Date.now();
326
- while (!/(?:composer-2-5|escape interrupt|ctrl\+c\/ctrl\+d)/i.test(plain) && Date.now() - readyStartedAt < READY_WAIT_MS) {
327
- await delay(500);
328
- }
329
- events.push({ type: "ready_wait_finished", elapsedMs: Date.now() - startedAt, ready: /(?:composer-2-5|escape interrupt|ctrl\+c\/ctrl\+d)/i.test(plain) });
330
- child.write(`\x1b[200~${prompt}\x1b[201~\r`);
331
- events.push({ type: "prompt_sent", elapsedMs: Date.now() - startedAt, bytes: prompt.length });
332
- await delay(1_000);
333
- const responseStartOffset = plain.length;
308
+ const responseStartOffset = 0;
334
309
  events.push({ type: "response_watch_started", elapsedMs: Date.now() - startedAt, offset: responseStartOffset });
335
310
 
336
311
  let observed = false;
@@ -572,6 +547,7 @@ async function main() {
572
547
  const suiteEnv = {
573
548
  ...process.env,
574
549
  ...scenario.env,
550
+ PI_OFFLINE: "1",
575
551
  ...(args.suite === "cursor-abort-cleanup" ? { PLATFORM_ABORT_MARKER: "SHOULD_NOT_PRINT" } : {}),
576
552
  PI_CODING_AGENT_DIR: agentDir,
577
553
  PI_CURSOR_SDK_EVENT_DEBUG_DIR: debugDir,
@@ -581,21 +557,21 @@ async function main() {
581
557
  if (args.suite === "cursor-abort-cleanup") writeProcessSnapshot(logDir, "process-before", platform);
582
558
  const prompt = renderPrompt(scenario, platform);
583
559
  writeFileSync(join(artifactDir, "prompt.txt"), prompt);
584
- const piArgs = ["--approve", "--cursor-no-fast", "--cursor-mode", "agent", "--model", args.model, "--session-dir", sessionDir, "--session-id", `platform-${args.suite}-${Date.now()}`];
560
+ const piArgs = ["--approve", "--cursor-no-fast", "--cursor-mode", "agent", "--model", args.model, "--session-dir", sessionDir, "--session-id", `platform-${args.suite}-${Date.now()}`, prompt];
561
+ const ptyCommand = ptySpawnCommand(piArgs);
585
562
  writeFileSync(join(artifactDir, "pi-command.json"), JSON.stringify({
586
- piCli,
587
- args: piArgs,
563
+ ...ptyCommand,
588
564
  cwd: workspaceDir,
589
- env: Object.fromEntries(Object.entries(suiteEnv).filter(([key]) => key.startsWith("PI_CURSOR_") || key === "PI_CODING_AGENT_DIR" || key === "TERM")),
565
+ fileExists: existsSync(ptyCommand.file),
566
+ cwdExists: existsSync(workspaceDir),
567
+ env: Object.fromEntries(Object.entries(suiteEnv).filter(([key]) => key.startsWith("PI_CURSOR_") || key === "PI_CODING_AGENT_DIR" || key === "PI_OFFLINE" || key === "TERM")),
590
568
  }, null, 2));
591
569
  const ptyResult = await runPtyPi({
592
570
  artifactDir,
593
- piCli,
594
- piArgs,
571
+ ptyCommand,
595
572
  env: suiteEnv,
596
573
  cwd: workspaceDir,
597
574
  sessionDir,
598
- prompt,
599
575
  finalMarker: scenario.finalMarker,
600
576
  waitMs: args.waitMs,
601
577
  abortMode: args.suite === "cursor-abort-cleanup",
@@ -62,7 +62,7 @@ Write-SectionFile "NPM_CI_STDOUT" $NpmCiOut
62
62
  Write-SectionFile "NPM_CI_STDERR" $NpmCiErr
63
63
 
64
64
  Write-Output "=== check:platform-smoke ==="
65
- & npm.cmd run check:platform-smoke 1> $CheckPlatformSmokeOut 2> $CheckPlatformSmokeErr
65
+ & npm.cmd run check:platform-smoke -- --testTimeout=15000 1> $CheckPlatformSmokeOut 2> $CheckPlatformSmokeErr
66
66
  $CHECK_PLATFORM_SMOKE_EXIT = Exit-CodeFromLastCommand
67
67
  Write-Output "PLATFORM_CHECK_PLATFORM_SMOKE_EXIT=$CHECK_PLATFORM_SMOKE_EXIT"
68
68
  Write-SectionFile "CHECK_PLATFORM_SMOKE_STDOUT" $CheckPlatformSmokeOut
@@ -108,7 +108,7 @@ NATIVE_MATRIX_OK package=<name> grep=<yes/no> find=<yes/no> list=<yes/no> shell=
108
108
  PI_CURSOR_EXPOSE_BUILTIN_TOOLS: "0",
109
109
  PI_CURSOR_SDK_EVENT_DEBUG: "1",
110
110
  },
111
- promptTemplate: "Reply exactly HTTP1_LIVE_OK.",
111
+ promptTemplate: "Join HTTP1, LIVE, and OK with underscores and reply with only the result.",
112
112
  finalMarker: "HTTP1_LIVE_OK",
113
113
  requiredCards: ["http1-status"],
114
114
  },
@@ -28,7 +28,7 @@ function printHelp() {
28
28
 
29
29
  Commands:
30
30
  doctor Run all preflight checks (no Cursor tokens)
31
- run --target <names> Run one or more comma-separated targets concurrently
31
+ run --target <names> Run one or more comma-separated targets sequentially
32
32
  run --suite <name> Run one suite on all or specified targets
33
33
  run --target <n> --suite <n>
34
34
 
@@ -242,14 +242,14 @@ async function main() {
242
242
  }
243
243
 
244
244
  const startedAt = new Date().toISOString();
245
- const targetRuns = targets.map(async (targetName) => {
245
+ const results = [];
246
+ for (const targetName of targets) {
246
247
  console.log(`\n=== Target: ${targetName} ===`);
247
248
  const result = args.suite
248
249
  ? await runSuite(targetName, suites[0])
249
250
  : await runTarget(targetName, suites);
250
- return { targetName, result };
251
- });
252
- const results = await Promise.all(targetRuns);
251
+ results.push({ targetName, result });
252
+ }
253
253
  const finishedAt = new Date().toISOString();
254
254
  const latest = writeLatestPlatformSmokeIndex(config, results, {
255
255
  startedAt,
@@ -4,6 +4,7 @@ import { Cursor } from "@cursor/sdk";
4
4
  import { defaultApiKeyFromEnv, parseArgv } from "./lib/cursor-cli-args.mjs";
5
5
  import { scrubSensitiveText } from "../shared/cursor-sensitive-text.mjs";
6
6
  import { createScriptFail } from "./lib/cursor-script-fail.mjs";
7
+ import { normalizeCursorContextWindowEntries } from "../shared/cursor-model-selection-identities.mjs";
7
8
 
8
9
  const FALLBACK_MODELS_PATH = "src/cursor-fallback-models.generated.ts";
9
10
  const CONTEXT_WINDOWS_PATH = "src/bundled-context-windows.ts";
@@ -40,7 +41,9 @@ Notes:
40
41
  - This script prints model IDs/counts only; it never prints API keys.
41
42
  - Cursor.models.list() is the source of truth for fallback catalog metadata.
42
43
  - Checkpoint-derived context windows are optional input because collecting them
43
- requires successful local SDK runs; this script does not start agents.`);
44
+ requires successful local SDK runs; this script does not start agents.
45
+ - Context-window keys are limited to current selectable model IDs. Redundant
46
+ :fast/:slow aliases that send the default parameters collapse to one key.`);
44
47
  }
45
48
 
46
49
  const fail = createScriptFail("refresh-cursor-snapshots");
@@ -156,9 +159,10 @@ function hasContextParameter(model) {
156
159
  }
157
160
 
158
161
  function formatContextWindows(models, checkpointWindows, fallbackContextWindow) {
159
- const existing = parseExistingContextWindows();
160
- const merged = new Map(existing);
161
- for (const [modelId, contextWindow] of checkpointWindows) merged.set(modelId, contextWindow);
162
+ const merged = normalizeCursorContextWindowEntries(models, parseExistingContextWindows(), "existing bundled context windows");
163
+ for (const [modelId, contextWindow] of normalizeCursorContextWindowEntries(models, checkpointWindows, "checkpoint context windows")) {
164
+ merged.set(modelId, contextWindow);
165
+ }
162
166
  for (const model of models) {
163
167
  if (!hasContextParameter(model) && !merged.has(model.id)) merged.set(model.id, fallbackContextWindow);
164
168
  }
@@ -167,7 +171,7 @@ function formatContextWindows(models, checkpointWindows, fallbackContextWindow)
167
171
  const date = new Date().toISOString().slice(0, 10);
168
172
  const sorted = [...merged.entries()].sort(([a], [b]) => (a === "default" ? -1 : b === "default" ? 1 : a.localeCompare(b)));
169
173
  const lines = sorted.map(([modelId, contextWindow]) => `\t${JSON.stringify(modelId)}: ${contextWindow},`);
170
- return `// Generated from Cursor SDK checkpoint tokenDetails.maxTokens on ${date}.\n// Refresh with: npm run refresh:cursor-snapshots -- --write --context-windows ~/.pi/agent/cursor-sdk-context-windows.json\n// These are default/non-Max-mode SDK context windows for Cursor models that do not\n// expose a catalog \`context\` parameter. Do not replace them with Max Mode values\n// unless the Cursor SDK exposes an exact Max Mode model selection and the extension\n// uses that selection for matching pi model IDs.\nexport const BUNDLED_CONTEXT_WINDOWS = {\n${lines.join("\n")}\n} as const satisfies Record<string, number>;\n`;
174
+ return `// Generated from Cursor SDK checkpoint tokenDetails.maxTokens on ${date}.\n// Refresh with: npm run refresh:cursor-snapshots -- --write --context-windows ~/.pi/agent/cursor-sdk-context-windows.json\n// Keys are current selectable model IDs. Equivalent default :fast/:slow selections\n// collapse to one key; stale and ambiguous aliases are omitted. Values are observed\n// or conservative default/non-Max-mode limits and may override a catalog context\n// label when the completed SDK checkpoint reports a different effective limit.\nexport const BUNDLED_CONTEXT_WINDOWS = {\n${lines.join("\n")}\n} as const satisfies Record<string, number>;\n`;
171
175
  }
172
176
 
173
177
  const args = parseRefreshArgs(process.argv.slice(2));
@@ -184,11 +188,16 @@ if (!Array.isArray(rawModels) || rawModels.length === 0) fail("Cursor.models.lis
184
188
  const models = rawModels.map(sanitizeModelItem).sort((a, b) => a.id.localeCompare(b.id));
185
189
  const checkpointWindows = parseContextWindowsFile(args.contextWindowsPath);
186
190
  const fallbackSource = formatFallbackModels(models, sdkVersion);
187
- const contextWindowSource = args.contextWindowsPath ? formatContextWindows(models, checkpointWindows, args.fallbackContextWindow) : undefined;
191
+ let contextWindowSource;
192
+ try {
193
+ contextWindowSource = args.contextWindowsPath ? formatContextWindows(models, checkpointWindows, args.fallbackContextWindow) : undefined;
194
+ } catch (error) {
195
+ fail(error instanceof Error ? error.message : String(error));
196
+ }
188
197
  const existingContextWindowCount = parseExistingContextWindows().size;
189
198
 
190
199
  console.log(`Fetched ${models.length} Cursor models with @cursor/sdk@${sdkVersion}.`);
191
- console.log(`Context windows: ${checkpointWindows.size} checkpoint override(s), ${existingContextWindowCount} existing bundled entr${existingContextWindowCount === 1 ? "y" : "ies"}.`);
200
+ console.log(`Context windows: ${checkpointWindows.size} checkpoint input(s), ${existingContextWindowCount} existing bundled entr${existingContextWindowCount === 1 ? "y" : "ies"}.`);
192
201
  console.log(`First models: ${models.slice(0, 8).map((model) => model.id).join(", ")}${models.length > 8 ? ", ..." : ""}`);
193
202
 
194
203
  if (args.check) {
@@ -0,0 +1,21 @@
1
+ import type { ModelListItem } from "@cursor/sdk";
2
+
3
+ export interface CursorModelSelectionIdentity {
4
+ model: ModelListItem;
5
+ selectionModelId: string;
6
+ context?: string;
7
+ fastOverride?: boolean;
8
+ piModelId: string;
9
+ contextWindowKey: string;
10
+ baseContextWindowKey: string;
11
+ }
12
+
13
+ export declare function getCursorModelSelectionIdentities(
14
+ models: readonly ModelListItem[],
15
+ ): CursorModelSelectionIdentity[];
16
+
17
+ export declare function normalizeCursorContextWindowEntries(
18
+ models: readonly ModelListItem[],
19
+ entries: ReadonlyMap<string, number>,
20
+ source?: string,
21
+ ): Map<string, number>;
@@ -0,0 +1,95 @@
1
+ function getParameter(model, id) {
2
+ return (model.parameters ?? []).find((parameter) => parameter.id === id);
3
+ }
4
+
5
+ function getDefaultParam(model, id) {
6
+ const variant = (model.variants ?? []).find((candidate) => candidate.isDefault) ?? model.variants?.[0];
7
+ return variant?.params?.find((param) => param.id === id)?.value.toLowerCase();
8
+ }
9
+
10
+ function getAmbiguousAliases(models) {
11
+ const ownersByAlias = new Map();
12
+ for (const model of models) {
13
+ for (const rawAlias of model.aliases ?? []) {
14
+ const alias = rawAlias.trim();
15
+ if (!alias || alias === model.id) continue;
16
+ const owners = ownersByAlias.get(alias) ?? new Set();
17
+ owners.add(model.id);
18
+ ownersByAlias.set(alias, owners);
19
+ }
20
+ }
21
+ return new Set([...ownersByAlias].filter(([, owners]) => owners.size > 1).map(([alias]) => alias));
22
+ }
23
+
24
+ function getSelectableIds(model, reservedIds, ambiguousAliases) {
25
+ const ids = [model.id];
26
+ for (const rawAlias of model.aliases ?? []) {
27
+ const alias = rawAlias.trim();
28
+ if (!alias || alias === model.id || ids.includes(alias) || reservedIds.has(alias) || ambiguousAliases.has(alias)) continue;
29
+ ids.push(alias);
30
+ }
31
+ return ids;
32
+ }
33
+
34
+ function encodePiModelId(modelId, context, fastOverride) {
35
+ const contextQualified = context ? `${modelId}@${context}` : modelId;
36
+ if (fastOverride === true) return `${contextQualified}:fast`;
37
+ if (fastOverride === false) return `${contextQualified}:slow`;
38
+ return contextQualified;
39
+ }
40
+
41
+ export function getCursorModelSelectionIdentities(models) {
42
+ const sortedModels = [...models].sort((a, b) => a.id.localeCompare(b.id));
43
+ const reservedIds = new Set(models.map((model) => model.id));
44
+ const ambiguousAliases = getAmbiguousAliases(models);
45
+ const usedPiModelIds = new Set();
46
+ const identities = [];
47
+
48
+ for (const model of sortedModels) {
49
+ const contextValues = getParameter(model, "context")?.values?.map(({ value }) => value) ?? [];
50
+ const contexts = contextValues.length > 0 ? contextValues : [undefined];
51
+ const hasFast = getParameter(model, "fast") !== undefined;
52
+ const fastOverrides = hasFast ? [undefined, true, false] : [undefined];
53
+ const defaultFast = getDefaultParam(model, "fast");
54
+
55
+ for (const selectionModelId of getSelectableIds(model, reservedIds, ambiguousAliases)) {
56
+ for (const context of contexts) {
57
+ for (const fastOverride of fastOverrides) {
58
+ const piModelId = encodePiModelId(selectionModelId, context, fastOverride);
59
+ if (usedPiModelIds.has(piModelId)) continue;
60
+ usedPiModelIds.add(piModelId);
61
+ const defaultEquivalent =
62
+ (fastOverride === true && defaultFast === "true") || (fastOverride === false && defaultFast === "false");
63
+ identities.push({
64
+ model,
65
+ selectionModelId,
66
+ ...(context ? { context } : {}),
67
+ ...(fastOverride !== undefined ? { fastOverride } : {}),
68
+ piModelId,
69
+ contextWindowKey: defaultEquivalent ? encodePiModelId(selectionModelId, context) : piModelId,
70
+ baseContextWindowKey: encodePiModelId(model.id, context, defaultEquivalent ? undefined : fastOverride),
71
+ });
72
+ }
73
+ }
74
+ }
75
+ }
76
+
77
+ return identities;
78
+ }
79
+
80
+ export function normalizeCursorContextWindowEntries(models, entries, source = "context windows") {
81
+ const canonicalBySelectableId = new Map(
82
+ getCursorModelSelectionIdentities(models).map(({ piModelId, contextWindowKey }) => [piModelId, contextWindowKey]),
83
+ );
84
+ const normalized = new Map();
85
+ for (const [modelId, contextWindow] of entries) {
86
+ const canonicalId = modelId === "default" ? modelId : canonicalBySelectableId.get(modelId);
87
+ if (!canonicalId) continue;
88
+ const existing = normalized.get(canonicalId);
89
+ if (existing !== undefined && existing !== contextWindow) {
90
+ throw new Error(`${source} assigns conflicting windows to equivalent selection ${canonicalId}: ${existing} and ${contextWindow}`);
91
+ }
92
+ normalized.set(canonicalId, contextWindow);
93
+ }
94
+ return normalized;
95
+ }
@@ -1,30 +1,36 @@
1
- // Generated from Cursor SDK checkpoint tokenDetails.maxTokens on 2026-05-18.
1
+ // Generated from Cursor SDK checkpoint tokenDetails.maxTokens on 2026-08-02.
2
2
  // Refresh with: npm run refresh:cursor-snapshots -- --write --context-windows ~/.pi/agent/cursor-sdk-context-windows.json
3
- // These are default/non-Max-mode SDK context windows for Cursor models that do not
4
- // expose a catalog `context` parameter. Do not replace them with Max Mode values
5
- // unless the Cursor SDK exposes an exact Max Mode model selection and the extension
6
- // uses that selection for matching pi model IDs.
3
+ // Keys are current selectable model IDs. Equivalent default :fast/:slow selections
4
+ // collapse to one key; stale and ambiguous aliases are omitted. Values are observed
5
+ // or conservative default/non-Max-mode limits and may override a catalog context
6
+ // label when the completed SDK checkpoint reports a different effective limit.
7
7
  export const BUNDLED_CONTEXT_WINDOWS = {
8
8
  "default": 200000,
9
+ "claude-fable-5@300k": 300000,
9
10
  "claude-haiku-4-5": 200000,
10
11
  "claude-opus-4-5": 200000,
11
- "composer-1.5": 200000,
12
+ "claude-opus-4-8@1m": 300000,
12
13
  "composer-2": 200000,
13
14
  "composer-2.5": 200000,
14
15
  "gemini-2.5-flash": 200000,
15
16
  "gemini-3-flash": 200000,
16
17
  "gemini-3.1-pro": 200000,
18
+ "gemini-3.5-flash": 200000,
19
+ "gemini-3.6-flash": 200000,
20
+ "glm-5.2": 200000,
21
+ "gpt-5-5@272k": 272000,
17
22
  "gpt-5-mini": 272000,
18
23
  "gpt-5.1": 272000,
19
- "gpt-5.1-codex-max": 272000,
20
- "gpt-5.1-codex-mini": 272000,
21
24
  "gpt-5.2": 272000,
22
- "gpt-5.2-codex": 272000,
23
25
  "gpt-5.3-codex": 272000,
24
- "gpt-5.3-codex-spark": 128000,
25
26
  "gpt-5.4-mini": 272000,
26
27
  "gpt-5.4-nano": 272000,
27
28
  "gpt-5.5@272k": 272000,
28
- "grok-4-20": 200000,
29
- "kimi-k2.5": 262000,
29
+ "gpt-5.6@1m": 272000,
30
+ "grok-4.5": 256000,
31
+ "grok-4.5:slow": 256000,
32
+ "kimi-k2.7-code": 200000,
33
+ "kimi-k3": 200000,
34
+ "opus-4.8@1m": 300000,
35
+ "opus-4.8@300k": 300000,
30
36
  } as const satisfies Record<string, number>;
package/src/context.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
- import type { Context, Message, ToolCall } from "@earendil-works/pi-ai/compat";
2
+ import type { Context, Message, ToolCall } from "@earendil-works/pi-ai";
3
3
  import { convertToLlm } from "@earendil-works/pi-coding-agent";
4
4
  import type { AgentModeOption, SDKImage } from "@cursor/sdk";
5
5
  import { CURSOR_PI_BRIDGE_PREFERENCE_TEXT } from "./cursor-bridge-contract.js";
@@ -1,4 +1,4 @@
1
- import type { Context } from "@earendil-works/pi-ai/compat";
1
+ import type { Context } from "@earendil-works/pi-ai";
2
2
 
3
3
  /** Tool names from the provider context snapshot at stream start (not live pi.getActiveTools()). */
4
4
  export function getActiveContextToolNames(context: Context): ReadonlySet<string> | undefined {
@@ -1,4 +1,4 @@
1
- import type { AssistantMessage, AssistantMessageEventStream } from "@earendil-works/pi-ai/compat";
1
+ import type { AssistantMessage, AssistantMessageEventStream } from "@earendil-works/pi-ai";
2
2
 
3
3
  function withDisplayOnlyThinking(partial: AssistantMessage, thinking: string): AssistantMessage {
4
4
  return { ...partial, content: [...partial.content, { type: "thinking", thinking }] };