pi-cursor-sdk 0.1.15 → 0.1.17
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 +56 -1
- package/README.md +20 -8
- package/docs/cursor-live-smoke-checklist.md +267 -0
- package/docs/cursor-model-ux-spec.md +15 -5
- package/docs/cursor-native-tool-replay.md +16 -5
- package/package.json +12 -5
- package/scripts/steering-rpc-smoke.mjs +238 -0
- package/scripts/tmux-live-smoke.sh +418 -0
- package/scripts/validate-smoke-jsonl.mjs +152 -0
- package/src/context.ts +180 -5
- package/src/cursor-bridge-contract.ts +27 -0
- package/src/cursor-edit-diff.ts +11 -0
- package/src/cursor-env-boolean.ts +22 -0
- package/src/cursor-live-run-accounting.ts +65 -0
- package/src/cursor-live-run-coordinator.ts +483 -0
- package/src/cursor-native-tool-display-registration.ts +93 -0
- package/src/cursor-native-tool-display-replay.ts +465 -0
- package/src/cursor-native-tool-display-state.ts +78 -0
- package/src/cursor-native-tool-display-tools.ts +102 -0
- package/src/cursor-native-tool-display.ts +10 -639
- package/src/cursor-partial-content-emitter.ts +121 -0
- package/src/cursor-pi-tool-bridge-abort.ts +133 -0
- package/src/cursor-pi-tool-bridge-diagnostics.ts +179 -0
- package/src/cursor-pi-tool-bridge-mcp.ts +118 -0
- package/src/cursor-pi-tool-bridge-run.ts +384 -0
- package/src/cursor-pi-tool-bridge-server.ts +182 -0
- package/src/cursor-pi-tool-bridge-snapshot.ts +88 -0
- package/src/cursor-pi-tool-bridge-types.ts +80 -0
- package/src/cursor-pi-tool-bridge.ts +77 -602
- package/src/cursor-provider-live-run-drain.ts +379 -0
- package/src/cursor-provider-turn-coordinator.ts +456 -0
- package/src/cursor-provider.ts +133 -1092
- package/src/cursor-question-tool.ts +7 -2
- package/src/cursor-record-utils.ts +26 -0
- package/src/cursor-sdk-output-filter.ts +100 -0
- package/src/cursor-sensitive-text.ts +37 -0
- package/src/cursor-session-agent.ts +372 -0
- package/src/cursor-session-cwd.ts +14 -19
- package/src/cursor-session-scope.ts +65 -0
- package/src/cursor-state.ts +38 -10
- package/src/cursor-tool-transcript.ts +28 -1229
- package/src/cursor-transcript-tool-formatters.ts +641 -0
- package/src/cursor-transcript-tool-specs.ts +441 -0
- package/src/cursor-transcript-utils.ts +276 -0
- package/src/cursor-usage-accounting.ts +71 -0
- package/src/index.ts +20 -3
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type Api,
|
|
3
|
+
type AssistantMessage,
|
|
4
|
+
type AssistantMessageEventStream,
|
|
5
|
+
type Context,
|
|
6
|
+
type Model,
|
|
7
|
+
} from "@earendil-works/pi-ai";
|
|
8
|
+
import { scheduler } from "node:timers/promises";
|
|
9
|
+
import {
|
|
10
|
+
CursorLiveRunAbortError,
|
|
11
|
+
createCursorLiveRunCoordinator,
|
|
12
|
+
hasTrailingUserMessagesAfterToolResults,
|
|
13
|
+
type CursorLiveQueuedEvent,
|
|
14
|
+
type CursorLiveRun,
|
|
15
|
+
} from "./cursor-live-run-coordinator.js";
|
|
16
|
+
import {
|
|
17
|
+
deleteCursorNativeToolDisplay,
|
|
18
|
+
recordCursorNativeToolDisplay,
|
|
19
|
+
type CursorNativeToolDisplayItem,
|
|
20
|
+
} from "./cursor-native-tool-display.js";
|
|
21
|
+
import { type CursorPiBridgeToolRequest } from "./cursor-pi-tool-bridge.js";
|
|
22
|
+
import { resetSessionCursorAgent } from "./cursor-session-agent.js";
|
|
23
|
+
import { applyCursorApproximateUsage } from "./cursor-usage-accounting.js";
|
|
24
|
+
import { CursorPartialContentEmitter } from "./cursor-partial-content-emitter.js";
|
|
25
|
+
import { hasUsableText } from "./cursor-record-utils.js";
|
|
26
|
+
|
|
27
|
+
export const DEFAULT_CURSOR_NATIVE_REPLAY_IDLE_DISPOSE_MS = 5 * 60 * 1000;
|
|
28
|
+
const CURSOR_NATIVE_REPLAY_TOOL_ID_PATTERN = /^(cursor-replay-\d+-\d+)-tool-\d+$/;
|
|
29
|
+
|
|
30
|
+
interface CursorLiveTurnState {
|
|
31
|
+
emitter: CursorPartialContentEmitter;
|
|
32
|
+
emittedText: string;
|
|
33
|
+
}
|
|
34
|
+
let cursorNativeReplayIdleDisposeMs = DEFAULT_CURSOR_NATIVE_REPLAY_IDLE_DISPOSE_MS;
|
|
35
|
+
|
|
36
|
+
type CursorLiveRunDrainMode = "emit" | "chain_user_input";
|
|
37
|
+
type CursorLiveRunDrainOutcome = "tool_use" | "stop" | "error" | "aborted" | "chain_user_input";
|
|
38
|
+
type LiveRunPreSendOutcome = "stream_ended" | "continue_send";
|
|
39
|
+
|
|
40
|
+
let cursorNativeReplayCounter = 0;
|
|
41
|
+
|
|
42
|
+
export async function abandonSessionCursorAgent(scopeKey: string | undefined): Promise<void> {
|
|
43
|
+
if (!scopeKey) return;
|
|
44
|
+
await resetSessionCursorAgent(scopeKey);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const cursorLiveRuns = createCursorLiveRunCoordinator({
|
|
48
|
+
getIdleDisposeMs: () => cursorNativeReplayIdleDisposeMs,
|
|
49
|
+
deleteNativeToolDisplay: deleteCursorNativeToolDisplay,
|
|
50
|
+
abandonSessionAgent: (scopeKey) => abandonSessionCursorAgent(scopeKey),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
export function createCursorNativeReplayId(): string {
|
|
54
|
+
cursorNativeReplayCounter += 1;
|
|
55
|
+
return `cursor-replay-${Date.now()}-${cursorNativeReplayCounter}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function getCursorNativeReplayIdFromToolCallId(toolCallId: string): string | undefined {
|
|
59
|
+
return CURSOR_NATIVE_REPLAY_TOOL_ID_PATTERN.exec(toolCallId)?.[1];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function getPendingCursorLiveRun(context: Context): CursorLiveRun | undefined {
|
|
63
|
+
return cursorLiveRuns.getPendingFromContext(context, getCursorNativeReplayIdFromToolCallId);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function getActiveCursorLiveRunForCurrentScope(): CursorLiveRun | undefined {
|
|
67
|
+
return cursorLiveRuns.getActiveForScope();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function splitTextIntoReplayDeltas(text: string): string[] {
|
|
71
|
+
const deltas: string[] = [];
|
|
72
|
+
let remaining = text;
|
|
73
|
+
while (remaining.length > 0) {
|
|
74
|
+
if (remaining.length <= 96) {
|
|
75
|
+
deltas.push(remaining);
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
const boundary = Math.max(48, remaining.lastIndexOf(" ", 96));
|
|
79
|
+
deltas.push(remaining.slice(0, boundary));
|
|
80
|
+
remaining = remaining.slice(boundary);
|
|
81
|
+
}
|
|
82
|
+
return deltas;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function emitTextDeltas(
|
|
86
|
+
stream: AssistantMessageEventStream,
|
|
87
|
+
partial: AssistantMessage,
|
|
88
|
+
deltas: string[],
|
|
89
|
+
): Promise<string> {
|
|
90
|
+
const emitter = new CursorPartialContentEmitter(stream, partial, -1, true);
|
|
91
|
+
for (const delta of deltas) {
|
|
92
|
+
emitter.appendTextDelta(delta);
|
|
93
|
+
await Promise.resolve();
|
|
94
|
+
}
|
|
95
|
+
return emitter.closeText();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export async function settleCursorLiveToolBatch(run: CursorLiveRun): Promise<void> {
|
|
99
|
+
const eventType = cursorLiveRuns.peekEvent(run)?.type;
|
|
100
|
+
if (eventType !== "tool" && eventType !== "bridge-tool") return;
|
|
101
|
+
await scheduler.wait(75);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function emitCursorLiveQueuedEvent(
|
|
105
|
+
turn: CursorLiveTurnState,
|
|
106
|
+
event: Exclude<CursorLiveQueuedEvent, { type: "tool" } | { type: "bridge-tool" }>,
|
|
107
|
+
run?: CursorLiveRun,
|
|
108
|
+
): void {
|
|
109
|
+
if (event.type === "thinking-delta") {
|
|
110
|
+
turn.emitter.appendThinkingDelta(event.text);
|
|
111
|
+
} else if (event.type === "thinking-completed") {
|
|
112
|
+
turn.emitter.closeThinking();
|
|
113
|
+
} else if (event.type === "text-delta") {
|
|
114
|
+
turn.emittedText += event.text;
|
|
115
|
+
if (run) run.emittedText += event.text;
|
|
116
|
+
turn.emitter.appendTextDelta(event.text);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function isCursorTextBoundary(text: string, index: number): boolean {
|
|
121
|
+
if (index <= 0 || index >= text.length) return true;
|
|
122
|
+
const before = text[index - 1];
|
|
123
|
+
const after = text[index];
|
|
124
|
+
return !/[\p{L}\p{N}_]/u.test(before) || !/[\p{L}\p{N}_]/u.test(after);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function trimAlreadyEmittedCursorText(text: string, emittedText: string, options?: { allowPartialPrefix?: boolean }): string {
|
|
128
|
+
if (!text || !emittedText) return text;
|
|
129
|
+
if (text === emittedText) return "";
|
|
130
|
+
if (text.startsWith(emittedText) && (options?.allowPartialPrefix || isCursorTextBoundary(text, emittedText.length))) {
|
|
131
|
+
return text.slice(emittedText.length);
|
|
132
|
+
}
|
|
133
|
+
if (emittedText.endsWith(text) && isCursorTextBoundary(emittedText, emittedText.length - text.length)) return "";
|
|
134
|
+
const trimmedText = text.trim();
|
|
135
|
+
const trimmedEmittedText = emittedText.trim();
|
|
136
|
+
if (trimmedText === trimmedEmittedText) return "";
|
|
137
|
+
if (trimmedText && trimmedEmittedText.endsWith(trimmedText)) {
|
|
138
|
+
const suffixStart = trimmedEmittedText.length - trimmedText.length;
|
|
139
|
+
if (isCursorTextBoundary(trimmedEmittedText, suffixStart)) return "";
|
|
140
|
+
}
|
|
141
|
+
return text;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function trimCurrentTurnAlreadyEmittedCursorText(text: string, currentTurnEmittedText: string, emittedText = currentTurnEmittedText): string {
|
|
145
|
+
if (!currentTurnEmittedText) return trimAlreadyEmittedCursorText(text, emittedText);
|
|
146
|
+
const currentTurnTrimmedText = trimAlreadyEmittedCursorText(text, currentTurnEmittedText, { allowPartialPrefix: true });
|
|
147
|
+
if (currentTurnTrimmedText !== text) return currentTurnTrimmedText;
|
|
148
|
+
if (emittedText.endsWith(currentTurnEmittedText)) {
|
|
149
|
+
const emittedTextTrimmedText = trimAlreadyEmittedCursorText(text, emittedText, { allowPartialPrefix: true });
|
|
150
|
+
if (emittedTextTrimmedText !== text) return emittedTextTrimmedText;
|
|
151
|
+
}
|
|
152
|
+
return trimAlreadyEmittedCursorText(text, emittedText);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function selectCursorFinalText(
|
|
156
|
+
resultText: unknown,
|
|
157
|
+
textDeltas: readonly string[],
|
|
158
|
+
emittedText: string,
|
|
159
|
+
fallbackText?: string,
|
|
160
|
+
options?: { allowPartialPrefix?: boolean },
|
|
161
|
+
): string {
|
|
162
|
+
const candidates = [typeof resultText === "string" ? resultText : undefined, fallbackText, textDeltas.join("")];
|
|
163
|
+
for (const candidate of candidates) {
|
|
164
|
+
if (!hasUsableText(candidate)) continue;
|
|
165
|
+
const trimmedCandidate = trimAlreadyEmittedCursorText(candidate, emittedText, options);
|
|
166
|
+
if (hasUsableText(trimmedCandidate)) return trimmedCandidate;
|
|
167
|
+
}
|
|
168
|
+
return "";
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function emitCursorNativeToolUseTurn(
|
|
172
|
+
stream: AssistantMessageEventStream,
|
|
173
|
+
partial: AssistantMessage,
|
|
174
|
+
model: Model<Api>,
|
|
175
|
+
context: Context,
|
|
176
|
+
run: CursorLiveRun,
|
|
177
|
+
toolResultInputTokens: number,
|
|
178
|
+
tools: CursorNativeToolDisplayItem[],
|
|
179
|
+
): void {
|
|
180
|
+
const shouldTerminate = run.done && !run.finalText?.trim() && !cursorLiveRuns.peekEvent(run);
|
|
181
|
+
for (const tool of tools) {
|
|
182
|
+
const contentIndex = partial.content.length;
|
|
183
|
+
partial.content.push({
|
|
184
|
+
type: "toolCall",
|
|
185
|
+
id: tool.id,
|
|
186
|
+
name: tool.toolName,
|
|
187
|
+
arguments: tool.args,
|
|
188
|
+
});
|
|
189
|
+
stream.push({ type: "toolcall_start", contentIndex, partial });
|
|
190
|
+
stream.push({ type: "toolcall_delta", contentIndex, delta: JSON.stringify(tool.args), partial });
|
|
191
|
+
const block = partial.content[contentIndex];
|
|
192
|
+
if (block.type === "toolCall") stream.push({ type: "toolcall_end", contentIndex, toolCall: block, partial });
|
|
193
|
+
if (recordCursorNativeToolDisplay({ ...tool, terminate: shouldTerminate })) {
|
|
194
|
+
run.recordedToolDisplayIds.push(tool.id);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
applyCursorApproximateUsage(partial, model, context, cursorLiveRuns.takeTurnInputTokens(run, toolResultInputTokens));
|
|
198
|
+
partial.stopReason = "toolUse";
|
|
199
|
+
stream.push({ type: "done", reason: "toolUse", message: partial });
|
|
200
|
+
cursorLiveRuns.requestIdleDispose(run);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function emitCursorBridgeToolUseTurn(
|
|
204
|
+
stream: AssistantMessageEventStream,
|
|
205
|
+
partial: AssistantMessage,
|
|
206
|
+
model: Model<Api>,
|
|
207
|
+
context: Context,
|
|
208
|
+
run: CursorLiveRun,
|
|
209
|
+
toolResultInputTokens: number,
|
|
210
|
+
requests: CursorPiBridgeToolRequest[],
|
|
211
|
+
): void {
|
|
212
|
+
for (const request of requests) {
|
|
213
|
+
const contentIndex = partial.content.length;
|
|
214
|
+
partial.content.push({
|
|
215
|
+
type: "toolCall",
|
|
216
|
+
id: request.piToolCallId,
|
|
217
|
+
name: request.piToolName,
|
|
218
|
+
arguments: request.args,
|
|
219
|
+
});
|
|
220
|
+
stream.push({ type: "toolcall_start", contentIndex, partial });
|
|
221
|
+
stream.push({ type: "toolcall_delta", contentIndex, delta: JSON.stringify(request.args), partial });
|
|
222
|
+
const block = partial.content[contentIndex];
|
|
223
|
+
if (block.type === "toolCall") stream.push({ type: "toolcall_end", contentIndex, toolCall: block, partial });
|
|
224
|
+
}
|
|
225
|
+
applyCursorApproximateUsage(partial, model, context, cursorLiveRuns.takeTurnInputTokens(run, toolResultInputTokens));
|
|
226
|
+
partial.stopReason = "toolUse";
|
|
227
|
+
stream.push({ type: "done", reason: "toolUse", message: partial });
|
|
228
|
+
cursorLiveRuns.requestIdleDispose(run);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async function emitCursorLiveRunPendingToolUseTurn(
|
|
232
|
+
stream: AssistantMessageEventStream,
|
|
233
|
+
partial: AssistantMessage,
|
|
234
|
+
model: Model<Api>,
|
|
235
|
+
context: Context,
|
|
236
|
+
run: CursorLiveRun,
|
|
237
|
+
toolResultInputTokens: number,
|
|
238
|
+
signal?: AbortSignal,
|
|
239
|
+
beforeEmit?: () => void,
|
|
240
|
+
): Promise<"tool_use" | undefined> {
|
|
241
|
+
const eventType = cursorLiveRuns.peekEvent(run)?.type;
|
|
242
|
+
if (eventType !== "tool" && eventType !== "bridge-tool") return undefined;
|
|
243
|
+
await settleCursorLiveToolBatch(run);
|
|
244
|
+
if (signal?.aborted) throw new CursorLiveRunAbortError();
|
|
245
|
+
beforeEmit?.();
|
|
246
|
+
if (eventType === "tool") {
|
|
247
|
+
const tools = cursorLiveRuns.collectNativeToolBatch(run);
|
|
248
|
+
emitCursorNativeToolUseTurn(stream, partial, model, context, run, toolResultInputTokens, tools);
|
|
249
|
+
} else {
|
|
250
|
+
const requests = cursorLiveRuns.collectBridgeToolBatch(run);
|
|
251
|
+
emitCursorBridgeToolUseTurn(stream, partial, model, context, run, toolResultInputTokens, requests);
|
|
252
|
+
}
|
|
253
|
+
return "tool_use";
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export async function drainCursorLiveRunTurn(
|
|
257
|
+
stream: AssistantMessageEventStream,
|
|
258
|
+
partial: AssistantMessage,
|
|
259
|
+
model: Model<Api>,
|
|
260
|
+
context: Context,
|
|
261
|
+
run: CursorLiveRun,
|
|
262
|
+
toolResultInputTokens: number,
|
|
263
|
+
options: { mode: CursorLiveRunDrainMode; signal?: AbortSignal },
|
|
264
|
+
): Promise<CursorLiveRunDrainOutcome> {
|
|
265
|
+
const turn: CursorLiveTurnState = {
|
|
266
|
+
emitter: new CursorPartialContentEmitter(stream, partial, -1, true),
|
|
267
|
+
emittedText: "",
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
while (true) {
|
|
271
|
+
if (options.mode === "chain_user_input" && cursorLiveRuns.isReady(run)) {
|
|
272
|
+
await cursorLiveRuns.release(run);
|
|
273
|
+
return "chain_user_input";
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
while (cursorLiveRuns.peekEvent(run)) {
|
|
277
|
+
const toolUse = await emitCursorLiveRunPendingToolUseTurn(
|
|
278
|
+
stream,
|
|
279
|
+
partial,
|
|
280
|
+
model,
|
|
281
|
+
context,
|
|
282
|
+
run,
|
|
283
|
+
toolResultInputTokens,
|
|
284
|
+
options.signal,
|
|
285
|
+
options.mode === "emit" ? () => turn.emitter.closeAll() : undefined,
|
|
286
|
+
);
|
|
287
|
+
if (toolUse) return toolUse;
|
|
288
|
+
const event = cursorLiveRuns.shiftEvent(run);
|
|
289
|
+
if (!event || event.type === "tool" || event.type === "bridge-tool") continue;
|
|
290
|
+
if (options.mode === "emit") emitCursorLiveQueuedEvent(turn, event, run);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (run.disposed) {
|
|
294
|
+
partial.stopReason = "aborted";
|
|
295
|
+
stream.push({ type: "error", reason: "aborted", error: partial });
|
|
296
|
+
return "aborted";
|
|
297
|
+
}
|
|
298
|
+
if (run.cancelled) {
|
|
299
|
+
partial.stopReason = "aborted";
|
|
300
|
+
stream.push({ type: "error", reason: "aborted", error: partial });
|
|
301
|
+
await cursorLiveRuns.release(run);
|
|
302
|
+
return "aborted";
|
|
303
|
+
}
|
|
304
|
+
if (run.errorMessage) {
|
|
305
|
+
partial.stopReason = "error";
|
|
306
|
+
partial.errorMessage = run.errorMessage;
|
|
307
|
+
stream.push({ type: "error", reason: "error", error: partial });
|
|
308
|
+
await cursorLiveRuns.release(run);
|
|
309
|
+
return "error";
|
|
310
|
+
}
|
|
311
|
+
if (run.done) {
|
|
312
|
+
if (options.mode === "chain_user_input") {
|
|
313
|
+
await cursorLiveRuns.release(run);
|
|
314
|
+
return "chain_user_input";
|
|
315
|
+
}
|
|
316
|
+
turn.emitter.closeAll();
|
|
317
|
+
const finalText = trimCurrentTurnAlreadyEmittedCursorText(run.finalText ?? run.textDeltas.join(""), turn.emittedText, run.emittedText);
|
|
318
|
+
if (finalText) {
|
|
319
|
+
await emitTextDeltas(stream, partial, splitTextIntoReplayDeltas(finalText));
|
|
320
|
+
}
|
|
321
|
+
applyCursorApproximateUsage(partial, model, context, cursorLiveRuns.takeTurnInputTokens(run, toolResultInputTokens));
|
|
322
|
+
partial.stopReason = "stop";
|
|
323
|
+
stream.push({ type: "done", reason: "stop", message: partial });
|
|
324
|
+
await cursorLiveRuns.release(run);
|
|
325
|
+
return "stop";
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
await cursorLiveRuns.waitForProgress(run, options.signal);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export async function drainExistingCursorLiveRunBeforeSend(
|
|
333
|
+
stream: AssistantMessageEventStream,
|
|
334
|
+
partial: AssistantMessage,
|
|
335
|
+
model: Model<Api>,
|
|
336
|
+
context: Context,
|
|
337
|
+
signal?: AbortSignal,
|
|
338
|
+
): Promise<LiveRunPreSendOutcome> {
|
|
339
|
+
while (true) {
|
|
340
|
+
const run = getPendingCursorLiveRun(context) ?? getActiveCursorLiveRunForCurrentScope();
|
|
341
|
+
if (!run || run.disposed) return "continue_send";
|
|
342
|
+
|
|
343
|
+
try {
|
|
344
|
+
const outcome = await cursorLiveRuns.withRunLease(run, signal, async () => {
|
|
345
|
+
if (run.disposed) return "continue_send" as const;
|
|
346
|
+
const consumed = cursorLiveRuns.consumeToolResults(run, context, getCursorNativeReplayIdFromToolCallId);
|
|
347
|
+
run.bridgeRun?.resolveToolResults(consumed.toolResults);
|
|
348
|
+
const shouldChainUserInput = run.chainUserInputAfterCompletion || hasTrailingUserMessagesAfterToolResults(context);
|
|
349
|
+
if (shouldChainUserInput) run.chainUserInputAfterCompletion = true;
|
|
350
|
+
while (!cursorLiveRuns.isReady(run)) {
|
|
351
|
+
await cursorLiveRuns.waitForProgress(run, signal);
|
|
352
|
+
}
|
|
353
|
+
if (run.disposed) return "continue_send" as const;
|
|
354
|
+
const drainOutcome = await drainCursorLiveRunTurn(stream, partial, model, context, run, consumed.toolResultInputTokens, {
|
|
355
|
+
mode: shouldChainUserInput ? "chain_user_input" : "emit",
|
|
356
|
+
signal,
|
|
357
|
+
});
|
|
358
|
+
return drainOutcome === "chain_user_input" ? "continue_send" : "stream_ended";
|
|
359
|
+
});
|
|
360
|
+
if (outcome === "continue_send" && !run.disposed && cursorLiveRuns.getActiveForScope(run.sessionAgentScopeKey) === run) {
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
return outcome;
|
|
364
|
+
} catch (error) {
|
|
365
|
+
if (error instanceof CursorLiveRunAbortError) await cursorLiveRuns.release(run);
|
|
366
|
+
throw error;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export function setCursorNativeReplayIdleDisposeMs(value: number): void {
|
|
372
|
+
cursorNativeReplayIdleDisposeMs = value;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
export function resetCursorNativeReplayIdleDisposeMs(): void {
|
|
376
|
+
cursorNativeReplayIdleDisposeMs = DEFAULT_CURSOR_NATIVE_REPLAY_IDLE_DISPOSE_MS;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export { hasTrailingUserMessagesAfterToolResults };
|