pi-cursor-sdk 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/README.md +65 -13
- package/dist/context-window-cache.js +0 -6
- package/dist/context.js +8 -0
- package/dist/cursor-cloud-lifecycle.js +122 -98
- package/dist/cursor-durable-fs.js +97 -3
- package/dist/cursor-fallback-models.generated.js +288 -1
- package/dist/cursor-fallback-warning.js +2 -2
- package/dist/cursor-http1.js +8 -8
- package/dist/cursor-live-run-coordinator.js +9 -13
- package/dist/cursor-native-replay-routing.js +1 -1
- package/dist/cursor-native-tool-display-registration.js +46 -22
- package/dist/cursor-native-tool-display-state.js +57 -22
- package/dist/cursor-native-tool-display-tools.js +5 -6
- package/dist/cursor-pi-tool-bridge-constants.js +23 -2
- package/dist/cursor-pi-tool-bridge-run.js +7 -3
- package/dist/cursor-pi-tool-bridge-snapshot.js +1 -1
- package/dist/cursor-pi-tool-bridge.js +31 -9
- package/dist/cursor-provider-binding.js +58 -0
- package/dist/cursor-provider-errors.js +28 -7
- package/dist/cursor-provider-lazy.js +23 -11
- package/dist/cursor-provider-live-run-drain.js +15 -12
- package/dist/cursor-provider-run-finalizer.js +19 -8
- package/dist/cursor-provider-turn-coordinator.js +1 -0
- package/dist/cursor-provider-turn-display-router.js +4 -0
- package/dist/cursor-provider-turn-emit.js +1 -0
- package/dist/cursor-provider-turn-finalize.js +29 -20
- package/dist/cursor-provider-turn-prepare.js +24 -11
- package/dist/cursor-provider-turn-runner.js +37 -11
- package/dist/cursor-provider-turn-send.js +22 -9
- package/dist/cursor-provider-turn-summary.js +89 -0
- package/dist/cursor-provider.js +9 -3
- package/dist/cursor-request-provenance.js +42 -0
- package/dist/cursor-ripgrep-path.js +14 -3
- package/dist/cursor-runtime-state.js +50 -43
- package/dist/cursor-sdk-billed-usage.js +54 -75
- package/dist/cursor-sdk-event-debug-session.js +5 -6
- package/dist/cursor-sdk-event-debug.js +9 -6
- package/dist/cursor-sdk-process-error-guard.js +8 -7
- package/dist/cursor-sdk-runtime.js +13 -0
- package/dist/cursor-session-agent-cleanup.js +2 -2
- package/dist/cursor-session-agent-lifecycle.js +18 -9
- package/dist/cursor-session-agent-lineage.js +28 -12
- package/dist/cursor-session-agent-resume.js +51 -35
- package/dist/cursor-session-agent.js +9 -9
- package/dist/cursor-session-compaction-prep.js +2 -7
- package/dist/cursor-session-scope.js +71 -39
- package/dist/cursor-session-settings.js +61 -0
- package/dist/cursor-skill-tool.js +32 -49
- package/dist/cursor-state.js +72 -62
- package/dist/cursor-usage-accounting.js +13 -70
- package/dist/cursor-usage-command.js +89 -0
- package/dist/cursor-usage-ledger.js +512 -0
- package/dist/index.js +11 -19
- package/dist/model-discovery.js +11 -19
- package/docs/cursor-dogfood-checklist.md +3 -3
- package/docs/cursor-live-smoke-checklist.md +15 -15
- package/docs/cursor-model-ux-spec.md +59 -18
- package/docs/cursor-native-tool-replay.md +5 -3
- package/docs/cursor-native-tool-visual-audit.md +8 -8
- package/docs/cursor-testing-lessons.md +47 -23
- package/docs/cursor-tool-surfaces.md +1 -1
- package/docs/platform-smoke-implementation.md +10 -6
- package/docs/platform-smoke.md +35 -46
- package/package.json +6 -6
- package/scripts/cloud-runtime-smoke.mjs +4 -2
- package/scripts/local-resume-smoke.mjs +1 -1
- package/scripts/platform-smoke/card-detect.mjs +2 -8
- package/scripts/platform-smoke/targets.mjs +8 -15
- package/scripts/platform-smoke/visual-evidence.mjs +7 -2
- package/scripts/tmux-live-smoke.sh +57 -90
- package/src/context-window-cache.ts +0 -6
- package/src/context.ts +8 -0
- package/src/cursor-cloud-lifecycle.ts +124 -98
- package/src/cursor-durable-fs.ts +65 -3
- package/src/cursor-fallback-models.generated.ts +288 -1
- package/src/cursor-fallback-warning.ts +2 -2
- package/src/cursor-http1.ts +8 -8
- package/src/cursor-live-run-coordinator.ts +12 -13
- package/src/cursor-native-replay-routing.ts +2 -1
- package/src/cursor-native-tool-display-registration.ts +48 -24
- package/src/cursor-native-tool-display-state.ts +65 -21
- package/src/cursor-native-tool-display-tools.ts +7 -4
- package/src/cursor-pi-tool-bridge-constants.ts +27 -2
- package/src/cursor-pi-tool-bridge-run.ts +11 -3
- package/src/cursor-pi-tool-bridge-snapshot.ts +1 -1
- package/src/cursor-pi-tool-bridge.ts +26 -9
- package/src/cursor-provider-binding.ts +62 -0
- package/src/cursor-provider-errors.ts +27 -7
- package/src/cursor-provider-lazy.ts +21 -15
- package/src/cursor-provider-live-run-drain.ts +20 -13
- package/src/cursor-provider-run-finalizer.ts +24 -15
- package/src/cursor-provider-turn-coordinator.ts +2 -0
- package/src/cursor-provider-turn-display-router.ts +5 -0
- package/src/cursor-provider-turn-emit.ts +3 -1
- package/src/cursor-provider-turn-finalize.ts +38 -23
- package/src/cursor-provider-turn-prepare.ts +23 -14
- package/src/cursor-provider-turn-runner.ts +40 -13
- package/src/cursor-provider-turn-send.ts +23 -15
- package/src/cursor-provider-turn-summary.ts +94 -0
- package/src/cursor-provider-turn-types.ts +37 -4
- package/src/cursor-provider.ts +25 -3
- package/src/cursor-request-provenance.ts +64 -0
- package/src/cursor-ripgrep-path.ts +15 -5
- package/src/cursor-runtime-state.ts +53 -48
- package/src/cursor-sdk-billed-usage.ts +54 -102
- package/src/cursor-sdk-event-debug-session.ts +5 -5
- package/src/cursor-sdk-event-debug.ts +10 -6
- package/src/cursor-sdk-process-error-guard.ts +7 -7
- package/src/cursor-sdk-runtime.ts +14 -0
- package/src/cursor-session-agent-cleanup.ts +2 -2
- package/src/cursor-session-agent-lifecycle.ts +14 -9
- package/src/cursor-session-agent-lineage.ts +26 -12
- package/src/cursor-session-agent-resume.ts +49 -36
- package/src/cursor-session-agent.ts +13 -10
- package/src/cursor-session-compaction-prep.ts +2 -7
- package/src/cursor-session-scope.ts +87 -39
- package/src/cursor-session-settings.ts +72 -0
- package/src/cursor-skill-tool.ts +37 -61
- package/src/cursor-state.ts +74 -67
- package/src/cursor-usage-accounting.ts +17 -82
- package/src/cursor-usage-command.ts +76 -0
- package/src/cursor-usage-ledger.ts +500 -0
- package/src/index.ts +14 -23
- package/src/model-discovery.ts +14 -22
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { CursorLiveRunAbortError } from "./cursor-live-run-coordinator.js";
|
|
2
2
|
import { drainExistingCursorLiveRunBeforeSend } from "./cursor-provider-live-run-drain.js";
|
|
3
|
-
import { getCursorSessionCwd } from "./cursor-session-scope.js";
|
|
4
3
|
import { installCursorSdkProcessErrorGuard } from "./cursor-sdk-process-error-guard.js";
|
|
5
4
|
import type { CursorRuntime } from "./cursor-config.js";
|
|
6
5
|
import { CursorSdkEventDebugSink } from "./cursor-sdk-event-debug.js";
|
|
@@ -22,14 +21,15 @@ import type {
|
|
|
22
21
|
CursorProviderTurnSendResult,
|
|
23
22
|
LiveCursorProviderTurnRuntime,
|
|
24
23
|
LocalCursorProviderTurnPrepareResult,
|
|
24
|
+
StartedCursorProviderTurn,
|
|
25
25
|
} from "./cursor-provider-turn-types.js";
|
|
26
26
|
|
|
27
27
|
export type { CursorProviderTurnRunnerParams } from "./cursor-provider-turn-types.js";
|
|
28
28
|
|
|
29
|
-
type LocalLivePreparedTurn = LocalCursorProviderTurnPrepareResult & { runtime: LiveCursorProviderTurnRuntime };
|
|
29
|
+
type LocalLivePreparedTurn = StartedCursorProviderTurn & LocalCursorProviderTurnPrepareResult & { runtime: LiveCursorProviderTurnRuntime };
|
|
30
30
|
|
|
31
|
-
function requireLocalLivePreparedTurn(prepared:
|
|
32
|
-
if (prepared.runtimeTarget !== "local" || prepared.runtime.kind !== "live") {
|
|
31
|
+
function requireLocalLivePreparedTurn(prepared: StartedCursorProviderTurn): LocalLivePreparedTurn {
|
|
32
|
+
if (prepared.runtimeTarget !== "local" || prepared.execution !== "conversation" || prepared.runtime.kind !== "live") {
|
|
33
33
|
throw new Error("Cursor live run requires a local live prepared turn");
|
|
34
34
|
}
|
|
35
35
|
return prepared as LocalLivePreparedTurn;
|
|
@@ -53,6 +53,7 @@ export class CursorProviderTurnRunner {
|
|
|
53
53
|
async run(sdkProcessErrorGuard: ReturnType<typeof installCursorSdkProcessErrorGuard>): Promise<void> {
|
|
54
54
|
const { stream, partial, model, context, options, sdkEventDebugRef } = this.params;
|
|
55
55
|
let prepared: CursorProviderTurnPrepareResult | undefined;
|
|
56
|
+
let started: StartedCursorProviderTurn | undefined;
|
|
56
57
|
let sendResult: CursorProviderTurnSendResult | undefined;
|
|
57
58
|
let liveCompletion: CursorLiveRunCompletion | undefined;
|
|
58
59
|
const runFinalizer = new CursorRunFinalizer({
|
|
@@ -65,21 +66,23 @@ export class CursorProviderTurnRunner {
|
|
|
65
66
|
|
|
66
67
|
try {
|
|
67
68
|
this.throwIfAborted();
|
|
68
|
-
const
|
|
69
|
+
const { scope } = this.params;
|
|
70
|
+
const cwd = scope.cwd;
|
|
69
71
|
this.sdkEventDebug = CursorSdkEventDebugSink.maybeCreate({
|
|
70
72
|
cwd,
|
|
71
73
|
modelId: model.id,
|
|
72
74
|
provider: model.provider,
|
|
75
|
+
scope,
|
|
73
76
|
});
|
|
74
77
|
sdkEventDebugRef.current = this.sdkEventDebug;
|
|
75
78
|
this.sdkEventDebug?.recordContextSnapshot(context);
|
|
76
79
|
// Resolved once here, before any drain await, so the drain decision and the
|
|
77
80
|
// prepare dispatch below always act on the same config snapshot.
|
|
78
|
-
const resolvedConfig = resolveCursorProviderTurnConfig(cwd);
|
|
81
|
+
const resolvedConfig = resolveCursorProviderTurnConfig(cwd, scope.projectTrusted, scope.scopeKey);
|
|
79
82
|
this.runtimeTarget = resolvedConfig.runtime.value;
|
|
80
|
-
if (resolvedConfig.runtime.value === "local") {
|
|
83
|
+
if (resolvedConfig.runtime.value === "local" && this.params.request.purpose === "normal") {
|
|
81
84
|
if (
|
|
82
|
-
(await drainExistingCursorLiveRunBeforeSend(stream, partial, model, context, options?.signal, this.sdkEventDebug)) ===
|
|
85
|
+
(await drainExistingCursorLiveRunBeforeSend(stream, partial, model, context, options?.signal, this.sdkEventDebug, scope.scopeKey, this.params.request.occupancyFloor)) ===
|
|
83
86
|
"stream_ended"
|
|
84
87
|
) {
|
|
85
88
|
return;
|
|
@@ -97,9 +100,33 @@ export class CursorProviderTurnRunner {
|
|
|
97
100
|
resolvedConfig,
|
|
98
101
|
});
|
|
99
102
|
|
|
103
|
+
const usage = await this.params.usageRecorder.start({
|
|
104
|
+
agent: prepared.agent,
|
|
105
|
+
runtime: prepared.runtimeTarget,
|
|
106
|
+
model: { id: model.id, provider: model.provider, cost: { ...model.cost } },
|
|
107
|
+
modelSelection: prepared.meta.modelSelection,
|
|
108
|
+
purpose: this.params.request.purpose,
|
|
109
|
+
...(prepared.runtimeTarget === "local" ? { storeIdentity: prepared.storeIdentity.stateRoot } : {}),
|
|
110
|
+
resumed: prepared.runtimeTarget === "local" && prepared.execution === "conversation" && prepared.sessionAgentLease.resumed === true,
|
|
111
|
+
newlyCreated: prepared.runtimeTarget !== "local" || prepared.execution === "summary" || (prepared.sessionAgentLease.created && !prepared.sessionAgentLease.resumed),
|
|
112
|
+
}).catch(error => {
|
|
113
|
+
this.params.usageRecorder.notePersistenceFailure(error);
|
|
114
|
+
throw error;
|
|
115
|
+
});
|
|
116
|
+
started = { ...prepared, usage };
|
|
117
|
+
if (started.runtime.liveRun) {
|
|
118
|
+
started.runtime.liveRun.onAbandon = async () => {
|
|
119
|
+
try {
|
|
120
|
+
await usage.recordTerminal({ status: "abandon" });
|
|
121
|
+
} catch (error) {
|
|
122
|
+
usage.notePersistenceFailure(error);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
100
127
|
sendResult = await sendCursorProviderTurn({
|
|
101
128
|
params: this.params,
|
|
102
|
-
prepared,
|
|
129
|
+
prepared: started,
|
|
103
130
|
sdkEventDebug: this.sdkEventDebug,
|
|
104
131
|
sdkProcessErrorGuard,
|
|
105
132
|
throwIfAborted: () => this.throwIfAborted(),
|
|
@@ -108,7 +135,7 @@ export class CursorProviderTurnRunner {
|
|
|
108
135
|
const { send } = sendResult;
|
|
109
136
|
|
|
110
137
|
if (prepared.runtime.kind === "live") {
|
|
111
|
-
const livePrepared = requireLocalLivePreparedTurn(
|
|
138
|
+
const livePrepared = requireLocalLivePreparedTurn(started);
|
|
112
139
|
liveCompletion = runFinalizer.startLiveRunCompletion({
|
|
113
140
|
send,
|
|
114
141
|
prepared: livePrepared,
|
|
@@ -126,7 +153,7 @@ export class CursorProviderTurnRunner {
|
|
|
126
153
|
|
|
127
154
|
const outcomePromise = awaitFinalizeCursorRunOutcome({
|
|
128
155
|
run: send.run,
|
|
129
|
-
prepared,
|
|
156
|
+
prepared: started,
|
|
130
157
|
cursorAgentMessageOffset: send.cursorAgentMessageOffset,
|
|
131
158
|
modelId: model.id,
|
|
132
159
|
signal: options?.signal,
|
|
@@ -141,12 +168,12 @@ export class CursorProviderTurnRunner {
|
|
|
141
168
|
const finalized = await outcomePromise;
|
|
142
169
|
await runFinalizer.applyTerminalEvent({
|
|
143
170
|
kind: "direct",
|
|
144
|
-
prepared,
|
|
171
|
+
prepared: started,
|
|
145
172
|
outcome: finalized.outcome,
|
|
146
173
|
displayOnlyTraceBlock: finalized.displayOnlyTraceBlock,
|
|
147
174
|
});
|
|
148
175
|
} catch (error) {
|
|
149
|
-
await runFinalizer.applyTerminalEvent({ kind: "error", prepared, error });
|
|
176
|
+
await runFinalizer.applyTerminalEvent({ kind: "error", prepared: started ?? prepared, error });
|
|
150
177
|
} finally {
|
|
151
178
|
await runFinalizer.cleanup(prepared, sendResult, liveCompletion);
|
|
152
179
|
}
|
|
@@ -1,24 +1,20 @@
|
|
|
1
1
|
import type { SendOptions } from "@cursor/sdk";
|
|
2
2
|
import { countCursorAgentMessages } from "./cursor-agent-message-web-tools.js";
|
|
3
|
-
import {
|
|
4
|
-
createCursorCloudLifecyclePersistenceError,
|
|
5
|
-
recordCursorCloudLifecycleSafely,
|
|
6
|
-
} from "./cursor-cloud-lifecycle.js";
|
|
7
|
-
import { CursorLiveRunAbortError } from "./cursor-live-run-coordinator.js";
|
|
3
|
+
import { createCursorCloudLifecyclePersistenceError } from "./cursor-cloud-lifecycle.js";
|
|
8
4
|
import { cursorLiveRuns } from "./cursor-provider-live-run-drain.js";
|
|
9
5
|
import { consumeCursorLocalForceOverride } from "./cursor-runtime-state.js";
|
|
10
6
|
import { recordCursorSessionAgentLineage } from "./cursor-session-agent-lineage.js";
|
|
11
7
|
import type { installCursorSdkProcessErrorGuard } from "./cursor-sdk-process-error-guard.js";
|
|
12
8
|
import type {
|
|
13
9
|
CursorProviderTurnRunnerParams,
|
|
14
|
-
|
|
10
|
+
StartedCursorProviderTurn,
|
|
15
11
|
CursorProviderTurnSendResult,
|
|
16
12
|
} from "./cursor-provider-turn-types.js";
|
|
17
13
|
import type { CursorSdkEventDebugSink } from "./cursor-sdk-event-debug.js";
|
|
18
14
|
|
|
19
15
|
export interface SendCursorProviderTurnParams {
|
|
20
16
|
params: CursorProviderTurnRunnerParams;
|
|
21
|
-
prepared:
|
|
17
|
+
prepared: StartedCursorProviderTurn;
|
|
22
18
|
sdkEventDebug: CursorSdkEventDebugSink | undefined;
|
|
23
19
|
sdkProcessErrorGuard: ReturnType<typeof installCursorSdkProcessErrorGuard>;
|
|
24
20
|
throwIfAborted: () => void;
|
|
@@ -27,7 +23,7 @@ export interface SendCursorProviderTurnParams {
|
|
|
27
23
|
|
|
28
24
|
const CLOUD_LEDGER_CANCEL_TIMEOUT_MS = 5000;
|
|
29
25
|
|
|
30
|
-
async function requestBoundedCloudRunCancellation(run: Awaited<ReturnType<
|
|
26
|
+
async function requestBoundedCloudRunCancellation(run: Awaited<ReturnType<StartedCursorProviderTurn["agent"]["send"]>>): Promise<boolean> {
|
|
31
27
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
32
28
|
const timeout = new Promise<false>((resolve) => {
|
|
33
29
|
timer = setTimeout(() => resolve(false), CLOUD_LEDGER_CANCEL_TIMEOUT_MS);
|
|
@@ -75,9 +71,9 @@ export async function sendCursorProviderTurn(sendParams: SendCursorProviderTurnP
|
|
|
75
71
|
abortRegistration?.signal.addEventListener("abort", abortListener, { once: true });
|
|
76
72
|
throwIfAborted();
|
|
77
73
|
let cursorAgentMessageOffset: number | undefined;
|
|
78
|
-
if (prepared.runtimeTarget === "local") {
|
|
74
|
+
if (prepared.runtimeTarget === "local" && prepared.execution === "conversation") {
|
|
79
75
|
try {
|
|
80
|
-
cursorAgentMessageOffset = await countCursorAgentMessages(agent.agentId, cwd, prepared.
|
|
76
|
+
cursorAgentMessageOffset = await countCursorAgentMessages(agent.agentId, cwd, prepared.store);
|
|
81
77
|
} catch (error) {
|
|
82
78
|
recordDebug(() => sdkEventDebug?.recordError("cursor_agent_message_count", error));
|
|
83
79
|
}
|
|
@@ -102,6 +98,13 @@ export async function sendCursorProviderTurn(sendParams: SendCursorProviderTurnP
|
|
|
102
98
|
mode: meta.agentMode,
|
|
103
99
|
model: meta.modelSelection,
|
|
104
100
|
onDelta: (args) => {
|
|
101
|
+
if (args.update.type === "turn-ended") {
|
|
102
|
+
try {
|
|
103
|
+
prepared.usage.observeRawTurn(args.update.usage);
|
|
104
|
+
} catch (error) {
|
|
105
|
+
params.usageRecorder.notePersistenceFailure(error);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
105
108
|
recordDebug(() => sdkEventDebug?.recordOnDelta(args.update));
|
|
106
109
|
turnCoordinator.handleDelta(args.update);
|
|
107
110
|
},
|
|
@@ -111,17 +114,23 @@ export async function sendCursorProviderTurn(sendParams: SendCursorProviderTurnP
|
|
|
111
114
|
},
|
|
112
115
|
};
|
|
113
116
|
throwIfAborted();
|
|
114
|
-
if (prepared.runtimeTarget === "local" && consumeCursorLocalForceOverride(prepared.localForce)) {
|
|
117
|
+
if (prepared.runtimeTarget === "local" && prepared.execution === "conversation" && consumeCursorLocalForceOverride(prepared.localForce, params.scope.scopeKey)) {
|
|
115
118
|
sendOptions.local = { force: true };
|
|
116
119
|
}
|
|
117
120
|
const runPromise = agent.send(payload, sendOptions);
|
|
118
121
|
// Record at send initiation (promise created), including later reject/cancel paths.
|
|
119
|
-
if (prepared.runtimeTarget === "local") {
|
|
120
|
-
recordCursorSessionAgentLineage(agent.agentId);
|
|
122
|
+
if (prepared.runtimeTarget === "local" && prepared.execution === "conversation") {
|
|
123
|
+
recordCursorSessionAgentLineage(agent.agentId, params.scope.scopeKey);
|
|
121
124
|
}
|
|
122
125
|
const run = await runPromise;
|
|
123
126
|
sdkRun = run;
|
|
124
|
-
|
|
127
|
+
runtime.sdkRun = run;
|
|
128
|
+
try {
|
|
129
|
+
await prepared.usage.recordRun({ runId: run.id, requestId: run.requestId });
|
|
130
|
+
} catch (error) {
|
|
131
|
+
params.usageRecorder.notePersistenceFailure(error);
|
|
132
|
+
}
|
|
133
|
+
if (prepared.runtimeTarget === "cloud" && !params.recordCloudLifecycle({ agentId: run.agentId, runId: run.id }, resolvedApiKey)) {
|
|
125
134
|
const cancellationConfirmed = await requestBoundedCloudRunCancellation(run);
|
|
126
135
|
throw createCursorCloudLifecyclePersistenceError(run.agentId, "run", cancellationConfirmed, resolvedApiKey);
|
|
127
136
|
}
|
|
@@ -143,7 +152,6 @@ export async function sendCursorProviderTurn(sendParams: SendCursorProviderTurnP
|
|
|
143
152
|
sdkProcessErrorGuard.suppressAbortErrors();
|
|
144
153
|
liveRun?.bridgeRun?.cancel("Cursor SDK run aborted");
|
|
145
154
|
await run.cancel().catch(() => {});
|
|
146
|
-
throw new CursorLiveRunAbortError();
|
|
147
155
|
}
|
|
148
156
|
|
|
149
157
|
completed = true;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { ModelSelection, SDKAgent } from "@cursor/sdk";
|
|
2
|
+
import { buildCursorSummaryPrompt, estimateCursorPromptTokens } from "./context.js";
|
|
3
|
+
import { configureCursorSdkHttp1 } from "./cursor-http1.js";
|
|
4
|
+
import { createCursorNativeReplayId } from "./cursor-provider-live-run-drain.js";
|
|
5
|
+
import type { PrepareCursorProviderTurnParams } from "./cursor-provider-turn-prepare.js";
|
|
6
|
+
import { CursorSdkTurnCoordinator } from "./cursor-provider-turn-coordinator.js";
|
|
7
|
+
import type { CursorProviderTurnLifecycle, SummaryCursorProviderTurnPrepareResult } from "./cursor-provider-turn-types.js";
|
|
8
|
+
import { ensureCursorRipgrepPath } from "./cursor-ripgrep-path.js";
|
|
9
|
+
import { loadCursorSdk } from "./cursor-sdk-runtime.js";
|
|
10
|
+
import { installCursorSdkOutputFilter, suppressCursorSdkOutput } from "./cursor-sdk-output-filter.js";
|
|
11
|
+
import { buildCursorLocalAgentOptions } from "./cursor-session-agent.js";
|
|
12
|
+
import { openCursorSessionStoreForScope, type OpenCursorSessionStore } from "./cursor-session-store.js";
|
|
13
|
+
import { getCursorPromptOptions } from "./cursor-usage-accounting.js";
|
|
14
|
+
|
|
15
|
+
function summaryLifecycle(agent: SDKAgent, sessionStore: OpenCursorSessionStore): CursorProviderTurnLifecycle {
|
|
16
|
+
let disposal: Promise<void> | undefined;
|
|
17
|
+
const dispose = () => disposal ??= (async () => {
|
|
18
|
+
try {
|
|
19
|
+
await agent[Symbol.asyncDispose]();
|
|
20
|
+
} finally {
|
|
21
|
+
await sessionStore.dispose();
|
|
22
|
+
}
|
|
23
|
+
})();
|
|
24
|
+
return { trackRunCompletion: () => {}, commitSend: () => {}, abandon: dispose, dispose };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** A native summary never enters the conversation pool or inherits its tool/mode/resume state. */
|
|
28
|
+
export async function prepareCursorSummaryProviderTurn(
|
|
29
|
+
prepareParams: PrepareCursorProviderTurnParams,
|
|
30
|
+
selection: ModelSelection,
|
|
31
|
+
): Promise<SummaryCursorProviderTurnPrepareResult> {
|
|
32
|
+
const { params, cwd, resolvedApiKey, resolvedConfig, sdkEventDebug, throwIfAborted } = prepareParams;
|
|
33
|
+
let sessionStore: OpenCursorSessionStore | undefined;
|
|
34
|
+
let agent: SDKAgent | undefined;
|
|
35
|
+
let restoreCursorSdkOutputFilter: (() => void) | undefined;
|
|
36
|
+
let completed = false;
|
|
37
|
+
try {
|
|
38
|
+
ensureCursorRipgrepPath();
|
|
39
|
+
const sdk = await loadCursorSdk();
|
|
40
|
+
configureCursorSdkHttp1(sdk, resolvedConfig.local.useHttp1ForAgent);
|
|
41
|
+
restoreCursorSdkOutputFilter = installCursorSdkOutputFilter();
|
|
42
|
+
sessionStore = (await openCursorSessionStoreForScope({
|
|
43
|
+
cwd, scopeKey: params.scope.scopeKey, persistent: false, hasResumeHandle: false,
|
|
44
|
+
})).sessionStore;
|
|
45
|
+
throwIfAborted();
|
|
46
|
+
agent = await suppressCursorSdkOutput(() => sdk.Agent.create({
|
|
47
|
+
apiKey: resolvedApiKey,
|
|
48
|
+
model: selection,
|
|
49
|
+
mode: "agent",
|
|
50
|
+
tools: [],
|
|
51
|
+
local: buildCursorLocalAgentOptions({ cwd, store: sessionStore!.store, settingSources: [] }),
|
|
52
|
+
}));
|
|
53
|
+
throwIfAborted();
|
|
54
|
+
const prompt = buildCursorSummaryPrompt(params.context);
|
|
55
|
+
const promptInputTokens = estimateCursorPromptTokens(prompt, getCursorPromptOptions(params.model));
|
|
56
|
+
const nativeReplayId = createCursorNativeReplayId();
|
|
57
|
+
const textDeltas: string[] = [];
|
|
58
|
+
const turnCoordinator = new CursorSdkTurnCoordinator({
|
|
59
|
+
stream: params.stream, partial: params.partial, cwd, resolvedApiKey,
|
|
60
|
+
useNativeToolReplay: false, nativeReplayId, textDeltas, debugRecorder: sdkEventDebug,
|
|
61
|
+
});
|
|
62
|
+
sdkEventDebug?.recordProviderMeta({
|
|
63
|
+
purpose: params.request.purpose,
|
|
64
|
+
runtime: "local",
|
|
65
|
+
summaryAgentId: agent.agentId,
|
|
66
|
+
model: { id: params.model.id, provider: params.model.provider, selection },
|
|
67
|
+
agentMode: "agent", settingSources: [], tools: [], resumedAgent: false,
|
|
68
|
+
});
|
|
69
|
+
completed = true;
|
|
70
|
+
return {
|
|
71
|
+
runtimeTarget: "local", execution: "summary", agent, cwd,
|
|
72
|
+
store: sessionStore.store, storeIdentity: sessionStore.identity,
|
|
73
|
+
payload: { text: prompt.text },
|
|
74
|
+
meta: {
|
|
75
|
+
sendPlan: { mode: "bootstrap", resetAgent: false, reason: "initial" },
|
|
76
|
+
prompt, bootstrap: true, promptInputTokens,
|
|
77
|
+
useNativeToolReplay: false, bridgeEnabled: false, nativeReplayId,
|
|
78
|
+
agentMode: "agent", modelSelection: selection,
|
|
79
|
+
},
|
|
80
|
+
contextWindowAgentId: agent.agentId, textDeltas, restoreCursorSdkOutputFilter,
|
|
81
|
+
lifecycle: summaryLifecycle(agent, sessionStore),
|
|
82
|
+
runtime: { kind: "direct", turnCoordinator },
|
|
83
|
+
};
|
|
84
|
+
} finally {
|
|
85
|
+
if (!completed) {
|
|
86
|
+
try {
|
|
87
|
+
await agent?.[Symbol.asyncDispose]().catch(() => {});
|
|
88
|
+
} finally {
|
|
89
|
+
await sessionStore?.dispose().catch(() => {});
|
|
90
|
+
restoreCursorSdkOutputFilter?.();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -6,7 +6,7 @@ import type {
|
|
|
6
6
|
Model,
|
|
7
7
|
SimpleStreamOptions,
|
|
8
8
|
} from "@earendil-works/pi-ai";
|
|
9
|
-
import type { AgentModeOption, ModelSelection, SDKAgent, SDKImage } from "@cursor/sdk";
|
|
9
|
+
import type { AgentModeOption, LocalAgentStore, ModelSelection, SDKAgent, SDKImage } from "@cursor/sdk";
|
|
10
10
|
import type { CursorLiveRun } from "./cursor-live-run-coordinator.js";
|
|
11
11
|
import type { SessionCursorAgentLease } from "./cursor-session-agent.js";
|
|
12
12
|
import type { planCursorSessionSend } from "./cursor-session-agent.js";
|
|
@@ -14,9 +14,22 @@ import type { CursorSdkEventDebugSink } from "./cursor-sdk-event-debug.js";
|
|
|
14
14
|
import type { CursorSdkTurnCoordinator } from "./cursor-provider-turn-coordinator.js";
|
|
15
15
|
import type { CursorPrompt } from "./context.js";
|
|
16
16
|
import type { CursorResolvedSetting } from "./cursor-config.js";
|
|
17
|
-
import type {
|
|
17
|
+
import type { CursorRequestProvenance } from "./cursor-request-provenance.js";
|
|
18
|
+
import type { CursorUsageRecorder, CursorUsageTurnRecorder } from "./cursor-usage-ledger.js";
|
|
19
|
+
import type { CursorSessionStoreIdentity } from "./cursor-session-store.js";
|
|
20
|
+
|
|
21
|
+
import type { CursorPiToolBridge } from "./cursor-pi-tool-bridge.js";
|
|
22
|
+
import type { CursorCloudLifecycleRecorder } from "./cursor-cloud-lifecycle.js";
|
|
23
|
+
import type { CursorNativeToolDisplayState } from "./cursor-native-tool-display-state.js";
|
|
24
|
+
import type { CursorTurnScope } from "./cursor-session-scope.js";
|
|
18
25
|
|
|
19
26
|
export interface CursorProviderTurnRunnerParams {
|
|
27
|
+
scope: CursorTurnScope;
|
|
28
|
+
bridge?: CursorPiToolBridge;
|
|
29
|
+
nativeDisplay?: CursorNativeToolDisplayState;
|
|
30
|
+
recordCloudLifecycle: CursorCloudLifecycleRecorder;
|
|
31
|
+
request: CursorRequestProvenance;
|
|
32
|
+
usageRecorder: CursorUsageRecorder;
|
|
20
33
|
model: Model<Api>;
|
|
21
34
|
context: Context;
|
|
22
35
|
stream: AssistantMessageEventStream;
|
|
@@ -45,7 +58,8 @@ export interface CursorProviderTurnSendMeta {
|
|
|
45
58
|
|
|
46
59
|
interface CursorProviderTurnRuntimeBase {
|
|
47
60
|
turnCoordinator: CursorSdkTurnCoordinator;
|
|
48
|
-
|
|
61
|
+
sdkRun?: Awaited<ReturnType<SDKAgent["send"]>>;
|
|
62
|
+
usageTerminalRecorded?: boolean;
|
|
49
63
|
}
|
|
50
64
|
|
|
51
65
|
/**
|
|
@@ -85,15 +99,30 @@ interface CursorProviderTurnPrepareResultBase {
|
|
|
85
99
|
lifecycle: CursorProviderTurnLifecycle;
|
|
86
100
|
}
|
|
87
101
|
|
|
88
|
-
|
|
102
|
+
interface LocalCursorProviderTurnPrepareResultBase extends CursorProviderTurnPrepareResultBase {
|
|
89
103
|
runtimeTarget: "local";
|
|
104
|
+
store: LocalAgentStore;
|
|
105
|
+
storeIdentity: CursorSessionStoreIdentity;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface LocalCursorProviderTurnPrepareResult extends LocalCursorProviderTurnPrepareResultBase {
|
|
109
|
+
execution: "conversation";
|
|
90
110
|
runtime: CursorProviderTurnRuntime;
|
|
91
111
|
sessionAgentScopeKey: string;
|
|
92
112
|
sessionAgentLease: SessionCursorAgentLease;
|
|
93
113
|
localForce: CursorResolvedSetting<boolean>;
|
|
94
114
|
}
|
|
95
115
|
|
|
116
|
+
export interface SummaryCursorProviderTurnPrepareResult extends LocalCursorProviderTurnPrepareResultBase {
|
|
117
|
+
execution: "summary";
|
|
118
|
+
runtime: DirectCursorProviderTurnRuntime;
|
|
119
|
+
sessionAgentScopeKey?: undefined;
|
|
120
|
+
sessionAgentLease?: undefined;
|
|
121
|
+
}
|
|
122
|
+
|
|
96
123
|
export interface CloudCursorProviderTurnPrepareResult extends CursorProviderTurnPrepareResultBase {
|
|
124
|
+
execution: "conversation";
|
|
125
|
+
recordCloudLifecycle: CursorCloudLifecycleRecorder;
|
|
97
126
|
runtimeTarget: "cloud";
|
|
98
127
|
runtime: DirectCursorProviderTurnRuntime;
|
|
99
128
|
sessionAgentScopeKey?: undefined;
|
|
@@ -108,8 +137,12 @@ export interface CloudCursorProviderTurnPrepareResult extends CursorProviderTurn
|
|
|
108
137
|
*/
|
|
109
138
|
export type CursorProviderTurnPrepareResult =
|
|
110
139
|
| LocalCursorProviderTurnPrepareResult
|
|
140
|
+
| SummaryCursorProviderTurnPrepareResult
|
|
111
141
|
| CloudCursorProviderTurnPrepareResult;
|
|
112
142
|
|
|
143
|
+
/** Accounting has claimed the origin before this turn can execute a send. */
|
|
144
|
+
export type StartedCursorProviderTurn = CursorProviderTurnPrepareResult & { usage: CursorUsageTurnRecorder };
|
|
145
|
+
|
|
113
146
|
export interface CursorProviderTurnSend {
|
|
114
147
|
run: Awaited<ReturnType<SDKAgent["send"]>>;
|
|
115
148
|
cursorAgentMessageOffset: number | undefined;
|
package/src/cursor-provider.ts
CHANGED
|
@@ -22,7 +22,12 @@ import { installCursorSdkProcessErrorGuard } from "./cursor-sdk-process-error-gu
|
|
|
22
22
|
import { sanitizeCursorProviderError } from "./cursor-provider-errors.js";
|
|
23
23
|
import { resolveCursorApiKey } from "./cursor-api-key.js";
|
|
24
24
|
import { CursorProviderTurnRunner } from "./cursor-provider-turn-runner.js";
|
|
25
|
-
import {
|
|
25
|
+
import type { CursorTurnScope } from "./cursor-session-scope.js";
|
|
26
|
+
import type { CursorPiToolBridge } from "./cursor-pi-tool-bridge.js";
|
|
27
|
+
import type { CursorNativeToolDisplayState } from "./cursor-native-tool-display-state.js";
|
|
28
|
+
import type { CursorCloudLifecycleRecorder } from "./cursor-cloud-lifecycle.js";
|
|
29
|
+
import type { CursorRequestProvenance } from "./cursor-request-provenance.js";
|
|
30
|
+
import type { CursorUsageRecorder } from "./cursor-usage-ledger.js";
|
|
26
31
|
import { runExclusiveCursorSessionTurn, __testUtils as cursorSessionTurnQueueTestUtils } from "./cursor-session-turn-queue.js";
|
|
27
32
|
|
|
28
33
|
function makeInitialMessage(model: Model<Api>): AssistantMessage {
|
|
@@ -45,11 +50,22 @@ function makeInitialMessage(model: Model<Api>): AssistantMessage {
|
|
|
45
50
|
};
|
|
46
51
|
}
|
|
47
52
|
|
|
53
|
+
export interface CursorProviderOwnership {
|
|
54
|
+
scope: CursorTurnScope;
|
|
55
|
+
bridge?: CursorPiToolBridge;
|
|
56
|
+
nativeDisplay?: CursorNativeToolDisplayState;
|
|
57
|
+
recordCloudLifecycle: CursorCloudLifecycleRecorder;
|
|
58
|
+
request: CursorRequestProvenance;
|
|
59
|
+
usageRecorder: CursorUsageRecorder;
|
|
60
|
+
}
|
|
61
|
+
|
|
48
62
|
export function streamCursor(
|
|
49
63
|
model: Model<Api>,
|
|
50
64
|
context: Context,
|
|
51
|
-
options
|
|
65
|
+
options: SimpleStreamOptions | undefined,
|
|
66
|
+
ownership: CursorProviderOwnership,
|
|
52
67
|
): AssistantMessageEventStream {
|
|
68
|
+
const { scope, bridge, nativeDisplay, recordCloudLifecycle, request, usageRecorder } = ownership;
|
|
53
69
|
const stream = createAssistantMessageEventStream();
|
|
54
70
|
const sdkEventDebugRef: { current?: CursorSdkEventDebugSink } = {};
|
|
55
71
|
attachCursorSdkEventDebugPiStreamTap(stream, sdkEventDebugRef);
|
|
@@ -59,6 +75,12 @@ export function streamCursor(
|
|
|
59
75
|
|
|
60
76
|
const runner = new CursorProviderTurnRunner({
|
|
61
77
|
model,
|
|
78
|
+
scope,
|
|
79
|
+
bridge,
|
|
80
|
+
nativeDisplay,
|
|
81
|
+
recordCloudLifecycle,
|
|
82
|
+
request,
|
|
83
|
+
usageRecorder,
|
|
62
84
|
context,
|
|
63
85
|
stream,
|
|
64
86
|
partial,
|
|
@@ -69,7 +91,7 @@ export function streamCursor(
|
|
|
69
91
|
try {
|
|
70
92
|
stream.push({ type: "start", partial });
|
|
71
93
|
await runExclusiveCursorSessionTurn(
|
|
72
|
-
|
|
94
|
+
scope.scopeKey,
|
|
73
95
|
() => runner.run(installCursorSdkProcessErrorGuard()),
|
|
74
96
|
options?.signal,
|
|
75
97
|
);
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { convertToLlm, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import type { Api, Context, Model } from "@earendil-works/pi-ai";
|
|
4
|
+
|
|
5
|
+
export interface CursorRequestProvenance {
|
|
6
|
+
readonly purpose: "normal" | "compaction" | "tree";
|
|
7
|
+
readonly occupancyFloor?: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface Measurement {
|
|
11
|
+
readonly api: string;
|
|
12
|
+
readonly provider: string;
|
|
13
|
+
readonly model: string;
|
|
14
|
+
readonly tokens: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Receipt data contains hashes and scalars only, never conversation text. */
|
|
18
|
+
export interface CursorRequestProjectionSnapshot {
|
|
19
|
+
readonly digest: string;
|
|
20
|
+
readonly measurements: readonly Measurement[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function projectionDigest(messages: readonly unknown[]): string {
|
|
24
|
+
const hash = createHash("sha256");
|
|
25
|
+
for (const message of messages) {
|
|
26
|
+
// Stable value equivalence survives Pi's cloning and property insertion order.
|
|
27
|
+
hash.update(JSON.stringify(message, (_key, value: unknown) => {
|
|
28
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return value;
|
|
29
|
+
return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)));
|
|
30
|
+
}));
|
|
31
|
+
hash.update("\n");
|
|
32
|
+
}
|
|
33
|
+
return hash.digest("hex");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function captureCursorRequestProjection(manager: ExtensionContext["sessionManager"]): CursorRequestProjectionSnapshot {
|
|
37
|
+
const branch = manager.getBranch();
|
|
38
|
+
const projection = manager.buildSessionProjection();
|
|
39
|
+
const order = new Map(branch.map((entry, index) => [entry.id, index]));
|
|
40
|
+
const boundary = branch.findLastIndex((entry) => entry.type === "compaction" || entry.type === "context_edit");
|
|
41
|
+
const measurements: Measurement[] = [];
|
|
42
|
+
// Raw branch chronology, not projected order or clocks, determines freshness.
|
|
43
|
+
for (const entry of [...projection.entries].sort((a, b) => (order.get(a.sourceEntry.id) ?? -1) - (order.get(b.sourceEntry.id) ?? -1))) {
|
|
44
|
+
if ((order.get(entry.sourceEntry.id) ?? -1) <= boundary) continue;
|
|
45
|
+
for (const message of entry.messages) {
|
|
46
|
+
if (message.role !== "assistant" || message.stopReason === "error" || message.stopReason === "aborted") continue;
|
|
47
|
+
const tokens = message.usage.totalTokens;
|
|
48
|
+
if (!Number.isFinite(tokens) || tokens <= 0) continue;
|
|
49
|
+
measurements.push(Object.freeze({ api: message.api, provider: message.provider, model: message.model, tokens }));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return Object.freeze({ digest: projectionDigest(convertToLlm(projection.messages)), measurements: Object.freeze(measurements) });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function resolveCursorRequestProvenance(
|
|
56
|
+
snapshot: CursorRequestProjectionSnapshot,
|
|
57
|
+
model: Model<Api>,
|
|
58
|
+
context: Context,
|
|
59
|
+
purpose: CursorRequestProvenance["purpose"],
|
|
60
|
+
): CursorRequestProvenance {
|
|
61
|
+
if (purpose !== "normal" || snapshot.digest !== projectionDigest(context.messages)) return Object.freeze({ purpose });
|
|
62
|
+
const measurement = snapshot.measurements.findLast((item) => item.api === model.api && item.provider === model.provider && item.model === model.id && item.tokens <= model.contextWindow);
|
|
63
|
+
return Object.freeze({ purpose, ...(measurement ? { occupancyFloor: measurement.tokens } : {}) });
|
|
64
|
+
}
|
|
@@ -4,16 +4,26 @@ import { dirname, isAbsolute, join } from "node:path";
|
|
|
4
4
|
|
|
5
5
|
const RIPGREP_ENV = "CURSOR_RIPGREP_PATH";
|
|
6
6
|
|
|
7
|
-
export function
|
|
7
|
+
export function resolveCursorSdkPlatformPackageDirectory(
|
|
8
8
|
fromModuleUrl: string | URL = import.meta.url,
|
|
9
9
|
): string | undefined {
|
|
10
10
|
try {
|
|
11
11
|
const require = createRequire(fromModuleUrl);
|
|
12
|
-
const platformPackage = `@cursor/sdk-${process.platform}-${process.arch}`;
|
|
13
12
|
const sdkEntry = require.resolve("@cursor/sdk");
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
);
|
|
13
|
+
return dirname(require.resolve(`@cursor/sdk-${process.platform}-${process.arch}/package.json`, {
|
|
14
|
+
paths: [dirname(sdkEntry)],
|
|
15
|
+
}));
|
|
16
|
+
} catch {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function resolveBundledCursorRipgrepPath(
|
|
22
|
+
fromModuleUrl: string | URL = import.meta.url,
|
|
23
|
+
): string | undefined {
|
|
24
|
+
const packageDirectory = resolveCursorSdkPlatformPackageDirectory(fromModuleUrl);
|
|
25
|
+
if (!packageDirectory) return undefined;
|
|
26
|
+
try {
|
|
17
27
|
const ripgrepPath = join(packageDirectory, "bin", process.platform === "win32" ? "rg.exe" : "rg");
|
|
18
28
|
accessSync(ripgrepPath, constants.X_OK);
|
|
19
29
|
return ripgrepPath;
|