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.
Files changed (88) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +49 -9
  3. package/docs/cursor-dogfood-checklist.md +57 -0
  4. package/docs/cursor-live-smoke-checklist.md +115 -9
  5. package/docs/cursor-model-ux-spec.md +57 -17
  6. package/docs/cursor-native-tool-replay.md +15 -7
  7. package/docs/cursor-native-tool-visual-audit.md +104 -59
  8. package/docs/cursor-testing-lessons.md +8 -3
  9. package/docs/cursor-tool-surfaces.md +69 -0
  10. package/package.json +34 -10
  11. package/scripts/debug-provider-events.d.mts +59 -0
  12. package/scripts/debug-provider-events.mjs +70 -175
  13. package/scripts/debug-sdk-events.d.mts +90 -0
  14. package/scripts/debug-sdk-events.mjs +36 -98
  15. package/scripts/fixtures/plan-strip-shim/index.ts +12 -0
  16. package/scripts/isolated-cursor-smoke.sh +264 -102
  17. package/scripts/lib/cursor-child-process.d.mts +10 -0
  18. package/scripts/lib/cursor-child-process.mjs +50 -0
  19. package/scripts/lib/cursor-cli-args.d.mts +63 -0
  20. package/scripts/lib/cursor-cli-args.mjs +129 -0
  21. package/scripts/lib/cursor-script-fail.d.mts +1 -0
  22. package/scripts/lib/cursor-script-fail.mjs +13 -0
  23. package/scripts/lib/cursor-sdk-output-filter.d.mts +5 -0
  24. package/scripts/lib/cursor-smoke-env.d.mts +38 -0
  25. package/scripts/lib/cursor-smoke-env.mjs +81 -0
  26. package/scripts/lib/cursor-smoke-shell.sh +174 -0
  27. package/scripts/lib/cursor-visual-render.d.mts +15 -0
  28. package/scripts/lib/cursor-visual-render.mjs +131 -0
  29. package/scripts/probe-mcp-coldstart.mjs +20 -38
  30. package/scripts/refresh-cursor-model-snapshots.mjs +29 -65
  31. package/scripts/steering-rpc-smoke.mjs +170 -65
  32. package/scripts/tmux-live-smoke.sh +152 -98
  33. package/scripts/visual-tui-smoke.mjs +659 -0
  34. package/shared/cursor-sdk-event-debug-env.d.mts +12 -0
  35. package/shared/cursor-sdk-event-debug-env.mjs +13 -0
  36. package/shared/cursor-sensitive-text.d.mts +1 -0
  37. package/{scripts/lib/cursor-probe-utils.mjs → shared/cursor-sensitive-text.mjs} +1 -13
  38. package/shared/cursor-setting-sources.d.mts +5 -0
  39. package/shared/cursor-setting-sources.mjs +22 -0
  40. package/src/context.ts +21 -12
  41. package/src/cursor-bridge-contract.ts +1 -3
  42. package/src/cursor-incomplete-tool-visibility.ts +22 -5
  43. package/src/cursor-native-tool-display-registration.ts +63 -27
  44. package/src/cursor-native-tool-display-replay.ts +246 -144
  45. package/src/cursor-native-tool-display-state.ts +2 -0
  46. package/src/cursor-native-tool-display-tools.ts +149 -41
  47. package/src/cursor-provider-live-run-drain.ts +1 -52
  48. package/src/cursor-provider-run-finalizer.ts +235 -0
  49. package/src/cursor-provider-run-outcome.ts +149 -0
  50. package/src/cursor-provider-turn-api-key.ts +8 -0
  51. package/src/cursor-provider-turn-coordinator.ts +98 -446
  52. package/src/cursor-provider-turn-display-router.ts +216 -0
  53. package/src/cursor-provider-turn-emit.ts +59 -0
  54. package/src/cursor-provider-turn-finalize.ts +119 -0
  55. package/src/cursor-provider-turn-lifecycle-emitter.ts +97 -0
  56. package/src/cursor-provider-turn-message-offset.ts +15 -0
  57. package/src/cursor-provider-turn-prepare.ts +216 -0
  58. package/src/cursor-provider-turn-runner.ts +138 -0
  59. package/src/cursor-provider-turn-sdk-normalizer.ts +88 -0
  60. package/src/cursor-provider-turn-send.ts +103 -0
  61. package/src/cursor-provider-turn-shell-output.ts +107 -0
  62. package/src/cursor-provider-turn-tool-ledger.ts +126 -0
  63. package/src/cursor-provider-turn-types.ts +87 -0
  64. package/src/cursor-provider.ts +16 -504
  65. package/src/cursor-replay-activity-builders.ts +276 -0
  66. package/src/cursor-replay-source-names.ts +33 -0
  67. package/src/cursor-replay-summary-args.ts +191 -0
  68. package/src/cursor-replay-tool-details.ts +464 -0
  69. package/src/cursor-run-final-text.ts +56 -0
  70. package/src/cursor-sdk-abort-error-guard.ts +4 -0
  71. package/src/cursor-sdk-event-debug-constants.ts +14 -5
  72. package/src/cursor-sdk-event-debug.ts +2 -1
  73. package/src/cursor-sensitive-text.ts +3 -36
  74. package/src/cursor-session-agent.ts +3 -1
  75. package/src/cursor-setting-sources.ts +7 -10
  76. package/src/cursor-state.ts +232 -28
  77. package/src/cursor-tool-lifecycle.ts +9 -8
  78. package/src/cursor-tool-manifest.ts +41 -0
  79. package/src/cursor-tool-names.ts +18 -106
  80. package/src/cursor-tool-presentation-registry.ts +556 -0
  81. package/src/cursor-tool-transcript.ts +1 -1
  82. package/src/cursor-tool-visibility.ts +3 -27
  83. package/src/cursor-transcript-tool-formatters.ts +0 -59
  84. package/src/cursor-transcript-tool-specs.ts +158 -233
  85. package/src/cursor-transcript-utils.ts +0 -44
  86. package/src/cursor-web-tool-activity.ts +10 -60
  87. package/src/cursor-web-tool-args.ts +39 -0
  88. package/src/index.ts +4 -10
