pi-cursor-sdk 0.2.0 → 0.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/README.md +55 -51
  3. package/dist/bundled-context-windows.js +39 -0
  4. package/dist/context-window-cache.js +92 -0
  5. package/dist/context.js +353 -0
  6. package/dist/cursor-active-tools.js +3 -0
  7. package/dist/cursor-agent-message-web-tools.js +165 -0
  8. package/dist/cursor-agents-context-registration.js +17 -0
  9. package/dist/cursor-agents-context.js +127 -0
  10. package/dist/cursor-api-key.js +34 -0
  11. package/dist/cursor-bridge-contract.js +27 -0
  12. package/dist/cursor-cloud-lifecycle.js +650 -0
  13. package/dist/cursor-cloud-local-state.js +460 -0
  14. package/dist/cursor-cloud-options.js +145 -0
  15. package/dist/cursor-cloud-reporting.js +222 -0
  16. package/dist/cursor-compact-tool-summary.js +75 -0
  17. package/dist/cursor-config.js +613 -0
  18. package/dist/cursor-context-tools.js +6 -0
  19. package/dist/cursor-display-only-trace.js +11 -0
  20. package/dist/cursor-display-text.js +18 -0
  21. package/dist/cursor-durable-fs.js +53 -0
  22. package/dist/cursor-edit-diff.js +13 -0
  23. package/dist/cursor-env-boolean.js +19 -0
  24. package/dist/cursor-fallback-models.generated.js +7760 -0
  25. package/dist/cursor-fallback-warning.js +15 -0
  26. package/dist/cursor-http1.js +45 -0
  27. package/dist/cursor-incomplete-tool-visibility.js +83 -0
  28. package/dist/cursor-live-run-accounting.js +55 -0
  29. package/dist/cursor-live-run-coordinator.js +409 -0
  30. package/dist/cursor-mcp-timeout-override.js +116 -0
  31. package/dist/cursor-model-lifecycle.js +32 -0
  32. package/dist/cursor-model.js +5 -0
  33. package/dist/cursor-native-replay-routing.js +35 -0
  34. package/dist/cursor-native-replay-trace.js +28 -0
  35. package/dist/cursor-native-tool-display-registration.js +102 -0
  36. package/dist/cursor-native-tool-display-replay.js +428 -0
  37. package/dist/cursor-native-tool-display-state.js +70 -0
  38. package/dist/cursor-native-tool-display-tools.js +123 -0
  39. package/dist/cursor-native-tool-names.js +11 -0
  40. package/dist/cursor-partial-content-emitter.js +130 -0
  41. package/dist/cursor-pi-context.js +30 -0
  42. package/dist/cursor-pi-tool-bridge-abort.js +114 -0
  43. package/dist/cursor-pi-tool-bridge-constants.js +6 -0
  44. package/dist/cursor-pi-tool-bridge-diagnostics.js +125 -0
  45. package/dist/cursor-pi-tool-bridge-env.js +18 -0
  46. package/dist/cursor-pi-tool-bridge-mcp.js +100 -0
  47. package/dist/cursor-pi-tool-bridge-run.js +355 -0
  48. package/dist/cursor-pi-tool-bridge-server.js +163 -0
  49. package/dist/cursor-pi-tool-bridge-snapshot.js +63 -0
  50. package/dist/cursor-pi-tool-bridge-types.js +1 -0
  51. package/dist/cursor-pi-tool-bridge.js +123 -0
  52. package/dist/cursor-provider-errors.js +311 -0
  53. package/dist/cursor-provider-lazy.js +39 -0
  54. package/dist/cursor-provider-live-run-drain.js +420 -0
  55. package/dist/cursor-provider-overflow.js +90 -0
  56. package/dist/cursor-provider-run-finalizer.js +176 -0
  57. package/dist/cursor-provider-run-outcome.js +76 -0
  58. package/dist/cursor-provider-turn-coordinator.js +293 -0
  59. package/dist/cursor-provider-turn-display-router.js +158 -0
  60. package/dist/cursor-provider-turn-emit.js +41 -0
  61. package/dist/cursor-provider-turn-finalize.js +154 -0
  62. package/dist/cursor-provider-turn-lifecycle-emitter.js +126 -0
  63. package/dist/cursor-provider-turn-prepare.js +379 -0
  64. package/dist/cursor-provider-turn-runner.js +146 -0
  65. package/dist/cursor-provider-turn-sdk-normalizer.js +74 -0
  66. package/dist/cursor-provider-turn-send.js +138 -0
  67. package/dist/cursor-provider-turn-shell-output.js +117 -0
  68. package/dist/cursor-provider-turn-tool-ledger.js +99 -0
  69. package/dist/cursor-provider-turn-types.js +1 -0
  70. package/dist/cursor-provider.js +72 -0
  71. package/dist/cursor-question-tool.js +194 -0
  72. package/dist/cursor-record-utils.js +61 -0
  73. package/dist/cursor-replay-activity-builders.js +114 -0
  74. package/dist/cursor-replay-source-names.js +27 -0
  75. package/dist/cursor-replay-summary-args.js +105 -0
  76. package/dist/cursor-replay-tool-details.js +167 -0
  77. package/dist/cursor-ripgrep-path.js +27 -0
  78. package/dist/cursor-run-final-text.js +63 -0
  79. package/dist/cursor-runtime-state.js +380 -0
  80. package/dist/cursor-sdk-billed-usage.js +86 -0
  81. package/dist/cursor-sdk-event-debug-constants.js +33 -0
  82. package/dist/cursor-sdk-event-debug-session.js +107 -0
  83. package/dist/cursor-sdk-event-debug.js +461 -0
  84. package/dist/cursor-sdk-output-filter.js +1 -0
  85. package/dist/cursor-sdk-process-error-guard.js +221 -0
  86. package/dist/cursor-sdk-runtime.js +3 -0
  87. package/dist/cursor-sensitive-text.js +23 -0
  88. package/dist/cursor-session-agent-cleanup.js +311 -0
  89. package/dist/cursor-session-agent-lifecycle.js +32 -0
  90. package/dist/cursor-session-agent-lineage.js +103 -0
  91. package/dist/cursor-session-agent-resume.js +417 -0
  92. package/dist/cursor-session-agent.js +507 -0
  93. package/dist/cursor-session-compaction-prep.js +21 -0
  94. package/dist/cursor-session-scope.js +113 -0
  95. package/dist/cursor-session-send-policy.js +18 -0
  96. package/dist/cursor-session-store.js +105 -0
  97. package/dist/cursor-session-turn-queue.js +50 -0
  98. package/dist/cursor-setting-sources.js +15 -0
  99. package/dist/cursor-skill-tool.js +230 -0
  100. package/dist/cursor-state.js +519 -0
  101. package/dist/cursor-task-presentation.js +59 -0
  102. package/dist/cursor-tool-lifecycle.js +94 -0
  103. package/dist/cursor-tool-manifest.js +34 -0
  104. package/dist/cursor-tool-presentation-registry.js +305 -0
  105. package/dist/cursor-tool-result-display-readers.js +158 -0
  106. package/dist/cursor-tool-transcript.js +32 -0
  107. package/dist/cursor-tool-visibility.js +28 -0
  108. package/dist/cursor-transcript-tool-formatters.js +618 -0
  109. package/dist/cursor-transcript-tool-specs.js +284 -0
  110. package/dist/cursor-transcript-utils.js +170 -0
  111. package/dist/cursor-usage-accounting.js +166 -0
  112. package/dist/cursor-web-tool-activity.js +23 -0
  113. package/dist/cursor-web-tool-args.js +12 -0
  114. package/dist/index.js +82 -0
  115. package/dist/model-discovery.js +305 -0
  116. package/dist/model-list-cache.js +156 -0
  117. package/docs/cursor-dogfood-checklist.md +2 -1
  118. package/docs/cursor-live-smoke-checklist.md +13 -12
  119. package/docs/cursor-model-ux-spec.md +27 -6
  120. package/docs/cursor-native-tool-replay.md +13 -13
  121. package/docs/cursor-native-tool-visual-audit.md +6 -3
  122. package/docs/cursor-testing-lessons.md +14 -8
  123. package/docs/cursor-tool-surfaces.md +5 -5
  124. package/docs/platform-smoke.md +3 -3
  125. package/package.json +19 -7
  126. package/platform-smoke.config.mjs +1 -1
  127. package/scripts/build.mjs +126 -0
  128. package/scripts/cloud-runtime-smoke.mjs +3 -1
  129. package/scripts/debug-provider-events.mjs +3 -1
  130. package/scripts/debug-sdk-events.mjs +1 -1
  131. package/scripts/isolated-cursor-smoke.sh +5 -4
  132. package/scripts/lib/cloud-smoke-cleanup-evidence.mjs +1 -1
  133. package/scripts/lib/cursor-visual-render.mjs +1 -1
  134. package/scripts/lib/ensure-built.mjs +16 -0
  135. package/scripts/lib/local-resume-smoke-harness.mjs +1 -1
  136. package/scripts/local-resume-smoke.mjs +3 -1
  137. package/scripts/platform-smoke/card-detect.mjs +1 -1
  138. package/scripts/platform-smoke/live-suite-runner.mjs +1 -1
  139. package/scripts/platform-smoke/render-ansi.mjs +1 -1
  140. package/scripts/platform-smoke/targets.mjs +2 -2
  141. package/scripts/prepare.mjs +102 -0
  142. package/scripts/steering-rpc-smoke.mjs +4 -2
  143. package/scripts/tmux-live-smoke.sh +11 -11
  144. package/scripts/visual-tui-smoke-self-test.mjs +4 -1
  145. package/scripts/visual-tui-smoke.mjs +51 -7
  146. package/src/bundled-context-windows.ts +4 -1
  147. package/src/context.ts +27 -4
  148. package/src/cursor-agents-context-registration.ts +2 -2
  149. package/src/cursor-agents-context.ts +24 -10
  150. package/src/cursor-api-key.ts +5 -4
  151. package/src/cursor-cloud-reporting.ts +23 -2
  152. package/src/cursor-context-tools.ts +3 -1
  153. package/src/cursor-fallback-models.generated.ts +592 -204
  154. package/src/cursor-live-run-coordinator.ts +11 -6
  155. package/src/cursor-native-tool-display-registration.ts +10 -11
  156. package/src/cursor-partial-content-emitter.ts +13 -0
  157. package/src/cursor-pi-context.ts +44 -0
  158. package/src/cursor-provider-errors.ts +1 -1
  159. package/src/cursor-provider-lazy.ts +5 -4
  160. package/src/cursor-provider-live-run-drain.ts +14 -9
  161. package/src/cursor-provider-run-finalizer.ts +1 -0
  162. package/src/cursor-provider-turn-coordinator.ts +15 -0
  163. package/src/cursor-provider-turn-emit.ts +6 -0
  164. package/src/cursor-provider-turn-finalize.ts +12 -0
  165. package/src/cursor-provider-turn-prepare.ts +10 -5
  166. package/src/cursor-provider-turn-types.ts +2 -0
  167. package/src/cursor-sdk-billed-usage.ts +107 -0
  168. package/src/cursor-sdk-process-error-guard.ts +6 -4
  169. package/src/cursor-session-agent-lifecycle.ts +8 -9
  170. package/src/cursor-session-agent-resume.ts +23 -0
  171. package/src/cursor-session-compaction-prep.ts +4 -0
  172. package/src/cursor-skill-tool.ts +1 -1
  173. package/src/cursor-tool-manifest.ts +1 -1
  174. package/src/cursor-usage-accounting.ts +70 -9
  175. package/src/index.ts +1 -1
  176. package/tsconfig.build.json +8 -0
  177. package/tsconfig.json +16 -0
