pi-cursor-sdk 0.2.0 → 0.3.7
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 +73 -0
- package/README.md +55 -51
- package/dist/bundled-context-windows.js +39 -0
- package/dist/context-window-cache.js +92 -0
- package/dist/context.js +353 -0
- package/dist/cursor-active-tools.js +3 -0
- package/dist/cursor-agent-message-web-tools.js +165 -0
- package/dist/cursor-agents-context-registration.js +17 -0
- package/dist/cursor-agents-context.js +127 -0
- package/dist/cursor-api-key.js +34 -0
- package/dist/cursor-bridge-contract.js +27 -0
- package/dist/cursor-cloud-lifecycle.js +650 -0
- package/dist/cursor-cloud-local-state.js +460 -0
- package/dist/cursor-cloud-options.js +145 -0
- package/dist/cursor-cloud-reporting.js +222 -0
- package/dist/cursor-compact-tool-summary.js +75 -0
- package/dist/cursor-config.js +613 -0
- package/dist/cursor-context-tools.js +6 -0
- package/dist/cursor-display-only-trace.js +11 -0
- package/dist/cursor-display-text.js +18 -0
- package/dist/cursor-durable-fs.js +53 -0
- package/dist/cursor-edit-diff.js +13 -0
- package/dist/cursor-env-boolean.js +19 -0
- package/dist/cursor-fallback-models.generated.js +7760 -0
- package/dist/cursor-fallback-warning.js +15 -0
- package/dist/cursor-http1.js +45 -0
- package/dist/cursor-incomplete-tool-visibility.js +83 -0
- package/dist/cursor-live-run-accounting.js +55 -0
- package/dist/cursor-live-run-coordinator.js +409 -0
- package/dist/cursor-mcp-timeout-override.js +116 -0
- package/dist/cursor-model-lifecycle.js +32 -0
- package/dist/cursor-model.js +5 -0
- package/dist/cursor-native-replay-routing.js +35 -0
- package/dist/cursor-native-replay-trace.js +28 -0
- package/dist/cursor-native-tool-display-registration.js +102 -0
- package/dist/cursor-native-tool-display-replay.js +428 -0
- package/dist/cursor-native-tool-display-state.js +70 -0
- package/dist/cursor-native-tool-display-tools.js +123 -0
- package/dist/cursor-native-tool-names.js +11 -0
- package/dist/cursor-partial-content-emitter.js +130 -0
- package/dist/cursor-pi-context.js +30 -0
- package/dist/cursor-pi-tool-bridge-abort.js +114 -0
- package/dist/cursor-pi-tool-bridge-constants.js +6 -0
- package/dist/cursor-pi-tool-bridge-diagnostics.js +125 -0
- package/dist/cursor-pi-tool-bridge-env.js +18 -0
- package/dist/cursor-pi-tool-bridge-mcp.js +100 -0
- package/dist/cursor-pi-tool-bridge-run.js +355 -0
- package/dist/cursor-pi-tool-bridge-server.js +163 -0
- package/dist/cursor-pi-tool-bridge-snapshot.js +63 -0
- package/dist/cursor-pi-tool-bridge-types.js +1 -0
- package/dist/cursor-pi-tool-bridge.js +123 -0
- package/dist/cursor-provider-errors.js +311 -0
- package/dist/cursor-provider-lazy.js +39 -0
- package/dist/cursor-provider-live-run-drain.js +420 -0
- package/dist/cursor-provider-overflow.js +90 -0
- package/dist/cursor-provider-run-finalizer.js +176 -0
- package/dist/cursor-provider-run-outcome.js +76 -0
- package/dist/cursor-provider-turn-coordinator.js +293 -0
- package/dist/cursor-provider-turn-display-router.js +158 -0
- package/dist/cursor-provider-turn-emit.js +41 -0
- package/dist/cursor-provider-turn-finalize.js +154 -0
- package/dist/cursor-provider-turn-lifecycle-emitter.js +126 -0
- package/dist/cursor-provider-turn-prepare.js +379 -0
- package/dist/cursor-provider-turn-runner.js +146 -0
- package/dist/cursor-provider-turn-sdk-normalizer.js +74 -0
- package/dist/cursor-provider-turn-send.js +138 -0
- package/dist/cursor-provider-turn-shell-output.js +117 -0
- package/dist/cursor-provider-turn-tool-ledger.js +99 -0
- package/dist/cursor-provider-turn-types.js +1 -0
- package/dist/cursor-provider.js +72 -0
- package/dist/cursor-question-tool.js +194 -0
- package/dist/cursor-record-utils.js +61 -0
- package/dist/cursor-replay-activity-builders.js +114 -0
- package/dist/cursor-replay-source-names.js +27 -0
- package/dist/cursor-replay-summary-args.js +105 -0
- package/dist/cursor-replay-tool-details.js +167 -0
- package/dist/cursor-ripgrep-path.js +27 -0
- package/dist/cursor-run-final-text.js +63 -0
- package/dist/cursor-runtime-state.js +380 -0
- package/dist/cursor-sdk-billed-usage.js +86 -0
- package/dist/cursor-sdk-event-debug-constants.js +33 -0
- package/dist/cursor-sdk-event-debug-session.js +107 -0
- package/dist/cursor-sdk-event-debug.js +461 -0
- package/dist/cursor-sdk-output-filter.js +1 -0
- package/dist/cursor-sdk-process-error-guard.js +221 -0
- package/dist/cursor-sdk-runtime.js +3 -0
- package/dist/cursor-sensitive-text.js +23 -0
- package/dist/cursor-session-agent-cleanup.js +311 -0
- package/dist/cursor-session-agent-lifecycle.js +32 -0
- package/dist/cursor-session-agent-lineage.js +103 -0
- package/dist/cursor-session-agent-resume.js +417 -0
- package/dist/cursor-session-agent.js +507 -0
- package/dist/cursor-session-compaction-prep.js +21 -0
- package/dist/cursor-session-scope.js +113 -0
- package/dist/cursor-session-send-policy.js +18 -0
- package/dist/cursor-session-store.js +105 -0
- package/dist/cursor-session-turn-queue.js +50 -0
- package/dist/cursor-setting-sources.js +15 -0
- package/dist/cursor-skill-tool.js +230 -0
- package/dist/cursor-state.js +519 -0
- package/dist/cursor-task-presentation.js +59 -0
- package/dist/cursor-tool-lifecycle.js +94 -0
- package/dist/cursor-tool-manifest.js +34 -0
- package/dist/cursor-tool-presentation-registry.js +305 -0
- package/dist/cursor-tool-result-display-readers.js +158 -0
- package/dist/cursor-tool-transcript.js +32 -0
- package/dist/cursor-tool-visibility.js +28 -0
- package/dist/cursor-transcript-tool-formatters.js +618 -0
- package/dist/cursor-transcript-tool-specs.js +284 -0
- package/dist/cursor-transcript-utils.js +170 -0
- package/dist/cursor-usage-accounting.js +166 -0
- package/dist/cursor-web-tool-activity.js +23 -0
- package/dist/cursor-web-tool-args.js +12 -0
- package/dist/index.js +82 -0
- package/dist/model-discovery.js +305 -0
- package/dist/model-list-cache.js +156 -0
- package/docs/cursor-dogfood-checklist.md +2 -1
- package/docs/cursor-live-smoke-checklist.md +13 -12
- package/docs/cursor-model-ux-spec.md +27 -6
- package/docs/cursor-native-tool-replay.md +13 -13
- package/docs/cursor-native-tool-visual-audit.md +6 -3
- package/docs/cursor-testing-lessons.md +14 -8
- package/docs/cursor-tool-surfaces.md +5 -5
- package/docs/platform-smoke.md +3 -3
- package/package.json +19 -7
- package/platform-smoke.config.mjs +1 -1
- package/scripts/build.mjs +126 -0
- package/scripts/cloud-runtime-smoke.mjs +3 -1
- package/scripts/debug-provider-events.mjs +3 -1
- package/scripts/debug-sdk-events.mjs +1 -1
- package/scripts/isolated-cursor-smoke.sh +5 -4
- package/scripts/lib/cloud-smoke-cleanup-evidence.mjs +1 -1
- package/scripts/lib/cursor-visual-render.mjs +1 -1
- package/scripts/lib/ensure-built.mjs +16 -0
- package/scripts/lib/local-resume-smoke-harness.mjs +1 -1
- package/scripts/local-resume-smoke.mjs +3 -1
- package/scripts/platform-smoke/card-detect.mjs +1 -1
- package/scripts/platform-smoke/live-suite-runner.mjs +1 -1
- package/scripts/platform-smoke/render-ansi.mjs +1 -1
- package/scripts/platform-smoke/targets.mjs +2 -2
- package/scripts/prepare.mjs +102 -0
- package/scripts/steering-rpc-smoke.mjs +4 -2
- package/scripts/tmux-live-smoke.sh +11 -11
- package/scripts/visual-tui-smoke-self-test.mjs +4 -1
- package/scripts/visual-tui-smoke.mjs +51 -7
- package/src/bundled-context-windows.ts +4 -1
- package/src/context.ts +27 -4
- package/src/cursor-agents-context-registration.ts +2 -2
- package/src/cursor-agents-context.ts +24 -10
- package/src/cursor-api-key.ts +5 -4
- package/src/cursor-cloud-reporting.ts +23 -2
- package/src/cursor-context-tools.ts +3 -1
- package/src/cursor-fallback-models.generated.ts +592 -204
- package/src/cursor-live-run-coordinator.ts +11 -6
- package/src/cursor-native-tool-display-registration.ts +10 -11
- package/src/cursor-partial-content-emitter.ts +13 -0
- package/src/cursor-pi-context.ts +44 -0
- package/src/cursor-provider-errors.ts +1 -1
- package/src/cursor-provider-lazy.ts +5 -4
- package/src/cursor-provider-live-run-drain.ts +14 -9
- package/src/cursor-provider-run-finalizer.ts +1 -0
- package/src/cursor-provider-turn-coordinator.ts +15 -0
- package/src/cursor-provider-turn-emit.ts +6 -0
- package/src/cursor-provider-turn-finalize.ts +12 -0
- package/src/cursor-provider-turn-prepare.ts +10 -5
- package/src/cursor-provider-turn-types.ts +2 -0
- package/src/cursor-sdk-billed-usage.ts +107 -0
- package/src/cursor-sdk-process-error-guard.ts +6 -4
- package/src/cursor-session-agent-lifecycle.ts +8 -9
- package/src/cursor-session-agent-resume.ts +23 -0
- package/src/cursor-session-compaction-prep.ts +4 -0
- package/src/cursor-skill-tool.ts +1 -1
- package/src/cursor-tool-manifest.ts +1 -1
- package/src/cursor-usage-accounting.ts +70 -9
- package/src/index.ts +1 -1
- package/tsconfig.build.json +8 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,618 @@
|
|
|
1
|
+
import { resolveCursorEditDiff } from "./cursor-edit-diff.js";
|
|
2
|
+
import { extractWebFetchTarget, extractWebSearchQuery } from "./cursor-web-tool-args.js";
|
|
3
|
+
import { asRecord, getArray, getBoolean, getFirstStringByKeys, getNumber, getRecord, getString, } from "./cursor-record-utils.js";
|
|
4
|
+
import { getCursorTaskTranscriptHeader } from "./cursor-task-presentation.js";
|
|
5
|
+
import { collectTaskText, getGenerateImageDisplayPath, readMcpDisplayResult, getReadLintDiagnostics, getReadLintPaths, getTodoItems, } from "./cursor-tool-result-display-readers.js";
|
|
6
|
+
import { formatDisplayPath, formatDiffString, formatError, formatPathArg, joinSections, limitItems, limitText, LOCAL_READ_PREVIEW_NOTICE, DEFAULT_READ_TRANSCRIPT_CHARS, DEFAULT_READ_TRANSCRIPT_LINES, DEFAULT_NATIVE_READ_DISPLAY_LINES, readFilePreview, stringifyUnknown, truncateArg, } from "./cursor-transcript-utils.js";
|
|
7
|
+
export function usesLocalReadPreview(args, result, options) {
|
|
8
|
+
if (result.status === "error")
|
|
9
|
+
return false;
|
|
10
|
+
const value = asRecord(result.value);
|
|
11
|
+
const resultContent = getString(value, "content");
|
|
12
|
+
if (resultContent && resultContent.length > 0)
|
|
13
|
+
return false;
|
|
14
|
+
const rawPath = typeof args.path === "string" ? args.path : undefined;
|
|
15
|
+
if (!rawPath)
|
|
16
|
+
return false;
|
|
17
|
+
const readOptions = {
|
|
18
|
+
...options,
|
|
19
|
+
maxChars: options.maxChars ?? DEFAULT_READ_TRANSCRIPT_CHARS,
|
|
20
|
+
maxLines: options.maxLines ?? DEFAULT_READ_TRANSCRIPT_LINES,
|
|
21
|
+
};
|
|
22
|
+
return readFilePreview(rawPath, readOptions) !== undefined;
|
|
23
|
+
}
|
|
24
|
+
function getReadContent(args, result, options) {
|
|
25
|
+
const rawPath = typeof args.path === "string" ? args.path : undefined;
|
|
26
|
+
const readOptions = {
|
|
27
|
+
...options,
|
|
28
|
+
maxChars: options.maxChars ?? DEFAULT_READ_TRANSCRIPT_CHARS,
|
|
29
|
+
maxLines: options.maxLines ?? DEFAULT_READ_TRANSCRIPT_LINES,
|
|
30
|
+
};
|
|
31
|
+
const value = asRecord(result.value);
|
|
32
|
+
const resultContent = getString(value, "content");
|
|
33
|
+
if (resultContent && resultContent.length > 0)
|
|
34
|
+
return resultContent;
|
|
35
|
+
if (!rawPath)
|
|
36
|
+
return stringifyUnknown(result.value);
|
|
37
|
+
const localPreview = readFilePreview(rawPath, readOptions);
|
|
38
|
+
return localPreview ? `${LOCAL_READ_PREVIEW_NOTICE}\n${localPreview}` : stringifyUnknown(result.value);
|
|
39
|
+
}
|
|
40
|
+
export function formatRead(args, result, options) {
|
|
41
|
+
const rawPath = typeof args.path === "string" ? args.path : undefined;
|
|
42
|
+
const path = rawPath ? formatDisplayPath(rawPath, options.cwd) : "unknown";
|
|
43
|
+
if (result.status === "error")
|
|
44
|
+
return joinSections(`read ${path}`, formatError(result.error));
|
|
45
|
+
const value = asRecord(result.value);
|
|
46
|
+
const totalLines = getNumber(value, "totalLines");
|
|
47
|
+
const readOptions = {
|
|
48
|
+
...options,
|
|
49
|
+
maxChars: options.maxChars ?? DEFAULT_READ_TRANSCRIPT_CHARS,
|
|
50
|
+
maxLines: options.maxLines ?? DEFAULT_READ_TRANSCRIPT_LINES,
|
|
51
|
+
};
|
|
52
|
+
return joinSections(`read ${path}`, limitText(getReadContent(args, result, options), readOptions, totalLines));
|
|
53
|
+
}
|
|
54
|
+
export function buildReadDisplayArgs(args, options, result) {
|
|
55
|
+
const rawPath = typeof args.path === "string" ? args.path : undefined;
|
|
56
|
+
const displayArgs = rawPath ? { ...args, path: formatDisplayPath(rawPath, options.cwd) } : args;
|
|
57
|
+
if (result && usesLocalReadPreview(args, result, options)) {
|
|
58
|
+
return { ...displayArgs, localReadPreview: true };
|
|
59
|
+
}
|
|
60
|
+
return displayArgs;
|
|
61
|
+
}
|
|
62
|
+
function buildPathDisplayArgs(args, options) {
|
|
63
|
+
const rawPath = typeof args.path === "string" ? args.path : undefined;
|
|
64
|
+
return rawPath ? { ...args, path: formatDisplayPath(rawPath, options.cwd) } : args;
|
|
65
|
+
}
|
|
66
|
+
export function buildWriteDisplayArgs(args, options) {
|
|
67
|
+
const displayArgs = buildPathDisplayArgs(args, options);
|
|
68
|
+
const content = getCursorWriteArgContent(args);
|
|
69
|
+
return content === undefined ? displayArgs : { ...displayArgs, content };
|
|
70
|
+
}
|
|
71
|
+
const CURSOR_EDIT_PATH_KEYS = ["path", "filePath", "file_path"];
|
|
72
|
+
const CURSOR_EDIT_OLD_TEXT_KEYS = ["oldText", "old_text", "oldString", "old_string", "oldStr", "old_str"];
|
|
73
|
+
const CURSOR_EDIT_NEW_TEXT_KEYS = ["newText", "new_text", "newString", "new_string", "newStr", "new_str"];
|
|
74
|
+
const CURSOR_NOTEBOOK_EDIT_ARG_KEYS = ["cellId", "cell_id", "cellIndex", "cell_index", "cellType", "cell_type", "notebookPath", "notebook_path"];
|
|
75
|
+
function getCursorEditPathArg(args) {
|
|
76
|
+
const path = getFirstStringByKeys(args, CURSOR_EDIT_PATH_KEYS);
|
|
77
|
+
return path?.trim() ? path : undefined;
|
|
78
|
+
}
|
|
79
|
+
function isCursorNotebookEditToolName(toolName) {
|
|
80
|
+
const normalized = toolName.replace(/[\s_-]+/g, "").toLowerCase();
|
|
81
|
+
return normalized === "editnotebook" || normalized === "notebookedit";
|
|
82
|
+
}
|
|
83
|
+
function isCursorStrReplaceToolName(toolName) {
|
|
84
|
+
const normalized = toolName.replace(/[\s_-]+/g, "").toLowerCase();
|
|
85
|
+
return normalized === "strreplace";
|
|
86
|
+
}
|
|
87
|
+
function hasAnyKey(record, keys) {
|
|
88
|
+
return keys.some((key) => record[key] !== undefined);
|
|
89
|
+
}
|
|
90
|
+
function isNotebookPath(path) {
|
|
91
|
+
return path?.toLowerCase().endsWith(".ipynb") === true;
|
|
92
|
+
}
|
|
93
|
+
function isCursorNotebookEditActivity(rawToolName, args) {
|
|
94
|
+
if (isCursorNotebookEditToolName(rawToolName))
|
|
95
|
+
return true;
|
|
96
|
+
if (hasAnyKey(args, CURSOR_NOTEBOOK_EDIT_ARG_KEYS))
|
|
97
|
+
return true;
|
|
98
|
+
return !isCursorStrReplaceToolName(rawToolName) && isNotebookPath(getCursorEditPathArg(args));
|
|
99
|
+
}
|
|
100
|
+
function asNativeEditReplacement(value) {
|
|
101
|
+
const record = asRecord(value);
|
|
102
|
+
const oldText = record ? getFirstStringByKeys(record, CURSOR_EDIT_OLD_TEXT_KEYS) : undefined;
|
|
103
|
+
const newText = record ? getFirstStringByKeys(record, CURSOR_EDIT_NEW_TEXT_KEYS) : undefined;
|
|
104
|
+
if (typeof oldText !== "string" || oldText.length === 0 || typeof newText !== "string")
|
|
105
|
+
return undefined;
|
|
106
|
+
return { oldText, newText };
|
|
107
|
+
}
|
|
108
|
+
function getNativeEditReplacementsFromArgs(args) {
|
|
109
|
+
const edits = getArray(args, "edits")?.map(asNativeEditReplacement);
|
|
110
|
+
if (edits && edits.length > 0 && edits.every((edit) => edit !== undefined))
|
|
111
|
+
return edits;
|
|
112
|
+
const singleEdit = asNativeEditReplacement(args);
|
|
113
|
+
return singleEdit ? [singleEdit] : undefined;
|
|
114
|
+
}
|
|
115
|
+
export function buildNativeEditDisplayArgs(rawToolName, args, options) {
|
|
116
|
+
if (isCursorNotebookEditActivity(rawToolName, args))
|
|
117
|
+
return undefined;
|
|
118
|
+
const rawPath = getCursorEditPathArg(args);
|
|
119
|
+
const edits = getNativeEditReplacementsFromArgs(args);
|
|
120
|
+
if (!rawPath || !edits)
|
|
121
|
+
return undefined;
|
|
122
|
+
return { path: formatDisplayPath(rawPath, options.cwd), edits };
|
|
123
|
+
}
|
|
124
|
+
export function buildCursorEditActivityDisplayArgs(args, options) {
|
|
125
|
+
const rawPath = getCursorEditPathArg(args);
|
|
126
|
+
return rawPath ? { ...args, path: formatDisplayPath(rawPath, options.cwd) } : args;
|
|
127
|
+
}
|
|
128
|
+
export function formatNativeReadDisplayContent(args, result, options) {
|
|
129
|
+
const value = asRecord(result.value);
|
|
130
|
+
const totalLines = getNumber(value, "totalLines");
|
|
131
|
+
const readOptions = {
|
|
132
|
+
...options,
|
|
133
|
+
maxChars: options.maxChars ?? DEFAULT_READ_TRANSCRIPT_CHARS,
|
|
134
|
+
maxLines: options.maxLines ?? DEFAULT_NATIVE_READ_DISPLAY_LINES,
|
|
135
|
+
};
|
|
136
|
+
const content = getReadContent(args, result, readOptions);
|
|
137
|
+
if (totalLines === undefined)
|
|
138
|
+
return limitText(content, readOptions);
|
|
139
|
+
const maxLines = readOptions.maxLines ?? DEFAULT_NATIVE_READ_DISPLAY_LINES;
|
|
140
|
+
const lines = content.split("\n");
|
|
141
|
+
const visible = lines.slice(0, maxLines).join("\n");
|
|
142
|
+
if (totalLines <= maxLines && lines.length <= maxLines)
|
|
143
|
+
return visible;
|
|
144
|
+
if (visible.length > (readOptions.maxChars ?? DEFAULT_READ_TRANSCRIPT_CHARS))
|
|
145
|
+
return limitText(content, readOptions, totalLines);
|
|
146
|
+
return `${visible}\n\n[${Math.max(totalLines - maxLines, 0)} more lines in file. Use offset=${maxLines + 1} to continue.]`;
|
|
147
|
+
}
|
|
148
|
+
export function getShellOutput(result, args = {}) {
|
|
149
|
+
const value = asRecord(result.value);
|
|
150
|
+
const stdout = getString(value, "stdout") ?? "";
|
|
151
|
+
const stderr = getString(value, "stderr") ?? "";
|
|
152
|
+
const exitCode = getNumber(value, "exitCode");
|
|
153
|
+
const timeoutMs = getNumber(args, "timeout");
|
|
154
|
+
const executionTimeMs = getNumber(value, "executionTime");
|
|
155
|
+
const timedOut = timeoutMs !== undefined && executionTimeMs !== undefined && executionTimeMs >= timeoutMs;
|
|
156
|
+
const outputParts = [];
|
|
157
|
+
if (stdout)
|
|
158
|
+
outputParts.push(stdout.trimEnd());
|
|
159
|
+
if (stderr)
|
|
160
|
+
outputParts.push(stderr.trimEnd());
|
|
161
|
+
if (exitCode !== undefined && exitCode !== 0)
|
|
162
|
+
outputParts.push(`Command exited with code ${exitCode}`);
|
|
163
|
+
if (timedOut)
|
|
164
|
+
outputParts.push(`Command backgrounded after ${(timeoutMs / 1000).toFixed(0)} second timeout`);
|
|
165
|
+
return { text: outputParts.filter(Boolean).join("\n\n") || "(no output)", exitCode, timedOut };
|
|
166
|
+
}
|
|
167
|
+
export function buildShellDisplayArgs(args) {
|
|
168
|
+
const command = typeof args.command === "string" ? args.command : undefined;
|
|
169
|
+
const timeoutMs = getNumber(args, "timeout");
|
|
170
|
+
const displayArgs = command ? { command } : { ...args };
|
|
171
|
+
if (timeoutMs !== undefined) {
|
|
172
|
+
displayArgs.timeout = timeoutMs / 1000;
|
|
173
|
+
}
|
|
174
|
+
return displayArgs;
|
|
175
|
+
}
|
|
176
|
+
export function formatShell(args, result, options) {
|
|
177
|
+
const command = typeof args.command === "string" ? args.command : stringifyUnknown(args).trim();
|
|
178
|
+
if (result.status === "error")
|
|
179
|
+
return joinSections(`$ ${command || "shell"}`, formatError(result.error));
|
|
180
|
+
const value = asRecord(result.value);
|
|
181
|
+
const executionTime = getNumber(value, "executionTime");
|
|
182
|
+
const outputParts = [getShellOutput(result, args).text];
|
|
183
|
+
if (executionTime !== undefined)
|
|
184
|
+
outputParts.push(`Took ${(executionTime / 1000).toFixed(1)}s`);
|
|
185
|
+
return joinSections(`$ ${command || "shell"}`, limitText(outputParts.filter(Boolean).join("\n\n"), options));
|
|
186
|
+
}
|
|
187
|
+
function renderTreeNode(node, depth = 0, lines = []) {
|
|
188
|
+
const record = asRecord(node);
|
|
189
|
+
if (!record)
|
|
190
|
+
return lines;
|
|
191
|
+
const name = getString(record, "name") ?? getString(record, "path") ?? getString(record, "relativePath") ?? "";
|
|
192
|
+
const indent = " ".repeat(depth);
|
|
193
|
+
if (name)
|
|
194
|
+
lines.push(`${indent}${name}`);
|
|
195
|
+
const children = getArray(record, "children") ?? getArray(record, "entries") ?? getArray(record, "files") ?? [];
|
|
196
|
+
for (const child of children)
|
|
197
|
+
renderTreeNode(child, depth + 1, lines);
|
|
198
|
+
return lines;
|
|
199
|
+
}
|
|
200
|
+
export function getLsBody(result, options) {
|
|
201
|
+
const value = asRecord(result.value);
|
|
202
|
+
const root = value?.directoryTreeRoot ?? result.value;
|
|
203
|
+
const treeLines = renderTreeNode(root);
|
|
204
|
+
const body = treeLines.length > 0 ? treeLines.join("\n") : stringifyUnknown(result.value);
|
|
205
|
+
return limitText(body, options);
|
|
206
|
+
}
|
|
207
|
+
export function formatLs(args, result, options) {
|
|
208
|
+
const path = formatPathArg(args, options) ?? ".";
|
|
209
|
+
if (result.status === "error")
|
|
210
|
+
return joinSections(`ls ${path}`, formatError(result.error));
|
|
211
|
+
return joinSections(`ls ${path}`, getLsBody(result, options));
|
|
212
|
+
}
|
|
213
|
+
export function formatGlob(args, result, options) {
|
|
214
|
+
const header = `$ ${synthesizeGlobBashCommand(args, options)}`;
|
|
215
|
+
if (result.status === "error")
|
|
216
|
+
return joinSections(header, formatError(result.error));
|
|
217
|
+
return joinSections(header, getGlobBody(result, options));
|
|
218
|
+
}
|
|
219
|
+
function formatSearchCount(totalMatches) {
|
|
220
|
+
return totalMatches === 1 ? "1 match" : `${totalMatches} matches`;
|
|
221
|
+
}
|
|
222
|
+
function formatSearchFile(file) {
|
|
223
|
+
return file.endsWith(":") ? file.slice(0, -1) : file;
|
|
224
|
+
}
|
|
225
|
+
function collectSearchResults(value) {
|
|
226
|
+
const record = asRecord(value);
|
|
227
|
+
const outputs = [];
|
|
228
|
+
const activeEditorResult = record?.activeEditorResult;
|
|
229
|
+
if (activeEditorResult)
|
|
230
|
+
outputs.push(activeEditorResult);
|
|
231
|
+
const workspaceResults = asRecord(record?.workspaceResults);
|
|
232
|
+
if (workspaceResults)
|
|
233
|
+
outputs.push(...Object.values(workspaceResults));
|
|
234
|
+
if (outputs.length === 0)
|
|
235
|
+
outputs.push(value);
|
|
236
|
+
const lines = [];
|
|
237
|
+
let sawExplicitNoMatches = false;
|
|
238
|
+
for (const outputValue of outputs) {
|
|
239
|
+
const outputRecord = asRecord(outputValue);
|
|
240
|
+
const type = getString(outputRecord, "type");
|
|
241
|
+
const output = getRecord(outputRecord, "output");
|
|
242
|
+
if (type === "content") {
|
|
243
|
+
const matches = getArray(output, "matches") ?? [];
|
|
244
|
+
if (matches.length === 0 && getNumber(output, "totalMatches") === 0)
|
|
245
|
+
sawExplicitNoMatches = true;
|
|
246
|
+
for (const match of matches) {
|
|
247
|
+
const matchRecord = asRecord(match);
|
|
248
|
+
const file = formatSearchFile(getString(matchRecord, "file") ?? "");
|
|
249
|
+
const lineNumber = getNumber(matchRecord, "lineNumber");
|
|
250
|
+
const line = getString(matchRecord, "line") ?? "";
|
|
251
|
+
if (lineNumber === undefined && !line.trim()) {
|
|
252
|
+
if (file)
|
|
253
|
+
lines.push(file);
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
const location = `${file}${lineNumber !== undefined ? `:${lineNumber}` : ""}`;
|
|
257
|
+
lines.push(line ? `${location}: ${line}` : location);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
else if (type === "files") {
|
|
261
|
+
const files = getArray(output, "files") ?? [];
|
|
262
|
+
if (files.length === 0 && getNumber(output, "totalMatches") === 0)
|
|
263
|
+
sawExplicitNoMatches = true;
|
|
264
|
+
lines.push(...files.filter((entry) => typeof entry === "string").map(formatSearchFile));
|
|
265
|
+
}
|
|
266
|
+
else if (type === "count") {
|
|
267
|
+
const counts = getArray(output, "counts") ?? [];
|
|
268
|
+
if (counts.length === 0 && getNumber(output, "totalMatches") === 0)
|
|
269
|
+
sawExplicitNoMatches = true;
|
|
270
|
+
for (const count of counts) {
|
|
271
|
+
const countRecord = asRecord(count);
|
|
272
|
+
lines.push(`${getString(countRecord, "file") ?? ""}: ${getNumber(countRecord, "count") ?? 0}`.trim());
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
const totalMatches = getNumber(outputRecord, "totalMatches");
|
|
277
|
+
if (totalMatches !== undefined) {
|
|
278
|
+
if (totalMatches === 0) {
|
|
279
|
+
sawExplicitNoMatches = true;
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
lines.push(formatSearchCount(totalMatches));
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
lines.push(stringifyUnknown(outputValue));
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
const topLevelTotalMatches = getNumber(record, "totalMatches");
|
|
289
|
+
if (lines.length === 0 && topLevelTotalMatches !== undefined) {
|
|
290
|
+
return topLevelTotalMatches === 0 ? ["(no matches)"] : [formatSearchCount(topLevelTotalMatches)];
|
|
291
|
+
}
|
|
292
|
+
if (lines.length === 0 && sawExplicitNoMatches)
|
|
293
|
+
return ["(no matches)"];
|
|
294
|
+
return lines.filter(Boolean);
|
|
295
|
+
}
|
|
296
|
+
function synthesizeGrepBashCommand(args, options) {
|
|
297
|
+
const pattern = typeof args.pattern === "string" ? args.pattern : "";
|
|
298
|
+
const path = typeof args.path === "string" ? formatDisplayPath(args.path, options.cwd) : undefined;
|
|
299
|
+
const glob = typeof args.glob === "string" ? args.glob : undefined;
|
|
300
|
+
return ["grep", pattern && JSON.stringify(pattern), path ?? glob].filter(Boolean).join(" ");
|
|
301
|
+
}
|
|
302
|
+
export function buildGrepDisplayArgs(args, options) {
|
|
303
|
+
const displayArgs = {};
|
|
304
|
+
const pattern = typeof args.pattern === "string" ? args.pattern : undefined;
|
|
305
|
+
const path = typeof args.path === "string" ? formatDisplayPath(args.path, options.cwd) : undefined;
|
|
306
|
+
const glob = typeof args.glob === "string" ? args.glob : undefined;
|
|
307
|
+
const ignoreCase = getBoolean(args, "caseInsensitive");
|
|
308
|
+
const context = getNumber(args, "context") ?? getNumber(args, "contextBefore") ?? getNumber(args, "contextAfter");
|
|
309
|
+
const limit = getNumber(args, "headLimit");
|
|
310
|
+
if (pattern !== undefined)
|
|
311
|
+
displayArgs.pattern = pattern;
|
|
312
|
+
if (path !== undefined)
|
|
313
|
+
displayArgs.path = path;
|
|
314
|
+
if (glob !== undefined)
|
|
315
|
+
displayArgs.glob = glob;
|
|
316
|
+
if (ignoreCase !== undefined)
|
|
317
|
+
displayArgs.ignoreCase = ignoreCase;
|
|
318
|
+
if (context !== undefined)
|
|
319
|
+
displayArgs.context = context;
|
|
320
|
+
if (limit !== undefined)
|
|
321
|
+
displayArgs.limit = limit;
|
|
322
|
+
return Object.keys(displayArgs).length > 0 ? displayArgs : args;
|
|
323
|
+
}
|
|
324
|
+
function getGlobPattern(args) {
|
|
325
|
+
return typeof args.globPattern === "string" ? args.globPattern : typeof args.pattern === "string" ? args.pattern : "*";
|
|
326
|
+
}
|
|
327
|
+
function getGlobTargetDirectory(args, options) {
|
|
328
|
+
const rawPath = typeof args.targetDirectory === "string" ? args.targetDirectory : typeof args.path === "string" ? args.path : undefined;
|
|
329
|
+
return rawPath ? formatDisplayPath(rawPath, options.cwd) : undefined;
|
|
330
|
+
}
|
|
331
|
+
function synthesizeGlobBashCommand(args, options) {
|
|
332
|
+
const pattern = getGlobPattern(args);
|
|
333
|
+
const targetDirectory = getGlobTargetDirectory(args, options);
|
|
334
|
+
return targetDirectory ? `glob ${pattern} in ${targetDirectory}` : `glob ${pattern}`;
|
|
335
|
+
}
|
|
336
|
+
export function buildFindDisplayArgs(args, options) {
|
|
337
|
+
const displayArgs = { pattern: getGlobPattern(args) };
|
|
338
|
+
const targetDirectory = getGlobTargetDirectory(args, options);
|
|
339
|
+
const limit = getNumber(args, "limit") ?? getNumber(args, "headLimit");
|
|
340
|
+
if (targetDirectory !== undefined)
|
|
341
|
+
displayArgs.path = targetDirectory;
|
|
342
|
+
if (limit !== undefined)
|
|
343
|
+
displayArgs.limit = limit;
|
|
344
|
+
return displayArgs;
|
|
345
|
+
}
|
|
346
|
+
export function getGrepBody(result, options) {
|
|
347
|
+
const lines = collectSearchResults(result.value);
|
|
348
|
+
const limited = limitItems(lines, options);
|
|
349
|
+
const body = limited.omitted > 0 ? `${limited.items.join("\n")}\n... (${limited.omitted} more matches truncated)` : limited.items.join("\n");
|
|
350
|
+
return limitText(body || stringifyUnknown(result.value), options);
|
|
351
|
+
}
|
|
352
|
+
export function getGlobBody(result, options) {
|
|
353
|
+
const value = asRecord(result.value);
|
|
354
|
+
const files = getArray(value, "files")?.filter((entry) => typeof entry === "string") ?? [];
|
|
355
|
+
if (files.length === 0) {
|
|
356
|
+
const totalMatches = getNumber(value, "totalMatches");
|
|
357
|
+
const totalFiles = getNumber(value, "totalFiles");
|
|
358
|
+
if (totalMatches === 0 || totalFiles === 0)
|
|
359
|
+
return "No files found matching pattern";
|
|
360
|
+
return stringifyUnknown(result.value);
|
|
361
|
+
}
|
|
362
|
+
const limited = limitItems(files, options);
|
|
363
|
+
const body = limited.omitted > 0 ? `${limited.items.join("\n")}\n... (${limited.omitted} more files truncated)` : limited.items.join("\n");
|
|
364
|
+
return limitText(body, options);
|
|
365
|
+
}
|
|
366
|
+
export function formatGrep(args, result, options) {
|
|
367
|
+
const header = `$ ${synthesizeGrepBashCommand(args, options)}`;
|
|
368
|
+
if (result.status === "error")
|
|
369
|
+
return joinSections(header, formatError(result.error));
|
|
370
|
+
return joinSections(header, getGrepBody(result, options));
|
|
371
|
+
}
|
|
372
|
+
export function getCursorWriteArgContent(args) {
|
|
373
|
+
return getString(args, "content") ?? getString(args, "fileContent") ?? getString(args, "contents");
|
|
374
|
+
}
|
|
375
|
+
function getCursorWriteRecordedContent(args, resultValue) {
|
|
376
|
+
return getCursorWriteArgContent(args) ?? getString(resultValue, "fileContentAfterWrite");
|
|
377
|
+
}
|
|
378
|
+
export function formatWrite(args, result, options) {
|
|
379
|
+
const path = formatPathArg(args, options) ?? "unknown";
|
|
380
|
+
if (result.status === "error")
|
|
381
|
+
return joinSections(`write ${path}`, formatError(result.error));
|
|
382
|
+
const value = asRecord(result.value);
|
|
383
|
+
const linesCreated = getNumber(value, "linesCreated");
|
|
384
|
+
const fileSize = getNumber(value, "fileSize");
|
|
385
|
+
const fileContentAfterWrite = getCursorWriteRecordedContent(args, value);
|
|
386
|
+
const parts = [
|
|
387
|
+
linesCreated !== undefined ? `Created ${linesCreated} lines` : undefined,
|
|
388
|
+
fileSize !== undefined ? `File size: ${fileSize} bytes` : undefined,
|
|
389
|
+
fileContentAfterWrite ? limitText(fileContentAfterWrite, options) : undefined,
|
|
390
|
+
].filter((part) => Boolean(part));
|
|
391
|
+
return joinSections(`write ${path}`, parts.join("\n\n") || stringifyUnknown(result.value));
|
|
392
|
+
}
|
|
393
|
+
export function formatEdit(args, result, options) {
|
|
394
|
+
const path = formatPathArg(args, options) ?? "unknown";
|
|
395
|
+
if (result.status === "error")
|
|
396
|
+
return joinSections(`edit ${path}`, formatError(result.error));
|
|
397
|
+
const value = asRecord(result.value);
|
|
398
|
+
const diff = formatDiffString(resolveCursorEditDiff(value), options);
|
|
399
|
+
const linesAdded = getNumber(value, "linesAdded");
|
|
400
|
+
const linesRemoved = getNumber(value, "linesRemoved");
|
|
401
|
+
const stats = [
|
|
402
|
+
linesAdded !== undefined ? `+${linesAdded}` : undefined,
|
|
403
|
+
linesRemoved !== undefined ? `-${linesRemoved}` : undefined,
|
|
404
|
+
].filter(Boolean).join(" ");
|
|
405
|
+
const body = [stats, diff ? limitText(diff, options) : undefined].filter((part) => Boolean(part)).join("\n\n");
|
|
406
|
+
return joinSections(`edit ${path}`, body || stringifyUnknown(result.value));
|
|
407
|
+
}
|
|
408
|
+
export function formatDelete(args, result, options) {
|
|
409
|
+
const path = formatPathArg(args, options) ?? "unknown";
|
|
410
|
+
if (result.status === "error")
|
|
411
|
+
return joinSections(`delete ${path}`, formatError(result.error));
|
|
412
|
+
const value = asRecord(result.value);
|
|
413
|
+
const fileSize = getNumber(value, "fileSize");
|
|
414
|
+
return joinSections(`delete ${path}`, fileSize !== undefined ? `Deleted ${fileSize} bytes` : stringifyUnknown(result.value));
|
|
415
|
+
}
|
|
416
|
+
export function formatReadLints(args, result, options) {
|
|
417
|
+
const paths = getReadLintPaths(args, result, options);
|
|
418
|
+
const header = `readLints${paths.length > 0 ? ` ${paths.join(" ")}` : ""}`;
|
|
419
|
+
if (result.status === "error")
|
|
420
|
+
return joinSections(header, formatError(result.error));
|
|
421
|
+
const lines = getReadLintDiagnostics(result, options);
|
|
422
|
+
if (lines.length === 0 && paths.length > 0)
|
|
423
|
+
return joinSections(header, `No diagnostics in ${paths.join(", ")}`);
|
|
424
|
+
return joinSections(header, limitText(lines.join("\n") || stringifyUnknown(result.value), options));
|
|
425
|
+
}
|
|
426
|
+
function formatTodoStatus(status) {
|
|
427
|
+
if (status === "completed")
|
|
428
|
+
return "✓";
|
|
429
|
+
if (status === "inProgress")
|
|
430
|
+
return "…";
|
|
431
|
+
if (status === "pending")
|
|
432
|
+
return "○";
|
|
433
|
+
return "•";
|
|
434
|
+
}
|
|
435
|
+
export function formatTodos(args, result, options, header) {
|
|
436
|
+
if (result.status === "error")
|
|
437
|
+
return joinSections(header, formatError(result.error));
|
|
438
|
+
const todos = getTodoItems(args, result);
|
|
439
|
+
if (todos.length === 0)
|
|
440
|
+
return joinSections(header, limitText(stringifyUnknown(result.value), options));
|
|
441
|
+
const lines = todos.map((todo) => `${formatTodoStatus(todo.status)} ${todo.content}${todo.status ? ` (${todo.status})` : ""}`);
|
|
442
|
+
return joinSections(header, limitText(lines.join("\n"), options));
|
|
443
|
+
}
|
|
444
|
+
export function formatPlan(args, result, options) {
|
|
445
|
+
if (result.status === "error")
|
|
446
|
+
return joinSections("createPlan", formatError(result.error));
|
|
447
|
+
const planText = getString(args, "plan") ?? getString(asRecord(result.value), "plan");
|
|
448
|
+
if (planText?.trim())
|
|
449
|
+
return joinSections("createPlan", limitText(planText, options));
|
|
450
|
+
return formatTodos(args, result, options, "createPlan");
|
|
451
|
+
}
|
|
452
|
+
export function formatTask(args, result, options) {
|
|
453
|
+
const header = getCursorTaskTranscriptHeader(args, result.value);
|
|
454
|
+
if (result.status === "error")
|
|
455
|
+
return joinSections(header, formatError(result.error));
|
|
456
|
+
const taskText = collectTaskText(result, options);
|
|
457
|
+
return joinSections(header, limitText(taskText || stringifyUnknown(result.value), options));
|
|
458
|
+
}
|
|
459
|
+
export function formatGenerateImage(args, result, options) {
|
|
460
|
+
const prompt = getString(args, "prompt") ?? getString(args, "description") ?? "image";
|
|
461
|
+
if (result.status === "error")
|
|
462
|
+
return joinSections(`generateImage ${prompt}`, formatError(result.error));
|
|
463
|
+
const value = asRecord(result.value);
|
|
464
|
+
const displayPath = getGenerateImageDisplayPath(args, result, options);
|
|
465
|
+
const hasImageData = typeof value?.imageData === "string" && value.imageData.length > 0;
|
|
466
|
+
const lines = [displayPath ? `Saved image: ${displayPath}` : undefined, hasImageData ? "Image data returned by Cursor SDK." : undefined].filter((line) => Boolean(line));
|
|
467
|
+
if (lines.length > 0)
|
|
468
|
+
return joinSections(`generateImage ${prompt}`, lines.join("\n"));
|
|
469
|
+
return joinSections(`generateImage ${prompt}`, limitText(stringifyUnknown(result.value), options));
|
|
470
|
+
}
|
|
471
|
+
export function formatSemSearch(args, result, options) {
|
|
472
|
+
const query = getString(args, "query") ?? "semantic search";
|
|
473
|
+
const header = `semSearch ${truncateArg(query)}`;
|
|
474
|
+
if (result.status === "error")
|
|
475
|
+
return joinSections(header, formatError(result.error));
|
|
476
|
+
const value = asRecord(result.value);
|
|
477
|
+
const results = getString(value, "results");
|
|
478
|
+
const targetDirectories = getArray(args, "targetDirectories");
|
|
479
|
+
const explanation = getString(args, "explanation");
|
|
480
|
+
const lines = [];
|
|
481
|
+
if (explanation?.trim())
|
|
482
|
+
lines.push(`Explanation: ${explanation.trim()}`);
|
|
483
|
+
if (targetDirectories && targetDirectories.length > 0) {
|
|
484
|
+
const dirs = targetDirectories
|
|
485
|
+
.map((entry) => (typeof entry === "string" ? entry : stringifyUnknown(entry)))
|
|
486
|
+
.join(", ");
|
|
487
|
+
lines.push(`Scope: ${dirs}`);
|
|
488
|
+
}
|
|
489
|
+
if (results?.trim())
|
|
490
|
+
lines.push(results.trim());
|
|
491
|
+
const body = lines.length > 0 ? lines.join("\n\n") : stringifyUnknown(result.value);
|
|
492
|
+
return joinSections(header, limitText(body, options));
|
|
493
|
+
}
|
|
494
|
+
function formatRecordScreenMode(mode) {
|
|
495
|
+
switch (mode) {
|
|
496
|
+
case "START_RECORDING":
|
|
497
|
+
return "start recording";
|
|
498
|
+
case "SAVE_RECORDING":
|
|
499
|
+
return "save recording";
|
|
500
|
+
case "DISCARD_RECORDING":
|
|
501
|
+
return "discard recording";
|
|
502
|
+
default:
|
|
503
|
+
return mode ?? "record screen";
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
function formatRecordingDurationMs(ms) {
|
|
507
|
+
if (ms === undefined || !Number.isFinite(ms) || ms < 0)
|
|
508
|
+
return undefined;
|
|
509
|
+
if (ms < 1000)
|
|
510
|
+
return `${Math.round(ms)}ms`;
|
|
511
|
+
const seconds = ms / 1000;
|
|
512
|
+
return seconds < 60 ? `${seconds.toFixed(1)}s` : `${Math.floor(seconds / 60)}m ${Math.round(seconds % 60)}s`;
|
|
513
|
+
}
|
|
514
|
+
export function formatRecordScreen(args, result, options) {
|
|
515
|
+
const mode = getString(args, "mode");
|
|
516
|
+
const header = `recordScreen ${formatRecordScreenMode(mode)}`;
|
|
517
|
+
if (result.status === "error")
|
|
518
|
+
return joinSections(header, formatError(result.error));
|
|
519
|
+
const value = asRecord(result.value);
|
|
520
|
+
const path = getString(value, "path");
|
|
521
|
+
const displayPath = path ? formatDisplayPath(path, options.cwd) : undefined;
|
|
522
|
+
const duration = formatRecordingDurationMs(getNumber(value, "recordingDurationMs"));
|
|
523
|
+
const wasCancelled = getBoolean(value, "wasPriorRecordingCancelled");
|
|
524
|
+
const lines = [];
|
|
525
|
+
if (displayPath)
|
|
526
|
+
lines.push(`Recording: ${displayPath}`);
|
|
527
|
+
if (duration)
|
|
528
|
+
lines.push(`Duration: ${duration}`);
|
|
529
|
+
if (wasCancelled === true)
|
|
530
|
+
lines.push("Prior recording cancelled.");
|
|
531
|
+
if (lines.length === 0) {
|
|
532
|
+
if (mode === "START_RECORDING")
|
|
533
|
+
lines.push("Recording started.");
|
|
534
|
+
else if (mode === "DISCARD_RECORDING")
|
|
535
|
+
lines.push("Recording discarded.");
|
|
536
|
+
else
|
|
537
|
+
lines.push("Screen recording updated.");
|
|
538
|
+
}
|
|
539
|
+
return joinSections(header, lines.join("\n"));
|
|
540
|
+
}
|
|
541
|
+
function formatWebToolBody(toolLabel, result, options) {
|
|
542
|
+
if (result.status === "error")
|
|
543
|
+
return joinSections(toolLabel, formatError(result.error));
|
|
544
|
+
return joinSections(toolLabel, limitText(readMcpDisplayResult(result).body, options));
|
|
545
|
+
}
|
|
546
|
+
export function formatWebSearch(args, result, options) {
|
|
547
|
+
const query = extractWebSearchQuery(args);
|
|
548
|
+
const header = query ? `web search ${query}` : "web search";
|
|
549
|
+
return formatWebToolBody(header, result, options);
|
|
550
|
+
}
|
|
551
|
+
export function formatWebFetch(args, result, options) {
|
|
552
|
+
const target = extractWebFetchTarget(args);
|
|
553
|
+
const header = target ? `web fetch ${target}` : "web fetch";
|
|
554
|
+
return formatWebToolBody(header, result, options);
|
|
555
|
+
}
|
|
556
|
+
export function formatMcp(args, result, options) {
|
|
557
|
+
const toolName = typeof args.toolName === "string" ? args.toolName : "mcp";
|
|
558
|
+
if (result.status === "error")
|
|
559
|
+
return joinSections(toolName, formatError(result.error));
|
|
560
|
+
return joinSections(toolName, limitText(readMcpDisplayResult(result).body, options));
|
|
561
|
+
}
|
|
562
|
+
const UNKNOWN_TOOL_FALLBACK_MAX_ARGS = 8;
|
|
563
|
+
const UNKNOWN_TOOL_FALLBACK_MAX_CHARS = 240;
|
|
564
|
+
const UNKNOWN_TOOL_FALLBACK_MAX_LINES = 6;
|
|
565
|
+
function summarizeUnknownToolArgValue(value) {
|
|
566
|
+
if (typeof value === "string")
|
|
567
|
+
return truncateArg(value);
|
|
568
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
569
|
+
return String(value);
|
|
570
|
+
if (Array.isArray(value)) {
|
|
571
|
+
const preview = value.slice(0, 3).map((entry) => summarizeUnknownToolArgValue(entry)).join(", ");
|
|
572
|
+
const omitted = value.length - Math.min(value.length, 3);
|
|
573
|
+
return omitted > 0 ? `[${preview}, +${omitted} more]` : `[${preview}]`;
|
|
574
|
+
}
|
|
575
|
+
return truncateArg(stringifyUnknown(value).replace(/\s+/g, " "));
|
|
576
|
+
}
|
|
577
|
+
function summarizeUnknownToolArgs(args) {
|
|
578
|
+
const entries = Object.entries(args).slice(0, UNKNOWN_TOOL_FALLBACK_MAX_ARGS);
|
|
579
|
+
if (entries.length === 0)
|
|
580
|
+
return "";
|
|
581
|
+
const parts = entries.map(([key, value]) => `${key}=${summarizeUnknownToolArgValue(value)}`);
|
|
582
|
+
const omitted = Object.keys(args).length - entries.length;
|
|
583
|
+
const body = parts.join(", ");
|
|
584
|
+
return omitted > 0 ? `${body} (+${omitted} more)` : body;
|
|
585
|
+
}
|
|
586
|
+
function summarizeUnknownToolResult(value, options) {
|
|
587
|
+
const text = stringifyUnknown(value).trim();
|
|
588
|
+
if (!text)
|
|
589
|
+
return "";
|
|
590
|
+
return limitText(text.replace(/\s+/g, " "), {
|
|
591
|
+
...options,
|
|
592
|
+
maxChars: options.maxChars ?? UNKNOWN_TOOL_FALLBACK_MAX_CHARS,
|
|
593
|
+
maxLines: options.maxLines ?? UNKNOWN_TOOL_FALLBACK_MAX_LINES,
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
function summarizeUnknownToolError(error, options) {
|
|
597
|
+
const text = formatError(error).trim();
|
|
598
|
+
if (!text)
|
|
599
|
+
return "Error";
|
|
600
|
+
return limitText(text, {
|
|
601
|
+
...options,
|
|
602
|
+
maxChars: options.maxChars ?? UNKNOWN_TOOL_FALLBACK_MAX_CHARS,
|
|
603
|
+
maxLines: options.maxLines ?? UNKNOWN_TOOL_FALLBACK_MAX_LINES,
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
export function formatFallback(name, args, result, options) {
|
|
607
|
+
const header = name === "unknown" ? "Cursor tool" : name;
|
|
608
|
+
if (result.status === "error") {
|
|
609
|
+
const argsSummary = summarizeUnknownToolArgs(args);
|
|
610
|
+
const errorSummary = summarizeUnknownToolError(result.error, options);
|
|
611
|
+
const body = [argsSummary, errorSummary].filter(Boolean).join("\n\n");
|
|
612
|
+
return joinSections(header, body ? limitText(body, options) : undefined);
|
|
613
|
+
}
|
|
614
|
+
const argsSummary = summarizeUnknownToolArgs(args);
|
|
615
|
+
const resultSummary = summarizeUnknownToolResult(result.value, options);
|
|
616
|
+
const body = [argsSummary, resultSummary].filter(Boolean).join("\n\n");
|
|
617
|
+
return joinSections(header, body ? limitText(body, options) : undefined);
|
|
618
|
+
}
|