@@ -1,78 +1,26 @@
1
1
  import {
2
2
  type Api,
3
+ type AssistantMessage,
3
4
  type AssistantMessageEventStream,
4
5
  type Context,
5
6
  createAssistantMessageEventStream,
6
7
  type Model,
7
8
  type SimpleStreamOptions,
8
- type AssistantMessage,
9
9
  } from "@earendil-works/pi-ai";
10
- import { Agent, createAgentPlatform } from "@cursor/sdk";
11
- import type { SDKAgent } from "@cursor/sdk";
12
- import { installCursorMcpToolTimeoutOverride } from "./cursor-mcp-timeout-override.js";
13
- import { installCursorSdkOutputFilter, suppressCursorSdkOutput } from "./cursor-sdk-output-filter.js";
14
- import {
15
- acquireSessionCursorAgent,
16
- buildCursorSessionSendPrompt,
17
- disposeAllSessionCursorAgents,
18
- planCursorSessionSend,
19
- resetSessionCursorAgent,
20
- } from "./cursor-session-agent.js";
21
- import {
22
- type CursorPiBridgeToolRequest,
23
- type CursorPiToolBridgeRun,
24
- } from "./cursor-pi-tool-bridge.js";
25
- import {
26
- applyCursorApproximateUsage,
27
- estimateCursorPromptInputTokens,
28
- getCursorPromptOptions,
29
- } from "./cursor-usage-accounting.js";
30
- import { getCursorSessionCwd } from "./cursor-session-cwd.js";
31
- import { getActiveContextToolNames } from "./cursor-context-tools.js";
32
- import { CursorLiveRunAbortError, type CursorLiveRun } from "./cursor-live-run-coordinator.js";
33
10
  import {
34
- abandonSessionCursorAgent,
35
- createCursorNativeReplayId,
36
- cursorLiveRuns,
37
- drainCursorLiveRunTurn,
38
- drainExistingCursorLiveRunBeforeSend,
39
- flushPendingCursorLiveRunTraceEventsToStream,
40
11
  DEFAULT_CURSOR_NATIVE_REPLAY_IDLE_DISPOSE_MS,
41
12
  getPendingCursorLiveRun,
42
13
  hasTrailingUserMessagesAfterToolResults,
43
14
  releaseAllPendingCursorLiveRunsForTests,
44
15
  resetCursorNativeReplayIdleDisposeMs,
45
- selectCursorFinalText,
46
16
  setCursorNativeReplayIdleDisposeMs,
47
- settleCursorLiveToolBatch,
48
17
  } from "./cursor-provider-live-run-drain.js";
