pi-cursor-sdk 0.1.36 → 0.1.38

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 (74) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/docs/cursor-model-ux-spec.md +1 -1
  3. package/docs/cursor-native-tool-replay.md +9 -9
  4. package/package.json +1 -1
  5. package/scripts/platform-smoke/card-detect.mjs +1 -1
  6. package/src/context-window-cache.ts +10 -14
  7. package/src/context.ts +1 -1
  8. package/src/cursor-agent-message-web-tools.ts +2 -1
  9. package/src/cursor-agents-context-registration.ts +18 -0
  10. package/src/cursor-agents-context.ts +21 -30
  11. package/src/cursor-edit-diff.ts +4 -2
  12. package/src/cursor-fallback-warning.ts +22 -0
  13. package/src/cursor-incomplete-tool-visibility.ts +5 -11
  14. package/src/cursor-live-run-coordinator.ts +1 -1
  15. package/src/cursor-mcp-timeout-override.ts +0 -2
  16. package/src/cursor-model-lifecycle.ts +72 -0
  17. package/src/cursor-native-replay-routing.ts +1 -1
  18. package/src/cursor-native-replay-trace.ts +1 -1
  19. package/src/cursor-native-tool-display-registration.ts +16 -28
  20. package/src/cursor-native-tool-display-replay.ts +12 -47
  21. package/src/cursor-native-tool-display-state.ts +1 -1
  22. package/src/cursor-native-tool-display-tools.ts +10 -18
  23. package/src/cursor-native-tool-names.ts +16 -0
  24. package/src/cursor-pi-tool-bridge-env.ts +12 -0
  25. package/src/cursor-pi-tool-bridge-mcp.ts +16 -21
  26. package/src/cursor-pi-tool-bridge-run.ts +5 -5
  27. package/src/cursor-pi-tool-bridge-server.ts +8 -3
  28. package/src/cursor-pi-tool-bridge-snapshot.ts +7 -13
  29. package/src/cursor-pi-tool-bridge.ts +7 -7
  30. package/src/cursor-provider-lazy.ts +51 -0
  31. package/src/cursor-provider-live-run-drain.ts +1 -1
  32. package/src/cursor-provider-run-finalizer.ts +5 -5
  33. package/src/cursor-provider-run-outcome.ts +0 -1
  34. package/src/cursor-provider-turn-coordinator.ts +4 -5
  35. package/src/cursor-provider-turn-display-router.ts +5 -1
  36. package/src/cursor-provider-turn-emit.ts +1 -1
  37. package/src/cursor-provider-turn-lifecycle-emitter.ts +1 -5
  38. package/src/cursor-provider-turn-prepare.ts +13 -9
  39. package/src/cursor-provider-turn-runner.ts +3 -11
  40. package/src/cursor-provider-turn-sdk-normalizer.ts +28 -5
  41. package/src/cursor-provider-turn-send.ts +7 -2
  42. package/src/cursor-provider-turn-types.ts +1 -3
  43. package/src/cursor-provider.ts +3 -2
  44. package/src/cursor-question-tool.ts +5 -18
  45. package/src/cursor-record-utils.ts +42 -0
  46. package/src/cursor-replay-activity-builders.ts +16 -122
  47. package/src/cursor-replay-tool-details.ts +57 -197
  48. package/src/cursor-sdk-event-debug.ts +6 -6
  49. package/src/cursor-sensitive-text.ts +4 -4
  50. package/src/cursor-session-agent-lifecycle.ts +47 -0
  51. package/src/cursor-session-agent.ts +9 -47
  52. package/src/cursor-session-scope.ts +23 -4
  53. package/src/cursor-setting-sources.ts +8 -8
  54. package/src/cursor-skill-tool.ts +25 -32
  55. package/src/cursor-state.ts +66 -45
  56. package/src/cursor-tool-lifecycle.ts +16 -9
  57. package/src/cursor-tool-presentation-registry.ts +42 -169
  58. package/src/cursor-tool-result-display-readers.ts +185 -0
  59. package/src/cursor-tool-transcript.ts +17 -33
  60. package/src/cursor-tool-visibility.ts +9 -1
  61. package/src/cursor-transcript-tool-formatters.ts +23 -172
  62. package/src/cursor-transcript-tool-specs.ts +17 -57
  63. package/src/cursor-transcript-utils.ts +2 -34
  64. package/src/cursor-usage-accounting.ts +0 -6
  65. package/src/cursor-web-tool-activity.ts +4 -12
  66. package/src/cursor-web-tool-args.ts +1 -9
  67. package/src/index.ts +15 -16
  68. package/src/model-discovery.ts +5 -4
  69. package/src/model-list-cache.ts +37 -38
  70. package/src/cursor-native-tool-display.ts +0 -10
  71. package/src/cursor-provider-turn-api-key.ts +0 -1
  72. package/src/cursor-provider-turn-message-offset.ts +0 -15
  73. package/src/cursor-session-cwd.ts +0 -28
  74. package/src/cursor-tool-names.ts +0 -18
@@ -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,18 +0,0 @@
1
- export {
2
- CURSOR_REPLAY_ACTIVITY_LABEL_KEYS_BY_TOOL_NAME,
3
- CURSOR_REPLAY_ACTIVITY_TOOL_NAME,
4
- CURSOR_REPLAY_LEGACY_TOOL_NAMES,
5
- getCursorReplayActivityLabelKey,
6
- getCursorReplayActivityTitle,
7
- getCursorReplayDisplayLabel,
8
- getCursorReplayPromptLabel,
9
- getCursorReplaySideEffectDescription,
10
- getCursorReplayOperationLabel,
11
- getCursorReplayWrapperLabel,
12
- isCursorReplayLegacyToolName,
13
- isCursorReplayToolName,
14
- isExcludedFromCursorBridgeExposure,
15
- type CursorReplayActivityToolName,
16
- type CursorReplayLegacyToolName,
17
- type CursorReplayToolName,
18
- } from "./cursor-tool-presentation-registry.js";