pi-cursor-sdk 0.1.37 → 0.1.39
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 +34 -0
- package/README.md +2 -2
- package/docs/cursor-model-ux-spec.md +1 -1
- package/docs/cursor-native-tool-replay.md +5 -5
- package/package.json +1 -1
- package/scripts/platform-smoke/card-detect.mjs +1 -1
- package/src/context-window-cache.ts +10 -14
- package/src/context.ts +1 -1
- package/src/cursor-agent-message-web-tools.ts +2 -1
- package/src/cursor-agents-context-registration.ts +18 -0
- package/src/cursor-agents-context.ts +21 -30
- package/src/cursor-edit-diff.ts +4 -2
- package/src/cursor-fallback-warning.ts +22 -0
- package/src/cursor-incomplete-tool-visibility.ts +5 -10
- package/src/cursor-live-run-coordinator.ts +1 -1
- package/src/cursor-mcp-timeout-override.ts +0 -2
- package/src/cursor-model-lifecycle.ts +72 -0
- package/src/cursor-native-replay-routing.ts +1 -1
- package/src/cursor-native-replay-trace.ts +1 -1
- package/src/cursor-native-tool-display-registration.ts +16 -28
- package/src/cursor-native-tool-display-replay.ts +4 -21
- package/src/cursor-native-tool-display-state.ts +1 -1
- package/src/cursor-native-tool-display-tools.ts +10 -17
- package/src/cursor-native-tool-names.ts +16 -0
- package/src/cursor-pi-tool-bridge-env.ts +12 -0
- package/src/cursor-pi-tool-bridge-mcp.ts +16 -21
- package/src/cursor-pi-tool-bridge-run.ts +5 -5
- package/src/cursor-pi-tool-bridge-server.ts +8 -3
- package/src/cursor-pi-tool-bridge-snapshot.ts +7 -13
- package/src/cursor-pi-tool-bridge.ts +7 -7
- package/src/cursor-provider-errors.ts +11 -4
- package/src/cursor-provider-lazy.ts +51 -0
- package/src/cursor-provider-live-run-drain.ts +1 -1
- package/src/cursor-provider-run-finalizer.ts +5 -5
- package/src/cursor-provider-run-outcome.ts +0 -1
- package/src/cursor-provider-turn-coordinator.ts +16 -6
- package/src/cursor-provider-turn-display-router.ts +5 -1
- package/src/cursor-provider-turn-emit.ts +1 -1
- package/src/cursor-provider-turn-lifecycle-emitter.ts +1 -5
- package/src/cursor-provider-turn-prepare.ts +13 -9
- package/src/cursor-provider-turn-runner.ts +3 -11
- package/src/cursor-provider-turn-sdk-normalizer.ts +28 -5
- package/src/cursor-provider-turn-send.ts +7 -2
- package/src/cursor-provider-turn-shell-output.ts +38 -3
- package/src/cursor-provider-turn-types.ts +1 -3
- package/src/cursor-provider.ts +3 -2
- package/src/cursor-question-tool.ts +5 -18
- package/src/cursor-record-utils.ts +42 -0
- package/src/cursor-replay-activity-builders.ts +16 -122
- package/src/cursor-replay-tool-details.ts +52 -80
- package/src/cursor-sdk-event-debug.ts +6 -6
- package/src/cursor-sensitive-text.ts +4 -4
- package/src/cursor-session-agent-lifecycle.ts +47 -0
- package/src/cursor-session-agent.ts +9 -47
- package/src/cursor-session-scope.ts +23 -4
- package/src/cursor-setting-sources.ts +8 -8
- package/src/cursor-skill-tool.ts +25 -32
- package/src/cursor-state.ts +66 -45
- package/src/cursor-tool-lifecycle.ts +22 -10
- package/src/cursor-tool-presentation-registry.ts +27 -18
- package/src/cursor-tool-result-display-readers.ts +185 -0
- package/src/cursor-tool-transcript.ts +17 -33
- package/src/cursor-tool-visibility.ts +9 -1
- package/src/cursor-transcript-tool-formatters.ts +23 -172
- package/src/cursor-transcript-tool-specs.ts +16 -41
- package/src/cursor-transcript-utils.ts +2 -34
- package/src/cursor-usage-accounting.ts +0 -6
- package/src/cursor-web-tool-activity.ts +4 -12
- package/src/cursor-web-tool-args.ts +1 -9
- package/src/index.ts +15 -16
- package/src/model-discovery.ts +5 -4
- package/src/model-list-cache.ts +37 -38
- package/src/cursor-native-tool-display.ts +0 -10
- package/src/cursor-provider-turn-api-key.ts +0 -1
- package/src/cursor-provider-turn-message-offset.ts +0 -15
- package/src/cursor-session-cwd.ts +0 -28
- package/src/cursor-tool-names.ts +0 -9
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
type IncompleteCursorToolRunOutcome,
|
|
14
14
|
} from "./cursor-incomplete-tool-visibility.js";
|
|
15
15
|
import { getToolName } from "./cursor-transcript-utils.js";
|
|
16
|
-
import {
|
|
16
|
+
import { getNormalizedCursorToolName } from "./cursor-tool-visibility.js";
|
|
17
17
|
import { buildCursorPiToolDisplay } from "./cursor-tool-transcript.js";
|
|
18
18
|
import { getField } from "./cursor-record-utils.js";
|
|
19
19
|
import { CursorTurnDisplayRouter } from "./cursor-provider-turn-display-router.js";
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
import { resolveCursorToolCompletion } from "./cursor-provider-turn-sdk-normalizer.js";
|
|
25
25
|
import {
|
|
26
26
|
CursorShellOutputTracker,
|
|
27
|
+
formatCursorShellOutputProgressText,
|
|
27
28
|
getCursorShellOutputDelta,
|
|
28
29
|
isCursorShellToolCall,
|
|
29
30
|
} from "./cursor-provider-turn-shell-output.js";
|
|
@@ -32,10 +33,6 @@ import {
|
|
|
32
33
|
getToolFingerprint,
|
|
33
34
|
} from "./cursor-provider-turn-tool-ledger.js";
|
|
34
35
|
|
|
35
|
-
function getNormalizedCursorToolName(toolCall: unknown): string {
|
|
36
|
-
return classifyCursorToolVisibility(toolCall).normalizedName;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
36
|
export interface CursorSdkTurnCoordinatorOptions {
|
|
40
37
|
stream: AssistantMessageEventStream;
|
|
41
38
|
partial: AssistantMessage;
|
|
@@ -207,11 +204,24 @@ export class CursorSdkTurnCoordinator {
|
|
|
207
204
|
identity: resolution.identity,
|
|
208
205
|
source: resolution.source,
|
|
209
206
|
});
|
|
207
|
+
if (resolution.matchedStartedCallId && resolution.matchedStartedCallId !== update.callId) {
|
|
208
|
+
this.ledger.recordCompletedIdentity(`cursor-tool:${resolution.matchedStartedCallId}`);
|
|
209
|
+
}
|
|
210
210
|
return;
|
|
211
211
|
}
|
|
212
212
|
if (update.type === "shell-output-delta") {
|
|
213
213
|
const delta = getCursorShellOutputDelta(update);
|
|
214
|
-
if (delta)
|
|
214
|
+
if (delta) {
|
|
215
|
+
const progress = this.shellOutput.appendShellOutputDelta(delta);
|
|
216
|
+
const progressText = progress ? formatCursorShellOutputProgressText(progress, this.resolvedApiKey) : undefined;
|
|
217
|
+
if (progressText) {
|
|
218
|
+
if (this.liveRun) {
|
|
219
|
+
cursorLiveRuns.queueEvent(this.liveRun, { type: "thinking-delta", text: progressText });
|
|
220
|
+
} else {
|
|
221
|
+
this.contentEmitter.appendThinkingDelta(progressText);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
215
225
|
return;
|
|
216
226
|
}
|
|
217
227
|
if (update.type === "summary") {
|
|
@@ -10,7 +10,11 @@ import {
|
|
|
10
10
|
type IncompleteCursorToolDiscardReason,
|
|
11
11
|
} from "./cursor-incomplete-tool-visibility.js";
|
|
12
12
|
import { scrubPiToolDisplay, scrubSensitiveText } from "./cursor-sensitive-text.js";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
buildCursorPiToolDisplay,
|
|
15
|
+
formatCursorToolTranscript,
|
|
16
|
+
getCursorCreatePlanText,
|
|
17
|
+
} from "./cursor-tool-transcript.js";
|
|
14
18
|
import { getToolName } from "./cursor-transcript-utils.js";
|
|
15
19
|
import type { CursorPartialContentEmitter } from "./cursor-partial-content-emitter.js";
|
|
16
20
|
import type { CursorToolDisplaySource } from "./cursor-provider-turn-tool-ledger.js";
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { CursorLiveRunAbortError } from "./cursor-live-run-coordinator.js";
|
|
2
2
|
import {
|
|
3
|
+
cursorLiveRuns,
|
|
3
4
|
drainCursorLiveRunTurn,
|
|
4
5
|
flushPendingCursorLiveRunTraceEventsToStream,
|
|
5
6
|
settleCursorLiveToolBatch,
|
|
6
7
|
} from "./cursor-provider-live-run-drain.js";
|
|
7
|
-
import { cursorLiveRuns } from "./cursor-provider-live-run-drain.js";
|
|
8
8
|
import {
|
|
9
9
|
buildIncompleteCursorToolRunOutcome,
|
|
10
10
|
type IncompleteCursorToolRunOutcomeInput,
|
|
@@ -8,13 +8,9 @@ import {
|
|
|
8
8
|
formatCursorToolLifecycleProgressText,
|
|
9
9
|
isCursorToolLifecycleEligible,
|
|
10
10
|
} from "./cursor-tool-lifecycle.js";
|
|
11
|
-
import {
|
|
11
|
+
import { getNormalizedCursorToolName } from "./cursor-tool-visibility.js";
|
|
12
12
|
import { getStartedToolCallFingerprint } from "./cursor-provider-turn-tool-ledger.js";
|
|
13
13
|
|
|
14
|
-
function getNormalizedCursorToolName(toolCall: unknown): string {
|
|
15
|
-
return classifyCursorToolVisibility(toolCall).normalizedName;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
14
|
export interface CursorToolLifecycleEmitterOptions {
|
|
19
15
|
liveRun?: CursorLiveRun;
|
|
20
16
|
resolvedApiKey?: string;
|
|
@@ -8,23 +8,27 @@ import {
|
|
|
8
8
|
resetSessionCursorAgent,
|
|
9
9
|
} from "./cursor-session-agent.js";
|
|
10
10
|
import type { CursorPiBridgeToolRequest } from "./cursor-pi-tool-bridge.js";
|
|
11
|
-
import {
|
|
11
|
+
import { estimateCursorPromptTokens } from "./context.js";
|
|
12
|
+
import { getCursorPromptOptions } from "./cursor-usage-accounting.js";
|
|
12
13
|
import { getActiveContextToolNames } from "./cursor-context-tools.js";
|
|
13
14
|
import type { CursorLiveRun } from "./cursor-live-run-coordinator.js";
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
import {
|
|
16
|
+
abandonSessionCursorAgent,
|
|
17
|
+
createCursorNativeReplayId,
|
|
18
|
+
cursorLiveRuns,
|
|
19
|
+
} from "./cursor-provider-live-run-drain.js";
|
|
20
|
+
import { getCursorProviderAgentModeOrThrow, getEffectiveFastForModelId } from "./cursor-state.js";
|
|
17
21
|
import { buildCursorModelSelection } from "./model-discovery.js";
|
|
18
22
|
import { getEffectiveCursorSettingSources } from "./cursor-setting-sources.js";
|
|
19
|
-
import { resolveCursorPiToolBridgeEnabled } from "./cursor-pi-tool-bridge-
|
|
23
|
+
import { resolveCursorPiToolBridgeEnabled } from "./cursor-pi-tool-bridge-env.js";
|
|
20
24
|
import {
|
|
21
25
|
buildCursorToolManifestText,
|
|
22
26
|
resolveCursorToolManifestEnabled,
|
|
23
27
|
} from "./cursor-tool-manifest.js";
|
|
24
|
-
import { isCursorNativeToolDisplayRuntimeEnabled } from "./cursor-native-tool-display.js";
|
|
28
|
+
import { isCursorNativeToolDisplayRuntimeEnabled } from "./cursor-native-tool-display-state.js";
|
|
25
29
|
import { MISSING_CURSOR_API_KEY_MESSAGE } from "./cursor-provider-errors.js";
|
|
26
30
|
import { CursorSdkTurnCoordinator } from "./cursor-provider-turn-coordinator.js";
|
|
27
|
-
import { resolveCursorApiKey } from "./cursor-
|
|
31
|
+
import { resolveCursorApiKey } from "./cursor-api-key.js";
|
|
28
32
|
import { loadCursorSdk } from "./cursor-sdk-runtime.js";
|
|
29
33
|
import type {
|
|
30
34
|
CursorProviderTurnPrepareResult,
|
|
@@ -53,7 +57,7 @@ export async function prepareCursorProviderTurn(
|
|
|
53
57
|
|
|
54
58
|
try {
|
|
55
59
|
const fastEnabled = getEffectiveFastForModelId(model.id);
|
|
56
|
-
const agentMode =
|
|
60
|
+
const agentMode = getCursorProviderAgentModeOrThrow();
|
|
57
61
|
const selection = buildCursorModelSelection(model.id, options?.reasoning ?? "off", fastEnabled);
|
|
58
62
|
const settingSources = getEffectiveCursorSettingSources();
|
|
59
63
|
const { Agent } = await loadCursorSdk();
|
|
@@ -116,7 +120,7 @@ export async function prepareCursorProviderTurn(
|
|
|
116
120
|
images: prompt.images.length > 0 ? prompt.images : undefined,
|
|
117
121
|
};
|
|
118
122
|
const sessionBridgeRun = bridgeRun;
|
|
119
|
-
const promptInputTokens =
|
|
123
|
+
const promptInputTokens = estimateCursorPromptTokens(prompt, promptOptions);
|
|
120
124
|
const useNativeToolReplay = isCursorNativeToolDisplayRuntimeEnabled();
|
|
121
125
|
const activeToolNames = getActiveContextToolNames(context);
|
|
122
126
|
sdkEventDebug?.recordProviderMeta({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CursorLiveRunAbortError } from "./cursor-live-run-coordinator.js";
|
|
2
2
|
import { drainExistingCursorLiveRunBeforeSend } from "./cursor-provider-live-run-drain.js";
|
|
3
|
-
import { getCursorSessionCwd } from "./cursor-session-
|
|
3
|
+
import { getCursorSessionCwd } from "./cursor-session-scope.js";
|
|
4
4
|
import { installCursorSdkProcessErrorGuard } from "./cursor-sdk-process-error-guard.js";
|
|
5
5
|
import { CursorSdkEventDebugSink } from "./cursor-sdk-event-debug.js";
|
|
6
6
|
import { awaitFinalizeCursorRunOutcome } from "./cursor-provider-turn-finalize.js";
|
|
@@ -17,7 +17,6 @@ import type {
|
|
|
17
17
|
CursorProviderTurnSendResult,
|
|
18
18
|
} from "./cursor-provider-turn-types.js";
|
|
19
19
|
|
|
20
|
-
export { resolveCursorApiKey } from "./cursor-provider-turn-api-key.js";
|
|
21
20
|
export type { CursorProviderTurnRunnerParams } from "./cursor-provider-turn-types.js";
|
|
22
21
|
|
|
23
22
|
export class CursorProviderTurnRunner {
|
|
@@ -34,13 +33,6 @@ export class CursorProviderTurnRunner {
|
|
|
34
33
|
if (this.options?.signal?.aborted) throw new CursorLiveRunAbortError();
|
|
35
34
|
}
|
|
36
35
|
|
|
37
|
-
private discardIncompleteTools(
|
|
38
|
-
prepared: CursorProviderTurnPrepareResult | undefined,
|
|
39
|
-
outcome: import("./cursor-incomplete-tool-visibility.js").IncompleteCursorToolRunOutcomeInput,
|
|
40
|
-
): void {
|
|
41
|
-
discardIncompleteToolsFromPrepared(prepared, outcome);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
36
|
async run(sdkProcessErrorGuard: ReturnType<typeof installCursorSdkProcessErrorGuard>): Promise<void> {
|
|
45
37
|
const { stream, partial, model, context, options, sdkEventDebugRef } = this.params;
|
|
46
38
|
let prepared: CursorProviderTurnPrepareResult | undefined;
|
|
@@ -94,13 +86,13 @@ export class CursorProviderTurnRunner {
|
|
|
94
86
|
send,
|
|
95
87
|
prepared,
|
|
96
88
|
modelId: model.id,
|
|
97
|
-
discardIncompleteTools: (outcome) =>
|
|
89
|
+
discardIncompleteTools: (outcome) => discardIncompleteToolsFromPrepared(prepared, outcome),
|
|
98
90
|
});
|
|
99
91
|
await emitCursorLiveTurn({
|
|
100
92
|
params: this.params,
|
|
101
93
|
prepared,
|
|
102
94
|
sdkEventDebug: this.sdkEventDebug,
|
|
103
|
-
discardIncompleteTools: (outcome) =>
|
|
95
|
+
discardIncompleteTools: (outcome) => discardIncompleteToolsFromPrepared(prepared, outcome),
|
|
104
96
|
});
|
|
105
97
|
return;
|
|
106
98
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { asRecord } from "./cursor-record-utils.js";
|
|
2
2
|
import type { CursorLiveRun } from "./cursor-live-run-coordinator.js";
|
|
3
3
|
import {
|
|
4
4
|
mergeShellOutputDeltasIntoCursorToolCall,
|
|
@@ -8,6 +8,22 @@ import type { CursorToolCompletionLedger } from "./cursor-provider-turn-tool-led
|
|
|
8
8
|
|
|
9
9
|
export type CursorToolCompletionSource = "delta" | "step";
|
|
10
10
|
|
|
11
|
+
function mergeCursorToolCalls(startedToolCall: unknown, completedToolCall: unknown): unknown {
|
|
12
|
+
const started = asRecord(startedToolCall);
|
|
13
|
+
const completed = asRecord(completedToolCall);
|
|
14
|
+
if (!started) return completedToolCall;
|
|
15
|
+
if (!completed) return startedToolCall;
|
|
16
|
+
return {
|
|
17
|
+
...started,
|
|
18
|
+
...completed,
|
|
19
|
+
name: completed.name ?? started.name,
|
|
20
|
+
type: completed.type ?? started.type,
|
|
21
|
+
args: completed.args ?? started.args,
|
|
22
|
+
input: completed.input ?? started.input,
|
|
23
|
+
result: completed.result ?? started.result,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
11
27
|
export type ToolCompletionResolution =
|
|
12
28
|
| { action: "ignore-bridge"; identity?: string }
|
|
13
29
|
| {
|
|
@@ -47,15 +63,22 @@ export function resolveCursorToolCompletion(options: ResolveCursorToolCompletion
|
|
|
47
63
|
const callId = options.callId;
|
|
48
64
|
identity = typeof callId === "string" ? `cursor-tool:${callId}` : undefined;
|
|
49
65
|
resolvedToolCall = mergeCursorToolCalls(options.startedToolCall, options.toolCall);
|
|
50
|
-
if (typeof callId === "string") {
|
|
66
|
+
if (typeof callId === "string" && options.ledger.hasStartedToolCall(callId)) {
|
|
51
67
|
options.onClearStartedCallId?.(callId);
|
|
52
68
|
options.ledger.clearStartedToolCall(callId);
|
|
69
|
+
} else {
|
|
70
|
+
matchedStartedCallId = options.ledger.removeStartedToolCallForStep(options.toolCall, callId);
|
|
71
|
+
if (matchedStartedCallId) options.onClearStartedCallId?.(matchedStartedCallId);
|
|
53
72
|
}
|
|
54
73
|
resolvedToolCall = mergeShellOutputDeltasIntoCursorToolCall(
|
|
55
74
|
resolvedToolCall,
|
|
56
|
-
|
|
75
|
+
matchedStartedCallId
|
|
76
|
+
? options.shellOutput.takeDeltasForCall(matchedStartedCallId)
|
|
77
|
+
: typeof callId === "string"
|
|
78
|
+
? options.shellOutput.takeDeltasForCall(callId)
|
|
79
|
+
: undefined,
|
|
57
80
|
);
|
|
58
|
-
source = identity ? "started" : "fallback";
|
|
81
|
+
source = identity || matchedStartedCallId ? "started" : "fallback";
|
|
59
82
|
} else {
|
|
60
83
|
matchedStartedCallId = options.ledger.removeStartedToolCallForStep(options.toolCall, options.callId);
|
|
61
84
|
if (matchedStartedCallId) {
|
|
@@ -77,7 +100,7 @@ export function resolveCursorToolCompletion(options: ResolveCursorToolCompletion
|
|
|
77
100
|
}
|
|
78
101
|
|
|
79
102
|
if (options.source === "delta") {
|
|
80
|
-
return { action: "handle", toolCall: resolvedToolCall, identity, source };
|
|
103
|
+
return { action: "handle", toolCall: resolvedToolCall, identity, source, matchedStartedCallId };
|
|
81
104
|
}
|
|
82
105
|
return {
|
|
83
106
|
action: "handle",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { SendOptions } from "@cursor/sdk";
|
|
2
|
+
import { countCursorAgentMessages } from "./cursor-agent-message-web-tools.js";
|
|
2
3
|
import { CursorLiveRunAbortError } from "./cursor-live-run-coordinator.js";
|
|
3
4
|
import { cursorLiveRuns } from "./cursor-provider-live-run-drain.js";
|
|
4
|
-
import { getCursorAgentMessageOffset } from "./cursor-provider-turn-message-offset.js";
|
|
5
5
|
import type { installCursorSdkProcessErrorGuard } from "./cursor-sdk-process-error-guard.js";
|
|
6
6
|
import type {
|
|
7
7
|
CursorProviderTurnRunnerParams,
|
|
@@ -41,7 +41,12 @@ export async function sendCursorProviderTurn(sendParams: SendCursorProviderTurnP
|
|
|
41
41
|
try {
|
|
42
42
|
abortRegistration?.signal.addEventListener("abort", abortListener, { once: true });
|
|
43
43
|
throwIfAborted();
|
|
44
|
-
|
|
44
|
+
let cursorAgentMessageOffset: number | undefined;
|
|
45
|
+
try {
|
|
46
|
+
cursorAgentMessageOffset = await countCursorAgentMessages(agent.agentId, cwd);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
sdkEventDebug?.recordError("cursor_agent_message_count", error);
|
|
49
|
+
}
|
|
45
50
|
throwIfAborted();
|
|
46
51
|
sdkEventDebug?.recordSendMeta({
|
|
47
52
|
mode: meta.sendPlan.mode,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { InteractionUpdate } from "@cursor/sdk";
|
|
2
2
|
import { asRecord, getField, hasUsableText } from "./cursor-record-utils.js";
|
|
3
|
+
import { scrubSensitiveText } from "./cursor-sensitive-text.js";
|
|
4
|
+
import { truncateCursorDisplayLine } from "./cursor-display-text.js";
|
|
3
5
|
import { classifyCursorToolVisibility } from "./cursor-tool-visibility.js";
|
|
4
6
|
|
|
5
7
|
export interface CursorShellOutputDelta {
|
|
@@ -12,6 +14,12 @@ export interface CursorShellOutputDeltas {
|
|
|
12
14
|
stderr: string[];
|
|
13
15
|
}
|
|
14
16
|
|
|
17
|
+
export interface CursorShellOutputProgressDelta extends CursorShellOutputDelta {
|
|
18
|
+
callId: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const SHELL_OUTPUT_PROGRESS_MAX_DELTAS_PER_CALL = 3;
|
|
22
|
+
|
|
15
23
|
export function isCursorShellToolCall(toolCall: unknown): boolean {
|
|
16
24
|
return classifyCursorToolVisibility(toolCall).normalizedKey === "shell";
|
|
17
25
|
}
|
|
@@ -27,6 +35,22 @@ export function getCursorShellOutputDelta(update: InteractionUpdate): CursorShel
|
|
|
27
35
|
return { stream: eventCase, data };
|
|
28
36
|
}
|
|
29
37
|
|
|
38
|
+
function getCursorShellOutputProgressPreview(data: string): string | undefined {
|
|
39
|
+
return data
|
|
40
|
+
.split(/\r?\n/)
|
|
41
|
+
.map((line) => line.trim())
|
|
42
|
+
.find((line) => line.length > 0);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function formatCursorShellOutputProgressText(
|
|
46
|
+
progress: CursorShellOutputProgressDelta,
|
|
47
|
+
apiKey?: string,
|
|
48
|
+
): string | undefined {
|
|
49
|
+
const preview = getCursorShellOutputProgressPreview(progress.data);
|
|
50
|
+
if (!preview) return undefined;
|
|
51
|
+
return `Cursor shell ${progress.stream}: ${truncateCursorDisplayLine(scrubSensitiveText(preview, apiKey), 160)}\n`;
|
|
52
|
+
}
|
|
53
|
+
|
|
30
54
|
export function mergeShellOutputDeltasIntoCursorToolCall(
|
|
31
55
|
toolCall: unknown,
|
|
32
56
|
deltas: CursorShellOutputDeltas | undefined,
|
|
@@ -65,6 +89,7 @@ export class CursorShellOutputTracker {
|
|
|
65
89
|
private readonly activeShellCallIds = new Set<string>();
|
|
66
90
|
private readonly ambiguousShellOutputCallIds = new Set<string>();
|
|
67
91
|
private readonly shellOutputDeltasByCallId = new Map<string, CursorShellOutputDeltas>();
|
|
92
|
+
private readonly shellOutputProgressCountsByCallId = new Map<string, number>();
|
|
68
93
|
|
|
69
94
|
onShellToolStarted(callId: string): void {
|
|
70
95
|
this.activeShellCallIds.add(callId);
|
|
@@ -73,29 +98,38 @@ export class CursorShellOutputTracker {
|
|
|
73
98
|
onShellToolCleared(callId: string): void {
|
|
74
99
|
this.activeShellCallIds.delete(callId);
|
|
75
100
|
this.ambiguousShellOutputCallIds.delete(callId);
|
|
101
|
+
this.shellOutputProgressCountsByCallId.delete(callId);
|
|
76
102
|
}
|
|
77
103
|
|
|
78
|
-
appendShellOutputDelta(delta: CursorShellOutputDelta):
|
|
104
|
+
appendShellOutputDelta(delta: CursorShellOutputDelta): CursorShellOutputProgressDelta | undefined {
|
|
79
105
|
if (this.activeShellCallIds.size !== 1) {
|
|
80
106
|
for (const activeCallId of this.activeShellCallIds) {
|
|
81
107
|
this.ambiguousShellOutputCallIds.add(activeCallId);
|
|
82
108
|
this.shellOutputDeltasByCallId.delete(activeCallId);
|
|
109
|
+
this.shellOutputProgressCountsByCallId.delete(activeCallId);
|
|
83
110
|
}
|
|
84
|
-
return;
|
|
111
|
+
return undefined;
|
|
85
112
|
}
|
|
86
113
|
const [callId] = this.activeShellCallIds;
|
|
87
|
-
if (!callId || this.ambiguousShellOutputCallIds.has(callId)) return;
|
|
114
|
+
if (!callId || this.ambiguousShellOutputCallIds.has(callId)) return undefined;
|
|
88
115
|
let deltas = this.shellOutputDeltasByCallId.get(callId);
|
|
89
116
|
if (!deltas) {
|
|
90
117
|
deltas = { stdout: [], stderr: [] };
|
|
91
118
|
this.shellOutputDeltasByCallId.set(callId, deltas);
|
|
92
119
|
}
|
|
93
120
|
deltas[delta.stream].push(delta.data);
|
|
121
|
+
|
|
122
|
+
if (!getCursorShellOutputProgressPreview(delta.data)) return undefined;
|
|
123
|
+
const progressCount = this.shellOutputProgressCountsByCallId.get(callId) ?? 0;
|
|
124
|
+
if (progressCount >= SHELL_OUTPUT_PROGRESS_MAX_DELTAS_PER_CALL) return undefined;
|
|
125
|
+
this.shellOutputProgressCountsByCallId.set(callId, progressCount + 1);
|
|
126
|
+
return { ...delta, callId };
|
|
94
127
|
}
|
|
95
128
|
|
|
96
129
|
takeDeltasForCall(callId: string): CursorShellOutputDeltas | undefined {
|
|
97
130
|
const deltas = this.shellOutputDeltasByCallId.get(callId);
|
|
98
131
|
this.shellOutputDeltasByCallId.delete(callId);
|
|
132
|
+
this.shellOutputProgressCountsByCallId.delete(callId);
|
|
99
133
|
return deltas;
|
|
100
134
|
}
|
|
101
135
|
|
|
@@ -103,5 +137,6 @@ export class CursorShellOutputTracker {
|
|
|
103
137
|
this.activeShellCallIds.clear();
|
|
104
138
|
this.ambiguousShellOutputCallIds.clear();
|
|
105
139
|
this.shellOutputDeltasByCallId.clear();
|
|
140
|
+
this.shellOutputProgressCountsByCallId.clear();
|
|
106
141
|
}
|
|
107
142
|
}
|
|
@@ -61,7 +61,7 @@ export type CursorProviderTurnRuntime = DirectCursorProviderTurnRuntime | LiveCu
|
|
|
61
61
|
* Send, finalize, and cleanup phases receive this immutable object instead of
|
|
62
62
|
* keeping parallel liveRun/turnCoordinator/resource bags in sync by convention.
|
|
63
63
|
*/
|
|
64
|
-
export interface
|
|
64
|
+
export interface CursorProviderTurnPrepareResult {
|
|
65
65
|
agent: SDKAgent;
|
|
66
66
|
cwd: string;
|
|
67
67
|
payload: CursorProviderTurnSendPayload;
|
|
@@ -74,8 +74,6 @@ export interface PreparedCursorProviderTurn {
|
|
|
74
74
|
runtime: CursorProviderTurnRuntime;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
export type CursorProviderTurnPrepareResult = PreparedCursorProviderTurn;
|
|
78
|
-
|
|
79
77
|
export interface CursorProviderTurnSend {
|
|
80
78
|
run: Awaited<ReturnType<SDKAgent["send"]>>;
|
|
81
79
|
cursorAgentMessageOffset: number | undefined;
|
package/src/cursor-provider.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
type SimpleStreamOptions,
|
|
9
9
|
} from "@earendil-works/pi-ai";
|
|
10
10
|
import {
|
|
11
|
+
cursorLiveRuns,
|
|
11
12
|
DEFAULT_CURSOR_NATIVE_REPLAY_IDLE_DISPOSE_MS,
|
|
12
13
|
getPendingCursorLiveRun,
|
|
13
14
|
hasTrailingUserMessagesAfterToolResults,
|
|
@@ -15,12 +16,12 @@ import {
|
|
|
15
16
|
resetCursorNativeReplayIdleDisposeMs,
|
|
16
17
|
setCursorNativeReplayIdleDisposeMs,
|
|
17
18
|
} from "./cursor-provider-live-run-drain.js";
|
|
18
|
-
import { cursorLiveRuns } from "./cursor-provider-live-run-drain.js";
|
|
19
19
|
import { disposeAllSessionCursorAgents } from "./cursor-session-agent.js";
|
|
20
20
|
import { attachCursorSdkEventDebugPiStreamTap, type CursorSdkEventDebugSink } from "./cursor-sdk-event-debug.js";
|
|
21
21
|
import { installCursorSdkProcessErrorGuard } from "./cursor-sdk-process-error-guard.js";
|
|
22
22
|
import { sanitizeCursorProviderError } from "./cursor-provider-errors.js";
|
|
23
|
-
import {
|
|
23
|
+
import { resolveCursorApiKey } from "./cursor-api-key.js";
|
|
24
|
+
import { CursorProviderTurnRunner } from "./cursor-provider-turn-runner.js";
|
|
24
25
|
|
|
25
26
|
function makeInitialMessage(model: Model<Api>): AssistantMessage {
|
|
26
27
|
return {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Text } from "@earendil-works/pi-tui";
|
|
3
3
|
import { Type } from "typebox";
|
|
4
4
|
import { isCursorModel } from "./cursor-model.js";
|
|
5
|
-
import {
|
|
5
|
+
import { registerCursorModelLifecycle, type CursorModelLifecycleExtensionApi } from "./cursor-model-lifecycle.js";
|
|
6
|
+
import { resolveCursorPiToolBridgeEnabled } from "./cursor-pi-tool-bridge-env.js";
|
|
6
7
|
|
|
7
8
|
export const CURSOR_ASK_QUESTION_TOOL_NAME = "cursor_ask_question";
|
|
8
9
|
|
|
@@ -35,12 +36,7 @@ interface CursorQuestionDetails {
|
|
|
35
36
|
cancelled: boolean;
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
interface CursorQuestionToolExtensionApi extends Pick<ExtensionAPI, "getActiveTools" | "registerTool" | "setActiveTools"
|
|
39
|
-
on(event: "session_start", handler: ExtensionHandler<SessionStartEvent>): void;
|
|
40
|
-
on(event: "before_agent_start", handler: ExtensionHandler<BeforeAgentStartEvent>): void;
|
|
41
|
-
on(event: "turn_start", handler: ExtensionHandler<TurnStartEvent>): void;
|
|
42
|
-
on(event: "model_select", handler: (event: { model: ExtensionContext["model"] }, ctx: ExtensionContext) => Promise<void> | void): void;
|
|
43
|
-
}
|
|
39
|
+
interface CursorQuestionToolExtensionApi extends Pick<ExtensionAPI, "getActiveTools" | "registerTool" | "setActiveTools">, CursorModelLifecycleExtensionApi {}
|
|
44
40
|
|
|
45
41
|
type RawQuestionOption = string | { label?: string; value?: string; description?: string };
|
|
46
42
|
|
|
@@ -232,16 +228,7 @@ export function registerCursorQuestionTool(pi: CursorQuestionToolExtensionApi):
|
|
|
232
228
|
},
|
|
233
229
|
});
|
|
234
230
|
|
|
235
|
-
pi
|
|
236
|
-
syncCursorQuestionToolForModel(pi, ctx.model);
|
|
237
|
-
});
|
|
238
|
-
pi.on("before_agent_start", (_event, ctx) => {
|
|
231
|
+
registerCursorModelLifecycle(pi, (ctx) => {
|
|
239
232
|
syncCursorQuestionToolForModel(pi, ctx.model);
|
|
240
233
|
});
|
|
241
|
-
pi.on("turn_start", (_event, ctx) => {
|
|
242
|
-
syncCursorQuestionToolForModel(pi, ctx.model);
|
|
243
|
-
});
|
|
244
|
-
pi.on("model_select", (event) => {
|
|
245
|
-
syncCursorQuestionToolForModel(pi, event.model);
|
|
246
|
-
});
|
|
247
234
|
}
|
|
@@ -10,6 +10,48 @@ export function hasUsableText(value: string | undefined): value is string {
|
|
|
10
10
|
return typeof value === "string" && value.trim().length > 0;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
export function getString(record: Record<string, unknown> | undefined, key: string): string | undefined {
|
|
14
|
+
const value = record?.[key];
|
|
15
|
+
return typeof value === "string" ? value : undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function getNumber(record: Record<string, unknown> | undefined, key: string): number | undefined {
|
|
19
|
+
const value = record?.[key];
|
|
20
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function getBoolean(record: Record<string, unknown> | undefined, key: string): boolean | undefined {
|
|
24
|
+
const value = record?.[key];
|
|
25
|
+
return typeof value === "boolean" ? value : undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function getRecord(record: Record<string, unknown> | undefined, key: string): Record<string, unknown> | undefined {
|
|
29
|
+
return asRecord(record?.[key]);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function getArray(record: Record<string, unknown> | undefined, key: string): unknown[] | undefined {
|
|
33
|
+
const value = record?.[key];
|
|
34
|
+
return Array.isArray(value) ? value : undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function firstNonEmptyString(...values: Array<string | undefined>): string | undefined {
|
|
38
|
+
for (const value of values) {
|
|
39
|
+
const trimmed = value?.trim();
|
|
40
|
+
if (trimmed) return trimmed;
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function stringifyUnknown(value: unknown, options: { pretty?: boolean } = {}): string {
|
|
46
|
+
if (value === undefined) return "";
|
|
47
|
+
if (typeof value === "string") return value;
|
|
48
|
+
try {
|
|
49
|
+
return JSON.stringify(value, null, options.pretty ? 2 : undefined) ?? String(value);
|
|
50
|
+
} catch {
|
|
51
|
+
return String(value);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
13
55
|
export function getFirstStringByKeys(
|
|
14
56
|
record: Record<string, unknown> | undefined,
|
|
15
57
|
keys: readonly string[],
|