pi-cursor-sdk 0.1.36 → 0.1.38
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 +38 -0
- package/docs/cursor-model-ux-spec.md +1 -1
- package/docs/cursor-native-tool-replay.md +9 -9
- 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 -11
- 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 +12 -47
- package/src/cursor-native-tool-display-state.ts +1 -1
- package/src/cursor-native-tool-display-tools.ts +10 -18
- 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-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 +4 -5
- 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-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 +57 -197
- 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 +16 -9
- package/src/cursor-tool-presentation-registry.ts +42 -169
- 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 +17 -57
- 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 -18
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { AssistantMessage } from "@earendil-works/pi-ai";
|
|
2
|
-
import { cursorLiveRuns } from "./cursor-provider-live-run-drain.js";
|
|
3
|
-
import { abandonSessionCursorAgent } from "./cursor-provider-live-run-drain.js";
|
|
2
|
+
import { abandonSessionCursorAgent, cursorLiveRuns } from "./cursor-provider-live-run-drain.js";
|
|
4
3
|
import {
|
|
5
4
|
classifyCursorRunEmission,
|
|
6
5
|
getCursorRunAbortMessage,
|
|
@@ -12,7 +11,10 @@ import {
|
|
|
12
11
|
sanitizeCursorProviderError,
|
|
13
12
|
} from "./cursor-provider-errors.js";
|
|
14
13
|
import { CursorLiveRunAbortError } from "./cursor-live-run-coordinator.js";
|
|
15
|
-
import
|
|
14
|
+
import {
|
|
15
|
+
buildIncompleteCursorToolRunOutcome,
|
|
16
|
+
type IncompleteCursorToolRunOutcomeInput,
|
|
17
|
+
} from "./cursor-incomplete-tool-visibility.js";
|
|
16
18
|
import type { installCursorSdkProcessErrorGuard } from "./cursor-sdk-process-error-guard.js";
|
|
17
19
|
import type { CursorSdkEventDebugSink } from "./cursor-sdk-event-debug.js";
|
|
18
20
|
import { awaitFinalizeCursorRunOutcome } from "./cursor-provider-turn-finalize.js";
|
|
@@ -24,8 +26,6 @@ import type {
|
|
|
24
26
|
} from "./cursor-provider-turn-types.js";
|
|
25
27
|
import { applyCursorApproximateUsage } from "./cursor-usage-accounting.js";
|
|
26
28
|
import { hasUsableText } from "./cursor-record-utils.js";
|
|
27
|
-
import { buildIncompleteCursorToolRunOutcome } from "./cursor-incomplete-tool-visibility.js";
|
|
28
|
-
|
|
29
29
|
export type CursorTurnTerminalEvent =
|
|
30
30
|
| {
|
|
31
31
|
kind: "direct";
|
|
@@ -10,7 +10,6 @@ import { hasUsableText } from "./cursor-record-utils.js";
|
|
|
10
10
|
import {
|
|
11
11
|
buildIncompleteCursorToolRunOutcome,
|
|
12
12
|
type IncompleteCursorToolRunOutcome,
|
|
13
|
-
type IncompleteCursorToolRunOutcomeInput,
|
|
14
13
|
} from "./cursor-incomplete-tool-visibility.js";
|
|
15
14
|
|
|
16
15
|
/** Unified SDK wait() facts consumed by live and direct emission strategies. */
|
|
@@ -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";
|
|
@@ -32,10 +32,6 @@ import {
|
|
|
32
32
|
getToolFingerprint,
|
|
33
33
|
} from "./cursor-provider-turn-tool-ledger.js";
|
|
34
34
|
|
|
35
|
-
function getNormalizedCursorToolName(toolCall: unknown): string {
|
|
36
|
-
return classifyCursorToolVisibility(toolCall).normalizedName;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
35
|
export interface CursorSdkTurnCoordinatorOptions {
|
|
40
36
|
stream: AssistantMessageEventStream;
|
|
41
37
|
partial: AssistantMessage;
|
|
@@ -207,6 +203,9 @@ export class CursorSdkTurnCoordinator {
|
|
|
207
203
|
identity: resolution.identity,
|
|
208
204
|
source: resolution.source,
|
|
209
205
|
});
|
|
206
|
+
if (resolution.matchedStartedCallId && resolution.matchedStartedCallId !== update.callId) {
|
|
207
|
+
this.ledger.recordCompletedIdentity(`cursor-tool:${resolution.matchedStartedCallId}`);
|
|
208
|
+
}
|
|
210
209
|
return;
|
|
211
210
|
}
|
|
212
211
|
if (update.type === "shell-output-delta") {
|
|
@@ -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,
|
|
@@ -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[],
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { scrubSensitiveText } from "./cursor-sensitive-text.js";
|
|
2
1
|
import type {
|
|
3
2
|
CursorReplayGenerateImageSummaryArgs,
|
|
4
3
|
CursorReplayMcpSummaryArgs,
|
|
@@ -13,16 +12,20 @@ import type {
|
|
|
13
12
|
CursorReplayWebSearchSummaryArgs,
|
|
14
13
|
} from "./cursor-replay-summary-args.js";
|
|
15
14
|
import type { CursorReplayGenerateImageDetailFields } from "./cursor-replay-tool-details.js";
|
|
16
|
-
import { asRecord } from "./cursor-record-utils.js";
|
|
15
|
+
import { asRecord, getArray, getNumber, getString } from "./cursor-record-utils.js";
|
|
16
|
+
import { firstNonEmptyLine, formatDisplayPath, truncateArg } from "./cursor-transcript-utils.js";
|
|
17
17
|
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
18
|
+
collectTaskText,
|
|
19
|
+
getGenerateImageDisplayPath,
|
|
20
|
+
getGenerateImagePath,
|
|
21
|
+
readMcpDisplayResult,
|
|
22
|
+
getReadLintDiagnostics,
|
|
23
|
+
getReadLintPaths,
|
|
24
|
+
getTaskDescription,
|
|
25
|
+
getTodoItems,
|
|
26
|
+
getTodoTotalCount,
|
|
27
|
+
inferImageMimeType,
|
|
28
|
+
} from "./cursor-tool-result-display-readers.js";
|
|
26
29
|
import { extractWebFetchTarget, extractWebSearchQuery } from "./cursor-web-tool-args.js";
|
|
27
30
|
|
|
28
31
|
export interface CursorReplayActivityBuildContext {
|
|
@@ -48,14 +51,6 @@ export function buildDeleteReplayDetailFields({ args, result, options }: CursorR
|
|
|
48
51
|
};
|
|
49
52
|
}
|
|
50
53
|
|
|
51
|
-
export function buildEmptyReplayDetailFields(): Record<string, never> {
|
|
52
|
-
return {};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export function buildCollapsedReplayDetailFields(): { collapseDetailsByDefault: true } {
|
|
56
|
-
return { collapseDetailsByDefault: true };
|
|
57
|
-
}
|
|
58
|
-
|
|
59
54
|
export function buildReadLintsReplaySummaryArgs({
|
|
60
55
|
args,
|
|
61
56
|
result,
|
|
@@ -75,7 +70,7 @@ export function buildTodoReplaySummaryArgs(
|
|
|
75
70
|
result: CursorReplayActivityBuildContext["result"],
|
|
76
71
|
): CursorReplayTodoSummaryArgs {
|
|
77
72
|
const todos = getTodoItems(args, result);
|
|
78
|
-
const totalCount =
|
|
73
|
+
const totalCount = getTodoTotalCount(args, result, todos);
|
|
79
74
|
const completedCount = todos.filter((todo) => todo.status === "completed").length;
|
|
80
75
|
const inProgressCount = todos.filter((todo) => todo.status === "inProgress").length;
|
|
81
76
|
const pendingCount = todos.filter((todo) => todo.status === "pending").length;
|
|
@@ -94,7 +89,7 @@ export function buildCreatePlanReplaySummaryArgs({ args, result }: CursorReplayA
|
|
|
94
89
|
}
|
|
95
90
|
|
|
96
91
|
export function buildTaskReplaySummaryArgs({ args, result }: CursorReplayActivityBuildContext): CursorReplayTaskSummaryArgs {
|
|
97
|
-
const description =
|
|
92
|
+
const description = getTaskDescription(args, result);
|
|
98
93
|
const preview = firstNonEmptyLine(collectTaskText(result));
|
|
99
94
|
return {
|
|
100
95
|
description: truncateArg(description),
|
|
@@ -117,7 +112,7 @@ export function buildGenerateImageReplaySummaryArgs({
|
|
|
117
112
|
|
|
118
113
|
export function buildMcpReplaySummaryArgs({ args, result }: CursorReplayActivityBuildContext): CursorReplayMcpSummaryArgs {
|
|
119
114
|
const toolName = getString(args, "toolName") ?? "mcp";
|
|
120
|
-
const preview =
|
|
115
|
+
const preview = readMcpDisplayResult(result).preview;
|
|
121
116
|
return {
|
|
122
117
|
toolName: truncateArg(toolName),
|
|
123
118
|
...(preview ? { preview } : {}),
|
|
@@ -173,104 +168,3 @@ export function buildGenerateImageReplayDetailFields(
|
|
|
173
168
|
expandedText: contentText,
|
|
174
169
|
};
|
|
175
170
|
}
|
|
176
|
-
|
|
177
|
-
function getReadLintPaths(args: Record<string, unknown>, result: CursorReplayActivityBuildContext["result"], options: CursorReplayActivityBuildContext["options"]): string[] {
|
|
178
|
-
const explicitPaths = Array.isArray(args.paths)
|
|
179
|
-
? args.paths.filter((entry): entry is string => typeof entry === "string")
|
|
180
|
-
: typeof args.path === "string"
|
|
181
|
-
? [args.path]
|
|
182
|
-
: [];
|
|
183
|
-
const resultPaths = (getArray(asRecord(result.value), "fileDiagnostics") ?? [])
|
|
184
|
-
.map((file) => getString(asRecord(file), "path"))
|
|
185
|
-
.filter((entry): entry is string => Boolean(entry));
|
|
186
|
-
return [...new Set([...explicitPaths, ...resultPaths].map((entry) => formatDisplayPath(entry, options.cwd)))];
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function getReadLintDiagnostics(result: CursorReplayActivityBuildContext["result"], options: CursorReplayActivityBuildContext["options"]): string[] {
|
|
190
|
-
const value = asRecord(result.value);
|
|
191
|
-
const files = getArray(value, "fileDiagnostics") ?? [];
|
|
192
|
-
const lines: string[] = [];
|
|
193
|
-
for (const file of files) {
|
|
194
|
-
const fileRecord = asRecord(file);
|
|
195
|
-
const pathValue = getString(fileRecord, "path");
|
|
196
|
-
const path = pathValue ? formatDisplayPath(pathValue, options.cwd) : "unknown";
|
|
197
|
-
const diagnostics = getArray(fileRecord, "diagnostics") ?? [];
|
|
198
|
-
for (const diagnostic of diagnostics) {
|
|
199
|
-
const diagnosticRecord = asRecord(diagnostic);
|
|
200
|
-
const severity = getString(diagnosticRecord, "severity") ?? "diagnostic";
|
|
201
|
-
const message = getString(diagnosticRecord, "message") ?? "";
|
|
202
|
-
const source = getString(diagnosticRecord, "source");
|
|
203
|
-
lines.push(`${path}: ${severity}${source ? ` ${source}` : ""}: ${message}`);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
return lines;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function getTodoItems(args: Record<string, unknown>, result: CursorReplayActivityBuildContext["result"]): Array<{ content: string; status?: string }> {
|
|
210
|
-
const value = asRecord(result.value);
|
|
211
|
-
const rawTodos = getArray(value, "todos") ?? getArray(args, "todos") ?? [];
|
|
212
|
-
const todos: Array<{ content: string; status?: string }> = [];
|
|
213
|
-
for (const todo of rawTodos) {
|
|
214
|
-
const record = asRecord(todo);
|
|
215
|
-
const content = getString(record, "content");
|
|
216
|
-
if (!content) continue;
|
|
217
|
-
const status = getString(record, "status");
|
|
218
|
-
todos.push(status ? { content, status } : { content });
|
|
219
|
-
}
|
|
220
|
-
return todos;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
function collectTaskText(result: CursorReplayActivityBuildContext["result"]): string {
|
|
224
|
-
const value = asRecord(result.value);
|
|
225
|
-
const success = getRecord(getRecord(value, "result"), "success");
|
|
226
|
-
const command = getString(success, "command");
|
|
227
|
-
const stdout = getString(success, "stdout");
|
|
228
|
-
const interleavedOutput = getString(success, "interleavedOutput");
|
|
229
|
-
const assistantMessages = (getArray(value, "conversationSteps") ?? [])
|
|
230
|
-
.map((step) => getString(getRecord(asRecord(step), "assistantMessage"), "text"))
|
|
231
|
-
.filter((entry): entry is string => Boolean(entry));
|
|
232
|
-
const parts = [command ? `$ ${command}` : undefined, stdout || interleavedOutput, ...assistantMessages].filter((part): part is string => Boolean(part));
|
|
233
|
-
return parts.join("\n");
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
function getGenerateImagePath(args: Record<string, unknown>, result: CursorReplayActivityBuildContext["result"]): string | undefined {
|
|
237
|
-
const value = asRecord(result.value);
|
|
238
|
-
return getString(value, "filePath") ?? getString(args, "filePath") ?? getString(args, "path");
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
function getGenerateImageDisplayPath(args: Record<string, unknown>, result: CursorReplayActivityBuildContext["result"], options: CursorReplayActivityBuildContext["options"]): string | undefined {
|
|
242
|
-
const path = getGenerateImagePath(args, result);
|
|
243
|
-
return path ? formatDisplayPath(path, options.cwd) : undefined;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
function inferImageMimeType(path: string | undefined): string | undefined {
|
|
247
|
-
const lower = path?.toLowerCase();
|
|
248
|
-
if (!lower) return undefined;
|
|
249
|
-
if (lower.endsWith(".png")) return "image/png";
|
|
250
|
-
if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
|
|
251
|
-
if (lower.endsWith(".gif")) return "image/gif";
|
|
252
|
-
if (lower.endsWith(".webp")) return "image/webp";
|
|
253
|
-
return undefined;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
function getMcpContentText(entry: unknown): string | undefined {
|
|
257
|
-
const record = asRecord(entry);
|
|
258
|
-
const directText = getString(record, "text");
|
|
259
|
-
if (directText) return directText;
|
|
260
|
-
const nestedText = getRecord(record, "text");
|
|
261
|
-
return getString(nestedText, "text");
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
function getMcpResultPreview(result: CursorReplayActivityBuildContext["result"]): string | undefined {
|
|
265
|
-
if (result.status === "error") return undefined;
|
|
266
|
-
const value = asRecord(result.value);
|
|
267
|
-
const content = getArray(value, "content") ?? [];
|
|
268
|
-
for (const entry of content) {
|
|
269
|
-
const text = getMcpContentText(entry);
|
|
270
|
-
if (text) {
|
|
271
|
-
const line = firstNonEmptyLine(text);
|
|
272
|
-
if (line) return truncateArg(scrubSensitiveText(line), 120);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
return undefined;
|
|
276
|
-
}
|