cursor-openai-byok 1.0.2 → 1.1.4
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/README.md +5 -46
- package/TROUBLESHOOTING.md +7 -3
- package/lib/cli.js +358 -135
- package/lib/daemon-entry.js +30 -11
- package/lib/watcher-entry.js +1548 -0
- package/package.json +5 -6
package/lib/cli.js
CHANGED
|
@@ -109,16 +109,18 @@ function importCcursorConfig() {
|
|
|
109
109
|
function install(options) {
|
|
110
110
|
writeDefaultConfig();
|
|
111
111
|
const patch = patcher.installPatch(options);
|
|
112
|
-
const extension = service.installExtension();
|
|
112
|
+
const extension = service.installExtension(options);
|
|
113
|
+
const lifecycle = service.installLifecycle(options);
|
|
113
114
|
const storage = syncCursorStorage(listModels(loadConfig()));
|
|
114
|
-
print({ config: configPath(), patch, extension, storage });
|
|
115
|
+
print({ config: configPath(), patch, extension, lifecycle, storage });
|
|
115
116
|
}
|
|
116
117
|
|
|
117
118
|
function uninstall(options) {
|
|
118
119
|
const stopped = service.stopBackground();
|
|
119
|
-
const
|
|
120
|
+
const lifecycle = service.uninstallLifecycle();
|
|
121
|
+
const extension = service.uninstallExtension(options);
|
|
120
122
|
const patch = patcher.uninstallPatch(options);
|
|
121
|
-
print({ stopped, extension, patch });
|
|
123
|
+
print({ stopped, lifecycle, extension, patch });
|
|
122
124
|
}
|
|
123
125
|
|
|
124
126
|
async function doctor(options) {
|
|
@@ -142,19 +144,22 @@ async function doctor(options) {
|
|
|
142
144
|
|
|
143
145
|
try {
|
|
144
146
|
const st = patcher.status(options);
|
|
145
|
-
checks.push({ name: "cursor", ok: st.supportedVersion, ...st });
|
|
147
|
+
checks.push({ name: "cursor", ok: st.supportedVersion && st.patched, ...st });
|
|
146
148
|
} catch (err) {
|
|
147
149
|
checks.push({ name: "cursor", ok: false, error: err.message });
|
|
148
150
|
}
|
|
149
151
|
|
|
150
|
-
|
|
152
|
+
const extension = service.extensionStatus(options);
|
|
153
|
+
checks.push({ name: "extension", ok: extension.installed, ...extension });
|
|
154
|
+
const lifecycle = service.lifecycleStatus(options);
|
|
155
|
+
checks.push({ name: "lifecycle", ok: lifecycle.installed, ...lifecycle });
|
|
151
156
|
const runningPid = service.isRunning();
|
|
152
157
|
checks.push({
|
|
153
158
|
name: "daemon-process",
|
|
154
159
|
ok: true,
|
|
155
160
|
running: !!runningPid,
|
|
156
161
|
pid: runningPid || undefined,
|
|
157
|
-
note: runningPid ? "server is running" : "server is expected to run only while Cursor
|
|
162
|
+
note: runningPid ? "server is running" : "server is expected to run only while Cursor lifecycle is active",
|
|
158
163
|
});
|
|
159
164
|
|
|
160
165
|
try {
|
|
@@ -170,7 +175,7 @@ async function doctor(options) {
|
|
|
170
175
|
ok: !runningPid,
|
|
171
176
|
running: false,
|
|
172
177
|
error: err.message,
|
|
173
|
-
note: runningPid ? "server pid exists but health check failed" : "health is skipped when Cursor is
|
|
178
|
+
note: runningPid ? "server pid exists but health check failed" : "health is skipped when Cursor lifecycle is inactive",
|
|
174
179
|
});
|
|
175
180
|
}
|
|
176
181
|
|
|
@@ -214,6 +219,10 @@ function pidPath() {
|
|
|
214
219
|
return path.join(configDir(), "daemon.pid");
|
|
215
220
|
}
|
|
216
221
|
|
|
222
|
+
function watcherPidPath() {
|
|
223
|
+
return path.join(configDir(), "watcher.pid");
|
|
224
|
+
}
|
|
225
|
+
|
|
217
226
|
function runtimePath(name) {
|
|
218
227
|
return path.join(configDir(), name);
|
|
219
228
|
}
|
|
@@ -245,6 +254,7 @@ module.exports = {
|
|
|
245
254
|
configPath,
|
|
246
255
|
logPath,
|
|
247
256
|
pidPath,
|
|
257
|
+
watcherPidPath,
|
|
248
258
|
runtimePath,
|
|
249
259
|
platformName,
|
|
250
260
|
cursorExtensionsDir,
|
|
@@ -256,16 +266,15 @@ module.exports = {
|
|
|
256
266
|
"package.json": function(module, exports, require, __filename, __dirname) {
|
|
257
267
|
module.exports = {
|
|
258
268
|
"name": "cursor-openai-byok",
|
|
259
|
-
"version": "1.
|
|
269
|
+
"version": "1.1.4",
|
|
260
270
|
"description": "Local BYOK bridge for Cursor using OpenAI-compatible providers.",
|
|
261
271
|
"displayName": "Cursor OpenAI BYOK",
|
|
262
272
|
"publisher": "cursor-openai-byok",
|
|
263
273
|
"type": "commonjs",
|
|
274
|
+
"extensionKind": [
|
|
275
|
+
"ui"
|
|
276
|
+
],
|
|
264
277
|
"main": "./lib/extension.js",
|
|
265
|
-
"repository": {
|
|
266
|
-
"type": "git",
|
|
267
|
-
"url": "git+https://git.3weijia.com/base/ai/cursor-openai-byok.git"
|
|
268
|
-
},
|
|
269
278
|
"keywords": [
|
|
270
279
|
"cursor",
|
|
271
280
|
"byok",
|
|
@@ -289,7 +298,7 @@ module.exports = {
|
|
|
289
298
|
"registry": "https://registry.npmjs.org/"
|
|
290
299
|
},
|
|
291
300
|
"activationEvents": [
|
|
292
|
-
"
|
|
301
|
+
"onStartupFinished"
|
|
293
302
|
],
|
|
294
303
|
"contributes": {
|
|
295
304
|
"commands": [
|
|
@@ -458,12 +467,17 @@ function listModels(config) {
|
|
|
458
467
|
return models;
|
|
459
468
|
}
|
|
460
469
|
|
|
461
|
-
function resolveModel(config, requestedModel) {
|
|
470
|
+
function resolveModel(config, requestedModel, options = {}) {
|
|
462
471
|
const models = listModels(config);
|
|
463
472
|
const fallback = config.defaults && config.defaults.model;
|
|
464
473
|
const target = requestedModel || fallback || (models[0] && models[0].displayName);
|
|
465
474
|
const found = models.find((m) => m.displayName === target || m.apiModel === target);
|
|
466
475
|
if (!found) {
|
|
476
|
+
if (options.allowUnconfigured && typeof target === "string" && target) {
|
|
477
|
+
const base = models.find((m) => m.displayName === fallback || m.apiModel === fallback) || models[0];
|
|
478
|
+
const provider = base && config.providers.find((p) => p.id === base.providerId);
|
|
479
|
+
if (provider) return { provider, model: { ...base, displayName: target, apiModel: target } };
|
|
480
|
+
}
|
|
467
481
|
throw new Error(`Model ${target || "<empty>"} is not configured`);
|
|
468
482
|
}
|
|
469
483
|
const provider = config.providers.find((p) => p.id === found.providerId);
|
|
@@ -560,7 +574,7 @@ function toCursorModel(model) {
|
|
|
560
574
|
vendorName: providerName,
|
|
561
575
|
vendor: { id: 9000, displayName: providerName },
|
|
562
576
|
tagline: "OpenAI-compatible BYOK",
|
|
563
|
-
visibleInRoutedModelView:
|
|
577
|
+
visibleInRoutedModelView: false,
|
|
564
578
|
isUserAdded: true,
|
|
565
579
|
byokModel: true,
|
|
566
580
|
};
|
|
@@ -698,10 +712,13 @@ module.exports = { log, tailLog, redactText };
|
|
|
698
712
|
"use strict";
|
|
699
713
|
|
|
700
714
|
const fs = require("fs");
|
|
715
|
+
const os = require("os");
|
|
701
716
|
const path = require("path");
|
|
702
717
|
const childProcess = require("child_process");
|
|
718
|
+
const { findCursorAppRoot, readCursorInfo } = require("../patcher/cursor-paths");
|
|
703
719
|
const {
|
|
704
720
|
pidPath,
|
|
721
|
+
watcherPidPath,
|
|
705
722
|
logPath,
|
|
706
723
|
installedExtensionDir,
|
|
707
724
|
cursorExtensionsDir,
|
|
@@ -759,7 +776,7 @@ function stopBackground() {
|
|
|
759
776
|
return { stopped: true, pid };
|
|
760
777
|
}
|
|
761
778
|
|
|
762
|
-
function installExtension() {
|
|
779
|
+
function installExtension(options = {}) {
|
|
763
780
|
const sourceRoot = path.resolve(__dirname, "..", "..");
|
|
764
781
|
const target = installedExtensionDir();
|
|
765
782
|
ensureDir(cursorExtensionsDir());
|
|
@@ -773,7 +790,7 @@ function installExtension() {
|
|
|
773
790
|
return { installed: true, extensionDir: target };
|
|
774
791
|
}
|
|
775
792
|
|
|
776
|
-
function uninstallExtension() {
|
|
793
|
+
function uninstallExtension(options = {}) {
|
|
777
794
|
const target = installedExtensionDir();
|
|
778
795
|
removeInstalledExtensionVersions();
|
|
779
796
|
removeFromCursorExtensionIndex();
|
|
@@ -781,9 +798,97 @@ function uninstallExtension() {
|
|
|
781
798
|
return { uninstalled: true, extensionDir: target };
|
|
782
799
|
}
|
|
783
800
|
|
|
784
|
-
function extensionStatus() {
|
|
801
|
+
function extensionStatus(options = {}) {
|
|
785
802
|
const target = installedExtensionDir();
|
|
786
|
-
return { installed: fs.existsSync(path.join(target, "extension", "extension.js")), extensionDir: target };
|
|
803
|
+
return { installed: fs.existsSync(path.join(target, "lib", "extension.js")) || fs.existsSync(path.join(target, "extension", "extension.js")), extensionDir: target };
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
function installLifecycle(options = {}) {
|
|
807
|
+
const appRoot = findCursorAppRoot(options.appRoot);
|
|
808
|
+
if (!/^3\.11\./.test(readCursorInfo(appRoot).version)) return { installed: false, required: false };
|
|
809
|
+
if (process.platform === "darwin") return installLaunchAgent();
|
|
810
|
+
if (process.platform === "win32") return installWindowsWatcher();
|
|
811
|
+
return { installed: false, required: true, error: `Unsupported platform ${process.platform}` };
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
function uninstallLifecycle() {
|
|
815
|
+
if (process.platform === "darwin") {
|
|
816
|
+
const plist = launchAgentPath();
|
|
817
|
+
childProcess.spawnSync("launchctl", ["bootout", `gui/${process.getuid()}`, plist], { encoding: "utf8" });
|
|
818
|
+
fs.rmSync(plist, { force: true });
|
|
819
|
+
} else if (process.platform === "win32") {
|
|
820
|
+
childProcess.spawnSync("reg.exe", ["delete", "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", "/v", "CursorOpenAIByok", "/f"], { encoding: "utf8" });
|
|
821
|
+
}
|
|
822
|
+
stopWatcherProcess();
|
|
823
|
+
return { uninstalled: true };
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
function lifecycleStatus(options = {}) {
|
|
827
|
+
try {
|
|
828
|
+
const appRoot = findCursorAppRoot(options.appRoot);
|
|
829
|
+
if (!/^3\.11\./.test(readCursorInfo(appRoot).version)) return { installed: true, required: false, mode: "extension" };
|
|
830
|
+
} catch {}
|
|
831
|
+
if (process.platform === "darwin") return { installed: fs.existsSync(launchAgentPath()), required: true, mode: "launch-agent" };
|
|
832
|
+
if (process.platform === "win32") return { installed: !!watcherProcess(), required: true, mode: "startup-watcher" };
|
|
833
|
+
return { installed: false, required: true, mode: "unsupported" };
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
function installLaunchAgent() {
|
|
837
|
+
ensureConfigDir();
|
|
838
|
+
const plist = launchAgentPath();
|
|
839
|
+
const watcher = path.join(installedExtensionDir(), "lib", "watcher-entry.js");
|
|
840
|
+
fs.mkdirSync(path.dirname(plist), { recursive: true });
|
|
841
|
+
fs.writeFileSync(plist, launchAgentPlist(watcher));
|
|
842
|
+
childProcess.spawnSync("launchctl", ["bootout", `gui/${process.getuid()}`, plist], { encoding: "utf8" });
|
|
843
|
+
const result = childProcess.spawnSync("launchctl", ["bootstrap", `gui/${process.getuid()}`, plist], { encoding: "utf8" });
|
|
844
|
+
if (result.status !== 0) throw new Error(result.stderr || result.stdout || "Failed to install lifecycle LaunchAgent");
|
|
845
|
+
return { installed: true, required: true, mode: "launch-agent", file: plist };
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
function installWindowsWatcher() {
|
|
849
|
+
const watcher = path.join(installedExtensionDir(), "lib", "watcher-entry.js");
|
|
850
|
+
const command = `\"${nodeBin()}\" \"${watcher}\"`;
|
|
851
|
+
const result = childProcess.spawnSync("reg.exe", ["add", "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", "/v", "CursorOpenAIByok", "/t", "REG_SZ", "/d", command, "/f"], { encoding: "utf8" });
|
|
852
|
+
if (result.status !== 0) throw new Error(result.stderr || result.stdout || "Failed to install lifecycle startup entry");
|
|
853
|
+
childProcess.spawn(nodeBin(), [watcher], { detached: true, stdio: "ignore" }).unref();
|
|
854
|
+
return { installed: true, required: true, mode: "startup-watcher" };
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
function launchAgentPath() {
|
|
858
|
+
return path.join(os.homedir(), "Library", "LaunchAgents", "cursor-openai-byok.lifecycle.plist");
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
function launchAgentPlist(watcher) {
|
|
862
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
863
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
864
|
+
<plist version="1.0"><dict>
|
|
865
|
+
<key>Label</key><string>cursor-openai-byok.lifecycle</string>
|
|
866
|
+
<key>ProgramArguments</key><array><string>${escapeXml(nodeBin())}</string><string>${escapeXml(watcher)}</string></array>
|
|
867
|
+
<key>RunAtLoad</key><true/><key>KeepAlive</key><true/>
|
|
868
|
+
<key>StandardOutPath</key><string>${escapeXml(logPath())}</string>
|
|
869
|
+
<key>StandardErrorPath</key><string>${escapeXml(logPath())}</string>
|
|
870
|
+
</dict></plist>
|
|
871
|
+
`;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
function escapeXml(value) {
|
|
875
|
+
return String(value).replace(/[&<>"']/g, (ch) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[ch]);
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
function watcherProcess() {
|
|
879
|
+
try {
|
|
880
|
+
const pid = Number(fs.readFileSync(watcherPidPath(), "utf8"));
|
|
881
|
+
process.kill(pid, 0);
|
|
882
|
+
return pid;
|
|
883
|
+
} catch {
|
|
884
|
+
return false;
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
function stopWatcherProcess() {
|
|
889
|
+
const pid = watcherProcess();
|
|
890
|
+
if (pid) try { process.kill(pid, "SIGTERM"); } catch {}
|
|
891
|
+
try { fs.unlinkSync(watcherPidPath()); } catch {}
|
|
787
892
|
}
|
|
788
893
|
|
|
789
894
|
function copyTree(src, dst, options = {}) {
|
|
@@ -830,6 +935,16 @@ function writeCursorExtensionIndex(target) {
|
|
|
830
935
|
scheme: "file",
|
|
831
936
|
},
|
|
832
937
|
relativeLocation: path.basename(target),
|
|
938
|
+
metadata: {
|
|
939
|
+
isApplicationScoped: true,
|
|
940
|
+
installedTimestamp: Date.now(),
|
|
941
|
+
source: "vsix",
|
|
942
|
+
targetPlatform: "undefined",
|
|
943
|
+
updated: false,
|
|
944
|
+
private: false,
|
|
945
|
+
isPreReleaseVersion: false,
|
|
946
|
+
hasPreReleaseVersion: false,
|
|
947
|
+
},
|
|
833
948
|
});
|
|
834
949
|
fs.writeFileSync(file, JSON.stringify(entries, null, 2));
|
|
835
950
|
}
|
|
@@ -879,6 +994,84 @@ module.exports = {
|
|
|
879
994
|
installExtension,
|
|
880
995
|
uninstallExtension,
|
|
881
996
|
extensionStatus,
|
|
997
|
+
installLifecycle,
|
|
998
|
+
uninstallLifecycle,
|
|
999
|
+
lifecycleStatus,
|
|
1000
|
+
watcherProcess,
|
|
1001
|
+
};
|
|
1002
|
+
|
|
1003
|
+
},
|
|
1004
|
+
"src/patcher/cursor-paths.js": function(module, exports, require, __filename, __dirname) {
|
|
1005
|
+
"use strict";
|
|
1006
|
+
|
|
1007
|
+
const fs = require("fs");
|
|
1008
|
+
const path = require("path");
|
|
1009
|
+
const os = require("os");
|
|
1010
|
+
|
|
1011
|
+
function candidateCursorAppRoots() {
|
|
1012
|
+
if (process.platform === "darwin") {
|
|
1013
|
+
return [
|
|
1014
|
+
"/Applications/Cursor.app/Contents/Resources/app",
|
|
1015
|
+
path.join(os.homedir(), "Applications/Cursor.app/Contents/Resources/app"),
|
|
1016
|
+
];
|
|
1017
|
+
}
|
|
1018
|
+
if (process.platform === "win32") {
|
|
1019
|
+
const local = process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local");
|
|
1020
|
+
return [
|
|
1021
|
+
path.join(local, "Programs", "Cursor", "resources", "app"),
|
|
1022
|
+
path.join(local, "Cursor", "resources", "app"),
|
|
1023
|
+
];
|
|
1024
|
+
}
|
|
1025
|
+
return [];
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
function findCursorAppRoot(explicitRoot) {
|
|
1029
|
+
const roots = explicitRoot ? [explicitRoot] : candidateCursorAppRoots();
|
|
1030
|
+
for (const root of roots) {
|
|
1031
|
+
if (workbenchPaths(root).length > 0) return root;
|
|
1032
|
+
}
|
|
1033
|
+
throw new Error(`Cursor app root not found. Checked: ${roots.join(", ")}`);
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
function workbenchPath(appRoot) {
|
|
1037
|
+
return path.join(appRoot, "out", "vs", "workbench", "workbench.desktop.main.js");
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
function workbenchPaths(appRoot) {
|
|
1041
|
+
const dir = path.join(appRoot, "out", "vs", "workbench");
|
|
1042
|
+
return ["workbench.desktop.main.js", "workbench.glass.main.js"]
|
|
1043
|
+
.map((name) => path.join(dir, name))
|
|
1044
|
+
.filter((file) => fs.existsSync(file));
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
function productJsonPath(appRoot) {
|
|
1048
|
+
return path.join(appRoot, "product.json");
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
function packageJsonPath(appRoot) {
|
|
1052
|
+
return path.join(appRoot, "package.json");
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
function readCursorInfo(appRoot) {
|
|
1056
|
+
const productPath = productJsonPath(appRoot);
|
|
1057
|
+
const packagePath = packageJsonPath(appRoot);
|
|
1058
|
+
const product = fs.existsSync(productPath) ? JSON.parse(fs.readFileSync(productPath, "utf8")) : {};
|
|
1059
|
+
const pkg = fs.existsSync(packagePath) ? JSON.parse(fs.readFileSync(packagePath, "utf8")) : {};
|
|
1060
|
+
return {
|
|
1061
|
+
appRoot,
|
|
1062
|
+
version: product.version || pkg.version || "unknown",
|
|
1063
|
+
commit: product.commit || "unknown",
|
|
1064
|
+
workbenchPath: workbenchPath(appRoot),
|
|
1065
|
+
workbenchPaths: workbenchPaths(appRoot),
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
module.exports = {
|
|
1070
|
+
candidateCursorAppRoots,
|
|
1071
|
+
findCursorAppRoot,
|
|
1072
|
+
workbenchPath,
|
|
1073
|
+
workbenchPaths,
|
|
1074
|
+
readCursorInfo,
|
|
882
1075
|
};
|
|
883
1076
|
|
|
884
1077
|
},
|
|
@@ -983,9 +1176,9 @@ async function sendNdjson(res, iterator) {
|
|
|
983
1176
|
res.end();
|
|
984
1177
|
}
|
|
985
1178
|
|
|
986
|
-
async function* runCursorAgentLoop(config, payload) {
|
|
1179
|
+
async function* runCursorAgentLoop(config, payload, customModels = new Set()) {
|
|
987
1180
|
const requestedModel = payload.model || payload.modelName || payload?.runRequest?.requestedModel?.modelId;
|
|
988
|
-
const route = resolveModel(config, requestedModel);
|
|
1181
|
+
const route = resolveModel(config, requestedModel, { allowUnconfigured: customModels.has(requestedModel) });
|
|
989
1182
|
const openaiBody = cursorPayloadToOpenAI(payload, route);
|
|
990
1183
|
log("cursor-run", {
|
|
991
1184
|
model: route.model.displayName,
|
|
@@ -1026,6 +1219,7 @@ async function* runCursorAgentLoop(config, payload) {
|
|
|
1026
1219
|
}
|
|
1027
1220
|
|
|
1028
1221
|
function createServer() {
|
|
1222
|
+
const customModels = new Set();
|
|
1029
1223
|
return http.createServer(async (req, res) => {
|
|
1030
1224
|
try {
|
|
1031
1225
|
if (req.method === "OPTIONS") return sendJson(res, 204, {});
|
|
@@ -1047,16 +1241,25 @@ function createServer() {
|
|
|
1047
1241
|
if (req.method === "GET" && url.pathname === "/cursor/v1/should-handle") {
|
|
1048
1242
|
const model = url.searchParams.get("model");
|
|
1049
1243
|
try {
|
|
1050
|
-
const route = resolveModel(config, model);
|
|
1244
|
+
const route = resolveModel(config, model, { allowUnconfigured: customModels.has(model) });
|
|
1051
1245
|
return sendJson(res, 200, { ok: true, handle: true, model: route.model });
|
|
1052
1246
|
} catch {
|
|
1053
1247
|
return sendJson(res, 200, { ok: true, handle: false });
|
|
1054
1248
|
}
|
|
1055
1249
|
}
|
|
1056
1250
|
|
|
1251
|
+
if (req.method === "POST" && url.pathname === "/cursor/v1/custom-models") {
|
|
1252
|
+
const payload = await readJson(req);
|
|
1253
|
+
customModels.clear();
|
|
1254
|
+
for (const model of payload.models || []) {
|
|
1255
|
+
if (typeof model === "string" && model.length <= 128 && !/[\x00-\x1f\x7f]/.test(model)) customModels.add(model);
|
|
1256
|
+
}
|
|
1257
|
+
return sendJson(res, 200, { ok: true, models: [...customModels] });
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1057
1260
|
if (req.method === "POST" && url.pathname === "/cursor/v1/run-agent-loop") {
|
|
1058
1261
|
const payload = await readJson(req);
|
|
1059
|
-
return sendNdjson(res, runCursorAgentLoop(config, payload));
|
|
1262
|
+
return sendNdjson(res, runCursorAgentLoop(config, payload, customModels));
|
|
1060
1263
|
}
|
|
1061
1264
|
|
|
1062
1265
|
if (req.method === "POST" && url.pathname === "/cursor/v1/tool-result") {
|
|
@@ -1631,20 +1834,34 @@ const { MARKER, LEGACY_MARKER, helperSnippet, agentLoopSnippet } = require("./te
|
|
|
1631
1834
|
|
|
1632
1835
|
const RUN_LOOP_NEEDLE = `_e=SC(n.withName("AgentCompatService.agentClientService.run")),oe=new Q$t(this.instantiationService,z,re,f,ue);`;
|
|
1633
1836
|
const CONNECT_HELPER_NEEDLE = `function E5_(n,e){return vp1(n,t=>{switch(t.kind){case rn.Unary:return wp1(e,n,t);case rn.ServerStreaming:return Sp1(e,n,t);case rn.ClientStreaming:return kp1(e,n,t);case rn.BiDiStreaming:return Cp1(e,n,t);default:return null}})}`;
|
|
1634
|
-
const UNARY_NEEDLE = `const s=await n.unary(e,t,r?.signal,r?.timeoutMs,r?.headers,i,r?.contextValues);return r?.onHeader?.(s.header),r?.onTrailer?.(s.trailer),s.message`;
|
|
1635
1837
|
const MODEL_ENRICH_NEEDLE = `s=XI_(s,a.models);`;
|
|
1636
1838
|
|
|
1637
1839
|
function status(options = {}) {
|
|
1638
1840
|
const appRoot = findCursorAppRoot(options.appRoot);
|
|
1639
1841
|
const info = readCursorInfo(appRoot);
|
|
1640
|
-
const
|
|
1842
|
+
const bundles = info.workbenchPaths.map((workbenchPath) => bundleStatus(workbenchPath));
|
|
1641
1843
|
return {
|
|
1642
1844
|
...info,
|
|
1643
1845
|
supportedVersion: isSupportedVersion(info.version),
|
|
1846
|
+
patched: bundles.every((bundle) => bundle.patched),
|
|
1847
|
+
legacyPatched: bundles.some((bundle) => bundle.legacyPatched),
|
|
1848
|
+
hasRunLoopNeedle: bundles.every((bundle) => bundle.hasRunLoopNeedle),
|
|
1849
|
+
hasConnectNeedle: bundles.every((bundle) => bundle.hasConnectNeedle),
|
|
1850
|
+
hasUnaryNeedle: bundles.every((bundle) => bundle.hasUnaryNeedle),
|
|
1851
|
+
hasModelEnrichNeedle: bundles.some((bundle) => bundle.hasModelEnrichNeedle),
|
|
1852
|
+
bundles,
|
|
1853
|
+
};
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
function bundleStatus(workbenchPath) {
|
|
1857
|
+
const source = fs.readFileSync(workbenchPath, "utf8");
|
|
1858
|
+
return {
|
|
1859
|
+
workbenchPath,
|
|
1644
1860
|
patched: source.includes(MARKER),
|
|
1645
1861
|
legacyPatched: source.includes(LEGACY_MARKER),
|
|
1646
1862
|
hasRunLoopNeedle: !!findRunLoopPatchPoint(source),
|
|
1647
1863
|
hasConnectNeedle: !!findConnectHelperNeedle(source),
|
|
1864
|
+
hasUnaryNeedle: !!findUnaryPatchPoint(source) || source.includes(`await ${MARKER}().patchAvailableModels(`),
|
|
1648
1865
|
hasModelEnrichNeedle: source.includes(MODEL_ENRICH_NEEDLE),
|
|
1649
1866
|
};
|
|
1650
1867
|
}
|
|
@@ -1655,42 +1872,58 @@ function installPatch(options = {}) {
|
|
|
1655
1872
|
if (!isSupportedVersion(info.version) && !options.force) {
|
|
1656
1873
|
throw new Error(`Unsupported Cursor version ${info.version}. Re-run with --force only after validating needles.`);
|
|
1657
1874
|
}
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1875
|
+
const changes = [];
|
|
1876
|
+
for (const workbenchPath of info.workbenchPaths) {
|
|
1877
|
+
let source = fs.readFileSync(workbenchPath, "utf8");
|
|
1878
|
+
if (source.includes(MARKER)) continue;
|
|
1879
|
+
if (source.includes(LEGACY_MARKER) && !options.force) {
|
|
1880
|
+
throw new Error(`Legacy local BYOK patch is already present in ${workbenchPath}. Restore Cursor from backup or use --force after manual review.`);
|
|
1881
|
+
}
|
|
1882
|
+
source = patchSource(source, workbenchPath);
|
|
1883
|
+
changes.push({ workbenchPath, source });
|
|
1662
1884
|
}
|
|
1885
|
+
if (changes.length === 0) return { ...info, changed: false, alreadyInstalled: true };
|
|
1886
|
+
|
|
1887
|
+
const backupPaths = changes.map(({ workbenchPath }) => backupWorkbench(workbenchPath));
|
|
1888
|
+
changes.forEach(({ workbenchPath, source }) => fs.writeFileSync(workbenchPath, source));
|
|
1889
|
+
return { ...info, changed: true, backupPath: backupPaths[0], backupPaths };
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
function patchSource(source, workbenchPath) {
|
|
1663
1893
|
const connectNeedle = findConnectHelperNeedle(source);
|
|
1894
|
+
const unary = findUnaryPatchPoint(source);
|
|
1664
1895
|
const runLoop = findRunLoopPatchPoint(source);
|
|
1665
|
-
if (!runLoop) throw new Error(
|
|
1666
|
-
if (!connectNeedle) throw new Error(
|
|
1667
|
-
if (!
|
|
1668
|
-
if (!source.includes(MODEL_ENRICH_NEEDLE)) throw new Error("Model enrichment needle not found");
|
|
1896
|
+
if (!runLoop) throw new Error(`AgentCompatService.runAgentLoop needle not found in ${workbenchPath}`);
|
|
1897
|
+
if (!connectNeedle) throw new Error(`Connect helper needle not found in ${workbenchPath}`);
|
|
1898
|
+
if (!unary) throw new Error(`Unary transport needle not found in ${workbenchPath}`);
|
|
1669
1899
|
|
|
1670
|
-
const backupPath = backupWorkbench(info.workbenchPath);
|
|
1671
1900
|
source = source.replace(connectNeedle, helperSnippet() + connectNeedle);
|
|
1672
|
-
source = source.replace(
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
source = source.replace(MODEL_ENRICH_NEEDLE, `s=XI_(s,a.models);s=await ${MARKER}().enrichModels(s);`);
|
|
1901
|
+
source = source.replace(unary.needle, `${unary.call};try{if(${unary.service}?.typeName==="aiserver.v1.AiService"&&${unary.method}?.name==="AvailableModels")await ${MARKER}().patchAvailableModels(${unary.response}.message,${unary.request})}catch{}${unary.returnStatement}`);
|
|
1902
|
+
if (source.includes(MODEL_ENRICH_NEEDLE)) {
|
|
1903
|
+
source = source.replace(MODEL_ENRICH_NEEDLE, `s=XI_(s,a.models);s=await ${MARKER}().enrichModels(s);`);
|
|
1904
|
+
}
|
|
1677
1905
|
source = source.replace(runLoop.needle, runLoop.needle + agentLoopSnippet(runLoop.vars));
|
|
1678
|
-
|
|
1679
|
-
return { ...info, changed: true, backupPath };
|
|
1906
|
+
return source;
|
|
1680
1907
|
}
|
|
1681
1908
|
|
|
1682
1909
|
function uninstallPatch(options = {}) {
|
|
1683
1910
|
const appRoot = findCursorAppRoot(options.appRoot);
|
|
1684
1911
|
const info = readCursorInfo(appRoot);
|
|
1685
|
-
const
|
|
1686
|
-
const
|
|
1687
|
-
|
|
1912
|
+
const restores = info.workbenchPaths.map((workbenchPath) => {
|
|
1913
|
+
const backups = findBackups(workbenchPath).filter((file) => !fs.readFileSync(file, "utf8").includes(MARKER));
|
|
1914
|
+
if (backups.length === 0) throw new Error(`No suitable cursor-openai-byok backup found for ${workbenchPath}`);
|
|
1915
|
+
return { workbenchPath, restoredFrom: backups[backups.length - 1] };
|
|
1688
1916
|
});
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
return {
|
|
1917
|
+
restores.forEach((restore) => {
|
|
1918
|
+
restore.currentBackup = backupWorkbench(restore.workbenchPath, "pre-uninstall");
|
|
1919
|
+
fs.copyFileSync(restore.restoredFrom, restore.workbenchPath);
|
|
1920
|
+
});
|
|
1921
|
+
return {
|
|
1922
|
+
...info,
|
|
1923
|
+
restoredFrom: restores[0].restoredFrom,
|
|
1924
|
+
currentBackup: restores[0].currentBackup,
|
|
1925
|
+
restores,
|
|
1926
|
+
};
|
|
1694
1927
|
}
|
|
1695
1928
|
|
|
1696
1929
|
function backupWorkbench(workbenchPath, label = "backup") {
|
|
@@ -1704,21 +1937,37 @@ function findBackups(workbenchPath) {
|
|
|
1704
1937
|
const dir = path.dirname(workbenchPath);
|
|
1705
1938
|
const base = path.basename(workbenchPath);
|
|
1706
1939
|
return fs.readdirSync(dir)
|
|
1707
|
-
.filter((name) => name.startsWith(`${base}
|
|
1940
|
+
.filter((name) => name.startsWith(`${base}.`) && name.includes("cursor-openai-byok"))
|
|
1708
1941
|
.map((name) => path.join(dir, name))
|
|
1709
1942
|
.sort();
|
|
1710
1943
|
}
|
|
1711
1944
|
|
|
1712
1945
|
function isSupportedVersion(version) {
|
|
1713
|
-
return /^3\.8\./.test(version);
|
|
1946
|
+
return /^(?:3\.8|3\.11)\./.test(version);
|
|
1714
1947
|
}
|
|
1715
1948
|
|
|
1716
1949
|
function findConnectHelperNeedle(source) {
|
|
1717
1950
|
if (source.includes(CONNECT_HELPER_NEEDLE)) return CONNECT_HELPER_NEEDLE;
|
|
1718
|
-
const match = source.match(/function\s+\w+\(
|
|
1951
|
+
const match = source.match(/function\s+\w+\((\w+),(\w+)\)\{return\s+\w+\(\1,(\w+)=>\{switch\(\3\.kind\)\{case\s+\w+\.Unary:return\s+\w+\(\2,\1,\3\);case\s+\w+\.ServerStreaming:return\s+\w+\(\2,\1,\3\);case\s+\w+\.ClientStreaming:return\s+\w+\(\2,\1,\3\);case\s+\w+\.BiDiStreaming:return\s+\w+\(\2,\1,\3\);default:return null\}\}\)\}/);
|
|
1719
1952
|
return match ? match[0] : null;
|
|
1720
1953
|
}
|
|
1721
1954
|
|
|
1955
|
+
function findUnaryPatchPoint(source) {
|
|
1956
|
+
const re = /const\s+(\w+)=await\s+\w+\.unary\((\w+),(\w+),(\w+)\?\.signal,\4\?\.timeoutMs,\4\?\.headers,(\w+),\4\?\.contextValues\);return\s+\4\?\.onHeader\?\.\(\1\.header\),\4\?\.onTrailer\?\.\(\1\.trailer\),\1\.message/;
|
|
1957
|
+
const match = source.match(re);
|
|
1958
|
+
if (!match) return null;
|
|
1959
|
+
const split = match[0].indexOf(";return");
|
|
1960
|
+
return {
|
|
1961
|
+
needle: match[0],
|
|
1962
|
+
call: match[0].slice(0, split),
|
|
1963
|
+
returnStatement: match[0].slice(split + 1),
|
|
1964
|
+
response: match[1],
|
|
1965
|
+
service: match[2],
|
|
1966
|
+
method: match[3],
|
|
1967
|
+
request: match[5],
|
|
1968
|
+
};
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1722
1971
|
function findRunLoopPatchPoint(source) {
|
|
1723
1972
|
if (source.includes(RUN_LOOP_NEEDLE)) {
|
|
1724
1973
|
return {
|
|
@@ -1728,94 +1977,66 @@ function findRunLoopPatchPoint(source) {
|
|
|
1728
1977
|
}
|
|
1729
1978
|
const re = /const\{userText:(\w+),richText:(\w+),mode:(\w+),selectedContext:(\w+),requestContext:(\w+),modelDetails:(\w+),generationUUID:(\w+),conversationId:(\w+),messageId:(\w+),[\s\S]{0,900}?conversationState:(\w+),[\s\S]{0,1600}?(\w+)=_b\(n\.withName\("AgentCompatService\.agentClientService\.run"\)\),(\w+)=new\s+\w+\(this\.instantiationService,[^;]+?\);/;
|
|
1730
1979
|
const match = source.match(re);
|
|
1731
|
-
if (
|
|
1980
|
+
if (match) {
|
|
1981
|
+
return {
|
|
1982
|
+
needle: match[0],
|
|
1983
|
+
vars: {
|
|
1984
|
+
userText: match[1],
|
|
1985
|
+
richText: match[2],
|
|
1986
|
+
mode: match[3],
|
|
1987
|
+
selectedContext: match[4],
|
|
1988
|
+
requestContext: match[5],
|
|
1989
|
+
model: match[6],
|
|
1990
|
+
generationUUID: match[7],
|
|
1991
|
+
conversationId: match[8],
|
|
1992
|
+
messageId: match[9],
|
|
1993
|
+
conversationState: match[10],
|
|
1994
|
+
ctx: match[11],
|
|
1995
|
+
adapter: match[12],
|
|
1996
|
+
event: "e",
|
|
1997
|
+
},
|
|
1998
|
+
};
|
|
1999
|
+
}
|
|
2000
|
+
const anchor = source.indexOf('"AgentCompatService.agentClientService.run"');
|
|
2001
|
+
if (anchor < 0) return null;
|
|
2002
|
+
const start = Math.max(0, source.lastIndexOf("async runAgentLoop", anchor));
|
|
2003
|
+
const segment = source.slice(start, anchor + 500);
|
|
2004
|
+
const args = segment.match(/const\{userText:(\w+),richText:(\w+),mode:(\w+),selectedContext:(\w+),requestContext:(\w+),modelDetails:(\w+),generationUUID:(\w+),conversationId:(\w+),messageId:(\w+),[\s\S]{0,900}?conversationState:(\w+),/);
|
|
2005
|
+
const context = segment.match(/const\s+[\w$]+=[\w$]+,\[([\w$]+),[\w$]+\]=[\w$]+\.withCancel\(\)/);
|
|
2006
|
+
const adapter = segment.match(/[\w$]+=[\w$]+\([\w$]+\.withName\("AgentCompatService\.agentClientService\.run"\)\),([\w$]+)=new\s+[\w$]+\(this\.instantiationService,[^;]+?\);/);
|
|
2007
|
+
const functionArgs = segment.match(/async runAgentLoop\([\w$]+,([\w$]+)\)/);
|
|
2008
|
+
if (!args || !context || !adapter || !functionArgs) return null;
|
|
2009
|
+
const eventFactory = findInteractionUpdateFactory(source);
|
|
2010
|
+
if (!eventFactory) return null;
|
|
2011
|
+
const needleStart = segment.indexOf(args[0]);
|
|
2012
|
+
const needleEnd = adapter.index + adapter[0].length;
|
|
1732
2013
|
return {
|
|
1733
|
-
needle:
|
|
2014
|
+
needle: segment.slice(needleStart, needleEnd),
|
|
1734
2015
|
vars: {
|
|
1735
|
-
userText:
|
|
1736
|
-
richText:
|
|
1737
|
-
mode:
|
|
1738
|
-
selectedContext:
|
|
1739
|
-
requestContext:
|
|
1740
|
-
model:
|
|
1741
|
-
generationUUID:
|
|
1742
|
-
conversationId:
|
|
1743
|
-
messageId:
|
|
1744
|
-
conversationState:
|
|
1745
|
-
|
|
1746
|
-
adapter:
|
|
1747
|
-
event:
|
|
2016
|
+
userText: args[1],
|
|
2017
|
+
richText: args[2],
|
|
2018
|
+
mode: args[3],
|
|
2019
|
+
selectedContext: args[4],
|
|
2020
|
+
requestContext: args[5],
|
|
2021
|
+
model: args[6],
|
|
2022
|
+
generationUUID: args[7],
|
|
2023
|
+
conversationId: args[8],
|
|
2024
|
+
messageId: args[9],
|
|
2025
|
+
conversationState: args[10],
|
|
2026
|
+
contextExpression: context[1],
|
|
2027
|
+
adapter: adapter[1],
|
|
2028
|
+
event: functionArgs[1],
|
|
2029
|
+
eventFactory,
|
|
1748
2030
|
},
|
|
1749
2031
|
};
|
|
1750
2032
|
}
|
|
1751
2033
|
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
"src/patcher/cursor-paths.js": function(module, exports, require, __filename, __dirname) {
|
|
1756
|
-
"use strict";
|
|
1757
|
-
|
|
1758
|
-
const fs = require("fs");
|
|
1759
|
-
const path = require("path");
|
|
1760
|
-
const os = require("os");
|
|
1761
|
-
|
|
1762
|
-
function candidateCursorAppRoots() {
|
|
1763
|
-
if (process.platform === "darwin") {
|
|
1764
|
-
return [
|
|
1765
|
-
"/Applications/Cursor.app/Contents/Resources/app",
|
|
1766
|
-
path.join(os.homedir(), "Applications/Cursor.app/Contents/Resources/app"),
|
|
1767
|
-
];
|
|
1768
|
-
}
|
|
1769
|
-
if (process.platform === "win32") {
|
|
1770
|
-
const local = process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local");
|
|
1771
|
-
return [
|
|
1772
|
-
path.join(local, "Programs", "Cursor", "resources", "app"),
|
|
1773
|
-
path.join(local, "Cursor", "resources", "app"),
|
|
1774
|
-
];
|
|
1775
|
-
}
|
|
1776
|
-
return [];
|
|
1777
|
-
}
|
|
1778
|
-
|
|
1779
|
-
function findCursorAppRoot(explicitRoot) {
|
|
1780
|
-
const roots = explicitRoot ? [explicitRoot] : candidateCursorAppRoots();
|
|
1781
|
-
for (const root of roots) {
|
|
1782
|
-
const workbench = workbenchPath(root);
|
|
1783
|
-
if (fs.existsSync(workbench)) return root;
|
|
1784
|
-
}
|
|
1785
|
-
throw new Error(`Cursor app root not found. Checked: ${roots.join(", ")}`);
|
|
1786
|
-
}
|
|
1787
|
-
|
|
1788
|
-
function workbenchPath(appRoot) {
|
|
1789
|
-
return path.join(appRoot, "out", "vs", "workbench", "workbench.desktop.main.js");
|
|
1790
|
-
}
|
|
1791
|
-
|
|
1792
|
-
function productJsonPath(appRoot) {
|
|
1793
|
-
return path.join(appRoot, "product.json");
|
|
1794
|
-
}
|
|
1795
|
-
|
|
1796
|
-
function packageJsonPath(appRoot) {
|
|
1797
|
-
return path.join(appRoot, "package.json");
|
|
1798
|
-
}
|
|
1799
|
-
|
|
1800
|
-
function readCursorInfo(appRoot) {
|
|
1801
|
-
const productPath = productJsonPath(appRoot);
|
|
1802
|
-
const packagePath = packageJsonPath(appRoot);
|
|
1803
|
-
const product = fs.existsSync(productPath) ? JSON.parse(fs.readFileSync(productPath, "utf8")) : {};
|
|
1804
|
-
const pkg = fs.existsSync(packagePath) ? JSON.parse(fs.readFileSync(packagePath, "utf8")) : {};
|
|
1805
|
-
return {
|
|
1806
|
-
appRoot,
|
|
1807
|
-
version: product.version || pkg.version || "unknown",
|
|
1808
|
-
commit: product.commit || "unknown",
|
|
1809
|
-
workbenchPath: workbenchPath(appRoot),
|
|
1810
|
-
};
|
|
2034
|
+
function findInteractionUpdateFactory(source) {
|
|
2035
|
+
const match = source.match(/(\w+)=\{textDelta\(\w+\)\{return new\s+\w+\(\{message:\{case:"textDelta",[\s\S]{0,2500}?turnEnded\(/);
|
|
2036
|
+
return match ? match[1] : null;
|
|
1811
2037
|
}
|
|
1812
2038
|
|
|
1813
|
-
module.exports = {
|
|
1814
|
-
candidateCursorAppRoots,
|
|
1815
|
-
findCursorAppRoot,
|
|
1816
|
-
workbenchPath,
|
|
1817
|
-
readCursorInfo,
|
|
1818
|
-
};
|
|
2039
|
+
module.exports = { status, installPatch, uninstallPatch, isSupportedVersion, findConnectHelperNeedle, findUnaryPatchPoint, findRunLoopPatchPoint };
|
|
1819
2040
|
|
|
1820
2041
|
},
|
|
1821
2042
|
"src/patcher/templates.js": function(module, exports, require, __filename, __dirname) {
|
|
@@ -1826,7 +2047,7 @@ const LEGACY_MARKER = "__LOCAL_CURSOR_BYOK_V0";
|
|
|
1826
2047
|
const ENDPOINT = "http://127.0.0.1:39832";
|
|
1827
2048
|
|
|
1828
2049
|
function helperSnippet() {
|
|
1829
|
-
return `function ${MARKER}(){if(globalThis.${MARKER})return globalThis.${MARKER};const endpoint="${ENDPOINT}";const sleep=ms=>new Promise(r=>setTimeout(r,ms));const tip=t=>({primaryText:"",secondaryText:"",secondaryWarningText:false,icon:"",tertiaryText:"",tertiaryTextUrl:"",markdownContent:t});const esc=v=>String(v??"").replace(/[\\\\*_\\[\\]\`]/g,"\\\\$&");const byok={async models(){const started=Date.now();for(;;){try{const r=await fetch(endpoint+"/cursor/v1/models");const j=await r.json();return j&&j.ok?j.models||[]:[]}catch(e){if(Date.now()-started>2e3)return[];await sleep(120)}}},toCursorModel(m){const name=m.displayName,provider=m.providerName||m.providerId||"BYOK",ctx=m.contextTokenLimit||128000,md="**"+esc(name)+"**<br />OpenAI-compatible BYOK model via "+esc(provider)+".<br /><br />"+ctx.toLocaleString()+" context window";return{name,defaultOn:true,parameterDefinitions:[],variants:[{parameterValues:[],displayName:name,isMaxMode:false,isDefaultMaxConfig:true,isDefaultNonMaxConfig:true,tooltipData:tip(md),displayNameOutsidePicker:name,variantStringRepresentation:name+"[]",legacySlug:name}],legacySlugs:[name],idAliases:[name],cloudAgentEffortModes:[],supportsAgent:true,degradationStatus:0,tooltipData:tip(md),supportsThinking:false,supportsImages:m.supportsImages!==false,supportsMaxMode:true,clientDisplayName:name,serverModelName:name,supportsNonMaxMode:true,tooltipDataForMaxMode:tip(md),isRecommendedForBackgroundComposer:false,supportsPlanMode:true,inputboxShortModelName:name,supportsSandboxing:true,supportsCmdK:true,contextTokenLimit:ctx,contextTokenLimitForMaxMode:ctx,maxOutputTokens:m.maxOutputTokens||8192,namedModelSectionIndex:1,vendorName:provider,vendor:{id:9000,displayName:provider},tagline:"OpenAI-compatible BYOK",visibleInRoutedModelView:
|
|
2050
|
+
return `function ${MARKER}(){if(globalThis.${MARKER})return globalThis.${MARKER};const endpoint="${ENDPOINT}";const sleep=ms=>new Promise(r=>setTimeout(r,ms));const tip=t=>({primaryText:"",secondaryText:"",secondaryWarningText:false,icon:"",tertiaryText:"",tertiaryTextUrl:"",markdownContent:t});const esc=v=>String(v??"").replace(/[\\\\*_\\[\\]\`]/g,"\\\\$&");const byok={async models(){const started=Date.now();for(;;){try{const r=await fetch(endpoint+"/cursor/v1/models");const j=await r.json();return j&&j.ok?j.models||[]:[]}catch(e){if(Date.now()-started>2e3)return[];await sleep(120)}}},toCursorModel(m){const name=m.displayName,provider=m.providerName||m.providerId||"BYOK",ctx=m.contextTokenLimit||128000,md="**"+esc(name)+"**<br />OpenAI-compatible BYOK model via "+esc(provider)+".<br /><br />"+ctx.toLocaleString()+" context window";return{name,defaultOn:true,parameterDefinitions:[],variants:[{parameterValues:[],displayName:name,isMaxMode:false,isDefaultMaxConfig:true,isDefaultNonMaxConfig:true,tooltipData:tip(md),displayNameOutsidePicker:name,variantStringRepresentation:name+"[]",legacySlug:name}],legacySlugs:[name],idAliases:[name],cloudAgentEffortModes:[],supportsAgent:true,degradationStatus:0,tooltipData:tip(md),supportsThinking:false,supportsImages:m.supportsImages!==false,supportsMaxMode:true,clientDisplayName:name,serverModelName:name,supportsNonMaxMode:true,tooltipDataForMaxMode:tip(md),isRecommendedForBackgroundComposer:false,supportsPlanMode:true,inputboxShortModelName:name,supportsSandboxing:true,supportsCmdK:true,contextTokenLimit:ctx,contextTokenLimitForMaxMode:ctx,maxOutputTokens:m.maxOutputTokens||8192,namedModelSectionIndex:1,vendorName:provider,vendor:{id:9000,displayName:provider},tagline:"OpenAI-compatible BYOK",visibleInRoutedModelView:false,isUserAdded:true,byokModel:true}},async shouldHandle(model){try{if(!model)return false;const r=await fetch(endpoint+"/cursor/v1/should-handle?model="+encodeURIComponent(model));const j=await r.json();return !!(j&&j.ok&&j.handle)}catch{return false}},async enrichModels(list,models){try{models=models||await this.models();if(!models.length)return list;const out=Array.isArray(list)?list:[];for(const m of models){const cm=this.toCursorModel(m),i=out.findIndex(x=>x&&(x.name===m.displayName||x.clientDisplayName===m.displayName||x.serverModelName===m.displayName));i>=0?out[i]={...out[i],...cm}:out.push(cm)}return out}catch(e){console.warn("cursor-openai-byok model enrich failed",e);return list}},async registerCustomModels(models){try{await fetch(endpoint+"/cursor/v1/custom-models",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({models})})}catch{}},async patchAvailableModels(msg,req){try{if(!msg)return;const models=await this.models(),known=new Set((msg.models||[]).flatMap(m=>[m?.name,m?.clientDisplayName,m?.serverModelName]).filter(Boolean)),names=Array.isArray(req?.additionalModelNames)?req.additionalModelNames:[],custom=[...new Set(names.filter(n=>typeof n==="string"&&n&&!known.has(n)&&!models.some(m=>m.displayName===n)))];await this.registerCustomModels(custom);const all=[...models,...custom.map(displayName=>({displayName,providerName:"BYOK"}))];if(!all.length)return;msg.modelNames=msg.modelNames||[];msg.models=await this.enrichModels(msg.models||[],all);for(const m of all)if(!msg.modelNames.includes(m.displayName))msg.modelNames.push(m.displayName)}catch(e){console.warn("cursor-openai-byok model patch failed",e)}},async runAgent(payload,onEvent){const res=await fetch(endpoint+"/cursor/v1/run-agent-loop",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(payload)});if(!res.ok)throw new Error("BYOK HTTP "+res.status);const reader=res.body.getReader();const decoder=new TextDecoder();let buf="";for(;;){const item=await reader.read();if(item.done)break;buf+=decoder.decode(item.value,{stream:true});let idx;while((idx=buf.indexOf("\\n"))>=0){const line=buf.slice(0,idx).trim();buf=buf.slice(idx+1);if(line){try{await onEvent(JSON.parse(line))}catch(e){console.warn("cursor-openai-byok event failed",e)}}}}}};globalThis.${MARKER}=byok;return byok}`;
|
|
1830
2051
|
}
|
|
1831
2052
|
|
|
1832
2053
|
function agentLoopSnippet(vars) {
|
|
@@ -1845,7 +2066,9 @@ function agentLoopSnippet(vars) {
|
|
|
1845
2066
|
messageId: "b",
|
|
1846
2067
|
conversationState: "W",
|
|
1847
2068
|
};
|
|
1848
|
-
|
|
2069
|
+
const context = v.contextExpression || `${v.ctx}.ctx`;
|
|
2070
|
+
const events = v.eventFactory || "E0c";
|
|
2071
|
+
return `if(!${v.event}.subagentTypeName&&await ${MARKER}().shouldHandle(${v.model}?.modelName)){const sendText=async text=>{if(text)await ${v.adapter}.sendUpdate(${context},${events}.textDelta(text))};const sendDone=async()=>{await ${v.adapter}.sendUpdate(${context},${events}.turnEnded())};try{await ${MARKER}().runAgent({model:${v.model}?.modelName,userText:${v.userText},richText:${v.richText},mode:${v.mode},selectedContext:${v.selectedContext},requestContext:${v.requestContext},generationUUID:${v.generationUUID},conversationId:${v.conversationId},messageId:${v.messageId},conversationState:${v.conversationState},event:${v.event},tools:${v.event}.tools},async ev=>{if(ev.type==="text_delta")await sendText(ev.text||"");else if(ev.type==="tool_calls_ready")await sendText("\\n[BYOK tool calls requested: "+(ev.toolCalls||[]).map(t=>t.name||t.id).join(", ")+"]\\n");else if(ev.type==="error")await sendText("Local BYOK error: "+(ev.error||"unknown"));});await sendDone();return}catch(err){await sendText("Local BYOK error: "+(err&&err.message?err.message:String(err)));await sendDone();return}}`;
|
|
1849
2072
|
}
|
|
1850
2073
|
|
|
1851
2074
|
module.exports = { MARKER, LEGACY_MARKER, helperSnippet, agentLoopSnippet };
|