pi-cursor-sdk 0.1.37 → 0.1.39

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 (77) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +2 -2
  3. package/docs/cursor-model-ux-spec.md +1 -1
  4. package/docs/cursor-native-tool-replay.md +5 -5
  5. package/package.json +1 -1
  6. package/scripts/platform-smoke/card-detect.mjs +1 -1
  7. package/src/context-window-cache.ts +10 -14
  8. package/src/context.ts +1 -1
  9. package/src/cursor-agent-message-web-tools.ts +2 -1
  10. package/src/cursor-agents-context-registration.ts +18 -0
  11. package/src/cursor-agents-context.ts +21 -30
  12. package/src/cursor-edit-diff.ts +4 -2
  13. package/src/cursor-fallback-warning.ts +22 -0
  14. package/src/cursor-incomplete-tool-visibility.ts +5 -10
  15. package/src/cursor-live-run-coordinator.ts +1 -1
  16. package/src/cursor-mcp-timeout-override.ts +0 -2
  17. package/src/cursor-model-lifecycle.ts +72 -0
  18. package/src/cursor-native-replay-routing.ts +1 -1
  19. package/src/cursor-native-replay-trace.ts +1 -1
  20. package/src/cursor-native-tool-display-registration.ts +16 -28
  21. package/src/cursor-native-tool-display-replay.ts +4 -21
  22. package/src/cursor-native-tool-display-state.ts +1 -1
  23. package/src/cursor-native-tool-display-tools.ts +10 -17
  24. package/src/cursor-native-tool-names.ts +16 -0
  25. package/src/cursor-pi-tool-bridge-env.ts +12 -0
  26. package/src/cursor-pi-tool-bridge-mcp.ts +16 -21
  27. package/src/cursor-pi-tool-bridge-run.ts +5 -5
  28. package/src/cursor-pi-tool-bridge-server.ts +8 -3
  29. package/src/cursor-pi-tool-bridge-snapshot.ts +7 -13
  30. package/src/cursor-pi-tool-bridge.ts +7 -7
  31. package/src/cursor-provider-errors.ts +11 -4
  32. package/src/cursor-provider-lazy.ts +51 -0
  33. package/src/cursor-provider-live-run-drain.ts +1 -1
  34. package/src/cursor-provider-run-finalizer.ts +5 -5
  35. package/src/cursor-provider-run-outcome.ts +0 -1
  36. package/src/cursor-provider-turn-coordinator.ts +16 -6
  37. package/src/cursor-provider-turn-display-router.ts +5 -1
  38. package/src/cursor-provider-turn-emit.ts +1 -1
  39. package/src/cursor-provider-turn-lifecycle-emitter.ts +1 -5
  40. package/src/cursor-provider-turn-prepare.ts +13 -9
  41. package/src/cursor-provider-turn-runner.ts +3 -11
  42. package/src/cursor-provider-turn-sdk-normalizer.ts +28 -5
  43. package/src/cursor-provider-turn-send.ts +7 -2
  44. package/src/cursor-provider-turn-shell-output.ts +38 -3
  45. package/src/cursor-provider-turn-types.ts +1 -3
  46. package/src/cursor-provider.ts +3 -2
  47. package/src/cursor-question-tool.ts +5 -18
  48. package/src/cursor-record-utils.ts +42 -0
  49. package/src/cursor-replay-activity-builders.ts +16 -122
  50. package/src/cursor-replay-tool-details.ts +52 -80
  51. package/src/cursor-sdk-event-debug.ts +6 -6
  52. package/src/cursor-sensitive-text.ts +4 -4
  53. package/src/cursor-session-agent-lifecycle.ts +47 -0
  54. package/src/cursor-session-agent.ts +9 -47
  55. package/src/cursor-session-scope.ts +23 -4
  56. package/src/cursor-setting-sources.ts +8 -8
  57. package/src/cursor-skill-tool.ts +25 -32
  58. package/src/cursor-state.ts +66 -45
  59. package/src/cursor-tool-lifecycle.ts +22 -10
  60. package/src/cursor-tool-presentation-registry.ts +27 -18
  61. package/src/cursor-tool-result-display-readers.ts +185 -0
  62. package/src/cursor-tool-transcript.ts +17 -33
  63. package/src/cursor-tool-visibility.ts +9 -1
  64. package/src/cursor-transcript-tool-formatters.ts +23 -172
  65. package/src/cursor-transcript-tool-specs.ts +16 -41
  66. package/src/cursor-transcript-utils.ts +2 -34
  67. package/src/cursor-usage-accounting.ts +0 -6
  68. package/src/cursor-web-tool-activity.ts +4 -12
  69. package/src/cursor-web-tool-args.ts +1 -9
  70. package/src/index.ts +15 -16
  71. package/src/model-discovery.ts +5 -4
  72. package/src/model-list-cache.ts +37 -38
  73. package/src/cursor-native-tool-display.ts +0 -10
  74. package/src/cursor-provider-turn-api-key.ts +0 -1
  75. package/src/cursor-provider-turn-message-offset.ts +0 -15
  76. package/src/cursor-session-cwd.ts +0 -28
  77. package/src/cursor-tool-names.ts +0 -9
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  CURSOR_KNOWN_NORMALIZED_TOOL_NAMES,
3
3
  CURSOR_REPLAY_ACTIVITY_TOOL_NAME,
