pi-cursor-sdk 0.1.9 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/README.md +29 -10
- package/docs/cursor-model-ux-spec.md +16 -19
- package/package.json +12 -6
- package/scripts/refresh-cursor-model-snapshots.mjs +234 -0
- package/src/context.ts +128 -35
- package/src/cursor-fallback-models.generated.ts +145 -0
- package/src/cursor-native-tool-display.ts +152 -13
- package/src/cursor-provider.ts +32 -5
- package/src/cursor-tool-transcript.ts +44 -5
- package/src/index.ts +35 -8
- package/src/model-discovery.ts +2 -142
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { closeSync, openSync, readSync, statSync } from "node:fs";
|
|
1
|
+
import { closeSync, openSync, readSync, realpathSync, statSync } from "node:fs";
|
|
2
2
|
import { isAbsolute, relative, resolve } from "node:path";
|
|
3
3
|
|
|
4
4
|
const DEFAULT_MAX_TRANSCRIPT_CHARS = 24000;
|
|
@@ -6,6 +6,8 @@ const DEFAULT_MAX_TRANSCRIPT_LINES = 800;
|
|
|
6
6
|
const DEFAULT_MAX_LIST_ITEMS = 200;
|
|
7
7
|
const DEFAULT_READ_TRANSCRIPT_CHARS = 4000;
|
|
8
8
|
const DEFAULT_READ_TRANSCRIPT_LINES = 12;
|
|
9
|
+
const LOCAL_READ_PREVIEW_NOTICE =
|
|
10
|
+
"[local file preview at transcript time; Cursor read result content was unavailable]";
|
|
9
11
|
|
|
10
12
|
interface TranscriptOptions {
|
|
11
13
|
maxChars?: number;
|
|
@@ -194,15 +196,18 @@ function isSensitivePreviewPath(filePath: string): boolean {
|
|
|
194
196
|
function readFilePreview(path: string, options: TranscriptOptions): string | undefined {
|
|
195
197
|
const cwd = options.cwd ?? process.cwd();
|
|
196
198
|
const filePath = resolveFilePath(path, cwd);
|
|
197
|
-
if (!isPathWithinCwd(filePath, cwd) || isSensitivePreviewPath(filePath)) return undefined;
|
|
198
199
|
|
|
199
200
|
const maxChars = options.maxChars ?? DEFAULT_READ_TRANSCRIPT_CHARS;
|
|
200
201
|
const maxBytes = Math.max(8192, maxChars * 4);
|
|
201
202
|
let fd: number | undefined;
|
|
202
203
|
try {
|
|
203
|
-
const
|
|
204
|
+
const realCwd = realpathSync(cwd);
|
|
205
|
+
const realFilePath = realpathSync(filePath);
|
|
206
|
+
if (!isPathWithinCwd(realFilePath, realCwd) || isSensitivePreviewPath(filePath) || isSensitivePreviewPath(realFilePath)) return undefined;
|
|
207
|
+
|
|
208
|
+
const stat = statSync(realFilePath);
|
|
204
209
|
if (!stat.isFile()) return undefined;
|
|
205
|
-
fd = openSync(
|
|
210
|
+
fd = openSync(realFilePath, "r");
|
|
206
211
|
const buffer = Buffer.alloc(Math.min(stat.size, maxBytes));
|
|
207
212
|
const bytesRead = readSync(fd, buffer, 0, buffer.length, 0);
|
|
208
213
|
const text = buffer.toString("utf8", 0, bytesRead);
|
|
@@ -229,7 +234,10 @@ function getReadContent(args: Record<string, unknown>, result: NormalizedResult,
|
|
|
229
234
|
};
|
|
230
235
|
const value = asRecord(result.value);
|
|
231
236
|
const resultContent = getString(value, "content");
|
|
232
|
-
|
|
237
|
+
if (resultContent && resultContent.length > 0) return resultContent;
|
|
238
|
+
if (!rawPath) return stringifyUnknown(result.value);
|
|
239
|
+
const localPreview = readFilePreview(rawPath, readOptions);
|
|
240
|
+
return localPreview ? `${LOCAL_READ_PREVIEW_NOTICE}\n${localPreview}` : stringifyUnknown(result.value);
|
|
233
241
|
}
|
|
234
242
|
|
|
235
243
|
function formatRead(args: Record<string, unknown>, result: NormalizedResult, options: TranscriptOptions): string {
|
|
@@ -536,6 +544,37 @@ export function buildCursorPiToolDisplay(toolCall: unknown, options: TranscriptO
|
|
|
536
544
|
};
|
|
537
545
|
}
|
|
538
546
|
|
|
547
|
+
if (name === "edit") {
|
|
548
|
+
const value = asRecord(result.value);
|
|
549
|
+
return {
|
|
550
|
+
toolName: "cursor_edit",
|
|
551
|
+
args,
|
|
552
|
+
result: textToolResult(formatEdit(args, result, options), {
|
|
553
|
+
cursorToolName: "edit",
|
|
554
|
+
path: typeof args.path === "string" ? formatDisplayPath(args.path, options.cwd) : undefined,
|
|
555
|
+
linesAdded: getNumber(value, "linesAdded"),
|
|
556
|
+
linesRemoved: getNumber(value, "linesRemoved"),
|
|
557
|
+
diffString: getString(value, "diffString"),
|
|
558
|
+
}),
|
|
559
|
+
isError: result.status === "error",
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
if (name === "write") {
|
|
564
|
+
const value = asRecord(result.value);
|
|
565
|
+
return {
|
|
566
|
+
toolName: "cursor_write",
|
|
567
|
+
args,
|
|
568
|
+
result: textToolResult(formatWrite(args, result, options), {
|
|
569
|
+
cursorToolName: "write",
|
|
570
|
+
path: typeof args.path === "string" ? formatDisplayPath(args.path, options.cwd) : undefined,
|
|
571
|
+
linesCreated: getNumber(value, "linesCreated"),
|
|
572
|
+
fileSize: getNumber(value, "fileSize"),
|
|
573
|
+
}),
|
|
574
|
+
isError: result.status === "error",
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
|
|
539
578
|
return buildGenericPiToolDisplay(name, args, result, options);
|
|
540
579
|
}
|
|
541
580
|
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,24 @@
|
|
|
1
|
-
import type { ExtensionAPI } 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 { registerCursorFastControls } from "./cursor-state.js";
|
|
4
4
|
import { registerCursorNativeToolDisplay } from "./cursor-native-tool-display.js";
|
|
5
5
|
import { streamCursor } from "./cursor-provider.js";
|
|
6
6
|
|
|
7
|
+
function createCursorProviderConfig(models: ProviderModelConfig[]): ProviderConfig {
|
|
8
|
+
return {
|
|
9
|
+
name: "Cursor",
|
|
10
|
+
baseUrl: "https://cursor.com",
|
|
11
|
+
apiKey: "CURSOR_API_KEY",
|
|
12
|
+
api: "cursor-sdk",
|
|
13
|
+
models,
|
|
14
|
+
streamSimple: streamCursor,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function registerCursorProvider(pi: ExtensionAPI, models: ProviderModelConfig[]): void {
|
|
19
|
+
pi.registerProvider("cursor", createCursorProviderConfig(models));
|
|
20
|
+
}
|
|
21
|
+
|
|
7
22
|
export default async function (pi: ExtensionAPI) {
|
|
8
23
|
registerCursorFastControls(pi);
|
|
9
24
|
registerCursorNativeToolDisplay(pi);
|
|
@@ -21,12 +36,24 @@ export default async function (pi: ExtensionAPI) {
|
|
|
21
36
|
});
|
|
22
37
|
}
|
|
23
38
|
|
|
24
|
-
pi.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
39
|
+
pi.registerCommand("cursor-refresh-models", {
|
|
40
|
+
description: "Refresh the live Cursor model catalog without restarting pi",
|
|
41
|
+
handler: async (_args, ctx) => {
|
|
42
|
+
let refreshFallbackIssue: CursorModelFallbackIssue | undefined;
|
|
43
|
+
const refreshedModels = await discoverModels({
|
|
44
|
+
onFallback: (issue) => {
|
|
45
|
+
refreshFallbackIssue = issue;
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
registerCursorProvider(pi, refreshedModels);
|
|
49
|
+
if (!ctx.hasUI) return;
|
|
50
|
+
if (refreshFallbackIssue) {
|
|
51
|
+
ctx.ui.notify(`Cursor model catalog refresh still using fallback models: ${refreshFallbackIssue.message}`, "warning");
|
|
52
|
+
} else {
|
|
53
|
+
ctx.ui.notify(`Cursor model catalog refreshed with ${refreshedModels.length} model${refreshedModels.length === 1 ? "" : "s"}.`, "info");
|
|
54
|
+
}
|
|
55
|
+
},
|
|
31
56
|
});
|
|
57
|
+
|
|
58
|
+
registerCursorProvider(pi, models);
|
|
32
59
|
}
|
package/src/model-discovery.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
import { AuthStorage, type ProviderModelConfig } from "@earendil-works/pi-coding-agent";
|
|
9
9
|
import type { ModelThinkingLevel, ThinkingLevelMap } from "@earendil-works/pi-ai";
|
|
10
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,148 +18,7 @@ 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 /
|
|
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: "200k" }],
|
|
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
|
|