ocx-cursor 1.0.5 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ocx-cursor",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "Use OpenCodex models in Cursor through an authenticated HTTPS gateway",
5
5
  "keywords": [
6
6
  "opencodex",
package/src/catalog.mjs CHANGED
@@ -39,7 +39,7 @@ export function aliasFor(sourceId) {
39
39
 
40
40
  export function inferredEfforts(sourceId) {
41
41
  const modelId = sourceId.split("/").at(-1);
42
- if (/^claude-(?:fable-5|sonnet-5|opus-(?:5|4-[78]))$/.test(modelId)) return [...allowedEfforts];
42
+ if (/^claude-(?:fable-5(?:-1)?|sonnet-5|opus-(?:5|4-[78]))$/.test(modelId)) return [...allowedEfforts];
43
43
  if (/^claude-(?:opus-4-6|sonnet-4-6)$/.test(modelId)) return ["low", "medium", "high", "max"];
44
44
  if (/^gpt-5\.6-(?:luna|sol|terra)$/.test(modelId)) return [...allowedEfforts];
45
45
  if (/^gpt-5\.[45](?:$|-)/.test(modelId)) return ["low", "medium", "high", "xhigh"];
@@ -16,12 +16,21 @@ export const cursorPatchMarker = "/*ocx-cursor-model-metadata-v6*/";
16
16
  export const legacyCursorPatchMarker = "/*ocx-cursor-model-metadata*/";
17
17
  export const cursorByokRoutingPatchMarker = "/*ocx-cursor-byok-model-routing-v1*/";
18
18
  export const cursorSubagentModelsPatchMarker = "/*ocx-cursor-subagent-models-v1*/";
19
- export const cursorSubagentCredentialsPatchMarker = "/*ocx-cursor-subagent-credentials-v1*/";
20
- export const cursorSubagentLegacyDetailsPatchMarker = "/*ocx-cursor-subagent-legacy-details-v1*/";
19
+ export const cursorSubagentCredentialsPatchMarker = "/*ocx-cursor-subagent-credentials-v2*/";
20
+ export const legacyCursorSubagentCredentialsPatchMarker = "/*ocx-cursor-subagent-credentials-v1*/";
21
+ export const cursorSubagentLegacyDetailsPatchMarker = "/*ocx-cursor-subagent-legacy-details-v2*/";
22
+ export const legacyCursorSubagentLegacyDetailsPatchMarker = "/*ocx-cursor-subagent-legacy-details-v1*/";
21
23
  export const cursorSubagentPromptRoutingPatchMarker = "/*ocx-cursor-subagent-prompt-routing-v3*/";
22
- export const cursorSubagentExecutionRoutingPatchMarker = "/*ocx-cursor-subagent-execution-routing-v5*/";
24
+ export const cursorSubagentExecutionRoutingPatchMarker = "/*ocx-cursor-subagent-execution-routing-v6*/";
23
25
  export const cursorSubagentRunRoutingPatchMarker = "/*ocx-cursor-subagent-run-routing-v1*/";
24
26
  export const cursorSubagentRuntimeCredentialsPatchMarker = "/*ocx-cursor-subagent-runtime-credentials-v1*/";
27
+ export const cursorSubagentModelDisplayPatchMarker = "/*ocx-cursor-subagent-model-display-v2*/";
28
+ export const legacyCursorSubagentModelDisplayPatchMarker = "/*ocx-cursor-subagent-model-display-v1*/";
29
+ export const cursorSubagentTaskArgsDisplayPatchMarker = "/*ocx-cursor-subagent-task-args-display-v5*/";
30
+ export const legacyCursorSubagentTaskArgsDisplayV4PatchMarker = "/*ocx-cursor-subagent-task-args-display-v4*/";
31
+ export const legacyCursorSubagentTaskArgsDisplayV3PatchMarker = "/*ocx-cursor-subagent-task-args-display-v3*/";
32
+ export const legacyCursorSubagentTaskArgsDisplayV2PatchMarker = "/*ocx-cursor-subagent-task-args-display-v2*/";
33
+ export const legacyCursorSubagentTaskArgsDisplayPatchMarker = "/*ocx-cursor-subagent-task-args-display-v1*/";
25
34
  export const cursorRoutedSubagentTypesPatchMarker = "/*ocx-cursor-hide-routed-subagent-types-v1*/";
26
35
  export const cursorLocalModeDisabled = "localMode:!1";
27
36
  export const cursorLocalModeEnabled = "localMode:!0";
@@ -39,22 +48,34 @@ const previousCatalogInjection = /\/\*ocx-cursor-model-metadata(?:-v[2-5])?\*\/(
39
48
  const byokModelRouting = /function (?<functionName>[A-Za-z_$][\w$]*)\((?<model>[A-Za-z_$][\w$]*),(?<settings>[A-Za-z_$][\w$]*)\)\{return (?<isClaude>[A-Za-z_$][\w$]*)\(\k<model>\)\?\k<settings>\.useClaudeKey\?"anthropic":void 0:(?<isGemini>[A-Za-z_$][\w$]*)\(\k<model>\)\?\k<settings>\.useGoogleKey\?"google":void 0:\k<settings>\.useOpenAIKey\?"openai":void 0\}/g;
40
49
  const explicitSubagentModelFilter = /function (?<functionName>[A-Za-z_$][\w$]*)\((?<model>[A-Za-z_$][\w$]*),(?<isUserAdded>[A-Za-z_$][\w$]*),(?<storage>[A-Za-z_$][\w$]*)\)\{return \k<isUserAdded>===!0\?!0:(?<hasProviderOverride>[A-Za-z_$][\w$]*)\(\k<storage>\)\|\|(?<usesByok>[A-Za-z_$][\w$]*)\(\k<model>,\k<storage>\)\}(?=function [A-Za-z_$][\w$]*\([A-Za-z_$][\w$]*\)\{const\{modelDetails:)/g;
41
50
  const explicitSubagentModelSelection = /function (?<functionName>[A-Za-z_$][\w$]*)\((?<input>[A-Za-z_$][\w$]*)\)\{const\{modelDetails:(?<modelDetails>[A-Za-z_$][\w$]*),flagEnabled:(?<flagEnabled>[A-Za-z_$][\w$]*),availableModels:(?<availableModels>[A-Za-z_$][\w$]*),storage:(?<storage>[A-Za-z_$][\w$]*),resolveModelParametersForSubmission:(?<resolveParameters>[A-Za-z_$][\w$]*)\}=\k<input>,(?<maxMode>[A-Za-z_$][\w$]*)=\k<modelDetails>\.maxMode===!0;/g;
51
+ const explicitSubagentByokGuard = /if\((?<hasByok>[A-Za-z_$][\w$]*)\((?<modelDetails>[A-Za-z_$][\w$]*)\)\|\|!\((?<flagEnabled>[A-Za-z_$][\w$]*)\|\|(?<maxMode>[A-Za-z_$][\w$]*)\)\)return\{\};/g;
42
52
  const explicitSubagentRequestedModel = /new (?<requestedModelType>[A-Za-z_$][\w$]*)\(\{modelId:(?<model>[A-Za-z_$][\w$]*)\.name,maxMode:(?<maxMode>[A-Za-z_$][\w$]*),parameters:(?<parameters>[A-Za-z_$][\w$]*)\.map\((?<parameter>[A-Za-z_$][\w$]*)=>new (?<parameterType>[A-Za-z_$][\w$]*)\(\{id:\k<parameter>\.id,value:\k<parameter>\.value\}\)\)\}\)(?=\}\)\}\})/g;
43
53
  const explicitSubagentSelectionCall = /getSelectedSubagentModelSelections\((?<modelDetails>[A-Za-z_$][\w$]*)\)\{return (?<selector>[A-Za-z_$][\w$]*)\(\{modelDetails:\k<modelDetails>,flagEnabled:(?<featureGate>[\s\S]*?),availableModels:this\.modelConfigService\.getAvailableDefaultModels\(\),storage:this\.reactiveStorageService\.applicationUserPersistentStorage,resolveModelParametersForSubmission:\((?<model>[A-Za-z_$][\w$]*),(?<maxMode>[A-Za-z_$][\w$]*)\)=>this\.modelConfigService\.resolveModelParametersForSubmission\(\k<model>,void 0,\k<maxMode>\)\}\)\}/g;
44
54
  const agentModelDetailsType = /(?<type>[A-Za-z_$][\w$]*)=[A-Za-z_$][\w$]*\.makeMessageType\("agent\.v1\.ModelDetails"/g;
45
55
  const selectedSubagentLegacyDetails = /selectedSubagentModelDetails:(?<options>[A-Za-z_$][\w$]*)\.selectedSubagentModelsLegacy/g;
56
+ const legacySelectedSubagentDetailsInjection = /selectedSubagentModelDetails:\/\*ocx-cursor-subagent-legacy-details-v1\*\/(?<options>[A-Za-z_$][\w$]*)\.selectedSubagentModelsLegacy\?\.length\?[\s\S]*?credentials:ocxCursorModel\.credentials\}\)\)/g;
46
57
  const agentCustomSystemPrompt = /customSystemPrompt:(?<options>[A-Za-z_$][\w$]*)\.customSystemPrompt(?=,harness:)/g;
47
58
  const agentRequestAction = /(?<prefix>conversationState:[A-Za-z_$][\w$]*\([A-Za-z_$][\w$]*\),action:)(?<action>[A-Za-z_$][\w$]*)(?=,modelDetails:)/g;
48
59
  const legacySubagentPromptRouting = /customSystemPrompt:\(\(\)=>\{\/\*ocx-cursor-subagent-prompt-routing-v1\*\/const ocxCursorModelIds=\((?<options>[A-Za-z_$][\w$]*)\.selectedSubagentModels[\s\S]*?\}\)\(\)(?=,harness:)/g;
49
60
  const legacySubagentRequestRouting = /(?<prefix>conversationState:[A-Za-z_$][\w$]*\([A-Za-z_$][\w$]*\),action:)\(\(\)=>\{\/\*ocx-cursor-subagent-prompt-routing-v2\*\/[\s\S]*?return (?<action>[A-Za-z_$][\w$]*)\}\)\(\)(?=,modelDetails:)/g;
50
61
  const createOrResumeSubagentStart = /async createOrResumeSubagent\((?<input>[A-Za-z_$][\w$]*)\)\{/g;
51
62
  const legacySubagentExecutionRouting = /(?<start>async createOrResumeSubagent\((?<input>[A-Za-z_$][\w$]*)\)\{)\/\*ocx-cursor-subagent-execution-routing-v[1-4]\*\/[\s\S]*?(?=let [A-Za-z_$][\w$]*,[A-Za-z_$][\w$]*=\k<input>\.resumeAgentId;)/g;
63
+ const currentSubagentExecutionRouting = /(?<start>async createOrResumeSubagent\((?<input>[A-Za-z_$][\w$]*)\)\{)\/\*ocx-cursor-subagent-execution-routing-v5\*\/[\s\S]*?(?=const [A-Za-z_$][\w$]*=\{mode:)/g;
64
+ const subagentSpawnMethod = /(?<start>async _createOrResumeSubagentSpawn\((?<input>[A-Za-z_$][\w$]*),[A-Za-z_$][\w$]*\)\{)(?<body>[\s\S]*?)(?=async runSubagentWithHandle\()/g;
52
65
  const legacySubagentModelConfigFixup = /(?<fixup>[A-Za-z_$][\w$]*)=ocxCursorRequestedModel\?void 0:typeof this\._modelConfigService\.fixupModelConfigForCurrentFlag=="function"\?this\._modelConfigService\.fixupModelConfigForCurrentFlag\(\{modelName:(?<input>[A-Za-z_$][\w$]*)\.modelId,maxMode:(?<composerConfig>[A-Za-z_$][\w$]*)\.maxMode===!0\}\):void 0/g;
53
66
  const subagentModelConfigFixup = /(?<fixup>[A-Za-z_$][\w$]*)=typeof this\._modelConfigService\.fixupModelConfigForCurrentFlag=="function"\?this\._modelConfigService\.fixupModelConfigForCurrentFlag\(\{modelName:(?<input>[A-Za-z_$][\w$]*)\.modelId,maxMode:(?<composerConfig>[A-Za-z_$][\w$]*)\.maxMode===!0\}\):void 0/g;
54
67
  const appendSubagentComposer = /(?<declaration>const (?<result>[A-Za-z_$][\w$]*)=await this\._composerDataService\.appendSubComposer\((?<composer>[A-Za-z_$][\w$]*)\))/g;
55
68
  const runSubagentWithHandle = /async runSubagentWithHandle\((?<input>[A-Za-z_$][\w$]*),(?<handle>[A-Za-z_$][\w$]*)\)\{return this\._runSubagent\(\k<input>,\k<handle>\)\}/g;
56
69
  const runSubagentModelConfigFixup = /(?<fixed>[A-Za-z_$][\w$]*)=\(typeof this\._modelConfigService\.fixupModelConfigForCurrentFlag=="function"\?this\._modelConfigService\.fixupModelConfigForCurrentFlag\(\{modelName:(?<resolved>[A-Za-z_$][\w$]*),maxMode:(?<maxMode>[A-Za-z_$][\w$]*)\}\):void 0\)\?\.selectedModels\?\?\[\],(?<stored>[A-Za-z_$][\w$]*)=(?<config>[A-Za-z_$][\w$]*)\?\.selectedModels\?\?\[\]/g;
57
70
  const requestedModelCredentials = /(?<prefix>new [A-Za-z_$][\w$]*\(\{modelId:(?<model>[A-Za-z_$][\w$]*),maxMode:(?<details>[A-Za-z_$][\w$]*)\.maxMode,parameters:[\s\S]*?,credentials:)this\.convertModelDetailsToCredentials\(\k<details>\)(?<suffix>\}\))(?=\}convertModelDetailsToCredentials)/g;
71
+ const subagentModelDisplaySelection = /function (?<functionName>[A-Za-z_$][\w$]*)\((?<input>[A-Za-z_$][\w$]*)\)\{return \k<input>\.resumeTargetComposerId!==void 0\?\k<input>\.subagentModelName:\k<input>\.bestOfNModelName!==void 0\?\k<input>\.bestOfNModelName:\k<input>\.taskParamModelName\?\?\k<input>\.subagentModelName\}/g;
72
+ const legacySubagentModelDisplaySelection = /function (?<functionName>[A-Za-z_$][\w$]*)\((?<input>[A-Za-z_$][\w$]*)\)\{\/\*ocx-cursor-subagent-model-display-v1\*\/return \k<input>\.subagentModelName\?\.startsWith\("opencodex\/"\)\?\k<input>\.subagentModelName:\k<input>\.resumeTargetComposerId!==void 0\?\k<input>\.subagentModelName:\k<input>\.bestOfNModelName!==void 0\?\k<input>\.bestOfNModelName:\k<input>\.taskParamModelName\?\?\k<input>\.subagentModelName\}/g;
73
+ const subagentTaskArgsContext = /(?<args>[A-Za-z_$][\w$]*)=(?<argsExtractor>[A-Za-z_$][\w$]*)\((?<bubble>[A-Za-z_$][\w$]*)\),(?<resume>[A-Za-z_$][\w$]*)=(?<resumeExtractor>[A-Za-z_$][\w$]*)\(\k<args>\),[\s\S]{0,400}?(?<taskModel>[A-Za-z_$][\w$]*)=\k<resume>===void 0\?\k<bubble>\.params\?\.model:void 0/g;
74
+ const directSubagentTaskArgsContext = /subagentTypeName:(?<bubble>[A-Za-z_$][\w$]*)\.params\?\.name[\s\S]{0,2500}?taskParamModelName:(?<taskModel>[A-Za-z_$][\w$]*)\}\);return/g;
75
+ const legacySubagentTaskArgsDisplayV4 = /taskParamModelName:\/\*ocx-cursor-subagent-task-args-display-v4\*\/\(\(\)=>\{[\s\S]*?\}\)\(\)/g;
76
+ const legacySubagentTaskArgsDisplayV3 = /taskParamModelName:\/\*ocx-cursor-subagent-task-args-display-v3\*\/\(\(\)=>\{[\s\S]*?return ocxCursorTaskModel\}\)\(\)/g;
77
+ const legacySubagentTaskArgsDisplayV2 = /taskParamModelName:\/\*ocx-cursor-subagent-task-args-display-v2\*\/\(\(\)=>\{[\s\S]*?return ocxCursorTaskModel\}\)\(\)/g;
78
+ const legacySubagentTaskArgsDisplay = /taskParamModelName:\/\*ocx-cursor-subagent-task-args-display-v1\*\/(?<taskModel>[A-Za-z_$][\w$]*)\?\?(?<args>[A-Za-z_$][\w$]*)\?\.model/g;
58
79
  const rawSubagentLoad = /(?<raw>[A-Za-z_$][\w$]*)=this\.subagentsService\.peekRawSubagents\(\)/g;
59
80
  const localModelConstructor = /function (?<functionName>[A-Za-z_$][\w$]*)\(e,t\)\{return new (?<modelType>[A-Za-z_$][\w$]*\.[A-Za-z_$][\w$]*)\(\{modelId:e,displayModelId:e,displayName:null!=t\?t:e,displayNameShort:null!=t\?t:e,aliases:\[\]\}\)\}/g;
60
81
  const localRuntimeVisionCapability = /"boolean"==typeof (?<object>[A-Za-z_$][\w$]*)\.supports_vision\?\{supports_vision:\k<object>\.supports_vision\}:\{\}\)/g;
@@ -67,16 +88,45 @@ const directoryPatchQueues = new Map();
67
88
 
68
89
  export function clearCursorAppQuarantine(options = {}) {
69
90
  const execute = options.execFileSync || execFileSync;
70
- execute("/usr/bin/xattr", ["-dr", "com.apple.quarantine", options.appPath || cursorAppPath], {
71
- stdio: "ignore",
72
- });
91
+ const appPath = options.appPath || cursorAppPath;
92
+ try {
93
+ execute("/usr/bin/xattr", ["-dr", "com.apple.quarantine", appPath], {
94
+ stdio: "ignore",
95
+ });
96
+ } catch (error) {
97
+ // Read-only nested app directories can make recursive xattr traversal fail
98
+ // even after the bundle root has no quarantine attribute. List root
99
+ // attributes so probe failures and a real remaining quarantine are never
100
+ // hidden.
101
+ try {
102
+ const rootAttributes = execute("/usr/bin/xattr", [appPath], {
103
+ encoding: "utf8",
104
+ });
105
+ const hasRootQuarantine = String(rootAttributes)
106
+ .split(/\r?\n/)
107
+ .some((attribute) => attribute.trim() === "com.apple.quarantine");
108
+ if (!hasRootQuarantine) return;
109
+ } catch {
110
+ // Fail closed when the root probe itself cannot be completed.
111
+ }
112
+ throw error;
113
+ }
73
114
  }
74
115
 
75
116
  export function finalizeCursorAppPatch(options = {}) {
76
117
  const execute = options.execFileSync || execFileSync;
77
118
  const appPath = options.appPath || cursorAppPath;
119
+ const verifyArgs = ["--verify", "--deep", "--strict", appPath];
120
+ let signatureIsValid = false;
121
+ try {
122
+ execute("/usr/bin/codesign", verifyArgs, { stdio: "ignore" });
123
+ signatureIsValid = true;
124
+ } catch {
125
+ // A rewritten bundle invalidates Cursor's signature and needs a fresh ad-hoc signature.
126
+ }
127
+ if (signatureIsValid) return;
78
128
  execute("/usr/bin/codesign", ["--force", "--sign", "-", appPath], { stdio: "ignore" });
79
- execute("/usr/bin/codesign", ["--verify", "--deep", "--strict", appPath], { stdio: "ignore" });
129
+ execute("/usr/bin/codesign", verifyArgs, { stdio: "ignore" });
80
130
  clearCursorAppQuarantine({ ...options, appPath, execFileSync: execute });
81
131
  }
82
132
 
@@ -158,6 +208,14 @@ export function patchCursorExplicitSubagentModelsSource(source) {
158
208
 
159
209
  export function patchCursorSubagentCredentialsSource(source) {
160
210
  if (source.includes(cursorSubagentCredentialsPatchMarker)) return { status: "already-patched", source };
211
+ if (source.includes(legacyCursorSubagentCredentialsPatchMarker)) {
212
+ let upgraded = source.replace(legacyCursorSubagentCredentialsPatchMarker, cursorSubagentCredentialsPatchMarker);
213
+ upgraded = replaceSingleMatch(upgraded, explicitSubagentByokGuard, (match) => {
214
+ const { hasByok, modelDetails, flagEnabled, maxMode } = match.groups;
215
+ return `if(${hasByok}(${modelDetails})&&!${modelDetails}.modelName?.startsWith("opencodex/")||!(${flagEnabled}||${maxMode}))return{};`;
216
+ }, "explicit subagent BYOK guard");
217
+ return { status: "patched", source: upgraded };
218
+ }
161
219
  let patched = replaceSingleMatch(source, explicitSubagentModelSelection, (match) => {
162
220
  const { functionName, input, modelDetails, flagEnabled, availableModels, storage, resolveParameters, maxMode } = match.groups;
163
221
  return `function ${functionName}(${input}){${cursorSubagentCredentialsPatchMarker}const{modelDetails:${modelDetails},flagEnabled:${flagEnabled},availableModels:${availableModels},storage:${storage},resolveModelParametersForSubmission:${resolveParameters},resolveCredentialsForSubmission:ocxCursorResolveCredentials}=${input},${maxMode}=${modelDetails}.maxMode===!0;`;
@@ -170,19 +228,29 @@ export function patchCursorSubagentCredentialsSource(source) {
170
228
  const { modelDetails, selector, featureGate, model, maxMode } = match.groups;
171
229
  return `getSelectedSubagentModelSelections(${modelDetails}){return ${selector}({modelDetails:${modelDetails},flagEnabled:${featureGate},availableModels:this.modelConfigService.getAvailableDefaultModels(),storage:this.reactiveStorageService.applicationUserPersistentStorage,resolveModelParametersForSubmission:(${model},${maxMode})=>this.modelConfigService.resolveModelParametersForSubmission(${model},void 0,${maxMode}),resolveCredentialsForSubmission:ocxCursorModel=>ocxCursorModel.startsWith("opencodex/")?this.convertModelDetailsToCredentials({apiKey:this.cursorAuthenticationService.openAIKey()??void 0,openaiApiBaseUrl:this.reactiveStorageService.applicationUserPersistentStorage.openAIBaseUrl}):void 0})}`;
172
230
  }, "explicit subagent selection caller");
231
+ patched = replaceSingleMatch(patched, explicitSubagentByokGuard, (match) => {
232
+ const { hasByok, modelDetails, flagEnabled, maxMode } = match.groups;
233
+ return `if(${hasByok}(${modelDetails})&&!${modelDetails}.modelName?.startsWith("opencodex/")||!(${flagEnabled}||${maxMode}))return{};`;
234
+ }, "explicit subagent BYOK guard");
173
235
  return { status: "patched", source: patched };
174
236
  }
175
237
 
176
238
  export function patchCursorSubagentLegacyDetailsSource(source) {
177
239
  if (source.includes(cursorSubagentLegacyDetailsPatchMarker)) return { status: "already-patched", source };
178
- const modelDetailsMatches = [...source.matchAll(agentModelDetailsType)];
240
+ let restored = source;
241
+ if (restored.includes(legacyCursorSubagentLegacyDetailsPatchMarker)) {
242
+ restored = replaceSingleMatch(restored, legacySelectedSubagentDetailsInjection, (match) => {
243
+ return `selectedSubagentModelDetails:${match.groups.options}.selectedSubagentModelsLegacy`;
244
+ }, "legacy explicit subagent model details");
245
+ }
246
+ const modelDetailsMatches = [...restored.matchAll(agentModelDetailsType)];
179
247
  if (modelDetailsMatches.length !== 1) {
180
248
  throw new Error(`Expected one Cursor agent model details type, found ${modelDetailsMatches.length}`);
181
249
  }
182
250
  const modelDetailsType = modelDetailsMatches[0].groups.type;
183
- const patched = replaceSingleMatch(source, selectedSubagentLegacyDetails, (match) => {
251
+ const patched = replaceSingleMatch(restored, selectedSubagentLegacyDetails, (match) => {
184
252
  const { options } = match.groups;
185
- return `selectedSubagentModelDetails:${cursorSubagentLegacyDetailsPatchMarker}${options}.selectedSubagentModelsLegacy?.length?${options}.selectedSubagentModelsLegacy:${options}.selectedSubagentModels?.filter(ocxCursorModel=>ocxCursorModel.modelId.startsWith("opencodex/")).map(ocxCursorModel=>new ${modelDetailsType}({modelId:ocxCursorModel.modelId,displayModelId:ocxCursorModel.modelId,displayName:ocxCursorModel.modelId,displayNameShort:ocxCursorModel.modelId,aliases:[],maxMode:ocxCursorModel.maxMode,credentials:ocxCursorModel.credentials}))`;
253
+ return `selectedSubagentModelDetails:${cursorSubagentLegacyDetailsPatchMarker}[...(${options}.selectedSubagentModelsLegacy??[]),...(${options}.selectedSubagentModels??[]).filter(ocxCursorModel=>ocxCursorModel.modelId.startsWith("opencodex/")&&!(${options}.selectedSubagentModelsLegacy??[]).some(ocxCursorLegacyModel=>ocxCursorLegacyModel.modelId===ocxCursorModel.modelId)).map(ocxCursorModel=>new ${modelDetailsType}({modelId:ocxCursorModel.modelId,displayModelId:ocxCursorModel.modelId,displayName:ocxCursorModel.modelId,displayNameShort:ocxCursorModel.modelId,aliases:[],maxMode:ocxCursorModel.maxMode,credentials:ocxCursorModel.credentials}))]`;
186
254
  }, "legacy explicit subagent model details");
187
255
  return { status: "patched", source: patched };
188
256
  }
@@ -215,11 +283,34 @@ export function patchCursorSubagentPromptRoutingSource(source) {
215
283
  export function patchCursorSubagentExecutionRoutingSource(source) {
216
284
  if (source.includes(cursorSubagentExecutionRoutingPatchMarker)) return { status: "already-patched", source };
217
285
  let restored = source;
286
+ let restoredLegacySpawn = false;
287
+ if (restored.includes("/*ocx-cursor-subagent-execution-routing-v5*/")) {
288
+ restored = replaceSingleMatch(restored, currentSubagentExecutionRouting, (match) => match.groups.start, "current subagent execution routing");
289
+ const spawnMatches = [...restored.matchAll(subagentSpawnMethod)];
290
+ if (spawnMatches.length === 1) {
291
+ const { start, input, body } = spawnMatches[0].groups;
292
+ const routedBody = body
293
+ .replaceAll("ocxCursorRequestedModel?void 0:", `${input}.modelId?.startsWith("opencodex/")?void 0:`)
294
+ .replaceAll("ocxCursorRequestedModel&&", `${input}.modelId?.startsWith("opencodex/")&&`)
295
+ .replaceAll("modelName:ocxCursorRequestedModel", `modelName:${input}.modelId`)
296
+ .replaceAll("modelId:ocxCursorRequestedModel", `modelId:${input}.modelId`);
297
+ if (routedBody !== body) {
298
+ restored = replaceSingleMatch(restored, subagentSpawnMethod, `${start}${routedBody}`, "current subagent spawn routing");
299
+ restoredLegacySpawn = true;
300
+ }
301
+ }
302
+ }
218
303
  if (restored.includes("/*ocx-cursor-subagent-execution-routing-v4*/")) {
219
- restored = replaceSingleMatch(restored, legacySubagentModelConfigFixup, (match) => {
220
- const { fixup, input, composerConfig } = match.groups;
221
- return `${fixup}=typeof this._modelConfigService.fixupModelConfigForCurrentFlag=="function"?this._modelConfigService.fixupModelConfigForCurrentFlag({modelName:${input}.modelId,maxMode:${composerConfig}.maxMode===!0}):void 0`;
222
- }, "legacy subagent model config fixup");
304
+ const legacyFixups = [...restored.matchAll(legacySubagentModelConfigFixup)];
305
+ if (legacyFixups.length > 1) {
306
+ throw new Error(`Expected at most one Cursor legacy subagent model config fixup, found ${legacyFixups.length}`);
307
+ }
308
+ if (legacyFixups.length === 1) {
309
+ restored = replaceSingleMatch(restored, legacySubagentModelConfigFixup, (match) => {
310
+ const { fixup, input, composerConfig } = match.groups;
311
+ return `${fixup}=typeof this._modelConfigService.fixupModelConfigForCurrentFlag=="function"?this._modelConfigService.fixupModelConfigForCurrentFlag({modelName:${input}.modelId,maxMode:${composerConfig}.maxMode===!0}):void 0`;
312
+ }, "legacy subagent model config fixup");
313
+ }
223
314
  }
224
315
  if (restored.includes("/*ocx-cursor-subagent-execution-routing-v1*/") || restored.includes("/*ocx-cursor-subagent-execution-routing-v2*/") || restored.includes("/*ocx-cursor-subagent-execution-routing-v3*/") || restored.includes("/*ocx-cursor-subagent-execution-routing-v4*/")) {
225
316
  restored = replaceSingleMatch(restored, legacySubagentExecutionRouting, (match) => match.groups.start, "legacy subagent execution routing");
@@ -228,14 +319,28 @@ export function patchCursorSubagentExecutionRoutingSource(source) {
228
319
  const { input } = match.groups;
229
320
  return `${match[0]}${cursorSubagentExecutionRoutingPatchMarker}const ocxCursorModelMarker=typeof ${input}.prompt==="string"?/^\\s*<ocx-subagent-model>(opencodex\\/[A-Za-z0-9._\\/-]+)<\\/ocx-subagent-model>\\s*/.exec(${input}.prompt):null,ocxCursorRequestedModel=ocxCursorModelMarker?.[1];if(ocxCursorRequestedModel&&!${input}.resumeAgentId){const ocxCursorModelAvailable=this._modelConfigService.getAvailableDefaultModels().some(ocxCursorModel=>ocxCursorModel?.name===ocxCursorRequestedModel);if(!ocxCursorModelAvailable)throw new Error("OpenCodex subagent model is unavailable: "+ocxCursorRequestedModel);${input}={...${input},modelId:ocxCursorRequestedModel}}if(ocxCursorModelMarker)${input}={...${input},prompt:${input}.prompt.slice(ocxCursorModelMarker[0].length)};`;
230
321
  }, "subagent execution entrypoint");
231
- patched = replaceSingleMatch(patched, subagentModelConfigFixup, (match) => {
232
- const { fixup, input, composerConfig } = match.groups;
233
- return `${fixup}=ocxCursorRequestedModel?void 0:typeof this._modelConfigService.fixupModelConfigForCurrentFlag=="function"?this._modelConfigService.fixupModelConfigForCurrentFlag({modelName:${input}.modelId,maxMode:${composerConfig}.maxMode===!0}):void 0`;
234
- }, "subagent model config fixup");
235
- patched = replaceSingleMatch(patched, appendSubagentComposer, (match) => {
236
- const { declaration, composer } = match.groups;
237
- return `ocxCursorRequestedModel&&(${composer}.modelConfig={modelName:ocxCursorRequestedModel,maxMode:${composer}.modelConfig?.maxMode===!0,selectedModels:[{modelId:ocxCursorRequestedModel,parameters:[]}]});${declaration}`;
238
- }, "final subagent model config");
322
+ const configFixups = [...patched.matchAll(subagentModelConfigFixup)];
323
+ if (configFixups.length > 1) throw new Error(`Expected at most one Cursor subagent model config fixup, found ${configFixups.length}`);
324
+ const allowMissingConfigFixup = restoredLegacySpawn || source.includes("/*ocx-cursor-subagent-execution-routing-v4*/");
325
+ if (configFixups.length === 0 && !allowMissingConfigFixup) {
326
+ throw new Error("Expected one Cursor subagent model config fixup, found 0");
327
+ }
328
+ if (configFixups.length === 1) {
329
+ patched = replaceSingleMatch(patched, subagentModelConfigFixup, (match) => {
330
+ const { fixup, input, composerConfig } = match.groups;
331
+ // The execution entrypoint and the composer-spawn helper are separate
332
+ // methods in current Cursor builds. The requested model marker is scoped
333
+ // to createOrResumeSubagent, so derive routing from the already-normalized
334
+ // request model here instead of referencing that out-of-scope variable.
335
+ return `${fixup}=${input}.modelId?.startsWith("opencodex/")?void 0:typeof this._modelConfigService.fixupModelConfigForCurrentFlag=="function"?this._modelConfigService.fixupModelConfigForCurrentFlag({modelName:${input}.modelId,maxMode:${composerConfig}.maxMode===!0}):void 0`;
336
+ }, "subagent model config fixup");
337
+ }
338
+ if (!restoredLegacySpawn) {
339
+ patched = replaceSingleMatch(patched, appendSubagentComposer, (match) => {
340
+ const { declaration, composer } = match.groups;
341
+ return `(${composer}.modelConfig?.modelName?.startsWith("opencodex/"))&&(${composer}.modelConfig={modelName:${composer}.modelConfig.modelName,maxMode:${composer}.modelConfig?.maxMode===!0,selectedModels:[{modelId:${composer}.modelConfig.modelName,parameters:[]}]});${declaration}`;
342
+ }, "final subagent model config");
343
+ }
239
344
  return { status: "patched", source: patched };
240
345
  }
241
346
 
@@ -261,6 +366,56 @@ export function patchCursorSubagentRuntimeCredentialsSource(source) {
261
366
  return { status: "patched", source: patched };
262
367
  }
263
368
 
369
+ export function patchCursorSubagentModelDisplaySource(source) {
370
+ if (source.includes(cursorSubagentModelDisplayPatchMarker)) return { status: "already-patched", source };
371
+ let restored = source;
372
+ if (restored.includes(legacyCursorSubagentModelDisplayPatchMarker)) {
373
+ restored = replaceSingleMatch(restored, legacySubagentModelDisplaySelection, (match) => {
374
+ const { functionName, input } = match.groups;
375
+ return `function ${functionName}(${input}){return ${input}.resumeTargetComposerId!==void 0?${input}.subagentModelName:${input}.bestOfNModelName!==void 0?${input}.bestOfNModelName:${input}.taskParamModelName??${input}.subagentModelName}`;
376
+ }, "legacy subagent model display selector");
377
+ }
378
+ const patched = replaceSingleMatch(restored, subagentModelDisplaySelection, (match) => {
379
+ const { functionName, input } = match.groups;
380
+ return `function ${functionName}(${input}){${cursorSubagentModelDisplayPatchMarker}return ${input}.resumeTargetComposerId!==void 0?${input}.subagentModelName:${input}.bestOfNModelName!==void 0?${input}.bestOfNModelName:${input}.taskParamModelName?.startsWith("opencodex/")?${input}.taskParamModelName:${input}.taskParamModelName?.startsWith("composer-")&&${input}.subagentModelName?.startsWith("opencodex/")?${input}.subagentModelName:${input}.taskParamModelName??${input}.subagentModelName}`;
381
+ }, "subagent model display selector");
382
+ return { status: "patched", source: patched };
383
+ }
384
+
385
+ export function patchCursorSubagentTaskArgsDisplaySource(source) {
386
+ if (source.includes(cursorSubagentTaskArgsDisplayPatchMarker)) return { status: "already-patched", source };
387
+ const legacyContexts = [...source.matchAll(subagentTaskArgsContext)];
388
+ const contexts = legacyContexts.length > 0
389
+ ? legacyContexts
390
+ : [...source.matchAll(directSubagentTaskArgsContext)];
391
+ if (contexts.length !== 1) {
392
+ throw new Error(`Expected one Cursor subagent task args context, found ${contexts.length}`);
393
+ }
394
+ const { args, bubble, taskModel } = contexts[0].groups;
395
+ const prompt = args ? `${bubble}.params?.prompt??${args}?.prompt??""` : `${bubble}.params?.prompt??""`;
396
+ const fallback = args ? `${taskModel}??${args}?.model` : taskModel;
397
+ const replacement = `taskParamModelName:${cursorSubagentTaskArgsDisplayPatchMarker}/<ocx-subagent-model>(opencodex\\/[^<]+)<\\/ocx-subagent-model>/.exec(${prompt})?.[1]??${fallback}`;
398
+ if (source.includes(legacyCursorSubagentTaskArgsDisplayV4PatchMarker)) {
399
+ const upgraded = replaceSingleMatch(source, legacySubagentTaskArgsDisplayV4, replacement, "v4 subagent task args model display field");
400
+ return { status: "patched", source: upgraded };
401
+ }
402
+ if (source.includes(legacyCursorSubagentTaskArgsDisplayV3PatchMarker)) {
403
+ const upgraded = replaceSingleMatch(source, legacySubagentTaskArgsDisplayV3, replacement, "v3 subagent task args model display field");
404
+ return { status: "patched", source: upgraded };
405
+ }
406
+ if (source.includes(legacyCursorSubagentTaskArgsDisplayV2PatchMarker)) {
407
+ const upgraded = replaceSingleMatch(source, legacySubagentTaskArgsDisplayV2, replacement, "v2 subagent task args model display field");
408
+ return { status: "patched", source: upgraded };
409
+ }
410
+ if (source.includes(legacyCursorSubagentTaskArgsDisplayPatchMarker)) {
411
+ const upgraded = replaceSingleMatch(source, legacySubagentTaskArgsDisplay, replacement, "legacy subagent task args model display field");
412
+ return { status: "patched", source: upgraded };
413
+ }
414
+ const field = new RegExp(`taskParamModelName:${taskModel}(?=[},])`, "g");
415
+ const patched = replaceSingleMatch(source, field, replacement, "subagent task args model display field");
416
+ return { status: "patched", source: patched };
417
+ }
418
+
264
419
  export function patchCursorRoutedSubagentTypesSource(source) {
265
420
  if (source.includes(cursorRoutedSubagentTypesPatchMarker)) return { status: "already-patched", source };
266
421
  const patched = replaceSingleMatch(source, rawSubagentLoad, (match) => {
@@ -280,9 +435,11 @@ export function patchCursorWorkbenchSource(source) {
280
435
  const executionRouting = patchCursorSubagentExecutionRoutingSource(promptRouting.source);
281
436
  const runRouting = patchCursorSubagentRunRoutingSource(executionRouting.source);
282
437
  const runtimeCredentials = patchCursorSubagentRuntimeCredentialsSource(runRouting.source);
283
- const subagentTypes = patchCursorRoutedSubagentTypesSource(runtimeCredentials.source);
438
+ const modelDisplay = patchCursorSubagentModelDisplaySource(runtimeCredentials.source);
439
+ const taskArgsDisplay = patchCursorSubagentTaskArgsDisplaySource(modelDisplay.source);
440
+ const subagentTypes = patchCursorRoutedSubagentTypesSource(taskArgsDisplay.source);
284
441
  return {
285
- status: metadata.status === "patched" || routing.status === "patched" || subagents.status === "patched" || credentials.status === "patched" || legacyDetails.status === "patched" || promptRouting.status === "patched" || executionRouting.status === "patched" || runRouting.status === "patched" || runtimeCredentials.status === "patched" || subagentTypes.status === "patched"
442
+ status: metadata.status === "patched" || routing.status === "patched" || subagents.status === "patched" || credentials.status === "patched" || legacyDetails.status === "patched" || promptRouting.status === "patched" || executionRouting.status === "patched" || runRouting.status === "patched" || runtimeCredentials.status === "patched" || modelDisplay.status === "patched" || taskArgsDisplay.status === "patched" || subagentTypes.status === "patched"
286
443
  ? "patched"
287
444
  : "already-patched",
288
445
  source: subagentTypes.source,
package/src/gateway.mjs CHANGED
@@ -45,7 +45,169 @@ function disableStrictFunctionTools(tools) {
45
45
  }
46
46
  }
47
47
 
48
- export function rewriteModelAliasBody(body, catalog) {
48
+ function normalizeCursorTool(tool) {
49
+ if (!isRecord(tool) || tool.type !== undefined || typeof tool.name !== "string" || !isRecord(tool.input_schema)) {
50
+ return tool;
51
+ }
52
+ const definition = {
53
+ name: tool.name,
54
+ ...(typeof tool.description === "string" ? { description: tool.description } : {}),
55
+ parameters: tool.input_schema,
56
+ ...(typeof tool.strict === "boolean" ? { strict: tool.strict } : {}),
57
+ };
58
+ return { type: "function", function: definition };
59
+ }
60
+
61
+ function normalizeToolChoice(choice) {
62
+ if (!isRecord(choice) || typeof choice.type !== "string") return choice;
63
+ if (choice.type === "auto") return "auto";
64
+ if (choice.type === "none") return "none";
65
+ if (choice.type === "any") return "required";
66
+ if (choice.type === "tool" && typeof choice.name === "string") {
67
+ return { type: "function", function: { name: choice.name } };
68
+ }
69
+ return choice;
70
+ }
71
+
72
+ function normalizeCursorTooling(payload) {
73
+ if (Array.isArray(payload.tools)) payload.tools = payload.tools.map(normalizeCursorTool);
74
+ if (payload.tool_choice !== undefined) payload.tool_choice = normalizeToolChoice(payload.tool_choice);
75
+ }
76
+
77
+ function toolResultText(value) {
78
+ if (typeof value === "string") return value;
79
+ if (Array.isArray(value)) return value.map(toolResultText).filter(Boolean).join("\n");
80
+ if (isRecord(value)) {
81
+ if (typeof value.text === "string") return value.text;
82
+ if (value.content !== undefined) return toolResultText(value.content);
83
+ try {
84
+ return JSON.stringify(value);
85
+ } catch {
86
+ return "";
87
+ }
88
+ }
89
+ return value === undefined || value === null ? "" : String(value);
90
+ }
91
+
92
+ function openAiToolCallFromCursorBlock(block) {
93
+ let args = "{}";
94
+ try {
95
+ args = JSON.stringify(block.input ?? {}) ?? "{}";
96
+ } catch {
97
+ // Keep the request valid if Cursor supplied a non-serializable input value.
98
+ }
99
+ return {
100
+ id: block.id,
101
+ type: "function",
102
+ function: {
103
+ name: block.name,
104
+ arguments: args,
105
+ },
106
+ };
107
+ }
108
+
109
+ export function normalizeCursorChatMessages(messages) {
110
+ if (!Array.isArray(messages)) return messages;
111
+ const normalized = [];
112
+ for (const message of messages) {
113
+ if (!isRecord(message) || !Array.isArray(message.content)) {
114
+ normalized.push(message);
115
+ continue;
116
+ }
117
+ if (message.role === "assistant") {
118
+ const allToolUses = message.content.filter((block) => isRecord(block) && block.type === "tool_use");
119
+ const toolUses = allToolUses.filter((block) => typeof block.id === "string" && block.id.length > 0 && typeof block.name === "string");
120
+ if (allToolUses.length > toolUses.length) {
121
+ normalized.push(message);
122
+ continue;
123
+ }
124
+ if (toolUses.length === 0) {
125
+ normalized.push(message);
126
+ continue;
127
+ }
128
+ const existingToolCalls = Array.isArray(message.tool_calls) ? message.tool_calls : [];
129
+ const existingIds = new Set(existingToolCalls.map((call) => call?.id).filter((id) => typeof id === "string"));
130
+ const newToolUses = toolUses.filter((block) => !existingIds.has(block.id));
131
+ const content = message.content.filter((block) => !(isRecord(block) && block.type === "tool_use"));
132
+ normalized.push({
133
+ ...message,
134
+ content: content.length > 0 ? content : null,
135
+ tool_calls: [
136
+ ...existingToolCalls,
137
+ ...newToolUses.map((block) => openAiToolCallFromCursorBlock(block)),
138
+ ],
139
+ });
140
+ continue;
141
+ }
142
+ const toolResults = message.content.filter((block) => isRecord(block) && block.type === "tool_result");
143
+ if (message.role !== "user" || toolResults.length === 0) {
144
+ normalized.push(message);
145
+ continue;
146
+ }
147
+ if (toolResults.some((block) => typeof block.tool_use_id !== "string" || block.tool_use_id.length === 0)) {
148
+ normalized.push(message);
149
+ continue;
150
+ }
151
+ for (const block of toolResults) {
152
+ normalized.push({
153
+ role: "tool",
154
+ tool_call_id: block.tool_use_id,
155
+ content: toolResultText(block.content),
156
+ });
157
+ }
158
+ const remainingContent = message.content.filter((block) => !(isRecord(block) && block.type === "tool_result"));
159
+ if (remainingContent.length > 0) normalized.push({ ...message, content: remainingContent });
160
+ }
161
+ return normalized;
162
+ }
163
+
164
+ function enrichSubagentModelTools(tools, catalog) {
165
+ if (!Array.isArray(tools)) return;
166
+ const aliases = catalog.map(({ alias }) => alias).filter((alias) => alias.startsWith(managedPrefix));
167
+ if (aliases.length === 0) return;
168
+ const modelList = aliases.map((alias) => `- ${alias}`).join("\n");
169
+ const modelListPattern = /(If the user explicitly asks for the model of a subagent\/task,[\s\S]*?)(?=\n\nIf the user isn't asking)/;
170
+ for (const tool of tools) {
171
+ if (!isRecord(tool) || tool.type !== "function") continue;
172
+ const definition = isRecord(tool.function) ? tool.function : tool;
173
+ if (typeof definition.name !== "string" || !/^(subagent|task)$/i.test(definition.name)) continue;
174
+ if (typeof definition.description === "string" && modelListPattern.test(definition.description)) {
175
+ definition.description = definition.description.replace(modelListPattern, `$1\n${modelList}`);
176
+ }
177
+ const model = definition.parameters?.properties?.model;
178
+ if (!isRecord(model)) continue;
179
+ if (Array.isArray(model.enum)) {
180
+ model.enum = [...new Set([...model.enum, ...aliases])];
181
+ }
182
+ if (typeof model.description === "string") {
183
+ model.description += ` Available OpenCodex model slugs: ${aliases.join(", ")}.`;
184
+ }
185
+ }
186
+ }
187
+
188
+ function restoreRoutedSubagentModels(messages, catalog) {
189
+ if (!Array.isArray(messages)) return;
190
+ const aliases = new Set(catalog.map(({ alias }) => alias));
191
+ const restore = (text) => {
192
+ if (typeof text !== "string") return text;
193
+ const requested = [...text.matchAll(/<ocx-subagent-model>(opencodex\/[^<]+)<\/ocx-subagent-model>/g)]
194
+ .map((match) => match[1])
195
+ .find((alias) => aliases.has(alias));
196
+ return requested ? text.split("composer-2.5").join(requested) : text;
197
+ };
198
+ for (const message of messages) {
199
+ if (!isRecord(message)) continue;
200
+ if (typeof message.content === "string") {
201
+ message.content = restore(message.content);
202
+ } else if (Array.isArray(message.content)) {
203
+ for (const part of message.content) {
204
+ if (isRecord(part) && typeof part.text === "string") part.text = restore(part.text);
205
+ }
206
+ }
207
+ }
208
+ }
209
+
210
+ export function rewriteModelAliasBody(body, catalog, route = "POST /v1/chat/completions") {
49
211
  if (!body?.length) return body;
50
212
  let payload;
51
213
  try {
@@ -54,6 +216,12 @@ export function rewriteModelAliasBody(body, catalog) {
54
216
  return body;
55
217
  }
56
218
  if (!isRecord(payload) || typeof payload.model !== "string" || !payload.model.startsWith(managedPrefix)) return body;
219
+ restoreRoutedSubagentModels(payload.messages, catalog);
220
+ if (route === "POST /v1/chat/completions") {
221
+ payload.messages = normalizeCursorChatMessages(payload.messages);
222
+ normalizeCursorTooling(payload);
223
+ enrichSubagentModelTools(payload.tools, catalog);
224
+ }
57
225
 
58
226
  const variant = /^(opencodex\/.+?)\[([^\]]+)\]$/.exec(payload.model);
59
227
  let alias = variant?.[1] || payload.model;
@@ -203,7 +371,7 @@ export function startGateway(options) {
203
371
  const upstream = options.upstream || opencodexEndpoint();
204
372
  const receivedBody = await readBody(req);
205
373
  if (downstreamClosed) return;
206
- const body = rewriteModelAliasBody(receivedBody, catalog);
374
+ const body = rewriteModelAliasBody(receivedBody, catalog, route);
207
375
  const token = options.serviceToken !== undefined ? options.serviceToken : await serviceToken();
208
376
  if (downstreamClosed) return;
209
377
  upstreamReq = http.request({