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/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) {
@@ -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
- 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.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: 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,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 source = fs.readFileSync(info.workbenchPath, "utf8");
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
- let source = fs.readFileSync(info.workbenchPath, "utf8");
1659
- if (source.includes(MARKER)) return { ...info, changed: false, alreadyInstalled: true };
1660
- if (source.includes(LEGACY_MARKER) && !options.force) {
1661
- throw new Error("Legacy local BYOK patch is already present. Restore Cursor from backup or use --force after manual review.");
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("AgentCompatService.runAgentLoop needle not found");
1666
- 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");
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
- 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);`);
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
- fs.writeFileSync(info.workbenchPath, source);
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 backups = findBackups(info.workbenchPath).filter((file) => {
1686
- const data = fs.readFileSync(file, "utf8");
1687
- return !data.includes(MARKER);
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
- if (backups.length === 0) throw new Error("No suitable cursor-openai-byok backup found");
1690
- const latest = backups[backups.length - 1];
1691
- const currentBackup = backupWorkbench(info.workbenchPath, "pre-uninstall");
1692
- fs.copyFileSync(latest, info.workbenchPath);
1693
- return { ...info, restoredFrom: latest, currentBackup };
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}.backup-cursor-openai-byok-`) || name.includes("cursor-openai-byok"))
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+\(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\}\}\)\}/);
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 (!match) return null;
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: match[0],
2014
+ needle: segment.slice(needleStart, needleEnd),
1734
2015
  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",
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
- 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
- };
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: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}`;
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
- 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}}`;
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 };