49
- import { getEffectiveFastForModelId } from "./cursor-state.js";
50
- import { buildCursorModelSelection } from "./model-discovery.js";
51
- import { getCheckpointContextWindow, saveCachedContextWindow } from "./context-window-cache.js";
52
- import {
53
- attachCursorSdkEventDebugPiStreamTap,
54
- CursorSdkEventDebugSink,
55
- } from "./cursor-sdk-event-debug.js";
56
- import { CursorSdkTurnCoordinator } from "./cursor-provider-turn-coordinator.js";
57
- import { isCursorNativeToolDisplayRuntimeEnabled } from "./cursor-native-tool-display.js";
58
- import {
59
- formatCursorSdkAbortMessage,
60
- formatCursorSdkRunFailureDetail,
61
- MISSING_CURSOR_API_KEY_MESSAGE,
62
- resolveCursorSdkAbortCause,
63
- sanitizeCursorProviderError,
64
- } from "./cursor-provider-errors.js";
65
- import { getEffectiveCursorSettingSources } from "./cursor-setting-sources.js";
66
- import { hasUsableText } from "./cursor-record-utils.js";
67
- import {
68
- countCursorAgentMessages,
69
- loadCursorTranscriptWebToolCallsAfterOffset,
70
- } from "./cursor-agent-message-web-tools.js";
18
+ import { cursorLiveRuns } from "./cursor-provider-live-run-drain.js";
19
+ import { disposeAllSessionCursorAgents } from "./cursor-session-agent.js";
20
+ import { attachCursorSdkEventDebugPiStreamTap, type CursorSdkEventDebugSink } from "./cursor-sdk-event-debug.js";
71
21
  import { installCursorSdkAbortErrorSuppression } from "./cursor-sdk-abort-error-guard.js";
72
- import {
73
- buildIncompleteCursorToolRunOutcome,
74
- type IncompleteCursorToolRunOutcomeInput,
75
- } from "./cursor-incomplete-tool-visibility.js";
22
+ import { sanitizeCursorProviderError } from "./cursor-provider-errors.js";
23
+ import { CursorProviderTurnRunner, resolveCursorApiKey } from "./cursor-provider-turn-runner.js";
76
24
 
