pi-cursor-sdk 0.1.18 → 0.1.20
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 +58 -0
- package/README.md +59 -1
- package/docs/cursor-live-smoke-checklist.md +4 -1
- package/docs/cursor-model-ux-spec.md +7 -5
- package/docs/cursor-native-tool-replay.md +99 -3
- package/docs/cursor-testing-lessons.md +234 -5
- package/package.json +10 -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/probe-mcp-coldstart.mjs +244 -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 +124 -0
- package/src/cursor-live-run-coordinator.ts +18 -7
- package/src/cursor-mcp-timeout-override.ts +66 -11
- 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 +65 -6
- 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 +220 -33
- package/src/cursor-provider.ts +302 -93
- 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 +602 -0
- package/src/cursor-sensitive-text.ts +27 -7
- package/src/cursor-session-agent.ts +279 -82
- 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 +85 -0
- package/src/cursor-tool-names.ts +39 -0
- package/src/cursor-tool-transcript.ts +4 -2
- package/src/cursor-tool-visibility.ts +63 -0
- package/src/cursor-transcript-tool-formatters.ts +228 -5
- package/src/cursor-transcript-tool-specs.ts +135 -24
- package/src/cursor-transcript-utils.ts +12 -0
- package/src/cursor-web-tool-activity.ts +84 -0
- package/src/index.ts +4 -1
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
CURSOR_REPLAY_ACTIVITY_LABEL_KEYS_BY_TOOL_NAME,
|
|
3
|
+
CURSOR_REPLAY_ACTIVITY_TOOL_NAME,
|
|
4
|
+
getCursorReplayActivityTitle,
|
|
5
|
+
getCursorReplayDisplayLabel,
|
|
6
|
+
type CursorReplayLegacyToolName,
|
|
7
|
+
} from "./cursor-tool-names.js";
|
|
2
8
|
import { resolveCursorEditDiff } from "./cursor-edit-diff.js";
|
|
3
9
|
import {
|
|
4
10
|
asRecord,
|
|
@@ -33,7 +39,11 @@ import {
|
|
|
33
39
|
formatGrep,
|
|
34
40
|
formatLs,
|
|
35
41
|
formatMcp,
|
|
42
|
+
formatWebFetch,
|
|
43
|
+
formatWebSearch,
|
|
36
44
|
formatPlan,
|
|
45
|
+
formatRecordScreen,
|
|
46
|
+
formatSemSearch,
|
|
37
47
|
formatRead,
|
|
38
48
|
formatReadLints,
|
|
39
49
|
formatShell,
|
|
@@ -55,9 +65,14 @@ import {
|
|
|
55
65
|
getTodoTotalCount,
|
|
56
66
|
inferImageMimeType,
|
|
57
67
|
summarizePlan,
|
|
68
|
+
summarizeMcp,
|
|
69
|
+
summarizeRecordScreen,
|
|
70
|
+
summarizeSemSearch,
|
|
58
71
|
summarizeTask,
|
|
59
72
|
summarizeTodos,
|
|
73
|
+
usesLocalReadPreview,
|
|
60
74
|
} from "./cursor-transcript-tool-formatters.js";
|
|
75
|
+
import { extractWebFetchTarget, extractWebSearchQuery } from "./cursor-web-tool-activity.js";
|
|
61
76
|
|
|
62
77
|
export interface ToolDisplayContext {
|
|
63
78
|
rawName: string;
|
|
@@ -105,19 +120,23 @@ function buildReplaySummaryDisplay(
|
|
|
105
120
|
details: Record<string, unknown>,
|
|
106
121
|
): CursorPiToolDisplay {
|
|
107
122
|
const isError = result.status === "error";
|
|
108
|
-
const summary = isError ?
|
|
123
|
+
const summary = isError ? details.summary : (details.summary ?? firstNonEmptyLine(contentText));
|
|
109
124
|
return {
|
|
110
125
|
toolName,
|
|
111
126
|
args,
|
|
112
127
|
result: textToolResult(contentText, {
|
|
113
128
|
...details,
|
|
114
|
-
summary
|
|
129
|
+
summary,
|
|
115
130
|
expandedText: details.expandedText ?? contentText,
|
|
116
131
|
}),
|
|
117
132
|
isError,
|
|
118
133
|
};
|
|
119
134
|
}
|
|
120
135
|
|
|
136
|
+
function getCursorToolActivityTitle(toolName: string): string {
|
|
137
|
+
return getCursorReplayActivityTitle(toolName) ?? buildGenericUnknownToolActivityTitle(toolName);
|
|
138
|
+
}
|
|
139
|
+
|
|
121
140
|
function buildActivityReplayDisplay(cursorToolName: string, spec: ToolDisplaySpec, context: ToolDisplayContext): CursorPiToolDisplay {
|
|
122
141
|
const activity = spec.activityReplay;
|
|
123
142
|
if (!activity) throw new Error(`Missing activity replay spec for ${cursorToolName}`);
|
|
@@ -144,15 +163,34 @@ function buildActivityReplayDisplay(cursorToolName: string, spec: ToolDisplaySpe
|
|
|
144
163
|
);
|
|
145
164
|
}
|
|
146
165
|
|
|
166
|
+
function buildGenericUnknownToolActivityTitle(displayName: string): string {
|
|
167
|
+
if (displayName === "unknown") return "Cursor tool";
|
|
168
|
+
return `Cursor ${truncateArg(displayName)}`;
|
|
169
|
+
}
|
|
170
|
+
|
|
147
171
|
function buildGenericPiToolDisplay(context: ToolDisplayContext): CursorPiToolDisplay {
|
|
148
|
-
const { name, args, result, options } = context;
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
172
|
+
const { rawName, name, args, result, options } = context;
|
|
173
|
+
const displayName = rawName.trim() || name;
|
|
174
|
+
const activityTitle = buildGenericUnknownToolActivityTitle(displayName);
|
|
175
|
+
const contentText = formatFallback(name, args, result, options);
|
|
176
|
+
const fallbackBody = contentText.includes("\n\n") ? contentText.slice(contentText.indexOf("\n\n") + 2) : "";
|
|
177
|
+
const activitySummary =
|
|
178
|
+
result.status === "error" ? undefined : firstNonEmptyLine(fallbackBody);
|
|
179
|
+
const activityArgs = buildCursorActivityDisplayArgs(
|
|
180
|
+
{ cursorToolName: displayName === "unknown" ? "tool" : displayName },
|
|
181
|
+
activityTitle,
|
|
182
|
+
activitySummary,
|
|
183
|
+
);
|
|
184
|
+
const summary =
|
|
185
|
+
result.status === "error"
|
|
186
|
+
? undefined
|
|
187
|
+
: activitySummary ?? truncateArg(displayName === "unknown" ? "tool" : displayName);
|
|
188
|
+
return buildReplaySummaryDisplay(CURSOR_REPLAY_ACTIVITY_TOOL_NAME, activityArgs, result, contentText, {
|
|
189
|
+
cursorToolName: name,
|
|
190
|
+
title: activityTitle,
|
|
191
|
+
summary,
|
|
192
|
+
expandedText: contentText,
|
|
193
|
+
});
|
|
156
194
|
}
|
|
157
195
|
|
|
158
196
|
function buildEditPiToolDisplay(context: ToolDisplayContext): CursorPiToolDisplay {
|
|
@@ -163,7 +201,7 @@ function buildEditPiToolDisplay(context: ToolDisplayContext): CursorPiToolDispla
|
|
|
163
201
|
const nativeEditArgs = buildNativeEditDisplayArgs(rawName, args, options);
|
|
164
202
|
const baseActivityArgs = buildCursorEditActivityDisplayArgs(args, options);
|
|
165
203
|
const displayPath = typeof baseActivityArgs.path === "string" ? baseActivityArgs.path : undefined;
|
|
166
|
-
const activityTitle =
|
|
204
|
+
const activityTitle = getCursorToolActivityTitle("edit");
|
|
167
205
|
const activityArgs = buildCursorActivityDisplayArgs(baseActivityArgs, activityTitle, displayPath);
|
|
168
206
|
const contentText = formatEdit(activityArgs, result, options);
|
|
169
207
|
const details = {
|
|
@@ -212,7 +250,7 @@ function buildWritePiToolDisplay(context: ToolDisplayContext): CursorPiToolDispl
|
|
|
212
250
|
expandedText: contentText,
|
|
213
251
|
};
|
|
214
252
|
if (content === undefined) {
|
|
215
|
-
const activityTitle =
|
|
253
|
+
const activityTitle = getCursorToolActivityTitle("write");
|
|
216
254
|
return buildReplaySummaryDisplay(
|
|
217
255
|
CURSOR_REPLAY_ACTIVITY_TOOL_NAME,
|
|
218
256
|
buildCursorActivityDisplayArgs(displayArgs, activityTitle, displayPath ?? "file"),
|
|
@@ -238,10 +276,14 @@ const TOOL_DISPLAY_SPECS: Record<string, ToolDisplaySpec> = {
|
|
|
238
276
|
formatTranscript: ({ args, result, options }) => formatRead(args, result, options),
|
|
239
277
|
buildPiToolDisplay: ({ args, result, options }) => {
|
|
240
278
|
const isError = result.status === "error";
|
|
279
|
+
const usesLocalPreview = !isError && usesLocalReadPreview(args, result, options);
|
|
241
280
|
return {
|
|
242
281
|
toolName: "read",
|
|
243
|
-
args: buildReadDisplayArgs(args, options),
|
|
244
|
-
result: textToolResult(
|
|
282
|
+
args: buildReadDisplayArgs(args, options, result),
|
|
283
|
+
result: textToolResult(
|
|
284
|
+
isError ? formatError(result.error) : formatNativeReadDisplayContent(args, result, options),
|
|
285
|
+
usesLocalPreview ? { localReadPreview: true } : undefined,
|
|
286
|
+
),
|
|
245
287
|
isError,
|
|
246
288
|
};
|
|
247
289
|
},
|
|
@@ -304,7 +346,7 @@ const TOOL_DISPLAY_SPECS: Record<string, ToolDisplaySpec> = {
|
|
|
304
346
|
formatTranscript: ({ args, result, options }) => formatDelete(args, result, options),
|
|
305
347
|
buildPiToolDisplay: (context) => buildActivityReplayDisplay("delete", TOOL_DISPLAY_SPECS.delete, context),
|
|
306
348
|
activityReplay: {
|
|
307
|
-
labelKey:
|
|
349
|
+
labelKey: CURSOR_REPLAY_ACTIVITY_LABEL_KEYS_BY_TOOL_NAME.delete,
|
|
308
350
|
buildActivityArgs: ({ args, options }) => {
|
|
309
351
|
const displayPath = typeof args.path === "string" ? formatDisplayPath(args.path, options.cwd) : undefined;
|
|
310
352
|
return displayPath ? { path: displayPath } : {};
|
|
@@ -328,7 +370,7 @@ const TOOL_DISPLAY_SPECS: Record<string, ToolDisplaySpec> = {
|
|
|
328
370
|
formatTranscript: ({ args, result, options }) => formatReadLints(args, result, options),
|
|
329
371
|
buildPiToolDisplay: (context) => buildActivityReplayDisplay("readLints", TOOL_DISPLAY_SPECS.readLints, context),
|
|
330
372
|
activityReplay: {
|
|
331
|
-
labelKey:
|
|
373
|
+
labelKey: CURSOR_REPLAY_ACTIVITY_LABEL_KEYS_BY_TOOL_NAME.readLints,
|
|
332
374
|
buildActivityArgs: ({ args, result, options }) => {
|
|
333
375
|
const paths = getReadLintPaths(args, result, options);
|
|
334
376
|
const diagnosticCount = getReadLintDiagnostics(result, options).length;
|
|
@@ -346,7 +388,7 @@ const TOOL_DISPLAY_SPECS: Record<string, ToolDisplaySpec> = {
|
|
|
346
388
|
formatTranscript: ({ args, result, options }) => formatTodos(args, result, options, "updateTodos"),
|
|
347
389
|
buildPiToolDisplay: (context) => buildActivityReplayDisplay("updateTodos", TOOL_DISPLAY_SPECS.updateTodos, context),
|
|
348
390
|
activityReplay: {
|
|
349
|
-
labelKey:
|
|
391
|
+
labelKey: CURSOR_REPLAY_ACTIVITY_LABEL_KEYS_BY_TOOL_NAME.updateTodos,
|
|
350
392
|
buildActivityArgs: ({ args, result }) => {
|
|
351
393
|
const todos = getTodoItems(args, result);
|
|
352
394
|
return { totalCount: getTodoTotalCount(args, result, todos) };
|
|
@@ -359,7 +401,7 @@ const TOOL_DISPLAY_SPECS: Record<string, ToolDisplaySpec> = {
|
|
|
359
401
|
formatTranscript: ({ args, result, options }) => formatPlan(args, result, options),
|
|
360
402
|
buildPiToolDisplay: (context) => buildActivityReplayDisplay("createPlan", TOOL_DISPLAY_SPECS.createPlan, context),
|
|
361
403
|
activityReplay: {
|
|
362
|
-
labelKey:
|
|
404
|
+
labelKey: CURSOR_REPLAY_ACTIVITY_LABEL_KEYS_BY_TOOL_NAME.createPlan,
|
|
363
405
|
buildActivityArgs: ({ args, result }) => {
|
|
364
406
|
const todos = getTodoItems(args, result);
|
|
365
407
|
return { totalCount: getTodoTotalCount(args, result, todos) };
|
|
@@ -372,7 +414,7 @@ const TOOL_DISPLAY_SPECS: Record<string, ToolDisplaySpec> = {
|
|
|
372
414
|
formatTranscript: ({ args, result, options }) => formatTask(args, result, options),
|
|
373
415
|
buildPiToolDisplay: (context) => buildActivityReplayDisplay("task", TOOL_DISPLAY_SPECS.task, context),
|
|
374
416
|
activityReplay: {
|
|
375
|
-
labelKey:
|
|
417
|
+
labelKey: CURSOR_REPLAY_ACTIVITY_LABEL_KEYS_BY_TOOL_NAME.task,
|
|
376
418
|
buildActivityArgs: ({ args, result }) => {
|
|
377
419
|
const description = getTaskDescription(args, result);
|
|
378
420
|
return { description: truncateArg(description) };
|
|
@@ -388,7 +430,7 @@ const TOOL_DISPLAY_SPECS: Record<string, ToolDisplaySpec> = {
|
|
|
388
430
|
formatTranscript: ({ args, result, options }) => formatGenerateImage(args, result, options),
|
|
389
431
|
buildPiToolDisplay: (context) => buildActivityReplayDisplay("generateImage", TOOL_DISPLAY_SPECS.generateImage, context),
|
|
390
432
|
activityReplay: {
|
|
391
|
-
labelKey:
|
|
433
|
+
labelKey: CURSOR_REPLAY_ACTIVITY_LABEL_KEYS_BY_TOOL_NAME.generateImage,
|
|
392
434
|
buildActivityArgs: ({ args }) => {
|
|
393
435
|
const prompt = getString(args, "prompt") ?? getString(args, "description") ?? "image";
|
|
394
436
|
return { prompt: truncateArg(prompt) };
|
|
@@ -415,14 +457,83 @@ const TOOL_DISPLAY_SPECS: Record<string, ToolDisplaySpec> = {
|
|
|
415
457
|
formatTranscript: ({ args, result, options }) => formatMcp(args, result, options),
|
|
416
458
|
buildPiToolDisplay: (context) => buildActivityReplayDisplay("mcp", TOOL_DISPLAY_SPECS.mcp, context),
|
|
417
459
|
activityReplay: {
|
|
418
|
-
labelKey:
|
|
460
|
+
labelKey: CURSOR_REPLAY_ACTIVITY_LABEL_KEYS_BY_TOOL_NAME.mcp,
|
|
419
461
|
buildActivityArgs: ({ args }) => {
|
|
420
462
|
const toolName = getString(args, "toolName") ?? "mcp";
|
|
421
463
|
return { toolName: truncateArg(toolName) };
|
|
422
464
|
},
|
|
423
|
-
buildActivitySummary: ({ args }) =>
|
|
465
|
+
buildActivitySummary: ({ args, result }) => summarizeMcp(args, result),
|
|
466
|
+
buildDetails: ({ args, result }) => ({
|
|
467
|
+
summary: result.status === "error" ? undefined : summarizeMcp(args, result),
|
|
468
|
+
}),
|
|
469
|
+
},
|
|
470
|
+
},
|
|
471
|
+
semSearch: {
|
|
472
|
+
formatTranscript: ({ args, result, options }) => formatSemSearch(args, result, options),
|
|
473
|
+
buildPiToolDisplay: (context) => buildActivityReplayDisplay("semSearch", TOOL_DISPLAY_SPECS.semSearch, context),
|
|
474
|
+
activityReplay: {
|
|
475
|
+
labelKey: CURSOR_REPLAY_ACTIVITY_LABEL_KEYS_BY_TOOL_NAME.semSearch,
|
|
476
|
+
buildActivityArgs: ({ args }) => {
|
|
477
|
+
const query = getString(args, "query") ?? "semantic search";
|
|
478
|
+
return { query: truncateArg(query) };
|
|
479
|
+
},
|
|
480
|
+
buildActivitySummary: ({ args }) => summarizeSemSearch(args),
|
|
481
|
+
buildDetails: ({ result }, contentText) => ({
|
|
482
|
+
summary: result.status === "error" ? undefined : firstNonEmptyLine(contentText) ?? "semantic search results captured",
|
|
483
|
+
}),
|
|
484
|
+
},
|
|
485
|
+
},
|
|
486
|
+
recordScreen: {
|
|
487
|
+
formatTranscript: ({ args, result, options }) => formatRecordScreen(args, result, options),
|
|
488
|
+
buildPiToolDisplay: (context) => buildActivityReplayDisplay("recordScreen", TOOL_DISPLAY_SPECS.recordScreen, context),
|
|
489
|
+
activityReplay: {
|
|
490
|
+
labelKey: CURSOR_REPLAY_ACTIVITY_LABEL_KEYS_BY_TOOL_NAME.recordScreen,
|
|
491
|
+
buildActivityArgs: ({ args, result, options }) => {
|
|
492
|
+
const mode = getString(args, "mode");
|
|
493
|
+
const path = getString(asRecord(result.value), "path");
|
|
494
|
+
return {
|
|
495
|
+
...(mode ? { mode } : {}),
|
|
496
|
+
...(path ? { path: formatDisplayPath(path, options.cwd) } : {}),
|
|
497
|
+
};
|
|
498
|
+
},
|
|
499
|
+
buildActivitySummary: ({ args, result, options }) => summarizeRecordScreen(args, result, options),
|
|
500
|
+
buildDetails: ({ args, result, options }, contentText) => ({
|
|
501
|
+
summary:
|
|
502
|
+
result.status === "error"
|
|
503
|
+
? undefined
|
|
504
|
+
: summarizeRecordScreen(args, result, options) ?? firstNonEmptyLine(contentText) ?? "screen recording updated",
|
|
505
|
+
}),
|
|
506
|
+
},
|
|
507
|
+
},
|
|
508
|
+
webSearch: {
|
|
509
|
+
formatTranscript: ({ args, result, options }) => formatWebSearch(args, result, options),
|
|
510
|
+
buildPiToolDisplay: (context) => buildActivityReplayDisplay("webSearch", TOOL_DISPLAY_SPECS.webSearch, context),
|
|
511
|
+
activityReplay: {
|
|
512
|
+
labelKey: CURSOR_REPLAY_ACTIVITY_LABEL_KEYS_BY_TOOL_NAME.webSearch,
|
|
513
|
+
buildActivityArgs: ({ args }) => {
|
|
514
|
+
const query = extractWebSearchQuery(args);
|
|
515
|
+
return query ? { query: truncateArg(query) } : {};
|
|
516
|
+
},
|
|
517
|
+
buildActivitySummary: ({ args }) => truncateArg(extractWebSearchQuery(args) ?? "web search"),
|
|
518
|
+
buildDetails: ({ result }, contentText) => ({
|
|
519
|
+
summary: result.status === "error" ? undefined : firstNonEmptyLine(contentText) ?? "web search result captured",
|
|
520
|
+
collapseDetailsByDefault: true,
|
|
521
|
+
}),
|
|
522
|
+
},
|
|
523
|
+
},
|
|
524
|
+
webFetch: {
|
|
525
|
+
formatTranscript: ({ args, result, options }) => formatWebFetch(args, result, options),
|
|
526
|
+
buildPiToolDisplay: (context) => buildActivityReplayDisplay("webFetch", TOOL_DISPLAY_SPECS.webFetch, context),
|
|
527
|
+
activityReplay: {
|
|
528
|
+
labelKey: CURSOR_REPLAY_ACTIVITY_LABEL_KEYS_BY_TOOL_NAME.webFetch,
|
|
529
|
+
buildActivityArgs: ({ args }) => {
|
|
530
|
+
const target = extractWebFetchTarget(args);
|
|
531
|
+
return target ? { url: truncateArg(target) } : {};
|
|
532
|
+
},
|
|
533
|
+
buildActivitySummary: ({ args }) => truncateArg(extractWebFetchTarget(args) ?? "web fetch"),
|
|
424
534
|
buildDetails: ({ result }, contentText) => ({
|
|
425
|
-
summary: result.status === "error" ? undefined : firstNonEmptyLine(contentText) ?? "
|
|
535
|
+
summary: result.status === "error" ? undefined : firstNonEmptyLine(contentText) ?? "web fetch result captured",
|
|
536
|
+
collapseDetailsByDefault: true,
|
|
426
537
|
}),
|
|
427
538
|
},
|
|
428
539
|
},
|
|
@@ -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) => {
|