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
|
@@ -11,7 +11,6 @@ export const CURSOR_ACTIVATE_SKILL_MCP_NAME = "pi__cursor_activate_skill";
|
|
|
11
11
|
const AVAILABLE_SKILLS_SECTION_PATTERN = /<skills>\nThe following skills provide specialized instructions for specific tasks\.[\s\S]*?<\/available_skills>\n<\/skills>|\n\nThe following skills provide specialized instructions for specific tasks\.[\s\S]*?<\/available_skills>/;
|
|
12
12
|
const MAX_SKILL_RESOURCES = 80;
|
|
13
13
|
const RESOURCE_DIR_NAMES = ["scripts", "references", "assets"];
|
|
14
|
-
let currentSkillsByName = new Map();
|
|
15
14
|
function escapeXml(value) {
|
|
16
15
|
return value
|
|
17
16
|
.replace(/&/g, "&")
|
|
@@ -23,32 +22,9 @@ function escapeXml(value) {
|
|
|
23
22
|
function getVisibleSkills(skills) {
|
|
24
23
|
return (skills ?? []).filter((skill) => !skill.disableModelInvocation);
|
|
25
24
|
}
|
|
26
|
-
function setCurrentSkills(skills) {
|
|
27
|
-
currentSkillsByName = new Map(getVisibleSkills(skills).map((skill) => [skill.name, skill]));
|
|
28
|
-
}
|
|
29
|
-
function getAvailableSkillNames() {
|
|
30
|
-
return [...currentSkillsByName.keys()].sort();
|
|
31
|
-
}
|
|
32
25
|
function resolveEffectiveRuntimeForSkillLifecycle(cursorModel, ctx) {
|
|
33
26
|
return cursorModel ? resolveEffectiveCursorConfigForContext(ctx).runtime.value : "local";
|
|
34
27
|
}
|
|
35
|
-
function shouldExposeSkillTool(model, runtime) {
|
|
36
|
-
return runtime === "local" && isCursorModel(model) && resolveCursorPiToolBridgeEnabled() && currentSkillsByName.size > 0;
|
|
37
|
-
}
|
|
38
|
-
function syncCursorSkillToolForModel(pi, model, runtime) {
|
|
39
|
-
const activeToolNames = new Set(pi.getActiveTools());
|
|
40
|
-
const shouldBeActive = !arePiToolsDisabled(pi) && shouldExposeSkillTool(model, runtime);
|
|
41
|
-
const alreadyActive = activeToolNames.has(CURSOR_ACTIVATE_SKILL_TOOL_NAME);
|
|
42
|
-
if (shouldBeActive === alreadyActive)
|
|
43
|
-
return;
|
|
44
|
-
if (shouldBeActive) {
|
|
45
|
-
activeToolNames.add(CURSOR_ACTIVATE_SKILL_TOOL_NAME);
|
|
46
|
-
}
|
|
47
|
-
else {
|
|
48
|
-
activeToolNames.delete(CURSOR_ACTIVATE_SKILL_TOOL_NAME);
|
|
49
|
-
}
|
|
50
|
-
pi.setActiveTools([...activeToolNames]);
|
|
51
|
-
}
|
|
52
28
|
export function formatCursorSkillsForPrompt(skills) {
|
|
53
29
|
const visibleSkills = getVisibleSkills(skills);
|
|
54
30
|
if (visibleSkills.length === 0)
|
|
@@ -119,15 +95,6 @@ async function listSkillResourcePaths(baseDir) {
|
|
|
119
95
|
}
|
|
120
96
|
return resources;
|
|
121
97
|
}
|
|
122
|
-
function buildActivationDetails(skill, resources = []) {
|
|
123
|
-
return {
|
|
124
|
-
name: skill?.name,
|
|
125
|
-
filePath: skill?.filePath,
|
|
126
|
-
baseDir: skill ? dirname(skill.filePath) : undefined,
|
|
127
|
-
resources,
|
|
128
|
-
availableSkillNames: getAvailableSkillNames(),
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
98
|
function formatSkillResources(resources) {
|
|
132
99
|
if (resources.length === 0)
|
|
133
100
|
return "<skill_resources />";
|
|
@@ -150,18 +117,35 @@ function wrapSkillContent(skill, content, resources) {
|
|
|
150
117
|
].join("\n");
|
|
151
118
|
}
|
|
152
119
|
export function registerCursorSkillTool(pi) {
|
|
120
|
+
let currentSkillsByName = new Map();
|
|
121
|
+
function setCurrentSkills(skills) {
|
|
122
|
+
currentSkillsByName = new Map(getVisibleSkills(skills).map((skill) => [skill.name, skill]));
|
|
123
|
+
}
|
|
124
|
+
function getAvailableSkillNames() {
|
|
125
|
+
return [...currentSkillsByName.keys()].sort();
|
|
126
|
+
}
|
|
127
|
+
function syncCursorSkillToolForModel(model, runtime) {
|
|
128
|
+
const activeToolNames = new Set(pi.getActiveTools());
|
|
129
|
+
const shouldBeActive = !arePiToolsDisabled(pi) && runtime === "local" && isCursorModel(model)
|
|
130
|
+
&& resolveCursorPiToolBridgeEnabled() && currentSkillsByName.size > 0;
|
|
131
|
+
const alreadyActive = activeToolNames.has(CURSOR_ACTIVATE_SKILL_TOOL_NAME);
|
|
132
|
+
if (shouldBeActive === alreadyActive)
|
|
133
|
+
return;
|
|
134
|
+
if (shouldBeActive) {
|
|
135
|
+
activeToolNames.add(CURSOR_ACTIVATE_SKILL_TOOL_NAME);
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
activeToolNames.delete(CURSOR_ACTIVATE_SKILL_TOOL_NAME);
|
|
139
|
+
}
|
|
140
|
+
pi.setActiveTools([...activeToolNames]);
|
|
141
|
+
}
|
|
153
142
|
pi.registerTool({
|
|
154
143
|
name: CURSOR_ACTIVATE_SKILL_TOOL_NAME,
|
|
155
144
|
label: "Cursor skill",
|
|
156
145
|
description: "Load full pi Agent Skill instructions for Cursor. Use with a skill name from the current <available_skills> catalog before applying that skill.",
|
|
157
|
-
promptSnippet: "Load full pi Agent Skill instructions for a listed skill before Cursor applies that skill",
|
|
158
146
|
parameters: Type.Object({
|
|
159
147
|
name: Type.String({ description: "Skill name from the current <available_skills> catalog" }),
|
|
160
148
|
}),
|
|
161
|
-
promptGuidelines: [
|
|
162
|
-
`Use ${CURSOR_ACTIVATE_SKILL_TOOL_NAME} only for skill names listed in the current <available_skills> catalog.`,
|
|
163
|
-
"After loading a skill, follow its instructions and resolve relative skill paths against the returned skill directory.",
|
|
164
|
-
],
|
|
165
149
|
async execute(_toolCallId, params) {
|
|
166
150
|
const requestedName = params.name?.trim();
|
|
167
151
|
if (!requestedName) {
|
|
@@ -178,7 +162,13 @@ export function registerCursorSkillTool(pi) {
|
|
|
178
162
|
]);
|
|
179
163
|
return {
|
|
180
164
|
content: [{ type: "text", text: wrapSkillContent(skill, content, resources) }],
|
|
181
|
-
details:
|
|
165
|
+
details: {
|
|
166
|
+
name: skill.name,
|
|
167
|
+
filePath: skill.filePath,
|
|
168
|
+
baseDir: dirname(skill.filePath),
|
|
169
|
+
resources,
|
|
170
|
+
availableSkillNames: getAvailableSkillNames(),
|
|
171
|
+
},
|
|
182
172
|
};
|
|
183
173
|
}
|
|
184
174
|
catch (error) {
|
|
@@ -188,7 +178,7 @@ export function registerCursorSkillTool(pi) {
|
|
|
188
178
|
});
|
|
189
179
|
const clearSkillsAndSync = (model, runtime = "local") => {
|
|
190
180
|
setCurrentSkills([]);
|
|
191
|
-
syncCursorSkillToolForModel(
|
|
181
|
+
syncCursorSkillToolForModel(model, runtime);
|
|
192
182
|
};
|
|
193
183
|
registerCursorModelLifecycle(pi, {
|
|
194
184
|
sessionStart: (_event, ctx) => {
|
|
@@ -202,7 +192,7 @@ export function registerCursorSkillTool(pi) {
|
|
|
202
192
|
const runtime = resolveEffectiveRuntimeForSkillLifecycle(cursorModel, ctx);
|
|
203
193
|
if (!cursorModel || runtime === "cloud")
|
|
204
194
|
setCurrentSkills([]);
|
|
205
|
-
syncCursorSkillToolForModel(
|
|
195
|
+
syncCursorSkillToolForModel(ctx.model, runtime);
|
|
206
196
|
},
|
|
207
197
|
beforeAgentStart: (event, ctx) => {
|
|
208
198
|
const cursorModel = isCursorModel(ctx.model);
|
|
@@ -213,7 +203,7 @@ export function registerCursorSkillTool(pi) {
|
|
|
213
203
|
else {
|
|
214
204
|
setCurrentSkills([]);
|
|
215
205
|
}
|
|
216
|
-
syncCursorSkillToolForModel(
|
|
206
|
+
syncCursorSkillToolForModel(ctx.model, runtime);
|
|
217
207
|
const resolved = resolveCursorSkillSystemPrompt(event.systemPrompt, ctx.model, event.systemPromptOptions, runtime);
|
|
218
208
|
if (resolved === event.systemPrompt)
|
|
219
209
|
return undefined;
|
|
@@ -221,10 +211,3 @@ export function registerCursorSkillTool(pi) {
|
|
|
221
211
|
},
|
|
222
212
|
});
|
|
223
213
|
}
|
|
224
|
-
export const __testUtils = {
|
|
225
|
-
AVAILABLE_SKILLS_SECTION_PATTERN,
|
|
226
|
-
buildActivationDetails,
|
|
227
|
-
setCurrentSkills,
|
|
228
|
-
listSkillResourcePaths,
|
|
229
|
-
wrapSkillContent,
|
|
230
|
-
};
|
package/dist/cursor-state.js
CHANGED
|
@@ -6,7 +6,8 @@ import { CURSOR_SETTING_SOURCES_ENV, DEFAULT_CURSOR_SETTING_SOURCES, resolveCurs
|
|
|
6
6
|
import { isCursorModel } from "./cursor-model.js";
|
|
7
7
|
import { registerCursorModelLifecycle } from "./cursor-model-lifecycle.js";
|
|
8
8
|
import { asRecord } from "./cursor-record-utils.js";
|
|
9
|
-
import { getCursorSessionScopeKey } from "./cursor-session-scope.js";
|
|
9
|
+
import { getCursorSessionScopeSnapshot, getCursorSessionScopeKey } from "./cursor-session-scope.js";
|
|
10
|
+
import { cursorSettingsScopeForContext, getCursorSessionSettings, registerCursorSessionSettings, resetCursorSessionSettingsForTests } from "./cursor-session-settings.js";
|
|
10
11
|
import { refreshSessionCursorAgentConfig } from "./cursor-session-agent.js";
|
|
11
12
|
import { getCursorModelMetadata } from "./model-discovery.js";
|
|
12
13
|
import { cursorFastDefaultsFromConfig, getCursorSdkUserConfigPath, loadCursorSdkUserConfig, mergeCursorSdkConfigForUpdate, resolveCursorFastDefault, updateCursorSdkConfig, } from "./cursor-config.js";
|
|
@@ -15,13 +16,8 @@ export { consumeCursorLocalForceOverride, getCursorCliConfig, getCursorSessionCo
|
|
|
15
16
|
const FAST_ENTRY_TYPE = "cursor-fast-state";
|
|
16
17
|
const MODE_ENTRY_TYPE = "cursor-mode-state";
|
|
17
18
|
const DEFAULT_CURSOR_AGENT_MODE = "agent";
|
|
18
|
-
const sessionFastPreferences = new Map();
|
|
19
19
|
const authoritativeGlobalFastPreferenceIds = new Set();
|
|
20
20
|
let globalFastPreferences = new Map();
|
|
21
|
-
let cliForceFast = false;
|
|
22
|
-
let cliForceNoFast = false;
|
|
23
|
-
let sessionCursorAgentMode;
|
|
24
|
-
let cliCursorModeState = { kind: "unset" };
|
|
25
21
|
const invalidCursorModeNotifiedSessionScopeKeys = new Set();
|
|
26
22
|
export function isCursorAgentMode(value) {
|
|
27
23
|
return value === "agent" || value === "plan";
|
|
@@ -64,7 +60,8 @@ function saveGlobalCursorHttp1Enabled(enabled) {
|
|
|
64
60
|
local: { useHttp1ForAgent: enabled },
|
|
65
61
|
}), { newFileMode: 0o600 });
|
|
66
62
|
}
|
|
67
|
-
function restoreSessionFastPreferences(branch) {
|
|
63
|
+
function restoreSessionFastPreferences(branch, scopeKey) {
|
|
64
|
+
const sessionFastPreferences = getCursorSessionSettings(scopeKey).fast;
|
|
68
65
|
sessionFastPreferences.clear();
|
|
69
66
|
for (const entry of branch) {
|
|
70
67
|
if (entry.type !== "custom" || entry.customType !== FAST_ENTRY_TYPE)
|
|
@@ -76,30 +73,32 @@ function restoreSessionFastPreferences(branch) {
|
|
|
76
73
|
}
|
|
77
74
|
}
|
|
78
75
|
}
|
|
79
|
-
function restoreSessionCursorMode(branch) {
|
|
80
|
-
|
|
76
|
+
function restoreSessionCursorMode(branch, scopeKey) {
|
|
77
|
+
const state = getCursorSessionSettings(scopeKey);
|
|
78
|
+
state.mode = undefined;
|
|
81
79
|
for (const entry of branch) {
|
|
82
80
|
if (entry.type !== "custom" || entry.customType !== MODE_ENTRY_TYPE)
|
|
83
81
|
continue;
|
|
84
82
|
if (isCursorModeEntryData(entry.data)) {
|
|
85
|
-
|
|
83
|
+
state.mode = entry.data.mode;
|
|
86
84
|
}
|
|
87
85
|
}
|
|
88
86
|
}
|
|
89
87
|
function restoreSessionCursorPreferences(ctx) {
|
|
90
88
|
const branch = ctx.sessionManager.getBranch();
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
89
|
+
const scopeKey = cursorSettingsScopeForContext(ctx);
|
|
90
|
+
restoreSessionCursorRuntimeState(branch, scopeKey);
|
|
91
|
+
restoreSessionFastPreferences(branch, scopeKey);
|
|
92
|
+
restoreSessionCursorMode(branch, scopeKey);
|
|
93
|
+
restoreSessionCursorHttp1(branch, scopeKey);
|
|
94
|
+
}
|
|
95
|
+
function restoreSessionCursorHttp1(branch, scopeKey) {
|
|
96
|
+
setStoredCursorHttp1Enabled(undefined, scopeKey);
|
|
98
97
|
for (const entry of branch) {
|
|
99
98
|
if (entry.type !== "custom" || entry.customType !== CURSOR_HTTP1_ENTRY_TYPE)
|
|
100
99
|
continue;
|
|
101
100
|
if (isCursorHttp1EntryData(entry.data)) {
|
|
102
|
-
setStoredCursorHttp1Enabled(entry.data.enabled);
|
|
101
|
+
setStoredCursorHttp1Enabled(entry.data.enabled, scopeKey);
|
|
103
102
|
}
|
|
104
103
|
}
|
|
105
104
|
}
|
|
@@ -113,7 +112,8 @@ function getMapFastPreference(map, metadata) {
|
|
|
113
112
|
const preferenceModelId = getFastPreferenceModelId(metadata);
|
|
114
113
|
return map.get(preferenceModelId) ?? (preferenceModelId !== metadata.baseModelId ? map.get(metadata.baseModelId) : undefined);
|
|
115
114
|
}
|
|
116
|
-
function getEffectiveFast(modelId) {
|
|
115
|
+
function getEffectiveFast(modelId, scopeKey) {
|
|
116
|
+
const { forceNoFast: cliForceNoFast, forceFast: cliForceFast } = getCursorSessionSettings(scopeKey).cli;
|
|
117
117
|
const metadata = getCursorModelMetadata(modelId);
|
|
118
118
|
if (!metadata?.supportsFast)
|
|
119
119
|
return undefined;
|
|
@@ -123,7 +123,7 @@ function getEffectiveFast(modelId) {
|
|
|
123
123
|
aliasOverride: metadata.fastOverride,
|
|
124
124
|
sessionValue: authoritativeGlobalFastPreferenceIds.has(getFastPreferenceModelId(metadata))
|
|
125
125
|
? undefined
|
|
126
|
-
: getMapFastPreference(
|
|
126
|
+
: getMapFastPreference(getCursorSessionSettings(scopeKey).fast, metadata),
|
|
127
127
|
userValue: getMapFastPreference(globalFastPreferences, metadata),
|
|
128
128
|
modelDefault: metadata.defaultFast,
|
|
129
129
|
}).value;
|
|
@@ -131,21 +131,22 @@ function getEffectiveFast(modelId) {
|
|
|
131
131
|
function formatInvalidCursorMode(raw) {
|
|
132
132
|
return `Invalid --cursor-mode "${raw}". Use "agent" or "plan".`;
|
|
133
133
|
}
|
|
134
|
-
export function getStoredCursorAgentMode() {
|
|
135
|
-
return
|
|
134
|
+
export function getStoredCursorAgentMode(scopeKey) {
|
|
135
|
+
return getCursorSessionSettings(scopeKey).mode ?? DEFAULT_CURSOR_AGENT_MODE;
|
|
136
136
|
}
|
|
137
|
-
export function resolveCursorAgentMode() {
|
|
137
|
+
export function resolveCursorAgentMode(scopeKey) {
|
|
138
|
+
const cliCursorModeState = getCursorSessionSettings(scopeKey).cli.mode;
|
|
138
139
|
switch (cliCursorModeState.kind) {
|
|
139
140
|
case "valid":
|
|
140
141
|
return { kind: "valid", mode: cliCursorModeState.mode };
|
|
141
142
|
case "invalid":
|
|
142
143
|
return { kind: "invalid", raw: cliCursorModeState.raw, message: cliCursorModeState.message };
|
|
143
144
|
case "unset":
|
|
144
|
-
return { kind: "valid", mode: getStoredCursorAgentMode() };
|
|
145
|
+
return { kind: "valid", mode: getStoredCursorAgentMode(scopeKey) };
|
|
145
146
|
}
|
|
146
147
|
}
|
|
147
|
-
export function getCursorProviderAgentModeOrThrow() {
|
|
148
|
-
const resolution = resolveCursorAgentMode();
|
|
148
|
+
export function getCursorProviderAgentModeOrThrow(scopeKey) {
|
|
149
|
+
const resolution = resolveCursorAgentMode(scopeKey);
|
|
149
150
|
if (resolution.kind === "invalid")
|
|
150
151
|
throw new Error(resolution.message);
|
|
151
152
|
return resolution.mode;
|
|
@@ -157,14 +158,14 @@ function updateCursorStatus(ctx, model = ctx.model) {
|
|
|
157
158
|
}
|
|
158
159
|
const metadata = getCursorModelMetadata(model.id);
|
|
159
160
|
const resolution = resolveCursorStatusRuntime(ctx);
|
|
160
|
-
const modeResolution = resolveCursorAgentMode();
|
|
161
|
+
const modeResolution = resolveCursorAgentMode(cursorSettingsScopeForContext(ctx));
|
|
161
162
|
const mode = modeResolution.kind === "invalid" ? "invalid" : modeResolution.mode;
|
|
162
163
|
if (resolution.kind === "invalid") {
|
|
163
164
|
ctx.ui.setStatus("cursor", formatCursorStatus("invalid", undefined, mode));
|
|
164
165
|
return;
|
|
165
166
|
}
|
|
166
167
|
const runtime = resolution.runtime.value;
|
|
167
|
-
const fast = runtime === "cloud" ? undefined : metadata?.supportsFast ? getEffectiveFast(model.id) : undefined;
|
|
168
|
+
const fast = runtime === "cloud" ? undefined : metadata?.supportsFast ? getEffectiveFast(model.id, cursorSettingsScopeForContext(ctx)) : undefined;
|
|
168
169
|
ctx.ui.setStatus("cursor", formatCursorStatus(runtime, fast, mode, resolution.useHttp1ForAgent.value));
|
|
169
170
|
}
|
|
170
171
|
function getCurrentCursorMetadata(ctx) {
|
|
@@ -181,7 +182,8 @@ function restoreMapValue(map, key, previous) {
|
|
|
181
182
|
map.set(key, previous);
|
|
182
183
|
}
|
|
183
184
|
}
|
|
184
|
-
function persistFastPreference(pi, modelId, fast) {
|
|
185
|
+
function persistFastPreference(pi, modelId, fast, scopeKey) {
|
|
186
|
+
const sessionFastPreferences = getCursorSessionSettings(scopeKey).fast;
|
|
185
187
|
const previousSession = sessionFastPreferences.get(modelId);
|
|
186
188
|
const previousGlobal = globalFastPreferences.get(modelId);
|
|
187
189
|
sessionFastPreferences.set(modelId, fast);
|
|
@@ -204,25 +206,26 @@ function persistFastPreference(pi, modelId, fast) {
|
|
|
204
206
|
return error;
|
|
205
207
|
}
|
|
206
208
|
}
|
|
207
|
-
function persistCursorModePreference(pi, mode) {
|
|
208
|
-
const
|
|
209
|
-
|
|
209
|
+
function persistCursorModePreference(pi, mode, scopeKey) {
|
|
210
|
+
const state = getCursorSessionSettings(scopeKey);
|
|
211
|
+
const previousMode = state.mode;
|
|
212
|
+
state.mode = mode;
|
|
210
213
|
try {
|
|
211
214
|
pi.appendEntry(MODE_ENTRY_TYPE, { mode });
|
|
212
215
|
}
|
|
213
216
|
catch (error) {
|
|
214
|
-
|
|
217
|
+
state.mode = previousMode;
|
|
215
218
|
throw error;
|
|
216
219
|
}
|
|
217
220
|
}
|
|
218
|
-
function persistCursorHttp1Preference(pi, enabled) {
|
|
219
|
-
const previousSession = getStoredCursorHttp1Enabled();
|
|
220
|
-
setStoredCursorHttp1Enabled(enabled);
|
|
221
|
+
function persistCursorHttp1Preference(pi, enabled, scopeKey) {
|
|
222
|
+
const previousSession = getStoredCursorHttp1Enabled(scopeKey);
|
|
223
|
+
setStoredCursorHttp1Enabled(enabled, scopeKey);
|
|
221
224
|
try {
|
|
222
225
|
saveGlobalCursorHttp1Enabled(enabled);
|
|
223
226
|
}
|
|
224
227
|
catch (error) {
|
|
225
|
-
setStoredCursorHttp1Enabled(previousSession);
|
|
228
|
+
setStoredCursorHttp1Enabled(previousSession, scopeKey);
|
|
226
229
|
throw error;
|
|
227
230
|
}
|
|
228
231
|
try {
|
|
@@ -235,24 +238,25 @@ function persistCursorHttp1Preference(pi, enabled) {
|
|
|
235
238
|
return error;
|
|
236
239
|
}
|
|
237
240
|
}
|
|
238
|
-
function restoreCliCursorMode(raw) {
|
|
239
|
-
|
|
241
|
+
function restoreCliCursorMode(raw, scopeKey) {
|
|
242
|
+
const cli = getCursorSessionSettings(scopeKey).cli;
|
|
243
|
+
cli.mode = { kind: "unset" };
|
|
240
244
|
if (raw === undefined || raw === "" || raw === false)
|
|
241
245
|
return;
|
|
242
246
|
const parsed = parseCursorAgentMode(raw);
|
|
243
247
|
if (parsed) {
|
|
244
|
-
|
|
248
|
+
cli.mode = { kind: "valid", mode: parsed };
|
|
245
249
|
return;
|
|
246
250
|
}
|
|
247
251
|
const rawText = String(raw);
|
|
248
252
|
const message = formatInvalidCursorMode(rawText);
|
|
249
|
-
|
|
253
|
+
cli.mode = { kind: "invalid", raw: rawText, message };
|
|
250
254
|
}
|
|
251
255
|
function notifyInvalidCursorModeIfCursorActive(ctx) {
|
|
252
|
-
const modeResolution = resolveCursorAgentMode();
|
|
256
|
+
const modeResolution = resolveCursorAgentMode(cursorSettingsScopeForContext(ctx));
|
|
253
257
|
if (modeResolution.kind !== "invalid" || !ctx.hasUI || ctx.mode !== "tui")
|
|
254
258
|
return;
|
|
255
|
-
const scopeKey = getCursorSessionScopeKey();
|
|
259
|
+
const scopeKey = cursorSettingsScopeForContext(ctx) ?? getCursorSessionScopeKey();
|
|
256
260
|
if (invalidCursorModeNotifiedSessionScopeKeys.has(scopeKey))
|
|
257
261
|
return;
|
|
258
262
|
invalidCursorModeNotifiedSessionScopeKeys.add(scopeKey);
|
|
@@ -293,10 +297,11 @@ function emitCursorToolsDebugReport(pi, ctx) {
|
|
|
293
297
|
}
|
|
294
298
|
console.log(report);
|
|
295
299
|
}
|
|
296
|
-
export function getEffectiveFastForModelId(modelId) {
|
|
297
|
-
return getEffectiveFast(modelId);
|
|
300
|
+
export function getEffectiveFastForModelId(modelId, scopeKey) {
|
|
301
|
+
return getEffectiveFast(modelId, scopeKey);
|
|
298
302
|
}
|
|
299
303
|
export function registerCursorRuntimeControls(pi) {
|
|
304
|
+
registerCursorSessionSettings(pi);
|
|
300
305
|
registerCursorCloudRuntimeControls(pi, { refreshStatus: updateCursorStatus });
|
|
301
306
|
pi.registerFlag("cursor-fast", {
|
|
302
307
|
description: "Force Cursor fast mode for this run when the selected Cursor model supports it",
|
|
@@ -322,6 +327,7 @@ export function registerCursorRuntimeControls(pi) {
|
|
|
322
327
|
ctx.ui.notify(`Fast mode not supported by ${modelName}`, "info");
|
|
323
328
|
return;
|
|
324
329
|
}
|
|
330
|
+
const { forceNoFast: cliForceNoFast, forceFast: cliForceFast } = getCursorSessionSettings(cursorSettingsScopeForContext(ctx)).cli;
|
|
325
331
|
if (cliForceNoFast) {
|
|
326
332
|
ctx.ui.notify("Cursor fast is forced off by --cursor-no-fast", "info");
|
|
327
333
|
return;
|
|
@@ -336,11 +342,11 @@ export function registerCursorRuntimeControls(pi) {
|
|
|
336
342
|
return;
|
|
337
343
|
}
|
|
338
344
|
const preferenceModelId = getFastPreferenceModelId(metadata);
|
|
339
|
-
const current = getEffectiveFast(metadata.piModelId) ?? false;
|
|
345
|
+
const current = getEffectiveFast(metadata.piModelId, cursorSettingsScopeForContext(ctx)) ?? false;
|
|
340
346
|
const next = !current;
|
|
341
347
|
let appendError;
|
|
342
348
|
try {
|
|
343
|
-
appendError = persistFastPreference(pi, preferenceModelId, next);
|
|
349
|
+
appendError = persistFastPreference(pi, preferenceModelId, next, cursorSettingsScopeForContext(ctx));
|
|
344
350
|
}
|
|
345
351
|
catch (error) {
|
|
346
352
|
updateCursorStatus(ctx);
|
|
@@ -389,7 +395,7 @@ export function registerCursorRuntimeControls(pi) {
|
|
|
389
395
|
}
|
|
390
396
|
let appendError;
|
|
391
397
|
try {
|
|
392
|
-
appendError = persistCursorHttp1Preference(pi, next);
|
|
398
|
+
appendError = persistCursorHttp1Preference(pi, next, cursorSettingsScopeForContext(ctx));
|
|
393
399
|
}
|
|
394
400
|
catch (error) {
|
|
395
401
|
updateCursorStatus(ctx);
|
|
@@ -418,7 +424,7 @@ export function registerCursorRuntimeControls(pi) {
|
|
|
418
424
|
return;
|
|
419
425
|
}
|
|
420
426
|
try {
|
|
421
|
-
const result = await refreshSessionCursorAgentConfig();
|
|
427
|
+
const result = await refreshSessionCursorAgentConfig(getCursorSessionScopeSnapshot(pi).scopeKey);
|
|
422
428
|
const messages = {
|
|
423
429
|
reloaded: "Cursor SDK agent config refreshed.",
|
|
424
430
|
"no-agent": "No Cursor SDK agent exists yet; config will load on the next Cursor run.",
|
|
@@ -438,7 +444,7 @@ export function registerCursorRuntimeControls(pi) {
|
|
|
438
444
|
const usage = "Usage: /cursor-mode agent|plan";
|
|
439
445
|
const mode = parseCursorAgentMode(args);
|
|
440
446
|
if (!args.trim()) {
|
|
441
|
-
const modeResolution = resolveCursorAgentMode();
|
|
447
|
+
const modeResolution = resolveCursorAgentMode(cursorSettingsScopeForContext(ctx));
|
|
442
448
|
if (modeResolution.kind === "invalid") {
|
|
443
449
|
ctx.ui.notify(`${modeResolution.message} ${usage}`, "error");
|
|
444
450
|
}
|
|
@@ -451,15 +457,17 @@ export function registerCursorRuntimeControls(pi) {
|
|
|
451
457
|
ctx.ui.notify(`Invalid Cursor mode "${args.trim()}". ${usage}`, "error");
|
|
452
458
|
return;
|
|
453
459
|
}
|
|
460
|
+
const cli = getCursorSessionSettings(cursorSettingsScopeForContext(ctx)).cli;
|
|
461
|
+
const cliCursorModeState = cli.mode;
|
|
454
462
|
if (cliCursorModeState.kind === "valid") {
|
|
455
463
|
ctx.ui.notify(`Cursor mode is forced to ${cliCursorModeState.mode} by --cursor-mode`, "info");
|
|
456
464
|
return;
|
|
457
465
|
}
|
|
458
466
|
const clearedInvalidCliMode = cliCursorModeState.kind === "invalid";
|
|
459
467
|
try {
|
|
460
|
-
persistCursorModePreference(pi, mode);
|
|
468
|
+
persistCursorModePreference(pi, mode, cursorSettingsScopeForContext(ctx));
|
|
461
469
|
if (clearedInvalidCliMode)
|
|
462
|
-
|
|
470
|
+
cli.mode = { kind: "unset" };
|
|
463
471
|
}
|
|
464
472
|
catch (error) {
|
|
465
473
|
updateCursorStatus(ctx);
|
|
@@ -481,11 +489,13 @@ export function registerCursorRuntimeControls(pi) {
|
|
|
481
489
|
authoritativeGlobalFastPreferenceIds.clear();
|
|
482
490
|
setCursorHttp1GlobalPreferenceAuthoritative(false);
|
|
483
491
|
globalFastPreferences = loadGlobalFastPreferences();
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
492
|
+
const scopeKey = cursorSettingsScopeForContext(ctx);
|
|
493
|
+
const cli = getCursorSessionSettings(scopeKey).cli;
|
|
494
|
+
cli.forceFast = pi.getFlag("cursor-fast") === true;
|
|
495
|
+
cli.forceNoFast = pi.getFlag("cursor-no-fast") === true;
|
|
496
|
+
restoreCursorCliState(pi, scopeKey);
|
|
487
497
|
restoreSessionCursorPreferences(ctx);
|
|
488
|
-
restoreCliCursorMode(pi.getFlag("cursor-mode"));
|
|
498
|
+
restoreCliCursorMode(pi.getFlag("cursor-mode"), scopeKey);
|
|
489
499
|
},
|
|
490
500
|
sync: (ctx) => {
|
|
491
501
|
if (isCursorModel(ctx.model))
|
|
@@ -495,10 +505,10 @@ export function registerCursorRuntimeControls(pi) {
|
|
|
495
505
|
});
|
|
496
506
|
}
|
|
497
507
|
function resetCursorModeStateForTests() {
|
|
498
|
-
|
|
508
|
+
resetCursorSessionSettingsForTests();
|
|
499
509
|
setCursorHttp1GlobalPreferenceAuthoritative(false);
|
|
500
510
|
setStoredCursorHttp1Enabled(undefined);
|
|
501
|
-
|
|
511
|
+
getCursorSessionSettings().cli.mode = { kind: "unset" };
|
|
502
512
|
resetCursorRuntimeStateForTests();
|
|
503
513
|
invalidCursorModeNotifiedSessionScopeKeys.clear();
|
|
504
514
|
authoritativeGlobalFastPreferenceIds.clear();
|
|
@@ -511,9 +521,9 @@ export const __testUtils = {
|
|
|
511
521
|
DEFAULT_CURSOR_AGENT_MODE,
|
|
512
522
|
getConfigPath,
|
|
513
523
|
loadGlobalFastPreferences,
|
|
514
|
-
sessionFastPreferences,
|
|
515
|
-
getSessionCursorAgentMode: () =>
|
|
516
|
-
getCliCursorAgentMode: () => (
|
|
517
|
-
getCliCursorModeState: () =>
|
|
524
|
+
get sessionFastPreferences() { return getCursorSessionSettings().fast; },
|
|
525
|
+
getSessionCursorAgentMode: () => getCursorSessionSettings().mode,
|
|
526
|
+
getCliCursorAgentMode: () => { const mode = getCursorSessionSettings().cli.mode; return mode.kind === "valid" ? mode.mode : undefined; },
|
|
527
|
+
getCliCursorModeState: () => getCursorSessionSettings().cli.mode,
|
|
518
528
|
resetCursorModeStateForTests,
|
|
519
529
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { calculateCost } from "@earendil-works/pi-ai";
|
|
1
2
|
import { CURSOR_APPROX_CHARS_PER_TOKEN, CURSOR_IMAGE_TOKEN_ESTIMATE, estimateCursorContextTokens, estimateCursorTextTokens, } from "./context.js";
|
|
2
3
|
import { asRecord, getNumber } from "./cursor-record-utils.js";
|
|
3
4
|
function getPromptInputTokenBudget(model) {
|
|
@@ -81,86 +82,28 @@ export function applyCursorSdkUsage(partial, turnUsage) {
|
|
|
81
82
|
partial.usage.output = turnUsage.outputTokens;
|
|
82
83
|
partial.usage.cacheRead = turnUsage.cacheReadTokens;
|
|
83
84
|
partial.usage.cacheWrite = turnUsage.cacheWriteTokens;
|
|
84
|
-
//
|
|
85
|
+
// Full prompt + output equals the sum of Pi's disjoint components.
|
|
85
86
|
partial.usage.totalTokens = turnUsage.inputTokens + turnUsage.outputTokens;
|
|
86
87
|
}
|
|
87
|
-
function
|
|
88
|
-
return assistant.api === model.api && assistant.provider === model.provider && assistant.model === model.id;
|
|
89
|
-
}
|
|
90
|
-
function getLatestCompactionBoundary(context) {
|
|
91
|
-
for (let index = context.messages.length - 1; index >= 0; index -= 1) {
|
|
92
|
-
const message = context.messages[index];
|
|
93
|
-
if (message.role !== "compactionSummary")
|
|
94
|
-
continue;
|
|
95
|
-
const tokensBefore = message.tokensBefore;
|
|
96
|
-
return {
|
|
97
|
-
index,
|
|
98
|
-
tokensBefore: Number.isFinite(tokensBefore) && tokensBefore !== undefined && tokensBefore > 0 ? Math.floor(tokensBefore) : undefined,
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
return undefined;
|
|
102
|
-
}
|
|
103
|
-
function getLastAcceptedContextOccupancy(context, model) {
|
|
104
|
-
const boundary = getLatestCompactionBoundary(context);
|
|
105
|
-
for (let index = context.messages.length - 1; index >= 0; index -= 1) {
|
|
106
|
-
if (boundary && index < boundary.index)
|
|
107
|
-
break;
|
|
108
|
-
const message = context.messages[index];
|
|
109
|
-
if (message.role !== "assistant" || !("usage" in message))
|
|
110
|
-
continue;
|
|
111
|
-
const assistant = message;
|
|
112
|
-
if (assistant.stopReason === "aborted" || assistant.stopReason === "error" || !assistant.usage)
|
|
113
|
-
continue;
|
|
114
|
-
if (!isCompatibleCursorAssistantMeasurement(assistant, model))
|
|
115
|
-
continue;
|
|
116
|
-
const { usage } = assistant;
|
|
117
|
-
const total = usage.totalTokens || usage.input + usage.output + usage.cacheRead + usage.cacheWrite;
|
|
118
|
-
if (!Number.isFinite(total) || total <= 0 || total > model.contextWindow)
|
|
119
|
-
continue;
|
|
120
|
-
if (boundary?.tokensBefore !== undefined && total >= boundary.tokensBefore)
|
|
121
|
-
continue;
|
|
122
|
-
return total;
|
|
123
|
-
}
|
|
124
|
-
return 0;
|
|
125
|
-
}
|
|
126
|
-
export function applyCursorApproximateUsage(partial, model, context, sessionInputTokens) {
|
|
88
|
+
export function applyCursorApproximateUsage(partial, model, context, sessionInputTokens, occupancyFloor) {
|
|
127
89
|
const outputTokens = estimateCursorAssistantSessionOutputTokens(partial);
|
|
128
|
-
|
|
90
|
+
const floor = occupancyFloor !== undefined && Number.isFinite(occupancyFloor) && occupancyFloor > 0 && occupancyFloor <= model.contextWindow ? occupancyFloor : 0;
|
|
91
|
+
const totalTokens = Math.max(Math.max(0, sessionInputTokens) + outputTokens, estimateCursorContextTotalTokens(partial, model, context), floor);
|
|
92
|
+
// Estimated prompt components describe the same occupancy as totalTokens.
|
|
93
|
+
partial.usage.input = totalTokens - outputTokens;
|
|
129
94
|
partial.usage.output = outputTokens;
|
|
130
95
|
partial.usage.cacheRead = 0;
|
|
131
96
|
partial.usage.cacheWrite = 0;
|
|
132
|
-
|
|
133
|
-
partial.usage.totalTokens = Math.max(partial.usage.input + partial.usage.output, estimateCursorContextTotalTokens(partial, model, context), getLastAcceptedContextOccupancy(context, model));
|
|
134
|
-
}
|
|
135
|
-
function applyCursorOccupancyEstimate(partial, model, context) {
|
|
136
|
-
partial.usage.totalTokens = Math.max(estimateCursorContextTotalTokens(partial, model, context), getLastAcceptedContextOccupancy(context, model));
|
|
137
|
-
}
|
|
138
|
-
function isCurrentLocalOccupancy(turn, model, context) {
|
|
139
|
-
if (!isCursorSdkUsageSafeForPiMessage(turn, model))
|
|
140
|
-
return false;
|
|
141
|
-
const tokensBefore = getLatestCompactionBoundary(context)?.tokensBefore;
|
|
142
|
-
return tokensBefore === undefined || turn.inputTokens + turn.outputTokens < tokensBefore;
|
|
143
|
-
}
|
|
144
|
-
function applyResolvedCursorOccupancy(partial, model, context, localTurn) {
|
|
145
|
-
if (localTurn && isCurrentLocalOccupancy(localTurn, model, context)) {
|
|
146
|
-
partial.usage.totalTokens = localTurn.inputTokens + localTurn.outputTokens;
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
applyCursorOccupancyEstimate(partial, model, context);
|
|
97
|
+
partial.usage.totalTokens = totalTokens;
|
|
150
98
|
}
|
|
151
99
|
export function applyCursorUsage(partial, model, context, sessionInputTokens, sdkUsage) {
|
|
152
|
-
const billed = sdkUsage?.billed;
|
|
153
100
|
const localTurn = sdkUsage?.runtime === "local" ? sdkUsage.turn : undefined;
|
|
154
|
-
if (billed && isCursorSdkUsagePartitionSafe(billed, model)) {
|
|
155
|
-
applyCursorSdkUsage(partial, billed);
|
|
156
|
-
applyResolvedCursorOccupancy(partial, model, context, localTurn);
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
// Only local raw turn-ended usage has a captured full-prompt/cache-partition occupancy contract.
|
|
160
101
|
if (localTurn && isCursorSdkUsageSafeForPiMessage(localTurn, model)) {
|
|
102
|
+
// Fresh LOCAL occupancy supersedes any previous request-provenance floor.
|
|
161
103
|
applyCursorSdkUsage(partial, localTurn);
|
|
162
|
-
applyResolvedCursorOccupancy(partial, model, context, localTurn);
|
|
163
|
-
return;
|
|
164
104
|
}
|
|
165
|
-
|
|
105
|
+
else {
|
|
106
|
+
applyCursorApproximateUsage(partial, model, context, sessionInputTokens, sdkUsage?.occupancyFloor);
|
|
107
|
+
}
|
|
108
|
+
calculateCost(model, partial.usage);
|
|
166
109
|
}
|