pi-cursor-sdk 0.1.37 → 0.1.39
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 +34 -0
- package/README.md +2 -2
- package/docs/cursor-model-ux-spec.md +1 -1
- package/docs/cursor-native-tool-replay.md +5 -5
- package/package.json +1 -1
- package/scripts/platform-smoke/card-detect.mjs +1 -1
- package/src/context-window-cache.ts +10 -14
- package/src/context.ts +1 -1
- package/src/cursor-agent-message-web-tools.ts +2 -1
- package/src/cursor-agents-context-registration.ts +18 -0
- package/src/cursor-agents-context.ts +21 -30
- package/src/cursor-edit-diff.ts +4 -2
- package/src/cursor-fallback-warning.ts +22 -0
- package/src/cursor-incomplete-tool-visibility.ts +5 -10
- package/src/cursor-live-run-coordinator.ts +1 -1
- package/src/cursor-mcp-timeout-override.ts +0 -2
- package/src/cursor-model-lifecycle.ts +72 -0
- package/src/cursor-native-replay-routing.ts +1 -1
- package/src/cursor-native-replay-trace.ts +1 -1
- package/src/cursor-native-tool-display-registration.ts +16 -28
- package/src/cursor-native-tool-display-replay.ts +4 -21
- package/src/cursor-native-tool-display-state.ts +1 -1
- package/src/cursor-native-tool-display-tools.ts +10 -17
- package/src/cursor-native-tool-names.ts +16 -0
- package/src/cursor-pi-tool-bridge-env.ts +12 -0
- package/src/cursor-pi-tool-bridge-mcp.ts +16 -21
- package/src/cursor-pi-tool-bridge-run.ts +5 -5
- package/src/cursor-pi-tool-bridge-server.ts +8 -3
- package/src/cursor-pi-tool-bridge-snapshot.ts +7 -13
- package/src/cursor-pi-tool-bridge.ts +7 -7
- package/src/cursor-provider-errors.ts +11 -4
- package/src/cursor-provider-lazy.ts +51 -0
- package/src/cursor-provider-live-run-drain.ts +1 -1
- package/src/cursor-provider-run-finalizer.ts +5 -5
- package/src/cursor-provider-run-outcome.ts +0 -1
- package/src/cursor-provider-turn-coordinator.ts +16 -6
- package/src/cursor-provider-turn-display-router.ts +5 -1
- package/src/cursor-provider-turn-emit.ts +1 -1
- package/src/cursor-provider-turn-lifecycle-emitter.ts +1 -5
- package/src/cursor-provider-turn-prepare.ts +13 -9
- package/src/cursor-provider-turn-runner.ts +3 -11
- package/src/cursor-provider-turn-sdk-normalizer.ts +28 -5
- package/src/cursor-provider-turn-send.ts +7 -2
- package/src/cursor-provider-turn-shell-output.ts +38 -3
- package/src/cursor-provider-turn-types.ts +1 -3
- package/src/cursor-provider.ts +3 -2
- package/src/cursor-question-tool.ts +5 -18
- package/src/cursor-record-utils.ts +42 -0
- package/src/cursor-replay-activity-builders.ts +16 -122
- package/src/cursor-replay-tool-details.ts +52 -80
- package/src/cursor-sdk-event-debug.ts +6 -6
- package/src/cursor-sensitive-text.ts +4 -4
- package/src/cursor-session-agent-lifecycle.ts +47 -0
- package/src/cursor-session-agent.ts +9 -47
- package/src/cursor-session-scope.ts +23 -4
- package/src/cursor-setting-sources.ts +8 -8
- package/src/cursor-skill-tool.ts +25 -32
- package/src/cursor-state.ts +66 -45
- package/src/cursor-tool-lifecycle.ts +22 -10
- package/src/cursor-tool-presentation-registry.ts +27 -18
- package/src/cursor-tool-result-display-readers.ts +185 -0
- package/src/cursor-tool-transcript.ts +17 -33
- package/src/cursor-tool-visibility.ts +9 -1
- package/src/cursor-transcript-tool-formatters.ts +23 -172
- package/src/cursor-transcript-tool-specs.ts +16 -41
- package/src/cursor-transcript-utils.ts +2 -34
- package/src/cursor-usage-accounting.ts +0 -6
- package/src/cursor-web-tool-activity.ts +4 -12
- package/src/cursor-web-tool-args.ts +1 -9
- package/src/index.ts +15 -16
- package/src/model-discovery.ts +5 -4
- package/src/model-list-cache.ts +37 -38
- package/src/cursor-native-tool-display.ts +0 -10
- package/src/cursor-provider-turn-api-key.ts +0 -1
- package/src/cursor-provider-turn-message-offset.ts +0 -15
- package/src/cursor-session-cwd.ts +0 -28
- package/src/cursor-tool-names.ts +0 -9
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { asRecord, getArray, getNumber, getRecord, getString, stringifyUnknown } from "./cursor-record-utils.js";
|
|
2
|
+
import { scrubSensitiveText } from "./cursor-sensitive-text.js";
|
|
3
|
+
import { firstNonEmptyLine, formatDisplayPath, truncateArg } from "./cursor-transcript-utils.js";
|
|
4
|
+
|
|
5
|
+
export interface CursorToolResultLike {
|
|
6
|
+
status: string | undefined;
|
|
7
|
+
value: unknown;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface CursorToolResultReaderOptions {
|
|
11
|
+
cwd?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface CursorTodoItem {
|
|
15
|
+
content: string;
|
|
16
|
+
status?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function getReadLintPaths(
|
|
20
|
+
args: Record<string, unknown>,
|
|
21
|
+
result: CursorToolResultLike,
|
|
22
|
+
options: CursorToolResultReaderOptions,
|
|
23
|
+
): string[] {
|
|
24
|
+
const explicitPaths = Array.isArray(args.paths)
|
|
25
|
+
? args.paths.filter((entry): entry is string => typeof entry === "string")
|
|
26
|
+
: typeof args.path === "string"
|
|
27
|
+
? [args.path]
|
|
28
|
+
: [];
|
|
29
|
+
const resultPaths = (getArray(asRecord(result.value), "fileDiagnostics") ?? [])
|
|
30
|
+
.map((file) => getString(asRecord(file), "path"))
|
|
31
|
+
.filter((entry): entry is string => Boolean(entry));
|
|
32
|
+
return [...new Set([...explicitPaths, ...resultPaths].map((entry) => formatDisplayPath(entry, options.cwd)))];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function getReadLintDiagnostics(result: CursorToolResultLike, options: CursorToolResultReaderOptions): string[] {
|
|
36
|
+
const value = asRecord(result.value);
|
|
37
|
+
const files = getArray(value, "fileDiagnostics") ?? [];
|
|
38
|
+
const lines: string[] = [];
|
|
39
|
+
for (const file of files) {
|
|
40
|
+
const fileRecord = asRecord(file);
|
|
41
|
+
const pathValue = getString(fileRecord, "path");
|
|
42
|
+
const path = pathValue ? formatDisplayPath(pathValue, options.cwd) : "unknown";
|
|
43
|
+
const diagnostics = getArray(fileRecord, "diagnostics") ?? [];
|
|
44
|
+
for (const diagnostic of diagnostics) {
|
|
45
|
+
const diagnosticRecord = asRecord(diagnostic);
|
|
46
|
+
const severity = getString(diagnosticRecord, "severity") ?? "diagnostic";
|
|
47
|
+
const message = getString(diagnosticRecord, "message") ?? "";
|
|
48
|
+
const source = getString(diagnosticRecord, "source");
|
|
49
|
+
lines.push(`${path}: ${severity}${source ? ` ${source}` : ""}: ${message}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return lines;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function getTodoItems(args: Record<string, unknown>, result: CursorToolResultLike): CursorTodoItem[] {
|
|
56
|
+
const value = asRecord(result.value);
|
|
57
|
+
const rawTodos = getArray(value, "todos") ?? getArray(args, "todos") ?? [];
|
|
58
|
+
const todos: CursorTodoItem[] = [];
|
|
59
|
+
for (const todo of rawTodos) {
|
|
60
|
+
const record = asRecord(todo);
|
|
61
|
+
const content = getString(record, "content");
|
|
62
|
+
if (!content) continue;
|
|
63
|
+
const status = getString(record, "status");
|
|
64
|
+
todos.push(status ? { content, status } : { content });
|
|
65
|
+
}
|
|
66
|
+
return todos;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function getTodoTotalCount(args: Record<string, unknown>, result: CursorToolResultLike, todos: CursorTodoItem[]): number {
|
|
70
|
+
return getNumber(asRecord(result.value), "totalCount") ?? getNumber(args, "totalCount") ?? todos.length;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function getTaskDescription(args: Record<string, unknown>, result: CursorToolResultLike): string {
|
|
74
|
+
return getString(args, "description") ?? getString(asRecord(result.value), "description") ?? "task";
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function getNestedRecord(record: Record<string, unknown> | undefined, ...keys: string[]): Record<string, unknown> | undefined {
|
|
78
|
+
let current = record;
|
|
79
|
+
for (const key of keys) {
|
|
80
|
+
current = getRecord(current, key);
|
|
81
|
+
if (!current) return undefined;
|
|
82
|
+
}
|
|
83
|
+
return current;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function collectTaskText(result: CursorToolResultLike): string {
|
|
87
|
+
const value = asRecord(result.value);
|
|
88
|
+
const success = getNestedRecord(value, "result", "success");
|
|
89
|
+
const command = getString(success, "command");
|
|
90
|
+
const stdout = getString(success, "stdout");
|
|
91
|
+
const interleavedOutput = getString(success, "interleavedOutput");
|
|
92
|
+
const assistantMessages = (getArray(value, "conversationSteps") ?? [])
|
|
93
|
+
.map((step) => getString(getRecord(asRecord(step), "assistantMessage"), "text"))
|
|
94
|
+
.filter((entry): entry is string => Boolean(entry));
|
|
95
|
+
const parts = [command ? `$ ${command}` : undefined, stdout || interleavedOutput, ...assistantMessages].filter((part): part is string => Boolean(part));
|
|
96
|
+
return parts.join("\n");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function getGenerateImagePath(args: Record<string, unknown>, result: CursorToolResultLike): string | undefined {
|
|
100
|
+
const value = asRecord(result.value);
|
|
101
|
+
return getString(value, "filePath") ?? getString(args, "filePath") ?? getString(args, "path");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function getGenerateImageDisplayPath(
|
|
105
|
+
args: Record<string, unknown>,
|
|
106
|
+
result: CursorToolResultLike,
|
|
107
|
+
options: CursorToolResultReaderOptions,
|
|
108
|
+
): string | undefined {
|
|
109
|
+
const path = getGenerateImagePath(args, result);
|
|
110
|
+
return path ? formatDisplayPath(path, options.cwd) : undefined;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function inferImageMimeType(path: string | undefined): string | undefined {
|
|
114
|
+
const lower = path?.toLowerCase();
|
|
115
|
+
if (!lower) return undefined;
|
|
116
|
+
if (lower.endsWith(".png")) return "image/png";
|
|
117
|
+
if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
|
|
118
|
+
if (lower.endsWith(".gif")) return "image/gif";
|
|
119
|
+
if (lower.endsWith(".webp")) return "image/webp";
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function getMcpContentText(entry: unknown): string | undefined {
|
|
124
|
+
const record = asRecord(entry);
|
|
125
|
+
const directText = getString(record, "text");
|
|
126
|
+
if (directText) return directText;
|
|
127
|
+
const nestedText = getRecord(record, "text");
|
|
128
|
+
return getString(nestedText, "text");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function describeNonTextMcpContent(entry: unknown): string {
|
|
132
|
+
const record = asRecord(entry);
|
|
133
|
+
const type = getString(record, "type") ?? "content";
|
|
134
|
+
if (type === "image") {
|
|
135
|
+
const mimeType = getString(record, "mimeType") ?? getString(record, "mime") ?? getString(record, "mediaType");
|
|
136
|
+
return `[image${mimeType ? ` ${mimeType}` : ""} omitted]`;
|
|
137
|
+
}
|
|
138
|
+
if (type === "audio") return "[audio omitted]";
|
|
139
|
+
if (type === "resource") return "[resource omitted]";
|
|
140
|
+
return `[${type} omitted]`;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export interface McpDisplayResult {
|
|
144
|
+
isToolError: boolean;
|
|
145
|
+
text: string;
|
|
146
|
+
nonTextSummary: string;
|
|
147
|
+
body: string;
|
|
148
|
+
preview?: string;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function readMcpDisplayResult(result: CursorToolResultLike): McpDisplayResult {
|
|
152
|
+
if (result.status === "error") {
|
|
153
|
+
return { isToolError: false, text: "", nonTextSummary: "", body: "" };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const value = asRecord(result.value);
|
|
157
|
+
const isToolError = value?.isError === true;
|
|
158
|
+
const content = getArray(value, "content") ?? [];
|
|
159
|
+
const textParts: string[] = [];
|
|
160
|
+
const nonTextParts: string[] = [];
|
|
161
|
+
let preview: string | undefined;
|
|
162
|
+
|
|
163
|
+
for (const entry of content) {
|
|
164
|
+
const text = getMcpContentText(entry);
|
|
165
|
+
if (text) {
|
|
166
|
+
textParts.push(text);
|
|
167
|
+
const line = firstNonEmptyLine(text);
|
|
168
|
+
if (!preview && line) preview = truncateArg(scrubSensitiveText(line), 120);
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
nonTextParts.push(describeNonTextMcpContent(entry));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (!preview) preview = nonTextParts[0];
|
|
175
|
+
const text = textParts.join("\n");
|
|
176
|
+
const nonTextSummary = nonTextParts.join("\n");
|
|
177
|
+
const body = text || nonTextSummary || scrubSensitiveText(stringifyUnknown(result.value), undefined);
|
|
178
|
+
return {
|
|
179
|
+
isToolError,
|
|
180
|
+
text,
|
|
181
|
+
nonTextSummary,
|
|
182
|
+
body: `${isToolError ? "[tool error]\n" : ""}${body}`.trim(),
|
|
183
|
+
...(preview ? { preview } : {}),
|
|
184
|
+
};
|
|
185
|
+
}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
import { asRecord, getString } from "./cursor-record-utils.js";
|
|
2
|
+
import { normalizeCursorToolName } from "./cursor-tool-presentation-registry.js";
|
|
3
|
+
import {
|
|
4
|
+
buildCursorPiToolDisplayFromSpec,
|
|
5
|
+
formatCursorToolTranscriptFromSpec,
|
|
6
|
+
type ToolDisplayContext,
|
|
7
|
+
} from "./cursor-transcript-tool-specs.js";
|
|
1
8
|
import {
|
|
2
|
-
asRecord,
|
|
3
|
-
getString,
|
|
4
9
|
getToolArgs,
|
|
5
10
|
getToolName,
|
|
6
11
|
getToolResult,
|
|
@@ -8,27 +13,12 @@ import {
|
|
|
8
13
|
type CursorPiToolDisplay,
|
|
9
14
|
type TranscriptOptions,
|
|
10
15
|
} from "./cursor-transcript-utils.js";
|
|
11
|
-
import { normalizeCursorToolName as normalizeToolName } from "./cursor-tool-presentation-registry.js";
|
|
12
|
-
import {
|
|
13
|
-
buildCursorPiToolDisplayFromSpec,
|
|
14
|
-
formatCursorToolTranscriptFromSpec,
|
|
15
|
-
type ToolDisplayContext,
|
|
16
|
-
} from "./cursor-transcript-tool-specs.js";
|
|
17
16
|
import { resolveTranscriptToolName } from "./cursor-web-tool-activity.js";
|
|
18
17
|
|
|
19
18
|
export type { CursorPiToolDisplay } from "./cursor-transcript-utils.js";
|
|
19
|
+
export type { ToolDisplayContext } from "./cursor-transcript-tool-specs.js";
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
const name = normalizeToolName(getToolName(toolCall));
|
|
23
|
-
if (name !== "createPlan") return undefined;
|
|
24
|
-
const args = getToolArgs(toolCall);
|
|
25
|
-
const result = normalizeResult(getToolResult(toolCall));
|
|
26
|
-
const plan = getString(args, "plan") ?? getString(asRecord(result.value), "plan");
|
|
27
|
-
const trimmed = plan?.trim();
|
|
28
|
-
return trimmed || undefined;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function buildToolDisplayContext(toolCall: unknown, options: TranscriptOptions): ToolDisplayContext {
|
|
21
|
+
function buildToolDisplayContext(toolCall: unknown, options: TranscriptOptions = {}): ToolDisplayContext {
|
|
32
22
|
const rawName = getToolName(toolCall);
|
|
33
23
|
const args = getToolArgs(toolCall);
|
|
34
24
|
return {
|
|
@@ -48,18 +38,12 @@ export function buildCursorPiToolDisplay(toolCall: unknown, options: TranscriptO
|
|
|
48
38
|
return buildCursorPiToolDisplayFromSpec(buildToolDisplayContext(toolCall, options));
|
|
49
39
|
}
|
|
50
40
|
|
|
51
|
-
export function
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
name: completed.name ?? started.name,
|
|
60
|
-
type: completed.type ?? started.type,
|
|
61
|
-
args: completed.args ?? started.args,
|
|
62
|
-
input: completed.input ?? started.input,
|
|
63
|
-
result: completed.result ?? started.result,
|
|
64
|
-
};
|
|
41
|
+
export function getCursorCreatePlanText(toolCall: unknown): string | undefined {
|
|
42
|
+
const name = normalizeCursorToolName(getToolName(toolCall));
|
|
43
|
+
if (name !== "createPlan") return undefined;
|
|
44
|
+
const args = getToolArgs(toolCall);
|
|
45
|
+
const result = normalizeResult(getToolResult(toolCall));
|
|
46
|
+
const plan = getString(args, "plan") ?? getString(asRecord(result.value), "plan");
|
|
47
|
+
const trimmed = plan?.trim();
|
|
48
|
+
return trimmed || undefined;
|
|
65
49
|
}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
getCursorReplayActivityTitle,
|
|
3
|
+
getCursorToolVisibilityPolicy,
|
|
4
|
+
normalizeCursorToolName as normalizeToolName,
|
|
5
|
+
} from "./cursor-tool-presentation-registry.js";
|
|
2
6
|
import { getToolArgs, getToolName } from "./cursor-transcript-utils.js";
|
|
3
7
|
import { resolveTranscriptToolName } from "./cursor-web-tool-activity.js";
|
|
4
8
|
|
|
@@ -14,6 +18,10 @@ export interface CursorToolVisibility {
|
|
|
14
18
|
fastLocalDiscovery: boolean;
|
|
15
19
|
}
|
|
16
20
|
|
|
21
|
+
export function getNormalizedCursorToolName(toolCall: unknown): string {
|
|
22
|
+
return classifyCursorToolVisibility(toolCall).normalizedName;
|
|
23
|
+
}
|
|
24
|
+
|
|
17
25
|
export function classifyCursorToolVisibility(toolCall: unknown): CursorToolVisibility {
|
|
18
26
|
const args = getToolArgs(toolCall);
|
|
19
27
|
const displayName = resolveTranscriptToolName(getToolName(toolCall), args);
|
|
@@ -1,19 +1,29 @@
|
|
|
1
1
|
import { resolveCursorEditDiff } from "./cursor-edit-diff.js";
|
|
2
|
-
import {
|
|
3
|
-
import { extractWebFetchTarget, extractWebSearchQuery } from "./cursor-web-tool-activity.js";
|
|
4
|
-
import { getFirstStringByKeys } from "./cursor-record-utils.js";
|
|
2
|
+
import { extractWebFetchTarget, extractWebSearchQuery } from "./cursor-web-tool-args.js";
|
|
5
3
|
import {
|
|
6
4
|
asRecord,
|
|
7
|
-
formatDisplayPath,
|
|
8
|
-
formatDiffHeaderLine,
|
|
9
|
-
formatDiffString,
|
|
10
|
-
formatError,
|
|
11
|
-
formatPathArg,
|
|
12
5
|
getArray,
|
|
13
6
|
getBoolean,
|
|
7
|
+
getFirstStringByKeys,
|
|
14
8
|
getNumber,
|
|
15
9
|
getRecord,
|
|
16
10
|
getString,
|
|
11
|
+
} from "./cursor-record-utils.js";
|
|
12
|
+
import {
|
|
13
|
+
collectTaskText,
|
|
14
|
+
getGenerateImageDisplayPath,
|
|
15
|
+
readMcpDisplayResult,
|
|
16
|
+
getReadLintDiagnostics,
|
|
17
|
+
getReadLintPaths,
|
|
18
|
+
getTaskDescription,
|
|
19
|
+
getTodoItems,
|
|
20
|
+
} from "./cursor-tool-result-display-readers.js";
|
|
21
|
+
|
|
22
|
+
import {
|
|
23
|
+
formatDisplayPath,
|
|
24
|
+
formatDiffString,
|
|
25
|
+
formatError,
|
|
26
|
+
formatPathArg,
|
|
17
27
|
joinSections,
|
|
18
28
|
limitItems,
|
|
19
29
|
limitText,
|
|
@@ -23,7 +33,6 @@ import {
|
|
|
23
33
|
DEFAULT_NATIVE_READ_DISPLAY_LINES,
|
|
24
34
|
readFilePreview,
|
|
25
35
|
stringifyUnknown,
|
|
26
|
-
firstNonEmptyLine,
|
|
27
36
|
truncateArg,
|
|
28
37
|
type NormalizedResult,
|
|
29
38
|
type TranscriptOptions,
|
|
@@ -445,38 +454,6 @@ export function formatDelete(args: Record<string, unknown>, result: NormalizedRe
|
|
|
445
454
|
return joinSections(`delete ${path}`, fileSize !== undefined ? `Deleted ${fileSize} bytes` : stringifyUnknown(result.value));
|
|
446
455
|
}
|
|
447
456
|
|
|
448
|
-
export function getReadLintPaths(args: Record<string, unknown>, result: NormalizedResult, options: TranscriptOptions): string[] {
|
|
449
|
-
const explicitPaths = Array.isArray(args.paths)
|
|
450
|
-
? args.paths.filter((entry): entry is string => typeof entry === "string")
|
|
451
|
-
: typeof args.path === "string"
|
|
452
|
-
? [args.path]
|
|
453
|
-
: [];
|
|
454
|
-
const resultPaths = (getArray(asRecord(result.value), "fileDiagnostics") ?? [])
|
|
455
|
-
.map((file) => getString(asRecord(file), "path"))
|
|
456
|
-
.filter((entry): entry is string => Boolean(entry));
|
|
457
|
-
return [...new Set([...explicitPaths, ...resultPaths].map((entry) => formatDisplayPath(entry, options.cwd)))];
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
export function getReadLintDiagnostics(result: NormalizedResult, options: TranscriptOptions): string[] {
|
|
461
|
-
const value = asRecord(result.value);
|
|
462
|
-
const files = getArray(value, "fileDiagnostics") ?? [];
|
|
463
|
-
const lines: string[] = [];
|
|
464
|
-
for (const file of files) {
|
|
465
|
-
const fileRecord = asRecord(file);
|
|
466
|
-
const pathValue = getString(fileRecord, "path");
|
|
467
|
-
const path = pathValue ? formatDisplayPath(pathValue, options.cwd) : "unknown";
|
|
468
|
-
const diagnostics = getArray(fileRecord, "diagnostics") ?? [];
|
|
469
|
-
for (const diagnostic of diagnostics) {
|
|
470
|
-
const diagnosticRecord = asRecord(diagnostic);
|
|
471
|
-
const severity = getString(diagnosticRecord, "severity") ?? "diagnostic";
|
|
472
|
-
const message = getString(diagnosticRecord, "message") ?? "";
|
|
473
|
-
const source = getString(diagnosticRecord, "source");
|
|
474
|
-
lines.push(`${path}: ${severity}${source ? ` ${source}` : ""}: ${message}`);
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
return lines;
|
|
478
|
-
}
|
|
479
|
-
|
|
480
457
|
export function formatReadLints(args: Record<string, unknown>, result: NormalizedResult, options: TranscriptOptions): string {
|
|
481
458
|
const paths = getReadLintPaths(args, result, options);
|
|
482
459
|
const header = `readLints${paths.length > 0 ? ` ${paths.join(" ")}` : ""}`;
|
|
@@ -487,24 +464,6 @@ export function formatReadLints(args: Record<string, unknown>, result: Normalize
|
|
|
487
464
|
return joinSections(header, limitText(lines.join("\n") || stringifyUnknown(result.value), options));
|
|
488
465
|
}
|
|
489
466
|
|
|
490
|
-
export function getTodoItems(args: Record<string, unknown>, result: NormalizedResult): Array<{ content: string; status?: string }> {
|
|
491
|
-
const value = asRecord(result.value);
|
|
492
|
-
const rawTodos = getArray(value, "todos") ?? getArray(args, "todos") ?? [];
|
|
493
|
-
const todos: Array<{ content: string; status?: string }> = [];
|
|
494
|
-
for (const todo of rawTodos) {
|
|
495
|
-
const record = asRecord(todo);
|
|
496
|
-
const content = getString(record, "content");
|
|
497
|
-
if (!content) continue;
|
|
498
|
-
const status = getString(record, "status");
|
|
499
|
-
todos.push(status ? { content, status } : { content });
|
|
500
|
-
}
|
|
501
|
-
return todos;
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
export function getTodoTotalCount(args: Record<string, unknown>, result: NormalizedResult, todos: Array<{ content: string; status?: string }>): number {
|
|
505
|
-
return getNumber(asRecord(result.value), "totalCount") ?? getNumber(args, "totalCount") ?? todos.length;
|
|
506
|
-
}
|
|
507
|
-
|
|
508
467
|
function formatTodoStatus(status: string | undefined): string {
|
|
509
468
|
if (status === "completed") return "✓";
|
|
510
469
|
if (status === "inProgress") return "…";
|
|
@@ -527,32 +486,6 @@ export function formatPlan(args: Record<string, unknown>, result: NormalizedResu
|
|
|
527
486
|
return formatTodos(args, result, options, "createPlan");
|
|
528
487
|
}
|
|
529
488
|
|
|
530
|
-
export function getTaskDescription(args: Record<string, unknown>, result: NormalizedResult): string {
|
|
531
|
-
return getString(args, "description") ?? getString(asRecord(result.value), "description") ?? "task";
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
function getNestedRecord(record: Record<string, unknown> | undefined, ...keys: string[]): Record<string, unknown> | undefined {
|
|
535
|
-
let current = record;
|
|
536
|
-
for (const key of keys) {
|
|
537
|
-
current = getRecord(current, key);
|
|
538
|
-
if (!current) return undefined;
|
|
539
|
-
}
|
|
540
|
-
return current;
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
export function collectTaskText(result: NormalizedResult): string {
|
|
544
|
-
const value = asRecord(result.value);
|
|
545
|
-
const success = getNestedRecord(value, "result", "success");
|
|
546
|
-
const command = getString(success, "command");
|
|
547
|
-
const stdout = getString(success, "stdout");
|
|
548
|
-
const interleavedOutput = getString(success, "interleavedOutput");
|
|
549
|
-
const assistantMessages = (getArray(value, "conversationSteps") ?? [])
|
|
550
|
-
.map((step) => getString(getRecord(asRecord(step), "assistantMessage"), "text"))
|
|
551
|
-
.filter((entry): entry is string => Boolean(entry));
|
|
552
|
-
const parts = [command ? `$ ${command}` : undefined, stdout || interleavedOutput, ...assistantMessages].filter((part): part is string => Boolean(part));
|
|
553
|
-
return parts.join("\n");
|
|
554
|
-
}
|
|
555
|
-
|
|
556
489
|
export function formatTask(args: Record<string, unknown>, result: NormalizedResult, options: TranscriptOptions): string {
|
|
557
490
|
const description = getTaskDescription(args, result);
|
|
558
491
|
if (result.status === "error") return joinSections(`task ${description}`, formatError(result.error));
|
|
@@ -560,34 +493,10 @@ export function formatTask(args: Record<string, unknown>, result: NormalizedResu
|
|
|
560
493
|
return joinSections(`task ${description}`, limitText(taskText || stringifyUnknown(result.value), options));
|
|
561
494
|
}
|
|
562
495
|
|
|
563
|
-
function getGenerateImageValue(result: NormalizedResult): Record<string, unknown> | undefined {
|
|
564
|
-
return asRecord(result.value);
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
export function getGenerateImagePath(args: Record<string, unknown>, result: NormalizedResult): string | undefined {
|
|
568
|
-
const value = getGenerateImageValue(result);
|
|
569
|
-
return getString(value, "filePath") ?? getString(args, "filePath") ?? getString(args, "path");
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
export function getGenerateImageDisplayPath(args: Record<string, unknown>, result: NormalizedResult, options: TranscriptOptions): string | undefined {
|
|
573
|
-
const path = getGenerateImagePath(args, result);
|
|
574
|
-
return path ? formatDisplayPath(path, options.cwd) : undefined;
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
export function inferImageMimeType(path: string | undefined): string | undefined {
|
|
578
|
-
const lower = path?.toLowerCase();
|
|
579
|
-
if (!lower) return undefined;
|
|
580
|
-
if (lower.endsWith(".png")) return "image/png";
|
|
581
|
-
if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
|
|
582
|
-
if (lower.endsWith(".gif")) return "image/gif";
|
|
583
|
-
if (lower.endsWith(".webp")) return "image/webp";
|
|
584
|
-
return undefined;
|
|
585
|
-
}
|
|
586
|
-
|
|
587
496
|
export function formatGenerateImage(args: Record<string, unknown>, result: NormalizedResult, options: TranscriptOptions): string {
|
|
588
497
|
const prompt = getString(args, "prompt") ?? getString(args, "description") ?? "image";
|
|
589
498
|
if (result.status === "error") return joinSections(`generateImage ${prompt}`, formatError(result.error));
|
|
590
|
-
const value =
|
|
499
|
+
const value = asRecord(result.value);
|
|
591
500
|
const displayPath = getGenerateImageDisplayPath(args, result, options);
|
|
592
501
|
const hasImageData = typeof value?.imageData === "string" && value.imageData.length > 0;
|
|
593
502
|
const lines = [displayPath ? `Saved image: ${displayPath}` : undefined, hasImageData ? "Image data returned by Cursor SDK." : undefined].filter(
|
|
@@ -597,26 +506,6 @@ export function formatGenerateImage(args: Record<string, unknown>, result: Norma
|
|
|
597
506
|
return joinSections(`generateImage ${prompt}`, limitText(stringifyUnknown(result.value), options));
|
|
598
507
|
}
|
|
599
508
|
|
|
600
|
-
function getMcpContentText(entry: unknown): string | undefined {
|
|
601
|
-
const record = asRecord(entry);
|
|
602
|
-
const directText = getString(record, "text");
|
|
603
|
-
if (directText) return directText;
|
|
604
|
-
const nestedText = getRecord(record, "text");
|
|
605
|
-
return getString(nestedText, "text");
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
function describeNonTextMcpContent(entry: unknown): string {
|
|
609
|
-
const record = asRecord(entry);
|
|
610
|
-
const type = getString(record, "type") ?? "content";
|
|
611
|
-
if (type === "image") {
|
|
612
|
-
const mimeType = getString(record, "mimeType") ?? getString(record, "mime") ?? getString(record, "mediaType");
|
|
613
|
-
return `[image${mimeType ? ` ${mimeType}` : ""} omitted]`;
|
|
614
|
-
}
|
|
615
|
-
if (type === "audio") return "[audio omitted]";
|
|
616
|
-
if (type === "resource") return "[resource omitted]";
|
|
617
|
-
return `[${type} omitted]`;
|
|
618
|
-
}
|
|
619
|
-
|
|
620
509
|
export function formatSemSearch(args: Record<string, unknown>, result: NormalizedResult, options: TranscriptOptions): string {
|
|
621
510
|
const query = getString(args, "query") ?? "semantic search";
|
|
622
511
|
const header = `semSearch ${truncateArg(query)}`;
|
|
@@ -681,69 +570,31 @@ export function formatRecordScreen(args: Record<string, unknown>, result: Normal
|
|
|
681
570
|
return joinSections(header, lines.join("\n"));
|
|
682
571
|
}
|
|
683
572
|
|
|
684
|
-
export function getMcpResultPreview(result: NormalizedResult): string | undefined {
|
|
685
|
-
if (result.status === "error") return undefined;
|
|
686
|
-
const value = asRecord(result.value);
|
|
687
|
-
const content = getArray(value, "content") ?? [];
|
|
688
|
-
for (const entry of content) {
|
|
689
|
-
const text = getMcpContentText(entry);
|
|
690
|
-
if (text) {
|
|
691
|
-
const line = firstNonEmptyLine(text);
|
|
692
|
-
if (line) return truncateArg(scrubSensitiveText(line), 120);
|
|
693
|
-
}
|
|
694
|
-
const summary = describeNonTextMcpContent(entry);
|
|
695
|
-
if (summary) return summary;
|
|
696
|
-
}
|
|
697
|
-
return undefined;
|
|
698
|
-
}
|
|
699
|
-
|
|
700
573
|
function formatWebToolBody(
|
|
701
574
|
toolLabel: string,
|
|
702
|
-
args: Record<string, unknown>,
|
|
703
575
|
result: NormalizedResult,
|
|
704
576
|
options: TranscriptOptions,
|
|
705
|
-
summaryArg: string | undefined,
|
|
706
577
|
): string {
|
|
707
578
|
if (result.status === "error") return joinSections(toolLabel, formatError(result.error));
|
|
708
|
-
|
|
709
|
-
const value = asRecord(result.value);
|
|
710
|
-
const isError = getBoolean(value, "isError");
|
|
711
|
-
const content = getArray(value, "content") ?? [];
|
|
712
|
-
const text = content
|
|
713
|
-
.map((entry) => getMcpContentText(entry))
|
|
714
|
-
.filter((entry): entry is string => Boolean(entry))
|
|
715
|
-
.join("\n");
|
|
716
|
-
const contentSummary = content.length > 0 ? content.map(describeNonTextMcpContent).join("\n") : stringifyUnknown(result.value);
|
|
717
|
-
const body = `${isError ? "[tool error]\n" : ""}${text || contentSummary}`;
|
|
718
|
-
return joinSections(toolLabel, limitText(`${summary}${body}`.trim(), options));
|
|
579
|
+
return joinSections(toolLabel, limitText(readMcpDisplayResult(result).body, options));
|
|
719
580
|
}
|
|
720
581
|
|
|
721
582
|
export function formatWebSearch(args: Record<string, unknown>, result: NormalizedResult, options: TranscriptOptions): string {
|
|
722
583
|
const query = extractWebSearchQuery(args);
|
|
723
584
|
const header = query ? `web search ${query}` : "web search";
|
|
724
|
-
return formatWebToolBody(header,
|
|
585
|
+
return formatWebToolBody(header, result, options);
|
|
725
586
|
}
|
|
726
587
|
|
|
727
588
|
export function formatWebFetch(args: Record<string, unknown>, result: NormalizedResult, options: TranscriptOptions): string {
|
|
728
589
|
const target = extractWebFetchTarget(args);
|
|
729
590
|
const header = target ? `web fetch ${target}` : "web fetch";
|
|
730
|
-
return formatWebToolBody(header,
|
|
591
|
+
return formatWebToolBody(header, result, options);
|
|
731
592
|
}
|
|
732
593
|
|
|
733
594
|
export function formatMcp(args: Record<string, unknown>, result: NormalizedResult, options: TranscriptOptions): string {
|
|
734
595
|
const toolName = typeof args.toolName === "string" ? args.toolName : "mcp";
|
|
735
596
|
if (result.status === "error") return joinSections(toolName, formatError(result.error));
|
|
736
|
-
|
|
737
|
-
const value = asRecord(result.value);
|
|
738
|
-
const isError = getBoolean(value, "isError");
|
|
739
|
-
const content = getArray(value, "content") ?? [];
|
|
740
|
-
const text = content
|
|
741
|
-
.map((entry) => getMcpContentText(entry))
|
|
742
|
-
.filter((entry): entry is string => Boolean(entry))
|
|
743
|
-
.join("\n");
|
|
744
|
-
const contentSummary = content.length > 0 ? content.map(describeNonTextMcpContent).join("\n") : stringifyUnknown(result.value);
|
|
745
|
-
const body = `${isError ? "[tool error]\n" : ""}${text || contentSummary}`;
|
|
746
|
-
return joinSections(toolName, limitText(body, options));
|
|
597
|
+
return joinSections(toolName, limitText(readMcpDisplayResult(result).body, options));
|
|
747
598
|
}
|
|
748
599
|
|
|
749
600
|
const UNKNOWN_TOOL_FALLBACK_MAX_ARGS = 8;
|