pi-cursor-sdk 0.1.18 → 0.1.19
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 +38 -0
- package/README.md +37 -0
- package/docs/cursor-live-smoke-checklist.md +3 -0
- package/docs/cursor-model-ux-spec.md +4 -3
- package/docs/cursor-native-tool-replay.md +96 -2
- package/docs/cursor-testing-lessons.md +234 -5
- package/package.json +8 -2
- package/scripts/debug-provider-events.mjs +403 -0
- package/scripts/debug-sdk-events.mjs +413 -0
- package/scripts/lib/cursor-probe-utils.mjs +52 -0
- package/scripts/lib/cursor-sdk-output-filter.mjs +86 -0
- package/scripts/validate-smoke-jsonl.mjs +27 -3
- package/src/context.ts +45 -32
- package/src/cursor-agent-message-web-tools.ts +172 -0
- package/src/cursor-agents-context.ts +176 -0
- package/src/cursor-incomplete-tool-visibility.ts +118 -0
- package/src/cursor-live-run-coordinator.ts +18 -7
- package/src/cursor-model.ts +12 -0
- package/src/cursor-native-tool-display-registration.ts +1 -4
- package/src/cursor-native-tool-display-replay.ts +63 -5
- package/src/cursor-native-tool-display-tools.ts +20 -0
- package/src/cursor-pi-tool-bridge-diagnostics.ts +11 -1
- package/src/cursor-pi-tool-bridge-run.ts +16 -1
- package/src/cursor-pi-tool-bridge-types.ts +3 -0
- package/src/cursor-provider-errors.ts +96 -0
- package/src/cursor-provider-live-run-drain.ts +181 -62
- package/src/cursor-provider-turn-coordinator.ts +198 -32
- package/src/cursor-provider.ts +270 -83
- package/src/cursor-question-tool.ts +1 -4
- package/src/cursor-sdk-abort-error-guard.ts +109 -0
- package/src/cursor-sdk-event-debug-constants.ts +40 -0
- package/src/cursor-sdk-event-debug-session.ts +163 -0
- package/src/cursor-sdk-event-debug.ts +597 -0
- package/src/cursor-sensitive-text.ts +27 -7
- package/src/cursor-session-agent.ts +25 -3
- package/src/cursor-session-send-policy.ts +43 -0
- package/src/cursor-setting-sources.ts +29 -0
- package/src/cursor-state.ts +1 -5
- package/src/cursor-tool-lifecycle.ts +111 -0
- package/src/cursor-tool-names.ts +12 -0
- package/src/cursor-tool-transcript.ts +4 -2
- package/src/cursor-transcript-tool-formatters.ts +228 -5
- package/src/cursor-transcript-tool-specs.ts +113 -14
- package/src/cursor-transcript-utils.ts +12 -0
- package/src/cursor-web-tool-activity.ts +84 -0
- package/src/index.ts +4 -1
|
@@ -23,8 +23,10 @@ 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
25
|
import { hasUsableText } from "./cursor-record-utils.js";
|
|
26
|
+
import { formatCursorSdkAbortMessage, resolveCursorSdkAbortCause } from "./cursor-provider-errors.js";
|
|
26
27
|
import { formatInactiveCursorReplayTrace } from "./cursor-native-replay-trace.js";
|
|
27
28
|
import { partitionNativeToolsByActiveContext } from "./cursor-native-replay-routing.js";
|
|
29
|
+
import type { CursorSdkEventDebugRecorder } from "./cursor-sdk-event-debug.js";
|
|
28
30
|
|
|
29
31
|
export const DEFAULT_CURSOR_NATIVE_REPLAY_IDLE_DISPOSE_MS = 5 * 60 * 1000;
|
|
30
32
|
const CURSOR_NATIVE_REPLAY_TOOL_ID_PATTERN = /^(cursor-replay-\d+-\d+)-tool-\d+$/;
|
|
@@ -103,6 +105,37 @@ export async function settleCursorLiveToolBatch(run: CursorLiveRun): Promise<voi
|
|
|
103
105
|
await scheduler.wait(75);
|
|
104
106
|
}
|
|
105
107
|
|
|
108
|
+
export function flushPendingCursorLiveRunTraceEventsToStream(
|
|
109
|
+
stream: AssistantMessageEventStream,
|
|
110
|
+
partial: AssistantMessage,
|
|
111
|
+
run: CursorLiveRun,
|
|
112
|
+
options?: { includeTracesBehindQueuedTools?: boolean },
|
|
113
|
+
): void {
|
|
114
|
+
if (run.disposed) return;
|
|
115
|
+
const turn: CursorLiveTurnState = {
|
|
116
|
+
emitter: new CursorPartialContentEmitter(stream, partial, -1, true),
|
|
117
|
+
emittedText: "",
|
|
118
|
+
};
|
|
119
|
+
while (true) {
|
|
120
|
+
const event = cursorLiveRuns.peekEvent(run);
|
|
121
|
+
if (!event || event.type === "tool" || event.type === "bridge-tool") break;
|
|
122
|
+
cursorLiveRuns.shiftEvent(run);
|
|
123
|
+
emitCursorLiveQueuedEvent(turn, event, run);
|
|
124
|
+
}
|
|
125
|
+
if (options?.includeTracesBehindQueuedTools && run.pendingEvents.length > 0) {
|
|
126
|
+
const preserved: CursorLiveQueuedEvent[] = [];
|
|
127
|
+
for (const event of run.pendingEvents) {
|
|
128
|
+
if (event.type === "tool" || event.type === "bridge-tool") {
|
|
129
|
+
preserved.push(event);
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
emitCursorLiveQueuedEvent(turn, event, run);
|
|
133
|
+
}
|
|
134
|
+
run.pendingEvents = preserved;
|
|
135
|
+
}
|
|
136
|
+
turn.emitter.closeAll();
|
|
137
|
+
}
|
|
138
|
+
|
|
106
139
|
function emitCursorLiveQueuedEvent(
|
|
107
140
|
turn: CursorLiveTurnState,
|
|
108
141
|
event: Exclude<CursorLiveQueuedEvent, { type: "tool" } | { type: "bridge-tool" }>,
|
|
@@ -178,6 +211,7 @@ function emitCursorNativeToolUseTurn(
|
|
|
178
211
|
run: CursorLiveRun,
|
|
179
212
|
toolResultInputTokens: number,
|
|
180
213
|
tools: CursorNativeToolDisplayItem[],
|
|
214
|
+
debugRecorder?: CursorSdkEventDebugRecorder,
|
|
181
215
|
): void {
|
|
182
216
|
const shouldTerminate = run.done && !run.finalText?.trim() && !cursorLiveRuns.peekEvent(run);
|
|
183
217
|
for (const tool of tools) {
|
|
@@ -194,6 +228,11 @@ function emitCursorNativeToolUseTurn(
|
|
|
194
228
|
if (block.type === "toolCall") stream.push({ type: "toolcall_end", contentIndex, toolCall: block, partial });
|
|
195
229
|
if (recordCursorNativeToolDisplay({ ...tool, terminate: shouldTerminate })) {
|
|
196
230
|
run.recordedToolDisplayIds.push(tool.id);
|
|
231
|
+
debugRecorder?.recordDrainEvent("native_tool_display_recorded", {
|
|
232
|
+
toolId: tool.id,
|
|
233
|
+
toolName: tool.toolName,
|
|
234
|
+
terminate: shouldTerminate,
|
|
235
|
+
});
|
|
197
236
|
}
|
|
198
237
|
}
|
|
199
238
|
applyCursorApproximateUsage(partial, model, context, cursorLiveRuns.takeTurnInputTokens(run, toolResultInputTokens));
|
|
@@ -202,10 +241,20 @@ function emitCursorNativeToolUseTurn(
|
|
|
202
241
|
cursorLiveRuns.requestIdleDispose(run);
|
|
203
242
|
}
|
|
204
243
|
|
|
205
|
-
function emitInactiveCursorReplayTrace(
|
|
244
|
+
function emitInactiveCursorReplayTrace(
|
|
245
|
+
turn: CursorLiveTurnState,
|
|
246
|
+
tools: CursorNativeToolDisplayItem[],
|
|
247
|
+
debugRecorder?: CursorSdkEventDebugRecorder,
|
|
248
|
+
): void {
|
|
206
249
|
if (tools.length === 0) return;
|
|
207
250
|
for (const tool of tools) {
|
|
208
|
-
|
|
251
|
+
const traceText = formatInactiveCursorReplayTrace(tool);
|
|
252
|
+
debugRecorder?.recordDrainEvent("inactive_replay_trace", {
|
|
253
|
+
toolId: tool.id,
|
|
254
|
+
toolName: tool.toolName,
|
|
255
|
+
traceText,
|
|
256
|
+
});
|
|
257
|
+
turn.emitter.appendThinkingBlock(traceText);
|
|
209
258
|
}
|
|
210
259
|
}
|
|
211
260
|
|
|
@@ -245,21 +294,22 @@ async function emitCursorLiveRunPendingToolUseTurn(
|
|
|
245
294
|
context: Context,
|
|
246
295
|
run: CursorLiveRun,
|
|
247
296
|
toolResultInputTokens: number,
|
|
248
|
-
options: { mode: CursorLiveRunDrainMode; signal?: AbortSignal },
|
|
297
|
+
options: { mode: CursorLiveRunDrainMode; signal?: AbortSignal; debugRecorder?: CursorSdkEventDebugRecorder },
|
|
249
298
|
): Promise<"tool_use" | "handled" | undefined> {
|
|
299
|
+
const debugRecorder = options.debugRecorder ?? run.debugRecorder;
|
|
250
300
|
const eventType = cursorLiveRuns.peekEvent(run)?.type;
|
|
251
301
|
if (eventType !== "tool" && eventType !== "bridge-tool") return undefined;
|
|
252
302
|
await settleCursorLiveToolBatch(run);
|
|
253
303
|
if (options.signal?.aborted) throw new CursorLiveRunAbortError();
|
|
254
304
|
if (eventType === "tool") {
|
|
255
305
|
const { active, inactive } = partitionNativeToolsByActiveContext(context, cursorLiveRuns.collectNativeToolBatch(run));
|
|
256
|
-
if (options.mode === "emit") emitInactiveCursorReplayTrace(turn, inactive);
|
|
306
|
+
if (options.mode === "emit") emitInactiveCursorReplayTrace(turn, inactive, debugRecorder);
|
|
257
307
|
if (active.length === 0) {
|
|
258
308
|
// Inactive-only batch: trace was emitted above; do not emit toolUse.
|
|
259
309
|
return "handled";
|
|
260
310
|
}
|
|
261
311
|
if (options.mode === "emit") turn.emitter.closeAll();
|
|
262
|
-
emitCursorNativeToolUseTurn(stream, partial, model, context, run, toolResultInputTokens, active);
|
|
312
|
+
emitCursorNativeToolUseTurn(stream, partial, model, context, run, toolResultInputTokens, active, debugRecorder);
|
|
263
313
|
} else {
|
|
264
314
|
if (options.mode === "emit") turn.emitter.closeAll();
|
|
265
315
|
const requests = cursorLiveRuns.collectBridgeToolBatch(run);
|
|
@@ -275,73 +325,123 @@ export async function drainCursorLiveRunTurn(
|
|
|
275
325
|
context: Context,
|
|
276
326
|
run: CursorLiveRun,
|
|
277
327
|
toolResultInputTokens: number,
|
|
278
|
-
options: { mode: CursorLiveRunDrainMode; signal?: AbortSignal },
|
|
328
|
+
options: { mode: CursorLiveRunDrainMode; signal?: AbortSignal; debugRecorder?: CursorSdkEventDebugRecorder },
|
|
279
329
|
): Promise<CursorLiveRunDrainOutcome> {
|
|
330
|
+
const debugRecorder = options.debugRecorder ?? run.debugRecorder;
|
|
331
|
+
debugRecorder?.recordDrainEvent("turn_start", {
|
|
332
|
+
mode: options.mode,
|
|
333
|
+
runId: run.id,
|
|
334
|
+
pendingEventCount: run.pendingEvents.length,
|
|
335
|
+
done: run.done,
|
|
336
|
+
});
|
|
337
|
+
let outcome: CursorLiveRunDrainOutcome | undefined;
|
|
338
|
+
let outcomeDetails: Record<string, unknown> = {};
|
|
280
339
|
const turn: CursorLiveTurnState = {
|
|
281
340
|
emitter: new CursorPartialContentEmitter(stream, partial, -1, true),
|
|
282
341
|
emittedText: "",
|
|
283
342
|
};
|
|
284
343
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
344
|
+
try {
|
|
345
|
+
while (true) {
|
|
346
|
+
if (options.mode === "chain_user_input" && cursorLiveRuns.isReady(run)) {
|
|
347
|
+
await cursorLiveRuns.release(run);
|
|
348
|
+
outcome = "chain_user_input";
|
|
349
|
+
return outcome;
|
|
350
|
+
}
|
|
290
351
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
352
|
+
while (cursorLiveRuns.peekEvent(run)) {
|
|
353
|
+
const toolUse = await emitCursorLiveRunPendingToolUseTurn(
|
|
354
|
+
turn,
|
|
355
|
+
stream,
|
|
356
|
+
partial,
|
|
357
|
+
model,
|
|
358
|
+
context,
|
|
359
|
+
run,
|
|
360
|
+
toolResultInputTokens,
|
|
361
|
+
options,
|
|
362
|
+
);
|
|
363
|
+
if (toolUse === "tool_use") {
|
|
364
|
+
outcome = "tool_use";
|
|
365
|
+
return outcome;
|
|
366
|
+
}
|
|
367
|
+
if (toolUse === "handled") continue;
|
|
368
|
+
const event = cursorLiveRuns.shiftEvent(run);
|
|
369
|
+
if (!event || event.type === "tool" || event.type === "bridge-tool") continue;
|
|
370
|
+
if (options.mode === "emit") emitCursorLiveQueuedEvent(turn, event, run);
|
|
371
|
+
}
|
|
308
372
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
stream.push({ type: "error", reason: "error", error: partial });
|
|
324
|
-
await cursorLiveRuns.release(run);
|
|
325
|
-
return "error";
|
|
326
|
-
}
|
|
327
|
-
if (run.done) {
|
|
328
|
-
if (options.mode === "chain_user_input") {
|
|
373
|
+
if (run.disposed) {
|
|
374
|
+
partial.stopReason = "aborted";
|
|
375
|
+
partial.errorMessage = formatCursorSdkAbortMessage(
|
|
376
|
+
resolveCursorSdkAbortCause({ liveRunDisposed: true }),
|
|
377
|
+
);
|
|
378
|
+
stream.push({ type: "error", reason: "aborted", error: partial });
|
|
379
|
+
outcome = "aborted";
|
|
380
|
+
outcomeDetails = { reason: "disposed" };
|
|
381
|
+
return outcome;
|
|
382
|
+
}
|
|
383
|
+
if (run.cancelled) {
|
|
384
|
+
partial.stopReason = "aborted";
|
|
385
|
+
if (run.abortMessage) partial.errorMessage = run.abortMessage;
|
|
386
|
+
stream.push({ type: "error", reason: "aborted", error: partial });
|
|
329
387
|
await cursorLiveRuns.release(run);
|
|
330
|
-
|
|
388
|
+
outcome = "aborted";
|
|
389
|
+
outcomeDetails = { reason: "cancelled" };
|
|
390
|
+
return outcome;
|
|
331
391
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
392
|
+
if (run.errorMessage) {
|
|
393
|
+
partial.stopReason = "error";
|
|
394
|
+
partial.errorMessage = run.errorMessage;
|
|
395
|
+
stream.push({ type: "error", reason: "error", error: partial });
|
|
396
|
+
await cursorLiveRuns.release(run);
|
|
397
|
+
outcome = "error";
|
|
398
|
+
return outcome;
|
|
399
|
+
}
|
|
400
|
+
if (run.done) {
|
|
401
|
+
if (options.mode === "chain_user_input") {
|
|
402
|
+
await cursorLiveRuns.release(run);
|
|
403
|
+
outcome = "chain_user_input";
|
|
404
|
+
outcomeDetails = { reason: "run_done" };
|
|
405
|
+
return outcome;
|
|
406
|
+
}
|
|
407
|
+
turn.emitter.closeAll();
|
|
408
|
+
const finalText = trimCurrentTurnAlreadyEmittedCursorText(run.finalText ?? run.textDeltas.join(""), turn.emittedText, run.emittedText);
|
|
409
|
+
if (finalText) {
|
|
410
|
+
await emitTextDeltas(stream, partial, splitTextIntoReplayDeltas(finalText));
|
|
411
|
+
}
|
|
412
|
+
applyCursorApproximateUsage(partial, model, context, cursorLiveRuns.takeTurnInputTokens(run, toolResultInputTokens));
|
|
413
|
+
partial.stopReason = "stop";
|
|
414
|
+
stream.push({ type: "done", reason: "stop", message: partial });
|
|
415
|
+
await cursorLiveRuns.release(run);
|
|
416
|
+
outcome = "stop";
|
|
417
|
+
outcomeDetails = { finalTextLength: finalText.length };
|
|
418
|
+
return outcome;
|
|
336
419
|
}
|
|
337
|
-
applyCursorApproximateUsage(partial, model, context, cursorLiveRuns.takeTurnInputTokens(run, toolResultInputTokens));
|
|
338
|
-
partial.stopReason = "stop";
|
|
339
|
-
stream.push({ type: "done", reason: "stop", message: partial });
|
|
340
|
-
await cursorLiveRuns.release(run);
|
|
341
|
-
return "stop";
|
|
342
|
-
}
|
|
343
420
|
|
|
344
|
-
|
|
421
|
+
await cursorLiveRuns.waitForProgress(run, options.signal);
|
|
422
|
+
}
|
|
423
|
+
} catch (error) {
|
|
424
|
+
if (!outcome) {
|
|
425
|
+
if (error instanceof CursorLiveRunAbortError) {
|
|
426
|
+
outcome = "aborted";
|
|
427
|
+
outcomeDetails = { reason: "signal_aborted" };
|
|
428
|
+
} else {
|
|
429
|
+
outcome = "error";
|
|
430
|
+
outcomeDetails = {
|
|
431
|
+
reason: "drain_error",
|
|
432
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
throw error;
|
|
437
|
+
} finally {
|
|
438
|
+
debugRecorder?.recordDrainEvent("turn_end", {
|
|
439
|
+
outcome: outcome ?? "error",
|
|
440
|
+
runId: run.id,
|
|
441
|
+
pendingEventCount: run.pendingEvents.length,
|
|
442
|
+
done: run.done,
|
|
443
|
+
...outcomeDetails,
|
|
444
|
+
});
|
|
345
445
|
}
|
|
346
446
|
}
|
|
347
447
|
|
|
@@ -351,10 +451,15 @@ export async function drainExistingCursorLiveRunBeforeSend(
|
|
|
351
451
|
model: Model<Api>,
|
|
352
452
|
context: Context,
|
|
353
453
|
signal?: AbortSignal,
|
|
454
|
+
turnDebugRecorder?: CursorSdkEventDebugRecorder,
|
|
354
455
|
): Promise<LiveRunPreSendOutcome> {
|
|
456
|
+
turnDebugRecorder?.recordDrainEvent("pre_send_start", {});
|
|
355
457
|
while (true) {
|
|
356
458
|
const run = getPendingCursorLiveRun(context) ?? getActiveCursorLiveRunForCurrentScope();
|
|
357
|
-
if (!run || run.disposed)
|
|
459
|
+
if (!run || run.disposed) {
|
|
460
|
+
turnDebugRecorder?.recordDrainEvent("pre_send_end", { outcome: "continue_send", reason: "no_pending_run" });
|
|
461
|
+
return "continue_send";
|
|
462
|
+
}
|
|
358
463
|
|
|
359
464
|
try {
|
|
360
465
|
const outcome = await cursorLiveRuns.withRunLease(run, signal, async () => {
|
|
@@ -370,14 +475,28 @@ export async function drainExistingCursorLiveRunBeforeSend(
|
|
|
370
475
|
const drainOutcome = await drainCursorLiveRunTurn(stream, partial, model, context, run, consumed.toolResultInputTokens, {
|
|
371
476
|
mode: shouldChainUserInput ? "chain_user_input" : "emit",
|
|
372
477
|
signal,
|
|
478
|
+
debugRecorder: turnDebugRecorder,
|
|
373
479
|
});
|
|
374
|
-
|
|
480
|
+
const mapped = drainOutcome === "chain_user_input" ? "continue_send" : "stream_ended";
|
|
481
|
+
turnDebugRecorder?.recordDrainEvent("pre_send_iteration", {
|
|
482
|
+
runId: run.id,
|
|
483
|
+
drainOutcome,
|
|
484
|
+
outcome: mapped,
|
|
485
|
+
shouldChainUserInput,
|
|
486
|
+
});
|
|
487
|
+
return mapped;
|
|
375
488
|
});
|
|
376
489
|
if (outcome === "continue_send" && !run.disposed && cursorLiveRuns.getActiveForScope(run.sessionAgentScopeKey) === run) {
|
|
377
490
|
continue;
|
|
378
491
|
}
|
|
492
|
+
turnDebugRecorder?.recordDrainEvent("pre_send_end", { outcome, runId: run.id });
|
|
379
493
|
return outcome;
|
|
380
494
|
} catch (error) {
|
|
495
|
+
turnDebugRecorder?.recordDrainEvent("pre_send_end", {
|
|
496
|
+
outcome: error instanceof CursorLiveRunAbortError ? "aborted" : "error",
|
|
497
|
+
runId: run.id,
|
|
498
|
+
reason: error instanceof CursorLiveRunAbortError ? "signal_aborted" : "drain_error",
|
|
499
|
+
});
|
|
381
500
|
if (error instanceof CursorLiveRunAbortError) await cursorLiveRuns.release(run);
|
|
382
501
|
throw error;
|
|
383
502
|
}
|