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