77
25
  function makeInitialMessage(model: Model<Api>): AssistantMessage {
78
26
  return {
@@ -94,41 +42,6 @@ function makeInitialMessage(model: Model<Api>): AssistantMessage {
94
42
  };
95
43
  }
96
44
 
97
- const CURSOR_API_KEY_ENV_VAR = "CURSOR_API_KEY";
98
-
99
- function resolveCursorApiKey(apiKey?: string): string | undefined {
100
- const trimmed = apiKey?.trim();
101
- if (!trimmed) return undefined;
102
- if (trimmed === CURSOR_API_KEY_ENV_VAR) return process.env.CURSOR_API_KEY?.trim();
103
- return trimmed;
104
- }
105
-
106
- async function cacheSdkContextWindow(agentId: string, modelId: string): Promise<void> {
107
- try {
108
- const platform = await createAgentPlatform();
109
- const checkpoint = await platform.checkpointStore.loadLatest(agentId);
110
- const contextWindow = getCheckpointContextWindow(checkpoint);
111
- if (contextWindow) saveCachedContextWindow(modelId, contextWindow);
112
- } catch {
113
- // Context-window cache failures must not affect response streaming.
114
- }
115
- }
116
-
117
- function hasCursorAssistantText(resultText: unknown, textDeltas: readonly string[], fallbackText?: string): boolean {
118
- return (
119
- hasUsableText(typeof resultText === "string" ? resultText : undefined) ||
120
- hasUsableText(textDeltas.join("")) ||
121
- hasUsableText(fallbackText)
122
- );
123
- }
124
-
125
- function discardIncompleteToolsForRunOutcome(
126
- turnCoordinator: CursorSdkTurnCoordinator | undefined,
127
- outcome: IncompleteCursorToolRunOutcomeInput,
128
- ): void {
129
- turnCoordinator?.discardIncompleteStartedToolCalls(buildIncompleteCursorToolRunOutcome(outcome));
130
- }
131
-
132
45
  export function streamCursor(
133
46
  model: Model<Api>,
134
47
  context: Context,
@@ -140,422 +53,21 @@ export function streamCursor(
140
53
 
141
54
  (async () => {
142
55
  const partial = makeInitialMessage(model);
143
- let agent: SDKAgent | null = null;
144
- let activeLiveRun: CursorLiveRun | undefined;
145
- let bridgeRun: CursorPiToolBridgeRun | undefined;
146
- let liveRunForBridgeQueue: CursorLiveRun | undefined;
147
- const queuedBridgeRequestsBeforeLiveRun: CursorPiBridgeToolRequest[] = [];
148
- let resolvedApiKey: string | undefined;
149
- let sessionAgentScopeKey = "";
150
- let abortSignal: AbortSignal | undefined;
151
- let abortListener: (() => void) | undefined;
152
- let restoreCursorSdkOutputFilter: (() => void) | undefined;
153
- let sdkEventDebug: CursorSdkEventDebugSink | undefined;
154
- let deferSdkEventDebugFinalize = false;
155
56
  const sdkAbortErrorSuppression = installCursorSdkAbortErrorSuppression();
156
57
 
157
- const pushSanitizedStreamError = (error: unknown, reason: "error" | "aborted" = "error"): void => {
158
- partial.stopReason = reason;
159
- partial.errorMessage =
160
- reason === "aborted"
161
- ? formatCursorSdkAbortMessage(resolveCursorSdkAbortCause({ signalAborted: options?.signal?.aborted }))
162
- : sanitizeCursorProviderError(error, resolvedApiKey ?? options?.apiKey);
163
- stream.push({ type: "error", reason, error: partial });
164
- };
165
-
166
- const getCursorAgentMessageOffset = async (agentId: string, cwd: string): Promise<number | undefined> => {
167
- try {
168
- return await countCursorAgentMessages(agentId, cwd);
169
- } catch (error) {
170
- sdkEventDebug?.recordError("cursor_agent_message_count", error);
171
- return undefined;
172
- }
173
- };
174
-
175
- const replayCursorTranscriptWebToolCalls = async (
176
- agentId: string,
177
- cwd: string,
178
- messageOffset: number | undefined,
179
- turnCoordinator: CursorSdkTurnCoordinator,
180
- ): Promise<void> => {
181
- try {
182
- const transcriptToolCalls = await loadCursorTranscriptWebToolCallsAfterOffset({ agentId, cwd, offset: messageOffset });
183
- if (transcriptToolCalls.length === 0) return;
184
- sdkEventDebug?.recordCoordinatorEvent("cursor-transcript-web-tools", {
185
- agentId,
186
- messageOffset,
187
- count: transcriptToolCalls.length,
188
- });
189
- turnCoordinator.handleTranscriptCompletedToolCalls(transcriptToolCalls);
190
- } catch (error) {
191
- sdkEventDebug?.recordError("cursor_transcript_web_tools", error);
192
- }
193
- };
58
+ const runner = new CursorProviderTurnRunner({
59
+ model,
60
+ context,
61
+ stream,
62
+ partial,
63
+ options,
64
+ sdkEventDebugRef,
65
+ });
194
66
 
195
67
  try {
196
- let turnCoordinatorForCleanup: CursorSdkTurnCoordinator | undefined;
197
- try {
198
- const throwIfAborted = (): void => {
199
- if (options?.signal?.aborted) throw new CursorLiveRunAbortError();
200
- };
201
-
202
- stream.push({ type: "start", partial });
203
- throwIfAborted();
204
-
205
- const cwd = getCursorSessionCwd();
206
- sdkEventDebug = CursorSdkEventDebugSink.maybeCreate({
207
- cwd,
208
- modelId: model.id,
209
- provider: model.provider,
210
- });
211
- sdkEventDebugRef.current = sdkEventDebug;
212
- sdkEventDebug?.recordContextSnapshot(context);
213
-
214
- if ((await drainExistingCursorLiveRunBeforeSend(stream, partial, model, context, options?.signal, sdkEventDebug)) === "stream_ended") {
215
- sdkEventDebug?.recordFinalPartial(partial);
216
- await sdkEventDebug?.finalize();
217
- sdkEventDebugRef.current = undefined;
218
- return;
219
- }
220
-
221
- const apiKey = resolveCursorApiKey(options?.apiKey);
222
- if (!apiKey) throw new Error(MISSING_CURSOR_API_KEY_MESSAGE);
223
- resolvedApiKey = apiKey;
224
-
225
- const fastEnabled = getEffectiveFastForModelId(model.id);
226
- const selection = buildCursorModelSelection(model.id, options?.reasoning ?? "off", fastEnabled);
227
- const settingSources = getEffectiveCursorSettingSources();
228
-
229
- installCursorMcpToolTimeoutOverride();
230
- restoreCursorSdkOutputFilter = installCursorSdkOutputFilter();
231
- const sessionAgentAcquireParams = {
232
- apiKey,
233
- cwd,
234
- modelSelection: selection,
235
- settingSources,
236
- debugRecorder: sdkEventDebug,
237
- onBridgeToolRequest: (request: CursorPiBridgeToolRequest) => {
238
- if (liveRunForBridgeQueue && !liveRunForBridgeQueue.disposed) {
239
- cursorLiveRuns.queueEvent(liveRunForBridgeQueue, { type: "bridge-tool", request });
240
- } else {
241
- queuedBridgeRequestsBeforeLiveRun.push(request);
242
- }
243
- },
244
- createAgent: (createOptions: Parameters<typeof Agent.create>[0]) =>
245
- suppressCursorSdkOutput(() => Agent.create(createOptions)),
246
- };
247
- let sessionAgentLease = await acquireSessionCursorAgent(sessionAgentAcquireParams);
248
- sessionAgentScopeKey = sessionAgentLease.scopeKey;
249
- agent = sessionAgentLease.agent;
250
- bridgeRun = sessionAgentLease.bridgeRun;
251
- throwIfAborted();
252
-
253
- const promptOptions = getCursorPromptOptions(model);
254
- let sendPlan = planCursorSessionSend(sessionAgentLease.sendState, context);
255
- let prompt = buildCursorSessionSendPrompt(context, promptOptions, sendPlan);
256
- if (sendPlan.resetAgent) {
257
- await resetSessionCursorAgent(sessionAgentLease.scopeKey);
258
- sessionAgentLease = await acquireSessionCursorAgent(sessionAgentAcquireParams);
259
- sessionAgentScopeKey = sessionAgentLease.scopeKey;
260
- agent = sessionAgentLease.agent;
261
- bridgeRun = sessionAgentLease.bridgeRun;
262
- sendPlan = planCursorSessionSend(sessionAgentLease.sendState, context);
263
- prompt = buildCursorSessionSendPrompt(context, promptOptions, sendPlan);
264
- }
265
- const bootstrap = sendPlan.mode === "bootstrap";
266
- const sessionBridgeRun = sessionAgentLease.bridgeRun;
267
- const promptInputTokens = estimateCursorPromptInputTokens(prompt, promptOptions);
268
- const useNativeToolReplay = isCursorNativeToolDisplayRuntimeEnabled();
269
- const activeToolNames = getActiveContextToolNames(context);
270
- sdkEventDebug?.recordProviderMeta({
271
- model: {
272
- id: model.id,
273
- provider: model.provider,
274
- api: model.api,
275
- reasoning: options?.reasoning ?? "off",
276
- fastEnabled,
277
- selection,
278
- },
279
- settingSources: settingSources ?? null,
280
- sendState: sessionAgentLease.sendState,
281
- sendPlan,
282
- promptOptions,
283
- activeToolNames: activeToolNames ? [...activeToolNames] : [],
284
- sessionAgentScopeKey,
285
- bridgeRunId: bridgeRun?.id,
286
- });
287
- const nativeReplayId = createCursorNativeReplayId();
288
- const textDeltas: string[] = [];
289
- const useLiveRun = useNativeToolReplay || bridgeRun !== undefined;
290
- const liveRun: CursorLiveRun | undefined = useLiveRun
291
- ? cursorLiveRuns.start({
292
- id: useNativeToolReplay ? nativeReplayId : bridgeRun!.id,
293
- agent,
294
- bridgeRun,
295
- sessionBridgeRun,
296
- sessionAgentScopeKey,
297
- promptInputTokens,
298
- textDeltas,
299
- debugRecorder: sdkEventDebug,
300
- })
301
- : undefined;
302
- if (liveRun) {
303
- activeLiveRun = liveRun;
304
- liveRunForBridgeQueue = liveRun;
305
- for (const request of queuedBridgeRequestsBeforeLiveRun.splice(0)) {
306
- cursorLiveRuns.queueEvent(liveRun, { type: "bridge-tool", request });
307
- }
308
- }
309
- const turnCoordinator = new CursorSdkTurnCoordinator({
310
- stream,
311
- partial,
312
- cwd,
313
- resolvedApiKey,
314
- liveRun,
315
- useNativeToolReplay,
316
- activeToolNames,
317
- nativeReplayId,
318
- textDeltas,
319
- debugRecorder: sdkEventDebug,
320
- });
321
- turnCoordinatorForCleanup = turnCoordinator;
322
-
323
- // Handle abort signal
324
- let run: Awaited<ReturnType<SDKAgent["send"]>> | null = null;
325
- abortListener = () => {
326
- sdkAbortErrorSuppression.suppressAbortErrors();
327
- activeLiveRun?.bridgeRun?.cancel("Cursor SDK run aborted");
328
- if (run) {
329
- run.cancel().catch(() => {});
330
- }
331
- };
332
- abortSignal = options?.signal;
333
- abortSignal?.addEventListener("abort", abortListener, { once: true });
334
-
335
- throwIfAborted();
336
- sdkEventDebug?.recordSendMeta({
337
- mode: sendPlan.mode,
338
- reason: sendPlan.reason,
339
- resetAgent: sendPlan.resetAgent,
340
- bootstrap,
341
- promptText: prompt.text,
342
- imageCount: prompt.images.length,
343
- useNativeToolReplay,
344
- bridgeEnabled: bridgeRun !== undefined,
345
- nativeReplayId,
346
- promptInputTokens,
347
- });
348
- const sendPayload = {
349
- text: prompt.text,
350
- images: prompt.images.length > 0 ? prompt.images : undefined,
351
- };
352
- sdkEventDebug?.recordSendPayload(sendPayload);
353
- const cursorAgentMessageOffset = await getCursorAgentMessageOffset(agent.agentId, cwd);
354
- sdkEventDebug?.recordProviderEvent("agent_send_start", sendPayload);
355
- run = await agent.send(sendPayload, {
356
- onDelta: (args) => {
357
- sdkEventDebug?.recordOnDelta(args.update);
358
- turnCoordinator.handleDelta(args.update);
359
- },
360
- onStep: (args) => {
361
- sdkEventDebug?.recordOnStep(args.step);
362
- turnCoordinator.handleStep(args.step);
363
- },
364
- });
365
- sdkEventDebug?.recordRunMeta({
366
- runId: run.id,
367
- agentId: run.agentId,
368
- status: run.status,
369
- });
370
- sdkEventDebug?.attachRunStream(run);
371
- sdkEventDebug?.recordProviderEvent("agent_send_returned", {
372
- runId: run.id,
373
- agentId: run.agentId,
374
- status: run.status,
375
- });
376
- if (liveRun) cursorLiveRuns.attachSdkRun(liveRun, run);
377
- if (options?.signal?.aborted) {
378
- sdkAbortErrorSuppression.suppressAbortErrors();
379
- liveRun?.bridgeRun?.cancel("Cursor SDK run aborted");
380
- await run.cancel().catch(() => {});
381
- throw new CursorLiveRunAbortError();
382
- }
383
- const activeRun = run;
384
- const activeSessionAgentLease = sessionAgentLease;
385
-
386
- if (liveRun) {
387
- deferSdkEventDebugFinalize = true;
388
- const waitCompletion = run
389
- .wait()
390
- .then(async (result) => {
391
- sdkEventDebug?.recordWaitResult(result);
392
- const finishedSuccessfully = result.status === "finished" && !options?.signal?.aborted;
393
- if (finishedSuccessfully) {
394
- await replayCursorTranscriptWebToolCalls(activeRun.agentId, cwd, cursorAgentMessageOffset, turnCoordinator);
395
- }
396
- const finalCursorText = finishedSuccessfully
397
- ? selectCursorFinalText(result.result, liveRun.textDeltas, liveRun.emittedText, turnCoordinator.planTextCandidate)
398
- : "";
399
- discardIncompleteToolsForRunOutcome(turnCoordinator, {
400
- status: result.status,
401
- signalAborted: options?.signal?.aborted,
402
- assistantTextProduced:
403
- finishedSuccessfully && hasCursorAssistantText(result.result, liveRun.textDeltas, turnCoordinator.planTextCandidate),
404
- });
405
- await sdkEventDebug?.captureRunArtifacts(run);
406
- if (liveRun.disposed) return;
407
- await cacheSdkContextWindow(liveRun.agent.agentId, model.id);
408
- if (liveRun.disposed) return;
409
- if (finishedSuccessfully) {
410
- activeSessionAgentLease.commitSend(context, bootstrap);
411
- cursorLiveRuns.markFinished(liveRun, finalCursorText);
412
- } else if (result.status === "cancelled" || options?.signal?.aborted) {
413
- cursorLiveRuns.markCancelled(
414
- liveRun,
415
- formatCursorSdkAbortMessage(
416
- resolveCursorSdkAbortCause({
417
- signalAborted: options?.signal?.aborted,
418
- sdkStatusCancelled: result.status === "cancelled",
419
- }),
420
- ),
421
- );
422
- } else {
423
- const failureDetail = formatCursorSdkRunFailureDetail(result, run?.result);
424
- cursorLiveRuns.markError(
425
- liveRun,
426
- sanitizeCursorProviderError(failureDetail, resolvedApiKey ?? options?.apiKey),
427
- );
428
- }
429
- })
430
- .catch(async (error: unknown) => {
431
- sdkEventDebug?.recordWaitResult({ status: "error", error: String(error) });
432
- sdkEventDebug?.recordError("run_wait", error);
433
- discardIncompleteToolsForRunOutcome(turnCoordinatorForCleanup, { status: "error" });
434
- await sdkEventDebug?.captureRunArtifacts(run);
435
- if (liveRun.disposed) return;
436
- cursorLiveRuns.markError(liveRun, sanitizeCursorProviderError(error, resolvedApiKey ?? options?.apiKey));
437
- });
438
-
439
- try {
440
- await cursorLiveRuns.withRunLease(liveRun, options?.signal, async () => {
441
- await cursorLiveRuns.waitForProgress(liveRun, options?.signal);
442
- await settleCursorLiveToolBatch(liveRun);
443
- turnCoordinator.closeTraceBlock();
444
- await drainCursorLiveRunTurn(stream, partial, model, context, liveRun, 0, {
445
- mode: "emit",
446
- signal: options?.signal,
447
- debugRecorder: sdkEventDebug,
448
- });
449
- });
450
- } catch (error) {
451
- if (error instanceof CursorLiveRunAbortError) {
452
- sdkAbortErrorSuppression.suppressAbortErrors();
453
- discardIncompleteToolsForRunOutcome(turnCoordinator, { status: "cancelled", signalAborted: true });
454
- turnCoordinator.closeTraceBlock();
455
- flushPendingCursorLiveRunTraceEventsToStream(stream, partial, liveRun, {
456
- includeTracesBehindQueuedTools: true,
457
- });
458
- await cursorLiveRuns.release(liveRun);
459
- }
460
- throw error;
461
- } finally {
462
- sdkEventDebugRef.current = undefined;
463
- activeSessionAgentLease.trackRunCompletion(waitCompletion);
464
- void waitCompletion
465
- .finally(async () => {
466
- try {
467
- sdkEventDebug?.recordFinalPartial(partial);
468
- await sdkEventDebug?.finalize();
469
- } finally {
470
- sdkAbortErrorSuppression.dispose();
471
- }
472
- })
473
- .catch(() => {});
474
- }
475
- agent = null;
476
- return;
477
- }
478
-
479
- const result = await run.wait();
480
- sdkEventDebug?.recordWaitResult(result);
481
- const finishedSuccessfully = result.status === "finished" && !options?.signal?.aborted;
482
- if (finishedSuccessfully) {
483
- await replayCursorTranscriptWebToolCalls(run.agentId, cwd, cursorAgentMessageOffset, turnCoordinator);
484
- }
485
- const finalCursorText = finishedSuccessfully
486
- ? selectCursorFinalText(result.result, textDeltas, textDeltas.join(""), turnCoordinator.planTextCandidate, {
487
- allowPartialPrefix: true,
488
- })
489
- : "";
490
- discardIncompleteToolsForRunOutcome(turnCoordinator, {
491
- status: result.status,
492
- signalAborted: options?.signal?.aborted,
493
- assistantTextProduced:
494
- finishedSuccessfully && hasCursorAssistantText(result.result, textDeltas, turnCoordinator.planTextCandidate),
495
- });
496
- await sdkEventDebug?.captureRunArtifacts(run);
497
- await cacheSdkContextWindow(agent.agentId, model.id);
498
-
499
- // Close any open thinking/activity trace, then use the final run result only when
500
- // Cursor did not stream text deltas.
501
- turnCoordinator.closeTraceBlock();
502
-
503
- if (result.status === "cancelled") {
504
- await abandonSessionCursorAgent(sessionAgentScopeKey);
505
- partial.stopReason = "aborted";
506
- partial.errorMessage = formatCursorSdkAbortMessage(
507
- resolveCursorSdkAbortCause({
508
- signalAborted: options?.signal?.aborted,
509
- sdkStatusCancelled: true,
510
- }),
511
- );
512
- stream.push({ type: "error", reason: "aborted", error: partial });
513
- } else if (result.status === "error") {
514
- await abandonSessionCursorAgent(sessionAgentScopeKey);
515
- partial.stopReason = "error";
516
- const failureDetail = formatCursorSdkRunFailureDetail(result, run.result);
517
- partial.errorMessage = sanitizeCursorProviderError(failureDetail, resolvedApiKey ?? options?.apiKey);
518
- stream.push({ type: "error", reason: "error", error: partial });
519
- } else {
520
- sessionAgentLease.commitSend(context, bootstrap);
521
- turnCoordinator.flushText(hasUsableText(finalCursorText) ? [finalCursorText] : []);
522
- applyCursorApproximateUsage(partial, model, context, promptInputTokens);
523
- stream.push({ type: "done", reason: "stop", message: partial });
524
- }
525
- } catch (error) {
526
- sdkEventDebug?.recordError("provider_stream", error);
527
- discardIncompleteToolsForRunOutcome(turnCoordinatorForCleanup, {
528
- status: error instanceof CursorLiveRunAbortError ? "cancelled" : "error",
529
- signalAborted: error instanceof CursorLiveRunAbortError,
530
- });
531
- if (activeLiveRun && !activeLiveRun.disposed) await cursorLiveRuns.release(activeLiveRun);
532
- else await abandonSessionCursorAgent(sessionAgentScopeKey);
533
- if (error instanceof CursorLiveRunAbortError) {
534
- sdkAbortErrorSuppression.suppressAbortErrors();
535
- pushSanitizedStreamError(error, "aborted");
536
- } else {
537
- pushSanitizedStreamError(error, "error");
538
- }
539
- } finally {
540
- if (!deferSdkEventDebugFinalize) {
541
- try {
542
- sdkEventDebug?.recordFinalPartial(partial);
543
- await sdkEventDebug?.finalize();
544
- } finally {
545
- sdkAbortErrorSuppression.dispose();
546
- }
547
- }
548
- sdkEventDebugRef.current = undefined;
549
- restoreCursorSdkOutputFilter?.();
550
-
551
- if (abortSignal && abortListener) {
552
- abortSignal.removeEventListener("abort", abortListener);
553
- }
554
- }
68
+ await runner.run(sdkAbortErrorSuppression);
555
69
  } catch (error) {
556
- if (activeLiveRun && !activeLiveRun.disposed) await cursorLiveRuns.release(activeLiveRun).catch(() => {});
557
- else await abandonSessionCursorAgent(sessionAgentScopeKey).catch(() => {});
558
- pushSanitizedStreamError(error, error instanceof CursorLiveRunAbortError ? "aborted" : "error");
70
+ await runner.handleOuterCatch(error);
559
71
  }
560
72
 
561
73
  stream.end();