pi-cursor-sdk 0.4.0 → 0.5.0
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 +30 -0
- package/README.md +65 -13
- package/dist/context-window-cache.js +0 -6
- package/dist/context.js +8 -0
- package/dist/cursor-cloud-lifecycle.js +122 -98
- package/dist/cursor-durable-fs.js +97 -3
- package/dist/cursor-fallback-models.generated.js +288 -1
- package/dist/cursor-fallback-warning.js +2 -2
- package/dist/cursor-http1.js +8 -8
- package/dist/cursor-live-run-coordinator.js +9 -13
- package/dist/cursor-native-replay-routing.js +1 -1
- package/dist/cursor-native-tool-display-registration.js +46 -22
- package/dist/cursor-native-tool-display-state.js +57 -22
- package/dist/cursor-native-tool-display-tools.js +5 -6
- package/dist/cursor-pi-tool-bridge-constants.js +23 -2
- package/dist/cursor-pi-tool-bridge-run.js +7 -3
- package/dist/cursor-pi-tool-bridge-snapshot.js +1 -1
- package/dist/cursor-pi-tool-bridge.js +31 -9
- package/dist/cursor-provider-binding.js +58 -0
- package/dist/cursor-provider-errors.js +28 -7
- package/dist/cursor-provider-lazy.js +23 -11
- package/dist/cursor-provider-live-run-drain.js +15 -12
- package/dist/cursor-provider-run-finalizer.js +19 -8
- package/dist/cursor-provider-turn-coordinator.js +1 -0
- package/dist/cursor-provider-turn-display-router.js +4 -0
- package/dist/cursor-provider-turn-emit.js +1 -0
- package/dist/cursor-provider-turn-finalize.js +29 -20
- package/dist/cursor-provider-turn-prepare.js +24 -11
- package/dist/cursor-provider-turn-runner.js +37 -11
- package/dist/cursor-provider-turn-send.js +22 -9
- package/dist/cursor-provider-turn-summary.js +89 -0
- package/dist/cursor-provider.js +9 -3
- package/dist/cursor-request-provenance.js +42 -0
- package/dist/cursor-ripgrep-path.js +14 -3
- package/dist/cursor-runtime-state.js +50 -43
- package/dist/cursor-sdk-billed-usage.js +54 -75
- package/dist/cursor-sdk-event-debug-session.js +5 -6
- package/dist/cursor-sdk-event-debug.js +9 -6
- package/dist/cursor-sdk-process-error-guard.js +8 -7
- package/dist/cursor-sdk-runtime.js +13 -0
- package/dist/cursor-session-agent-cleanup.js +2 -2
- package/dist/cursor-session-agent-lifecycle.js +18 -9
- package/dist/cursor-session-agent-lineage.js +28 -12
- package/dist/cursor-session-agent-resume.js +51 -35
- package/dist/cursor-session-agent.js +9 -9
- package/dist/cursor-session-compaction-prep.js +2 -7
- package/dist/cursor-session-scope.js +71 -39
- package/dist/cursor-session-settings.js +61 -0
- package/dist/cursor-skill-tool.js +32 -49
- package/dist/cursor-state.js +72 -62
- package/dist/cursor-usage-accounting.js +13 -70
- package/dist/cursor-usage-command.js +89 -0
- package/dist/cursor-usage-ledger.js +512 -0
- package/dist/index.js +11 -19
- package/dist/model-discovery.js +11 -19
- package/docs/cursor-dogfood-checklist.md +3 -3
- package/docs/cursor-live-smoke-checklist.md +15 -15
- package/docs/cursor-model-ux-spec.md +59 -18
- package/docs/cursor-native-tool-replay.md +5 -3
- package/docs/cursor-native-tool-visual-audit.md +8 -8
- package/docs/cursor-testing-lessons.md +47 -23
- package/docs/cursor-tool-surfaces.md +1 -1
- package/docs/platform-smoke-implementation.md +10 -6
- package/docs/platform-smoke.md +35 -46
- package/package.json +6 -6
- package/scripts/cloud-runtime-smoke.mjs +4 -2
- package/scripts/local-resume-smoke.mjs +1 -1
- package/scripts/platform-smoke/card-detect.mjs +2 -8
- package/scripts/platform-smoke/targets.mjs +8 -15
- package/scripts/platform-smoke/visual-evidence.mjs +7 -2
- package/scripts/tmux-live-smoke.sh +57 -90
- package/src/context-window-cache.ts +0 -6
- package/src/context.ts +8 -0
- package/src/cursor-cloud-lifecycle.ts +124 -98
- package/src/cursor-durable-fs.ts +65 -3
- package/src/cursor-fallback-models.generated.ts +288 -1
- package/src/cursor-fallback-warning.ts +2 -2
- package/src/cursor-http1.ts +8 -8
- package/src/cursor-live-run-coordinator.ts +12 -13
- package/src/cursor-native-replay-routing.ts +2 -1
- package/src/cursor-native-tool-display-registration.ts +48 -24
- package/src/cursor-native-tool-display-state.ts +65 -21
- package/src/cursor-native-tool-display-tools.ts +7 -4
- package/src/cursor-pi-tool-bridge-constants.ts +27 -2
- package/src/cursor-pi-tool-bridge-run.ts +11 -3
- package/src/cursor-pi-tool-bridge-snapshot.ts +1 -1
- package/src/cursor-pi-tool-bridge.ts +26 -9
- package/src/cursor-provider-binding.ts +62 -0
- package/src/cursor-provider-errors.ts +27 -7
- package/src/cursor-provider-lazy.ts +21 -15
- package/src/cursor-provider-live-run-drain.ts +20 -13
- package/src/cursor-provider-run-finalizer.ts +24 -15
- package/src/cursor-provider-turn-coordinator.ts +2 -0
- package/src/cursor-provider-turn-display-router.ts +5 -0
- package/src/cursor-provider-turn-emit.ts +3 -1
- package/src/cursor-provider-turn-finalize.ts +38 -23
- package/src/cursor-provider-turn-prepare.ts +23 -14
- package/src/cursor-provider-turn-runner.ts +40 -13
- package/src/cursor-provider-turn-send.ts +23 -15
- package/src/cursor-provider-turn-summary.ts +94 -0
- package/src/cursor-provider-turn-types.ts +37 -4
- package/src/cursor-provider.ts +25 -3
- package/src/cursor-request-provenance.ts +64 -0
- package/src/cursor-ripgrep-path.ts +15 -5
- package/src/cursor-runtime-state.ts +53 -48
- package/src/cursor-sdk-billed-usage.ts +54 -102
- package/src/cursor-sdk-event-debug-session.ts +5 -5
- package/src/cursor-sdk-event-debug.ts +10 -6
- package/src/cursor-sdk-process-error-guard.ts +7 -7
- package/src/cursor-sdk-runtime.ts +14 -0
- package/src/cursor-session-agent-cleanup.ts +2 -2
- package/src/cursor-session-agent-lifecycle.ts +14 -9
- package/src/cursor-session-agent-lineage.ts +26 -12
- package/src/cursor-session-agent-resume.ts +49 -36
- package/src/cursor-session-agent.ts +13 -10
- package/src/cursor-session-compaction-prep.ts +2 -7
- package/src/cursor-session-scope.ts +87 -39
- package/src/cursor-session-settings.ts +72 -0
- package/src/cursor-skill-tool.ts +37 -61
- package/src/cursor-state.ts +74 -67
- package/src/cursor-usage-accounting.ts +17 -82
- package/src/cursor-usage-command.ts +76 -0
- package/src/cursor-usage-ledger.ts +500 -0
- package/src/index.ts +14 -23
- package/src/model-discovery.ts +14 -22
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { createBashToolDefinition, createEditToolDefinition, createFindToolDefinition, createGrepToolDefinition, createLsToolDefinition, createReadToolDefinition, createWriteToolDefinition, } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Text } from "@earendil-works/pi-tui";
|
|
3
|
-
import { getCursorSessionCwd } from "./cursor-session-scope.js";
|
|
4
3
|
import { CURSOR_MODEL_ACTIVE_REPLAY_TOOL_NAMES, CURSOR_REPLAY_TOOL_NAMES, } from "./cursor-native-tool-names.js";
|
|
5
4
|
import { isCursorReplayToolName } from "./cursor-tool-presentation-registry.js";
|
|
6
5
|
import { createCursorReplayOnlyToolDefinition, isCursorReplayNativeEditDetails, isCursorReplayNativeWriteDetails, parseCursorReplayToolDetails, renderCursorReplayResult, renderNativeLookingCursorFileMutationCall, renderNativeLookingCursorReadReplayResult, } from "./cursor-native-tool-display-replay.js";
|
|
@@ -61,12 +60,12 @@ function getNativeReplayStrategy(toolName) {
|
|
|
61
60
|
? NATIVE_CURSOR_TOOL_STRATEGIES[toolName]
|
|
62
61
|
: undefined;
|
|
63
62
|
}
|
|
64
|
-
export function wrapNativeCursorTool(definition, getCurrentDefinition) {
|
|
63
|
+
export function wrapNativeCursorTool(definition, getCurrentDefinition, state) {
|
|
65
64
|
const strategy = getNativeReplayStrategy(definition.name);
|
|
66
65
|
return {
|
|
67
66
|
...definition,
|
|
68
67
|
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
69
|
-
const cursorDisplay = consumeCursorNativeToolDisplay(toolCallId);
|
|
68
|
+
const cursorDisplay = consumeCursorNativeToolDisplay(toolCallId, state, definition.name);
|
|
70
69
|
if (cursorDisplay) {
|
|
71
70
|
if (cursorDisplay.isError) {
|
|
72
71
|
const text = cursorDisplay.result.content
|
|
@@ -114,8 +113,8 @@ export function createNativeCursorToolDefinition(toolName, cwd) {
|
|
|
114
113
|
return createCursorReplayOnlyToolDefinition(toolName);
|
|
115
114
|
throw new Error(`Unsupported Cursor native replay tool: ${toolName}`);
|
|
116
115
|
}
|
|
117
|
-
export function registerNativeCursorTool(pi, toolName) {
|
|
118
|
-
const definition = createNativeCursorToolDefinition(toolName,
|
|
119
|
-
pi.registerTool(wrapNativeCursorTool(definition, () => createNativeCursorToolDefinition(toolName,
|
|
116
|
+
export function registerNativeCursorTool(pi, toolName, getCwd, state) {
|
|
117
|
+
const definition = createNativeCursorToolDefinition(toolName, getCwd());
|
|
118
|
+
pi.registerTool(wrapNativeCursorTool(definition, () => createNativeCursorToolDefinition(toolName, getCwd()), state));
|
|
120
119
|
}
|
|
121
120
|
export { CURSOR_MODEL_ACTIVE_REPLAY_TOOL_NAMES, CURSOR_REPLAY_TOOL_NAMES };
|
|
@@ -1,6 +1,27 @@
|
|
|
1
1
|
export const MCP_SERVER_NAME = "pi_tools";
|
|
2
2
|
export const MCP_ENDPOINT_ROOT = "/cursor-pi-tool-bridge";
|
|
3
|
-
const
|
|
3
|
+
export const CURSOR_PI_BRIDGE_TOOL_CALL_ID_MAX_LENGTH = 64;
|
|
4
|
+
const CURSOR_PI_BRIDGE_RUN_UUID_PATTERN = /^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/i;
|
|
5
|
+
const CURSOR_PI_BRIDGE_TOOL_CALL_ID_PATTERN = /^cursor-pi-bridge-[0-9a-f]{32}-t\d+$/i;
|
|
6
|
+
const LEGACY_CURSOR_PI_BRIDGE_TOOL_CALL_ID_PATTERN = /^cursor-pi-bridge-run-[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}-tool-\d+$/i;
|
|
7
|
+
/**
|
|
8
|
+
* Build the provider-facing bridge ID while enforcing the 64-character
|
|
9
|
+
* call_id limit used by OpenAI-compatible Responses backends.
|
|
10
|
+
*/
|
|
11
|
+
export function buildCursorPiBridgeToolCallId(runUuid, counter) {
|
|
12
|
+
if (!CURSOR_PI_BRIDGE_RUN_UUID_PATTERN.test(runUuid)) {
|
|
13
|
+
throw new Error("Cursor pi bridge run UUID must be canonical");
|
|
14
|
+
}
|
|
15
|
+
if (!Number.isSafeInteger(counter) || counter <= 0) {
|
|
16
|
+
throw new Error("Cursor pi bridge tool call counter must be a positive safe integer");
|
|
17
|
+
}
|
|
18
|
+
const toolCallId = `cursor-pi-bridge-${runUuid.replaceAll("-", "")}-t${counter}`;
|
|
19
|
+
if (toolCallId.length > CURSOR_PI_BRIDGE_TOOL_CALL_ID_MAX_LENGTH) {
|
|
20
|
+
throw new Error("Cursor pi bridge tool call ID limit exceeded");
|
|
21
|
+
}
|
|
22
|
+
return toolCallId;
|
|
23
|
+
}
|
|
4
24
|
export function isCursorPiBridgeToolCallId(toolCallId) {
|
|
5
|
-
return CURSOR_PI_BRIDGE_TOOL_CALL_ID_PATTERN.test(toolCallId)
|
|
25
|
+
return ((toolCallId.length <= CURSOR_PI_BRIDGE_TOOL_CALL_ID_MAX_LENGTH && CURSOR_PI_BRIDGE_TOOL_CALL_ID_PATTERN.test(toolCallId)) ||
|
|
26
|
+
LEGACY_CURSOR_PI_BRIDGE_TOOL_CALL_ID_PATTERN.test(toolCallId));
|
|
6
27
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { Server as McpProtocolServer, WebStandardStreamableHTTPServerTransport, } from "@modelcontextprotocol/server";
|
|
3
3
|
import { bridgeToolExecutionAbortTracker } from "./cursor-pi-tool-bridge-abort.js";
|
|
4
|
-
import { MCP_ENDPOINT_ROOT, MCP_SERVER_NAME } from "./cursor-pi-tool-bridge-constants.js";
|
|
4
|
+
import { buildCursorPiBridgeToolCallId, MCP_ENDPOINT_ROOT, MCP_SERVER_NAME, } from "./cursor-pi-tool-bridge-constants.js";
|
|
5
5
|
import { writeCursorPiToolBridgeDiagnostic, } from "./cursor-pi-tool-bridge-diagnostics.js";
|
|
6
6
|
import { resolveCursorPiToolBridgeCallTimeoutMs } from "./cursor-pi-tool-bridge-env.js";
|
|
7
7
|
import { asToolResultMessage, containsKnownMcpToolName, convertPiContentToMcpContent, normalizeMcpArgs, snapshotToolToMcpTool, waitForProtocolFlush, } from "./cursor-pi-tool-bridge-mcp.js";
|
|
@@ -15,6 +15,7 @@ export class CursorPiToolBridgeRunImpl {
|
|
|
15
15
|
registry;
|
|
16
16
|
env;
|
|
17
17
|
endpointPath;
|
|
18
|
+
runUuid;
|
|
18
19
|
callTimeoutMs;
|
|
19
20
|
knownMcpToolNames;
|
|
20
21
|
knownCursorMcpCallIds = new Set();
|
|
@@ -36,7 +37,9 @@ export class CursorPiToolBridgeRunImpl {
|
|
|
36
37
|
this.enabled = enabled;
|
|
37
38
|
this.onToolRequest = options.onToolRequest;
|
|
38
39
|
this.debugRecorder = options.debugRecorder;
|
|
39
|
-
|
|
40
|
+
const runUuid = randomUUID();
|
|
41
|
+
this.runUuid = runUuid;
|
|
42
|
+
this.id = `cursor-pi-bridge-run-${runUuid}`;
|
|
40
43
|
this.endpointPath = `${MCP_ENDPOINT_ROOT}/${randomUUID()}/mcp`;
|
|
41
44
|
this.callTimeoutMs = resolveCursorPiToolBridgeCallTimeoutMs(env);
|
|
42
45
|
this.knownMcpToolNames = new Set(snapshot.tools.map((tool) => tool.mcpToolName));
|
|
@@ -208,11 +211,12 @@ export class CursorPiToolBridgeRunImpl {
|
|
|
208
211
|
return Promise.reject(new Error("Cursor pi tool bridge run is disposed"));
|
|
209
212
|
this.toolCallCounter += 1;
|
|
210
213
|
const bridgeCallId = `${this.id}-bridge-${this.toolCallCounter}`;
|
|
214
|
+
const piToolCallId = buildCursorPiBridgeToolCallId(this.runUuid, this.toolCallCounter);
|
|
211
215
|
const request = {
|
|
212
216
|
runId: this.id,
|
|
213
217
|
bridgeCallId,
|
|
214
218
|
cursorMcpCallId,
|
|
215
|
-
piToolCallId
|
|
219
|
+
piToolCallId,
|
|
216
220
|
piToolName,
|
|
217
221
|
mcpToolName,
|
|
218
222
|
args: normalizeMcpArgs(argsValue),
|
|
@@ -42,7 +42,7 @@ export function buildCursorPiToolBridgeSnapshot(pi, options = {}) {
|
|
|
42
42
|
for (const tool of allTools) {
|
|
43
43
|
if (!activeToolNames.has(tool.name))
|
|
44
44
|
continue;
|
|
45
|
-
if (isExcludedFromCursorBridgeExposure(tool.name) && isRegisteredCursorNativeToolName(tool.name))
|
|
45
|
+
if (isExcludedFromCursorBridgeExposure(tool.name) && isRegisteredCursorNativeToolName(tool.name, pi))
|
|
46
46
|
continue;
|
|
47
47
|
if (!exposeOverlappingBuiltins && isOverlappingCursorNativePiToolName(tool.name))
|
|
48
48
|
continue;
|
|
@@ -7,6 +7,8 @@ import { LOOPBACK_HOST, CursorPiToolBridgeRegistry } from "./cursor-pi-tool-brid
|
|
|
7
7
|
export { resolveCursorPiToolBridgeDebugEnabled } from "./cursor-pi-tool-bridge-diagnostics.js";
|
|
8
8
|
export { CURSOR_PI_TOOL_BRIDGE_BUILTINS_ENV, CURSOR_PI_TOOL_BRIDGE_CALL_TIMEOUT_MS_ENV, CURSOR_PI_TOOL_BRIDGE_ENV, resolveCursorPiToolBridgeBuiltinsEnabled, resolveCursorPiToolBridgeCallTimeoutMs, resolveCursorPiToolBridgeEnabled, } from "./cursor-pi-tool-bridge-env.js";
|
|
9
9
|
export { buildCursorPiToolBridgeSnapshot, buildCursorPiToolBridgeSurfaceSignature, } from "./cursor-pi-tool-bridge-snapshot.js";
|
|
10
|
+
const bridges = new Map();
|
|
11
|
+
const activeCallsByPi = new Map();
|
|
10
12
|
let registeredCursorPiToolBridge;
|
|
11
13
|
const WINDOWS_BRIDGE_ABORT_ENV = "PI_CURSOR_BRIDGE_TOOL_CALL_ID";
|
|
12
14
|
function buildWindowsBridgeBashAbortCommand(command, marker) {
|
|
@@ -49,18 +51,27 @@ Get-CimInstance Win32_Process -Filter "Name = 'bash.exe' OR Name = 'sh.exe'" |
|
|
|
49
51
|
});
|
|
50
52
|
}
|
|
51
53
|
export function registerCursorPiToolBridge(pi) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
const previous = bridges.get(pi);
|
|
55
|
+
if (previous) {
|
|
56
|
+
const reason = "Cursor pi tool bridge extension reloaded";
|
|
57
|
+
for (const id of activeCallsByPi.get(pi) ?? [])
|
|
58
|
+
bridgeToolExecutionAbortTracker.abort(id, reason);
|
|
59
|
+
void previous.disposeAll(reason);
|
|
60
|
+
}
|
|
54
61
|
const bridge = new CursorPiToolBridgeRegistry(pi);
|
|
55
62
|
registeredCursorPiToolBridge = bridge;
|
|
63
|
+
bridges.set(pi, bridge);
|
|
64
|
+
const activeCalls = new Set();
|
|
65
|
+
activeCallsByPi.set(pi, activeCalls);
|
|
56
66
|
pi.on("tool_call", (event, ctx) => {
|
|
57
|
-
if (
|
|
67
|
+
if (bridges.get(pi) !== bridge)
|
|
58
68
|
return undefined;
|
|
59
69
|
if (!bridge.hasPendingPiToolCallId(event.toolCallId)) {
|
|
60
70
|
return isCursorPiBridgeToolCallId(event.toolCallId)
|
|
61
71
|
? { block: true, reason: "Cursor pi bridge tool call is no longer pending" }
|
|
62
72
|
: undefined;
|
|
63
73
|
}
|
|
74
|
+
activeCalls.add(event.toolCallId);
|
|
64
75
|
const windowsAbortMarker = installWindowsBridgeBashAbortMarker(event);
|
|
65
76
|
const trackingStarted = bridgeToolExecutionAbortTracker.track(event.toolCallId, {
|
|
66
77
|
signal: ctx.signal,
|
|
@@ -77,17 +88,26 @@ export function registerCursorPiToolBridge(pi) {
|
|
|
77
88
|
return { block: true, reason: "Cursor pi bridge tool execution was aborted before it started" };
|
|
78
89
|
});
|
|
79
90
|
pi.on("tool_result", (event) => {
|
|
80
|
-
|
|
91
|
+
if (activeCalls.delete(event.toolCallId))
|
|
92
|
+
bridgeToolExecutionAbortTracker.finish(event.toolCallId);
|
|
81
93
|
});
|
|
82
94
|
pi.on("session_shutdown", async (event) => {
|
|
95
|
+
if (bridges.get(pi) !== bridge)
|
|
96
|
+
return;
|
|
83
97
|
const reason = `Cursor pi tool bridge session shutdown: ${event.reason}`;
|
|
84
|
-
|
|
98
|
+
for (const id of activeCalls)
|
|
99
|
+
bridgeToolExecutionAbortTracker.abort(id, reason);
|
|
100
|
+
activeCalls.clear();
|
|
101
|
+
bridges.delete(pi);
|
|
102
|
+
activeCallsByPi.delete(pi);
|
|
103
|
+
if (registeredCursorPiToolBridge === bridge)
|
|
104
|
+
registeredCursorPiToolBridge = [...bridges.values()].at(-1);
|
|
85
105
|
await bridge.disposeAll(reason);
|
|
86
106
|
});
|
|
87
107
|
return bridge;
|
|
88
108
|
}
|
|
89
|
-
export function getRegisteredCursorPiToolBridge() {
|
|
90
|
-
return registeredCursorPiToolBridge;
|
|
109
|
+
export function getRegisteredCursorPiToolBridge(pi) {
|
|
110
|
+
return pi ? bridges.get(pi) : registeredCursorPiToolBridge;
|
|
91
111
|
}
|
|
92
112
|
export const __testUtils = {
|
|
93
113
|
CURSOR_PI_TOOL_BRIDGE_ENV,
|
|
@@ -116,8 +136,10 @@ export const __testUtils = {
|
|
|
116
136
|
},
|
|
117
137
|
resetRegisteredBridgeForTests() {
|
|
118
138
|
bridgeToolExecutionAbortTracker.abortAll("Cursor pi tool bridge test reset");
|
|
119
|
-
const
|
|
139
|
+
const registered = [...bridges.values()];
|
|
140
|
+
bridges.clear();
|
|
141
|
+
activeCallsByPi.clear();
|
|
120
142
|
registeredCursorPiToolBridge = undefined;
|
|
121
|
-
return bridge
|
|
143
|
+
return Promise.all(registered.map((bridge) => bridge.disposeAll("Cursor pi tool bridge test reset")));
|
|
122
144
|
},
|
|
123
145
|
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { CURSOR_API_KEY_CONFIG_VALUE } from "./cursor-api-key.js";
|
|
2
|
+
import { captureCursorCloudLifecycleRecorder } from "./cursor-cloud-lifecycle.js";
|
|
3
|
+
import { captureCursorUsageRecorder } from "./cursor-usage-ledger.js";
|
|
4
|
+
import { getCursorNativeToolDisplayState } from "./cursor-native-tool-display-state.js";
|
|
5
|
+
import { getRegisteredCursorPiToolBridge } from "./cursor-pi-tool-bridge.js";
|
|
6
|
+
import { createCursorLazyStream } from "./cursor-provider-lazy.js";
|
|
7
|
+
import { getCursorSessionScopeSnapshot } from "./cursor-session-scope.js";
|
|
8
|
+
import { captureCursorRequestProjection, resolveCursorRequestProvenance } from "./cursor-request-provenance.js";
|
|
9
|
+
export const CURSOR_PROVIDER_OWNERSHIP_ERROR = "Cursor provider ownership is unavailable: this request does not belong to the active Cursor registration. Bind this session's extensions and refresh Cursor models/reload it; use independent ModelRuntime instances for sibling sessions.";
|
|
10
|
+
const requestOwners = new WeakMap();
|
|
11
|
+
/** One closure per ExtensionAPI, including direct compaction/tree/bug-report streams. */
|
|
12
|
+
export function registerCursorProviderBinding(pi) {
|
|
13
|
+
const binding = { active: false, closed: false };
|
|
14
|
+
const operations = new WeakMap();
|
|
15
|
+
pi.on("session_before_compact", (event) => { operations.set(event.signal, "compaction"); });
|
|
16
|
+
pi.on("session_before_tree", (event) => { operations.set(event.signal, "tree"); });
|
|
17
|
+
pi.on("before_provider_headers", (event, ctx) => {
|
|
18
|
+
// Pi's SDK supplies this session-owned callback for ordinary and auxiliary
|
|
19
|
+
// streams. ModelRuntime forwards the same real headers object to the provider.
|
|
20
|
+
requestOwners.set(event.headers, {
|
|
21
|
+
binding,
|
|
22
|
+
scope: getCursorSessionScopeSnapshot(pi),
|
|
23
|
+
projection: captureCursorRequestProjection(ctx.sessionManager),
|
|
24
|
+
usageRecorder: captureCursorUsageRecorder(pi, ctx),
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
const stream = createCursorLazyStream((model, context, options) => {
|
|
28
|
+
const headers = options?.headers;
|
|
29
|
+
const owner = headers && requestOwners.get(headers);
|
|
30
|
+
if (headers)
|
|
31
|
+
requestOwners.delete(headers);
|
|
32
|
+
if (!binding.active || binding.closed)
|
|
33
|
+
throw new Error("Cursor provider binding is not active. Bind this session's extensions, or reload the session after shutdown.");
|
|
34
|
+
if (!owner)
|
|
35
|
+
throw new Error("Cursor provider ownership is unavailable: this call has no native session request receipt. Bare modelRegistry.streamSimple calls are unsupported for Cursor; use the owning AgentSession's stream path or an independent child AgentSession.");
|
|
36
|
+
if (owner.binding !== binding)
|
|
37
|
+
throw new Error(CURSOR_PROVIDER_OWNERSHIP_ERROR);
|
|
38
|
+
return {
|
|
39
|
+
request: resolveCursorRequestProvenance(owner.projection, model, context, options?.signal && operations.get(options.signal) || "normal"),
|
|
40
|
+
usageRecorder: owner.usageRecorder,
|
|
41
|
+
scope: owner.scope,
|
|
42
|
+
bridge: getRegisteredCursorPiToolBridge(pi),
|
|
43
|
+
nativeDisplay: getCursorNativeToolDisplayState(pi),
|
|
44
|
+
recordCloudLifecycle: captureCursorCloudLifecycleRecorder(pi),
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
pi.on("session_start", () => { if (!binding.closed)
|
|
48
|
+
binding.active = true; });
|
|
49
|
+
pi.on("session_shutdown", () => { binding.active = false; binding.closed = true; });
|
|
50
|
+
return (models) => {
|
|
51
|
+
if (binding.closed)
|
|
52
|
+
return;
|
|
53
|
+
pi.registerProvider("cursor", {
|
|
54
|
+
name: "Cursor", baseUrl: "https://cursor.com", apiKey: CURSOR_API_KEY_CONFIG_VALUE,
|
|
55
|
+
api: "cursor-sdk", models, streamSimple: stream,
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { asRecord } from "./cursor-record-utils.js";
|
|
2
2
|
import { scrubSensitiveText } from "./cursor-sensitive-text.js";
|
|
3
3
|
export const MISSING_CURSOR_API_KEY_MESSAGE = "Cursor SDK runs require a Cursor SDK API key. Cursor Agent CLI/Desktop login is not reused. Run /login -> Use an API key -> Cursor, set CURSOR_API_KEY before starting pi, or restart pi with --api-key.";
|
|
4
|
-
const GENERIC_CURSOR_SDK_ERROR_MESSAGE = "Cursor SDK request failed
|
|
4
|
+
const GENERIC_CURSOR_SDK_ERROR_MESSAGE = "Cursor SDK request failed without further error details.";
|
|
5
5
|
const AUTH_CURSOR_SDK_ERROR_MESSAGE = "Cursor SDK request failed because the Cursor SDK API key may be invalid or unauthorized. Cursor Agent CLI/Desktop login is not reused. Run /login -> Use an API key -> Cursor, verify CURSOR_API_KEY, or pass --api-key, then retry.";
|
|
6
6
|
const CLOUD_AUTH_CURSOR_SDK_ERROR_MESSAGE = "Cursor Cloud Agents request failed because Cloud API authentication rejected the API key. Use a user API key from Cursor Dashboard -> API Keys or a service account API key from Team settings; Team Admin API keys are not supported as Cursor Cloud Agents credentials. Configure the key with /login -> Use an API key -> Cursor, CURSOR_API_KEY, or --api-key, then retry.";
|
|
7
7
|
// Keep "Network error" aligned with pi's agent-level retry classifier.
|
|
8
|
-
const NETWORK_CURSOR_SDK_ERROR_MESSAGE = "Network error: Cursor SDK request failed during network or service I/O. Check your connection
|
|
8
|
+
const NETWORK_CURSOR_SDK_ERROR_MESSAGE = "Network error: Cursor SDK request failed during network or service I/O. Check your connection.";
|
|
9
9
|
// Keep this phrase aligned with pi's agent-level retry classifier (`provider.?returned.?error`).
|
|
10
10
|
const RETRYABLE_CURSOR_RUN_FAILURE_PREFIX = "Provider returned error: Cursor SDK run failed";
|
|
11
11
|
function isGenericErrorMessage(message) {
|
|
@@ -284,23 +284,44 @@ export function resolveCursorSdkAbortCause(options) {
|
|
|
284
284
|
return "live_run_disposed";
|
|
285
285
|
return "unknown";
|
|
286
286
|
}
|
|
287
|
+
function formatErrorDetail(error, apiKey, depth = 0) {
|
|
288
|
+
if (depth >= 3)
|
|
289
|
+
return "";
|
|
290
|
+
const record = asRecord(error);
|
|
291
|
+
const message = typeof error === "string" ? error : getErrorStringField(record, "message") ?? "";
|
|
292
|
+
const name = getErrorStringField(record, "name");
|
|
293
|
+
const code = getErrorEvidenceField(error, record, "code");
|
|
294
|
+
const scrub = (text, limit) => scrubSensitiveText(text, apiKey).trim().slice(0, limit);
|
|
295
|
+
const scrubbed = [
|
|
296
|
+
name && name !== "Error" ? `${scrub(name, 120)}:` : "",
|
|
297
|
+
code ? `(code: ${scrub(code, 120)})` : "",
|
|
298
|
+
scrub(message, 1600),
|
|
299
|
+
].filter(Boolean).join(" ");
|
|
300
|
+
const cause = record?.cause === undefined ? "" : formatErrorDetail(record.cause, apiKey, depth + 1);
|
|
301
|
+
return cause ? `${scrubbed}\nCaused by: ${cause}` : scrubbed;
|
|
302
|
+
}
|
|
287
303
|
export function sanitizeCursorProviderError(error, apiKey, runtimeTarget) {
|
|
288
304
|
const record = asRecord(error);
|
|
289
|
-
const message =
|
|
305
|
+
const message = typeof error === "string" ? error : getErrorStringField(record, "message") ?? "";
|
|
290
306
|
if (message === MISSING_CURSOR_API_KEY_MESSAGE)
|
|
291
307
|
return MISSING_CURSOR_API_KEY_MESSAGE;
|
|
292
|
-
const scrubbed =
|
|
308
|
+
const scrubbed = formatErrorDetail(error, apiKey);
|
|
309
|
+
// A loader failure is not an auth rejection, even inside an authentication wrapper.
|
|
310
|
+
if (/\b(?:ERR_MODULE_NOT_FOUND|MODULE_NOT_FOUND|ResolveMessage)\b|Cannot find (?:module|package)|does not provide an export named/i.test(scrubbed))
|
|
311
|
+
return scrubbed;
|
|
293
312
|
const integrationMessage = runtimeTarget === "cloud"
|
|
294
|
-
? formatIntegrationNotConnectedError(error, record,
|
|
313
|
+
? formatIntegrationNotConnectedError(error, record, scrubSensitiveText(message, apiKey).trim(), apiKey)
|
|
295
314
|
: undefined;
|
|
296
315
|
if (integrationMessage)
|
|
297
316
|
return integrationMessage;
|
|
298
317
|
const connectClassification = classifyCursorConnectError(error);
|
|
299
318
|
if ((runtimeTarget === "cloud" && getErrorName(error, record) === "AuthenticationError") ||
|
|
300
|
-
|
|
301
|
-
isLikelyAuthError(scrubbed)) {
|
|
319
|
+
/\b(?:invalid|revoked|expired|missing) (?:user )?api key\b|\bapi key (?:is )?(?:invalid|revoked|expired|missing)\b/i.test(scrubbed)) {
|
|
302
320
|
return runtimeTarget === "cloud" ? CLOUD_AUTH_CURSOR_SDK_ERROR_MESSAGE : AUTH_CURSOR_SDK_ERROR_MESSAGE;
|
|
303
321
|
}
|
|
322
|
+
if (connectClassification?.kind === "unauthenticated" || isLikelyAuthError(scrubbed)) {
|
|
323
|
+
return `Cursor SDK authentication failed. ${scrubbed}`;
|
|
324
|
+
}
|
|
304
325
|
if (connectClassification?.kind === "network" || isCursorSdkConnectionStalledError(error) || isLikelyNetworkTimeout(scrubbed))
|
|
305
326
|
return NETWORK_CURSOR_SDK_ERROR_MESSAGE;
|
|
306
327
|
if (isGenericCursorRunFailureMessage(scrubbed))
|
|
@@ -21,19 +21,31 @@ function makeProviderRuntimeErrorMessage(model, error, apiKey) {
|
|
|
21
21
|
errorMessage: `Cursor provider runtime failed: ${sanitizeCursorProviderError(error, apiKey)}`,
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
|
-
export function
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
for await (const event of streamCursor(model, context, options)) {
|
|
29
|
-
outer.push(event);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
catch (error) {
|
|
24
|
+
export function createCursorLazyStream(capture) {
|
|
25
|
+
return (model, context, options) => {
|
|
26
|
+
const outer = createAssistantMessageEventStream();
|
|
27
|
+
const fail = (error) => {
|
|
33
28
|
const message = makeProviderRuntimeErrorMessage(model, error, options?.apiKey);
|
|
34
29
|
outer.push({ type: "error", reason: "error", error: message });
|
|
35
30
|
outer.end(message);
|
|
31
|
+
};
|
|
32
|
+
let ownership;
|
|
33
|
+
try {
|
|
34
|
+
ownership = capture(model, context, options);
|
|
36
35
|
}
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
catch (error) {
|
|
37
|
+
fail(error);
|
|
38
|
+
return outer;
|
|
39
|
+
}
|
|
40
|
+
queueMicrotask(async () => {
|
|
41
|
+
try {
|
|
42
|
+
for await (const event of streamCursor(model, context, options, ownership))
|
|
43
|
+
outer.push(event);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
fail(error);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
return outer;
|
|
50
|
+
};
|
|
39
51
|
}
|
|
@@ -30,11 +30,11 @@ export function createCursorNativeReplayId() {
|
|
|
30
30
|
function getCursorNativeReplayIdFromToolCallId(toolCallId) {
|
|
31
31
|
return CURSOR_NATIVE_REPLAY_TOOL_ID_PATTERN.exec(toolCallId)?.[1];
|
|
32
32
|
}
|
|
33
|
-
export function getPendingCursorLiveRun(context) {
|
|
34
|
-
return cursorLiveRuns.getPendingFromContext(context, getCursorNativeReplayIdFromToolCallId);
|
|
33
|
+
export function getPendingCursorLiveRun(context, scopeKey) {
|
|
34
|
+
return cursorLiveRuns.getPendingFromContext(context, getCursorNativeReplayIdFromToolCallId, scopeKey);
|
|
35
35
|
}
|
|
36
|
-
export function getActiveCursorLiveRunForCurrentScope() {
|
|
37
|
-
return cursorLiveRuns.getActiveForScope();
|
|
36
|
+
export function getActiveCursorLiveRunForCurrentScope(scopeKey) {
|
|
37
|
+
return cursorLiveRuns.getActiveForScope(scopeKey);
|
|
38
38
|
}
|
|
39
39
|
function splitTextIntoReplayDeltas(text) {
|
|
40
40
|
const deltas = [];
|
|
@@ -125,7 +125,7 @@ function emitCursorLiveQueuedEvent(turn, event, run) {
|
|
|
125
125
|
turn.emitter.appendTextDelta(event.text);
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
|
-
function emitCursorNativeToolUseTurn(stream, partial, model, context, run, toolResultInputTokens, tools, debugRecorder) {
|
|
128
|
+
function emitCursorNativeToolUseTurn(stream, partial, model, context, run, toolResultInputTokens, tools, occupancyFloor, debugRecorder) {
|
|
129
129
|
const shouldTerminate = run.done && !run.finalText?.trim() && !cursorLiveRuns.peekEvent(run);
|
|
130
130
|
for (const tool of tools) {
|
|
131
131
|
const contentIndex = partial.content.length;
|
|
@@ -142,7 +142,7 @@ function emitCursorNativeToolUseTurn(stream, partial, model, context, run, toolR
|
|
|
142
142
|
const block = partial.content[contentIndex];
|
|
143
143
|
if (block.type === "toolCall")
|
|
144
144
|
stream.push({ type: "toolcall_end", contentIndex, toolCall: block, partial });
|
|
145
|
-
if (recordCursorNativeToolDisplay({ ...tool, terminate: shouldTerminate })) {
|
|
145
|
+
if (recordCursorNativeToolDisplay({ ...tool, terminate: shouldTerminate }, run.nativeDisplay)) {
|
|
146
146
|
run.recordedToolDisplayIds.push(tool.id);
|
|
147
147
|
debugRecorder?.recordDrainEvent("native_tool_display_recorded", {
|
|
148
148
|
toolId: tool.id,
|
|
@@ -154,6 +154,7 @@ function emitCursorNativeToolUseTurn(stream, partial, model, context, run, toolR
|
|
|
154
154
|
applyCursorUsage(partial, model, context, cursorLiveRuns.takeTurnInputTokens(run, toolResultInputTokens), {
|
|
155
155
|
runtime: "local",
|
|
156
156
|
turn: cursorLiveRuns.takeSdkTurnUsage(run),
|
|
157
|
+
occupancyFloor,
|
|
157
158
|
});
|
|
158
159
|
partial.stopReason = "toolUse";
|
|
159
160
|
stream.push({ type: "done", reason: "toolUse", message: partial });
|
|
@@ -172,7 +173,7 @@ function emitInactiveCursorReplayTrace(turn, tools, debugRecorder) {
|
|
|
172
173
|
turn.emitter.appendThinkingBlock(traceText);
|
|
173
174
|
}
|
|
174
175
|
}
|
|
175
|
-
function emitCursorBridgeToolUseTurn(stream, partial, model, context, run, toolResultInputTokens, requests) {
|
|
176
|
+
function emitCursorBridgeToolUseTurn(stream, partial, model, context, run, toolResultInputTokens, requests, occupancyFloor) {
|
|
176
177
|
for (const request of requests) {
|
|
177
178
|
const contentIndex = partial.content.length;
|
|
178
179
|
const serializedArgs = JSON.stringify(request.args);
|
|
@@ -191,6 +192,7 @@ function emitCursorBridgeToolUseTurn(stream, partial, model, context, run, toolR
|
|
|
191
192
|
applyCursorUsage(partial, model, context, cursorLiveRuns.takeTurnInputTokens(run, toolResultInputTokens), {
|
|
192
193
|
runtime: "local",
|
|
193
194
|
turn: cursorLiveRuns.takeSdkTurnUsage(run),
|
|
195
|
+
occupancyFloor,
|
|
194
196
|
});
|
|
195
197
|
partial.stopReason = "toolUse";
|
|
196
198
|
stream.push({ type: "done", reason: "toolUse", message: partial });
|
|
@@ -217,7 +219,7 @@ async function emitCursorLiveRunPendingToolUseTurn(turn, stream, partial, model,
|
|
|
217
219
|
cursorLiveRuns.ignoreFutureSdkTurnUsage(run);
|
|
218
220
|
if (options.mode === "emit")
|
|
219
221
|
turn.emitter.closeAll();
|
|
220
|
-
emitCursorNativeToolUseTurn(stream, partial, model, context, run, toolResultInputTokens, active, debugRecorder);
|
|
222
|
+
emitCursorNativeToolUseTurn(stream, partial, model, context, run, toolResultInputTokens, active, options.occupancyFloor, debugRecorder);
|
|
221
223
|
}
|
|
222
224
|
else {
|
|
223
225
|
const requests = cursorLiveRuns.collectBridgeToolBatch(run);
|
|
@@ -227,7 +229,7 @@ async function emitCursorLiveRunPendingToolUseTurn(turn, stream, partial, model,
|
|
|
227
229
|
cursorLiveRuns.ignoreFutureSdkTurnUsage(run);
|
|
228
230
|
if (options.mode === "emit")
|
|
229
231
|
turn.emitter.closeAll();
|
|
230
|
-
emitCursorBridgeToolUseTurn(stream, partial, model, context, run, toolResultInputTokens, requests);
|
|
232
|
+
emitCursorBridgeToolUseTurn(stream, partial, model, context, run, toolResultInputTokens, requests, options.occupancyFloor);
|
|
231
233
|
}
|
|
232
234
|
return "tool_use";
|
|
233
235
|
}
|
|
@@ -307,7 +309,7 @@ export async function drainCursorLiveRunTurn(stream, partial, model, context, ru
|
|
|
307
309
|
applyCursorUsage(partial, model, context, cursorLiveRuns.takeTurnInputTokens(run, toolResultInputTokens), {
|
|
308
310
|
runtime: "local",
|
|
309
311
|
turn: cursorLiveRuns.takeSdkTurnUsage(run),
|
|
310
|
-
|
|
312
|
+
occupancyFloor: options.occupancyFloor,
|
|
311
313
|
});
|
|
312
314
|
if (run.resumeNotice) {
|
|
313
315
|
emitDisplayOnlyTraceBlock(stream, partial, run.resumeNotice);
|
|
@@ -349,10 +351,10 @@ export async function drainCursorLiveRunTurn(stream, partial, model, context, ru
|
|
|
349
351
|
});
|
|
350
352
|
}
|
|
351
353
|
}
|
|
352
|
-
export async function drainExistingCursorLiveRunBeforeSend(stream, partial, model, context, signal, turnDebugRecorder) {
|
|
354
|
+
export async function drainExistingCursorLiveRunBeforeSend(stream, partial, model, context, signal, turnDebugRecorder, scopeKey, occupancyFloor) {
|
|
353
355
|
turnDebugRecorder?.recordDrainEvent("pre_send_start", {});
|
|
354
356
|
while (true) {
|
|
355
|
-
const run = getPendingCursorLiveRun(context) ?? getActiveCursorLiveRunForCurrentScope();
|
|
357
|
+
const run = getPendingCursorLiveRun(context, scopeKey) ?? getActiveCursorLiveRunForCurrentScope(scopeKey);
|
|
356
358
|
if (!run || run.disposed) {
|
|
357
359
|
turnDebugRecorder?.recordDrainEvent("pre_send_end", { outcome: "continue_send", reason: "no_pending_run" });
|
|
358
360
|
return "continue_send";
|
|
@@ -375,6 +377,7 @@ export async function drainExistingCursorLiveRunBeforeSend(stream, partial, mode
|
|
|
375
377
|
mode: shouldChainUserInput ? "chain_user_input" : "emit",
|
|
376
378
|
signal,
|
|
377
379
|
debugRecorder: turnDebugRecorder,
|
|
380
|
+
occupancyFloor,
|
|
378
381
|
});
|
|
379
382
|
const mapped = drainOutcome === "chain_user_input" ? "continue_send" : "stream_ended";
|
|
380
383
|
turnDebugRecorder?.recordDrainEvent("pre_send_iteration", {
|
|
@@ -3,7 +3,7 @@ import { classifyCursorRunEmission, getCursorRunAbortMessage, } from "./cursor-p
|
|
|
3
3
|
import { formatCursorSdkAbortMessage, resolveCursorSdkAbortCause, sanitizeCursorProviderError, } from "./cursor-provider-errors.js";
|
|
4
4
|
import { CursorLiveRunAbortError } from "./cursor-live-run-coordinator.js";
|
|
5
5
|
import { buildIncompleteCursorToolRunOutcome, } from "./cursor-incomplete-tool-visibility.js";
|
|
6
|
-
import { awaitFinalizeCursorRunOutcome } from "./cursor-provider-turn-finalize.js";
|
|
6
|
+
import { awaitFinalizeCursorRunOutcome, recordCursorProviderTerminalUsage } from "./cursor-provider-turn-finalize.js";
|
|
7
7
|
import { applyCursorUsage } from "./cursor-usage-accounting.js";
|
|
8
8
|
import { hasUsableText } from "./cursor-record-utils.js";
|
|
9
9
|
import { emitDisplayOnlyTraceBlock } from "./cursor-display-only-trace.js";
|
|
@@ -56,11 +56,15 @@ export class CursorRunFinalizer {
|
|
|
56
56
|
applyLiveRunOutcome(finalized.outcome, prepared, runnerParams.context);
|
|
57
57
|
})
|
|
58
58
|
.catch((error) => {
|
|
59
|
-
|
|
59
|
+
const aborted = error instanceof CursorLiveRunAbortError || runnerParams.options?.signal?.aborted === true;
|
|
60
|
+
this.safeCleanup(() => discardIncompleteTools({ status: aborted ? "cancelled" : "error" }));
|
|
60
61
|
if (!liveRun.disposed) {
|
|
61
|
-
|
|
62
|
+
if (aborted)
|
|
63
|
+
cursorLiveRuns.markCancelled(liveRun, this.abortMessage());
|
|
64
|
+
else
|
|
65
|
+
cursorLiveRuns.markError(liveRun, sanitizeCursorProviderError(error, this.params.resolvedApiKey() ?? runnerParams.options?.apiKey, "local"));
|
|
62
66
|
}
|
|
63
|
-
this.safeCleanup(() => sdkEventDebug?.recordWaitResult({ status: "error", error: String(error) }));
|
|
67
|
+
this.safeCleanup(() => sdkEventDebug?.recordWaitResult({ status: aborted ? "cancelled" : "error", error: String(error) }));
|
|
64
68
|
this.safeCleanup(() => sdkEventDebug?.recordError("run_wait", error));
|
|
65
69
|
});
|
|
66
70
|
// Mark the pooled local agent busy as soon as the SDK run exists so auto-compaction summarization
|
|
@@ -104,6 +108,9 @@ export class CursorRunFinalizer {
|
|
|
104
108
|
}
|
|
105
109
|
async applyDirectOutcome(prepared, outcome, displayOnlyTraceBlock) {
|
|
106
110
|
const { stream, partial, model, context } = this.params.runnerParams;
|
|
111
|
+
// Native summaries return at terminal emission, so release their temporary resources first.
|
|
112
|
+
if (prepared.execution === "summary")
|
|
113
|
+
await prepared.lifecycle.dispose().catch(() => { });
|
|
107
114
|
prepared.runtime.turnCoordinator.closeTraceBlock();
|
|
108
115
|
switch (classifyCursorRunEmission(outcome)) {
|
|
109
116
|
case "cancelled":
|
|
@@ -120,7 +127,7 @@ export class CursorRunFinalizer {
|
|
|
120
127
|
applyCursorUsage(partial, model, context, prepared.meta.promptInputTokens, {
|
|
121
128
|
runtime: prepared.runtimeTarget,
|
|
122
129
|
turn: prepared.runtime.turnCoordinator.lastSdkTurnUsage,
|
|
123
|
-
|
|
130
|
+
occupancyFloor: this.params.runnerParams.request.occupancyFloor,
|
|
124
131
|
});
|
|
125
132
|
if (prepared.meta.resumeNotice)
|
|
126
133
|
emitDisplayOnlyTraceBlock(stream, partial, prepared.meta.resumeNotice);
|
|
@@ -131,9 +138,13 @@ export class CursorRunFinalizer {
|
|
|
131
138
|
}
|
|
132
139
|
}
|
|
133
140
|
async applyErrorOutcome(prepared, error) {
|
|
141
|
+
const aborted = error instanceof CursorLiveRunAbortError || this.params.runnerParams.options?.signal?.aborted === true;
|
|
142
|
+
if (prepared && "usage" in prepared && (!prepared.runtime.sdkRun || prepared.runtime.kind !== "live")) {
|
|
143
|
+
await recordCursorProviderTerminalUsage(prepared, aborted ? "abort" : "error");
|
|
144
|
+
}
|
|
134
145
|
this.safeCleanup(() => prepared?.runtime.turnCoordinator.discardIncompleteStartedToolCalls(buildIncompleteCursorToolRunOutcome({
|
|
135
|
-
status:
|
|
136
|
-
signalAborted:
|
|
146
|
+
status: aborted ? "cancelled" : "error",
|
|
147
|
+
signalAborted: aborted,
|
|
137
148
|
})));
|
|
138
149
|
const activeLiveRun = prepared?.runtime.liveRun;
|
|
139
150
|
if (activeLiveRun && !activeLiveRun.disposed) {
|
|
@@ -143,7 +154,7 @@ export class CursorRunFinalizer {
|
|
|
143
154
|
await prepared?.lifecycle.abandon();
|
|
144
155
|
}
|
|
145
156
|
this.safeCleanup(() => this.params.sdkEventDebug()?.recordError("provider_stream", error));
|
|
146
|
-
if (
|
|
157
|
+
if (aborted) {
|
|
147
158
|
this.params.sdkProcessErrorGuard.suppressAbortErrors();
|
|
148
159
|
this.pushTerminalError(this.params.runnerParams.partial, "aborted", this.abortMessage());
|
|
149
160
|
}
|
|
@@ -49,6 +49,7 @@ export class CursorSdkTurnCoordinator {
|
|
|
49
49
|
liveRun: options.liveRun,
|
|
50
50
|
useNativeToolReplay: options.useNativeToolReplay,
|
|
51
51
|
activeToolNames: options.activeToolNames,
|
|
52
|
+
registeredToolNames: options.registeredToolNames,
|
|
52
53
|
nativeReplayId: options.nativeReplayId,
|
|
53
54
|
contentEmitter: this.contentEmitter,
|
|
54
55
|
debugRecorder: options.debugRecorder,
|
|
@@ -15,6 +15,7 @@ export class CursorTurnDisplayRouter {
|
|
|
15
15
|
liveRun;
|
|
16
16
|
useNativeToolReplay;
|
|
17
17
|
activeToolNames;
|
|
18
|
+
registeredToolNames;
|
|
18
19
|
nativeReplayId;
|
|
19
20
|
contentEmitter;
|
|
20
21
|
debugRecorder;
|
|
@@ -27,6 +28,7 @@ export class CursorTurnDisplayRouter {
|
|
|
27
28
|
this.liveRun = options.liveRun;
|
|
28
29
|
this.useNativeToolReplay = options.useNativeToolReplay;
|
|
29
30
|
this.activeToolNames = options.activeToolNames;
|
|
31
|
+
this.registeredToolNames = options.registeredToolNames;
|
|
30
32
|
this.nativeReplayId = options.nativeReplayId;
|
|
31
33
|
this.contentEmitter = options.contentEmitter;
|
|
32
34
|
this.debugRecorder = options.debugRecorder;
|
|
@@ -41,6 +43,7 @@ export class CursorTurnDisplayRouter {
|
|
|
41
43
|
toolName: display.toolName,
|
|
42
44
|
useNativeToolReplay: this.useNativeToolReplay,
|
|
43
45
|
activeToolNames: this.activeToolNames,
|
|
46
|
+
registeredToolNames: this.registeredToolNames,
|
|
44
47
|
hasLiveRun: this.liveRun !== undefined,
|
|
45
48
|
});
|
|
46
49
|
if (disposition === "queue_replay" && this.liveRun) {
|
|
@@ -78,6 +81,7 @@ export class CursorTurnDisplayRouter {
|
|
|
78
81
|
toolName: display.toolName,
|
|
79
82
|
useNativeToolReplay: this.useNativeToolReplay,
|
|
80
83
|
activeToolNames: this.activeToolNames,
|
|
84
|
+
registeredToolNames: this.registeredToolNames,
|
|
81
85
|
hasLiveRun: this.liveRun !== undefined,
|
|
82
86
|
});
|
|
83
87
|
if (disposition === "queue_replay" && this.liveRun && reason !== "abort") {
|