pi-cursor-sdk 0.2.0 → 0.3.6
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 +62 -0
- package/README.md +54 -50
- package/dist/bundled-context-windows.js +39 -0
- package/dist/context-window-cache.js +92 -0
- package/dist/context.js +338 -0
- package/dist/cursor-active-tools.js +3 -0
- package/dist/cursor-agent-message-web-tools.js +165 -0
- package/dist/cursor-agents-context-registration.js +17 -0
- package/dist/cursor-agents-context.js +113 -0
- package/dist/cursor-api-key.js +34 -0
- package/dist/cursor-bridge-contract.js +27 -0
- package/dist/cursor-cloud-lifecycle.js +650 -0
- package/dist/cursor-cloud-local-state.js +460 -0
- package/dist/cursor-cloud-options.js +145 -0
- package/dist/cursor-cloud-reporting.js +222 -0
- package/dist/cursor-compact-tool-summary.js +75 -0
- package/dist/cursor-config.js +613 -0
- package/dist/cursor-context-tools.js +4 -0
- package/dist/cursor-display-only-trace.js +11 -0
- package/dist/cursor-display-text.js +18 -0
- package/dist/cursor-durable-fs.js +53 -0
- package/dist/cursor-edit-diff.js +13 -0
- package/dist/cursor-env-boolean.js +19 -0
- package/dist/cursor-fallback-models.generated.js +7760 -0
- package/dist/cursor-fallback-warning.js +15 -0
- package/dist/cursor-http1.js +45 -0
- package/dist/cursor-incomplete-tool-visibility.js +83 -0
- package/dist/cursor-live-run-accounting.js +55 -0
- package/dist/cursor-live-run-coordinator.js +406 -0
- package/dist/cursor-mcp-timeout-override.js +116 -0
- package/dist/cursor-model-lifecycle.js +32 -0
- package/dist/cursor-model.js +5 -0
- package/dist/cursor-native-replay-routing.js +35 -0
- package/dist/cursor-native-replay-trace.js +28 -0
- package/dist/cursor-native-tool-display-registration.js +102 -0
- package/dist/cursor-native-tool-display-replay.js +428 -0
- package/dist/cursor-native-tool-display-state.js +70 -0
- package/dist/cursor-native-tool-display-tools.js +123 -0
- package/dist/cursor-native-tool-names.js +11 -0
- package/dist/cursor-partial-content-emitter.js +118 -0
- package/dist/cursor-pi-tool-bridge-abort.js +114 -0
- package/dist/cursor-pi-tool-bridge-constants.js +6 -0
- package/dist/cursor-pi-tool-bridge-diagnostics.js +125 -0
- package/dist/cursor-pi-tool-bridge-env.js +18 -0
- package/dist/cursor-pi-tool-bridge-mcp.js +100 -0
- package/dist/cursor-pi-tool-bridge-run.js +355 -0
- package/dist/cursor-pi-tool-bridge-server.js +163 -0
- package/dist/cursor-pi-tool-bridge-snapshot.js +63 -0
- package/dist/cursor-pi-tool-bridge-types.js +1 -0
- package/dist/cursor-pi-tool-bridge.js +123 -0
- package/dist/cursor-provider-errors.js +311 -0
- package/dist/cursor-provider-lazy.js +39 -0
- package/dist/cursor-provider-live-run-drain.js +412 -0
- package/dist/cursor-provider-overflow.js +90 -0
- package/dist/cursor-provider-run-finalizer.js +176 -0
- package/dist/cursor-provider-run-outcome.js +76 -0
- package/dist/cursor-provider-turn-coordinator.js +275 -0
- package/dist/cursor-provider-turn-display-router.js +158 -0
- package/dist/cursor-provider-turn-emit.js +35 -0
- package/dist/cursor-provider-turn-finalize.js +154 -0
- package/dist/cursor-provider-turn-lifecycle-emitter.js +126 -0
- package/dist/cursor-provider-turn-prepare.js +374 -0
- package/dist/cursor-provider-turn-runner.js +146 -0
- package/dist/cursor-provider-turn-sdk-normalizer.js +74 -0
- package/dist/cursor-provider-turn-send.js +138 -0
- package/dist/cursor-provider-turn-shell-output.js +117 -0
- package/dist/cursor-provider-turn-tool-ledger.js +99 -0
- package/dist/cursor-provider-turn-types.js +1 -0
- package/dist/cursor-provider.js +72 -0
- package/dist/cursor-question-tool.js +194 -0
- package/dist/cursor-record-utils.js +61 -0
- package/dist/cursor-replay-activity-builders.js +114 -0
- package/dist/cursor-replay-source-names.js +27 -0
- package/dist/cursor-replay-summary-args.js +105 -0
- package/dist/cursor-replay-tool-details.js +167 -0
- package/dist/cursor-ripgrep-path.js +27 -0
- package/dist/cursor-run-final-text.js +63 -0
- package/dist/cursor-runtime-state.js +380 -0
- package/dist/cursor-sdk-billed-usage.js +86 -0
- package/dist/cursor-sdk-event-debug-constants.js +33 -0
- package/dist/cursor-sdk-event-debug-session.js +107 -0
- package/dist/cursor-sdk-event-debug.js +461 -0
- package/dist/cursor-sdk-output-filter.js +1 -0
- package/dist/cursor-sdk-process-error-guard.js +221 -0
- package/dist/cursor-sdk-runtime.js +3 -0
- package/dist/cursor-sensitive-text.js +23 -0
- package/dist/cursor-session-agent-cleanup.js +311 -0
- package/dist/cursor-session-agent-lifecycle.js +32 -0
- package/dist/cursor-session-agent-lineage.js +103 -0
- package/dist/cursor-session-agent-resume.js +417 -0
- package/dist/cursor-session-agent.js +507 -0
- package/dist/cursor-session-compaction-prep.js +21 -0
- package/dist/cursor-session-scope.js +113 -0
- package/dist/cursor-session-send-policy.js +18 -0
- package/dist/cursor-session-store.js +105 -0
- package/dist/cursor-session-turn-queue.js +50 -0
- package/dist/cursor-setting-sources.js +15 -0
- package/dist/cursor-skill-tool.js +230 -0
- package/dist/cursor-state.js +519 -0
- package/dist/cursor-task-presentation.js +59 -0
- package/dist/cursor-tool-lifecycle.js +94 -0
- package/dist/cursor-tool-manifest.js +34 -0
- package/dist/cursor-tool-presentation-registry.js +305 -0
- package/dist/cursor-tool-result-display-readers.js +158 -0
- package/dist/cursor-tool-transcript.js +32 -0
- package/dist/cursor-tool-visibility.js +28 -0
- package/dist/cursor-transcript-tool-formatters.js +618 -0
- package/dist/cursor-transcript-tool-specs.js +284 -0
- package/dist/cursor-transcript-utils.js +170 -0
- package/dist/cursor-usage-accounting.js +166 -0
- package/dist/cursor-web-tool-activity.js +23 -0
- package/dist/cursor-web-tool-args.js +12 -0
- package/dist/index.js +82 -0
- package/dist/model-discovery.js +305 -0
- package/dist/model-list-cache.js +156 -0
- package/docs/cursor-dogfood-checklist.md +2 -1
- package/docs/cursor-live-smoke-checklist.md +13 -12
- package/docs/cursor-model-ux-spec.md +25 -6
- package/docs/cursor-native-tool-replay.md +13 -13
- package/docs/cursor-native-tool-visual-audit.md +6 -3
- package/docs/cursor-testing-lessons.md +7 -7
- package/docs/cursor-tool-surfaces.md +5 -5
- package/docs/platform-smoke.md +3 -3
- package/package.json +19 -7
- package/platform-smoke.config.mjs +1 -1
- package/scripts/build.mjs +126 -0
- package/scripts/cloud-runtime-smoke.mjs +3 -1
- package/scripts/debug-provider-events.mjs +3 -1
- package/scripts/debug-sdk-events.mjs +1 -1
- package/scripts/isolated-cursor-smoke.sh +5 -4
- package/scripts/lib/cloud-smoke-cleanup-evidence.mjs +1 -1
- package/scripts/lib/cursor-visual-render.mjs +1 -1
- package/scripts/lib/ensure-built.mjs +16 -0
- package/scripts/lib/local-resume-smoke-harness.mjs +1 -1
- package/scripts/local-resume-smoke.mjs +3 -1
- package/scripts/platform-smoke/card-detect.mjs +1 -1
- package/scripts/platform-smoke/live-suite-runner.mjs +1 -1
- package/scripts/platform-smoke/render-ansi.mjs +1 -1
- package/scripts/platform-smoke/targets.mjs +2 -2
- package/scripts/prepare.mjs +102 -0
- package/scripts/steering-rpc-smoke.mjs +4 -2
- package/scripts/tmux-live-smoke.sh +11 -11
- package/scripts/visual-tui-smoke-self-test.mjs +4 -1
- package/scripts/visual-tui-smoke.mjs +51 -7
- package/src/bundled-context-windows.ts +4 -1
- package/src/cursor-agents-context-registration.ts +2 -2
- package/src/cursor-agents-context.ts +0 -2
- package/src/cursor-api-key.ts +5 -4
- package/src/cursor-cloud-reporting.ts +23 -2
- package/src/cursor-fallback-models.generated.ts +592 -204
- package/src/cursor-live-run-coordinator.ts +1 -0
- package/src/cursor-native-tool-display-registration.ts +10 -11
- package/src/cursor-provider-errors.ts +1 -1
- package/src/cursor-provider-lazy.ts +5 -4
- package/src/cursor-provider-live-run-drain.ts +1 -0
- package/src/cursor-provider-run-finalizer.ts +1 -0
- package/src/cursor-provider-turn-finalize.ts +12 -0
- package/src/cursor-provider-turn-types.ts +2 -0
- package/src/cursor-sdk-billed-usage.ts +107 -0
- package/src/cursor-sdk-process-error-guard.ts +6 -4
- package/src/cursor-session-agent-lifecycle.ts +8 -9
- package/src/cursor-session-agent-resume.ts +23 -0
- package/src/cursor-session-compaction-prep.ts +4 -0
- package/src/cursor-tool-manifest.ts +1 -1
- package/src/cursor-usage-accounting.ts +70 -9
- package/src/index.ts +1 -1
- package/tsconfig.build.json +8 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { selectCursorFinalText } from "./cursor-run-final-text.js";
|
|
2
|
+
import { formatCursorSdkAbortMessage, formatCursorSdkRunFailureDetail, resolveCursorSdkAbortCause, sanitizeCursorProviderError, } from "./cursor-provider-errors.js";
|
|
3
|
+
import { hasUsableText } from "./cursor-record-utils.js";
|
|
4
|
+
import { buildIncompleteCursorToolRunOutcome, } from "./cursor-incomplete-tool-visibility.js";
|
|
5
|
+
function hasCursorAssistantText(resultText, textDeltas, fallbackText) {
|
|
6
|
+
return (hasUsableText(typeof resultText === "string" ? resultText : undefined) ||
|
|
7
|
+
hasUsableText(textDeltas.join("")) ||
|
|
8
|
+
hasUsableText(fallbackText));
|
|
9
|
+
}
|
|
10
|
+
export function isCursorRunFinishedSuccessfully(outcome) {
|
|
11
|
+
return outcome.kind === "finished";
|
|
12
|
+
}
|
|
13
|
+
function buildCursorRunAbortMessage(signalAborted, sdkStatusCancelled) {
|
|
14
|
+
return formatCursorSdkAbortMessage(resolveCursorSdkAbortCause({
|
|
15
|
+
signalAborted,
|
|
16
|
+
sdkStatusCancelled,
|
|
17
|
+
}));
|
|
18
|
+
}
|
|
19
|
+
export function resolveCursorRunOutcome(params) {
|
|
20
|
+
const { waitResult, signalAborted } = params;
|
|
21
|
+
const sdkCancelled = waitResult.status === "cancelled";
|
|
22
|
+
const callerAborted = signalAborted === true;
|
|
23
|
+
if (callerAborted || sdkCancelled) {
|
|
24
|
+
const incompleteTools = buildIncompleteCursorToolRunOutcome({
|
|
25
|
+
status: "cancelled",
|
|
26
|
+
signalAborted: callerAborted,
|
|
27
|
+
assistantTextProduced: false,
|
|
28
|
+
});
|
|
29
|
+
return {
|
|
30
|
+
kind: "cancelled",
|
|
31
|
+
waitResult,
|
|
32
|
+
incompleteTools,
|
|
33
|
+
abortMessage: buildCursorRunAbortMessage(callerAborted, sdkCancelled),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
if (waitResult.status === "error") {
|
|
37
|
+
const failureDetail = formatCursorSdkRunFailureDetail(waitResult, params.runResultFallback, params.runErrorFallback);
|
|
38
|
+
return {
|
|
39
|
+
kind: "error",
|
|
40
|
+
waitResult,
|
|
41
|
+
incompleteTools: buildIncompleteCursorToolRunOutcome({
|
|
42
|
+
status: "error",
|
|
43
|
+
assistantTextProduced: false,
|
|
44
|
+
}),
|
|
45
|
+
errorMessage: sanitizeCursorProviderError(failureDetail, params.resolvedApiKey ?? params.optionsApiKey, params.runtimeTarget),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const assistantTextProduced = hasCursorAssistantText(waitResult.result, params.textDeltas, params.planTextCandidate);
|
|
49
|
+
const incompleteTools = buildIncompleteCursorToolRunOutcome({
|
|
50
|
+
status: waitResult.status,
|
|
51
|
+
assistantTextProduced,
|
|
52
|
+
});
|
|
53
|
+
const finalText = selectCursorFinalText(waitResult.result, params.textDeltas, params.emittedText, params.planTextCandidate, params.selectFinalTextOptions);
|
|
54
|
+
return {
|
|
55
|
+
kind: "finished",
|
|
56
|
+
waitResult,
|
|
57
|
+
finalText,
|
|
58
|
+
incompleteTools,
|
|
59
|
+
assistantTextProduced,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export function classifyCursorRunEmission(outcome) {
|
|
63
|
+
switch (outcome.kind) {
|
|
64
|
+
case "finished":
|
|
65
|
+
return "finished";
|
|
66
|
+
case "cancelled":
|
|
67
|
+
return "cancelled";
|
|
68
|
+
case "error":
|
|
69
|
+
return "failed";
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
export function getCursorRunAbortMessage(outcome) {
|
|
73
|
+
if (outcome.kind === "cancelled")
|
|
74
|
+
return outcome.abortMessage;
|
|
75
|
+
return buildCursorRunAbortMessage(false, outcome.waitResult.status === "cancelled");
|
|
76
|
+
}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { cursorLiveRuns } from "./cursor-provider-live-run-drain.js";
|
|
2
|
+
import { truncateCursorDisplayLine } from "./cursor-display-text.js";
|
|
3
|
+
import { recordDiscardedIncompleteStartedToolCall, } from "./cursor-sdk-event-debug.js";
|
|
4
|
+
import { buildIncompleteCursorToolRunOutcome, resolveIncompleteCursorToolVisibility, } from "./cursor-incomplete-tool-visibility.js";
|
|
5
|
+
import { getToolName } from "./cursor-transcript-utils.js";
|
|
6
|
+
import { getNormalizedCursorToolName } from "./cursor-tool-visibility.js";
|
|
7
|
+
import { buildCursorPiToolDisplay } from "./cursor-tool-transcript.js";
|
|
8
|
+
import { getField } from "./cursor-record-utils.js";
|
|
9
|
+
import { CursorTurnDisplayRouter } from "./cursor-provider-turn-display-router.js";
|
|
10
|
+
import { createTurnCoordinatorContentEmitter, CursorToolLifecycleEmitter, } from "./cursor-provider-turn-lifecycle-emitter.js";
|
|
11
|
+
import { resolveCursorToolCompletion } from "./cursor-provider-turn-sdk-normalizer.js";
|
|
12
|
+
import { CursorShellOutputTracker, formatCursorShellOutputProgressText, getCursorShellOutputDelta, isCursorShellToolCall, } from "./cursor-provider-turn-shell-output.js";
|
|
13
|
+
import { CursorToolCompletionLedger, getToolFingerprint, } from "./cursor-provider-turn-tool-ledger.js";
|
|
14
|
+
import { readCursorSdkTurnUsageFromUpdate } from "./cursor-usage-accounting.js";
|
|
15
|
+
export class CursorSdkTurnCoordinator {
|
|
16
|
+
stream;
|
|
17
|
+
partial;
|
|
18
|
+
cwd;
|
|
19
|
+
resolvedApiKey;
|
|
20
|
+
liveRun;
|
|
21
|
+
useNativeToolReplay;
|
|
22
|
+
activeToolNames;
|
|
23
|
+
nativeReplayId;
|
|
24
|
+
textDeltas;
|
|
25
|
+
debugRecorder;
|
|
26
|
+
ledger = new CursorToolCompletionLedger();
|
|
27
|
+
shellOutput = new CursorShellOutputTracker();
|
|
28
|
+
displayRouter;
|
|
29
|
+
lifecycleEmitter;
|
|
30
|
+
contentEmitter;
|
|
31
|
+
sdkTurnUsage;
|
|
32
|
+
constructor(options) {
|
|
33
|
+
this.stream = options.stream;
|
|
34
|
+
this.partial = options.partial;
|
|
35
|
+
this.cwd = options.cwd;
|
|
36
|
+
this.resolvedApiKey = options.resolvedApiKey;
|
|
37
|
+
this.liveRun = options.liveRun;
|
|
38
|
+
this.useNativeToolReplay = options.useNativeToolReplay;
|
|
39
|
+
this.activeToolNames = options.activeToolNames;
|
|
40
|
+
this.nativeReplayId = options.nativeReplayId;
|
|
41
|
+
this.textDeltas = options.textDeltas;
|
|
42
|
+
this.debugRecorder = options.debugRecorder;
|
|
43
|
+
this.contentEmitter = createTurnCoordinatorContentEmitter(options.stream, options.partial);
|
|
44
|
+
this.displayRouter = new CursorTurnDisplayRouter({
|
|
45
|
+
cwd: options.cwd,
|
|
46
|
+
resolvedApiKey: options.resolvedApiKey,
|
|
47
|
+
liveRun: options.liveRun,
|
|
48
|
+
useNativeToolReplay: options.useNativeToolReplay,
|
|
49
|
+
activeToolNames: options.activeToolNames,
|
|
50
|
+
nativeReplayId: options.nativeReplayId,
|
|
51
|
+
contentEmitter: this.contentEmitter,
|
|
52
|
+
debugRecorder: options.debugRecorder,
|
|
53
|
+
});
|
|
54
|
+
this.lifecycleEmitter = new CursorToolLifecycleEmitter({
|
|
55
|
+
liveRun: options.liveRun,
|
|
56
|
+
resolvedApiKey: options.resolvedApiKey,
|
|
57
|
+
contentEmitter: this.contentEmitter,
|
|
58
|
+
debugRecorder: options.debugRecorder,
|
|
59
|
+
hasStartedToolCall: (callId) => this.ledger.hasStartedToolCall(callId),
|
|
60
|
+
isBridgeMcpToolCall: (toolCall) => options.liveRun?.bridgeRun?.isBridgeMcpToolCall(toolCall) ?? false,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
get planTextCandidate() {
|
|
64
|
+
return this.displayRouter.planTextCandidate;
|
|
65
|
+
}
|
|
66
|
+
get replayStarted() {
|
|
67
|
+
return this.displayRouter.nativeToolReplayStarted;
|
|
68
|
+
}
|
|
69
|
+
get lastSdkTurnUsage() {
|
|
70
|
+
return this.sdkTurnUsage;
|
|
71
|
+
}
|
|
72
|
+
discardIncompleteStartedToolCalls(outcome = buildIncompleteCursorToolRunOutcome()) {
|
|
73
|
+
for (const [callId, toolCall] of this.ledger.startedToolCallEntries()) {
|
|
74
|
+
const toolName = getNormalizedCursorToolName(toolCall);
|
|
75
|
+
recordDiscardedIncompleteStartedToolCall(this.debugRecorder, process.env, {
|
|
76
|
+
toolName,
|
|
77
|
+
callId,
|
|
78
|
+
reason: outcome.reason,
|
|
79
|
+
});
|
|
80
|
+
const visibilityDecision = resolveIncompleteCursorToolVisibility(toolCall, outcome);
|
|
81
|
+
if (visibilityDecision !== "emit") {
|
|
82
|
+
this.displayRouter.recordIncompleteSkip(toolName, visibilityDecision === "debugOnly" && outcome.assistantTextProduced
|
|
83
|
+
? "successful-run-text-produced"
|
|
84
|
+
: visibilityDecision);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const action = this.displayRouter.routeIncompleteStartedToolCall(toolCall, outcome.reason);
|
|
88
|
+
if (action)
|
|
89
|
+
this.displayRouter.emitDisplayAction(action);
|
|
90
|
+
}
|
|
91
|
+
this.ledger.clearStartedToolCalls();
|
|
92
|
+
this.shellOutput.clear();
|
|
93
|
+
this.lifecycleEmitter.clear();
|
|
94
|
+
}
|
|
95
|
+
closeTraceBlock() {
|
|
96
|
+
this.contentEmitter.closeThinking();
|
|
97
|
+
}
|
|
98
|
+
flushText(deltas) {
|
|
99
|
+
return this.contentEmitter.flushText(deltas);
|
|
100
|
+
}
|
|
101
|
+
handleDelta(update) {
|
|
102
|
+
const sdkTurnUsage = readCursorSdkTurnUsageFromUpdate(update);
|
|
103
|
+
if (sdkTurnUsage)
|
|
104
|
+
this.sdkTurnUsage = sdkTurnUsage;
|
|
105
|
+
if (this.liveRun && (update.type === "turn-ended" || sdkTurnUsage)) {
|
|
106
|
+
cursorLiveRuns.recordSdkTurnEnded(this.liveRun, sdkTurnUsage);
|
|
107
|
+
}
|
|
108
|
+
if (update.type === "text-delta") {
|
|
109
|
+
this.textDeltas.push(update.text);
|
|
110
|
+
if (this.liveRun) {
|
|
111
|
+
cursorLiveRuns.queueEvent(this.liveRun, { type: "text-delta", text: update.text });
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
this.contentEmitter.appendTextDelta(update.text);
|
|
115
|
+
}
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (update.type === "thinking-delta") {
|
|
119
|
+
if (this.liveRun) {
|
|
120
|
+
cursorLiveRuns.queueEvent(this.liveRun, { type: "thinking-delta", text: update.text });
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
this.contentEmitter.appendThinkingDelta(update.text);
|
|
124
|
+
}
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (update.type === "thinking-completed") {
|
|
128
|
+
if (this.liveRun) {
|
|
129
|
+
cursorLiveRuns.queueEvent(this.liveRun, { type: "thinking-completed" });
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
this.contentEmitter.closeThinking();
|
|
133
|
+
}
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (update.type === "partial-tool-call") {
|
|
137
|
+
this.lifecycleEmitter.maybeSchedule(update.callId, update.toolCall);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (update.type === "tool-call-started") {
|
|
141
|
+
if (this.liveRun?.bridgeRun?.isBridgeMcpToolCall(update.toolCall)) {
|
|
142
|
+
if (typeof update.callId === "string")
|
|
143
|
+
this.ledger.markBridgeStarted(update.callId);
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
this.lifecycleEmitter.maybeSchedule(update.callId, update.toolCall);
|
|
147
|
+
this.ledger.registerStartedToolCall(update.callId, update.toolCall);
|
|
148
|
+
if (isCursorShellToolCall(update.toolCall) && typeof update.callId === "string") {
|
|
149
|
+
this.shellOutput.onShellToolStarted(update.callId);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (update.type === "tool-call-completed") {
|
|
155
|
+
const resolution = resolveCursorToolCompletion({
|
|
156
|
+
source: "delta",
|
|
157
|
+
callId: update.callId,
|
|
158
|
+
toolCall: update.toolCall,
|
|
159
|
+
startedToolCall: this.ledger.getStartedToolCall(update.callId),
|
|
160
|
+
liveRun: this.liveRun,
|
|
161
|
+
ledger: this.ledger,
|
|
162
|
+
shellOutput: this.shellOutput,
|
|
163
|
+
onClearStartedCallId: (callId) => {
|
|
164
|
+
this.lifecycleEmitter.cancel(callId);
|
|
165
|
+
this.shellOutput.onShellToolCleared(callId);
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
if (resolution.action === "ignore-bridge") {
|
|
169
|
+
this.displayRouter.recordIgnoreBridgeDecision(resolution.identity, getToolName(update.toolCall), "delta");
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
this.handleCompletedToolCall(resolution.toolCall, {
|
|
173
|
+
identity: resolution.identity,
|
|
174
|
+
source: resolution.source,
|
|
175
|
+
});
|
|
176
|
+
if (resolution.matchedStartedCallId && resolution.matchedStartedCallId !== update.callId) {
|
|
177
|
+
this.ledger.recordCompletedIdentity(`cursor-tool:${resolution.matchedStartedCallId}`);
|
|
178
|
+
}
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (update.type === "shell-output-delta") {
|
|
182
|
+
const delta = getCursorShellOutputDelta(update);
|
|
183
|
+
if (delta) {
|
|
184
|
+
const progress = this.shellOutput.appendShellOutputDelta(delta);
|
|
185
|
+
const progressText = progress ? formatCursorShellOutputProgressText(progress, this.resolvedApiKey) : undefined;
|
|
186
|
+
if (progressText) {
|
|
187
|
+
if (this.liveRun) {
|
|
188
|
+
cursorLiveRuns.queueEvent(this.liveRun, { type: "thinking-delta", text: progressText });
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
this.contentEmitter.appendThinkingDelta(progressText);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (update.type === "summary") {
|
|
198
|
+
const summary = `Cursor summary: ${truncateCursorDisplayLine(update.summary)}\n`;
|
|
199
|
+
if (this.liveRun) {
|
|
200
|
+
cursorLiveRuns.queueEvent(this.liveRun, { type: "thinking-delta", text: summary });
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
this.contentEmitter.appendThinkingDelta(summary);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
handleStep(stepEnvelope) {
|
|
208
|
+
const stepType = getField(stepEnvelope, "type");
|
|
209
|
+
const step = getField(stepEnvelope, "message") ? stepEnvelope : undefined;
|
|
210
|
+
const rawStepToolCall = getField(step, "message");
|
|
211
|
+
if (stepType !== "toolCall")
|
|
212
|
+
return;
|
|
213
|
+
const toolCall = rawStepToolCall;
|
|
214
|
+
const stepId = getField(stepEnvelope, "id") ?? getField(toolCall, "id") ?? getField(toolCall, "callId");
|
|
215
|
+
if (!toolCall)
|
|
216
|
+
return;
|
|
217
|
+
const resolution = resolveCursorToolCompletion({
|
|
218
|
+
source: "step",
|
|
219
|
+
callId: stepId,
|
|
220
|
+
toolCall,
|
|
221
|
+
liveRun: this.liveRun,
|
|
222
|
+
ledger: this.ledger,
|
|
223
|
+
shellOutput: this.shellOutput,
|
|
224
|
+
onClearStartedCallId: (callId) => {
|
|
225
|
+
this.lifecycleEmitter.cancel(callId);
|
|
226
|
+
this.shellOutput.onShellToolCleared(callId);
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
if (resolution.action === "ignore-bridge") {
|
|
230
|
+
this.displayRouter.recordIgnoreBridgeDecision(resolution.identity, getToolName(toolCall), "step");
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
this.handleCompletedToolCall(resolution.toolCall, {
|
|
234
|
+
identity: resolution.identity,
|
|
235
|
+
source: resolution.source,
|
|
236
|
+
});
|
|
237
|
+
if (resolution.matchedStartedCallId && resolution.matchedStartedCallId !== stepId) {
|
|
238
|
+
this.ledger.recordCompletedIdentity(`cursor-tool:${resolution.matchedStartedCallId}`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
handleTranscriptCompletedToolCalls(toolCalls) {
|
|
242
|
+
for (const { identity, toolCall } of toolCalls) {
|
|
243
|
+
this.handleCompletedToolCall(toolCall, { identity, source: "transcript" });
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
handleCompletedToolCall(toolCall, options = {}) {
|
|
247
|
+
const display = buildCursorPiToolDisplay(toolCall, { cwd: this.cwd });
|
|
248
|
+
const fingerprint = getToolFingerprint({
|
|
249
|
+
toolName: display.toolName,
|
|
250
|
+
args: display.args,
|
|
251
|
+
result: display.result,
|
|
252
|
+
});
|
|
253
|
+
const duplicateReason = this.ledger.shouldSkipDuplicateCompletion({
|
|
254
|
+
identity: options.identity,
|
|
255
|
+
source: options.source,
|
|
256
|
+
fingerprint,
|
|
257
|
+
});
|
|
258
|
+
if (duplicateReason) {
|
|
259
|
+
this.displayRouter.recordDuplicateSkip(display.toolName, {
|
|
260
|
+
identity: options.identity,
|
|
261
|
+
source: options.source,
|
|
262
|
+
reason: duplicateReason,
|
|
263
|
+
});
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
this.ledger.recordCompletedTool({
|
|
267
|
+
identity: options.identity,
|
|
268
|
+
source: options.source,
|
|
269
|
+
fingerprint,
|
|
270
|
+
});
|
|
271
|
+
const action = this.displayRouter.routeCompletedToolCall(toolCall, options);
|
|
272
|
+
if (action)
|
|
273
|
+
this.displayRouter.emitDisplayAction(action);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { cursorLiveRuns } from "./cursor-provider-live-run-drain.js";
|
|
2
|
+
import { truncateCursorDisplayLine } from "./cursor-display-text.js";
|
|
3
|
+
import { formatInactiveCursorReplayTrace } from "./cursor-native-replay-trace.js";
|
|
4
|
+
import { resolveNativeReplayDisposition } from "./cursor-native-replay-routing.js";
|
|
5
|
+
import { buildIncompleteCursorToolDisplay, formatIncompleteCursorToolTrace, } from "./cursor-incomplete-tool-visibility.js";
|
|
6
|
+
import { scrubPiToolDisplay, scrubSensitiveText } from "./cursor-sensitive-text.js";
|
|
7
|
+
import { buildCursorPiToolDisplay, formatCursorToolTranscript, getCursorCreatePlanText, } from "./cursor-tool-transcript.js";
|
|
8
|
+
import { getToolName } from "./cursor-transcript-utils.js";
|
|
9
|
+
function formatCursorToolName(toolCall) {
|
|
10
|
+
return truncateCursorDisplayLine(getToolName(toolCall), 80) || "unknown";
|
|
11
|
+
}
|
|
12
|
+
export class CursorTurnDisplayRouter {
|
|
13
|
+
cwd;
|
|
14
|
+
resolvedApiKey;
|
|
15
|
+
liveRun;
|
|
16
|
+
useNativeToolReplay;
|
|
17
|
+
activeToolNames;
|
|
18
|
+
nativeReplayId;
|
|
19
|
+
contentEmitter;
|
|
20
|
+
debugRecorder;
|
|
21
|
+
nativeToolDisplayCounter = 0;
|
|
22
|
+
nativeToolReplayStarted = false;
|
|
23
|
+
planTextCandidate;
|
|
24
|
+
constructor(options) {
|
|
25
|
+
this.cwd = options.cwd;
|
|
26
|
+
this.resolvedApiKey = options.resolvedApiKey;
|
|
27
|
+
this.liveRun = options.liveRun;
|
|
28
|
+
this.useNativeToolReplay = options.useNativeToolReplay;
|
|
29
|
+
this.activeToolNames = options.activeToolNames;
|
|
30
|
+
this.nativeReplayId = options.nativeReplayId;
|
|
31
|
+
this.contentEmitter = options.contentEmitter;
|
|
32
|
+
this.debugRecorder = options.debugRecorder;
|
|
33
|
+
}
|
|
34
|
+
routeCompletedToolCall(toolCall, options = {}) {
|
|
35
|
+
const planText = getCursorCreatePlanText(toolCall);
|
|
36
|
+
if (planText)
|
|
37
|
+
this.planTextCandidate = scrubSensitiveText(planText, this.resolvedApiKey);
|
|
38
|
+
const transcript = scrubSensitiveText(formatCursorToolTranscript(toolCall, { cwd: this.cwd }), this.resolvedApiKey);
|
|
39
|
+
const display = buildCursorPiToolDisplay(toolCall, { cwd: this.cwd });
|
|
40
|
+
const disposition = resolveNativeReplayDisposition({
|
|
41
|
+
toolName: display.toolName,
|
|
42
|
+
useNativeToolReplay: this.useNativeToolReplay,
|
|
43
|
+
activeToolNames: this.activeToolNames,
|
|
44
|
+
hasLiveRun: this.liveRun !== undefined,
|
|
45
|
+
});
|
|
46
|
+
if (disposition === "queue_replay" && this.liveRun) {
|
|
47
|
+
this.nativeToolReplayStarted = true;
|
|
48
|
+
const id = `${this.nativeReplayId}-tool-${++this.nativeToolDisplayCounter}`;
|
|
49
|
+
const scrubbedDisplay = scrubPiToolDisplay(display, this.resolvedApiKey);
|
|
50
|
+
this.recordDisplayDecision({
|
|
51
|
+
action: "queue_replay",
|
|
52
|
+
disposition,
|
|
53
|
+
toolName: display.toolName,
|
|
54
|
+
identity: options.identity,
|
|
55
|
+
source: options.source,
|
|
56
|
+
transcript,
|
|
57
|
+
replayToolId: id,
|
|
58
|
+
});
|
|
59
|
+
return { kind: "queue_replay", tool: scrubbedDisplay, replayToolId: id, disposition };
|
|
60
|
+
}
|
|
61
|
+
const traceText = disposition === "inactive_trace"
|
|
62
|
+
? formatInactiveCursorReplayTrace(scrubPiToolDisplay(display, this.resolvedApiKey))
|
|
63
|
+
: transcript || `Cursor tool: ${formatCursorToolName(toolCall)} completed`;
|
|
64
|
+
this.recordDisplayDecision({
|
|
65
|
+
action: "emit_trace",
|
|
66
|
+
disposition,
|
|
67
|
+
toolName: display.toolName,
|
|
68
|
+
identity: options.identity,
|
|
69
|
+
source: options.source,
|
|
70
|
+
transcript,
|
|
71
|
+
traceText,
|
|
72
|
+
});
|
|
73
|
+
return { kind: "emit_trace", traceText, disposition };
|
|
74
|
+
}
|
|
75
|
+
routeIncompleteStartedToolCall(toolCall, reason) {
|
|
76
|
+
const display = scrubPiToolDisplay(buildIncompleteCursorToolDisplay(toolCall, reason, { apiKey: this.resolvedApiKey }), this.resolvedApiKey);
|
|
77
|
+
const disposition = resolveNativeReplayDisposition({
|
|
78
|
+
toolName: display.toolName,
|
|
79
|
+
useNativeToolReplay: this.useNativeToolReplay,
|
|
80
|
+
activeToolNames: this.activeToolNames,
|
|
81
|
+
hasLiveRun: this.liveRun !== undefined,
|
|
82
|
+
});
|
|
83
|
+
if (disposition === "queue_replay" && this.liveRun && reason !== "abort") {
|
|
84
|
+
this.nativeToolReplayStarted = true;
|
|
85
|
+
const id = `${this.nativeReplayId}-tool-${++this.nativeToolDisplayCounter}`;
|
|
86
|
+
this.recordDisplayDecision({
|
|
87
|
+
action: "queue_replay",
|
|
88
|
+
disposition,
|
|
89
|
+
toolName: display.toolName,
|
|
90
|
+
source: "started",
|
|
91
|
+
reason: "incomplete-started-tool-call",
|
|
92
|
+
replayToolId: id,
|
|
93
|
+
});
|
|
94
|
+
return { kind: "queue_replay", tool: display, replayToolId: id, disposition };
|
|
95
|
+
}
|
|
96
|
+
const traceText = disposition === "inactive_trace"
|
|
97
|
+
? formatInactiveCursorReplayTrace(display)
|
|
98
|
+
: formatIncompleteCursorToolTrace(display);
|
|
99
|
+
this.recordDisplayDecision({
|
|
100
|
+
action: "emit_trace",
|
|
101
|
+
disposition,
|
|
102
|
+
toolName: display.toolName,
|
|
103
|
+
source: "started",
|
|
104
|
+
reason: "incomplete-started-tool-call",
|
|
105
|
+
traceText,
|
|
106
|
+
});
|
|
107
|
+
return { kind: "emit_trace", traceText, disposition };
|
|
108
|
+
}
|
|
109
|
+
emitDisplayAction(action) {
|
|
110
|
+
if (action.kind === "queue_replay") {
|
|
111
|
+
if (!this.liveRun)
|
|
112
|
+
return;
|
|
113
|
+
cursorLiveRuns.queueEvent(this.liveRun, {
|
|
114
|
+
type: "tool",
|
|
115
|
+
tool: { ...action.tool, id: action.replayToolId },
|
|
116
|
+
});
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
this.emitCursorToolTrace(action.traceText);
|
|
120
|
+
}
|
|
121
|
+
recordIgnoreBridgeDecision(identity, toolName, source) {
|
|
122
|
+
this.debugRecorder?.recordDisplayDecision({
|
|
123
|
+
action: "ignore-bridge",
|
|
124
|
+
toolName,
|
|
125
|
+
identity,
|
|
126
|
+
source,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
recordDuplicateSkip(toolName, options) {
|
|
130
|
+
this.recordDisplayDecision({
|
|
131
|
+
action: "skip-duplicate",
|
|
132
|
+
toolName,
|
|
133
|
+
identity: options.identity,
|
|
134
|
+
source: options.source,
|
|
135
|
+
reason: options.reason,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
recordIncompleteSkip(toolName, reason) {
|
|
139
|
+
this.recordDisplayDecision({
|
|
140
|
+
action: "skip-incomplete-fast-local",
|
|
141
|
+
toolName,
|
|
142
|
+
source: "started",
|
|
143
|
+
reason,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
recordDisplayDecision(decision) {
|
|
147
|
+
this.debugRecorder?.recordDisplayDecision(decision);
|
|
148
|
+
}
|
|
149
|
+
emitCursorToolTrace(text) {
|
|
150
|
+
const traceText = text.endsWith("\n") ? text : `${text}\n`;
|
|
151
|
+
if (this.liveRun) {
|
|
152
|
+
cursorLiveRuns.queueEvent(this.liveRun, { type: "thinking-delta", text: traceText });
|
|
153
|
+
cursorLiveRuns.queueEvent(this.liveRun, { type: "thinking-completed" });
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
this.contentEmitter.appendThinkingBlock(traceText);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { CursorLiveRunAbortError } from "./cursor-live-run-coordinator.js";
|
|
2
|
+
import { cursorLiveRuns, drainCursorLiveRunTurn, flushPendingCursorLiveRunTraceEventsToStream, settleCursorLiveToolBatch, } from "./cursor-provider-live-run-drain.js";
|
|
3
|
+
import { buildIncompleteCursorToolRunOutcome, } from "./cursor-incomplete-tool-visibility.js";
|
|
4
|
+
export async function emitCursorLiveTurn(emitParams) {
|
|
5
|
+
const { params, prepared, sdkEventDebug, discardIncompleteTools } = emitParams;
|
|
6
|
+
if (prepared.runtime.kind !== "live")
|
|
7
|
+
throw new Error("emitCursorLiveTurn requires a live run");
|
|
8
|
+
const { liveRun, turnCoordinator } = prepared.runtime;
|
|
9
|
+
const { options, model } = params;
|
|
10
|
+
try {
|
|
11
|
+
await cursorLiveRuns.withRunLease(liveRun, options?.signal, async () => {
|
|
12
|
+
await cursorLiveRuns.waitForProgress(liveRun, options?.signal);
|
|
13
|
+
await settleCursorLiveToolBatch(liveRun);
|
|
14
|
+
turnCoordinator.closeTraceBlock();
|
|
15
|
+
await drainCursorLiveRunTurn(params.stream, params.partial, model, params.context, liveRun, 0, {
|
|
16
|
+
mode: "emit",
|
|
17
|
+
signal: options?.signal,
|
|
18
|
+
debugRecorder: sdkEventDebug,
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
catch (caught) {
|
|
23
|
+
if (caught instanceof CursorLiveRunAbortError) {
|
|
24
|
+
discardIncompleteTools({ status: "cancelled", signalAborted: true });
|
|
25
|
+
turnCoordinator.closeTraceBlock();
|
|
26
|
+
flushPendingCursorLiveRunTraceEventsToStream(params.stream, params.partial, liveRun, {
|
|
27
|
+
includeTracesBehindQueuedTools: true,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
throw caught;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function discardIncompleteToolsFromPrepared(prepared, outcome) {
|
|
34
|
+
prepared?.runtime.turnCoordinator.discardIncompleteStartedToolCalls(buildIncompleteCursorToolRunOutcome(outcome));
|
|
35
|
+
}
|