pi-cursor-sdk 0.1.18 → 0.1.19
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 +38 -0
- package/README.md +37 -0
- package/docs/cursor-live-smoke-checklist.md +3 -0
- package/docs/cursor-model-ux-spec.md +4 -3
- package/docs/cursor-native-tool-replay.md +96 -2
- package/docs/cursor-testing-lessons.md +234 -5
- package/package.json +8 -2
- package/scripts/debug-provider-events.mjs +403 -0
- package/scripts/debug-sdk-events.mjs +413 -0
- package/scripts/lib/cursor-probe-utils.mjs +52 -0
- package/scripts/lib/cursor-sdk-output-filter.mjs +86 -0
- package/scripts/validate-smoke-jsonl.mjs +27 -3
- package/src/context.ts +45 -32
- package/src/cursor-agent-message-web-tools.ts +172 -0
- package/src/cursor-agents-context.ts +176 -0
- package/src/cursor-incomplete-tool-visibility.ts +118 -0
- package/src/cursor-live-run-coordinator.ts +18 -7
- package/src/cursor-model.ts +12 -0
- package/src/cursor-native-tool-display-registration.ts +1 -4
- package/src/cursor-native-tool-display-replay.ts +63 -5
- package/src/cursor-native-tool-display-tools.ts +20 -0
- package/src/cursor-pi-tool-bridge-diagnostics.ts +11 -1
- package/src/cursor-pi-tool-bridge-run.ts +16 -1
- package/src/cursor-pi-tool-bridge-types.ts +3 -0
- package/src/cursor-provider-errors.ts +96 -0
- package/src/cursor-provider-live-run-drain.ts +181 -62
- package/src/cursor-provider-turn-coordinator.ts +198 -32
- package/src/cursor-provider.ts +270 -83
- package/src/cursor-question-tool.ts +1 -4
- package/src/cursor-sdk-abort-error-guard.ts +109 -0
- package/src/cursor-sdk-event-debug-constants.ts +40 -0
- package/src/cursor-sdk-event-debug-session.ts +163 -0
- package/src/cursor-sdk-event-debug.ts +597 -0
- package/src/cursor-sensitive-text.ts +27 -7
- package/src/cursor-session-agent.ts +25 -3
- package/src/cursor-session-send-policy.ts +43 -0
- package/src/cursor-setting-sources.ts +29 -0
- package/src/cursor-state.ts +1 -5
- package/src/cursor-tool-lifecycle.ts +111 -0
- package/src/cursor-tool-names.ts +12 -0
- package/src/cursor-tool-transcript.ts +4 -2
- package/src/cursor-transcript-tool-formatters.ts +228 -5
- package/src/cursor-transcript-tool-specs.ts +113 -14
- package/src/cursor-transcript-utils.ts +12 -0
- package/src/cursor-web-tool-activity.ts +84 -0
- package/src/index.ts +4 -1
|
@@ -33,7 +33,11 @@ import {
|
|
|
33
33
|
formatGrep,
|
|
34
34
|
formatLs,
|
|
35
35
|
formatMcp,
|
|
36
|
+
formatWebFetch,
|
|
37
|
+
formatWebSearch,
|
|
36
38
|
formatPlan,
|
|
39
|
+
formatRecordScreen,
|
|
40
|
+
formatSemSearch,
|
|
37
41
|
formatRead,
|
|
38
42
|
formatReadLints,
|
|
39
43
|
formatShell,
|
|
@@ -55,9 +59,14 @@ import {
|
|
|
55
59
|
getTodoTotalCount,
|
|
56
60
|
inferImageMimeType,
|
|
57
61
|
summarizePlan,
|
|
62
|
+
summarizeMcp,
|
|
63
|
+
summarizeRecordScreen,
|
|
64
|
+
summarizeSemSearch,
|
|
58
65
|
summarizeTask,
|
|
59
66
|
summarizeTodos,
|
|
67
|
+
usesLocalReadPreview,
|
|
60
68
|
} from "./cursor-transcript-tool-formatters.js";
|
|
69
|
+
import { extractWebFetchTarget, extractWebSearchQuery } from "./cursor-web-tool-activity.js";
|
|
61
70
|
|
|
62
71
|
export interface ToolDisplayContext {
|
|
63
72
|
rawName: string;
|
|
@@ -105,13 +114,13 @@ function buildReplaySummaryDisplay(
|
|
|
105
114
|
details: Record<string, unknown>,
|
|
106
115
|
): CursorPiToolDisplay {
|
|
107
116
|
const isError = result.status === "error";
|
|
108
|
-
const summary = isError ?
|
|
117
|
+
const summary = isError ? details.summary : (details.summary ?? firstNonEmptyLine(contentText));
|
|
109
118
|
return {
|
|
110
119
|
toolName,
|
|
111
120
|
args,
|
|
112
121
|
result: textToolResult(contentText, {
|
|
113
122
|
...details,
|
|
114
|
-
summary
|
|
123
|
+
summary,
|
|
115
124
|
expandedText: details.expandedText ?? contentText,
|
|
116
125
|
}),
|
|
117
126
|
isError,
|
|
@@ -144,15 +153,34 @@ function buildActivityReplayDisplay(cursorToolName: string, spec: ToolDisplaySpe
|
|
|
144
153
|
);
|
|
145
154
|
}
|
|
146
155
|
|
|
156
|
+
function buildGenericUnknownToolActivityTitle(displayName: string): string {
|
|
157
|
+
if (displayName === "unknown") return "Cursor tool";
|
|
158
|
+
return `Cursor ${truncateArg(displayName)}`;
|
|
159
|
+
}
|
|
160
|
+
|
|
147
161
|
function buildGenericPiToolDisplay(context: ToolDisplayContext): CursorPiToolDisplay {
|
|
148
|
-
const { name, args, result, options } = context;
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
162
|
+
const { rawName, name, args, result, options } = context;
|
|
163
|
+
const displayName = rawName.trim() || name;
|
|
164
|
+
const activityTitle = buildGenericUnknownToolActivityTitle(displayName);
|
|
165
|
+
const contentText = formatFallback(name, args, result, options);
|
|
166
|
+
const fallbackBody = contentText.includes("\n\n") ? contentText.slice(contentText.indexOf("\n\n") + 2) : "";
|
|
167
|
+
const activitySummary =
|
|
168
|
+
result.status === "error" ? undefined : firstNonEmptyLine(fallbackBody);
|
|
169
|
+
const activityArgs = buildCursorActivityDisplayArgs(
|
|
170
|
+
{ cursorToolName: displayName === "unknown" ? "tool" : displayName },
|
|
171
|
+
activityTitle,
|
|
172
|
+
activitySummary,
|
|
173
|
+
);
|
|
174
|
+
const summary =
|
|
175
|
+
result.status === "error"
|
|
176
|
+
? undefined
|
|
177
|
+
: activitySummary ?? truncateArg(displayName === "unknown" ? "tool" : displayName);
|
|
178
|
+
return buildReplaySummaryDisplay(CURSOR_REPLAY_ACTIVITY_TOOL_NAME, activityArgs, result, contentText, {
|
|
179
|
+
cursorToolName: name,
|
|
180
|
+
title: activityTitle,
|
|
181
|
+
summary,
|
|
182
|
+
expandedText: contentText,
|
|
183
|
+
});
|
|
156
184
|
}
|
|
157
185
|
|
|
158
186
|
function buildEditPiToolDisplay(context: ToolDisplayContext): CursorPiToolDisplay {
|
|
@@ -238,10 +266,14 @@ const TOOL_DISPLAY_SPECS: Record<string, ToolDisplaySpec> = {
|
|
|
238
266
|
formatTranscript: ({ args, result, options }) => formatRead(args, result, options),
|
|
239
267
|
buildPiToolDisplay: ({ args, result, options }) => {
|
|
240
268
|
const isError = result.status === "error";
|
|
269
|
+
const usesLocalPreview = !isError && usesLocalReadPreview(args, result, options);
|
|
241
270
|
return {
|
|
242
271
|
toolName: "read",
|
|
243
|
-
args: buildReadDisplayArgs(args, options),
|
|
244
|
-
result: textToolResult(
|
|
272
|
+
args: buildReadDisplayArgs(args, options, result),
|
|
273
|
+
result: textToolResult(
|
|
274
|
+
isError ? formatError(result.error) : formatNativeReadDisplayContent(args, result, options),
|
|
275
|
+
usesLocalPreview ? { localReadPreview: true } : undefined,
|
|
276
|
+
),
|
|
245
277
|
isError,
|
|
246
278
|
};
|
|
247
279
|
},
|
|
@@ -420,9 +452,76 @@ const TOOL_DISPLAY_SPECS: Record<string, ToolDisplaySpec> = {
|
|
|
420
452
|
const toolName = getString(args, "toolName") ?? "mcp";
|
|
421
453
|
return { toolName: truncateArg(toolName) };
|
|
422
454
|
},
|
|
423
|
-
buildActivitySummary: ({ args }) =>
|
|
455
|
+
buildActivitySummary: ({ args, result }) => summarizeMcp(args, result),
|
|
456
|
+
buildDetails: ({ args, result }) => ({
|
|
457
|
+
summary: result.status === "error" ? undefined : summarizeMcp(args, result),
|
|
458
|
+
}),
|
|
459
|
+
},
|
|
460
|
+
},
|
|
461
|
+
semSearch: {
|
|
462
|
+
formatTranscript: ({ args, result, options }) => formatSemSearch(args, result, options),
|
|
463
|
+
buildPiToolDisplay: (context) => buildActivityReplayDisplay("semSearch", TOOL_DISPLAY_SPECS.semSearch, context),
|
|
464
|
+
activityReplay: {
|
|
465
|
+
labelKey: "cursor_sem_search",
|
|
466
|
+
buildActivityArgs: ({ args }) => {
|
|
467
|
+
const query = getString(args, "query") ?? "semantic search";
|
|
468
|
+
return { query: truncateArg(query) };
|
|
469
|
+
},
|
|
470
|
+
buildActivitySummary: ({ args }) => summarizeSemSearch(args),
|
|
471
|
+
buildDetails: ({ result }, contentText) => ({
|
|
472
|
+
summary: result.status === "error" ? undefined : firstNonEmptyLine(contentText) ?? "semantic search results captured",
|
|
473
|
+
}),
|
|
474
|
+
},
|
|
475
|
+
},
|
|
476
|
+
recordScreen: {
|
|
477
|
+
formatTranscript: ({ args, result, options }) => formatRecordScreen(args, result, options),
|
|
478
|
+
buildPiToolDisplay: (context) => buildActivityReplayDisplay("recordScreen", TOOL_DISPLAY_SPECS.recordScreen, context),
|
|
479
|
+
activityReplay: {
|
|
480
|
+
labelKey: "cursor_record_screen",
|
|
481
|
+
buildActivityArgs: ({ args, result, options }) => {
|
|
482
|
+
const mode = getString(args, "mode");
|
|
483
|
+
const path = getString(asRecord(result.value), "path");
|
|
484
|
+
return {
|
|
485
|
+
...(mode ? { mode } : {}),
|
|
486
|
+
...(path ? { path: formatDisplayPath(path, options.cwd) } : {}),
|
|
487
|
+
};
|
|
488
|
+
},
|
|
489
|
+
buildActivitySummary: ({ args, result, options }) => summarizeRecordScreen(args, result, options),
|
|
490
|
+
buildDetails: ({ args, result, options }, contentText) => ({
|
|
491
|
+
summary:
|
|
492
|
+
result.status === "error"
|
|
493
|
+
? undefined
|
|
494
|
+
: summarizeRecordScreen(args, result, options) ?? firstNonEmptyLine(contentText) ?? "screen recording updated",
|
|
495
|
+
}),
|
|
496
|
+
},
|
|
497
|
+
},
|
|
498
|
+
webSearch: {
|
|
499
|
+
formatTranscript: ({ args, result, options }) => formatWebSearch(args, result, options),
|
|
500
|
+
buildPiToolDisplay: (context) => buildActivityReplayDisplay("webSearch", TOOL_DISPLAY_SPECS.webSearch, context),
|
|
501
|
+
activityReplay: {
|
|
502
|
+
labelKey: "cursor_web_search",
|
|
503
|
+
buildActivityArgs: ({ args }) => {
|
|
504
|
+
const query = extractWebSearchQuery(args);
|
|
505
|
+
return query ? { query: truncateArg(query) } : {};
|
|
506
|
+
},
|
|
507
|
+
buildActivitySummary: ({ args }) => truncateArg(extractWebSearchQuery(args) ?? "web search"),
|
|
508
|
+
buildDetails: ({ result }, contentText) => ({
|
|
509
|
+
summary: result.status === "error" ? undefined : firstNonEmptyLine(contentText) ?? "web search result captured",
|
|
510
|
+
}),
|
|
511
|
+
},
|
|
512
|
+
},
|
|
513
|
+
webFetch: {
|
|
514
|
+
formatTranscript: ({ args, result, options }) => formatWebFetch(args, result, options),
|
|
515
|
+
buildPiToolDisplay: (context) => buildActivityReplayDisplay("webFetch", TOOL_DISPLAY_SPECS.webFetch, context),
|
|
516
|
+
activityReplay: {
|
|
517
|
+
labelKey: "cursor_web_fetch",
|
|
518
|
+
buildActivityArgs: ({ args }) => {
|
|
519
|
+
const target = extractWebFetchTarget(args);
|
|
520
|
+
return target ? { url: truncateArg(target) } : {};
|
|
521
|
+
},
|
|
522
|
+
buildActivitySummary: ({ args }) => truncateArg(extractWebFetchTarget(args) ?? "web fetch"),
|
|
424
523
|
buildDetails: ({ result }, contentText) => ({
|
|
425
|
-
summary: result.status === "error" ? undefined : firstNonEmptyLine(contentText) ?? "
|
|
524
|
+
summary: result.status === "error" ? undefined : firstNonEmptyLine(contentText) ?? "web fetch result captured",
|
|
426
525
|
}),
|
|
427
526
|
},
|
|
428
527
|
},
|
|
@@ -43,6 +43,10 @@ export const DEFAULT_NATIVE_READ_DISPLAY_LINES = 20;
|
|
|
43
43
|
export const LOCAL_READ_PREVIEW_NOTICE =
|
|
44
44
|
"[local file preview at transcript time; Cursor read result content was unavailable]";
|
|
45
45
|
|
|
46
|
+
export function isLocalReadPreviewContent(text: string): boolean {
|
|
47
|
+
return text.startsWith(LOCAL_READ_PREVIEW_NOTICE);
|
|
48
|
+
}
|
|
49
|
+
|
|
46
50
|
export function getString(record: Record<string, unknown> | undefined, key: string): string | undefined {
|
|
47
51
|
const value = record?.[key];
|
|
48
52
|
return typeof value === "string" ? value : undefined;
|
|
@@ -113,6 +117,14 @@ export function normalizeToolName(name: string): string {
|
|
|
113
117
|
case "notebook_edit":
|
|
114
118
|
case "notebookedit":
|
|
115
119
|
return "edit";
|
|
120
|
+
case "websearch":
|
|
121
|
+
case "web_search":
|
|
122
|
+
case "web-search":
|
|
123
|
+
return "webSearch";
|
|
124
|
+
case "webfetch":
|
|
125
|
+
case "web_fetch":
|
|
126
|
+
case "web-fetch":
|
|
127
|
+
return "webFetch";
|
|
116
128
|
default:
|
|
117
129
|
return normalized || "unknown";
|
|
118
130
|
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { normalizeToolName } from "./cursor-transcript-utils.js";
|
|
2
|
+
|
|
3
|
+
export type CursorWebToolKind = "webSearch" | "webFetch";
|
|
4
|
+
|
|
5
|
+
const WEB_SEARCH_NAME_PATTERN =
|
|
6
|
+
/^(?:web[-_ ]?search|search[-_ ]?web|websearch|browser[-_ ]?search|cursor[-_ ]?web[-_ ]?search)$/i;
|
|
7
|
+
const WEB_FETCH_NAME_PATTERN =
|
|
8
|
+
/^(?:web[-_ ]?fetch|fetch[-_ ]?web|webfetch|browser[-_ ]?fetch|fetch[-_ ]?url|cursor[-_ ]?web[-_ ]?fetch)$/i;
|
|
9
|
+
|
|
10
|
+
function normalizeWebToolLookupName(name: string): string {
|
|
11
|
+
return name.replace(/\s+/g, " ").trim().toLowerCase();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function classifyCursorWebToolKind(name: string | undefined): CursorWebToolKind | undefined {
|
|
15
|
+
if (!name) return undefined;
|
|
16
|
+
const normalized = normalizeWebToolLookupName(name);
|
|
17
|
+
if (WEB_SEARCH_NAME_PATTERN.test(normalized) || normalized === "websearch" || normalized === "web_search") {
|
|
18
|
+
return "webSearch";
|
|
19
|
+
}
|
|
20
|
+
if (WEB_FETCH_NAME_PATTERN.test(normalized) || normalized === "webfetch" || normalized === "web_fetch") {
|
|
21
|
+
return "webFetch";
|
|
22
|
+
}
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function getNestedMcpArgs(args: Record<string, unknown>): Record<string, unknown> {
|
|
27
|
+
const nested = args.args;
|
|
28
|
+
return nested && typeof nested === "object" && !Array.isArray(nested) ? (nested as Record<string, unknown>) : {};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getMcpToolName(args: Record<string, unknown>): string | undefined {
|
|
32
|
+
const toolName = typeof args.toolName === "string" ? args.toolName : typeof args.tool_name === "string" ? args.tool_name : undefined;
|
|
33
|
+
const trimmed = toolName?.trim();
|
|
34
|
+
return trimmed || undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function firstNonEmptyString(...values: Array<string | undefined>): string | undefined {
|
|
38
|
+
for (const value of values) {
|
|
39
|
+
const trimmed = value?.trim();
|
|
40
|
+
if (trimmed) return trimmed;
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function extractWebSearchQuery(args: Record<string, unknown>): string | undefined {
|
|
46
|
+
const nested = getNestedMcpArgs(args);
|
|
47
|
+
return firstNonEmptyString(
|
|
48
|
+
typeof args.search_term === "string" ? args.search_term : undefined,
|
|
49
|
+
typeof args.searchTerm === "string" ? args.searchTerm : undefined,
|
|
50
|
+
typeof args.query === "string" ? args.query : undefined,
|
|
51
|
+
typeof args.q === "string" ? args.q : undefined,
|
|
52
|
+
typeof nested.search_term === "string" ? nested.search_term : undefined,
|
|
53
|
+
typeof nested.searchTerm === "string" ? nested.searchTerm : undefined,
|
|
54
|
+
typeof nested.query === "string" ? nested.query : undefined,
|
|
55
|
+
typeof nested.q === "string" ? nested.q : undefined,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function extractWebFetchTarget(args: Record<string, unknown>): string | undefined {
|
|
60
|
+
const nested = getNestedMcpArgs(args);
|
|
61
|
+
return firstNonEmptyString(
|
|
62
|
+
typeof args.url === "string" ? args.url : undefined,
|
|
63
|
+
typeof args.uri === "string" ? args.uri : undefined,
|
|
64
|
+
typeof args.href === "string" ? args.href : undefined,
|
|
65
|
+
typeof nested.url === "string" ? nested.url : undefined,
|
|
66
|
+
typeof nested.uri === "string" ? nested.uri : undefined,
|
|
67
|
+
typeof nested.href === "string" ? nested.href : undefined,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Maps SDK/host/MCP tool names to transcript display keys.
|
|
73
|
+
* Web search/fetch often arrives as MCP `toolName` values, not dedicated SDK ToolTypes.
|
|
74
|
+
*/
|
|
75
|
+
export function resolveTranscriptToolName(rawName: string, args: Record<string, unknown>): string {
|
|
76
|
+
const normalized = normalizeToolName(rawName);
|
|
77
|
+
const directWebKind = classifyCursorWebToolKind(rawName) ?? classifyCursorWebToolKind(normalized);
|
|
78
|
+
if (directWebKind) return directWebKind;
|
|
79
|
+
if (normalized === "mcp") {
|
|
80
|
+
const mcpWebKind = classifyCursorWebToolKind(getMcpToolName(args));
|
|
81
|
+
if (mcpWebKind) return mcpWebKind;
|
|
82
|
+
}
|
|
83
|
+
return normalized;
|
|
84
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { registerCursorNativeToolDisplay } from "./cursor-native-tool-display.js
|
|
|
5
5
|
import { registerCursorPiToolBridge } from "./cursor-pi-tool-bridge.js";
|
|
6
6
|
import { registerCursorQuestionTool } from "./cursor-question-tool.js";
|
|
7
7
|
import { registerCursorSessionCwd } from "./cursor-session-cwd.js";
|
|
8
|
+
import { registerCursorAgentsContextDedup } from "./cursor-agents-context.js";
|
|
8
9
|
import { registerCursorSessionAgent } from "./cursor-session-agent.js";
|
|
9
10
|
import { streamCursor } from "./cursor-provider.js";
|
|
10
11
|
|
|
@@ -21,7 +22,8 @@ type CursorExtensionApi =
|
|
|
21
22
|
& Parameters<typeof registerCursorFastControls>[0]
|
|
22
23
|
& Parameters<typeof registerCursorNativeToolDisplay>[0]
|
|
23
24
|
& Parameters<typeof registerCursorQuestionTool>[0]
|
|
24
|
-
& Parameters<typeof registerCursorPiToolBridge>[0]
|
|
25
|
+
& Parameters<typeof registerCursorPiToolBridge>[0]
|
|
26
|
+
& Parameters<typeof registerCursorAgentsContextDedup>[0];
|
|
25
27
|
|
|
26
28
|
function createCursorProviderConfig(models: ProviderModelConfig[]): ProviderConfig {
|
|
27
29
|
return {
|
|
@@ -46,6 +48,7 @@ export default async function (pi: CursorExtensionApi) {
|
|
|
46
48
|
registerCursorNativeToolDisplay(pi);
|
|
47
49
|
registerCursorQuestionTool(pi);
|
|
48
50
|
registerCursorPiToolBridge(pi);
|
|
51
|
+
registerCursorAgentsContextDedup(pi);
|
|
49
52
|
let fallbackIssue: CursorModelFallbackIssue | undefined;
|
|
50
53
|
const models = await discoverModels({
|
|
51
54
|
onFallback: (issue) => {
|