pi-cursor-sdk 0.1.20 → 0.1.21
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 +32 -0
- package/README.md +49 -9
- package/docs/cursor-dogfood-checklist.md +57 -0
- package/docs/cursor-live-smoke-checklist.md +115 -9
- package/docs/cursor-model-ux-spec.md +57 -17
- package/docs/cursor-native-tool-replay.md +15 -7
- package/docs/cursor-native-tool-visual-audit.md +104 -59
- package/docs/cursor-testing-lessons.md +8 -3
- package/docs/cursor-tool-surfaces.md +69 -0
- package/package.json +34 -10
- package/scripts/debug-provider-events.d.mts +59 -0
- package/scripts/debug-provider-events.mjs +70 -175
- package/scripts/debug-sdk-events.d.mts +90 -0
- package/scripts/debug-sdk-events.mjs +36 -98
- package/scripts/fixtures/plan-strip-shim/index.ts +12 -0
- package/scripts/isolated-cursor-smoke.sh +264 -102
- package/scripts/lib/cursor-child-process.d.mts +10 -0
- package/scripts/lib/cursor-child-process.mjs +50 -0
- package/scripts/lib/cursor-cli-args.d.mts +63 -0
- package/scripts/lib/cursor-cli-args.mjs +129 -0
- package/scripts/lib/cursor-script-fail.d.mts +1 -0
- package/scripts/lib/cursor-script-fail.mjs +13 -0
- package/scripts/lib/cursor-sdk-output-filter.d.mts +5 -0
- package/scripts/lib/cursor-smoke-env.d.mts +38 -0
- package/scripts/lib/cursor-smoke-env.mjs +81 -0
- package/scripts/lib/cursor-smoke-shell.sh +174 -0
- package/scripts/lib/cursor-visual-render.d.mts +15 -0
- package/scripts/lib/cursor-visual-render.mjs +131 -0
- package/scripts/probe-mcp-coldstart.mjs +20 -38
- package/scripts/refresh-cursor-model-snapshots.mjs +29 -65
- package/scripts/steering-rpc-smoke.mjs +170 -65
- package/scripts/tmux-live-smoke.sh +152 -98
- package/scripts/visual-tui-smoke.mjs +659 -0
- package/shared/cursor-sdk-event-debug-env.d.mts +12 -0
- package/shared/cursor-sdk-event-debug-env.mjs +13 -0
- package/shared/cursor-sensitive-text.d.mts +1 -0
- package/{scripts/lib/cursor-probe-utils.mjs → shared/cursor-sensitive-text.mjs} +1 -13
- package/shared/cursor-setting-sources.d.mts +5 -0
- package/shared/cursor-setting-sources.mjs +22 -0
- package/src/context.ts +21 -12
- package/src/cursor-bridge-contract.ts +1 -3
- package/src/cursor-incomplete-tool-visibility.ts +22 -5
- package/src/cursor-native-tool-display-registration.ts +63 -27
- package/src/cursor-native-tool-display-replay.ts +246 -144
- package/src/cursor-native-tool-display-state.ts +2 -0
- package/src/cursor-native-tool-display-tools.ts +149 -41
- package/src/cursor-provider-live-run-drain.ts +1 -52
- package/src/cursor-provider-run-finalizer.ts +235 -0
- package/src/cursor-provider-run-outcome.ts +149 -0
- package/src/cursor-provider-turn-api-key.ts +8 -0
- package/src/cursor-provider-turn-coordinator.ts +98 -446
- package/src/cursor-provider-turn-display-router.ts +216 -0
- package/src/cursor-provider-turn-emit.ts +59 -0
- package/src/cursor-provider-turn-finalize.ts +119 -0
- package/src/cursor-provider-turn-lifecycle-emitter.ts +97 -0
- package/src/cursor-provider-turn-message-offset.ts +15 -0
- package/src/cursor-provider-turn-prepare.ts +216 -0
- package/src/cursor-provider-turn-runner.ts +138 -0
- package/src/cursor-provider-turn-sdk-normalizer.ts +88 -0
- package/src/cursor-provider-turn-send.ts +103 -0
- package/src/cursor-provider-turn-shell-output.ts +107 -0
- package/src/cursor-provider-turn-tool-ledger.ts +126 -0
- package/src/cursor-provider-turn-types.ts +87 -0
- package/src/cursor-provider.ts +16 -504
- package/src/cursor-replay-activity-builders.ts +276 -0
- package/src/cursor-replay-source-names.ts +33 -0
- package/src/cursor-replay-summary-args.ts +191 -0
- package/src/cursor-replay-tool-details.ts +464 -0
- package/src/cursor-run-final-text.ts +56 -0
- package/src/cursor-sdk-abort-error-guard.ts +4 -0
- package/src/cursor-sdk-event-debug-constants.ts +14 -5
- package/src/cursor-sdk-event-debug.ts +2 -1
- package/src/cursor-sensitive-text.ts +3 -36
- package/src/cursor-session-agent.ts +3 -1
- package/src/cursor-setting-sources.ts +7 -10
- package/src/cursor-state.ts +232 -28
- package/src/cursor-tool-lifecycle.ts +9 -8
- package/src/cursor-tool-manifest.ts +41 -0
- package/src/cursor-tool-names.ts +18 -106
- package/src/cursor-tool-presentation-registry.ts +556 -0
- package/src/cursor-tool-transcript.ts +1 -1
- package/src/cursor-tool-visibility.ts +3 -27
- package/src/cursor-transcript-tool-formatters.ts +0 -59
- package/src/cursor-transcript-tool-specs.ts +158 -233
- package/src/cursor-transcript-utils.ts +0 -44
- package/src/cursor-web-tool-activity.ts +10 -60
- package/src/cursor-web-tool-args.ts +39 -0
- package/src/index.ts +4 -10
|
@@ -17,22 +17,147 @@ import {
|
|
|
17
17
|
isCursorReplayToolName,
|
|
18
18
|
} from "./cursor-tool-names.js";
|
|
19
19
|
import {
|
|
20
|
-
asCursorReplayToolDetails,
|
|
21
20
|
createCursorReplayOnlyToolDefinition,
|
|
21
|
+
isCursorReplayNativeEditDetails,
|
|
22
|
+
isCursorReplayNativeWriteDetails,
|
|
23
|
+
parseCursorReplayToolDetails,
|
|
22
24
|
renderCursorReplayResult,
|
|
23
25
|
renderNativeLookingCursorFileMutationCall,
|
|
24
26
|
renderNativeLookingCursorReadReplayResult,
|
|
25
27
|
} from "./cursor-native-tool-display-replay.js";
|
|
26
28
|
import {
|
|
27
29
|
consumeCursorNativeToolDisplay,
|
|
28
|
-
isCursorFileMutationToolName,
|
|
29
30
|
isCursorReplayToolCallId,
|
|
30
31
|
} from "./cursor-native-tool-display-state.js";
|
|
31
32
|
|
|
32
33
|
const CURSOR_MODEL_ACTIVE_REPLAY_TOOL_NAMES = [CURSOR_REPLAY_ACTIVITY_TOOL_NAME] as const;
|
|
33
34
|
const CURSOR_REPLAY_TOOL_NAMES = [CURSOR_REPLAY_ACTIVITY_TOOL_NAME, ...CURSOR_REPLAY_LEGACY_TOOL_NAMES] as const;
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
|
|
36
|
+
type AnyToolDefinition = ToolDefinition<TSchema, unknown, unknown>;
|
|
37
|
+
type RenderCall = NonNullable<AnyToolDefinition["renderCall"]>;
|
|
38
|
+
type RenderResult = NonNullable<AnyToolDefinition["renderResult"]>;
|
|
39
|
+
|
|
40
|
+
type NativeReplayStrategy = {
|
|
41
|
+
createDefinition: (cwd: string) => AnyToolDefinition;
|
|
42
|
+
missingReplayPolicy?: "block-file-mutation";
|
|
43
|
+
renderReplayCall?: (
|
|
44
|
+
args: Parameters<RenderCall>[0],
|
|
45
|
+
theme: Parameters<RenderCall>[1],
|
|
46
|
+
context: Parameters<RenderCall>[2],
|
|
47
|
+
renderBase: () => ReturnType<RenderCall>,
|
|
48
|
+
) => ReturnType<RenderCall>;
|
|
49
|
+
renderReplayResult?: (
|
|
50
|
+
result: Parameters<RenderResult>[0],
|
|
51
|
+
options: Parameters<RenderResult>[1],
|
|
52
|
+
theme: Parameters<RenderResult>[2],
|
|
53
|
+
context: Parameters<RenderResult>[3],
|
|
54
|
+
renderBase: () => ReturnType<RenderResult>,
|
|
55
|
+
) => ReturnType<RenderResult>;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
function emptyText(): Text {
|
|
59
|
+
return new Text("", 0, 0);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function renderReadReplayCall(
|
|
63
|
+
args: Parameters<RenderCall>[0],
|
|
64
|
+
theme: Parameters<RenderCall>[1],
|
|
65
|
+
context: Parameters<RenderCall>[2],
|
|
66
|
+
renderBase: () => ReturnType<RenderCall>,
|
|
67
|
+
): ReturnType<RenderCall> {
|
|
68
|
+
const rendered = renderBase();
|
|
69
|
+
if ((args as Record<string, unknown>).localReadPreview !== true || context.expanded) return rendered;
|
|
70
|
+
const baseText = rendered.render(120).join("\n").trimEnd();
|
|
71
|
+
const labeled = `${baseText}${theme.fg("muted", " · local file preview")}`;
|
|
72
|
+
if (rendered instanceof Text) {
|
|
73
|
+
rendered.setText(labeled);
|
|
74
|
+
return rendered;
|
|
75
|
+
}
|
|
76
|
+
return new Text(labeled, 0, 0);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function renderReadReplayResult(
|
|
80
|
+
result: Parameters<RenderResult>[0],
|
|
81
|
+
options: Parameters<RenderResult>[1],
|
|
82
|
+
theme: Parameters<RenderResult>[2],
|
|
83
|
+
context: Parameters<RenderResult>[3],
|
|
84
|
+
renderBase: () => ReturnType<RenderResult>,
|
|
85
|
+
): ReturnType<RenderResult> {
|
|
86
|
+
return renderNativeLookingCursorReadReplayResult(
|
|
87
|
+
result,
|
|
88
|
+
options,
|
|
89
|
+
theme,
|
|
90
|
+
context as Parameters<typeof renderNativeLookingCursorReadReplayResult>[3],
|
|
91
|
+
renderBase,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function renderEditReplayResult(
|
|
96
|
+
result: Parameters<RenderResult>[0],
|
|
97
|
+
options: Parameters<RenderResult>[1],
|
|
98
|
+
theme: Parameters<RenderResult>[2],
|
|
99
|
+
context: Parameters<RenderResult>[3],
|
|
100
|
+
renderBase: () => ReturnType<RenderResult>,
|
|
101
|
+
): ReturnType<RenderResult> {
|
|
102
|
+
const details = parseCursorReplayToolDetails(result.details);
|
|
103
|
+
return details && isCursorReplayNativeEditDetails(details)
|
|
104
|
+
? renderCursorReplayResult(result, options, theme, context as Parameters<typeof renderCursorReplayResult>[3], context.isError)
|
|
105
|
+
: renderBase();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function renderWriteReplayResult(
|
|
109
|
+
result: Parameters<RenderResult>[0],
|
|
110
|
+
options: Parameters<RenderResult>[1],
|
|
111
|
+
theme: Parameters<RenderResult>[2],
|
|
112
|
+
context: Parameters<RenderResult>[3],
|
|
113
|
+
renderBase: () => ReturnType<RenderResult>,
|
|
114
|
+
): ReturnType<RenderResult> {
|
|
115
|
+
const details = parseCursorReplayToolDetails(result.details);
|
|
116
|
+
return details && isCursorReplayNativeWriteDetails(details)
|
|
117
|
+
? renderCursorReplayResult(result, options, theme, context as Parameters<typeof renderCursorReplayResult>[3], context.isError)
|
|
118
|
+
: renderBase();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
type BuiltinNativeCursorToolName = "read" | "bash" | "edit" | "write" | "grep" | "find" | "ls";
|
|
122
|
+
|
|
123
|
+
const NATIVE_CURSOR_TOOL_STRATEGIES: Record<BuiltinNativeCursorToolName, NativeReplayStrategy> = {
|
|
124
|
+
read: {
|
|
125
|
+
createDefinition: (cwd) => createReadToolDefinition(cwd) as AnyToolDefinition,
|
|
126
|
+
renderReplayCall: renderReadReplayCall,
|
|
127
|
+
renderReplayResult: renderReadReplayResult,
|
|
128
|
+
},
|
|
129
|
+
bash: { createDefinition: (cwd) => createBashToolDefinition(cwd) as AnyToolDefinition },
|
|
130
|
+
edit: {
|
|
131
|
+
createDefinition: (cwd) => createEditToolDefinition(cwd) as AnyToolDefinition,
|
|
132
|
+
missingReplayPolicy: "block-file-mutation",
|
|
133
|
+
renderReplayCall: (args, theme, context) =>
|
|
134
|
+
renderNativeLookingCursorFileMutationCall("edit", args as Record<string, unknown>, theme, context.isPartial),
|
|
135
|
+
renderReplayResult: renderEditReplayResult,
|
|
136
|
+
},
|
|
137
|
+
write: {
|
|
138
|
+
createDefinition: (cwd) => createWriteToolDefinition(cwd) as AnyToolDefinition,
|
|
139
|
+
missingReplayPolicy: "block-file-mutation",
|
|
140
|
+
renderReplayCall: (args, theme, context) =>
|
|
141
|
+
renderNativeLookingCursorFileMutationCall("write", args as Record<string, unknown>, theme, context.isPartial),
|
|
142
|
+
renderReplayResult: renderWriteReplayResult,
|
|
143
|
+
},
|
|
144
|
+
grep: { createDefinition: (cwd) => createGrepToolDefinition(cwd) as AnyToolDefinition },
|
|
145
|
+
find: { createDefinition: (cwd) => createFindToolDefinition(cwd) as AnyToolDefinition },
|
|
146
|
+
ls: { createDefinition: (cwd) => createLsToolDefinition(cwd) as AnyToolDefinition },
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const BUILTIN_NATIVE_CURSOR_TOOL_NAMES = Object.keys(NATIVE_CURSOR_TOOL_STRATEGIES) as BuiltinNativeCursorToolName[];
|
|
150
|
+
export const NATIVE_CURSOR_TOOL_NAMES = [
|
|
151
|
+
...BUILTIN_NATIVE_CURSOR_TOOL_NAMES,
|
|
152
|
+
...CURSOR_REPLAY_TOOL_NAMES,
|
|
153
|
+
] as readonly NativeCursorToolName[];
|
|
154
|
+
export type NativeCursorToolName = BuiltinNativeCursorToolName | typeof CURSOR_REPLAY_TOOL_NAMES[number];
|
|
155
|
+
|
|
156
|
+
function getNativeReplayStrategy(toolName: string): NativeReplayStrategy | undefined {
|
|
157
|
+
return Object.hasOwn(NATIVE_CURSOR_TOOL_STRATEGIES, toolName)
|
|
158
|
+
? NATIVE_CURSOR_TOOL_STRATEGIES[toolName as BuiltinNativeCursorToolName]
|
|
159
|
+
: undefined;
|
|
160
|
+
}
|
|
36
161
|
|
|
37
162
|
export function isNativeCursorToolName(toolName: string): toolName is NativeCursorToolName {
|
|
38
163
|
return NATIVE_CURSOR_TOOL_NAMES.some((nativeToolName) => nativeToolName === toolName);
|
|
@@ -42,6 +167,7 @@ export function wrapNativeCursorTool<TParams extends TSchema, TDetails, TState>(
|
|
|
42
167
|
definition: ToolDefinition<TParams, TDetails, TState>,
|
|
43
168
|
getCurrentDefinition: () => ToolDefinition<TParams, TDetails, TState>,
|
|
44
169
|
): ToolDefinition<TParams, TDetails, TState> {
|
|
170
|
+
const strategy = getNativeReplayStrategy(definition.name);
|
|
45
171
|
return {
|
|
46
172
|
...definition,
|
|
47
173
|
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
@@ -60,61 +186,43 @@ export function wrapNativeCursorTool<TParams extends TSchema, TDetails, TState>(
|
|
|
60
186
|
terminate: cursorDisplay.terminate ?? true,
|
|
61
187
|
};
|
|
62
188
|
}
|
|
63
|
-
if (
|
|
189
|
+
if (strategy?.missingReplayPolicy === "block-file-mutation" && isCursorReplayToolCallId(toolCallId)) {
|
|
64
190
|
throw new Error(`No recorded Cursor ${definition.name} result was available. This replay-only call does not execute file mutations.`);
|
|
65
191
|
}
|
|
66
192
|
return getCurrentDefinition().execute(toolCallId, params, signal, onUpdate, ctx);
|
|
67
193
|
},
|
|
68
194
|
renderCall(args, theme, context) {
|
|
69
|
-
if (definition.name === "read" && isCursorReplayToolCallId(context.toolCallId)) {
|
|
70
|
-
const currentRenderCall = getCurrentDefinition().renderCall;
|
|
71
|
-
const rendered = currentRenderCall ? currentRenderCall(args, theme, context) : new Text("", 0, 0);
|
|
72
|
-
if ((args as Record<string, unknown>).localReadPreview === true && !context.expanded) {
|
|
73
|
-
const baseText = rendered.render(120).join("\n").trimEnd();
|
|
74
|
-
const labeled = `${baseText}${theme.fg("muted", " · local file preview")}`;
|
|
75
|
-
if (rendered instanceof Text) {
|
|
76
|
-
rendered.setText(labeled);
|
|
77
|
-
return rendered;
|
|
78
|
-
}
|
|
79
|
-
return new Text(labeled, 0, 0);
|
|
80
|
-
}
|
|
81
|
-
return rendered;
|
|
82
|
-
}
|
|
83
|
-
if (isCursorFileMutationToolName(definition.name) && isCursorReplayToolCallId(context.toolCallId)) {
|
|
84
|
-
return renderNativeLookingCursorFileMutationCall(definition.name, args as Record<string, unknown>, theme, context.isPartial);
|
|
85
|
-
}
|
|
86
195
|
const currentRenderCall = getCurrentDefinition().renderCall;
|
|
87
|
-
|
|
196
|
+
const renderBase = () => currentRenderCall?.(args, theme, context) ?? emptyText();
|
|
197
|
+
const isReplayCall = typeof context.toolCallId === "string" && isCursorReplayToolCallId(context.toolCallId);
|
|
198
|
+
if (isReplayCall && strategy?.renderReplayCall) {
|
|
199
|
+
return strategy.renderReplayCall(args, theme, context, renderBase) as ReturnType<NonNullable<ToolDefinition<TParams, TDetails, TState>["renderCall"]>>;
|
|
200
|
+
}
|
|
201
|
+
return renderBase();
|
|
88
202
|
},
|
|
89
203
|
renderResult(result, options, theme, context) {
|
|
90
|
-
const details = asCursorReplayToolDetails(result.details);
|
|
91
|
-
if (isCursorFileMutationToolName(definition.name) && details?.cursorToolName === definition.name) {
|
|
92
|
-
return renderCursorReplayResult(result, options, theme, context, context.isError);
|
|
93
|
-
}
|
|
94
|
-
if (definition.name === "read" && isCursorReplayToolCallId(context.toolCallId)) {
|
|
95
|
-
return renderNativeLookingCursorReadReplayResult(result, options, theme, context, () =>
|
|
96
|
-
getCurrentDefinition().renderResult?.(result, options, theme, context),
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
204
|
const currentRenderResult = getCurrentDefinition().renderResult;
|
|
100
|
-
|
|
205
|
+
const renderBase = () => currentRenderResult?.(result, options, theme, context) ?? emptyText();
|
|
206
|
+
const isReplayCall = typeof context.toolCallId === "string" && isCursorReplayToolCallId(context.toolCallId);
|
|
207
|
+
if (isReplayCall && strategy?.renderReplayResult) {
|
|
208
|
+
return strategy.renderReplayResult(result, options, theme, context, renderBase) as ReturnType<NonNullable<ToolDefinition<TParams, TDetails, TState>["renderResult"]>>;
|
|
209
|
+
}
|
|
210
|
+
return renderBase();
|
|
101
211
|
},
|
|
102
212
|
};
|
|
103
213
|
}
|
|
104
214
|
|
|
105
215
|
export function createNativeCursorToolDefinition(toolName: NativeCursorToolName, cwd: string): ToolDefinition<TSchema, unknown, unknown> {
|
|
106
|
-
|
|
107
|
-
if (
|
|
108
|
-
if (toolName === "edit") return createEditToolDefinition(cwd) as ToolDefinition<TSchema, unknown, unknown>;
|
|
109
|
-
if (toolName === "write") return createWriteToolDefinition(cwd) as ToolDefinition<TSchema, unknown, unknown>;
|
|
110
|
-
if (toolName === "grep") return createGrepToolDefinition(cwd) as ToolDefinition<TSchema, unknown, unknown>;
|
|
111
|
-
if (toolName === "find") return createFindToolDefinition(cwd) as ToolDefinition<TSchema, unknown, unknown>;
|
|
112
|
-
if (toolName === "ls") return createLsToolDefinition(cwd) as ToolDefinition<TSchema, unknown, unknown>;
|
|
216
|
+
const strategy = getNativeReplayStrategy(toolName);
|
|
217
|
+
if (strategy) return strategy.createDefinition(cwd);
|
|
113
218
|
if (isCursorReplayToolName(toolName)) return createCursorReplayOnlyToolDefinition(toolName) as ToolDefinition<TSchema, unknown, unknown>;
|
|
114
219
|
throw new Error(`Unsupported Cursor native replay tool: ${toolName}`);
|
|
115
220
|
}
|
|
116
221
|
|
|
117
|
-
export function registerNativeCursorTool(
|
|
222
|
+
export function registerNativeCursorTool(
|
|
223
|
+
pi: Pick<import("@earendil-works/pi-coding-agent").ExtensionAPI, "registerTool">,
|
|
224
|
+
toolName: NativeCursorToolName,
|
|
225
|
+
): void {
|
|
118
226
|
const definition = createNativeCursorToolDefinition(toolName, getCursorSessionCwd());
|
|
119
227
|
pi.registerTool(wrapNativeCursorTool(definition, () => createNativeCursorToolDefinition(toolName, getCursorSessionCwd())));
|
|
120
228
|
}
|
|
@@ -22,7 +22,7 @@ import { type CursorPiBridgeToolRequest } from "./cursor-pi-tool-bridge.js";
|
|
|
22
22
|
import { resetSessionCursorAgent } from "./cursor-session-agent.js";
|
|
23
23
|
import { applyCursorApproximateUsage } from "./cursor-usage-accounting.js";
|
|
24
24
|
import { CursorPartialContentEmitter } from "./cursor-partial-content-emitter.js";
|
|
25
|
-
import {
|
|
25
|
+
import { trimCurrentTurnAlreadyEmittedCursorText } from "./cursor-run-final-text.js";
|
|
26
26
|
import { formatCursorSdkAbortMessage, resolveCursorSdkAbortCause } from "./cursor-provider-errors.js";
|
|
27
27
|
import { formatInactiveCursorReplayTrace } from "./cursor-native-replay-trace.js";
|
|
28
28
|
import { partitionNativeToolsByActiveContext } from "./cursor-native-replay-routing.js";
|
|
@@ -152,57 +152,6 @@ function emitCursorLiveQueuedEvent(
|
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
function isCursorTextBoundary(text: string, index: number): boolean {
|
|
156
|
-
if (index <= 0 || index >= text.length) return true;
|
|
157
|
-
const before = text[index - 1];
|
|
158
|
-
const after = text[index];
|
|
159
|
-
return !/[\p{L}\p{N}_]/u.test(before) || !/[\p{L}\p{N}_]/u.test(after);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function trimAlreadyEmittedCursorText(text: string, emittedText: string, options?: { allowPartialPrefix?: boolean }): string {
|
|
163
|
-
if (!text || !emittedText) return text;
|
|
164
|
-
if (text === emittedText) return "";
|
|
165
|
-
if (text.startsWith(emittedText) && (options?.allowPartialPrefix || isCursorTextBoundary(text, emittedText.length))) {
|
|
166
|
-
return text.slice(emittedText.length);
|
|
167
|
-
}
|
|
168
|
-
if (emittedText.endsWith(text) && isCursorTextBoundary(emittedText, emittedText.length - text.length)) return "";
|
|
169
|
-
const trimmedText = text.trim();
|
|
170
|
-
const trimmedEmittedText = emittedText.trim();
|
|
171
|
-
if (trimmedText === trimmedEmittedText) return "";
|
|
172
|
-
if (trimmedText && trimmedEmittedText.endsWith(trimmedText)) {
|
|
173
|
-
const suffixStart = trimmedEmittedText.length - trimmedText.length;
|
|
174
|
-
if (isCursorTextBoundary(trimmedEmittedText, suffixStart)) return "";
|
|
175
|
-
}
|
|
176
|
-
return text;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function trimCurrentTurnAlreadyEmittedCursorText(text: string, currentTurnEmittedText: string, emittedText = currentTurnEmittedText): string {
|
|
180
|
-
if (!currentTurnEmittedText) return trimAlreadyEmittedCursorText(text, emittedText);
|
|
181
|
-
const currentTurnTrimmedText = trimAlreadyEmittedCursorText(text, currentTurnEmittedText, { allowPartialPrefix: true });
|
|
182
|
-
if (currentTurnTrimmedText !== text) return currentTurnTrimmedText;
|
|
183
|
-
if (emittedText.endsWith(currentTurnEmittedText)) {
|
|
184
|
-
const emittedTextTrimmedText = trimAlreadyEmittedCursorText(text, emittedText, { allowPartialPrefix: true });
|
|
185
|
-
if (emittedTextTrimmedText !== text) return emittedTextTrimmedText;
|
|
186
|
-
}
|
|
187
|
-
return trimAlreadyEmittedCursorText(text, emittedText);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
export function selectCursorFinalText(
|
|
191
|
-
resultText: unknown,
|
|
192
|
-
textDeltas: readonly string[],
|
|
193
|
-
emittedText: string,
|
|
194
|
-
fallbackText?: string,
|
|
195
|
-
options?: { allowPartialPrefix?: boolean },
|
|
196
|
-
): string {
|
|
197
|
-
const candidates = [typeof resultText === "string" ? resultText : undefined, fallbackText, textDeltas.join("")];
|
|
198
|
-
for (const candidate of candidates) {
|
|
199
|
-
if (!hasUsableText(candidate)) continue;
|
|
200
|
-
const trimmedCandidate = trimAlreadyEmittedCursorText(candidate, emittedText, options);
|
|
201
|
-
if (hasUsableText(trimmedCandidate)) return trimmedCandidate;
|
|
202
|
-
}
|
|
203
|
-
return "";
|
|
204
|
-
}
|
|
205
|
-
|
|
206
155
|
function emitCursorNativeToolUseTurn(
|
|
207
156
|
stream: AssistantMessageEventStream,
|
|
208
157
|
partial: AssistantMessage,
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import type { AssistantMessage } from "@earendil-works/pi-ai";
|
|
2
|
+
import { cursorLiveRuns } from "./cursor-provider-live-run-drain.js";
|
|
3
|
+
import { abandonSessionCursorAgent } from "./cursor-provider-live-run-drain.js";
|
|
4
|
+
import {
|
|
5
|
+
classifyCursorRunEmission,
|
|
6
|
+
getCursorRunAbortMessage,
|
|
7
|
+
type CursorRunOutcome,
|
|
8
|
+
} from "./cursor-provider-run-outcome.js";
|
|
9
|
+
import {
|
|
10
|
+
formatCursorSdkAbortMessage,
|
|
11
|
+
resolveCursorSdkAbortCause,
|
|
12
|
+
sanitizeCursorProviderError,
|
|
13
|
+
} from "./cursor-provider-errors.js";
|
|
14
|
+
import { CursorLiveRunAbortError } from "./cursor-live-run-coordinator.js";
|
|
15
|
+
import type { IncompleteCursorToolRunOutcomeInput } from "./cursor-incomplete-tool-visibility.js";
|
|
16
|
+
import type { installCursorSdkAbortErrorSuppression } from "./cursor-sdk-abort-error-guard.js";
|
|
17
|
+
import type { CursorSdkEventDebugSink } from "./cursor-sdk-event-debug.js";
|
|
18
|
+
import { awaitFinalizeCursorRunOutcome } from "./cursor-provider-turn-finalize.js";
|
|
19
|
+
import type {
|
|
20
|
+
CursorProviderTurnPrepareResult,
|
|
21
|
+
CursorProviderTurnRunnerParams,
|
|
22
|
+
CursorProviderTurnSend,
|
|
23
|
+
CursorProviderTurnSendResult,
|
|
24
|
+
} from "./cursor-provider-turn-types.js";
|
|
25
|
+
import { applyCursorApproximateUsage } from "./cursor-usage-accounting.js";
|
|
26
|
+
import { hasUsableText } from "./cursor-record-utils.js";
|
|
27
|
+
import { buildIncompleteCursorToolRunOutcome } from "./cursor-incomplete-tool-visibility.js";
|
|
28
|
+
|
|
29
|
+
export type CursorTurnTerminalEvent =
|
|
30
|
+
| {
|
|
31
|
+
kind: "direct";
|
|
32
|
+
prepared: CursorProviderTurnPrepareResult;
|
|
33
|
+
outcome: CursorRunOutcome;
|
|
34
|
+
}
|
|
35
|
+
| { kind: "error"; prepared: CursorProviderTurnPrepareResult | undefined; error: unknown };
|
|
36
|
+
|
|
37
|
+
function applyLiveRunOutcome(
|
|
38
|
+
outcome: CursorRunOutcome,
|
|
39
|
+
prepared: CursorProviderTurnPrepareResult,
|
|
40
|
+
context: CursorProviderTurnRunnerParams["context"],
|
|
41
|
+
): void {
|
|
42
|
+
if (prepared.runtime.kind !== "live" || prepared.runtime.liveRun.disposed) return;
|
|
43
|
+
const { liveRun } = prepared.runtime;
|
|
44
|
+
switch (classifyCursorRunEmission(outcome)) {
|
|
45
|
+
case "finished":
|
|
46
|
+
prepared.sessionAgentLease.commitSend(context, prepared.meta.bootstrap);
|
|
47
|
+
cursorLiveRuns.markFinished(liveRun, outcome.kind === "finished" ? outcome.finalText : "");
|
|
48
|
+
break;
|
|
49
|
+
case "cancelled":
|
|
50
|
+
cursorLiveRuns.markCancelled(liveRun, getCursorRunAbortMessage(outcome));
|
|
51
|
+
break;
|
|
52
|
+
case "failed":
|
|
53
|
+
cursorLiveRuns.markError(liveRun, outcome.kind === "error" ? outcome.errorMessage : "Cursor SDK run failed.");
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface CursorLiveRunCompletion {
|
|
59
|
+
waitCompletion: Promise<void>;
|
|
60
|
+
prepared: CursorProviderTurnPrepareResult;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface CursorRunFinalizerParams {
|
|
64
|
+
runnerParams: CursorProviderTurnRunnerParams;
|
|
65
|
+
sdkEventDebug: () => CursorSdkEventDebugSink | undefined;
|
|
66
|
+
sdkAbortErrorSuppression: ReturnType<typeof installCursorSdkAbortErrorSuppression>;
|
|
67
|
+
resolvedApiKey: () => string | undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface StartCursorLiveRunCompletionParams {
|
|
71
|
+
send: CursorProviderTurnSend;
|
|
72
|
+
prepared: CursorProviderTurnPrepareResult;
|
|
73
|
+
modelId: string;
|
|
74
|
+
discardIncompleteTools: (outcome: IncompleteCursorToolRunOutcomeInput) => void;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export class CursorRunFinalizer {
|
|
78
|
+
private terminalApplied = false;
|
|
79
|
+
|
|
80
|
+
constructor(private readonly params: CursorRunFinalizerParams) {}
|
|
81
|
+
|
|
82
|
+
startLiveRunCompletion(startParams: StartCursorLiveRunCompletionParams): CursorLiveRunCompletion {
|
|
83
|
+
const { runnerParams } = this.params;
|
|
84
|
+
const sdkEventDebug = this.params.sdkEventDebug();
|
|
85
|
+
const { send, prepared, modelId, discardIncompleteTools } = startParams;
|
|
86
|
+
const { run, cursorAgentMessageOffset } = send;
|
|
87
|
+
if (prepared.runtime.kind !== "live") throw new Error("startLiveRunCompletion requires a live run");
|
|
88
|
+
const { liveRun } = prepared.runtime;
|
|
89
|
+
const waitCompletion = awaitFinalizeCursorRunOutcome({
|
|
90
|
+
run,
|
|
91
|
+
prepared,
|
|
92
|
+
cursorAgentMessageOffset,
|
|
93
|
+
modelId,
|
|
94
|
+
signal: runnerParams.options?.signal,
|
|
95
|
+
runResultFallback: run.result,
|
|
96
|
+
resolvedApiKey: this.params.resolvedApiKey(),
|
|
97
|
+
optionsApiKey: runnerParams.options?.apiKey,
|
|
98
|
+
sdkEventDebug,
|
|
99
|
+
cacheContextWindow: true,
|
|
100
|
+
contextWindowAgentId: liveRun.agent.agentId,
|
|
101
|
+
})
|
|
102
|
+
.then(async (outcome) => {
|
|
103
|
+
applyLiveRunOutcome(outcome, prepared, runnerParams.context);
|
|
104
|
+
})
|
|
105
|
+
.catch(async (error: unknown) => {
|
|
106
|
+
sdkEventDebug?.recordWaitResult({ status: "error", error: String(error) });
|
|
107
|
+
sdkEventDebug?.recordError("run_wait", error);
|
|
108
|
+
discardIncompleteTools({ status: "error" });
|
|
109
|
+
await sdkEventDebug?.captureRunArtifacts(run);
|
|
110
|
+
if (liveRun.disposed) return;
|
|
111
|
+
cursorLiveRuns.markError(
|
|
112
|
+
liveRun,
|
|
113
|
+
sanitizeCursorProviderError(error, this.params.resolvedApiKey() ?? runnerParams.options?.apiKey),
|
|
114
|
+
);
|
|
115
|
+
});
|
|
116
|
+
return { waitCompletion, prepared };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async applyTerminalEvent(event: CursorTurnTerminalEvent): Promise<void> {
|
|
120
|
+
if (this.terminalApplied) return;
|
|
121
|
+
if (event.kind === "direct") {
|
|
122
|
+
await this.applyDirectOutcome(event.prepared, event.outcome);
|
|
123
|
+
this.terminalApplied = true;
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
await this.applyErrorOutcome(event.prepared, event.error);
|
|
127
|
+
this.terminalApplied = true;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async cleanup(
|
|
131
|
+
prepared: CursorProviderTurnPrepareResult | undefined,
|
|
132
|
+
sendResult: CursorProviderTurnSendResult | undefined,
|
|
133
|
+
liveCompletion: CursorLiveRunCompletion | undefined,
|
|
134
|
+
): Promise<void> {
|
|
135
|
+
this.safeCleanup(() => prepared?.restoreCursorSdkOutputFilter());
|
|
136
|
+
const abortRegistration = sendResult?.abortRegistration;
|
|
137
|
+
if (abortRegistration) {
|
|
138
|
+
this.safeCleanup(() => abortRegistration.signal.removeEventListener("abort", abortRegistration.listener));
|
|
139
|
+
}
|
|
140
|
+
this.params.runnerParams.sdkEventDebugRef.current = undefined;
|
|
141
|
+
if (liveCompletion) {
|
|
142
|
+
this.safeCleanup(() => liveCompletion.prepared.sessionAgentLease.trackRunCompletion(liveCompletion.waitCompletion));
|
|
143
|
+
void liveCompletion.waitCompletion
|
|
144
|
+
.finally(async () => {
|
|
145
|
+
await this.finalizeSdkEventDebugBestEffort();
|
|
146
|
+
this.safeCleanup(() => this.params.sdkAbortErrorSuppression.dispose());
|
|
147
|
+
})
|
|
148
|
+
.catch(() => {});
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
await this.finalizeSdkEventDebugBestEffort();
|
|
152
|
+
this.safeCleanup(() => this.params.sdkAbortErrorSuppression.dispose());
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private async applyDirectOutcome(
|
|
156
|
+
prepared: CursorProviderTurnPrepareResult,
|
|
157
|
+
outcome: CursorRunOutcome,
|
|
158
|
+
): Promise<void> {
|
|
159
|
+
const { stream, partial, model, context } = this.params.runnerParams;
|
|
160
|
+
prepared.runtime.turnCoordinator.closeTraceBlock();
|
|
161
|
+
switch (classifyCursorRunEmission(outcome)) {
|
|
162
|
+
case "cancelled":
|
|
163
|
+
await abandonSessionCursorAgent(prepared.sessionAgentScopeKey);
|
|
164
|
+
this.pushTerminalError(partial, "aborted", getCursorRunAbortMessage(outcome));
|
|
165
|
+
break;
|
|
166
|
+
case "failed":
|
|
167
|
+
await abandonSessionCursorAgent(prepared.sessionAgentScopeKey);
|
|
168
|
+
this.pushTerminalError(partial, "error", outcome.kind === "error" ? outcome.errorMessage : "Cursor SDK run failed.");
|
|
169
|
+
break;
|
|
170
|
+
case "finished":
|
|
171
|
+
prepared.sessionAgentLease.commitSend(context, prepared.meta.bootstrap);
|
|
172
|
+
prepared.runtime.turnCoordinator.flushText(
|
|
173
|
+
outcome.kind === "finished" && hasUsableText(outcome.finalText) ? [outcome.finalText] : [],
|
|
174
|
+
);
|
|
175
|
+
applyCursorApproximateUsage(partial, model, context, prepared.meta.promptInputTokens);
|
|
176
|
+
stream.push({ type: "done", reason: "stop", message: partial });
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private async applyErrorOutcome(prepared: CursorProviderTurnPrepareResult | undefined, error: unknown): Promise<void> {
|
|
182
|
+
this.params.sdkEventDebug()?.recordError("provider_stream", error);
|
|
183
|
+
prepared?.runtime.turnCoordinator.discardIncompleteStartedToolCalls(
|
|
184
|
+
buildIncompleteCursorToolRunOutcome({
|
|
185
|
+
status: error instanceof CursorLiveRunAbortError ? "cancelled" : "error",
|
|
186
|
+
signalAborted: error instanceof CursorLiveRunAbortError,
|
|
187
|
+
}),
|
|
188
|
+
);
|
|
189
|
+
const activeLiveRun = prepared?.runtime.liveRun;
|
|
190
|
+
if (activeLiveRun && !activeLiveRun.disposed) {
|
|
191
|
+
await cursorLiveRuns.release(activeLiveRun);
|
|
192
|
+
} else {
|
|
193
|
+
await abandonSessionCursorAgent(prepared?.sessionAgentScopeKey);
|
|
194
|
+
}
|
|
195
|
+
if (error instanceof CursorLiveRunAbortError) {
|
|
196
|
+
this.params.sdkAbortErrorSuppression.suppressAbortErrors();
|
|
197
|
+
this.pushTerminalError(this.params.runnerParams.partial, "aborted", this.abortMessage());
|
|
198
|
+
} else {
|
|
199
|
+
this.pushTerminalError(
|
|
200
|
+
this.params.runnerParams.partial,
|
|
201
|
+
"error",
|
|
202
|
+
sanitizeCursorProviderError(error, this.params.resolvedApiKey() ?? this.params.runnerParams.options?.apiKey),
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private pushTerminalError(partial: AssistantMessage, reason: "error" | "aborted", message: string): void {
|
|
208
|
+
partial.stopReason = reason;
|
|
209
|
+
partial.errorMessage = message;
|
|
210
|
+
this.params.runnerParams.stream.push({ type: "error", reason, error: partial });
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private abortMessage(): string {
|
|
214
|
+
return formatCursorSdkAbortMessage(
|
|
215
|
+
resolveCursorSdkAbortCause({ signalAborted: this.params.runnerParams.options?.signal?.aborted }),
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private safeCleanup(cleanup: () => void): void {
|
|
220
|
+
try {
|
|
221
|
+
cleanup();
|
|
222
|
+
} catch {
|
|
223
|
+
// Cleanup must not reclassify an already-emitted provider turn.
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private async finalizeSdkEventDebugBestEffort(): Promise<void> {
|
|
228
|
+
try {
|
|
229
|
+
this.params.sdkEventDebug()?.recordFinalPartial(this.params.runnerParams.partial);
|
|
230
|
+
await this.params.sdkEventDebug()?.finalize();
|
|
231
|
+
} catch {
|
|
232
|
+
// Debug artifact IO is best-effort and must not emit a second terminal event.
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import type { RunResult } from "@cursor/sdk";
|
|
2
|
+
import { selectCursorFinalText } from "./cursor-run-final-text.js";
|
|
3
|
+
import {
|
|
4
|
+
formatCursorSdkAbortMessage,
|
|
5
|
+
formatCursorSdkRunFailureDetail,
|
|
6
|
+
resolveCursorSdkAbortCause,
|
|
7
|
+
sanitizeCursorProviderError,
|
|
8
|
+
} from "./cursor-provider-errors.js";
|
|
9
|
+
import { hasUsableText } from "./cursor-record-utils.js";
|
|
10
|
+
import {
|
|
11
|
+
buildIncompleteCursorToolRunOutcome,
|
|
12
|
+
type IncompleteCursorToolRunOutcome,
|
|
13
|
+
type IncompleteCursorToolRunOutcomeInput,
|
|
14
|
+
} from "./cursor-incomplete-tool-visibility.js";
|
|
15
|
+
|
|
16
|
+
/** Unified SDK wait() facts consumed by live and direct emission strategies. */
|
|
17
|
+
export type CursorRunOutcome =
|
|
18
|
+
| {
|
|
19
|
+
kind: "finished";
|
|
20
|
+
waitResult: RunResult;
|
|
21
|
+
finalText: string;
|
|
22
|
+
incompleteTools: IncompleteCursorToolRunOutcome;
|
|
23
|
+
assistantTextProduced: boolean;
|
|
24
|
+
}
|
|
25
|
+
| {
|
|
26
|
+
kind: "cancelled";
|
|
27
|
+
waitResult: RunResult;
|
|
28
|
+
incompleteTools: IncompleteCursorToolRunOutcome;
|
|
29
|
+
abortMessage: string;
|
|
30
|
+
}
|
|
31
|
+
| {
|
|
32
|
+
kind: "error";
|
|
33
|
+
waitResult: RunResult;
|
|
34
|
+
incompleteTools: IncompleteCursorToolRunOutcome;
|
|
35
|
+
errorMessage: string;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export interface ResolveCursorRunOutcomeParams {
|
|
39
|
+
waitResult: RunResult;
|
|
40
|
+
signalAborted?: boolean;
|
|
41
|
+
textDeltas: readonly string[];
|
|
42
|
+
emittedText: string;
|
|
43
|
+
planTextCandidate?: string;
|
|
44
|
+
selectFinalTextOptions?: { allowPartialPrefix?: boolean };
|
|
45
|
+
runResultFallback?: string;
|
|
46
|
+
resolvedApiKey?: string;
|
|
47
|
+
optionsApiKey?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function hasCursorAssistantText(
|
|
51
|
+
resultText: unknown,
|
|
52
|
+
textDeltas: readonly string[],
|
|
53
|
+
fallbackText?: string,
|
|
54
|
+
): boolean {
|
|
55
|
+
return (
|
|
56
|
+
hasUsableText(typeof resultText === "string" ? resultText : undefined) ||
|
|
57
|
+
hasUsableText(textDeltas.join("")) ||
|
|
58
|
+
hasUsableText(fallbackText)
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function isCursorRunFinishedSuccessfully(outcome: CursorRunOutcome): boolean {
|
|
63
|
+
return outcome.kind === "finished";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function buildCursorRunAbortMessage(signalAborted: boolean | undefined, sdkStatusCancelled: boolean): string {
|
|
67
|
+
return formatCursorSdkAbortMessage(
|
|
68
|
+
resolveCursorSdkAbortCause({
|
|
69
|
+
signalAborted,
|
|
70
|
+
sdkStatusCancelled,
|
|
71
|
+
}),
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function resolveCursorRunOutcome(params: ResolveCursorRunOutcomeParams): CursorRunOutcome {
|
|
76
|
+
const { waitResult, signalAborted } = params;
|
|
77
|
+
const sdkCancelled = waitResult.status === "cancelled";
|
|
78
|
+
const callerAborted = signalAborted === true;
|
|
79
|
+
|
|
80
|
+
if (callerAborted || sdkCancelled) {
|
|
81
|
+
const incompleteTools = buildIncompleteCursorToolRunOutcome({
|
|
82
|
+
status: "cancelled",
|
|
83
|
+
signalAborted: callerAborted,
|
|
84
|
+
assistantTextProduced: false,
|
|
85
|
+
});
|
|
86
|
+
return {
|
|
87
|
+
kind: "cancelled",
|
|
88
|
+
waitResult,
|
|
89
|
+
incompleteTools,
|
|
90
|
+
abortMessage: buildCursorRunAbortMessage(callerAborted, sdkCancelled),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (waitResult.status === "error") {
|
|
95
|
+
const failureDetail = formatCursorSdkRunFailureDetail(waitResult, params.runResultFallback);
|
|
96
|
+
return {
|
|
97
|
+
kind: "error",
|
|
98
|
+
waitResult,
|
|
99
|
+
incompleteTools: buildIncompleteCursorToolRunOutcome({
|
|
100
|
+
status: "error",
|
|
101
|
+
assistantTextProduced: false,
|
|
102
|
+
}),
|
|
103
|
+
errorMessage: sanitizeCursorProviderError(failureDetail, params.resolvedApiKey ?? params.optionsApiKey),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const assistantTextProduced = hasCursorAssistantText(
|
|
108
|
+
waitResult.result,
|
|
109
|
+
params.textDeltas,
|
|
110
|
+
params.planTextCandidate,
|
|
111
|
+
);
|
|
112
|
+
const incompleteTools = buildIncompleteCursorToolRunOutcome({
|
|
113
|
+
status: waitResult.status,
|
|
114
|
+
assistantTextProduced,
|
|
115
|
+
});
|
|
116
|
+
const finalText = selectCursorFinalText(
|
|
117
|
+
waitResult.result,
|
|
118
|
+
params.textDeltas,
|
|
119
|
+
params.emittedText,
|
|
120
|
+
params.planTextCandidate,
|
|
121
|
+
params.selectFinalTextOptions,
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
kind: "finished",
|
|
126
|
+
waitResult,
|
|
127
|
+
finalText,
|
|
128
|
+
incompleteTools,
|
|
129
|
+
assistantTextProduced,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export type CursorRunEmission = "finished" | "cancelled" | "failed";
|
|
134
|
+
|
|
135
|
+
export function classifyCursorRunEmission(outcome: CursorRunOutcome): CursorRunEmission {
|
|
136
|
+
switch (outcome.kind) {
|
|
137
|
+
case "finished":
|
|
138
|
+
return "finished";
|
|
139
|
+
case "cancelled":
|
|
140
|
+
return "cancelled";
|
|
141
|
+
case "error":
|
|
142
|
+
return "failed";
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function getCursorRunAbortMessage(outcome: CursorRunOutcome): string {
|
|
147
|
+
if (outcome.kind === "cancelled") return outcome.abortMessage;
|
|
148
|
+
return buildCursorRunAbortMessage(false, outcome.waitResult.status === "cancelled");
|
|
149
|
+
}
|