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
package/src/cursor-tool-names.ts
CHANGED
|
@@ -10,6 +10,10 @@ export const CURSOR_REPLAY_LEGACY_TOOL_NAMES = [
|
|
|
10
10
|
"cursor_create_plan",
|
|
11
11
|
"cursor_generate_image",
|
|
12
12
|
"cursor_mcp",
|
|
13
|
+
"cursor_sem_search",
|
|
14
|
+
"cursor_record_screen",
|
|
15
|
+
"cursor_web_search",
|
|
16
|
+
"cursor_web_fetch",
|
|
13
17
|
] as const;
|
|
14
18
|
|
|
15
19
|
export type CursorReplayLegacyToolName = (typeof CURSOR_REPLAY_LEGACY_TOOL_NAMES)[number];
|
|
@@ -25,6 +29,10 @@ const CURSOR_REPLAY_SOURCE_TOOL_NAMES = {
|
|
|
25
29
|
cursor_create_plan: "createPlan",
|
|
26
30
|
cursor_generate_image: "generateImage",
|
|
27
31
|
cursor_mcp: "MCP",
|
|
32
|
+
cursor_sem_search: "semSearch",
|
|
33
|
+
cursor_record_screen: "recordScreen",
|
|
34
|
+
cursor_web_search: "web search",
|
|
35
|
+
cursor_web_fetch: "web fetch",
|
|
28
36
|
} as const satisfies Record<CursorReplayLegacyToolName, string>;
|
|
29
37
|
|
|
30
38
|
const CURSOR_REPLAY_PROMPT_LABELS = {
|
|
@@ -37,8 +45,30 @@ const CURSOR_REPLAY_PROMPT_LABELS = {
|
|
|
37
45
|
cursor_create_plan: "Cursor plan",
|
|
38
46
|
cursor_generate_image: "Cursor image generation",
|
|
39
47
|
cursor_mcp: "Cursor MCP",
|
|
48
|
+
cursor_sem_search: "Cursor semantic search",
|
|
49
|
+
cursor_record_screen: "Cursor screen recording",
|
|
50
|
+
cursor_web_search: "Cursor web search",
|
|
51
|
+
cursor_web_fetch: "Cursor web fetch",
|
|
40
52
|
} as const satisfies Record<CursorReplayLegacyToolName, string>;
|
|
41
53
|
|
|
54
|
+
export const CURSOR_REPLAY_ACTIVITY_LABEL_KEYS_BY_TOOL_NAME = {
|
|
55
|
+
edit: "cursor_edit",
|
|
56
|
+
write: "cursor_write",
|
|
57
|
+
readLints: "cursor_read_lints",
|
|
58
|
+
delete: "cursor_delete",
|
|
59
|
+
updateTodos: "cursor_update_todos",
|
|
60
|
+
task: "cursor_task",
|
|
61
|
+
createPlan: "cursor_create_plan",
|
|
62
|
+
generateImage: "cursor_generate_image",
|
|
63
|
+
mcp: "cursor_mcp",
|
|
64
|
+
semSearch: "cursor_sem_search",
|
|
65
|
+
recordScreen: "cursor_record_screen",
|
|
66
|
+
webSearch: "cursor_web_search",
|
|
67
|
+
webFetch: "cursor_web_fetch",
|
|
68
|
+
} as const satisfies Record<string, CursorReplayLegacyToolName>;
|
|
69
|
+
|
|
70
|
+
export type CursorReplayActivityToolName = keyof typeof CURSOR_REPLAY_ACTIVITY_LABEL_KEYS_BY_TOOL_NAME;
|
|
71
|
+
|
|
42
72
|
export function isCursorReplayLegacyToolName(toolName: string): toolName is CursorReplayLegacyToolName {
|
|
43
73
|
return CURSOR_REPLAY_LEGACY_TOOL_NAMES.some((legacyToolName) => legacyToolName === toolName);
|
|
44
74
|
}
|
|
@@ -65,3 +95,12 @@ export function getCursorReplayDisplayLabel(toolName: CursorReplayToolName): str
|
|
|
65
95
|
if (toolName === CURSOR_REPLAY_ACTIVITY_TOOL_NAME) return "Cursor activity";
|
|
66
96
|
return CURSOR_REPLAY_PROMPT_LABELS[toolName];
|
|
67
97
|
}
|
|
98
|
+
|
|
99
|
+
export function getCursorReplayActivityLabelKey(toolName: string): CursorReplayLegacyToolName | undefined {
|
|
100
|
+
return CURSOR_REPLAY_ACTIVITY_LABEL_KEYS_BY_TOOL_NAME[toolName as CursorReplayActivityToolName];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function getCursorReplayActivityTitle(toolName: string): string | undefined {
|
|
104
|
+
const labelKey = getCursorReplayActivityLabelKey(toolName);
|
|
105
|
+
return labelKey ? getCursorReplayDisplayLabel(labelKey) : undefined;
|
|
106
|
+
}
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
formatCursorToolTranscriptFromSpec,
|
|
15
15
|
type ToolDisplayContext,
|
|
16
16
|
} from "./cursor-transcript-tool-specs.js";
|
|
17
|
+
import { resolveTranscriptToolName } from "./cursor-web-tool-activity.js";
|
|
17
18
|
|
|
18
19
|
export type { CursorPiToolDisplay } from "./cursor-transcript-utils.js";
|
|
19
20
|
|
|
@@ -29,10 +30,11 @@ export function getCursorCreatePlanText(toolCall: unknown): string | undefined {
|
|
|
29
30
|
|
|
30
31
|
function buildToolDisplayContext(toolCall: unknown, options: TranscriptOptions): ToolDisplayContext {
|
|
31
32
|
const rawName = getToolName(toolCall);
|
|
33
|
+
const args = getToolArgs(toolCall);
|
|
32
34
|
return {
|
|
33
35
|
rawName,
|
|
34
|
-
name:
|
|
35
|
-
args
|
|
36
|
+
name: resolveTranscriptToolName(rawName, args),
|
|
37
|
+
args,
|
|
36
38
|
result: normalizeResult(getToolResult(toolCall)),
|
|
37
39
|
options,
|
|
38
40
|
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { getCursorReplayActivityTitle } from "./cursor-tool-names.js";
|
|
2
|
+
import { getToolArgs, getToolName, normalizeToolName } from "./cursor-transcript-utils.js";
|
|
3
|
+
import { resolveTranscriptToolName } from "./cursor-web-tool-activity.js";
|
|
4
|
+
|
|
5
|
+
interface CursorToolVisibilityConfig {
|
|
6
|
+
incompleteTitle?: string;
|
|
7
|
+
lifecycleTitle?: string;
|
|
8
|
+
lifecycleEligible?: boolean;
|
|
9
|
+
fastLocalDiscovery?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface CursorToolVisibility {
|
|
13
|
+
args: Record<string, unknown>;
|
|
14
|
+
displayName: string;
|
|
15
|
+
normalizedName: string;
|
|
16
|
+
normalizedKey: string;
|
|
17
|
+
activityTitle?: string;
|
|
18
|
+
incompleteTitle?: string;
|
|
19
|
+
lifecycleTitle?: string;
|
|
20
|
+
lifecycleEligible: boolean;
|
|
21
|
+
fastLocalDiscovery: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const CURSOR_TOOL_VISIBILITY_BY_NAME: Record<string, CursorToolVisibilityConfig> = {
|
|
25
|
+
read: { incompleteTitle: "Cursor read", fastLocalDiscovery: true },
|
|
26
|
+
grep: { incompleteTitle: "Cursor grep", fastLocalDiscovery: true },
|
|
27
|
+
glob: { incompleteTitle: "Cursor find", fastLocalDiscovery: true },
|
|
28
|
+
ls: { incompleteTitle: "Cursor ls", fastLocalDiscovery: true },
|
|
29
|
+
shell: { incompleteTitle: "Cursor shell", lifecycleTitle: "Cursor shell", lifecycleEligible: true },
|
|
30
|
+
task: { lifecycleEligible: true },
|
|
31
|
+
mcp: { lifecycleEligible: true },
|
|
32
|
+
generateimage: { lifecycleEligible: true },
|
|
33
|
+
recordscreen: { lifecycleEligible: true },
|
|
34
|
+
semsearch: { lifecycleEligible: true },
|
|
35
|
+
websearch: { lifecycleEligible: true },
|
|
36
|
+
webfetch: { lifecycleEligible: true },
|
|
37
|
+
createplan: { lifecycleEligible: true },
|
|
38
|
+
updatetodos: { lifecycleEligible: true },
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export function classifyCursorToolVisibility(toolCall: unknown): CursorToolVisibility {
|
|
42
|
+
const args = getToolArgs(toolCall);
|
|
43
|
+
const displayName = resolveTranscriptToolName(getToolName(toolCall), args);
|
|
44
|
+
const normalizedName = normalizeToolName(displayName);
|
|
45
|
+
const normalizedKey = normalizedName.toLowerCase();
|
|
46
|
+
const config = CURSOR_TOOL_VISIBILITY_BY_NAME[normalizedKey];
|
|
47
|
+
const replayActivityTitle = getCursorReplayActivityTitle(normalizedName);
|
|
48
|
+
return {
|
|
49
|
+
args,
|
|
50
|
+
displayName,
|
|
51
|
+
normalizedName,
|
|
52
|
+
normalizedKey,
|
|
53
|
+
activityTitle: replayActivityTitle ?? config?.incompleteTitle ?? config?.lifecycleTitle,
|
|
54
|
+
incompleteTitle: replayActivityTitle ?? config?.incompleteTitle,
|
|
55
|
+
lifecycleTitle: replayActivityTitle ?? config?.lifecycleTitle,
|
|
56
|
+
lifecycleEligible: config?.lifecycleEligible ?? false,
|
|
57
|
+
fastLocalDiscovery: config?.fastLocalDiscovery ?? false,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function isFastLocalDiscoveryTool(toolCall: unknown): boolean {
|
|
62
|
+
return classifyCursorToolVisibility(toolCall).fastLocalDiscovery;
|
|
63
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { resolveCursorEditDiff } from "./cursor-edit-diff.js";
|
|
2
|
+
import { scrubSensitiveText } from "./cursor-sensitive-text.js";
|
|
3
|
+
import { extractWebFetchTarget, extractWebSearchQuery } from "./cursor-web-tool-activity.js";
|
|
2
4
|
import { getFirstStringByKeys } from "./cursor-record-utils.js";
|
|
3
5
|
import {
|
|
4
6
|
asRecord,
|
|
@@ -27,6 +29,21 @@ import {
|
|
|
27
29
|
type TranscriptOptions,
|
|
28
30
|
} from "./cursor-transcript-utils.js";
|
|
29
31
|
|
|
32
|
+
export function usesLocalReadPreview(args: Record<string, unknown>, result: NormalizedResult, options: TranscriptOptions): boolean {
|
|
33
|
+
if (result.status === "error") return false;
|
|
34
|
+
const value = asRecord(result.value);
|
|
35
|
+
const resultContent = getString(value, "content");
|
|
36
|
+
if (resultContent && resultContent.length > 0) return false;
|
|
37
|
+
const rawPath = typeof args.path === "string" ? args.path : undefined;
|
|
38
|
+
if (!rawPath) return false;
|
|
39
|
+
const readOptions = {
|
|
40
|
+
...options,
|
|
41
|
+
maxChars: options.maxChars ?? DEFAULT_READ_TRANSCRIPT_CHARS,
|
|
42
|
+
maxLines: options.maxLines ?? DEFAULT_READ_TRANSCRIPT_LINES,
|
|
43
|
+
};
|
|
44
|
+
return readFilePreview(rawPath, readOptions) !== undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
30
47
|
function getReadContent(args: Record<string, unknown>, result: NormalizedResult, options: TranscriptOptions): string {
|
|
31
48
|
const rawPath = typeof args.path === "string" ? args.path : undefined;
|
|
32
49
|
const readOptions = {
|
|
@@ -57,9 +74,17 @@ export function formatRead(args: Record<string, unknown>, result: NormalizedResu
|
|
|
57
74
|
return joinSections(`read ${path}`, limitText(getReadContent(args, result, options), readOptions, totalLines));
|
|
58
75
|
}
|
|
59
76
|
|
|
60
|
-
export function buildReadDisplayArgs(
|
|
77
|
+
export function buildReadDisplayArgs(
|
|
78
|
+
args: Record<string, unknown>,
|
|
79
|
+
options: TranscriptOptions,
|
|
80
|
+
result?: NormalizedResult,
|
|
81
|
+
): Record<string, unknown> {
|
|
61
82
|
const rawPath = typeof args.path === "string" ? args.path : undefined;
|
|
62
|
-
|
|
83
|
+
const displayArgs = rawPath ? { ...args, path: formatDisplayPath(rawPath, options.cwd) } : args;
|
|
84
|
+
if (result && usesLocalReadPreview(args, result, options)) {
|
|
85
|
+
return { ...displayArgs, localReadPreview: true };
|
|
86
|
+
}
|
|
87
|
+
return displayArgs;
|
|
63
88
|
}
|
|
64
89
|
|
|
65
90
|
function buildPathDisplayArgs(args: Record<string, unknown>, options: TranscriptOptions): Record<string, unknown> {
|
|
@@ -617,6 +642,153 @@ function describeNonTextMcpContent(entry: unknown): string {
|
|
|
617
642
|
return `[${type} omitted]`;
|
|
618
643
|
}
|
|
619
644
|
|
|
645
|
+
export function summarizeSemSearch(args: Record<string, unknown>): string {
|
|
646
|
+
const query = getString(args, "query") ?? "semantic search";
|
|
647
|
+
const targetDirectories = getArray(args, "targetDirectories");
|
|
648
|
+
const dirHint =
|
|
649
|
+
targetDirectories && targetDirectories.length > 0
|
|
650
|
+
? ` (${targetDirectories.length} dir${targetDirectories.length === 1 ? "" : "s"})`
|
|
651
|
+
: "";
|
|
652
|
+
return truncateArg(`${query}${dirHint}`);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
export function formatSemSearch(args: Record<string, unknown>, result: NormalizedResult, options: TranscriptOptions): string {
|
|
656
|
+
const query = getString(args, "query") ?? "semantic search";
|
|
657
|
+
const header = `semSearch ${truncateArg(query)}`;
|
|
658
|
+
if (result.status === "error") return joinSections(header, formatError(result.error));
|
|
659
|
+
|
|
660
|
+
const value = asRecord(result.value);
|
|
661
|
+
const results = getString(value, "results");
|
|
662
|
+
const targetDirectories = getArray(args, "targetDirectories");
|
|
663
|
+
const explanation = getString(args, "explanation");
|
|
664
|
+
const lines: string[] = [];
|
|
665
|
+
if (explanation?.trim()) lines.push(`Explanation: ${explanation.trim()}`);
|
|
666
|
+
if (targetDirectories && targetDirectories.length > 0) {
|
|
667
|
+
const dirs = targetDirectories
|
|
668
|
+
.map((entry) => (typeof entry === "string" ? entry : stringifyUnknown(entry)))
|
|
669
|
+
.join(", ");
|
|
670
|
+
lines.push(`Scope: ${dirs}`);
|
|
671
|
+
}
|
|
672
|
+
if (results?.trim()) lines.push(results.trim());
|
|
673
|
+
const body = lines.length > 0 ? lines.join("\n\n") : stringifyUnknown(result.value);
|
|
674
|
+
return joinSections(header, limitText(body, options));
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function formatRecordScreenMode(mode: string | undefined): string {
|
|
678
|
+
switch (mode) {
|
|
679
|
+
case "START_RECORDING":
|
|
680
|
+
return "start recording";
|
|
681
|
+
case "SAVE_RECORDING":
|
|
682
|
+
return "save recording";
|
|
683
|
+
case "DISCARD_RECORDING":
|
|
684
|
+
return "discard recording";
|
|
685
|
+
default:
|
|
686
|
+
return mode ?? "record screen";
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function formatRecordingDurationMs(ms: number | undefined): string | undefined {
|
|
691
|
+
if (ms === undefined || !Number.isFinite(ms) || ms < 0) return undefined;
|
|
692
|
+
if (ms < 1000) return `${Math.round(ms)}ms`;
|
|
693
|
+
const seconds = ms / 1000;
|
|
694
|
+
return seconds < 60 ? `${seconds.toFixed(1)}s` : `${Math.floor(seconds / 60)}m ${Math.round(seconds % 60)}s`;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
export function summarizeRecordScreen(
|
|
698
|
+
args: Record<string, unknown>,
|
|
699
|
+
result: NormalizedResult,
|
|
700
|
+
options: TranscriptOptions,
|
|
701
|
+
): string {
|
|
702
|
+
const mode = getString(args, "mode");
|
|
703
|
+
if (result.status === "error") return formatRecordScreenMode(mode);
|
|
704
|
+
const value = asRecord(result.value);
|
|
705
|
+
const path = getString(value, "path");
|
|
706
|
+
const displayPath = path ? formatDisplayPath(path, options.cwd) : undefined;
|
|
707
|
+
const duration = formatRecordingDurationMs(getNumber(value, "recordingDurationMs"));
|
|
708
|
+
const modeLabel = formatRecordScreenMode(mode);
|
|
709
|
+
if (displayPath && duration) return `${displayPath} · ${duration}`;
|
|
710
|
+
if (displayPath) return displayPath;
|
|
711
|
+
if (duration) return `${modeLabel} · ${duration}`;
|
|
712
|
+
return modeLabel;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
export function formatRecordScreen(args: Record<string, unknown>, result: NormalizedResult, options: TranscriptOptions): string {
|
|
716
|
+
const mode = getString(args, "mode");
|
|
717
|
+
const header = `recordScreen ${formatRecordScreenMode(mode)}`;
|
|
718
|
+
if (result.status === "error") return joinSections(header, formatError(result.error));
|
|
719
|
+
|
|
720
|
+
const value = asRecord(result.value);
|
|
721
|
+
const path = getString(value, "path");
|
|
722
|
+
const displayPath = path ? formatDisplayPath(path, options.cwd) : undefined;
|
|
723
|
+
const duration = formatRecordingDurationMs(getNumber(value, "recordingDurationMs"));
|
|
724
|
+
const wasCancelled = getBoolean(value, "wasPriorRecordingCancelled");
|
|
725
|
+
const lines: string[] = [];
|
|
726
|
+
if (displayPath) lines.push(`Recording: ${displayPath}`);
|
|
727
|
+
if (duration) lines.push(`Duration: ${duration}`);
|
|
728
|
+
if (wasCancelled === true) lines.push("Prior recording cancelled.");
|
|
729
|
+
if (lines.length === 0) {
|
|
730
|
+
if (mode === "START_RECORDING") lines.push("Recording started.");
|
|
731
|
+
else if (mode === "DISCARD_RECORDING") lines.push("Recording discarded.");
|
|
732
|
+
else lines.push("Screen recording updated.");
|
|
733
|
+
}
|
|
734
|
+
return joinSections(header, lines.join("\n"));
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
export function getMcpResultPreview(result: NormalizedResult): string | undefined {
|
|
738
|
+
if (result.status === "error") return undefined;
|
|
739
|
+
const value = asRecord(result.value);
|
|
740
|
+
const content = getArray(value, "content") ?? [];
|
|
741
|
+
for (const entry of content) {
|
|
742
|
+
const text = getMcpContentText(entry);
|
|
743
|
+
if (text) {
|
|
744
|
+
const line = firstNonEmptyLine(text);
|
|
745
|
+
if (line) return truncateArg(scrubSensitiveText(line), 120);
|
|
746
|
+
}
|
|
747
|
+
const summary = describeNonTextMcpContent(entry);
|
|
748
|
+
if (summary) return summary;
|
|
749
|
+
}
|
|
750
|
+
return undefined;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
export function summarizeMcp(args: Record<string, unknown>, result: NormalizedResult): string {
|
|
754
|
+
const toolName = truncateArg(getString(args, "toolName") ?? "mcp");
|
|
755
|
+
const preview = getMcpResultPreview(result);
|
|
756
|
+
return preview && preview !== toolName ? `${toolName} · ${preview}` : toolName;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
function formatWebToolBody(
|
|
760
|
+
toolLabel: string,
|
|
761
|
+
args: Record<string, unknown>,
|
|
762
|
+
result: NormalizedResult,
|
|
763
|
+
options: TranscriptOptions,
|
|
764
|
+
summaryArg: string | undefined,
|
|
765
|
+
): string {
|
|
766
|
+
if (result.status === "error") return joinSections(toolLabel, formatError(result.error));
|
|
767
|
+
const summary = summaryArg ? `${summaryArg}\n\n` : "";
|
|
768
|
+
const value = asRecord(result.value);
|
|
769
|
+
const isError = getBoolean(value, "isError");
|
|
770
|
+
const content = getArray(value, "content") ?? [];
|
|
771
|
+
const text = content
|
|
772
|
+
.map((entry) => getMcpContentText(entry))
|
|
773
|
+
.filter((entry): entry is string => Boolean(entry))
|
|
774
|
+
.join("\n");
|
|
775
|
+
const contentSummary = content.length > 0 ? content.map(describeNonTextMcpContent).join("\n") : stringifyUnknown(result.value);
|
|
776
|
+
const body = `${isError ? "[tool error]\n" : ""}${text || contentSummary}`;
|
|
777
|
+
return joinSections(toolLabel, limitText(`${summary}${body}`.trim(), options));
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
export function formatWebSearch(args: Record<string, unknown>, result: NormalizedResult, options: TranscriptOptions): string {
|
|
781
|
+
const query = extractWebSearchQuery(args);
|
|
782
|
+
const header = query ? `web search ${query}` : "web search";
|
|
783
|
+
return formatWebToolBody(header, args, result, options, undefined);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
export function formatWebFetch(args: Record<string, unknown>, result: NormalizedResult, options: TranscriptOptions): string {
|
|
787
|
+
const target = extractWebFetchTarget(args);
|
|
788
|
+
const header = target ? `web fetch ${target}` : "web fetch";
|
|
789
|
+
return formatWebToolBody(header, args, result, options, undefined);
|
|
790
|
+
}
|
|
791
|
+
|
|
620
792
|
export function formatMcp(args: Record<string, unknown>, result: NormalizedResult, options: TranscriptOptions): string {
|
|
621
793
|
const toolName = typeof args.toolName === "string" ? args.toolName : "mcp";
|
|
622
794
|
if (result.status === "error") return joinSections(toolName, formatError(result.error));
|
|
@@ -633,9 +805,60 @@ export function formatMcp(args: Record<string, unknown>, result: NormalizedResul
|
|
|
633
805
|
return joinSections(toolName, limitText(body, options));
|
|
634
806
|
}
|
|
635
807
|
|
|
808
|
+
const UNKNOWN_TOOL_FALLBACK_MAX_ARGS = 8;
|
|
809
|
+
const UNKNOWN_TOOL_FALLBACK_MAX_CHARS = 240;
|
|
810
|
+
const UNKNOWN_TOOL_FALLBACK_MAX_LINES = 6;
|
|
811
|
+
|
|
812
|
+
function summarizeUnknownToolArgValue(value: unknown): string {
|
|
813
|
+
if (typeof value === "string") return truncateArg(value);
|
|
814
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
815
|
+
if (Array.isArray(value)) {
|
|
816
|
+
const preview = value.slice(0, 3).map((entry) => summarizeUnknownToolArgValue(entry)).join(", ");
|
|
817
|
+
const omitted = value.length - Math.min(value.length, 3);
|
|
818
|
+
return omitted > 0 ? `[${preview}, +${omitted} more]` : `[${preview}]`;
|
|
819
|
+
}
|
|
820
|
+
return truncateArg(stringifyUnknown(value).replace(/\s+/g, " "));
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
function summarizeUnknownToolArgs(args: Record<string, unknown>): string {
|
|
824
|
+
const entries = Object.entries(args).slice(0, UNKNOWN_TOOL_FALLBACK_MAX_ARGS);
|
|
825
|
+
if (entries.length === 0) return "";
|
|
826
|
+
const parts = entries.map(([key, value]) => `${key}=${summarizeUnknownToolArgValue(value)}`);
|
|
827
|
+
const omitted = Object.keys(args).length - entries.length;
|
|
828
|
+
const body = parts.join(", ");
|
|
829
|
+
return omitted > 0 ? `${body} (+${omitted} more)` : body;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
function summarizeUnknownToolResult(value: unknown, options: TranscriptOptions): string {
|
|
833
|
+
const text = stringifyUnknown(value).trim();
|
|
834
|
+
if (!text) return "";
|
|
835
|
+
return limitText(text.replace(/\s+/g, " "), {
|
|
836
|
+
...options,
|
|
837
|
+
maxChars: options.maxChars ?? UNKNOWN_TOOL_FALLBACK_MAX_CHARS,
|
|
838
|
+
maxLines: options.maxLines ?? UNKNOWN_TOOL_FALLBACK_MAX_LINES,
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
function summarizeUnknownToolError(error: unknown, options: TranscriptOptions): string {
|
|
843
|
+
const text = formatError(error).trim();
|
|
844
|
+
if (!text) return "Error";
|
|
845
|
+
return limitText(text, {
|
|
846
|
+
...options,
|
|
847
|
+
maxChars: options.maxChars ?? UNKNOWN_TOOL_FALLBACK_MAX_CHARS,
|
|
848
|
+
maxLines: options.maxLines ?? UNKNOWN_TOOL_FALLBACK_MAX_LINES,
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
|
|
636
852
|
export function formatFallback(name: string, args: Record<string, unknown>, result: NormalizedResult, options: TranscriptOptions): string {
|
|
637
853
|
const header = name === "unknown" ? "Cursor tool" : name;
|
|
638
|
-
if (result.status === "error")
|
|
639
|
-
|
|
640
|
-
|
|
854
|
+
if (result.status === "error") {
|
|
855
|
+
const argsSummary = summarizeUnknownToolArgs(args);
|
|
856
|
+
const errorSummary = summarizeUnknownToolError(result.error, options);
|
|
857
|
+
const body = [argsSummary, errorSummary].filter(Boolean).join("\n\n");
|
|
858
|
+
return joinSections(header, body ? limitText(body, options) : undefined);
|
|
859
|
+
}
|
|
860
|
+
const argsSummary = summarizeUnknownToolArgs(args);
|
|
861
|
+
const resultSummary = summarizeUnknownToolResult(result.value, options);
|
|
862
|
+
const body = [argsSummary, resultSummary].filter(Boolean).join("\n\n");
|
|
863
|
+
return joinSections(header, body ? limitText(body, options) : undefined);
|
|
641
864
|
}
|