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
package/src/cursor-skill-tool.ts
CHANGED
|
@@ -29,15 +29,13 @@ type CursorActivateSkillParams = {
|
|
|
29
29
|
};
|
|
30
30
|
|
|
31
31
|
interface CursorSkillActivationDetails {
|
|
32
|
-
name
|
|
33
|
-
filePath
|
|
34
|
-
baseDir
|
|
32
|
+
name: string;
|
|
33
|
+
filePath: string;
|
|
34
|
+
baseDir: string;
|
|
35
35
|
resources: string[];
|
|
36
36
|
availableSkillNames: string[];
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
let currentSkillsByName = new Map<string, Skill>();
|
|
40
|
-
|
|
41
39
|
function escapeXml(value: string): string {
|
|
42
40
|
return value
|
|
43
41
|
.replace(/&/g, "&")
|
|
@@ -51,14 +49,6 @@ function getVisibleSkills(skills: readonly Skill[] | undefined): Skill[] {
|
|
|
51
49
|
return (skills ?? []).filter((skill) => !skill.disableModelInvocation);
|
|
52
50
|
}
|
|
53
51
|
|
|
54
|
-
function setCurrentSkills(skills: readonly Skill[] | undefined): void {
|
|
55
|
-
currentSkillsByName = new Map(getVisibleSkills(skills).map((skill) => [skill.name, skill]));
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function getAvailableSkillNames(): string[] {
|
|
59
|
-
return [...currentSkillsByName.keys()].sort();
|
|
60
|
-
}
|
|
61
|
-
|
|
62
52
|
function resolveEffectiveRuntimeForSkillLifecycle(
|
|
63
53
|
cursorModel: boolean,
|
|
64
54
|
ctx: Pick<ExtensionContext, "cwd"> & Partial<Pick<ExtensionContext, "isProjectTrusted">>,
|
|
@@ -66,27 +56,6 @@ function resolveEffectiveRuntimeForSkillLifecycle(
|
|
|
66
56
|
return cursorModel ? resolveEffectiveCursorConfigForContext(ctx).runtime.value : "local";
|
|
67
57
|
}
|
|
68
58
|
|
|
69
|
-
function shouldExposeSkillTool(model: ExtensionContext["model"], runtime: CursorRuntime): boolean {
|
|
70
|
-
return runtime === "local" && isCursorModel(model) && resolveCursorPiToolBridgeEnabled() && currentSkillsByName.size > 0;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function syncCursorSkillToolForModel(
|
|
74
|
-
pi: Pick<ExtensionAPI, "getActiveTools" | "setActiveTools">,
|
|
75
|
-
model: ExtensionContext["model"],
|
|
76
|
-
runtime: CursorRuntime,
|
|
77
|
-
): void {
|
|
78
|
-
const activeToolNames = new Set(pi.getActiveTools());
|
|
79
|
-
const shouldBeActive = !arePiToolsDisabled(pi) && shouldExposeSkillTool(model, runtime);
|
|
80
|
-
const alreadyActive = activeToolNames.has(CURSOR_ACTIVATE_SKILL_TOOL_NAME);
|
|
81
|
-
if (shouldBeActive === alreadyActive) return;
|
|
82
|
-
if (shouldBeActive) {
|
|
83
|
-
activeToolNames.add(CURSOR_ACTIVATE_SKILL_TOOL_NAME);
|
|
84
|
-
} else {
|
|
85
|
-
activeToolNames.delete(CURSOR_ACTIVATE_SKILL_TOOL_NAME);
|
|
86
|
-
}
|
|
87
|
-
pi.setActiveTools([...activeToolNames]);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
59
|
export function formatCursorSkillsForPrompt(skills: readonly Skill[]): string {
|
|
91
60
|
const visibleSkills = getVisibleSkills(skills);
|
|
92
61
|
if (visibleSkills.length === 0) return "";
|
|
@@ -157,16 +126,6 @@ async function listSkillResourcePaths(baseDir: string): Promise<string[]> {
|
|
|
157
126
|
return resources;
|
|
158
127
|
}
|
|
159
128
|
|
|
160
|
-
function buildActivationDetails(skill: Skill | undefined, resources: string[] = []): CursorSkillActivationDetails {
|
|
161
|
-
return {
|
|
162
|
-
name: skill?.name,
|
|
163
|
-
filePath: skill?.filePath,
|
|
164
|
-
baseDir: skill ? dirname(skill.filePath) : undefined,
|
|
165
|
-
resources,
|
|
166
|
-
availableSkillNames: getAvailableSkillNames(),
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
|
|
170
129
|
function formatSkillResources(resources: readonly string[]): string {
|
|
171
130
|
if (resources.length === 0) return "<skill_resources />";
|
|
172
131
|
return [
|
|
@@ -190,18 +149,37 @@ function wrapSkillContent(skill: Skill, content: string, resources: readonly str
|
|
|
190
149
|
}
|
|
191
150
|
|
|
192
151
|
export function registerCursorSkillTool(pi: CursorSkillToolExtensionApi): void {
|
|
152
|
+
let currentSkillsByName = new Map<string, Skill>();
|
|
153
|
+
|
|
154
|
+
function setCurrentSkills(skills: readonly Skill[] | undefined): void {
|
|
155
|
+
currentSkillsByName = new Map(getVisibleSkills(skills).map((skill) => [skill.name, skill]));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function getAvailableSkillNames(): string[] {
|
|
159
|
+
return [...currentSkillsByName.keys()].sort();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function syncCursorSkillToolForModel(model: ExtensionContext["model"], runtime: CursorRuntime): void {
|
|
163
|
+
const activeToolNames = new Set(pi.getActiveTools());
|
|
164
|
+
const shouldBeActive = !arePiToolsDisabled(pi) && runtime === "local" && isCursorModel(model)
|
|
165
|
+
&& resolveCursorPiToolBridgeEnabled() && currentSkillsByName.size > 0;
|
|
166
|
+
const alreadyActive = activeToolNames.has(CURSOR_ACTIVATE_SKILL_TOOL_NAME);
|
|
167
|
+
if (shouldBeActive === alreadyActive) return;
|
|
168
|
+
if (shouldBeActive) {
|
|
169
|
+
activeToolNames.add(CURSOR_ACTIVATE_SKILL_TOOL_NAME);
|
|
170
|
+
} else {
|
|
171
|
+
activeToolNames.delete(CURSOR_ACTIVATE_SKILL_TOOL_NAME);
|
|
172
|
+
}
|
|
173
|
+
pi.setActiveTools([...activeToolNames]);
|
|
174
|
+
}
|
|
175
|
+
|
|
193
176
|
pi.registerTool({
|
|
194
177
|
name: CURSOR_ACTIVATE_SKILL_TOOL_NAME,
|
|
195
178
|
label: "Cursor skill",
|
|
196
179
|
description: "Load full pi Agent Skill instructions for Cursor. Use with a skill name from the current <available_skills> catalog before applying that skill.",
|
|
197
|
-
promptSnippet: "Load full pi Agent Skill instructions for a listed skill before Cursor applies that skill",
|
|
198
180
|
parameters: Type.Object({
|
|
199
181
|
name: Type.String({ description: "Skill name from the current <available_skills> catalog" }),
|
|
200
182
|
}),
|
|
201
|
-
promptGuidelines: [
|
|
202
|
-
`Use ${CURSOR_ACTIVATE_SKILL_TOOL_NAME} only for skill names listed in the current <available_skills> catalog.`,
|
|
203
|
-
"After loading a skill, follow its instructions and resolve relative skill paths against the returned skill directory.",
|
|
204
|
-
],
|
|
205
183
|
async execute(_toolCallId, params) {
|
|
206
184
|
const requestedName = (params as CursorActivateSkillParams).name?.trim();
|
|
207
185
|
if (!requestedName) {
|
|
@@ -221,7 +199,13 @@ export function registerCursorSkillTool(pi: CursorSkillToolExtensionApi): void {
|
|
|
221
199
|
]);
|
|
222
200
|
return {
|
|
223
201
|
content: [{ type: "text" as const, text: wrapSkillContent(skill, content, resources) }],
|
|
224
|
-
details:
|
|
202
|
+
details: {
|
|
203
|
+
name: skill.name,
|
|
204
|
+
filePath: skill.filePath,
|
|
205
|
+
baseDir: dirname(skill.filePath),
|
|
206
|
+
resources,
|
|
207
|
+
availableSkillNames: getAvailableSkillNames(),
|
|
208
|
+
} satisfies CursorSkillActivationDetails,
|
|
225
209
|
};
|
|
226
210
|
} catch (error) {
|
|
227
211
|
throw new Error(
|
|
@@ -233,7 +217,7 @@ export function registerCursorSkillTool(pi: CursorSkillToolExtensionApi): void {
|
|
|
233
217
|
|
|
234
218
|
const clearSkillsAndSync = (model: ExtensionContext["model"], runtime: CursorRuntime = "local"): void => {
|
|
235
219
|
setCurrentSkills([]);
|
|
236
|
-
syncCursorSkillToolForModel(
|
|
220
|
+
syncCursorSkillToolForModel(model, runtime);
|
|
237
221
|
};
|
|
238
222
|
|
|
239
223
|
registerCursorModelLifecycle(pi, {
|
|
@@ -247,7 +231,7 @@ export function registerCursorSkillTool(pi: CursorSkillToolExtensionApi): void {
|
|
|
247
231
|
const cursorModel = isCursorModel(ctx.model);
|
|
248
232
|
const runtime = resolveEffectiveRuntimeForSkillLifecycle(cursorModel, ctx);
|
|
249
233
|
if (!cursorModel || runtime === "cloud") setCurrentSkills([]);
|
|
250
|
-
syncCursorSkillToolForModel(
|
|
234
|
+
syncCursorSkillToolForModel(ctx.model, runtime);
|
|
251
235
|
},
|
|
252
236
|
beforeAgentStart: (event, ctx) => {
|
|
253
237
|
const cursorModel = isCursorModel(ctx.model);
|
|
@@ -257,18 +241,10 @@ export function registerCursorSkillTool(pi: CursorSkillToolExtensionApi): void {
|
|
|
257
241
|
} else {
|
|
258
242
|
setCurrentSkills([]);
|
|
259
243
|
}
|
|
260
|
-
syncCursorSkillToolForModel(
|
|
244
|
+
syncCursorSkillToolForModel(ctx.model, runtime);
|
|
261
245
|
const resolved = resolveCursorSkillSystemPrompt(event.systemPrompt, ctx.model, event.systemPromptOptions, runtime);
|
|
262
246
|
if (resolved === event.systemPrompt) return undefined;
|
|
263
247
|
return { systemPrompt: resolved };
|
|
264
248
|
},
|
|
265
249
|
});
|
|
266
250
|
}
|
|
267
|
-
|
|
268
|
-
export const __testUtils = {
|
|
269
|
-
AVAILABLE_SKILLS_SECTION_PATTERN,
|
|
270
|
-
buildActivationDetails,
|
|
271
|
-
setCurrentSkills,
|
|
272
|
-
listSkillResourcePaths,
|
|
273
|
-
wrapSkillContent,
|
|
274
|
-
};
|
package/src/cursor-state.ts
CHANGED
|
@@ -27,7 +27,8 @@ import {
|
|
|
27
27
|
import { isCursorModel } from "./cursor-model.js";
|
|
28
28
|
import { registerCursorModelLifecycle } from "./cursor-model-lifecycle.js";
|
|
29
29
|
import { asRecord } from "./cursor-record-utils.js";
|
|
30
|
-
import { getCursorSessionScopeKey } from "./cursor-session-scope.js";
|
|
30
|
+
import { getCursorSessionScopeSnapshot, getCursorSessionScopeKey } from "./cursor-session-scope.js";
|
|
31
|
+
import { cursorSettingsScopeForContext, getCursorSessionSettings, registerCursorSessionSettings, resetCursorSessionSettingsForTests } from "./cursor-session-settings.js";
|
|
31
32
|
import { refreshSessionCursorAgentConfig } from "./cursor-session-agent.js";
|
|
32
33
|
import { getCursorModelMetadata } from "./model-discovery.js";
|
|
33
34
|
import {
|
|
@@ -80,18 +81,8 @@ type CursorRuntimeControlsExtensionApi = Pick<
|
|
|
80
81
|
"appendEntry" | "getFlag" | "registerFlag" | "registerCommand" | "on" | "getActiveTools" | "getAllTools"
|
|
81
82
|
> & CursorRuntimeStateExtensionApi;
|
|
82
83
|
|
|
83
|
-
type CursorCliModeState =
|
|
84
|
-
| { kind: "unset" }
|
|
85
|
-
| { kind: "valid"; mode: AgentModeOption }
|
|
86
|
-
| { kind: "invalid"; raw: string; message: string };
|
|
87
|
-
|
|
88
|
-
const sessionFastPreferences = new Map<string, boolean>();
|
|
89
84
|
const authoritativeGlobalFastPreferenceIds = new Set<string>();
|
|
90
85
|
let globalFastPreferences = new Map<string, boolean>();
|
|
91
|
-
let cliForceFast = false;
|
|
92
|
-
let cliForceNoFast = false;
|
|
93
|
-
let sessionCursorAgentMode: AgentModeOption | undefined;
|
|
94
|
-
let cliCursorModeState: CursorCliModeState = { kind: "unset" };
|
|
95
86
|
const invalidCursorModeNotifiedSessionScopeKeys = new Set<string>();
|
|
96
87
|
|
|
97
88
|
export function isCursorAgentMode(value: unknown): value is AgentModeOption {
|
|
@@ -152,7 +143,8 @@ function saveGlobalCursorHttp1Enabled(enabled: boolean): void {
|
|
|
152
143
|
);
|
|
153
144
|
}
|
|
154
145
|
|
|
155
|
-
function restoreSessionFastPreferences(branch: readonly SessionEntry[]): void {
|
|
146
|
+
function restoreSessionFastPreferences(branch: readonly SessionEntry[], scopeKey?: string): void {
|
|
147
|
+
const sessionFastPreferences = getCursorSessionSettings(scopeKey).fast;
|
|
156
148
|
sessionFastPreferences.clear();
|
|
157
149
|
for (const entry of branch) {
|
|
158
150
|
if (entry.type !== "custom" || entry.customType !== FAST_ENTRY_TYPE) continue;
|
|
@@ -163,30 +155,32 @@ function restoreSessionFastPreferences(branch: readonly SessionEntry[]): void {
|
|
|
163
155
|
}
|
|
164
156
|
}
|
|
165
157
|
|
|
166
|
-
function restoreSessionCursorMode(branch: readonly SessionEntry[]): void {
|
|
167
|
-
|
|
158
|
+
function restoreSessionCursorMode(branch: readonly SessionEntry[], scopeKey?: string): void {
|
|
159
|
+
const state = getCursorSessionSettings(scopeKey);
|
|
160
|
+
state.mode = undefined;
|
|
168
161
|
for (const entry of branch) {
|
|
169
162
|
if (entry.type !== "custom" || entry.customType !== MODE_ENTRY_TYPE) continue;
|
|
170
163
|
if (isCursorModeEntryData(entry.data)) {
|
|
171
|
-
|
|
164
|
+
state.mode = entry.data.mode;
|
|
172
165
|
}
|
|
173
166
|
}
|
|
174
167
|
}
|
|
175
168
|
|
|
176
|
-
function restoreSessionCursorPreferences(ctx:
|
|
169
|
+
function restoreSessionCursorPreferences(ctx: Pick<ExtensionContext, "sessionManager">): void {
|
|
177
170
|
const branch = ctx.sessionManager.getBranch();
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
171
|
+
const scopeKey = cursorSettingsScopeForContext(ctx);
|
|
172
|
+
restoreSessionCursorRuntimeState(branch, scopeKey);
|
|
173
|
+
restoreSessionFastPreferences(branch, scopeKey);
|
|
174
|
+
restoreSessionCursorMode(branch, scopeKey);
|
|
175
|
+
restoreSessionCursorHttp1(branch, scopeKey);
|
|
182
176
|
}
|
|
183
177
|
|
|
184
|
-
function restoreSessionCursorHttp1(branch: readonly SessionEntry[]): void {
|
|
185
|
-
setStoredCursorHttp1Enabled(undefined);
|
|
178
|
+
function restoreSessionCursorHttp1(branch: readonly SessionEntry[], scopeKey?: string): void {
|
|
179
|
+
setStoredCursorHttp1Enabled(undefined, scopeKey);
|
|
186
180
|
for (const entry of branch) {
|
|
187
181
|
if (entry.type !== "custom" || entry.customType !== CURSOR_HTTP1_ENTRY_TYPE) continue;
|
|
188
182
|
if (isCursorHttp1EntryData(entry.data)) {
|
|
189
|
-
setStoredCursorHttp1Enabled(entry.data.enabled);
|
|
183
|
+
setStoredCursorHttp1Enabled(entry.data.enabled, scopeKey);
|
|
190
184
|
}
|
|
191
185
|
}
|
|
192
186
|
}
|
|
@@ -207,7 +201,8 @@ function getMapFastPreference(
|
|
|
207
201
|
return map.get(preferenceModelId) ?? (preferenceModelId !== metadata.baseModelId ? map.get(metadata.baseModelId) : undefined);
|
|
208
202
|
}
|
|
209
203
|
|
|
210
|
-
function getEffectiveFast(modelId: string): boolean | undefined {
|
|
204
|
+
function getEffectiveFast(modelId: string, scopeKey?: string): boolean | undefined {
|
|
205
|
+
const { forceNoFast: cliForceNoFast, forceFast: cliForceFast } = getCursorSessionSettings(scopeKey).cli;
|
|
211
206
|
const metadata = getCursorModelMetadata(modelId);
|
|
212
207
|
if (!metadata?.supportsFast) return undefined;
|
|
213
208
|
return resolveCursorFastDefault({
|
|
@@ -216,7 +211,7 @@ function getEffectiveFast(modelId: string): boolean | undefined {
|
|
|
216
211
|
aliasOverride: metadata.fastOverride,
|
|
217
212
|
sessionValue: authoritativeGlobalFastPreferenceIds.has(getFastPreferenceModelId(metadata))
|
|
218
213
|
? undefined
|
|
219
|
-
: getMapFastPreference(
|
|
214
|
+
: getMapFastPreference(getCursorSessionSettings(scopeKey).fast, metadata),
|
|
220
215
|
userValue: getMapFastPreference(globalFastPreferences, metadata),
|
|
221
216
|
modelDefault: metadata.defaultFast,
|
|
222
217
|
}).value;
|
|
@@ -230,28 +225,29 @@ export type CursorAgentModeResolution =
|
|
|
230
225
|
| { kind: "valid"; mode: AgentModeOption }
|
|
231
226
|
| { kind: "invalid"; raw: string; message: string };
|
|
232
227
|
|
|
233
|
-
export function getStoredCursorAgentMode(): AgentModeOption {
|
|
234
|
-
return
|
|
228
|
+
export function getStoredCursorAgentMode(scopeKey?: string): AgentModeOption {
|
|
229
|
+
return getCursorSessionSettings(scopeKey).mode ?? DEFAULT_CURSOR_AGENT_MODE;
|
|
235
230
|
}
|
|
236
231
|
|
|
237
|
-
export function resolveCursorAgentMode(): CursorAgentModeResolution {
|
|
232
|
+
export function resolveCursorAgentMode(scopeKey?: string): CursorAgentModeResolution {
|
|
233
|
+
const cliCursorModeState = getCursorSessionSettings(scopeKey).cli.mode;
|
|
238
234
|
switch (cliCursorModeState.kind) {
|
|
239
235
|
case "valid":
|
|
240
236
|
return { kind: "valid", mode: cliCursorModeState.mode };
|
|
241
237
|
case "invalid":
|
|
242
238
|
return { kind: "invalid", raw: cliCursorModeState.raw, message: cliCursorModeState.message };
|
|
243
239
|
case "unset":
|
|
244
|
-
return { kind: "valid", mode: getStoredCursorAgentMode() };
|
|
240
|
+
return { kind: "valid", mode: getStoredCursorAgentMode(scopeKey) };
|
|
245
241
|
}
|
|
246
242
|
}
|
|
247
243
|
|
|
248
|
-
export function getCursorProviderAgentModeOrThrow(): AgentModeOption {
|
|
249
|
-
const resolution = resolveCursorAgentMode();
|
|
244
|
+
export function getCursorProviderAgentModeOrThrow(scopeKey?: string): AgentModeOption {
|
|
245
|
+
const resolution = resolveCursorAgentMode(scopeKey);
|
|
250
246
|
if (resolution.kind === "invalid") throw new Error(resolution.message);
|
|
251
247
|
return resolution.mode;
|
|
252
248
|
}
|
|
253
249
|
|
|
254
|
-
type CursorStatusContext = Pick<ExtensionContext, "cwd"> & Partial<Pick<ExtensionContext, "isProjectTrusted">>;
|
|
250
|
+
type CursorStatusContext = Pick<ExtensionContext, "cwd"> & Partial<Pick<ExtensionContext, "isProjectTrusted" | "sessionManager">>;
|
|
255
251
|
|
|
256
252
|
function updateCursorStatus(ctx: CursorStatusContext & Pick<ExtensionContext, "model" | "ui">, model = ctx.model): void {
|
|
257
253
|
if (!model || !isCursorModel(model)) {
|
|
@@ -260,14 +256,14 @@ function updateCursorStatus(ctx: CursorStatusContext & Pick<ExtensionContext, "m
|
|
|
260
256
|
}
|
|
261
257
|
const metadata = getCursorModelMetadata(model.id);
|
|
262
258
|
const resolution = resolveCursorStatusRuntime(ctx);
|
|
263
|
-
const modeResolution = resolveCursorAgentMode();
|
|
259
|
+
const modeResolution = resolveCursorAgentMode(cursorSettingsScopeForContext(ctx));
|
|
264
260
|
const mode = modeResolution.kind === "invalid" ? "invalid" : modeResolution.mode;
|
|
265
261
|
if (resolution.kind === "invalid") {
|
|
266
262
|
ctx.ui.setStatus("cursor", formatCursorStatus("invalid", undefined, mode));
|
|
267
263
|
return;
|
|
268
264
|
}
|
|
269
265
|
const runtime = resolution.runtime.value;
|
|
270
|
-
const fast = runtime === "cloud" ? undefined : metadata?.supportsFast ? getEffectiveFast(model.id) : undefined;
|
|
266
|
+
const fast = runtime === "cloud" ? undefined : metadata?.supportsFast ? getEffectiveFast(model.id, cursorSettingsScopeForContext(ctx)) : undefined;
|
|
271
267
|
ctx.ui.setStatus(
|
|
272
268
|
"cursor",
|
|
273
269
|
formatCursorStatus(runtime, fast, mode, resolution.useHttp1ForAgent.value),
|
|
@@ -292,7 +288,9 @@ function persistFastPreference(
|
|
|
292
288
|
pi: Pick<ExtensionAPI, "appendEntry">,
|
|
293
289
|
modelId: string,
|
|
294
290
|
fast: boolean,
|
|
291
|
+
scopeKey: string | undefined,
|
|
295
292
|
): unknown | undefined {
|
|
293
|
+
const sessionFastPreferences = getCursorSessionSettings(scopeKey).fast;
|
|
296
294
|
const previousSession = sessionFastPreferences.get(modelId);
|
|
297
295
|
const previousGlobal = globalFastPreferences.get(modelId);
|
|
298
296
|
sessionFastPreferences.set(modelId, fast);
|
|
@@ -314,13 +312,14 @@ function persistFastPreference(
|
|
|
314
312
|
}
|
|
315
313
|
}
|
|
316
314
|
|
|
317
|
-
function persistCursorModePreference(pi: Pick<ExtensionAPI, "appendEntry">, mode: AgentModeOption): void {
|
|
318
|
-
const
|
|
319
|
-
|
|
315
|
+
function persistCursorModePreference(pi: Pick<ExtensionAPI, "appendEntry">, mode: AgentModeOption, scopeKey: string | undefined): void {
|
|
316
|
+
const state = getCursorSessionSettings(scopeKey);
|
|
317
|
+
const previousMode = state.mode;
|
|
318
|
+
state.mode = mode;
|
|
320
319
|
try {
|
|
321
320
|
pi.appendEntry<CursorModeEntryData>(MODE_ENTRY_TYPE, { mode });
|
|
322
321
|
} catch (error) {
|
|
323
|
-
|
|
322
|
+
state.mode = previousMode;
|
|
324
323
|
throw error;
|
|
325
324
|
}
|
|
326
325
|
}
|
|
@@ -328,13 +327,14 @@ function persistCursorModePreference(pi: Pick<ExtensionAPI, "appendEntry">, mode
|
|
|
328
327
|
function persistCursorHttp1Preference(
|
|
329
328
|
pi: Pick<ExtensionAPI, "appendEntry">,
|
|
330
329
|
enabled: boolean,
|
|
330
|
+
scopeKey: string | undefined,
|
|
331
331
|
): unknown | undefined {
|
|
332
|
-
const previousSession = getStoredCursorHttp1Enabled();
|
|
333
|
-
setStoredCursorHttp1Enabled(enabled);
|
|
332
|
+
const previousSession = getStoredCursorHttp1Enabled(scopeKey);
|
|
333
|
+
setStoredCursorHttp1Enabled(enabled, scopeKey);
|
|
334
334
|
try {
|
|
335
335
|
saveGlobalCursorHttp1Enabled(enabled);
|
|
336
336
|
} catch (error) {
|
|
337
|
-
setStoredCursorHttp1Enabled(previousSession);
|
|
337
|
+
setStoredCursorHttp1Enabled(previousSession, scopeKey);
|
|
338
338
|
throw error;
|
|
339
339
|
}
|
|
340
340
|
try {
|
|
@@ -347,23 +347,24 @@ function persistCursorHttp1Preference(
|
|
|
347
347
|
}
|
|
348
348
|
}
|
|
349
349
|
|
|
350
|
-
function restoreCliCursorMode(raw: boolean | string | undefined): void {
|
|
351
|
-
|
|
350
|
+
function restoreCliCursorMode(raw: boolean | string | undefined, scopeKey?: string): void {
|
|
351
|
+
const cli = getCursorSessionSettings(scopeKey).cli;
|
|
352
|
+
cli.mode = { kind: "unset" };
|
|
352
353
|
if (raw === undefined || raw === "" || raw === false) return;
|
|
353
354
|
const parsed = parseCursorAgentMode(raw);
|
|
354
355
|
if (parsed) {
|
|
355
|
-
|
|
356
|
+
cli.mode = { kind: "valid", mode: parsed };
|
|
356
357
|
return;
|
|
357
358
|
}
|
|
358
359
|
const rawText = String(raw);
|
|
359
360
|
const message = formatInvalidCursorMode(rawText);
|
|
360
|
-
|
|
361
|
+
cli.mode = { kind: "invalid", raw: rawText, message };
|
|
361
362
|
}
|
|
362
363
|
|
|
363
|
-
function notifyInvalidCursorModeIfCursorActive(ctx: Pick<ExtensionContext, "hasUI" | "mode" | "ui">): void {
|
|
364
|
-
const modeResolution = resolveCursorAgentMode();
|
|
364
|
+
function notifyInvalidCursorModeIfCursorActive(ctx: Pick<ExtensionContext, "hasUI" | "mode" | "ui" | "sessionManager">): void {
|
|
365
|
+
const modeResolution = resolveCursorAgentMode(cursorSettingsScopeForContext(ctx));
|
|
365
366
|
if (modeResolution.kind !== "invalid" || !ctx.hasUI || ctx.mode !== "tui") return;
|
|
366
|
-
const scopeKey = getCursorSessionScopeKey();
|
|
367
|
+
const scopeKey = cursorSettingsScopeForContext(ctx) ?? getCursorSessionScopeKey();
|
|
367
368
|
if (invalidCursorModeNotifiedSessionScopeKeys.has(scopeKey)) return;
|
|
368
369
|
invalidCursorModeNotifiedSessionScopeKeys.add(scopeKey);
|
|
369
370
|
ctx.ui.notify(modeResolution.message, "error");
|
|
@@ -414,11 +415,12 @@ function emitCursorToolsDebugReport(
|
|
|
414
415
|
console.log(report);
|
|
415
416
|
}
|
|
416
417
|
|
|
417
|
-
export function getEffectiveFastForModelId(modelId: string): boolean | undefined {
|
|
418
|
-
return getEffectiveFast(modelId);
|
|
418
|
+
export function getEffectiveFastForModelId(modelId: string, scopeKey?: string): boolean | undefined {
|
|
419
|
+
return getEffectiveFast(modelId, scopeKey);
|
|
419
420
|
}
|
|
420
421
|
|
|
421
422
|
export function registerCursorRuntimeControls(pi: CursorRuntimeControlsExtensionApi): void {
|
|
423
|
+
registerCursorSessionSettings(pi);
|
|
422
424
|
registerCursorCloudRuntimeControls(pi, { refreshStatus: updateCursorStatus });
|
|
423
425
|
|
|
424
426
|
pi.registerFlag("cursor-fast", {
|
|
@@ -448,6 +450,7 @@ export function registerCursorRuntimeControls(pi: CursorRuntimeControlsExtension
|
|
|
448
450
|
ctx.ui.notify(`Fast mode not supported by ${modelName}`, "info");
|
|
449
451
|
return;
|
|
450
452
|
}
|
|
453
|
+
const { forceNoFast: cliForceNoFast, forceFast: cliForceFast } = getCursorSessionSettings(cursorSettingsScopeForContext(ctx)).cli;
|
|
451
454
|
if (cliForceNoFast) {
|
|
452
455
|
ctx.ui.notify("Cursor fast is forced off by --cursor-no-fast", "info");
|
|
453
456
|
return;
|
|
@@ -466,11 +469,11 @@ export function registerCursorRuntimeControls(pi: CursorRuntimeControlsExtension
|
|
|
466
469
|
}
|
|
467
470
|
|
|
468
471
|
const preferenceModelId = getFastPreferenceModelId(metadata);
|
|
469
|
-
const current = getEffectiveFast(metadata.piModelId) ?? false;
|
|
472
|
+
const current = getEffectiveFast(metadata.piModelId, cursorSettingsScopeForContext(ctx)) ?? false;
|
|
470
473
|
const next = !current;
|
|
471
474
|
let appendError: unknown;
|
|
472
475
|
try {
|
|
473
|
-
appendError = persistFastPreference(pi, preferenceModelId, next);
|
|
476
|
+
appendError = persistFastPreference(pi, preferenceModelId, next, cursorSettingsScopeForContext(ctx));
|
|
474
477
|
} catch (error) {
|
|
475
478
|
updateCursorStatus(ctx);
|
|
476
479
|
ctx.ui.notify(`Failed to save Cursor fast preference: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
@@ -529,7 +532,7 @@ export function registerCursorRuntimeControls(pi: CursorRuntimeControlsExtension
|
|
|
529
532
|
}
|
|
530
533
|
let appendError: unknown;
|
|
531
534
|
try {
|
|
532
|
-
appendError = persistCursorHttp1Preference(pi, next);
|
|
535
|
+
appendError = persistCursorHttp1Preference(pi, next, cursorSettingsScopeForContext(ctx));
|
|
533
536
|
} catch (error) {
|
|
534
537
|
updateCursorStatus(ctx);
|
|
535
538
|
ctx.ui.notify(
|
|
@@ -568,7 +571,7 @@ export function registerCursorRuntimeControls(pi: CursorRuntimeControlsExtension
|
|
|
568
571
|
return;
|
|
569
572
|
}
|
|
570
573
|
try {
|
|
571
|
-
const result = await refreshSessionCursorAgentConfig();
|
|
574
|
+
const result = await refreshSessionCursorAgentConfig(getCursorSessionScopeSnapshot(pi).scopeKey);
|
|
572
575
|
const messages: Record<typeof result, string> = {
|
|
573
576
|
reloaded: "Cursor SDK agent config refreshed.",
|
|
574
577
|
"no-agent": "No Cursor SDK agent exists yet; config will load on the next Cursor run.",
|
|
@@ -588,7 +591,7 @@ export function registerCursorRuntimeControls(pi: CursorRuntimeControlsExtension
|
|
|
588
591
|
const usage = "Usage: /cursor-mode agent|plan";
|
|
589
592
|
const mode = parseCursorAgentMode(args);
|
|
590
593
|
if (!args.trim()) {
|
|
591
|
-
const modeResolution = resolveCursorAgentMode();
|
|
594
|
+
const modeResolution = resolveCursorAgentMode(cursorSettingsScopeForContext(ctx));
|
|
592
595
|
if (modeResolution.kind === "invalid") {
|
|
593
596
|
ctx.ui.notify(`${modeResolution.message} ${usage}`, "error");
|
|
594
597
|
} else {
|
|
@@ -600,14 +603,16 @@ export function registerCursorRuntimeControls(pi: CursorRuntimeControlsExtension
|
|
|
600
603
|
ctx.ui.notify(`Invalid Cursor mode "${args.trim()}". ${usage}`, "error");
|
|
601
604
|
return;
|
|
602
605
|
}
|
|
606
|
+
const cli = getCursorSessionSettings(cursorSettingsScopeForContext(ctx)).cli;
|
|
607
|
+
const cliCursorModeState = cli.mode;
|
|
603
608
|
if (cliCursorModeState.kind === "valid") {
|
|
604
609
|
ctx.ui.notify(`Cursor mode is forced to ${cliCursorModeState.mode} by --cursor-mode`, "info");
|
|
605
610
|
return;
|
|
606
611
|
}
|
|
607
612
|
const clearedInvalidCliMode = cliCursorModeState.kind === "invalid";
|
|
608
613
|
try {
|
|
609
|
-
persistCursorModePreference(pi, mode);
|
|
610
|
-
if (clearedInvalidCliMode)
|
|
614
|
+
persistCursorModePreference(pi, mode, cursorSettingsScopeForContext(ctx));
|
|
615
|
+
if (clearedInvalidCliMode) cli.mode = { kind: "unset" };
|
|
611
616
|
} catch (error) {
|
|
612
617
|
updateCursorStatus(ctx);
|
|
613
618
|
ctx.ui.notify(`Failed to save Cursor mode preference: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
@@ -633,11 +638,13 @@ export function registerCursorRuntimeControls(pi: CursorRuntimeControlsExtension
|
|
|
633
638
|
authoritativeGlobalFastPreferenceIds.clear();
|
|
634
639
|
setCursorHttp1GlobalPreferenceAuthoritative(false);
|
|
635
640
|
globalFastPreferences = loadGlobalFastPreferences();
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
641
|
+
const scopeKey = cursorSettingsScopeForContext(ctx);
|
|
642
|
+
const cli = getCursorSessionSettings(scopeKey).cli;
|
|
643
|
+
cli.forceFast = pi.getFlag("cursor-fast") === true;
|
|
644
|
+
cli.forceNoFast = pi.getFlag("cursor-no-fast") === true;
|
|
645
|
+
restoreCursorCliState(pi, scopeKey);
|
|
639
646
|
restoreSessionCursorPreferences(ctx);
|
|
640
|
-
restoreCliCursorMode(pi.getFlag("cursor-mode"));
|
|
647
|
+
restoreCliCursorMode(pi.getFlag("cursor-mode"), scopeKey);
|
|
641
648
|
},
|
|
642
649
|
sync: (ctx) => {
|
|
643
650
|
if (isCursorModel(ctx.model)) notifyInvalidCursorModeIfCursorActive(ctx);
|
|
@@ -647,10 +654,10 @@ export function registerCursorRuntimeControls(pi: CursorRuntimeControlsExtension
|
|
|
647
654
|
}
|
|
648
655
|
|
|
649
656
|
function resetCursorModeStateForTests(): void {
|
|
650
|
-
|
|
657
|
+
resetCursorSessionSettingsForTests();
|
|
651
658
|
setCursorHttp1GlobalPreferenceAuthoritative(false);
|
|
652
659
|
setStoredCursorHttp1Enabled(undefined);
|
|
653
|
-
|
|
660
|
+
getCursorSessionSettings().cli.mode = { kind: "unset" };
|
|
654
661
|
resetCursorRuntimeStateForTests();
|
|
655
662
|
invalidCursorModeNotifiedSessionScopeKeys.clear();
|
|
656
663
|
authoritativeGlobalFastPreferenceIds.clear();
|
|
@@ -664,9 +671,9 @@ export const __testUtils = {
|
|
|
664
671
|
DEFAULT_CURSOR_AGENT_MODE,
|
|
665
672
|
getConfigPath,
|
|
666
673
|
loadGlobalFastPreferences,
|
|
667
|
-
sessionFastPreferences,
|
|
668
|
-
getSessionCursorAgentMode: () =>
|
|
669
|
-
getCliCursorAgentMode: () => (
|
|
670
|
-
getCliCursorModeState: () =>
|
|
674
|
+
get sessionFastPreferences() { return getCursorSessionSettings().fast; },
|
|
675
|
+
getSessionCursorAgentMode: () => getCursorSessionSettings().mode,
|
|
676
|
+
getCliCursorAgentMode: () => { const mode = getCursorSessionSettings().cli.mode; return mode.kind === "valid" ? mode.mode : undefined; },
|
|
677
|
+
getCliCursorModeState: () => getCursorSessionSettings().cli.mode,
|
|
671
678
|
resetCursorModeStateForTests,
|
|
672
679
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { calculateCost } from "@earendil-works/pi-ai";
|
|
1
2
|
import type { Api, AssistantMessage, Context, Model } from "@earendil-works/pi-ai";
|
|
2
3
|
import {
|
|
3
4
|
CURSOR_APPROX_CHARS_PER_TOKEN,
|
|
@@ -112,7 +113,7 @@ export function isCursorSdkUsageSafeForPiMessage(turnUsage: CursorSdkTurnUsage,
|
|
|
112
113
|
export interface CursorSdkUsageApplyOptions {
|
|
113
114
|
runtime: CursorRuntime;
|
|
114
115
|
turn?: CursorSdkTurnUsage;
|
|
115
|
-
|
|
116
|
+
occupancyFloor?: number;
|
|
116
117
|
}
|
|
117
118
|
|
|
118
119
|
export function applyCursorSdkUsage(partial: AssistantMessage, turnUsage: CursorSdkTurnUsage): void {
|
|
@@ -121,84 +122,24 @@ export function applyCursorSdkUsage(partial: AssistantMessage, turnUsage: Cursor
|
|
|
121
122
|
partial.usage.output = turnUsage.outputTokens;
|
|
122
123
|
partial.usage.cacheRead = turnUsage.cacheReadTokens;
|
|
123
124
|
partial.usage.cacheWrite = turnUsage.cacheWriteTokens;
|
|
124
|
-
//
|
|
125
|
+
// Full prompt + output equals the sum of Pi's disjoint components.
|
|
125
126
|
partial.usage.totalTokens = turnUsage.inputTokens + turnUsage.outputTokens;
|
|
126
127
|
}
|
|
127
128
|
|
|
128
|
-
function
|
|
129
|
-
return assistant.api === model.api && assistant.provider === model.provider && assistant.model === model.id;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function getLatestCompactionBoundary(context: Context): { index: number; tokensBefore?: number } | undefined {
|
|
133
|
-
for (let index = context.messages.length - 1; index >= 0; index -= 1) {
|
|
134
|
-
const message = context.messages[index] as { role?: string; tokensBefore?: number };
|
|
135
|
-
if (message.role !== "compactionSummary") continue;
|
|
136
|
-
const tokensBefore = message.tokensBefore;
|
|
137
|
-
return {
|
|
138
|
-
index,
|
|
139
|
-
tokensBefore: Number.isFinite(tokensBefore) && tokensBefore !== undefined && tokensBefore > 0 ? Math.floor(tokensBefore) : undefined,
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
return undefined;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function getLastAcceptedContextOccupancy(context: Context, model: Model<Api>): number {
|
|
146
|
-
const boundary = getLatestCompactionBoundary(context);
|
|
147
|
-
for (let index = context.messages.length - 1; index >= 0; index -= 1) {
|
|
148
|
-
if (boundary && index < boundary.index) break;
|
|
149
|
-
const message = context.messages[index];
|
|
150
|
-
if (message.role !== "assistant" || !("usage" in message)) continue;
|
|
151
|
-
const assistant = message as AssistantMessage;
|
|
152
|
-
if (assistant.stopReason === "aborted" || assistant.stopReason === "error" || !assistant.usage) continue;
|
|
153
|
-
if (!isCompatibleCursorAssistantMeasurement(assistant, model)) continue;
|
|
154
|
-
const { usage } = assistant;
|
|
155
|
-
const total =
|
|
156
|
-
usage.totalTokens || usage.input + usage.output + usage.cacheRead + usage.cacheWrite;
|
|
157
|
-
if (!Number.isFinite(total) || total <= 0 || total > model.contextWindow) continue;
|
|
158
|
-
if (boundary?.tokensBefore !== undefined && total >= boundary.tokensBefore) continue;
|
|
159
|
-
return total;
|
|
160
|
-
}
|
|
161
|
-
return 0;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
export function applyCursorApproximateUsage(partial: AssistantMessage, model: Model<Api>, context: Context, sessionInputTokens: number): void {
|
|
129
|
+
export function applyCursorApproximateUsage(partial: AssistantMessage, model: Model<Api>, context: Context, sessionInputTokens: number, occupancyFloor?: number): void {
|
|
165
130
|
const outputTokens = estimateCursorAssistantSessionOutputTokens(partial);
|
|
166
|
-
|
|
131
|
+
const floor = occupancyFloor !== undefined && Number.isFinite(occupancyFloor) && occupancyFloor > 0 && occupancyFloor <= model.contextWindow ? occupancyFloor : 0;
|
|
132
|
+
const totalTokens = Math.max(
|
|
133
|
+
Math.max(0, sessionInputTokens) + outputTokens,
|
|
134
|
+
estimateCursorContextTotalTokens(partial, model, context),
|
|
135
|
+
floor,
|
|
136
|
+
);
|
|
137
|
+
// Estimated prompt components describe the same occupancy as totalTokens.
|
|
138
|
+
partial.usage.input = totalTokens - outputTokens;
|
|
167
139
|
partial.usage.output = outputTokens;
|
|
168
140
|
partial.usage.cacheRead = 0;
|
|
169
141
|
partial.usage.cacheWrite = 0;
|
|
170
|
-
|
|
171
|
-
partial.usage.totalTokens = Math.max(
|
|
172
|
-
partial.usage.input + partial.usage.output,
|
|
173
|
-
estimateCursorContextTotalTokens(partial, model, context),
|
|
174
|
-
getLastAcceptedContextOccupancy(context, model),
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function applyCursorOccupancyEstimate(partial: AssistantMessage, model: Model<Api>, context: Context): void {
|
|
179
|
-
partial.usage.totalTokens = Math.max(
|
|
180
|
-
estimateCursorContextTotalTokens(partial, model, context),
|
|
181
|
-
getLastAcceptedContextOccupancy(context, model),
|
|
182
|
-
);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function isCurrentLocalOccupancy(turn: CursorSdkTurnUsage, model: Model<Api>, context: Context): boolean {
|
|
186
|
-
if (!isCursorSdkUsageSafeForPiMessage(turn, model)) return false;
|
|
187
|
-
const tokensBefore = getLatestCompactionBoundary(context)?.tokensBefore;
|
|
188
|
-
return tokensBefore === undefined || turn.inputTokens + turn.outputTokens < tokensBefore;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function applyResolvedCursorOccupancy(
|
|
192
|
-
partial: AssistantMessage,
|
|
193
|
-
model: Model<Api>,
|
|
194
|
-
context: Context,
|
|
195
|
-
localTurn: CursorSdkTurnUsage | undefined,
|
|
196
|
-
): void {
|
|
197
|
-
if (localTurn && isCurrentLocalOccupancy(localTurn, model, context)) {
|
|
198
|
-
partial.usage.totalTokens = localTurn.inputTokens + localTurn.outputTokens;
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
applyCursorOccupancyEstimate(partial, model, context);
|
|
142
|
+
partial.usage.totalTokens = totalTokens;
|
|
202
143
|
}
|
|
203
144
|
|
|
204
145
|
export function applyCursorUsage(
|
|
@@ -208,18 +149,12 @@ export function applyCursorUsage(
|
|
|
208
149
|
sessionInputTokens: number,
|
|
209
150
|
sdkUsage?: CursorSdkUsageApplyOptions,
|
|
210
151
|
): void {
|
|
211
|
-
const billed = sdkUsage?.billed;
|
|
212
152
|
const localTurn = sdkUsage?.runtime === "local" ? sdkUsage.turn : undefined;
|
|
213
|
-
if (billed && isCursorSdkUsagePartitionSafe(billed, model)) {
|
|
214
|
-
applyCursorSdkUsage(partial, billed);
|
|
215
|
-
applyResolvedCursorOccupancy(partial, model, context, localTurn);
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
// Only local raw turn-ended usage has a captured full-prompt/cache-partition occupancy contract.
|
|
219
153
|
if (localTurn && isCursorSdkUsageSafeForPiMessage(localTurn, model)) {
|
|
154
|
+
// Fresh LOCAL occupancy supersedes any previous request-provenance floor.
|
|
220
155
|
applyCursorSdkUsage(partial, localTurn);
|
|
221
|
-
|
|
222
|
-
|
|
156
|
+
} else {
|
|
157
|
+
applyCursorApproximateUsage(partial, model, context, sessionInputTokens, sdkUsage?.occupancyFloor);
|
|
223
158
|
}
|
|
224
|
-
|
|
159
|
+
calculateCost(model, partial.usage);
|
|
225
160
|
}
|