cursor-openai-byok 1.0.2 → 1.1.3

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/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 extension = service.uninstallExtension();
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) {
@@ -147,14 +149,17 @@ async function doctor(options) {
147
149
  checks.push({ name: "cursor", ok: false, error: err.message });
148
150
  }
149
151
 
150
- checks.push({ name: "extension", ok: service.extensionStatus().installed, ...service.extensionStatus() });
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 extension is active",
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 not running",
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.0.2",
269
+ "version": "1.1.3",
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: true,
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) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&apos;" })[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,76 @@ 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
+ const workbench = workbenchPath(root);
1032
+ if (fs.existsSync(workbench)) return root;
1033
+ }
1034
+ throw new Error(`Cursor app root not found. Checked: ${roots.join(", ")}`);
1035
+ }
1036
+
1037
+ function workbenchPath(appRoot) {
1038
+ return path.join(appRoot, "out", "vs", "workbench", "workbench.desktop.main.js");
1039
+ }
1040
+
1041
+ function productJsonPath(appRoot) {
1042
+ return path.join(appRoot, "product.json");
1043
+ }
1044
+
1045
+ function packageJsonPath(appRoot) {
1046
+ return path.join(appRoot, "package.json");
1047
+ }
1048
+
1049
+ function readCursorInfo(appRoot) {
1050
+ const productPath = productJsonPath(appRoot);
1051
+ const packagePath = packageJsonPath(appRoot);
1052
+ const product = fs.existsSync(productPath) ? JSON.parse(fs.readFileSync(productPath, "utf8")) : {};
1053
+ const pkg = fs.existsSync(packagePath) ? JSON.parse(fs.readFileSync(packagePath, "utf8")) : {};
1054
+ return {
1055
+ appRoot,
1056
+ version: product.version || pkg.version || "unknown",
1057
+ commit: product.commit || "unknown",
1058
+ workbenchPath: workbenchPath(appRoot),
1059
+ };
1060
+ }
1061
+
1062
+ module.exports = {
1063
+ candidateCursorAppRoots,
1064
+ findCursorAppRoot,
1065
+ workbenchPath,
1066
+ readCursorInfo,
882
1067
  };
883
1068
 
884
1069
  },
@@ -983,9 +1168,9 @@ async function sendNdjson(res, iterator) {
983
1168
  res.end();
984
1169
  }
985
1170
 