@@ -0,0 +1,146 @@
1
+ import { CursorLiveRunAbortError } from "./cursor-live-run-coordinator.js";
2
+ import { drainExistingCursorLiveRunBeforeSend } from "./cursor-provider-live-run-drain.js";
3
+ import { invalidateSessionAgent } from "./cursor-session-agent.js";
4
+ import { getCursorSessionCwd, getCursorSessionScopeKey } from "./cursor-session-scope.js";
5
+ import { installCursorSdkProcessErrorGuard } from "./cursor-sdk-process-error-guard.js";
6
+ import { CursorSdkEventDebugSink } from "./cursor-sdk-event-debug.js";
7
+ import { awaitFinalizeCursorRunOutcome } from "./cursor-provider-turn-finalize.js";
8
+ import { discardIncompleteToolsFromPrepared, emitCursorLiveTurn, } from "./cursor-provider-turn-emit.js";
9
+ import { CursorRunFinalizer } from "./cursor-provider-run-finalizer.js";
10
+ import { prepareCursorProviderTurn, requireCursorApiKey, resolveCursorProviderTurnConfig, } from "./cursor-provider-turn-prepare.js";
11
+ import { sendCursorProviderTurn } from "./cursor-provider-turn-send.js";
12
+ function requireLocalLivePreparedTurn(prepared) {
13
+ if (prepared.runtimeTarget !== "local" || prepared.runtime.kind !== "live") {
14
+ throw new Error("Cursor live run requires a local live prepared turn");
15
+ }
16
+ return prepared;
17
+ }
18
+ export class CursorProviderTurnRunner {
19
+ params;
20
+ sdkEventDebug;
21
+ resolvedApiKey;
22
+ runtimeTarget;
23
+ constructor(params) {
24
+ this.params = params;
25
+ }
26
+ get options() {
27
+ return this.params.options;
28
+ }
29
+ throwIfAborted() {
30
+ if (this.options?.signal?.aborted)
31
+ throw new CursorLiveRunAbortError();
32
+ }
33
+ async run(sdkProcessErrorGuard) {
34
+ const { stream, partial, model, context, options, sdkEventDebugRef } = this.params;
35
+ let prepared;
36
+ let sendResult;
37
+ let liveCompletion;
38
+ const runFinalizer = new CursorRunFinalizer({
39
+ runnerParams: this.params,
40
+ sdkEventDebug: () => this.sdkEventDebug,
41
+ sdkProcessErrorGuard,
42
+ resolvedApiKey: () => this.resolvedApiKey,
43
+ runtimeTarget: () => this.runtimeTarget,
44
+ });
45
+ try {
46
+ this.throwIfAborted();
47
+ const cwd = getCursorSessionCwd();
48
+ this.sdkEventDebug = CursorSdkEventDebugSink.maybeCreate({
49
+ cwd,
50
+ modelId: model.id,
51
+ provider: model.provider,
52
+ });
53
+ sdkEventDebugRef.current = this.sdkEventDebug;
54
+ this.sdkEventDebug?.recordContextSnapshot(context);
55
+ // Resolved once here, before any drain await, so the drain decision and the
56
+ // prepare dispatch below always act on the same config snapshot.
57
+ const resolvedConfig = resolveCursorProviderTurnConfig(cwd);
58
+ this.runtimeTarget = resolvedConfig.runtime.value;
59
+ if (resolvedConfig.runtime.value === "local") {
60
+ // The observed local-executor closed-pipe EPIPE is contained only from this
61
+ // turn's pre-send live-run drain through run completion; for live runs the
62
+ // finalizer holds the guard until run.wait() settles. A hit marks
63
+ // this scope's pooled agent transport dead so the next acquire recreates it.
64
+ const localScopeKey = getCursorSessionScopeKey();
65
+ sdkProcessErrorGuard.containLocalTransportClosedPipe(() => invalidateSessionAgent(localScopeKey, { deadTransport: true }));
66
+ if ((await drainExistingCursorLiveRunBeforeSend(stream, partial, model, context, options?.signal, this.sdkEventDebug)) ===
67
+ "stream_ended") {
68
+ return;
69
+ }
70
+ }
71
+ this.throwIfAborted();
72
+ this.resolvedApiKey = requireCursorApiKey(options);
73
+ prepared = await prepareCursorProviderTurn({
74
+ params: this.params,
75
+ cwd,
76
+ resolvedApiKey: this.resolvedApiKey,
77
+ sdkEventDebug: this.sdkEventDebug,
78
+ throwIfAborted: () => this.throwIfAborted(),
79
+ resolvedConfig,
80
+ });
81
+ sendResult = await sendCursorProviderTurn({
82
+ params: this.params,
83
+ prepared,
84
+ sdkEventDebug: this.sdkEventDebug,
85
+ sdkProcessErrorGuard,
86
+ throwIfAborted: () => this.throwIfAborted(),
87
+ resolvedApiKey: this.resolvedApiKey,
88
+ });
89
+ const { send } = sendResult;
90
+ if (prepared.runtime.kind === "live") {
91
+ const livePrepared = requireLocalLivePreparedTurn(prepared);
92
+ liveCompletion = runFinalizer.startLiveRunCompletion({
93
+ send,
94
+ prepared: livePrepared,
95
+ modelId: model.id,
96
+ discardIncompleteTools: (outcome) => discardIncompleteToolsFromPrepared(livePrepared, outcome),
97
+ });
98
+ await emitCursorLiveTurn({
99
+ params: this.params,
100
+ prepared: livePrepared,
101
+ sdkEventDebug: this.sdkEventDebug,
102
+ discardIncompleteTools: (outcome) => discardIncompleteToolsFromPrepared(livePrepared, outcome),
103
+ });
104
+ return;
105
+ }
106
+ const outcomePromise = awaitFinalizeCursorRunOutcome({
107
+ run: send.run,
108
+ prepared,
109
+ cursorAgentMessageOffset: send.cursorAgentMessageOffset,
110
+ modelId: model.id,
111
+ signal: options?.signal,
112
+ runResultFallback: send.run.result,
113
+ runErrorFallback: send.run.error,
114
+ resolvedApiKey: this.resolvedApiKey,
115
+ optionsApiKey: options?.apiKey,
116
+ sdkEventDebug: this.sdkEventDebug,
117
+ contextWindowAgentId: prepared.contextWindowAgentId,
118
+ });
119
+ prepared.lifecycle.trackRunCompletion(outcomePromise);
120
+ const finalized = await outcomePromise;
121
+ await runFinalizer.applyTerminalEvent({
122
+ kind: "direct",
123
+ prepared,
124
+ outcome: finalized.outcome,
125
+ displayOnlyTraceBlock: finalized.displayOnlyTraceBlock,
126
+ });
127
+ }
128
+ catch (error) {
129
+ await runFinalizer.applyTerminalEvent({ kind: "error", prepared, error });
130
+ }
131
+ finally {
132
+ await runFinalizer.cleanup(prepared, sendResult, liveCompletion);
133
+ }
134
+ }
135
+ async handleOuterCatch(error) {
136
+ const runFinalizer = new CursorRunFinalizer({
137
+ runnerParams: this.params,
138
+ sdkEventDebug: () => this.sdkEventDebug,
139
+ sdkProcessErrorGuard: installCursorSdkProcessErrorGuard(),
140
+ resolvedApiKey: () => this.resolvedApiKey,
141
+ runtimeTarget: () => this.runtimeTarget,
142
+ });
143
+ await runFinalizer.applyTerminalEvent({ kind: "error", prepared: undefined, error });
144
+ await runFinalizer.cleanup(undefined, undefined, undefined);
145
+ }
146
+ }
@@ -0,0 +1,74 @@
1
+ import { asRecord } from "./cursor-record-utils.js";
2
+ import { mergeShellOutputDeltasIntoCursorToolCall, } from "./cursor-provider-turn-shell-output.js";
3
+ function mergeCursorToolCalls(startedToolCall, completedToolCall) {
4
+ const started = asRecord(startedToolCall);
5
+ const completed = asRecord(completedToolCall);
6
+ if (!started)
7
+ return completedToolCall;
8
+ if (!completed)
9
+ return startedToolCall;
10
+ return {
11
+ ...started,
12
+ ...completed,
13
+ name: completed.name ?? started.name,
14
+ type: completed.type ?? started.type,
15
+ args: completed.args ?? started.args,
16
+ input: completed.input ?? started.input,
17
+ result: completed.result ?? started.result,
18
+ };
19
+ }
20
+ export function resolveCursorToolCompletion(options) {
21
+ const bridgeStartedCallId = options.ledger.takeBridgeStartedCallId(typeof options.callId === "string" ? options.callId : "");
22
+ if (bridgeStartedCallId) {
23
+ options.ledger.recordCompletedIdentity(`cursor-tool:${bridgeStartedCallId}`);
24
+ return { action: "ignore-bridge", identity: `cursor-tool:${bridgeStartedCallId}` };
25
+ }
26
+ let matchedStartedCallId;
27
+ let resolvedToolCall;
28
+ let identity;
29
+ let source;
30
+ if (options.source === "delta") {
31
+ const callId = options.callId;
32
+ identity = typeof callId === "string" ? `cursor-tool:${callId}` : undefined;
33
+ resolvedToolCall = mergeCursorToolCalls(options.startedToolCall, options.toolCall);
34
+ if (typeof callId === "string" && options.ledger.hasStartedToolCall(callId)) {
35
+ options.onClearStartedCallId?.(callId);
36
+ options.ledger.clearStartedToolCall(callId);
37
+ }
38
+ else {
39
+ matchedStartedCallId = options.ledger.removeStartedToolCallForStep(options.toolCall, callId);
40
+ if (matchedStartedCallId)
41
+ options.onClearStartedCallId?.(matchedStartedCallId);
42
+ }
43
+ resolvedToolCall = mergeShellOutputDeltasIntoCursorToolCall(resolvedToolCall, matchedStartedCallId
44
+ ? options.shellOutput.takeDeltasForCall(matchedStartedCallId)
45
+ : typeof callId === "string"
46
+ ? options.shellOutput.takeDeltasForCall(callId)
47
+ : undefined);
48
+ source = identity || matchedStartedCallId ? "started" : "fallback";
49
+ }
50
+ else {
51
+ matchedStartedCallId = options.ledger.removeStartedToolCallForStep(options.toolCall, options.callId);
52
+ if (matchedStartedCallId) {
53
+ options.onClearStartedCallId?.(matchedStartedCallId);
54
+ }
55
+ resolvedToolCall = mergeShellOutputDeltasIntoCursorToolCall(options.toolCall, matchedStartedCallId ? options.shellOutput.takeDeltasForCall(matchedStartedCallId) : undefined);
56
+ const identityId = typeof options.callId === "string" ? options.callId : matchedStartedCallId;
57
+ identity = identityId ? `cursor-tool:${identityId}` : undefined;
58
+ }
59
+ if (options.liveRun?.bridgeRun?.isBridgeMcpToolCall(resolvedToolCall)) {
60
+ const bridgeIdentity = options.source === "step" && matchedStartedCallId ? `cursor-tool:${matchedStartedCallId}` : identity;
61
+ if (bridgeIdentity)
62
+ options.ledger.recordCompletedIdentity(bridgeIdentity);
63
+ return { action: "ignore-bridge", identity: bridgeIdentity };
64
+ }
65
+ if (options.source === "delta") {
66
+ return { action: "handle", toolCall: resolvedToolCall, identity, source, matchedStartedCallId };
67
+ }
68
+ return {
69
+ action: "handle",
70
+ toolCall: resolvedToolCall,
71
+ identity,
72
+ matchedStartedCallId,
73
+ };
74
+ }
@@ -0,0 +1,138 @@
1
+ import { countCursorAgentMessages } from "./cursor-agent-message-web-tools.js";
2
+ import { createCursorCloudLifecyclePersistenceError, recordCursorCloudLifecycleSafely, } from "./cursor-cloud-lifecycle.js";
3
+ import { CursorLiveRunAbortError } from "./cursor-live-run-coordinator.js";
4
+ import { cursorLiveRuns } from "./cursor-provider-live-run-drain.js";
5
+ import { consumeCursorLocalForceOverride } from "./cursor-runtime-state.js";
6
+ import { recordCursorSessionAgentLineage } from "./cursor-session-agent-lineage.js";
7
+ const CLOUD_LEDGER_CANCEL_TIMEOUT_MS = 5000;
8
+ async function requestBoundedCloudRunCancellation(run) {
9
+ let timer;
10
+ const timeout = new Promise((resolve) => {
11
+ timer = setTimeout(() => resolve(false), CLOUD_LEDGER_CANCEL_TIMEOUT_MS);
12
+ timer.unref?.();
13
+ });
14
+ try {
15
+ return await Promise.race([
16
+ Promise.resolve().then(() => run.cancel()).then(() => true, () => false),
17
+ timeout,
18
+ ]);
19
+ }
20
+ finally {
21
+ if (timer)
22
+ clearTimeout(timer);
23
+ }
24
+ }
25
+ function recordDebug(action) {
26
+ try {
27
+ action();
28
+ }
29
+ catch {
30
+ // Debug capture is optional and must never change provider execution.
31
+ }
32
+ }
33
+ export async function sendCursorProviderTurn(sendParams) {
34
+ const { params, prepared, sdkEventDebug, sdkProcessErrorGuard, throwIfAborted, resolvedApiKey } = sendParams;
35
+ const { options } = params;
36
+ const { agent, cwd, payload, meta, runtime } = prepared;
37
+ const { turnCoordinator, liveRun } = runtime;
38
+ let completed = false;
39
+ let sdkRun = null;
40
+ const abortListener = () => {
41
+ sdkProcessErrorGuard.suppressAbortErrors();
42
+ liveRun?.bridgeRun?.cancel("Cursor SDK run aborted");
43
+ if (sdkRun) {
44
+ sdkRun.cancel().catch(() => { });
45
+ }
46
+ };
47
+ const abortSignal = options?.signal;
48
+ const abortRegistration = abortSignal
49
+ ? { signal: abortSignal, listener: abortListener }
50
+ : undefined;
51
+ try {
52
+ abortRegistration?.signal.addEventListener("abort", abortListener, { once: true });
53
+ throwIfAborted();
54
+ let cursorAgentMessageOffset;
55
+ if (prepared.runtimeTarget === "local") {
56
+ try {
57
+ cursorAgentMessageOffset = await countCursorAgentMessages(agent.agentId, cwd, prepared.sessionAgentLease.store);
58
+ }
59
+ catch (error) {
60
+ recordDebug(() => sdkEventDebug?.recordError("cursor_agent_message_count", error));
61
+ }
62
+ }
63
+ throwIfAborted();
64
+ recordDebug(() => sdkEventDebug?.recordSendMeta({
65
+ mode: meta.sendPlan.mode,
66
+ reason: meta.sendPlan.reason,
67
+ resetAgent: meta.sendPlan.resetAgent,
68
+ bootstrap: meta.bootstrap,
69
+ promptText: meta.prompt.text,
70
+ imageCount: meta.prompt.images.length,
71
+ useNativeToolReplay: meta.useNativeToolReplay,
72
+ bridgeEnabled: meta.bridgeEnabled,
73
+ nativeReplayId: meta.nativeReplayId,
74
+ promptInputTokens: meta.promptInputTokens,
75
+ agentMode: meta.agentMode,
76
+ }));
77
+ recordDebug(() => sdkEventDebug?.recordSendPayload(payload));
78
+ recordDebug(() => sdkEventDebug?.recordProviderEvent("agent_send_start", payload));
79
+ const sendOptions = {
80
+ mode: meta.agentMode,
81
+ model: meta.modelSelection,
82
+ onDelta: (args) => {
83
+ recordDebug(() => sdkEventDebug?.recordOnDelta(args.update));
84
+ turnCoordinator.handleDelta(args.update);
85
+ },
86
+ onStep: (args) => {
87
+ recordDebug(() => sdkEventDebug?.recordOnStep(args.step));
88
+ turnCoordinator.handleStep(args.step);
89
+ },
90
+ };
91
+ throwIfAborted();
92
+ if (prepared.runtimeTarget === "local" && consumeCursorLocalForceOverride(prepared.localForce)) {
93
+ sendOptions.local = { force: true };
94
+ }
95
+ const runPromise = agent.send(payload, sendOptions);
96
+ // Record at send initiation (promise created), including later reject/cancel paths.
97
+ if (prepared.runtimeTarget === "local") {
98
+ recordCursorSessionAgentLineage(agent.agentId);
99
+ }
100
+ const run = await runPromise;
101
+ sdkRun = run;
102
+ if (prepared.runtimeTarget === "cloud" && !recordCursorCloudLifecycleSafely({ agentId: run.agentId, runId: run.id }, resolvedApiKey)) {
103
+ const cancellationConfirmed = await requestBoundedCloudRunCancellation(run);
104
+ throw createCursorCloudLifecyclePersistenceError(run.agentId, "run", cancellationConfirmed, resolvedApiKey);
105
+ }
106
+ recordDebug(() => sdkEventDebug?.recordRunMeta({
107
+ runId: run.id,
108
+ requestId: run.requestId,
109
+ agentId: run.agentId,
110
+ status: run.status,
111
+ }));
112
+ recordDebug(() => sdkEventDebug?.attachRunStream(run));
113
+ recordDebug(() => sdkEventDebug?.recordProviderEvent("agent_send_returned", {
114
+ runId: run.id,
115
+ requestId: run.requestId,
116
+ agentId: run.agentId,
117
+ status: run.status,
118
+ }));
119
+ if (liveRun)
120
+ cursorLiveRuns.attachSdkRun(liveRun, run);
121
+ if (options?.signal?.aborted) {
122
+ sdkProcessErrorGuard.suppressAbortErrors();
123
+ liveRun?.bridgeRun?.cancel("Cursor SDK run aborted");
124
+ await run.cancel().catch(() => { });
125
+ throw new CursorLiveRunAbortError();
126
+ }
127
+ completed = true;
128
+ return {
129
+ send: { run, cursorAgentMessageOffset },
130
+ abortRegistration,
131
+ };
132
+ }
133
+ finally {
134
+ if (!completed && abortRegistration) {
135
+ abortRegistration.signal.removeEventListener("abort", abortRegistration.listener);
136
+ }
137
+ }
138
+ }
@@ -0,0 +1,117 @@
1
+ import { asRecord, getField, hasUsableText } from "./cursor-record-utils.js";
2
+ import { scrubSensitiveText } from "./cursor-sensitive-text.js";
3
+ import { truncateCursorDisplayLine } from "./cursor-display-text.js";
4
+ import { classifyCursorToolVisibility } from "./cursor-tool-visibility.js";
5
+ const SHELL_OUTPUT_PROGRESS_MAX_DELTAS_PER_CALL = 3;
6
+ export function isCursorShellToolCall(toolCall) {
7
+ return classifyCursorToolVisibility(toolCall).normalizedKey === "shell";
8
+ }
9
+ export function getCursorShellOutputDelta(update) {
10
+ if (update.type !== "shell-output-delta")
11
+ return undefined;
12
+ const event = getField(update, "event");
13
+ const eventCase = getField(event, "case");
14
+ if (eventCase !== "stdout" && eventCase !== "stderr")
15
+ return undefined;
16
+ const value = getField(event, "value");
17
+ const data = getField(value, "data");
18
+ if (typeof data !== "string" || data.length === 0)
19
+ return undefined;
20
+ return { stream: eventCase, data };
21
+ }
22
+ function getCursorShellOutputProgressPreview(data) {
23
+ return data
24
+ .split(/\r?\n/)
25
+ .map((line) => line.trim())
26
+ .find((line) => line.length > 0);
27
+ }
28
+ export function formatCursorShellOutputProgressText(progress, apiKey) {
29
+ const preview = getCursorShellOutputProgressPreview(progress.data);
30
+ if (!preview)
31
+ return undefined;
32
+ return `Cursor shell ${progress.stream}: ${truncateCursorDisplayLine(scrubSensitiveText(preview, apiKey), 160)}\n`;
33
+ }
34
+ export function mergeShellOutputDeltasIntoCursorToolCall(toolCall, deltas) {
35
+ if (!deltas)
36
+ return toolCall;
37
+ const stdout = deltas.stdout.join("");
38
+ const stderr = deltas.stderr.join("");
39
+ if (!hasUsableText(stdout) && !hasUsableText(stderr))
40
+ return toolCall;
41
+ const toolRecord = asRecord(toolCall);
42
+ const result = getField(toolRecord, "result");
43
+ const resultRecord = asRecord(result);
44
+ if (!toolRecord || !resultRecord || resultRecord.status !== "success")
45
+ return toolCall;
46
+ const value = getField(resultRecord, "value");
47
+ const valueRecord = asRecord(value);
48
+ const completedStdout = getField(valueRecord, "stdout");
49
+ const completedStderr = getField(valueRecord, "stderr");
50
+ if (hasUsableText(typeof completedStdout === "string" ? completedStdout : undefined))
51
+ return toolCall;
52
+ if (hasUsableText(typeof completedStderr === "string" ? completedStderr : undefined))
53
+ return toolCall;
54
+ return {
55
+ ...toolRecord,
56
+ result: {
57
+ ...resultRecord,
58
+ value: {
59
+ ...(valueRecord ?? {}),
60
+ stdout,
61
+ stderr,
62
+ },
63
+ },
64
+ };
65
+ }
66
+ export class CursorShellOutputTracker {
67
+ activeShellCallIds = new Set();
68
+ ambiguousShellOutputCallIds = new Set();
69
+ shellOutputDeltasByCallId = new Map();
70
+ shellOutputProgressCountsByCallId = new Map();
71
+ onShellToolStarted(callId) {
72
+ this.activeShellCallIds.add(callId);
73
+ }
74
+ onShellToolCleared(callId) {
75
+ this.activeShellCallIds.delete(callId);
76
+ this.ambiguousShellOutputCallIds.delete(callId);
77
+ this.shellOutputProgressCountsByCallId.delete(callId);
78
+ }
79
+ appendShellOutputDelta(delta) {
80
+ if (this.activeShellCallIds.size !== 1) {
81
+ for (const activeCallId of this.activeShellCallIds) {
82
+ this.ambiguousShellOutputCallIds.add(activeCallId);
83
+ this.shellOutputDeltasByCallId.delete(activeCallId);
84
+ this.shellOutputProgressCountsByCallId.delete(activeCallId);
85
+ }
86
+ return undefined;
87
+ }
88
+ const [callId] = this.activeShellCallIds;
89
+ if (!callId || this.ambiguousShellOutputCallIds.has(callId))
90
+ return undefined;
91
+ let deltas = this.shellOutputDeltasByCallId.get(callId);
92
+ if (!deltas) {
93
+ deltas = { stdout: [], stderr: [] };
94
+ this.shellOutputDeltasByCallId.set(callId, deltas);
95
+ }
96
+ deltas[delta.stream].push(delta.data);
97
+ if (!getCursorShellOutputProgressPreview(delta.data))
98
+ return undefined;
99
+ const progressCount = this.shellOutputProgressCountsByCallId.get(callId) ?? 0;
100
+ if (progressCount >= SHELL_OUTPUT_PROGRESS_MAX_DELTAS_PER_CALL)
101
+ return undefined;
102
+ this.shellOutputProgressCountsByCallId.set(callId, progressCount + 1);
103
+ return { ...delta, callId };
104
+ }
105
+ takeDeltasForCall(callId) {
106
+ const deltas = this.shellOutputDeltasByCallId.get(callId);
107
+ this.shellOutputDeltasByCallId.delete(callId);
108
+ this.shellOutputProgressCountsByCallId.delete(callId);
109
+ return deltas;
110
+ }
111
+ clear() {
112
+ this.activeShellCallIds.clear();
113
+ this.ambiguousShellOutputCallIds.clear();
114
+ this.shellOutputDeltasByCallId.clear();
115
+ this.shellOutputProgressCountsByCallId.clear();
116
+ }
117
+ }
@@ -0,0 +1,99 @@
1
+ import { getToolArgs, getToolName } from "./cursor-transcript-utils.js";
2
+ export class CursorToolCompletionLedger {
3
+ startedToolCalls = new Map();
4
+ bridgeStartedToolCallIds = new Set();
5
+ completedToolIdentities = new Set();
6
+ completedStartedToolFingerprints = new Set();
7
+ completedFallbackToolFingerprints = new Set();
8
+ getStartedToolCall(callId) {
9
+ return this.startedToolCalls.get(callId);
10
+ }
11
+ hasStartedToolCall(callId) {
12
+ return this.startedToolCalls.has(callId);
13
+ }
14
+ registerStartedToolCall(callId, toolCall) {
15
+ this.startedToolCalls.set(callId, toolCall);
16
+ }
17
+ markBridgeStarted(callId) {
18
+ this.bridgeStartedToolCallIds.add(callId);
19
+ }
20
+ takeBridgeStartedCallId(callId) {
21
+ if (!this.bridgeStartedToolCallIds.has(callId))
22
+ return undefined;
23
+ this.bridgeStartedToolCallIds.delete(callId);
24
+ return callId;
25
+ }
26
+ clearStartedToolCall(callId) {
27
+ this.startedToolCalls.delete(callId);
28
+ this.bridgeStartedToolCallIds.delete(callId);
29
+ }
30
+ removeStartedToolCallForStep(toolCall, stepId) {
31
+ if (typeof stepId === "string" && this.startedToolCalls.has(stepId)) {
32
+ this.clearStartedToolCall(stepId);
33
+ return stepId;
34
+ }
35
+ const fingerprint = getStartedToolCallFingerprint(toolCall);
36
+ for (const [callId, startedToolCall] of this.startedToolCalls) {
37
+ if (getStartedToolCallFingerprint(startedToolCall) !== fingerprint)
38
+ continue;
39
+ this.clearStartedToolCall(callId);
40
+ return callId;
41
+ }
42
+ return undefined;
43
+ }
44
+ recordCompletedIdentity(identity) {
45
+ this.completedToolIdentities.add(identity);
46
+ }
47
+ shouldSkipDuplicateCompletion(input) {
48
+ if (input.identity && this.completedToolIdentities.has(input.identity)) {
49
+ return "identity-already-completed";
50
+ }
51
+ if (input.source === "started") {
52
+ if (this.completedFallbackToolFingerprints.has(input.fingerprint)) {
53
+ return "fallback-fingerprint-already-completed";
54
+ }
55
+ return undefined;
56
+ }
57
+ if (this.completedStartedToolFingerprints.has(input.fingerprint) ||
58
+ this.completedFallbackToolFingerprints.has(input.fingerprint)) {
59
+ return "fingerprint-already-completed";
60
+ }
61
+ return undefined;
62
+ }
63
+ recordCompletedTool(input) {
64
+ if (input.identity)
65
+ this.completedToolIdentities.add(input.identity);
66
+ if (input.source === "started") {
67
+ this.completedStartedToolFingerprints.add(input.fingerprint);
68
+ }
69
+ else {
70
+ this.completedFallbackToolFingerprints.add(input.fingerprint);
71
+ }
72
+ }
73
+ clear() {
74
+ this.startedToolCalls.clear();
75
+ this.bridgeStartedToolCallIds.clear();
76
+ this.completedToolIdentities.clear();
77
+ this.completedStartedToolFingerprints.clear();
78
+ this.completedFallbackToolFingerprints.clear();
79
+ }
80
+ /** Exposed for incomplete-tool discard iteration. */
81
+ startedToolCallEntries() {
82
+ return this.startedToolCalls.entries();
83
+ }
84
+ clearStartedToolCalls() {
85
+ this.startedToolCalls.clear();
86
+ this.bridgeStartedToolCallIds.clear();
87
+ }
88
+ }
89
+ export function getToolFingerprint(value) {
90
+ try {
91
+ return JSON.stringify(value);
92
+ }
93
+ catch {
94
+ return String(value);
95
+ }
96
+ }
97
+ export function getStartedToolCallFingerprint(toolCall) {
98
+ return getToolFingerprint({ toolName: getToolName(toolCall), args: getToolArgs(toolCall) });
99
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,72 @@
1
+ import { createAssistantMessageEventStream, } from "@earendil-works/pi-ai";
2
+ import { cursorLiveRuns, DEFAULT_CURSOR_NATIVE_REPLAY_IDLE_DISPOSE_MS, getPendingCursorLiveRun, hasTrailingUserMessagesAfterToolResults, releaseAllPendingCursorLiveRunsForTests, resetCursorNativeReplayIdleDisposeMs, setCursorNativeReplayIdleDisposeMs, } from "./cursor-provider-live-run-drain.js";
3
+ import { disposeAllSessionCursorAgents } from "./cursor-session-agent.js";
4
+ import { attachCursorSdkEventDebugPiStreamTap } from "./cursor-sdk-event-debug.js";
5
+ import { installCursorSdkProcessErrorGuard } from "./cursor-sdk-process-error-guard.js";
6
+ import { sanitizeCursorProviderError } from "./cursor-provider-errors.js";
7
+ import { resolveCursorApiKey } from "./cursor-api-key.js";
8
+ import { CursorProviderTurnRunner } from "./cursor-provider-turn-runner.js";
9
+ import { getCursorSessionScopeKey } from "./cursor-session-scope.js";
10
+ import { runExclusiveCursorSessionTurn, __testUtils as cursorSessionTurnQueueTestUtils } from "./cursor-session-turn-queue.js";
11
+ function makeInitialMessage(model) {
12
+ return {
13
+ role: "assistant",
14
+ content: [],
15
+ api: model.api,
16
+ provider: model.provider,
17
+ model: model.id,
18
+ usage: {
19
+ input: 0,
20
+ output: 0,
21
+ cacheRead: 0,
22
+ cacheWrite: 0,
23
+ totalTokens: 0,
24
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
25
+ },
26
+ stopReason: "stop",
27
+ timestamp: Date.now(),
28
+ };
29
+ }
30
+ export function streamCursor(model, context, options) {
31
+ const stream = createAssistantMessageEventStream();
32
+ const sdkEventDebugRef = {};
33
+ attachCursorSdkEventDebugPiStreamTap(stream, sdkEventDebugRef);
34
+ (async () => {
35
+ const partial = makeInitialMessage(model);
36
+ const runner = new CursorProviderTurnRunner({
37
+ model,
38
+ context,
39
+ stream,
40
+ partial,
41
+ options,
42
+ sdkEventDebugRef,
43
+ });
44
+ try {
45
+ stream.push({ type: "start", partial });
46
+ await runExclusiveCursorSessionTurn(getCursorSessionScopeKey(), () => runner.run(installCursorSdkProcessErrorGuard()), options?.signal);
47
+ }
48
+ catch (error) {
49
+ await runner.handleOuterCatch(error);
50
+ }
51
+ stream.end();
52
+ })().catch((error) => {
53
+ const partial = makeInitialMessage(model);
54
+ partial.stopReason = "error";
55
+ partial.errorMessage = sanitizeCursorProviderError(error, resolveCursorApiKey(options?.apiKey));
56
+ stream.push({ type: "error", reason: "error", error: partial });
57
+ stream.end();
58
+ });
59
+ return stream;
60
+ }
61
+ export const __testUtils = {
62
+ DEFAULT_CURSOR_NATIVE_REPLAY_IDLE_DISPOSE_MS,
63
+ pendingCursorNativeRunCount: cursorLiveRuns.count,
64
+ getPendingCursorLiveRun,
65
+ getActiveCursorLiveRunForScope: cursorLiveRuns.getActiveForScope,
66
+ hasTrailingUserMessagesAfterToolResults,
67
+ setCursorNativeReplayIdleDisposeMs,
68
+ resetCursorNativeReplayIdleDisposeMs,
69
+ releaseAllPendingCursorLiveRunsForTests,
70
+ resetSessionCursorAgents: () => disposeAllSessionCursorAgents(),
71
+ resetSessionTurnQueue: cursorSessionTurnQueueTestUtils.reset,
72
+ };