ocx-cursor 1.0.0 → 1.0.1
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/package.json +1 -1
- package/src/catalog.mjs +1 -0
- package/src/cursor-patch.mjs +53 -9
package/package.json
CHANGED
package/src/catalog.mjs
CHANGED
|
@@ -83,6 +83,7 @@ export function normalizeActiveCatalog(configured, active, fastModelIds = new Se
|
|
|
83
83
|
if (typeof model?.id !== "string" || model.owned_by === "opencodex") continue;
|
|
84
84
|
const configuredModel = configuredById.get(model.id);
|
|
85
85
|
const provider = model.id.includes("/") ? model.id.split("/", 1)[0] : String(model.owned_by || "openai").toLowerCase();
|
|
86
|
+
if (provider === "cursor") continue;
|
|
86
87
|
const inputModalities = Array.isArray(configuredModel?.inputModalities)
|
|
87
88
|
? configuredModel.inputModalities
|
|
88
89
|
: Array.isArray(model.capabilities?.input_modalities)
|
package/src/cursor-patch.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { chmod, copyFile, mkdir, readFile, rename, stat, writeFile } from "node:fs/promises";
|
|
3
|
-
import { basename, join } from "node:path";
|
|
3
|
+
import { basename, dirname, join } from "node:path";
|
|
4
4
|
import {
|
|
5
5
|
cursorBundleFiles,
|
|
6
6
|
cursorGlassWorkbenchFile,
|
|
@@ -10,8 +10,9 @@ import {
|
|
|
10
10
|
cursorWorkbenchFile,
|
|
11
11
|
} from "./paths.mjs";
|
|
12
12
|
|
|
13
|
-
export const cursorPatchMarker = "/*ocx-cursor-model-metadata-
|
|
13
|
+
export const cursorPatchMarker = "/*ocx-cursor-model-metadata-v6*/";
|
|
14
14
|
export const legacyCursorPatchMarker = "/*ocx-cursor-model-metadata*/";
|
|
15
|
+
export const cursorByokRoutingPatchMarker = "/*ocx-cursor-byok-model-routing-v1*/";
|
|
15
16
|
export const cursorLocalModeDisabled = "localMode:!1";
|
|
16
17
|
export const cursorLocalModeEnabled = "localMode:!0";
|
|
17
18
|
export const cursorLocalRuntimePatchMarker = "/*ocx-cursor-local-model-display-v2*/";
|
|
@@ -24,7 +25,8 @@ export function isCursorModelMetadataBundle(file) {
|
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
const catalogNormalization = /(?<normalization>\b(?<catalog>[A-Za-z_$][\w$]*)=\k<catalog>\.map\((?<item>[A-Za-z_$][\w$]*)=>(?<plain>[A-Za-z_$][\w$]*)\(\k<item>\)\)),(?=(?<batch>[A-Za-z_$][\w$]*)\(\(\)=>\{this\._reactiveStorageService\.setApplicationUserPersistentStorage\("availableDefaultModels2",\k<catalog>\))/g;
|
|
27
|
-
const previousCatalogInjection = /\/\*ocx-cursor-model-metadata(?:-v[
|
|
28
|
+
const previousCatalogInjection = /\/\*ocx-cursor-model-metadata(?:-v[2-5])?\*\/(?<catalog>[A-Za-z_$][\w$]*)=[\s\S]*?,(?=(?<batch>[A-Za-z_$][\w$]*)\(\(\)=>\{this\._reactiveStorageService\.setApplicationUserPersistentStorage\("availableDefaultModels2",\k<catalog>\))/g;
|
|
29
|
+
const byokModelRouting = /function (?<functionName>[A-Za-z_$][\w$]*)\((?<model>[A-Za-z_$][\w$]*),(?<settings>[A-Za-z_$][\w$]*)\)\{return (?<isClaude>[A-Za-z_$][\w$]*)\(\k<model>\)\?\k<settings>\.useClaudeKey\?"anthropic":void 0:(?<isGemini>[A-Za-z_$][\w$]*)\(\k<model>\)\?\k<settings>\.useGoogleKey\?"google":void 0:\k<settings>\.useOpenAIKey\?"openai":void 0\}/g;
|
|
28
30
|
const localModelConstructor = /function (?<functionName>[A-Za-z_$][\w$]*)\(e,t\)\{return new (?<modelType>[A-Za-z_$][\w$]*\.[A-Za-z_$][\w$]*)\(\{modelId:e,displayModelId:e,displayName:null!=t\?t:e,displayNameShort:null!=t\?t:e,aliases:\[\]\}\)\}/g;
|
|
29
31
|
const localRuntimeVisionCapability = /"boolean"==typeof (?<object>[A-Za-z_$][\w$]*)\.supports_vision\?\{supports_vision:\k<object>\.supports_vision\}:\{\}\)/g;
|
|
30
32
|
const localRuntimePickerInput = /toPickerInput\((?<model>[A-Za-z_$][\w$]*)\)\{var [A-Za-z_$][\w$]*;const [A-Za-z_$][\w$]*=this\.modelMetadataById\.get\(\k<model>\.modelId\),(?<capabilities>[A-Za-z_$][\w$]*)=.*?;return\{.*?supportsReasoning:[^,]+,supportsVision:/g;
|
|
@@ -32,6 +34,7 @@ const localRuntimeProviderPickerInput = /supportsReasoning:(?<reasoning>!0===\(n
|
|
|
32
34
|
const localRuntimeRequestParameters = /function\((?<payload>[A-Za-z_$][\w$]*),(?<model>[A-Za-z_$][\w$]*),(?<parameters>[A-Za-z_$][\w$]*),(?<apiType>[A-Za-z_$][\w$]*),(?<route>[A-Za-z_$][\w$]*),(?<extended>[A-Za-z_$][\w$]*)=!1\)\{(?=const [A-Za-z_$][\w$]*=function\([A-Za-z_$][\w$]*\)\{var [A-Za-z_$][\w$]*;const [A-Za-z_$][\w$]*=.*?\["reasoning","effort","thought_level"\]\.includes)/g;
|
|
33
35
|
const localRuntimeBuilderExport = /buildBottlerocketPickerModels:\(\)=>(?<builder>[A-Za-z_$][\w$]*)/g;
|
|
34
36
|
const transientPatchErrorCodes = new Set(["EACCES", "EBUSY", "EPERM"]);
|
|
37
|
+
const directoryPatchQueues = new Map();
|
|
35
38
|
|
|
36
39
|
function replaceSingleMatch(source, pattern, replacement, label) {
|
|
37
40
|
const matches = [...source.matchAll(pattern)];
|
|
@@ -41,13 +44,34 @@ function replaceSingleMatch(source, pattern, replacement, label) {
|
|
|
41
44
|
return `${source.slice(0, match.index)}${value}${source.slice(match.index + match[0].length)}`;
|
|
42
45
|
}
|
|
43
46
|
|
|
47
|
+
async function withWritableDirectory(file, callback) {
|
|
48
|
+
const directory = dirname(file);
|
|
49
|
+
const previous = directoryPatchQueues.get(directory) || Promise.resolve();
|
|
50
|
+
const current = previous.catch(() => {}).then(async () => {
|
|
51
|
+
const directoryMode = (await stat(directory)).mode & 0o777;
|
|
52
|
+
const restoreDirectoryMode = (directoryMode & 0o200) === 0;
|
|
53
|
+
if (restoreDirectoryMode) await chmod(directory, directoryMode | 0o200);
|
|
54
|
+
try {
|
|
55
|
+
return await callback();
|
|
56
|
+
} finally {
|
|
57
|
+
if (restoreDirectoryMode) await chmod(directory, directoryMode);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
directoryPatchQueues.set(directory, current);
|
|
61
|
+
try {
|
|
62
|
+
return await current;
|
|
63
|
+
} finally {
|
|
64
|
+
if (directoryPatchQueues.get(directory) === current) directoryPatchQueues.delete(directory);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
44
68
|
function metadataInjection(catalog) {
|
|
45
|
-
return `${cursorPatchMarker}${catalog}
|
|
69
|
+
return `${cursorPatchMarker}${catalog}=[...${catalog},...(this._reactiveStorageService.applicationUserPersistentStorage.availableDefaultModels2??[]).filter(ocxCursorStoredModel=>typeof ocxCursorStoredModel?.name==="string"&&ocxCursorStoredModel.name.startsWith("opencodex/")&&!${catalog}.some(ocxCursorFreshModel=>ocxCursorFreshModel?.name===ocxCursorStoredModel.name))].map(ocxCursorModel=>{if(!ocxCursorModel.name.startsWith("opencodex/"))return ocxCursorModel;const ocxCursorStored=(this._reactiveStorageService.applicationUserPersistentStorage.availableDefaultModels2??[]).find(ocxCursorCandidate=>ocxCursorCandidate?.name===ocxCursorModel.name);const ocxCursorDisplayWords={claude:"Claude",codex:"Codex",composer:"Composer",deepseek:"DeepSeek",fable:"Fable",fast:"Fast",flash:"Flash",gpt:"GPT",grok:"Grok",hy3:"HY3",kimi:"Kimi",luna:"Luna",max:"Max",mimo:"MiMo",mini:"Mini",opus:"Opus",pro:"Pro",qwen:"Qwen",sol:"Sol",sonnet:"Sonnet",spark:"Spark",terra:"Terra"};const ocxCursorDisplayName=ocxCursorStored?.clientDisplayName??((ocxCursorModel.name.startsWith("opencodex/cursor/")?"Cursor ":"")+ocxCursorModel.name.split("/").at(-1).split("-").map(ocxCursorWord=>{if(ocxCursorDisplayWords[ocxCursorWord])return ocxCursorDisplayWords[ocxCursorWord];const ocxCursorAttached=/^(qwen|kimi|gpt|claude|grok)(\\d+(?:\\.\\d+)*)$/.exec(ocxCursorWord);if(ocxCursorAttached)return ocxCursorDisplayWords[ocxCursorAttached[1]]+" "+ocxCursorAttached[2];if(/^v\\d/i.test(ocxCursorWord))return"V"+ocxCursorWord.slice(1);if(/^k\\d/i.test(ocxCursorWord))return"K"+ocxCursorWord.slice(1);if(/^\\d/.test(ocxCursorWord))return ocxCursorWord;return ocxCursorWord.slice(0,1).toUpperCase()+ocxCursorWord.slice(1)}).join(" "));const ocxCursorPrettyLabel=ocxCursorValue=>typeof ocxCursorValue==="string"?ocxCursorValue.split(ocxCursorModel.name).join(ocxCursorDisplayName):ocxCursorValue;const ocxCursorDefinitions=Array.isArray(ocxCursorModel.parameterDefinitions)&&ocxCursorModel.parameterDefinitions.length>0?ocxCursorModel.parameterDefinitions:ocxCursorStored?.parameterDefinitions??[];const ocxCursorVariantSource=Array.isArray(ocxCursorModel.variants)&&ocxCursorModel.variants.length>0?ocxCursorModel.variants:ocxCursorStored?.variants??[];const ocxCursorVariants=ocxCursorVariantSource.map(ocxCursorVariant=>({...ocxCursorVariant,displayName:ocxCursorPrettyLabel(ocxCursorVariant.displayName),displayNameOutsidePicker:ocxCursorPrettyLabel(ocxCursorVariant.displayNameOutsidePicker)}));const ocxCursorLegacySlugs=Array.isArray(ocxCursorModel.legacySlugs)&&ocxCursorModel.legacySlugs.length>0?ocxCursorModel.legacySlugs:ocxCursorStored?.legacySlugs??[];return{...ocxCursorModel,clientDisplayName:ocxCursorDisplayName,inputboxShortModelName:ocxCursorDisplayName,parameterDefinitions:ocxCursorDefinitions,variants:ocxCursorVariants,legacySlugs:ocxCursorLegacySlugs,supportsThinking:void 0!==ocxCursorModel.supportsThinking?ocxCursorModel.supportsThinking:ocxCursorStored?.supportsThinking}}),`;
|
|
46
70
|
}
|
|
47
71
|
|
|
48
|
-
|
|
72
|
+
function patchCursorModelMetadataSource(source) {
|
|
49
73
|
if (source.includes(cursorPatchMarker)) return { status: "already-patched", source };
|
|
50
|
-
if (source.includes(legacyCursorPatchMarker) || source.includes("/*ocx-cursor-model-metadata-v2*/") || source.includes("/*ocx-cursor-model-metadata-v3*/") || source.includes("/*ocx-cursor-model-metadata-v4*/")) {
|
|
74
|
+
if (source.includes(legacyCursorPatchMarker) || source.includes("/*ocx-cursor-model-metadata-v2*/") || source.includes("/*ocx-cursor-model-metadata-v3*/") || source.includes("/*ocx-cursor-model-metadata-v4*/") || source.includes("/*ocx-cursor-model-metadata-v5*/")) {
|
|
51
75
|
const matches = [...source.matchAll(previousCatalogInjection)];
|
|
52
76
|
if (matches.length !== 1) {
|
|
53
77
|
throw new Error(`Expected one legacy Cursor model metadata hook, found ${matches.length}`);
|
|
@@ -70,6 +94,24 @@ export function patchCursorWorkbenchSource(source) {
|
|
|
70
94
|
return { status: "patched", source: patched };
|
|
71
95
|
}
|
|
72
96
|
|
|
97
|
+
export function patchCursorByokModelRoutingSource(source) {
|
|
98
|
+
if (source.includes(cursorByokRoutingPatchMarker)) return { status: "already-patched", source };
|
|
99
|
+
const patched = replaceSingleMatch(source, byokModelRouting, (match) => {
|
|
100
|
+
const { functionName, model, settings, isClaude, isGemini } = match.groups;
|
|
101
|
+
return `function ${functionName}(${model},${settings}){${cursorByokRoutingPatchMarker}return ${isClaude}(${model})?${settings}.useClaudeKey?"anthropic":void 0:${isGemini}(${model})?${settings}.useGoogleKey?"google":void 0:${settings}.useOpenAIKey&&(${model}.startsWith("opencodex/")||${settings}.aiSettings?.userAddedModels?.includes(${model}))?"openai":void 0}`;
|
|
102
|
+
}, "BYOK model routing hook");
|
|
103
|
+
return { status: "patched", source: patched };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function patchCursorWorkbenchSource(source) {
|
|
107
|
+
const metadata = patchCursorModelMetadataSource(source);
|
|
108
|
+
const routing = patchCursorByokModelRoutingSource(metadata.source);
|
|
109
|
+
return {
|
|
110
|
+
status: metadata.status === "patched" || routing.status === "patched" ? "patched" : "already-patched",
|
|
111
|
+
source: routing.source,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
73
115
|
export function patchCursorLocalModeSource(source) {
|
|
74
116
|
const disabledCount = source.split(cursorLocalModeDisabled).length - 1;
|
|
75
117
|
if (disabledCount > 1) {
|
|
@@ -214,9 +256,11 @@ async function writePatchedFile(file, source, patched, backupDirectory) {
|
|
|
214
256
|
|
|
215
257
|
const mode = (await stat(file)).mode & 0o777;
|
|
216
258
|
const temporary = `${file}.ocx-cursor-${process.pid}`;
|
|
217
|
-
await
|
|
218
|
-
|
|
219
|
-
|
|
259
|
+
await withWritableDirectory(file, async () => {
|
|
260
|
+
await writeFile(temporary, patched.source, { mode });
|
|
261
|
+
await chmod(temporary, mode);
|
|
262
|
+
await rename(temporary, file);
|
|
263
|
+
});
|
|
220
264
|
return { status: patched.status, signature: await cursorWorkbenchSignature(file), backupPath };
|
|
221
265
|
}
|
|
222
266
|
|