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,420 @@
|
|
|
1
|
+
import { scheduler } from "node:timers/promises";
|
|
2
|
+
import { CursorLiveRunAbortError, createCursorLiveRunCoordinator, hasTrailingUserMessagesAfterToolResults, } from "./cursor-live-run-coordinator.js";
|
|
3
|
+
import { deleteCursorNativeToolDisplay, recordCursorNativeToolDisplay, } from "./cursor-native-tool-display-state.js";
|
|
4
|
+
import { resetSessionCursorAgent } from "./cursor-session-agent.js";
|
|
5
|
+
import { applyCursorUsage } from "./cursor-usage-accounting.js";
|
|
6
|
+
import { CURSOR_TEXT_MESSAGE_SEPARATOR, CursorPartialContentEmitter } from "./cursor-partial-content-emitter.js";
|
|
7
|
+
import { emitDisplayOnlyTraceBlock } from "./cursor-display-only-trace.js";
|
|
8
|
+
import { trimCurrentTurnAlreadyEmittedCursorText } from "./cursor-run-final-text.js";
|
|
9
|
+
import { formatCursorSdkAbortMessage, resolveCursorSdkAbortCause } from "./cursor-provider-errors.js";
|
|
10
|
+
import { formatInactiveCursorReplayTrace } from "./cursor-native-replay-trace.js";
|
|
11
|
+
import { partitionNativeToolsByActiveContext } from "./cursor-native-replay-routing.js";
|
|
12
|
+
export const DEFAULT_CURSOR_NATIVE_REPLAY_IDLE_DISPOSE_MS = 5 * 60 * 1000;
|
|
13
|
+
const CURSOR_NATIVE_REPLAY_TOOL_ID_PATTERN = /^(cursor-replay-\d+-\d+)-tool-\d+$/;
|
|
14
|
+
let cursorNativeReplayIdleDisposeMs = DEFAULT_CURSOR_NATIVE_REPLAY_IDLE_DISPOSE_MS;
|
|
15
|
+
let cursorNativeReplayCounter = 0;
|
|
16
|
+
export async function abandonSessionCursorAgent(scopeKey) {
|
|
17
|
+
if (!scopeKey)
|
|
18
|
+
return;
|
|
19
|
+
await resetSessionCursorAgent(scopeKey);
|
|
20
|
+
}
|
|
21
|
+
export const cursorLiveRuns = createCursorLiveRunCoordinator({
|
|
22
|
+
getIdleDisposeMs: () => cursorNativeReplayIdleDisposeMs,
|
|
23
|
+
deleteNativeToolDisplay: deleteCursorNativeToolDisplay,
|
|
24
|
+
abandonSessionAgent: (scopeKey) => abandonSessionCursorAgent(scopeKey),
|
|
25
|
+
});
|
|
26
|
+
export function createCursorNativeReplayId() {
|
|
27
|
+
cursorNativeReplayCounter += 1;
|
|
28
|
+
return `cursor-replay-${Date.now()}-${cursorNativeReplayCounter}`;
|
|
29
|
+
}
|
|
30
|
+
function getCursorNativeReplayIdFromToolCallId(toolCallId) {
|
|
31
|
+
return CURSOR_NATIVE_REPLAY_TOOL_ID_PATTERN.exec(toolCallId)?.[1];
|
|
32
|
+
}
|
|
33
|
+
export function getPendingCursorLiveRun(context) {
|
|
34
|
+
return cursorLiveRuns.getPendingFromContext(context, getCursorNativeReplayIdFromToolCallId);
|
|
35
|
+
}
|
|
36
|
+
export function getActiveCursorLiveRunForCurrentScope() {
|
|
37
|
+
return cursorLiveRuns.getActiveForScope();
|
|
38
|
+
}
|
|
39
|
+
function splitTextIntoReplayDeltas(text) {
|
|
40
|
+
const deltas = [];
|
|
41
|
+
let remaining = text;
|
|
42
|
+
while (remaining.length > 0) {
|
|
43
|
+
if (remaining.length <= 96) {
|
|
44
|
+
deltas.push(remaining);
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
const boundary = Math.max(48, remaining.lastIndexOf(" ", 96));
|
|
48
|
+
deltas.push(remaining.slice(0, boundary));
|
|
49
|
+
remaining = remaining.slice(boundary);
|
|
50
|
+
}
|
|
51
|
+
return deltas;
|
|
52
|
+
}
|
|
53
|
+
async function emitTextDeltas(emitter, deltas) {
|
|
54
|
+
for (const delta of deltas) {
|
|
55
|
+
emitter.appendTextDelta(delta);
|
|
56
|
+
await Promise.resolve();
|
|
57
|
+
}
|
|
58
|
+
return emitter.closeText();
|
|
59
|
+
}
|
|
60
|
+
export async function settleCursorLiveToolBatch(run) {
|
|
61
|
+
const eventType = cursorLiveRuns.peekEvent(run)?.type;
|
|
62
|
+
if (eventType !== "tool" && eventType !== "bridge-tool")
|
|
63
|
+
return;
|
|
64
|
+
await scheduler.wait(75);
|
|
65
|
+
}
|
|
66
|
+
async function waitForCursorLiveSdkTurnEnded(run, signal) {
|
|
67
|
+
const deadline = Date.now() + 125;
|
|
68
|
+
while (!cursorLiveRuns.hasSdkTurnEnded(run) && !run.done && !run.disposed && !run.cancelled && !run.errorMessage) {
|
|
69
|
+
const remainingMs = deadline - Date.now();
|
|
70
|
+
if (remainingMs <= 0)
|
|
71
|
+
return false;
|
|
72
|
+
await scheduler.wait(Math.min(25, remainingMs));
|
|
73
|
+
if (signal?.aborted)
|
|
74
|
+
throw new CursorLiveRunAbortError();
|
|
75
|
+
}
|
|
76
|
+
return cursorLiveRuns.hasSdkTurnEnded(run);
|
|
77
|
+
}
|
|
78
|
+
export function flushPendingCursorLiveRunTraceEventsToStream(stream, partial, run, options) {
|
|
79
|
+
if (run.disposed)
|
|
80
|
+
return;
|
|
81
|
+
const turn = {
|
|
82
|
+
emitter: options?.emitter ?? new CursorPartialContentEmitter(stream, partial, -1, true),
|
|
83
|
+
emittedText: "",
|
|
84
|
+
};
|
|
85
|
+
while (true) {
|
|
86
|
+
const event = cursorLiveRuns.peekEvent(run);
|
|
87
|
+
if (!event || event.type === "tool" || event.type === "bridge-tool")
|
|
88
|
+
break;
|
|
89
|
+
cursorLiveRuns.shiftEvent(run);
|
|
90
|
+
emitCursorLiveQueuedEvent(turn, event, run);
|
|
91
|
+
}
|
|
92
|
+
if (options?.includeTracesBehindQueuedTools && run.pendingEvents.length > 0) {
|
|
93
|
+
const preserved = [];
|
|
94
|
+
for (const event of run.pendingEvents) {
|
|
95
|
+
if (event.type === "tool" || event.type === "bridge-tool") {
|
|
96
|
+
preserved.push(event);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
emitCursorLiveQueuedEvent(turn, event, run);
|
|
100
|
+
}
|
|
101
|
+
run.pendingEvents = preserved;
|
|
102
|
+
}
|
|
103
|
+
turn.emitter.closeAll();
|
|
104
|
+
}
|
|
105
|
+
function emitCursorLiveQueuedEvent(turn, event, run) {
|
|
106
|
+
if (event.type === "thinking-delta") {
|
|
107
|
+
turn.emitter.appendThinkingDelta(event.text);
|
|
108
|
+
}
|
|
109
|
+
else if (event.type === "thinking-completed") {
|
|
110
|
+
turn.emitter.closeThinking();
|
|
111
|
+
}
|
|
112
|
+
else if (event.type === "text-completed") {
|
|
113
|
+
turn.emitter.completeTextMessage();
|
|
114
|
+
// Logical separators also keep suffix/prefix dedup aware of SDK messages,
|
|
115
|
+
// including boundaries queued across Pi tool-use turns.
|
|
116
|
+
if (turn.emittedText)
|
|
117
|
+
turn.emittedText += CURSOR_TEXT_MESSAGE_SEPARATOR;
|
|
118
|
+
if (run?.emittedText)
|
|
119
|
+
run.emittedText += CURSOR_TEXT_MESSAGE_SEPARATOR;
|
|
120
|
+
}
|
|
121
|
+
else if (event.type === "text-delta") {
|
|
122
|
+
turn.emittedText += event.text;
|
|
123
|
+
if (run)
|
|
124
|
+
run.emittedText += event.text;
|
|
125
|
+
turn.emitter.appendTextDelta(event.text);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function emitCursorNativeToolUseTurn(stream, partial, model, context, run, toolResultInputTokens, tools, debugRecorder) {
|
|
129
|
+
const shouldTerminate = run.done && !run.finalText?.trim() && !cursorLiveRuns.peekEvent(run);
|
|
130
|
+
for (const tool of tools) {
|
|
131
|
+
const contentIndex = partial.content.length;
|
|
132
|
+
partial.content.push({
|
|
133
|
+
type: "toolCall",
|
|
134
|
+
id: tool.id,
|
|
135
|
+
name: tool.toolName,
|
|
136
|
+
arguments: tool.args,
|
|
137
|
+
});
|
|
138
|
+
stream.push({ type: "toolcall_start", contentIndex, partial });
|
|
139
|
+
stream.push({ type: "toolcall_delta", contentIndex, delta: JSON.stringify(tool.args), partial });
|
|
140
|
+
const block = partial.content[contentIndex];
|
|
141
|
+
if (block.type === "toolCall")
|
|
142
|
+
stream.push({ type: "toolcall_end", contentIndex, toolCall: block, partial });
|
|
143
|
+
if (recordCursorNativeToolDisplay({ ...tool, terminate: shouldTerminate })) {
|
|
144
|
+
run.recordedToolDisplayIds.push(tool.id);
|
|
145
|
+
debugRecorder?.recordDrainEvent("native_tool_display_recorded", {
|
|
146
|
+
toolId: tool.id,
|
|
147
|
+
toolName: tool.toolName,
|
|
148
|
+
terminate: shouldTerminate,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
applyCursorUsage(partial, model, context, cursorLiveRuns.takeTurnInputTokens(run, toolResultInputTokens), {
|
|
153
|
+
runtime: "local",
|
|
154
|
+
turn: cursorLiveRuns.takeSdkTurnUsage(run),
|
|
155
|
+
});
|
|
156
|
+
partial.stopReason = "toolUse";
|
|
157
|
+
stream.push({ type: "done", reason: "toolUse", message: partial });
|
|
158
|
+
cursorLiveRuns.requestIdleDispose(run);
|
|
159
|
+
}
|
|
160
|
+
function emitInactiveCursorReplayTrace(turn, tools, debugRecorder) {
|
|
161
|
+
if (tools.length === 0)
|
|
162
|
+
return;
|
|
163
|
+
for (const tool of tools) {
|
|
164
|
+
const traceText = formatInactiveCursorReplayTrace(tool);
|
|
165
|
+
debugRecorder?.recordDrainEvent("inactive_replay_trace", {
|
|
166
|
+
toolId: tool.id,
|
|
167
|
+
toolName: tool.toolName,
|
|
168
|
+
traceText,
|
|
169
|
+
});
|
|
170
|
+
turn.emitter.appendThinkingBlock(traceText);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function emitCursorBridgeToolUseTurn(stream, partial, model, context, run, toolResultInputTokens, requests) {
|
|
174
|
+
for (const request of requests) {
|
|
175
|
+
const contentIndex = partial.content.length;
|
|
176
|
+
partial.content.push({
|
|
177
|
+
type: "toolCall",
|
|
178
|
+
id: request.piToolCallId,
|
|
179
|
+
name: request.piToolName,
|
|
180
|
+
arguments: request.args,
|
|
181
|
+
});
|
|
182
|
+
stream.push({ type: "toolcall_start", contentIndex, partial });
|
|
183
|
+
stream.push({ type: "toolcall_delta", contentIndex, delta: JSON.stringify(request.args), partial });
|
|
184
|
+
const block = partial.content[contentIndex];
|
|
185
|
+
if (block.type === "toolCall")
|
|
186
|
+
stream.push({ type: "toolcall_end", contentIndex, toolCall: block, partial });
|
|
187
|
+
}
|
|
188
|
+
applyCursorUsage(partial, model, context, cursorLiveRuns.takeTurnInputTokens(run, toolResultInputTokens), {
|
|
189
|
+
runtime: "local",
|
|
190
|
+
turn: cursorLiveRuns.takeSdkTurnUsage(run),
|
|
191
|
+
});
|
|
192
|
+
partial.stopReason = "toolUse";
|
|
193
|
+
stream.push({ type: "done", reason: "toolUse", message: partial });
|
|
194
|
+
cursorLiveRuns.requestIdleDispose(run);
|
|
195
|
+
}
|
|
196
|
+
async function emitCursorLiveRunPendingToolUseTurn(turn, stream, partial, model, context, run, toolResultInputTokens, options) {
|
|
197
|
+
const debugRecorder = options.debugRecorder ?? run.debugRecorder;
|
|
198
|
+
const eventType = cursorLiveRuns.peekEvent(run)?.type;
|
|
199
|
+
if (eventType !== "tool" && eventType !== "bridge-tool")
|
|
200
|
+
return undefined;
|
|
201
|
+
await settleCursorLiveToolBatch(run);
|
|
202
|
+
const sdkTurnEnded = await waitForCursorLiveSdkTurnEnded(run, options.signal);
|
|
203
|
+
if (options.signal?.aborted)
|
|
204
|
+
throw new CursorLiveRunAbortError();
|
|
205
|
+
if (eventType === "tool") {
|
|
206
|
+
const { active, inactive } = partitionNativeToolsByActiveContext(context, cursorLiveRuns.collectNativeToolBatch(run));
|
|
207
|
+
if (options.mode === "emit")
|
|
208
|
+
emitInactiveCursorReplayTrace(turn, inactive, debugRecorder);
|
|
209
|
+
if (active.length === 0) {
|
|
210
|
+
// Inactive-only batch: trace was emitted above; do not emit toolUse.
|
|
211
|
+
return "handled";
|
|
212
|
+
}
|
|
213
|
+
if (!sdkTurnEnded)
|
|
214
|
+
cursorLiveRuns.ignoreFutureSdkTurnUsage(run);
|
|
215
|
+
if (options.mode === "emit")
|
|
216
|
+
turn.emitter.closeAll();
|
|
217
|
+
emitCursorNativeToolUseTurn(stream, partial, model, context, run, toolResultInputTokens, active, debugRecorder);
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
const requests = cursorLiveRuns.collectBridgeToolBatch(run);
|
|
221
|
+
if (requests.length === 0)
|
|
222
|
+
return "handled";
|
|
223
|
+
if (!sdkTurnEnded)
|
|
224
|
+
cursorLiveRuns.ignoreFutureSdkTurnUsage(run);
|
|
225
|
+
if (options.mode === "emit")
|
|
226
|
+
turn.emitter.closeAll();
|
|
227
|
+
emitCursorBridgeToolUseTurn(stream, partial, model, context, run, toolResultInputTokens, requests);
|
|
228
|
+
}
|
|
229
|
+
return "tool_use";
|
|
230
|
+
}
|
|
231
|
+
export async function drainCursorLiveRunTurn(stream, partial, model, context, run, toolResultInputTokens, options) {
|
|
232
|
+
const debugRecorder = options.debugRecorder ?? run.debugRecorder;
|
|
233
|
+
debugRecorder?.recordDrainEvent("turn_start", {
|
|
234
|
+
mode: options.mode,
|
|
235
|
+
runId: run.id,
|
|
236
|
+
pendingEventCount: run.pendingEvents.length,
|
|
237
|
+
done: run.done,
|
|
238
|
+
});
|
|
239
|
+
let outcome;
|
|
240
|
+
let outcomeDetails = {};
|
|
241
|
+
const turn = {
|
|
242
|
+
emitter: options.emitter ?? new CursorPartialContentEmitter(stream, partial, -1, true),
|
|
243
|
+
emittedText: "",
|
|
244
|
+
};
|
|
245
|
+
try {
|
|
246
|
+
while (true) {
|
|
247
|
+
if (options.mode === "chain_user_input" && cursorLiveRuns.isReady(run)) {
|
|
248
|
+
await cursorLiveRuns.release(run);
|
|
249
|
+
outcome = "chain_user_input";
|
|
250
|
+
return outcome;
|
|
251
|
+
}
|
|
252
|
+
while (cursorLiveRuns.peekEvent(run)) {
|
|
253
|
+
const toolUse = await emitCursorLiveRunPendingToolUseTurn(turn, stream, partial, model, context, run, toolResultInputTokens, options);
|
|
254
|
+
if (toolUse === "tool_use") {
|
|
255
|
+
outcome = "tool_use";
|
|
256
|
+
return outcome;
|
|
257
|
+
}
|
|
258
|
+
if (toolUse === "handled")
|
|
259
|
+
continue;
|
|
260
|
+
const event = cursorLiveRuns.shiftEvent(run);
|
|
261
|
+
if (!event || event.type === "tool" || event.type === "bridge-tool")
|
|
262
|
+
continue;
|
|
263
|
+
if (options.mode === "emit")
|
|
264
|
+
emitCursorLiveQueuedEvent(turn, event, run);
|
|
265
|
+
}
|
|
266
|
+
if (run.disposed) {
|
|
267
|
+
partial.stopReason = "aborted";
|
|
268
|
+
partial.errorMessage = formatCursorSdkAbortMessage(resolveCursorSdkAbortCause({ liveRunDisposed: true }));
|
|
269
|
+
stream.push({ type: "error", reason: "aborted", error: partial });
|
|
270
|
+
outcome = "aborted";
|
|
271
|
+
outcomeDetails = { reason: "disposed" };
|
|
272
|
+
return outcome;
|
|
273
|
+
}
|
|
274
|
+
if (run.cancelled) {
|
|
275
|
+
partial.stopReason = "aborted";
|
|
276
|
+
if (run.abortMessage)
|
|
277
|
+
partial.errorMessage = run.abortMessage;
|
|
278
|
+
stream.push({ type: "error", reason: "aborted", error: partial });
|
|
279
|
+
await cursorLiveRuns.release(run);
|
|
280
|
+
outcome = "aborted";
|
|
281
|
+
outcomeDetails = { reason: "cancelled" };
|
|
282
|
+
return outcome;
|
|
283
|
+
}
|
|
284
|
+
if (run.errorMessage) {
|
|
285
|
+
partial.stopReason = "error";
|
|
286
|
+
partial.errorMessage = run.errorMessage;
|
|
287
|
+
stream.push({ type: "error", reason: "error", error: partial });
|
|
288
|
+
await cursorLiveRuns.release(run);
|
|
289
|
+
outcome = "error";
|
|
290
|
+
return outcome;
|
|
291
|
+
}
|
|
292
|
+
if (run.done) {
|
|
293
|
+
if (options.mode === "chain_user_input") {
|
|
294
|
+
await cursorLiveRuns.release(run);
|
|
295
|
+
outcome = "chain_user_input";
|
|
296
|
+
outcomeDetails = { reason: "run_done" };
|
|
297
|
+
return outcome;
|
|
298
|
+
}
|
|
299
|
+
turn.emitter.closeAll();
|
|
300
|
+
const finalText = trimCurrentTurnAlreadyEmittedCursorText(run.finalText ?? run.textDeltas.join(""), turn.emittedText, run.emittedText);
|
|
301
|
+
if (finalText) {
|
|
302
|
+
await emitTextDeltas(turn.emitter, splitTextIntoReplayDeltas(finalText));
|
|
303
|
+
}
|
|
304
|
+
applyCursorUsage(partial, model, context, cursorLiveRuns.takeTurnInputTokens(run, toolResultInputTokens), {
|
|
305
|
+
runtime: "local",
|
|
306
|
+
turn: cursorLiveRuns.takeSdkTurnUsage(run),
|
|
307
|
+
billed: run.billedTurnUsage,
|
|
308
|
+
});
|
|
309
|
+
if (run.resumeNotice) {
|
|
310
|
+
emitDisplayOnlyTraceBlock(stream, partial, run.resumeNotice);
|
|
311
|
+
run.resumeNotice = undefined;
|
|
312
|
+
}
|
|
313
|
+
partial.stopReason = "stop";
|
|
314
|
+
stream.push({ type: "done", reason: "stop", message: partial });
|
|
315
|
+
await cursorLiveRuns.release(run);
|
|
316
|
+
outcome = "stop";
|
|
317
|
+
outcomeDetails = { finalTextLength: finalText.length };
|
|
318
|
+
return outcome;
|
|
319
|
+
}
|
|
320
|
+
await cursorLiveRuns.waitForProgress(run, options.signal);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
catch (error) {
|
|
324
|
+
if (!outcome) {
|
|
325
|
+
if (error instanceof CursorLiveRunAbortError) {
|
|
326
|
+
outcome = "aborted";
|
|
327
|
+
outcomeDetails = { reason: "signal_aborted" };
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
outcome = "error";
|
|
331
|
+
outcomeDetails = {
|
|
332
|
+
reason: "drain_error",
|
|
333
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
throw error;
|
|
338
|
+
}
|
|
339
|
+
finally {
|
|
340
|
+
debugRecorder?.recordDrainEvent("turn_end", {
|
|
341
|
+
outcome: outcome ?? "error",
|
|
342
|
+
runId: run.id,
|
|
343
|
+
pendingEventCount: run.pendingEvents.length,
|
|
344
|
+
done: run.done,
|
|
345
|
+
...outcomeDetails,
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
export async function drainExistingCursorLiveRunBeforeSend(stream, partial, model, context, signal, turnDebugRecorder) {
|
|
350
|
+
turnDebugRecorder?.recordDrainEvent("pre_send_start", {});
|
|
351
|
+
while (true) {
|
|
352
|
+
const run = getPendingCursorLiveRun(context) ?? getActiveCursorLiveRunForCurrentScope();
|
|
353
|
+
if (!run || run.disposed) {
|
|
354
|
+
turnDebugRecorder?.recordDrainEvent("pre_send_end", { outcome: "continue_send", reason: "no_pending_run" });
|
|
355
|
+
return "continue_send";
|
|
356
|
+
}
|
|
357
|
+
try {
|
|
358
|
+
const outcome = await cursorLiveRuns.withRunLease(run, signal, async () => {
|
|
359
|
+
if (run.disposed)
|
|
360
|
+
return "continue_send";
|
|
361
|
+
const consumed = cursorLiveRuns.consumeToolResults(run, context, getCursorNativeReplayIdFromToolCallId);
|
|
362
|
+
await run.bridgeRun?.resolveToolResults(consumed.toolResults);
|
|
363
|
+
const shouldChainUserInput = run.chainUserInputAfterCompletion || hasTrailingUserMessagesAfterToolResults(context);
|
|
364
|
+
if (shouldChainUserInput)
|
|
365
|
+
run.chainUserInputAfterCompletion = true;
|
|
366
|
+
while (!cursorLiveRuns.isReady(run)) {
|
|
367
|
+
await cursorLiveRuns.waitForProgress(run, signal);
|
|
368
|
+
}
|
|
369
|
+
if (run.disposed)
|
|
370
|
+
return "continue_send";
|
|
371
|
+
const drainOutcome = await drainCursorLiveRunTurn(stream, partial, model, context, run, consumed.toolResultInputTokens, {
|
|
372
|
+
mode: shouldChainUserInput ? "chain_user_input" : "emit",
|
|
373
|
+
signal,
|
|
374
|
+
debugRecorder: turnDebugRecorder,
|
|
375
|
+
});
|
|
376
|
+
const mapped = drainOutcome === "chain_user_input" ? "continue_send" : "stream_ended";
|
|
377
|
+
turnDebugRecorder?.recordDrainEvent("pre_send_iteration", {
|
|
378
|
+
runId: run.id,
|
|
379
|
+
drainOutcome,
|
|
380
|
+
outcome: mapped,
|
|
381
|
+
shouldChainUserInput,
|
|
382
|
+
});
|
|
383
|
+
return mapped;
|
|
384
|
+
});
|
|
385
|
+
if (outcome === "continue_send" && !run.disposed && cursorLiveRuns.getActiveForScope(run.sessionAgentScopeKey) === run) {
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
turnDebugRecorder?.recordDrainEvent("pre_send_end", { outcome, runId: run.id });
|
|
389
|
+
return outcome;
|
|
390
|
+
}
|
|
391
|
+
catch (error) {
|
|
392
|
+
turnDebugRecorder?.recordDrainEvent("pre_send_end", {
|
|
393
|
+
outcome: error instanceof CursorLiveRunAbortError ? "aborted" : "error",
|
|
394
|
+
runId: run.id,
|
|
395
|
+
reason: error instanceof CursorLiveRunAbortError ? "signal_aborted" : "drain_error",
|
|
396
|
+
});
|
|
397
|
+
if (error instanceof CursorLiveRunAbortError)
|
|
398
|
+
await cursorLiveRuns.release(run);
|
|
399
|
+
throw error;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
export function setCursorNativeReplayIdleDisposeMs(value) {
|
|
404
|
+
cursorNativeReplayIdleDisposeMs = value;
|
|
405
|
+
}
|
|
406
|
+
export function resetCursorNativeReplayIdleDisposeMs() {
|
|
407
|
+
cursorNativeReplayIdleDisposeMs = DEFAULT_CURSOR_NATIVE_REPLAY_IDLE_DISPOSE_MS;
|
|
408
|
+
}
|
|
409
|
+
export async function releaseAllPendingCursorLiveRunsForTests() {
|
|
410
|
+
while (cursorLiveRuns.count() > 0) {
|
|
411
|
+
const run = cursorLiveRuns.getActiveForScope();
|
|
412
|
+
if (!run)
|
|
413
|
+
break;
|
|
414
|
+
const before = cursorLiveRuns.count();
|
|
415
|
+
await cursorLiveRuns.release(run);
|
|
416
|
+
if (cursorLiveRuns.count() >= before)
|
|
417
|
+
break;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
export { hasTrailingUserMessagesAfterToolResults };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { CURSOR_PROVIDER } from "./cursor-model.js";
|
|
2
|
+
/**
|
|
3
|
+
* Pi recognizes `context_length_exceeded` (its `OVERFLOW_PATTERNS` includes
|
|
4
|
+
* `/context[_ ]length[_ ]exceeded/i`) as a context-overflow signal and, on
|
|
5
|
+
* detecting it, drops the failed assistant message, compacts, and retries once.
|
|
6
|
+
*
|
|
7
|
+
* The Cursor provider sanitizes SDK failures into auth/network/generic messages
|
|
8
|
+
* that pi does NOT treat as overflow, so a genuine Cursor context-overflow
|
|
9
|
+
* failure would otherwise surface as a plain provider error and bypass pi's
|
|
10
|
+
* auto-compact-retry recovery. This handler rewrites only Cursor context-window
|
|
11
|
+
* failures into the `context_length_exceeded` form pi recognizes.
|
|
12
|
+
*
|
|
13
|
+
* See pi `docs/custom-provider.md` -> "Context Overflow Errors".
|
|
14
|
+
*/
|
|
15
|
+
export const CURSOR_OVERFLOW_MARKER = "context_length_exceeded";
|
|
16
|
+
/**
|
|
17
|
+
* Context-overflow phrases. These overlap pi's own `OVERFLOW_PATTERNS` so the
|
|
18
|
+
* false-positive surface matches pi's built-in detection for other providers.
|
|
19
|
+
*
|
|
20
|
+
* ponytail: intentionally narrow and textual. We do NOT match bare gRPC
|
|
21
|
+
* `resource_exhausted` (code 8) or `too many tokens` because pi documents those
|
|
22
|
+
* can false-match throttling/quota errors and trigger an unwanted compaction.
|
|
23
|
+
* If a live Cursor probe reveals a distinct overflow phrase, add it here; this
|
|
24
|
+
* pattern set is the single place to extend without touching the handler.
|
|
25
|
+
*/
|
|
26
|
+
const CURSOR_OVERFLOW_PATTERNS = [
|
|
27
|
+
/context[_ ]?length/i,
|
|
28
|
+
/context[_ ]?window/i,
|
|
29
|
+
/maximum context/i,
|
|
30
|
+
/prompt is too long/i,
|
|
31
|
+
/too large for model/i,
|
|
32
|
+
/exceed(?:s|ed).{0,30}context/i,
|
|
33
|
+
];
|
|
34
|
+
/** Never treat rate-limit/throttle signals as overflow (pi retries those separately). */
|
|
35
|
+
const CURSOR_THROTTLE_PATTERN = /too many requests|rate.?limit|throttl|retry.?after/i;
|
|
36
|
+
/**
|
|
37
|
+
* Map a finalized Cursor assistant `errorMessage` to pi's overflow form.
|
|
38
|
+
* Returns `undefined` when the message is not a Cursor context-overflow failure.
|
|
39
|
+
*
|
|
40
|
+
* Pure and idempotent so it is safe to call repeatedly and unit-test in isolation.
|
|
41
|
+
*/
|
|
42
|
+
export function normalizeCursorOverflowErrorMessage(errorMessage) {
|
|
43
|
+
const message = errorMessage?.trim();
|
|
44
|
+
if (!message)
|
|
45
|
+
return undefined;
|
|
46
|
+
// Idempotent: never double-prefix an already-normalized message.
|
|
47
|
+
if (message.includes(CURSOR_OVERFLOW_MARKER))
|
|
48
|
+
return undefined;
|
|
49
|
+
if (CURSOR_THROTTLE_PATTERN.test(message))
|
|
50
|
+
return undefined;
|
|
51
|
+
if (CURSOR_OVERFLOW_PATTERNS.some((pattern) => pattern.test(message))) {
|
|
52
|
+
return `${CURSOR_OVERFLOW_MARKER}: ${message}`;
|
|
53
|
+
}
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Rewrite a finalized assistant message into pi's overflow form when it is a
|
|
58
|
+
* Cursor context-overflow failure. Returns the replacement message (for the
|
|
59
|
+
* `message_end` result) or `undefined` to leave it unchanged.
|
|
60
|
+
*
|
|
61
|
+
* `isCursorProvider` carries the provider scoping decision out of the handler so
|
|
62
|
+
* the full logic is unit-testable without a pi event harness.
|
|
63
|
+
*/
|
|
64
|
+
export function rewriteCursorOverflowAssistantMessage(message, isCursorProvider) {
|
|
65
|
+
if (!isCursorProvider || message.stopReason !== "error")
|
|
66
|
+
return undefined;
|
|
67
|
+
const rewritten = normalizeCursorOverflowErrorMessage(message.errorMessage);
|
|
68
|
+
if (!rewritten)
|
|
69
|
+
return undefined;
|
|
70
|
+
return { ...message, errorMessage: rewritten };
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Register a `message_end` handler that rewrites Cursor context-overflow
|
|
74
|
+
* failures into the `context_length_exceeded` form pi auto-compacts on.
|
|
75
|
+
*
|
|
76
|
+
* Guarded exactly as pi's provider docs require: scoped to the Cursor provider,
|
|
77
|
+
* only for `stopReason === "error"`, never for throttling, and idempotent.
|
|
78
|
+
*/
|
|
79
|
+
export function registerCursorOverflowNormalization(pi) {
|
|
80
|
+
pi.on("message_end", (event, ctx) => {
|
|
81
|
+
const message = event.message;
|
|
82
|
+
if (message.role !== "assistant")
|
|
83
|
+
return undefined;
|
|
84
|
+
const isCursorProvider = message.provider === CURSOR_PROVIDER || ctx.model?.provider === CURSOR_PROVIDER;
|
|
85
|
+
const rewritten = rewriteCursorOverflowAssistantMessage(message, isCursorProvider);
|
|
86
|
+
if (!rewritten)
|
|
87
|
+
return undefined;
|
|
88
|
+
return { message: rewritten };
|
|
89
|
+
});
|
|
90
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { cursorLiveRuns } from "./cursor-provider-live-run-drain.js";
|
|
2
|
+
import { classifyCursorRunEmission, getCursorRunAbortMessage, } from "./cursor-provider-run-outcome.js";
|
|
3
|
+
import { formatCursorSdkAbortMessage, resolveCursorSdkAbortCause, sanitizeCursorProviderError, } from "./cursor-provider-errors.js";
|
|
4
|
+
import { CursorLiveRunAbortError } from "./cursor-live-run-coordinator.js";
|
|
5
|
+
import { buildIncompleteCursorToolRunOutcome, } from "./cursor-incomplete-tool-visibility.js";
|
|
6
|
+
import { awaitFinalizeCursorRunOutcome } from "./cursor-provider-turn-finalize.js";
|
|
7
|
+
import { applyCursorUsage } from "./cursor-usage-accounting.js";
|
|
8
|
+
import { hasUsableText } from "./cursor-record-utils.js";
|
|
9
|
+
import { emitDisplayOnlyTraceBlock } from "./cursor-display-only-trace.js";
|
|
10
|
+
function applyLiveRunOutcome(outcome, prepared, context) {
|
|
11
|
+
if (prepared.runtime.liveRun.disposed)
|
|
12
|
+
return;
|
|
13
|
+
const { liveRun } = prepared.runtime;
|
|
14
|
+
switch (classifyCursorRunEmission(outcome)) {
|
|
15
|
+
case "finished":
|
|
16
|
+
prepared.lifecycle.commitSend(context, prepared.meta.bootstrap);
|
|
17
|
+
if (prepared.meta.resumeNotice)
|
|
18
|
+
liveRun.resumeNotice = prepared.meta.resumeNotice;
|
|
19
|
+
cursorLiveRuns.markFinished(liveRun, outcome.kind === "finished" ? outcome.finalText : "");
|
|
20
|
+
break;
|
|
21
|
+
case "cancelled":
|
|
22
|
+
cursorLiveRuns.markCancelled(liveRun, getCursorRunAbortMessage(outcome));
|
|
23
|
+
break;
|
|
24
|
+
case "failed":
|
|
25
|
+
cursorLiveRuns.markError(liveRun, outcome.kind === "error" ? outcome.errorMessage : "Cursor SDK run failed.");
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export class CursorRunFinalizer {
|
|
30
|
+
params;
|
|
31
|
+
terminalApplied = false;
|
|
32
|
+
constructor(params) {
|
|
33
|
+
this.params = params;
|
|
34
|
+
}
|
|
35
|
+
startLiveRunCompletion(startParams) {
|
|
36
|
+
const { runnerParams } = this.params;
|
|
37
|
+
const sdkEventDebug = this.params.sdkEventDebug();
|
|
38
|
+
const { send, prepared, modelId, discardIncompleteTools } = startParams;
|
|
39
|
+
const { run, cursorAgentMessageOffset } = send;
|
|
40
|
+
const { liveRun } = prepared.runtime;
|
|
41
|
+
const waitCompletion = awaitFinalizeCursorRunOutcome({
|
|
42
|
+
run,
|
|
43
|
+
prepared,
|
|
44
|
+
cursorAgentMessageOffset,
|
|
45
|
+
modelId,
|
|
46
|
+
signal: runnerParams.options?.signal,
|
|
47
|
+
runResultFallback: run.result,
|
|
48
|
+
runErrorFallback: run.error,
|
|
49
|
+
resolvedApiKey: this.params.resolvedApiKey(),
|
|
50
|
+
optionsApiKey: runnerParams.options?.apiKey,
|
|
51
|
+
sdkEventDebug,
|
|
52
|
+
cacheContextWindow: true,
|
|
53
|
+
contextWindowAgentId: liveRun.agent.agentId,
|
|
54
|
+
})
|
|
55
|
+
.then(async (finalized) => {
|
|
56
|
+
applyLiveRunOutcome(finalized.outcome, prepared, runnerParams.context);
|
|
57
|
+
})
|
|
58
|
+
.catch((error) => {
|
|
59
|
+
this.safeCleanup(() => discardIncompleteTools({ status: "error" }));
|
|
60
|
+
if (!liveRun.disposed) {
|
|
61
|
+
cursorLiveRuns.markError(liveRun, sanitizeCursorProviderError(error, this.params.resolvedApiKey() ?? runnerParams.options?.apiKey, "local"));
|
|
62
|
+
}
|
|
63
|
+
this.safeCleanup(() => sdkEventDebug?.recordWaitResult({ status: "error", error: String(error) }));
|
|
64
|
+
this.safeCleanup(() => sdkEventDebug?.recordError("run_wait", error));
|
|
65
|
+
});
|
|
66
|
+
// Mark the pooled local agent busy as soon as the SDK run exists so auto-compaction summarization
|
|
67
|
+
// (and other concurrent acquires) wait for run.wait() instead of hitting AgentBusyError.
|
|
68
|
+
prepared.lifecycle.trackRunCompletion(waitCompletion);
|
|
69
|
+
return { waitCompletion, prepared };
|
|
70
|
+
}
|
|
71
|
+
async applyTerminalEvent(event) {
|
|
72
|
+
if (this.terminalApplied)
|
|
73
|
+
return;
|
|
74
|
+
if (event.kind === "direct") {
|
|
75
|
+
await this.applyDirectOutcome(event.prepared, event.outcome, event.displayOnlyTraceBlock);
|
|
76
|
+
this.terminalApplied = true;
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
await this.applyErrorOutcome(event.prepared, event.error);
|
|
80
|
+
this.terminalApplied = true;
|
|
81
|
+
}
|
|
82
|
+
async cleanup(prepared, sendResult, liveCompletion) {
|
|
83
|
+
this.safeCleanup(() => prepared?.restoreCursorSdkOutputFilter());
|
|
84
|
+
const abortRegistration = sendResult?.abortRegistration;
|
|
85
|
+
if (abortRegistration) {
|
|
86
|
+
this.safeCleanup(() => abortRegistration.signal.removeEventListener("abort", abortRegistration.listener));
|
|
87
|
+
}
|
|
88
|
+
this.params.runnerParams.sdkEventDebugRef.current = undefined;
|
|
89
|
+
if (liveCompletion) {
|
|
90
|
+
void liveCompletion.waitCompletion
|
|
91
|
+
.finally(async () => {
|
|
92
|
+
await this.finalizeSdkEventDebugBestEffort();
|
|
93
|
+
this.safeCleanup(() => this.params.sdkProcessErrorGuard.dispose());
|
|
94
|
+
})
|
|
95
|
+
.catch(() => { });
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
await prepared?.lifecycle.dispose().catch(() => { });
|
|
99
|
+
await this.finalizeSdkEventDebugBestEffort();
|
|
100
|
+
this.safeCleanup(() => this.params.sdkProcessErrorGuard.dispose());
|
|
101
|
+
}
|
|
102
|
+
async applyDirectOutcome(prepared, outcome, displayOnlyTraceBlock) {
|
|
103
|
+
const { stream, partial, model, context } = this.params.runnerParams;
|
|
104
|
+
prepared.runtime.turnCoordinator.closeTraceBlock();
|
|
105
|
+
switch (classifyCursorRunEmission(outcome)) {
|
|
106
|
+
case "cancelled":
|
|
107
|
+
await prepared.lifecycle.abandon();
|
|
108
|
+
this.pushTerminalError(partial, "aborted", getCursorRunAbortMessage(outcome));
|
|
109
|
+
break;
|
|
110
|
+
case "failed":
|
|
111
|
+
await prepared.lifecycle.abandon();
|
|
112
|
+
this.pushTerminalError(partial, "error", outcome.kind === "error" ? outcome.errorMessage : "Cursor SDK run failed.");
|
|
113
|
+
break;
|
|
114
|
+
case "finished":
|
|
115
|
+
prepared.lifecycle.commitSend(context, prepared.meta.bootstrap);
|
|
116
|
+
prepared.runtime.turnCoordinator.flushText(outcome.kind === "finished" && hasUsableText(outcome.finalText) ? [outcome.finalText] : []);
|
|
117
|
+
applyCursorUsage(partial, model, context, prepared.meta.promptInputTokens, {
|
|
118
|
+
runtime: prepared.runtimeTarget,
|
|
119
|
+
turn: prepared.runtime.turnCoordinator.lastSdkTurnUsage,
|
|
120
|
+
billed: prepared.runtime.billedTurnUsage,
|
|
121
|
+
});
|
|
122
|
+
if (prepared.meta.resumeNotice)
|
|
123
|
+
emitDisplayOnlyTraceBlock(stream, partial, prepared.meta.resumeNotice);
|
|
124
|
+
if (displayOnlyTraceBlock)
|
|
125
|
+
emitDisplayOnlyTraceBlock(stream, partial, displayOnlyTraceBlock);
|
|
126
|
+
stream.push({ type: "done", reason: "stop", message: partial });
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
async applyErrorOutcome(prepared, error) {
|
|
131
|
+
this.safeCleanup(() => prepared?.runtime.turnCoordinator.discardIncompleteStartedToolCalls(buildIncompleteCursorToolRunOutcome({
|
|
132
|
+
status: error instanceof CursorLiveRunAbortError ? "cancelled" : "error",
|
|
133
|
+
signalAborted: error instanceof CursorLiveRunAbortError,
|
|
134
|
+
})));
|
|
135
|
+
const activeLiveRun = prepared?.runtime.liveRun;
|
|
136
|
+
if (activeLiveRun && !activeLiveRun.disposed) {
|
|
137
|
+
await cursorLiveRuns.release(activeLiveRun);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
await prepared?.lifecycle.abandon();
|
|
141
|
+
}
|
|
142
|
+
this.safeCleanup(() => this.params.sdkEventDebug()?.recordError("provider_stream", error));
|
|
143
|
+
if (error instanceof CursorLiveRunAbortError) {
|
|
144
|
+
this.params.sdkProcessErrorGuard.suppressAbortErrors();
|
|
145
|
+
this.pushTerminalError(this.params.runnerParams.partial, "aborted", this.abortMessage());
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
this.pushTerminalError(this.params.runnerParams.partial, "error", sanitizeCursorProviderError(error, this.params.resolvedApiKey() ?? this.params.runnerParams.options?.apiKey, prepared?.runtimeTarget ?? this.params.runtimeTarget()));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
pushTerminalError(partial, reason, message) {
|
|
152
|
+
partial.stopReason = reason;
|
|
153
|
+
partial.errorMessage = message;
|
|
154
|
+
this.params.runnerParams.stream.push({ type: "error", reason, error: partial });
|
|
155
|
+
}
|
|
156
|
+
abortMessage() {
|
|
157
|
+
return formatCursorSdkAbortMessage(resolveCursorSdkAbortCause({ signalAborted: this.params.runnerParams.options?.signal?.aborted }));
|
|
158
|
+
}
|
|
159
|
+
safeCleanup(cleanup) {
|
|
160
|
+
try {
|
|
161
|
+
cleanup();
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
// Cleanup must not reclassify an already-emitted provider turn.
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
async finalizeSdkEventDebugBestEffort() {
|
|
168
|
+
try {
|
|
169
|
+
this.params.sdkEventDebug()?.recordFinalPartial(this.params.runnerParams.partial);
|
|
170
|
+
await this.params.sdkEventDebug()?.finalize();
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
// Debug artifact IO is best-effort and must not emit a second terminal event.
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|