4
- getCursorReplayActivityTitle,
5
4
  getCursorReplayCallSummary,
5
+ getCursorToolActivityTitle,
6
6
  getCursorToolActivityReplaySpec,
7
7
  getCursorToolGenerateImageReplaySpec,
8
8
  type CursorNormalizedToolName,
@@ -12,21 +12,16 @@ import { resolveCursorEditDiff } from "./cursor-edit-diff.js";
12
12
  import {
13
13
  assembleCursorReplayActivityDetails,
14
14
  assembleCursorReplayGenerateImageDetails,
15
- buildCursorReplayNativeEditDetails,
16
- buildCursorReplayNativeWriteDetails,
17
15
  CURSOR_REPLAY_UNREGISTERED_ACTIVITY_TOOL_NAME,
18
16
  type CursorReplayToolDetails,
19
17
  } from "./cursor-replay-tool-details.js";
18
+ import { asRecord, getNumber, getString } from "./cursor-record-utils.js";
20
19
  import {
21
- asRecord,
22
20
  firstNonEmptyLine,
23
21
  formatDisplayPath,
24
22
  formatDiffString,
25
23
  formatError,
26
- getNumber,
27
- getString,
28
24
  limitText,
29
- stringifyUnknown,
30
25
  truncateArg,
31
26
  type CursorPiToolDisplay,
32
27
  type NormalizedResult,
@@ -68,8 +63,6 @@ import {
68
63
  getShellOutput,
69
64
  usesLocalReadPreview,
70
65
  } from "./cursor-transcript-tool-formatters.js";
71
- import type { CursorReplaySummaryArgs } from "./cursor-replay-summary-args.js";
72
-
73
66
  export interface ToolDisplayContext {
74
67
  rawName: string;
75
68
  name: string;
@@ -102,13 +95,6 @@ function buildCursorActivityDisplayArgs(
102
95
  };
103
96
  }
104
97
 
105
- function buildRegistryReplaySummary(
106
- sourceToolName: CursorReplayActivityToolName,
107
- args: CursorReplaySummaryArgs,
108
- ): string | undefined {
109
- return getCursorReplayCallSummary(sourceToolName, args);
110
- }
111
-
112
98
  function buildReplaySummaryDisplay(
113
99
  toolName: string,
114
100
  args: Record<string, unknown>,
@@ -131,20 +117,16 @@ function buildReplaySummaryDisplay(
131
117
  };
132
118
  }
133
119
 
134
- function getCursorToolActivityTitle(toolName: string): string {
135
- return getCursorReplayActivityTitle(toolName) ?? buildGenericUnknownToolActivityTitle(toolName);
136
- }
137
-
138
120
  function buildActivityReplayDisplay(
139
121
  sourceToolName: NeutralActivityReplayToolName,
140
122
  context: ToolDisplayContext,
141
123
  ): CursorPiToolDisplay {
142
- const spec = TOOL_DISPLAY_SPECS[sourceToolName];
124
+ const spec = TOOL_DISPLAY_IMPLEMENTATIONS[sourceToolName];
143
125
  const activity = getCursorToolActivityReplaySpec(sourceToolName);
144
126
  if (!activity) throw new Error(`Missing activity replay spec for ${sourceToolName}`);
145
- const activityTitle = getCursorReplayActivityTitle(sourceToolName) ?? buildGenericUnknownToolActivityTitle(sourceToolName);
127
+ const activityTitle = getCursorToolActivityTitle(sourceToolName);
146
128
  const replayArgs = activity.buildActivityArgs(context);
147
- const activitySummary = buildRegistryReplaySummary(sourceToolName, replayArgs);
129
+ const activitySummary = getCursorReplayCallSummary(sourceToolName, replayArgs);
148
130
  const activityArgs = buildCursorActivityDisplayArgs({ ...replayArgs }, activityTitle, activitySummary);
149
131
  const contentText = spec.formatTranscript(context).trimEnd();
150
132
  const activityFields = activity.buildDetails(context, contentText);
@@ -160,12 +142,12 @@ function buildActivityReplayDisplay(
160
142
  }
161
143
 
162
144
  function buildGenerateImageReplayDisplay(context: ToolDisplayContext): CursorPiToolDisplay {
163
- const spec = TOOL_DISPLAY_SPECS.generateImage;
145
+ const spec = TOOL_DISPLAY_IMPLEMENTATIONS.generateImage;
164
146
  const replay = getCursorToolGenerateImageReplaySpec("generateImage");
165
147
  if (!replay) throw new Error("Missing generate image replay spec");
166
- const activityTitle = getCursorReplayActivityTitle("generateImage") ?? "Cursor image generation";
148
+ const activityTitle = getCursorToolActivityTitle("generateImage");
167
149
  const replayArgs = replay.buildActivityArgs(context);
168
- const activitySummary = buildRegistryReplaySummary("generateImage", replayArgs);
150
+ const activitySummary = getCursorReplayCallSummary("generateImage", replayArgs);
169
151
  const activityArgs = buildCursorActivityDisplayArgs({ ...replayArgs }, activityTitle, activitySummary);
170
152
  const contentText = spec.formatTranscript(context).trimEnd();
171
153
  const details = assembleCursorReplayGenerateImageDetails(
@@ -177,15 +159,10 @@ function buildGenerateImageReplayDisplay(context: ToolDisplayContext): CursorPiT
177
159
  return buildReplaySummaryDisplay(CURSOR_REPLAY_ACTIVITY_TOOL_NAME, activityArgs, context.result, contentText, details);
178
160
  }
179
161
 
180
- function buildGenericUnknownToolActivityTitle(displayName: string): string {
181
- if (displayName === "unknown") return "Cursor tool";
182
- return `Cursor ${truncateArg(displayName)}`;
183
- }
184
-
185
162
  function buildGenericPiToolDisplay(context: ToolDisplayContext): CursorPiToolDisplay {
186
163
  const { rawName, name, args, result, options } = context;
187
164
  const displayName = rawName.trim() || name;
188
- const activityTitle = buildGenericUnknownToolActivityTitle(displayName);
165
+ const activityTitle = getCursorToolActivityTitle(displayName);
189
166
  const contentText = formatFallback(name, args, result, options);
190
167
  const fallbackBody = contentText.includes("\n\n") ? contentText.slice(contentText.indexOf("\n\n") + 2) : "";
191
168
  const activitySummary =
@@ -237,14 +214,15 @@ function buildEditPiToolDisplay(context: ToolDisplayContext): CursorPiToolDispla
237
214
  const activityTitle = getCursorToolActivityTitle("edit");
238
215
  const activityArgs = buildCursorActivityDisplayArgs(baseActivityArgs, activityTitle, displayPath);
239
216
  const contentText = formatEdit(activityArgs, result, options);
240
- const details = buildCursorReplayNativeEditDetails({
217
+ const details: CursorReplayToolDetails = {
218
+ variant: "nativeEdit",
241
219
  path: displayPath,
242
220
  linesAdded: getNumber(value, "linesAdded"),
243
221
  linesRemoved: getNumber(value, "linesRemoved"),
244
222
  diffString: normalizedDiff,
245
223
  diff: normalizedDiff,
246
224
  firstChangedLine: getNumber(value, "firstChangedLine"),
247
- });
225
+ };
248
226
  if (nativeEditArgs) {
249
227
  return {
250
228
  toolName: "edit",
@@ -284,13 +262,14 @@ function buildWritePiToolDisplay(context: ToolDisplayContext): CursorPiToolDispl
284
262
  const displayArgs = buildWriteDisplayArgs(args, options);
285
263
  const displayPath = typeof args.path === "string" ? formatDisplayPath(args.path, options.cwd) : undefined;
286
264
  const contentText = formatWrite(args, result, options).trimEnd();
287
- const details = buildCursorReplayNativeWriteDetails({
265
+ const details: CursorReplayToolDetails = {
266
+ variant: "nativeWrite",
288
267
  path: displayPath,
289
268
  linesCreated: getNumber(value, "linesCreated"),
290
269
  fileSize: getNumber(value, "fileSize"),
291
270
  fileContentAfterWrite: getString(value, "fileContentAfterWrite"),
292
271
  expandedText: contentText,
293
- });
272
+ };
294
273
  if (content === undefined) {
295
274
  const activityTitle = getCursorToolActivityTitle("write");
296
275
  return buildReplaySummaryDisplay(
@@ -440,12 +419,8 @@ const TOOL_DISPLAY_IMPLEMENTATIONS: Record<CursorNormalizedToolName, ToolDisplay
440
419
 
441
420
  export const CURSOR_TOOL_DISPLAY_SPEC_KEYS = CURSOR_KNOWN_NORMALIZED_TOOL_NAMES;
442
421
 
443
- const TOOL_DISPLAY_SPECS = Object.fromEntries(
444
- CURSOR_KNOWN_NORMALIZED_TOOL_NAMES.map((name) => [name, TOOL_DISPLAY_IMPLEMENTATIONS[name]]),
445
- ) as Record<CursorNormalizedToolName, ToolDisplaySpec>;
446
-
447
422
  function getToolDisplaySpec(name: string): ToolDisplaySpec | undefined {
448
- if (Object.hasOwn(TOOL_DISPLAY_SPECS, name)) return TOOL_DISPLAY_SPECS[name as CursorNormalizedToolName];
423
+ if (Object.hasOwn(TOOL_DISPLAY_IMPLEMENTATIONS, name)) return TOOL_DISPLAY_IMPLEMENTATIONS[name as CursorNormalizedToolName];
449
424
  return undefined;
450
425
  }
451
426
 
@@ -1,8 +1,6 @@
1
1
  import { closeSync, openSync, readSync, realpathSync, statSync } from "node:fs";
2
2
  import { isAbsolute, relative, resolve } from "node:path";
3
- import { asRecord, getFirstStringByKeys } from "./cursor-record-utils.js";
4
-
5
- export { asRecord, getFirstStringByKeys } from "./cursor-record-utils.js";
3
+ import { asRecord, getArray, getRecord, getString, stringifyUnknown as stringifyUnknownValue } from "./cursor-record-utils.js";
6
4
 
7
5
  export interface TranscriptOptions {
8
6
  maxChars?: number;
@@ -47,30 +45,6 @@ export function isLocalReadPreviewContent(text: string): boolean {
47
45
  return text.startsWith(LOCAL_READ_PREVIEW_NOTICE);
48
46
  }
49
47
 
50
- export function getString(record: Record<string, unknown> | undefined, key: string): string | undefined {
51
- const value = record?.[key];
52
- return typeof value === "string" ? value : undefined;
53
- }
54
-
55
- export function getNumber(record: Record<string, unknown> | undefined, key: string): number | undefined {
56
- const value = record?.[key];
57
- return typeof value === "number" && Number.isFinite(value) ? value : undefined;
58
- }
59
-
60
- export function getBoolean(record: Record<string, unknown> | undefined, key: string): boolean | undefined {
61
- const value = record?.[key];
62
- return typeof value === "boolean" ? value : undefined;
63
- }
64
-
65
- export function getRecord(record: Record<string, unknown> | undefined, key: string): Record<string, unknown> | undefined {
66
- return asRecord(record?.[key]);
67
- }
68
-
69
- export function getArray(record: Record<string, unknown> | undefined, key: string): unknown[] | undefined {
70
- const value = record?.[key];
71
- return Array.isArray(value) ? value : undefined;
72
- }
73
-
74
48
  export function getToolName(toolCall: unknown): string {
75
49
  const record = asRecord(toolCall);
76
50
  return getString(record, "name") ?? getString(record, "type") ?? getString(record, "toolName") ?? "unknown";
@@ -96,13 +70,7 @@ export function normalizeResult(result: unknown): NormalizedResult {
96
70
  }
97
71
 
98
72
  export function stringifyUnknown(value: unknown): string {
99
- if (value === undefined) return "";
100
- if (typeof value === "string") return value;
101
- try {
102
- return JSON.stringify(value, null, 2) ?? String(value);
103
- } catch {
104
- return String(value);
105
- }
73
+ return stringifyUnknownValue(value, { pretty: true });
106
74
  }
107
75
 
108
76
  export function limitText(text: string, options: TranscriptOptions = {}, knownTotalLines?: number): string {
@@ -3,9 +3,7 @@ import {
3
3
  CURSOR_APPROX_CHARS_PER_TOKEN,
4
4
  CURSOR_IMAGE_TOKEN_ESTIMATE,
5
5
  estimateCursorContextTokens,
6
- estimateCursorPromptTokens,
7
6
  estimateCursorTextTokens,
8
- type CursorPrompt,
9
7
  type CursorPromptOptions,
10
8
  } from "./context.js";
11
9
 
@@ -28,10 +26,6 @@ export function getCursorPromptOptions(model: Model<Api>): CursorUsagePromptOpti
28
26
  };
29
27
  }
30
28
 
31
- export function estimateCursorPromptInputTokens(prompt: CursorPrompt, options: Pick<CursorPromptOptions, "charsPerToken" | "imageTokenEstimate">): number {
32
- return estimateCursorPromptTokens(prompt, options);
33
- }
34
-
35
29
  function stringifyUsageValue(value: unknown): string {
36
30
  try {
37
31
  return JSON.stringify(value) ?? "";
@@ -1,23 +1,15 @@
1
+ import { getFirstStringByKeys } from "./cursor-record-utils.js";
1
2
  import {
2
- classifyCursorWebToolKind as classifyCursorWebToolKindFromRegistry,
3
- type CursorWebToolKind,
3
+ classifyCursorWebToolKind,
4
+ normalizeCursorToolName as normalizeToolName,
4
5
  } from "./cursor-tool-presentation-registry.js";
5
- import { normalizeCursorToolName as normalizeToolName } from "./cursor-tool-presentation-registry.js";
6
- import { extractWebFetchTarget, extractWebSearchQuery } from "./cursor-web-tool-args.js";
7
-
8
- export type { CursorWebToolKind } from "./cursor-tool-presentation-registry.js";
9
- export { extractWebFetchTarget, extractWebSearchQuery } from "./cursor-web-tool-args.js";
10
6
 
11
7
  function getMcpToolName(args: Record<string, unknown>): string | undefined {
12
- const toolName = typeof args.toolName === "string" ? args.toolName : typeof args.tool_name === "string" ? args.tool_name : undefined;
8
+ const toolName = getFirstStringByKeys(args, ["toolName", "tool_name"]);
13
9
  const trimmed = toolName?.trim();
14
10
  return trimmed || undefined;
15
11
  }
16
12
 
17
- export function classifyCursorWebToolKind(name: string | undefined): CursorWebToolKind | undefined {
18
- return classifyCursorWebToolKindFromRegistry(name);
19
- }
20
-
21
13
  /**
22
14
  * Maps SDK/host/MCP tool names to transcript display keys.
23
15
  * Web search/fetch often arrives as MCP `toolName` values, not dedicated SDK ToolTypes.
@@ -1,17 +1,9 @@
1
- import { asRecord } from "./cursor-record-utils.js";
1
+ import { asRecord, firstNonEmptyString } from "./cursor-record-utils.js";
2
2
 
3
3
  function getNestedMcpArgs(args: Record<string, unknown>): Record<string, unknown> {
4
4
  return asRecord(args.args) ?? {};
5
5
  }
6
6
 
7
- function firstNonEmptyString(...values: Array<string | undefined>): string | undefined {
8
- for (const value of values) {
9
- const trimmed = value?.trim();
10
- if (trimmed) return trimmed;
11
- }
12
- return undefined;
13
- }
14
-
15
7
  export function extractWebSearchQuery(args: Record<string, unknown>): string | undefined {
16
8
  const nested = getNestedMcpArgs(args);
17
9
  return firstNonEmptyString(
package/src/index.ts CHANGED
@@ -1,26 +1,27 @@
1
- import type { ExtensionAPI, ExtensionContext, ProviderConfig, ProviderModelConfig } from "@earendil-works/pi-coding-agent";
1
+ import type { ExtensionAPI, ProviderConfig, ProviderModelConfig } from "@earendil-works/pi-coding-agent";
2
2
  import { discoverModels, type CursorModelFallbackIssue } from "./model-discovery.js";
3
3
  import { registerCursorRuntimeControls } from "./cursor-state.js";
4
- import { registerCursorNativeToolDisplay } from "./cursor-native-tool-display.js";
4
+ import { registerCursorNativeToolDisplay } from "./cursor-native-tool-display-registration.js";
5
5
  import { registerCursorPiToolBridge } from "./cursor-pi-tool-bridge.js";
6
6
  import { registerCursorQuestionTool } from "./cursor-question-tool.js";
7
7
  import { registerCursorSkillTool } from "./cursor-skill-tool.js";
8
- import { registerCursorSessionCwd } from "./cursor-session-cwd.js";
9
- import { registerCursorAgentsContextDedup } from "./cursor-agents-context.js";
10
- import { registerCursorSessionAgent } from "./cursor-session-agent.js";
11
- import { prepareCursorSessionForCompaction } from "./cursor-session-compaction-prep.js";
12
- import { streamCursor } from "./cursor-provider.js";
8
+ import { registerCursorSessionScope } from "./cursor-session-scope.js";
9
+ import { registerCursorSessionAgentLifecycle } from "./cursor-session-agent-lifecycle.js";
10
+ import { streamCursorLazy } from "./cursor-provider-lazy.js";
13
11
  import { CURSOR_API_KEY_CONFIG_VALUE } from "./cursor-api-key.js";
12
+ import { registerCursorFallbackIssueWarning } from "./cursor-fallback-warning.js";
13
+ import { registerCursorAgentsContextDedup } from "./cursor-agents-context-registration.js";
14
14
 
15
15
  type CursorExtensionApi =
16
16
  & Pick<ExtensionAPI, "registerProvider" | "registerCommand" | "on">
17
- & Parameters<typeof registerCursorSessionCwd>[0]
18
- & Parameters<typeof registerCursorSessionAgent>[0]
17
+ & Parameters<typeof registerCursorSessionScope>[0]
18
+ & Parameters<typeof registerCursorSessionAgentLifecycle>[0]
19
19
  & Parameters<typeof registerCursorRuntimeControls>[0]
20
20
  & Parameters<typeof registerCursorNativeToolDisplay>[0]
21
21
  & Parameters<typeof registerCursorQuestionTool>[0]
22
22
  & Parameters<typeof registerCursorSkillTool>[0]
23
23
  & Parameters<typeof registerCursorPiToolBridge>[0]
24
+ & Parameters<typeof registerCursorFallbackIssueWarning>[0]
24
25
  & Parameters<typeof registerCursorAgentsContextDedup>[0];
25
26
 
26
27
  function createCursorProviderConfig(models: ProviderModelConfig[]): ProviderConfig {
@@ -30,7 +31,7 @@ function createCursorProviderConfig(models: ProviderModelConfig[]): ProviderConf
30
31
  apiKey: CURSOR_API_KEY_CONFIG_VALUE,
31
32
  api: "cursor-sdk",
32
33
  models,
33
- streamSimple: streamCursor,
34
+ streamSimple: streamCursorLazy,
34
35
  };
35
36
  }
36
37
 
@@ -40,9 +41,10 @@ function registerCursorProvider(pi: Pick<ExtensionAPI, "registerProvider">, mode
40
41
 
41
42
  export default async function (pi: CursorExtensionApi) {
42
43
  // Session cwd must register before other session_start listeners that depend on it.
43
- registerCursorSessionCwd(pi);
44
- registerCursorSessionAgent(pi);
44
+ registerCursorSessionScope(pi);
45
+ registerCursorSessionAgentLifecycle(pi);
45
46
  pi.on("session_before_compact", async () => {
47
+ const { prepareCursorSessionForCompaction } = await import("./cursor-session-compaction-prep.js");
46
48
  await prepareCursorSessionForCompaction();
47
49
  });
48
50
  registerCursorRuntimeControls(pi);
@@ -59,10 +61,7 @@ export default async function (pi: CursorExtensionApi) {
59
61
  });
60
62
 
61
63
  if (fallbackIssue) {
62
- const issue = fallbackIssue;
63
- pi.on("session_start", async (_event, ctx) => {
64
- if (ctx.hasUI) ctx.ui.notify(issue.message, "warning");
65
- });
64
+ registerCursorFallbackIssueWarning(pi, fallbackIssue);
66
65
  }
67
66
 
68
67
  pi.registerCommand("cursor-refresh-models", {
@@ -4,12 +4,11 @@ import type {
4
4
  ModelParameterValue,
5
5
  ModelSelection,
6
6
  } from "@cursor/sdk";
7
- import { AuthStorage, type ProviderModelConfig } from "@earendil-works/pi-coding-agent";
7
+ import type { ProviderModelConfig } from "@earendil-works/pi-coding-agent";
8
8
  import type { ModelThinkingLevel, ThinkingLevelMap } from "@earendil-works/pi-ai";
9
9
  import { loadContextWindowCache } from "./context-window-cache.js";
10
10
  import { loadCursorSdk } from "./cursor-sdk-runtime.js";
11
- import { CURSOR_API_KEY_ENV_VAR, resolveCursorApiKey } from "./cursor-api-key.js";
12
- import { FALLBACK_MODEL_ITEMS } from "./cursor-fallback-models.generated.js";
11
+ import { resolveCursorApiKey } from "./cursor-api-key.js";
13
12
  import {
14
13
  fingerprintApiKey,
15
14
  loadAnyCachedModelCatalog,
@@ -62,6 +61,7 @@ function getCliApiKeyFromArgv(argv: string[] = process.argv): string | undefined
62
61
 
63
62
  async function getStoredCursorApiKey(): Promise<string | undefined> {
64
63
  try {
64
+ const { AuthStorage } = await import("@earendil-works/pi-coding-agent");
65
65
  return resolveCursorApiKey(await AuthStorage.create().getApiKey(CURSOR_PROVIDER_ID, { includeFallback: false }));
66
66
  } catch {
67
67
  return undefined;
@@ -462,8 +462,9 @@ function sanitizeDiscoveryError(error: unknown, apiKey: string): string | undefi
462
462
  return scrubbed || undefined;
463
463
  }
464
464
 
465
- function useFallbackModels(options: DiscoverModelsOptions, issue: CursorModelFallbackIssue): ProviderModelConfig[] {
465
+ async function useFallbackModels(options: DiscoverModelsOptions, issue: CursorModelFallbackIssue): Promise<ProviderModelConfig[]> {
466
466
  options.onFallback?.(issue);
467
+ const { FALLBACK_MODEL_ITEMS } = await import("./cursor-fallback-models.generated.js");
467
468
  return registerModelItems(FALLBACK_MODEL_ITEMS);
468
469
  }
469
470
 
@@ -4,6 +4,7 @@ import { dirname, join } from "node:path";
4
4
  import { getAgentDir } from "@earendil-works/pi-coding-agent";
5
5
  import type { ModelListItem } from "@cursor/sdk";
6
6
  import { parseEnvBoolean } from "./cursor-env-boolean.js";
7
+ import { asRecord } from "./cursor-record-utils.js";
7
8
 
8
9
  const MODEL_LIST_CACHE_FILE = "cursor-sdk-model-list.json";
9
10
  const MODEL_LIST_CACHE_VERSION = 1;
@@ -46,52 +47,53 @@ export function fingerprintApiKey(apiKey: string): string {
46
47
  return createHash("sha256").update(apiKey).digest("hex").slice(0, 16);
47
48
  }
48
49
 
49
- function isRecord(value: unknown): value is Record<string, unknown> {
50
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
51
- }
52
-
53
50
  function isStringArray(value: unknown): value is string[] {
54
51
  return Array.isArray(value) && value.every((entry) => typeof entry === "string");
55
52
  }
56
53
 
57
54
  function isModelParameterValue(value: unknown): value is NonNullable<ModelListItem["variants"]>[number]["params"][number] {
58
- return isRecord(value) && typeof value.id === "string" && typeof value.value === "string";
55
+ const record = asRecord(value);
56
+ return record !== undefined && typeof record.id === "string" && typeof record.value === "string";
59
57
  }
60
58
 
61
59
  function isModelParameterDefinitionValue(value: unknown): value is NonNullable<ModelListItem["parameters"]>[number]["values"][number] {
62
- return isRecord(value) && typeof value.value === "string" && (value.displayName === undefined || typeof value.displayName === "string");
60
+ const record = asRecord(value);
61
+ return record !== undefined && typeof record.value === "string" && (record.displayName === undefined || typeof record.displayName === "string");
63
62
  }
64
63
 
65
64
  function isModelParameterDefinition(value: unknown): value is NonNullable<ModelListItem["parameters"]>[number] {
66
- if (!isRecord(value)) return false;
65
+ const record = asRecord(value);
66
+ if (!record) return false;
67
67
  return (
68
- typeof value.id === "string" &&
69
- (value.displayName === undefined || typeof value.displayName === "string") &&
70
- Array.isArray(value.values) &&
71
- value.values.every(isModelParameterDefinitionValue)
68
+ typeof record.id === "string" &&
69
+ (record.displayName === undefined || typeof record.displayName === "string") &&
70
+ Array.isArray(record.values) &&
71
+ record.values.every(isModelParameterDefinitionValue)
72
72
  );
73
73
  }
74
74
 
75
75
  function isModelVariant(value: unknown): value is NonNullable<ModelListItem["variants"]>[number] {
76
- if (!isRecord(value)) return false;
76
+ const record = asRecord(value);
77
+ if (!record) return false;
77
78
  return (
78
- Array.isArray(value.params) &&
79
- value.params.every(isModelParameterValue) &&
80
- typeof value.displayName === "string" &&
81
- (value.description === undefined || typeof value.description === "string") &&
82
- (value.isDefault === undefined || typeof value.isDefault === "boolean")
79
+ Array.isArray(record.params) &&
80
+ record.params.every(isModelParameterValue) &&
81
+ typeof record.displayName === "string" &&
82
+ (record.description === undefined || typeof record.description === "string") &&
83
+ (record.isDefault === undefined || typeof record.isDefault === "boolean")
83
84
  );
84
85
  }
85
86
 
86
87
  function isModelListItem(value: unknown): value is ModelListItem {
87
- if (!isRecord(value)) return false;
88
+ const record = asRecord(value);
89
+ if (!record) return false;
88
90
  return (
89
- typeof value.id === "string" &&
90
- typeof value.displayName === "string" &&
91
- (value.description === undefined || typeof value.description === "string") &&
92
- (value.aliases === undefined || isStringArray(value.aliases)) &&
93
- (value.parameters === undefined || (Array.isArray(value.parameters) && value.parameters.every(isModelParameterDefinition))) &&
94
- (value.variants === undefined || (Array.isArray(value.variants) && value.variants.every(isModelVariant)))
91
+ typeof record.id === "string" &&
92
+ typeof record.displayName === "string" &&
93
+ (record.description === undefined || typeof record.description === "string") &&
94
+ (record.aliases === undefined || isStringArray(record.aliases)) &&
95
+ (record.parameters === undefined || (Array.isArray(record.parameters) && record.parameters.every(isModelParameterDefinition))) &&
96
+ (record.variants === undefined || (Array.isArray(record.variants) && record.variants.every(isModelVariant)))
95
97
  );
96
98
  }
97
99
 
@@ -100,21 +102,22 @@ function isValidFetchedAt(value: unknown): value is number {
100
102
  }
101
103
 
102
104
  function parseModelListCacheFile(value: unknown): ModelListCacheFile | undefined {
103
- if (!isRecord(value)) return undefined;
105
+ const record = asRecord(value);
106
+ if (!record) return undefined;
104
107
  if (
105
- value.version !== MODEL_LIST_CACHE_VERSION ||
106
- !isValidFetchedAt(value.fetchedAt) ||
107
- typeof value.keyFingerprint !== "string" ||
108
- !Array.isArray(value.models) ||
109
- !value.models.every(isModelListItem)
108
+ record.version !== MODEL_LIST_CACHE_VERSION ||
109
+ !isValidFetchedAt(record.fetchedAt) ||
110
+ typeof record.keyFingerprint !== "string" ||
111
+ !Array.isArray(record.models) ||
112
+ !record.models.every(isModelListItem)
110
113
  ) {
111
114
  return undefined;
112
115
  }
113
116
  return {
114
- version: value.version,
115
- fetchedAt: value.fetchedAt,
116
- keyFingerprint: value.keyFingerprint,
117
- models: value.models,
117
+ version: record.version,
118
+ fetchedAt: record.fetchedAt,
119
+ keyFingerprint: record.keyFingerprint,
120
+ models: record.models,
118
121
  };
119
122
  }
120
123
 
@@ -149,10 +152,6 @@ export function loadAnyCachedModelCatalog(keyFingerprint: string): CachedModelLi
149
152
  return { fetchedAt: cache.fetchedAt, models: cache.models };
150
153
  }
151
154
 
152
- export function loadAnyCachedModels(keyFingerprint: string): ModelListItem[] | undefined {
153
- return loadAnyCachedModelCatalog(keyFingerprint)?.models;
154
- }
155
-
156
155
  export function saveModelListCache(keyFingerprint: string, models: ModelListItem[]): boolean {
157
156
  if (isModelCacheDisabled()) return false;
158
157
  try {
@@ -1,10 +0,0 @@
1
- export type { CursorNativeToolDisplayItem } from "./cursor-native-tool-display-state.js";
2
- export {
3
- canRenderCursorToolNatively,
4
- deleteCursorNativeToolDisplay,
5
- isCursorNativeToolDisplayEnabled,
6
- isCursorNativeToolDisplayRuntimeEnabled,
7
- recordCursorNativeToolDisplay,
8
- __testUtils,
9
- } from "./cursor-native-tool-display-state.js";
10
- export { registerCursorNativeToolDisplay } from "./cursor-native-tool-display-registration.js";
@@ -1 +0,0 @@
1
- export { resolveCursorApiKey } from "./cursor-api-key.js";
@@ -1,15 +0,0 @@
1
- import { countCursorAgentMessages } from "./cursor-agent-message-web-tools.js";
2
- import type { CursorSdkEventDebugSink } from "./cursor-sdk-event-debug.js";
3
-
4
- export async function getCursorAgentMessageOffset(
5
- agentId: string,
6
- cwd: string,
7
- sdkEventDebug: CursorSdkEventDebugSink | undefined,
8
- ): Promise<number | undefined> {
9
- try {
10
- return await countCursorAgentMessages(agentId, cwd);
11
- } catch (error) {
12
- sdkEventDebug?.recordError("cursor_agent_message_count", error);
13
- return undefined;
14
- }
15
- }
@@ -1,28 +0,0 @@
1
- import {
2
- getCursorSessionCwdFromScope,
3
- registerCursorSessionScope,
4
- __testUtils as cursorSessionScopeTestUtils,
5
- } from "./cursor-session-scope.js";
6
- import type { ExtensionHandler, SessionStartEvent } from "@earendil-works/pi-coding-agent";
7
-
8
- interface CursorSessionCwdExtensionApi {
9
- on(event: "session_start", handler: ExtensionHandler<SessionStartEvent>): void;
10
- }
11
-
12
- /**
13
- * Pi session cwd when known; falls back to process.cwd() before session_start.
14
- * Updated on session_start only until pi threads cwd into streamSimple—mid-session cwd
15
- * changes without a new session_start event are not reflected here.
16
- */
17
- export function getCursorSessionCwd(): string {
18
- return getCursorSessionCwdFromScope();
19
- }
20
-
21
- export function registerCursorSessionCwd(pi: CursorSessionCwdExtensionApi): void {
22
- registerCursorSessionScope(pi);
23
- }
24
-
25
- export const __testUtils = {
26
- set: cursorSessionScopeTestUtils.set,
27
- reset: cursorSessionScopeTestUtils.reset,
28
- };
@@ -1,9 +0,0 @@
1
- export {
2
- CURSOR_REPLAY_ACTIVITY_TOOL_NAME,
3
- getCursorReplayActivityTitle,
4
- getCursorReplayPromptLabel,
5
- isCursorReplayToolName,
6
- isExcludedFromCursorBridgeExposure,
7
- type CursorReplayActivityToolName,
8
- type CursorReplayToolName,
9
- } from "./cursor-tool-presentation-registry.js";