986
- async function* runCursorAgentLoop(config, payload) {
1171
+ async function* runCursorAgentLoop(config, payload, customModels = new Set()) {
987
1172
  const requestedModel = payload.model || payload.modelName || payload?.runRequest?.requestedModel?.modelId;
988
- const route = resolveModel(config, requestedModel);
1173
+ const route = resolveModel(config, requestedModel, { allowUnconfigured: customModels.has(requestedModel) });
989
1174
  const openaiBody = cursorPayloadToOpenAI(payload, route);
990
1175
  log("cursor-run", {
991
1176
  model: route.model.displayName,
@@ -1026,6 +1211,7 @@ async function* runCursorAgentLoop(config, payload) {
1026
1211
  }
1027
1212
 
1028
1213
  function createServer() {
1214
+ const customModels = new Set();
1029
1215
  return http.createServer(async (req, res) => {
1030
1216
  try {
1031
1217
  if (req.method === "OPTIONS") return sendJson(res, 204, {});
@@ -1047,16 +1233,25 @@ function createServer() {
1047
1233
  if (req.method === "GET" && url.pathname === "/cursor/v1/should-handle") {
1048
1234
  const model = url.searchParams.get("model");
1049
1235
  try {
1050
- const route = resolveModel(config, model);
1236
+ const route = resolveModel(config, model, { allowUnconfigured: customModels.has(model) });
1051
1237
  return sendJson(res, 200, { ok: true, handle: true, model: route.model });
1052
1238
  } catch {
1053
1239
  return sendJson(res, 200, { ok: true, handle: false });
1054
1240
  }
1055
1241
  }
1056
1242
 
1243
+ if (req.method === "POST" && url.pathname === "/cursor/v1/custom-models") {
1244
+ const payload = await readJson(req);
1245
+ customModels.clear();
1246
+ for (const model of payload.models || []) {
1247
+ if (typeof model === "string" && model.length <= 128 && !/[\x00-\x1f\x7f]/.test(model)) customModels.add(model);
1248
+ }
1249
+ return sendJson(res, 200, { ok: true, models: [...customModels] });
1250
+ }
1251
+
1057
1252
  if (req.method === "POST" && url.pathname === "/cursor/v1/run-agent-loop") {
1058
1253
  const payload = await readJson(req);
1059
- return sendNdjson(res, runCursorAgentLoop(config, payload));
1254
+ return sendNdjson(res, runCursorAgentLoop(config, payload, customModels));
1060
1255
  }
1061
1256
 
1062
1257
  if (req.method === "POST" && url.pathname === "/cursor/v1/tool-result") {
@@ -1631,13 +1826,13 @@ const { MARKER, LEGACY_MARKER, helperSnippet, agentLoopSnippet } = require("./te
1631
1826
 
1632
1827
  const RUN_LOOP_NEEDLE = `_e=SC(n.withName("AgentCompatService.agentClientService.run")),oe=new Q$t(this.instantiationService,z,re,f,ue);`;
1633
1828
  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
1829
  const MODEL_ENRICH_NEEDLE = `s=XI_(s,a.models);`;
1636
1830
 
1637
1831
  function status(options = {}) {
1638
1832
  const appRoot = findCursorAppRoot(options.appRoot);
1639
1833
  const info = readCursorInfo(appRoot);
1640
1834
  const source = fs.readFileSync(info.workbenchPath, "utf8");
1835
+ const unary = findUnaryPatchPoint(source);
1641
1836
  return {
1642
1837
  ...info,
1643
1838
  supportedVersion: isSupportedVersion(info.version),
@@ -1645,6 +1840,7 @@ function status(options = {}) {
1645
1840
  legacyPatched: source.includes(LEGACY_MARKER),
1646
1841
  hasRunLoopNeedle: !!findRunLoopPatchPoint(source),
1647
1842
  hasConnectNeedle: !!findConnectHelperNeedle(source),
1843
+ hasUnaryNeedle: !!unary || source.includes(`await ${MARKER}().patchAvailableModels(`),
1648
1844
  hasModelEnrichNeedle: source.includes(MODEL_ENRICH_NEEDLE),
1649
1845
  };
1650
1846
  }
@@ -1661,19 +1857,18 @@ function installPatch(options = {}) {
1661
1857
  throw new Error("Legacy local BYOK patch is already present. Restore Cursor from backup or use --force after manual review.");
1662
1858
  }
1663
1859
  const connectNeedle = findConnectHelperNeedle(source);
1860
+ const unary = findUnaryPatchPoint(source);
1664
1861
  const runLoop = findRunLoopPatchPoint(source);
1665
1862
  if (!runLoop) throw new Error("AgentCompatService.runAgentLoop needle not found");
1666
1863
  if (!connectNeedle) throw new Error("Connect helper needle not found");
1667
- if (!source.includes(UNARY_NEEDLE)) throw new Error("Unary transport needle not found");
1668
- if (!source.includes(MODEL_ENRICH_NEEDLE)) throw new Error("Model enrichment needle not found");
1864
+ if (!unary) throw new Error("Unary transport needle not found");
1669
1865
 
1670
1866
  const backupPath = backupWorkbench(info.workbenchPath);
1671
1867
  source = source.replace(connectNeedle, helperSnippet() + connectNeedle);
1672
- source = source.replace(
1673
- UNARY_NEEDLE,
1674
- `const s=await n.unary(e,t,r?.signal,r?.timeoutMs,r?.headers,i,r?.contextValues);try{if(e?.typeName==="aiserver.v1.AiService"&&t?.name==="AvailableModels")await ${MARKER}().patchAvailableModels(s.message)}catch{}return r?.onHeader?.(s.header),r?.onTrailer?.(s.trailer),s.message`
1675
- );
1676
- source = source.replace(MODEL_ENRICH_NEEDLE, `s=XI_(s,a.models);s=await ${MARKER}().enrichModels(s);`);
1868
+ 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}`);
1869
+ if (source.includes(MODEL_ENRICH_NEEDLE)) {
1870
+ source = source.replace(MODEL_ENRICH_NEEDLE, `s=XI_(s,a.models);s=await ${MARKER}().enrichModels(s);`);
1871
+ }
1677
1872
  source = source.replace(runLoop.needle, runLoop.needle + agentLoopSnippet(runLoop.vars));
1678
1873
  fs.writeFileSync(info.workbenchPath, source);
1679
1874
  return { ...info, changed: true, backupPath };
@@ -1710,15 +1905,31 @@ function findBackups(workbenchPath) {
1710
1905
  }
1711
1906
 
1712
1907
  function isSupportedVersion(version) {
1713
- return /^3\.8\./.test(version);
1908
+ return /^(?:3\.8|3\.11)\./.test(version);
1714
1909
  }
1715
1910
 
1716
1911
  function findConnectHelperNeedle(source) {
1717
1912
  if (source.includes(CONNECT_HELPER_NEEDLE)) return CONNECT_HELPER_NEEDLE;
1718
- const match = source.match(/function\s+\w+\(n,e\)\{return\s+\w+\(n,t=>\{switch\(t\.kind\)\{case\s+\w+\.Unary:return\s+\w+\(e,n,t\);case\s+\w+\.ServerStreaming:return\s+\w+\(e,n,t\);case\s+\w+\.ClientStreaming:return\s+\w+\(e,n,t\);case\s+\w+\.BiDiStreaming:return\s+\w+\(e,n,t\);default:return null\}\}\)\}/);
1913
+ 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
1914
  return match ? match[0] : null;
1720
1915
  }
1721
1916
 
1917
+ function findUnaryPatchPoint(source) {
1918
+ 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/;
1919
+ const match = source.match(re);
1920
+ if (!match) return null;
1921
+ const split = match[0].indexOf(";return");
1922
+ return {
1923
+ needle: match[0],
1924
+ call: match[0].slice(0, split),
1925
+ returnStatement: match[0].slice(split + 1),
1926
+ response: match[1],
1927
+ service: match[2],
1928
+ method: match[3],
1929
+ request: match[5],
1930
+ };
1931
+ }
1932
+
1722
1933
  function findRunLoopPatchPoint(source) {
1723
1934
  if (source.includes(RUN_LOOP_NEEDLE)) {
1724
1935
  return {
@@ -1728,94 +1939,65 @@ function findRunLoopPatchPoint(source) {
1728
1939
  }
1729
1940
  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
1941
  const match = source.match(re);
1731
- if (!match) return null;
1942
+ if (match) {
1943
+ return {
1944
+ needle: match[0],
1945
+ vars: {
1946
+ userText: match[1],
1947
+ richText: match[2],
1948
+ mode: match[3],
1949
+ selectedContext: match[4],
1950
+ requestContext: match[5],
1951
+ model: match[6],
1952
+ generationUUID: match[7],
1953
+ conversationId: match[8],
1954
+ messageId: match[9],
1955
+ conversationState: match[10],
1956
+ ctx: match[11],
1957
+ adapter: match[12],
1958
+ event: "e",
1959
+ },
1960
+ };
1961
+ }
1962
+ const anchor = source.indexOf('"AgentCompatService.agentClientService.run"');
1963
+ if (anchor < 0) return null;
1964
+ const start = Math.max(0, source.lastIndexOf("async runAgentLoop", anchor));
1965
+ const segment = source.slice(start, anchor + 500);
1966
+ 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+),/);
1967
+ const context = segment.match(/const\s+\w+=\w+,\[(\w+),\w+\]=\w+\.withCancel\(\)/);
1968
+ const adapter = segment.match(/\w+=\w+\(\w+\.withName\("AgentCompatService\.agentClientService\.run"\)\),(\w+)=new\s+\w+\(this\.instantiationService,[^;]+?\);/);
1969
+ if (!args || !context || !adapter) return null;
1970
+ const eventFactory = findInteractionUpdateFactory(source);
1971
+ if (!eventFactory) return null;
1972
+ const needleStart = segment.indexOf(args[0]);
1973
+ const needleEnd = adapter.index + adapter[0].length;
1732
1974
  return {
1733
- needle: match[0],
1975
+ needle: segment.slice(needleStart, needleEnd),
1734
1976
  vars: {
1735
- userText: match[1],
1736
- richText: match[2],
1737
- mode: match[3],
1738
- selectedContext: match[4],
1739
- requestContext: match[5],
1740
- model: match[6],
1741
- generationUUID: match[7],
1742
- conversationId: match[8],
1743
- messageId: match[9],
1744
- conversationState: match[10],
1745
- ctx: match[11],
1746
- adapter: match[12],
1747
- event: "e",
1977
+ userText: args[1],
1978
+ richText: args[2],
1979
+ mode: args[3],
1980
+ selectedContext: args[4],
1981
+ requestContext: args[5],
1982
+ model: args[6],
1983
+ generationUUID: args[7],
1984
+ conversationId: args[8],
1985
+ messageId: args[9],
1986
+ conversationState: args[10],
1987
+ contextExpression: context[1],
1988
+ adapter: adapter[1],
1989
+ event: "t",
1990
+ eventFactory,
1748
1991
  },
1749
1992
  };
1750
1993
  }
1751
1994
 
1752
- module.exports = { status, installPatch, uninstallPatch };
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
- };
1995
+ function findInteractionUpdateFactory(source) {
1996
+ const match = source.match(/(\w+)=\{textDelta\(\w+\)\{return new\s+\w+\(\{message:\{case:"textDelta",[\s\S]{0,2500}?turnEnded\(/);
1997
+ return match ? match[1] : null;
1811
1998
  }
1812
1999
 
1813
- module.exports = {
1814
- candidateCursorAppRoots,
1815
- findCursorAppRoot,
1816
- workbenchPath,
1817
- readCursorInfo,
1818
- };
2000
+ module.exports = { status, installPatch, uninstallPatch, isSupportedVersion, findConnectHelperNeedle, findUnaryPatchPoint, findRunLoopPatchPoint };
1819
2001
 
1820
2002
  },
1821
2003
  "src/patcher/templates.js": function(module, exports, require, __filename, __dirname) {
@@ -1826,7 +2008,7 @@ const LEGACY_MARKER = "__LOCAL_CURSOR_BYOK_V0";
1826
2008
  const ENDPOINT = "http://127.0.0.1:39832";
1827
2009
 
1828
2010
  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:true,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){try{const 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 patchAvailableModels(msg){try{const models=await this.models();if(!models.length||!msg)return;msg.modelNames=msg.modelNames||[];msg.models=await this.enrichModels(msg.models||[]);for(const m of models)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}`;
2011
+ 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
2012
  }
1831
2013
 
1832
2014
  function agentLoopSnippet(vars) {
@@ -1845,7 +2027,9 @@ function agentLoopSnippet(vars) {
1845
2027
  messageId: "b",
1846
2028
  conversationState: "W",
1847
2029
  };
1848
- return `if(!${v.event}.subagentTypeName&&await ${MARKER}().shouldHandle(${v.model}?.modelName)){const sendText=async text=>{if(text)await ${v.adapter}.sendUpdate(${v.ctx}.ctx,E0c.textDelta(text))};const sendDone=async()=>{await ${v.adapter}.sendUpdate(${v.ctx}.ctx,E0c.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}}`;
2030
+ const context = v.contextExpression || `${v.ctx}.ctx`;
2031
+ const events = v.eventFactory || "E0c";
2032
+ 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
2033
  }
1850
2034
 
1851
2035
  module.exports = { MARKER, LEGACY_MARKER, helperSnippet, agentLoopSnippet };