@bitkyc08/opencodex 2.60.0 → 2.61.0

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.
Files changed (249) hide show
  1. package/AGENTS_INSTALL.md +64 -0
  2. package/README.md +28 -1
  3. package/bin/ocx.mjs +382 -209
  4. package/gui/dist/assets/App-CH6C5H7x.js +50 -0
  5. package/gui/dist/assets/Tray-CLZh48fM.js +1 -0
  6. package/gui/dist/assets/index-_bpvxJu0.css +1 -0
  7. package/gui/dist/assets/index-wpTOyepx.js +86 -0
  8. package/gui/dist/assets/usage-companion-chart-a0N58rRI.js +1 -0
  9. package/gui/dist/favicon.png +0 -0
  10. package/gui/dist/index.html +2 -2
  11. package/gui/dist/provider-icons/stepfun-color.svg +1 -0
  12. package/package.json +5 -1
  13. package/src/adapters/anthropic.ts +16 -0
  14. package/src/adapters/coding-agent/protocol.ts +36 -6
  15. package/src/adapters/coding-agent/turn.ts +10 -2
  16. package/src/adapters/command-code.ts +2 -1
  17. package/src/adapters/cursor/catalog.ts +51 -7
  18. package/src/adapters/cursor/protobuf-request.ts +6 -3
  19. package/src/adapters/cursor/request-builder.ts +13 -3
  20. package/src/adapters/cursor.ts +11 -2
  21. package/src/adapters/declaration-carrier.ts +45 -0
  22. package/src/adapters/devin.ts +75 -23
  23. package/src/adapters/google-antigravity-wire.ts +5 -2
  24. package/src/adapters/google-errors.ts +7 -1
  25. package/src/adapters/google.ts +29 -5
  26. package/src/adapters/image.ts +4 -1
  27. package/src/adapters/input-media-guard.ts +21 -9
  28. package/src/adapters/kiro/usage.ts +3 -2
  29. package/src/adapters/kiro-tool-fallback.ts +1 -1
  30. package/src/adapters/ollama-native.ts +6 -0
  31. package/src/adapters/openai-chat/developer-role.ts +61 -0
  32. package/src/adapters/openai-chat/messages.ts +46 -27
  33. package/src/adapters/openai-chat/parallel-tool-calls.ts +32 -0
  34. package/src/adapters/openai-chat/passthrough.ts +33 -9
  35. package/src/adapters/openai-chat/reasoning-wire.ts +89 -0
  36. package/src/adapters/openai-chat.ts +18 -57
  37. package/src/adapters/openai-responses/passthrough.ts +2 -0
  38. package/src/adapters/registry.ts +3 -2
  39. package/src/adapters/run-turn-queue.ts +178 -29
  40. package/src/adapters/xai-web-search.ts +16 -1
  41. package/src/bridge/errors.ts +8 -2
  42. package/src/bridge/response-json.ts +9 -1
  43. package/src/bridge/sse.ts +10 -0
  44. package/src/chat/inbound.ts +141 -5
  45. package/src/claude/desktop-3p.ts +7 -1
  46. package/src/claude/desktop-first-party.ts +183 -0
  47. package/src/claude/desktop-gateway-state.ts +41 -0
  48. package/src/claude/inbound-content-options.ts +6 -0
  49. package/src/claude/inbound.ts +32 -6
  50. package/src/claude/intercept/connect-proxy.ts +179 -0
  51. package/src/claude/intercept/listener.ts +122 -0
  52. package/src/claude/intercept/local-ca.ts +298 -0
  53. package/src/claude/intercept/runtime.ts +98 -0
  54. package/src/claude/intercept/settings.ts +189 -0
  55. package/src/cli/access.ts +87 -0
  56. package/src/cli/account-auth.ts +19 -0
  57. package/src/cli/capabilities.ts +31 -0
  58. package/src/cli/claude-desktop.ts +206 -16
  59. package/src/cli/codex-shim-autorestore.ts +3 -0
  60. package/src/cli/companion.ts +56 -0
  61. package/src/cli/dispatch.ts +43 -4
  62. package/src/cli/ensure-desired-integrations.ts +43 -5
  63. package/src/cli/help.ts +7 -9
  64. package/src/cli/index.ts +200 -61
  65. package/src/cli/init.ts +8 -0
  66. package/src/cli/integrations.ts +7 -1
  67. package/src/cli/registry.ts +41 -2
  68. package/src/cli/resolve.ts +230 -0
  69. package/src/cli/root.ts +24 -1
  70. package/src/cli/start-ownership-publication.ts +56 -0
  71. package/src/cli/status-probes.ts +2 -18
  72. package/src/cli/status.ts +62 -0
  73. package/src/cli/stop-report.ts +143 -0
  74. package/src/cli/uninstall-plan.ts +9 -0
  75. package/src/client/machine-listener.ts +2 -5
  76. package/src/clients/aside-profiles.ts +4 -0
  77. package/src/clients/config-export/zcode-store.ts +157 -0
  78. package/src/clients/config-export.ts +36 -0
  79. package/src/codex/app-server-processes.ts +72 -40
  80. package/src/codex/auth-api/login-flow.ts +6 -1
  81. package/src/codex/autostart-health.ts +28 -0
  82. package/src/codex/catalog/build-entries.ts +2 -2
  83. package/src/codex/catalog/effort.ts +3 -3
  84. package/src/codex/catalog/provider-models.ts +24 -15
  85. package/src/codex/catalog/retained-sync.ts +2 -2
  86. package/src/codex/convergence.ts +2 -2
  87. package/src/codex/history-provider.ts +12 -1
  88. package/src/codex/inject/config-toml.ts +41 -6
  89. package/src/codex/inject/paginated-openai-compat.ts +90 -0
  90. package/src/codex/inject.ts +18 -15
  91. package/src/codex/injected-marker.ts +18 -0
  92. package/src/codex/main-account.ts +6 -0
  93. package/src/codex/model-cache.ts +52 -6
  94. package/src/codex/model-entitlement-admission.ts +59 -0
  95. package/src/codex/model-entitlements.ts +87 -44
  96. package/src/codex/native-main-admission.ts +83 -0
  97. package/src/codex/routing/health-store.ts +39 -0
  98. package/src/codex/routing/selection.ts +37 -1
  99. package/src/codex/routing.ts +5 -41
  100. package/src/codex/shim-templates.ts +29 -3
  101. package/src/companion/settings.ts +132 -0
  102. package/src/config/atomic-write.ts +117 -5
  103. package/src/config/load-degrade.ts +34 -7
  104. package/src/config/process-state.ts +1 -1
  105. package/src/config/schema/config-schema.ts +27 -1
  106. package/src/config/schema/leaf-validators.ts +47 -0
  107. package/src/config.ts +1 -1
  108. package/src/generated/compatibility-version.json +418 -174
  109. package/src/integrations/config-io.ts +44 -10
  110. package/src/integrations/merge.ts +120 -13
  111. package/src/integrations/mutation-plan.ts +124 -18
  112. package/src/integrations/registry.ts +38 -0
  113. package/src/integrations/state.ts +78 -45
  114. package/src/integrations/target.ts +208 -0
  115. package/src/integrations/writer.ts +49 -11
  116. package/src/lab/conformance/fixture-provider.ts +5 -0
  117. package/src/lib/browser-launch-notice.ts +59 -0
  118. package/src/lib/bun-runtime.ts +6 -2
  119. package/src/lib/debug.ts +40 -0
  120. package/src/lib/open-url.ts +51 -7
  121. package/src/lib/package-tree-integrity.ts +2 -1
  122. package/src/lib/package-version.ts +8 -0
  123. package/src/lib/provider-egress.ts +310 -0
  124. package/src/lib/provider-outbound.ts +59 -14
  125. package/src/lib/proxy-env.ts +82 -7
  126. package/src/lib/request-execution-budget.ts +72 -0
  127. package/src/lib/request-failure-attribution.ts +183 -0
  128. package/src/lib/request-failure-model.ts +236 -0
  129. package/src/lib/request-resend-gate.ts +138 -0
  130. package/src/lib/standalone.ts +16 -0
  131. package/src/lib/upstream-retry.ts +167 -16
  132. package/src/lib/winsw.ts +2 -2
  133. package/src/oauth/index.ts +24 -1
  134. package/src/oauth/login-cli.ts +80 -29
  135. package/src/providers/api-key-resolve.ts +133 -0
  136. package/src/providers/api-key-selection.ts +5 -1
  137. package/src/providers/key-failover.ts +31 -1
  138. package/src/providers/key-store.ts +34 -110
  139. package/src/providers/model-rename-fields.ts +147 -0
  140. package/src/providers/model-rename-migration.ts +124 -37
  141. package/src/providers/quota/vendor-probes-key.ts +37 -22
  142. package/src/providers/reasoning-metadata.ts +43 -18
  143. package/src/providers/registry/entries-core.ts +9 -4
  144. package/src/providers/registry/entries-extended.ts +29 -4
  145. package/src/providers/registry/model-seeds.ts +47 -10
  146. package/src/providers/xai-transport.ts +12 -1
  147. package/src/reasoning-effort.ts +8 -0
  148. package/src/responses/function-call-compat.ts +38 -1
  149. package/src/responses/inline-document.ts +65 -0
  150. package/src/responses/input-media.ts +42 -8
  151. package/src/responses/muse-tool-name-alias.ts +19 -0
  152. package/src/responses/parser-content.ts +8 -2
  153. package/src/responses/parser-tools.ts +3 -0
  154. package/src/responses/parser.ts +3 -1
  155. package/src/responses/schema.ts +3 -0
  156. package/src/router.ts +17 -2
  157. package/src/server/admission-model-scope.ts +219 -0
  158. package/src/server/audio-live.ts +9 -3
  159. package/src/server/audio-upstream.ts +18 -0
  160. package/src/server/auth-cors.ts +26 -0
  161. package/src/server/chat-completions.ts +55 -2
  162. package/src/server/chat-native.ts +19 -4
  163. package/src/server/claude-messages.ts +55 -17
  164. package/src/server/grok-responses-snapshot-repair.ts +113 -11
  165. package/src/server/gui-freshness.ts +103 -0
  166. package/src/server/gui-static.ts +7 -9
  167. package/src/server/images.ts +59 -6
  168. package/src/server/index/claude-intercept-lifecycle.ts +49 -0
  169. package/src/server/index/serve-options.ts +56 -10
  170. package/src/server/index/spend-ledger-lifecycle.ts +34 -8
  171. package/src/server/index/startup-warnings.ts +24 -0
  172. package/src/server/index.ts +21 -28
  173. package/src/server/lifecycle.ts +4 -4
  174. package/src/server/live-call-bindings.ts +6 -0
  175. package/src/server/live.ts +88 -3
  176. package/src/server/management/agent-settings-routes.ts +121 -36
  177. package/src/server/management/companion-routes.ts +77 -0
  178. package/src/server/management/logs-usage-routes.ts +19 -0
  179. package/src/server/management/native-integration-routes.ts +103 -6
  180. package/src/server/management/oauth-account-routes.ts +45 -7
  181. package/src/server/management/route-registry.ts +6 -0
  182. package/src/server/management/shared.ts +18 -1
  183. package/src/server/management/usage-timeline-routes.ts +44 -0
  184. package/src/server/management-api.ts +8 -9
  185. package/src/server/proxy-liveness.ts +75 -0
  186. package/src/server/relay.ts +19 -2
  187. package/src/server/request-log-failure-attribution.ts +99 -0
  188. package/src/server/request-log.ts +114 -0
  189. package/src/server/request-metrics.ts +92 -30
  190. package/src/server/responses/codex-ws-wire.ts +34 -8
  191. package/src/server/responses/combo-stream-preflight.ts +168 -6
  192. package/src/server/responses/compact.ts +11 -0
  193. package/src/server/responses/core-opaque-recovery.ts +90 -0
  194. package/src/server/responses/fetch-helpers.ts +124 -8
  195. package/src/server/responses/input-admission.ts +10 -0
  196. package/src/server/responses/passthrough-delivery.ts +14 -1
  197. package/src/server/responses/passthrough-dispatch.ts +179 -35
  198. package/src/server/responses/passthrough-error.ts +27 -8
  199. package/src/server/responses/request-prepare.ts +42 -1
  200. package/src/server/responses/request-send-budget.ts +12 -0
  201. package/src/server/responses/request-transport.ts +24 -4
  202. package/src/server/responses/reset-replay.ts +108 -0
  203. package/src/server/responses-request-tool-scope.ts +214 -0
  204. package/src/server/responses-undeclared-tool-guard.ts +4 -1
  205. package/src/server/search.ts +25 -1
  206. package/src/server/usage-ledger-retention.ts +73 -0
  207. package/src/service/cli.ts +48 -2
  208. package/src/service/health.ts +3 -2
  209. package/src/service/install-state-contract.d.mts +27 -0
  210. package/src/service/install-state-contract.mjs +34 -0
  211. package/src/service/launchd.ts +1 -1
  212. package/src/service/orchestration.ts +2 -4
  213. package/src/service/ownership-compatibility.ts +164 -0
  214. package/src/service/ownership-mutation-lease.d.mts +32 -0
  215. package/src/service/ownership-mutation-lease.mjs +211 -0
  216. package/src/service/repair.ts +45 -1
  217. package/src/service/state-lock.ts +269 -0
  218. package/src/service/state-record.d.mts +36 -0
  219. package/src/service/state-record.mjs +138 -0
  220. package/src/service/state.ts +582 -68
  221. package/src/service/windows-taskxml.ts +11 -10
  222. package/src/service.ts +7 -3
  223. package/src/tray/windows-tray.ps1 +1 -1
  224. package/src/types/config.ts +37 -0
  225. package/src/types/provider.ts +73 -0
  226. package/src/types/request.ts +28 -2
  227. package/src/types/tools.ts +19 -0
  228. package/src/types.ts +3 -0
  229. package/src/update/index.ts +207 -63
  230. package/src/update/job.ts +9 -5
  231. package/src/update/ownership-transaction.ts +47 -0
  232. package/src/update/restart-ownership.ts +54 -0
  233. package/src/update/runtime-ownership.d.mts +40 -0
  234. package/src/update/runtime-ownership.mjs +122 -0
  235. package/src/usage/attempt-delivery.ts +198 -0
  236. package/src/usage/cache-diagnostic.ts +305 -0
  237. package/src/usage/failure-fingerprint.ts +118 -0
  238. package/src/usage/failure-projection-cache.ts +174 -0
  239. package/src/usage/failure-projection.ts +174 -0
  240. package/src/usage/ledger-retention.ts +165 -0
  241. package/src/usage/log.ts +126 -79
  242. package/src/usage/request-outcome.ts +150 -0
  243. package/src/usage/retention-contract.ts +28 -0
  244. package/src/usage/summary.ts +2 -2
  245. package/src/usage/telemetry-contract.ts +237 -0
  246. package/src/usage/timeline.ts +236 -0
  247. package/src/web-search/alpha-search.ts +21 -1
  248. package/gui/dist/assets/index-BTuCbqQd.css +0 -1
  249. package/gui/dist/assets/index-DoBVdPHP.js +0 -134
@@ -0,0 +1 @@
1
+ import{n as e}from"./index-wpTOyepx.js";var t={anthropic:`claude-color.svg`,"anthropic-apikey":`claude-color.svg`,"azure-openai":`openai.svg`,chatgpt:`openai.svg`,"cloudflare-ai-gateway":`cloudflare-ai-gateway-color.svg`,"cloudflare-workers-ai":`cloudflare-ai-gateway-color.svg`,cline:`cline-color.svg`,"cline-pass":`cline-color.svg`,"command-code":`commandcode-color.svg`,commandcode:`commandcode-color.svg`,cursor:`cursor-color.svg`,deepseek:`deepseek-color.svg`,devin:`devin.svg`,firepass:`firepass-color.svg`,fireworks:`fireworks-color.svg`,github:`github-copilot-color.svg`,"github-copilot":`copilot-color.svg`,"gitlab-duo":`gitlab-duo-color.svg`,google:`gemini-color.svg`,"google-antigravity":`antigravity-color.svg`,"google-vertex":`gemini-color.svg`,groq:`groq-color.svg`,huggingface:`huggingface-color.svg`,kimi:`kimi-color.svg`,"kimi-code":`kimi-color.svg`,kiro:`kiro-color.svg`,"lm-studio":`lm-studio-color.svg`,"meta-model":`meta.svg`,"meta-muse":`meta.svg`,mistral:`mistral-color.svg`,minimax:`minimax.svg`,"minimax-cn":`minimax.svg`,moonshot:`moonshot-color.svg`,nvidia:`nvidia-color.svg`,ollama:`ollama-color.svg`,"ollama-cloud":`ollama-color.svg`,openai:`openai.svg`,"openai-apikey":`openai.svg`,"opencode-free":`opencode.svg`,"opencode-go":`opencode.svg`,"opencode-zen":`opencode.svg`,openrouter:`openrouter-color.svg`,opper:`opper.svg`,qianfan:`qianfan-color.svg`,qoder:`qoder.svg`,"qoder-cn":`qoder.svg`,alibaba:`alibaba-color.svg`,"alibaba-token-plan":`alibaba-color.svg`,"alibaba-token-plan-intl":`alibaba-color.svg`,baseten:`baseten.svg`,bizrouter:`bizrouter.svg`,cerebras:`cerebras.svg`,crusoe:`crusoe.svg`,deepinfra:`deepinfra.svg`,digitalocean:`digitalocean.svg`,featherless:`featherless.svg`,hyperbolic:`hyperbolic.svg`,kilo:`kilo.svg`,nanogpt:`nanogpt.svg`,nebius:`nebius.svg`,neuralwatt:`neuralwatt.svg`,nous:`nous.svg`,novita:`novita.svg`,orcarouter:`orcarouter.svg`,"orcarouter-oauth":`orcarouter.svg`,packycode:`packycode.svg`,parallel:`parallel.svg`,sambanova:`sambanova.svg`,scaleway:`scaleway.svg`,stepfun:`stepfun-color.svg`,siliconflow:`siliconflow.svg`,synthetic:`synthetic.svg`,together:`together.svg`,umans:`umans.svg`,venice:`venice.svg`,vultr:`vultr.svg`,litellm:`litellm.svg`,zenmux:`zenmux.svg`,zai:`zai.svg`,"zhipu-bigmodel":`zai.svg`,"zhipu-bigmodel-coding":`zai.svg`,"qwen-cloud":`qwen-portal-color.svg`,"vercel-ai-gateway":`vercel-ai-gateway-color.svg`,vllm:`vllm-color.svg`,xai:`grok.svg`,"mimo-free":`xiaomi-color.svg`,mimo:`xiaomi-color.svg`,xiaomi:`xiaomi-color.svg`,"xiaomi-mimo":`xiaomi-color.svg`},n={anthropic:`Anthropic Claude`,"anthropic-apikey":`Anthropic Claude`,chatgpt:`ChatGPT`,openai:`OpenAI (Codex login)`,"openai-apikey":`OpenAI API`,"azure-openai":`Azure OpenAI`,"cloudflare-ai-gateway":`Cloudflare AI Gateway`,"cloudflare-workers-ai":`Cloudflare Workers AI`,cline:`Cline`,"cline-pass":`ClinePass`,nvidia:`NVIDIA NIM`,ollama:`Ollama`,"ollama-cloud":`Ollama Cloud`,xai:`xAI Grok`,"mimo-free":`MiMo Free`,xiaomi:`Xiaomi`,cursor:`Cursor`,deepseek:`DeepSeek`,devin:`Devin`,github:`GitHub`,"github-copilot":`GitHub Copilot`,"gitlab-duo":`GitLab Duo`,openrouter:`OpenRouter`,opper:`Opper`,"opencode-go":`OpenCode Go`,"opencode-free":`OpenCode Free`,"opencode-zen":`OpenCode Zen`,orcarouter:`OrcaRouter - API`,"orcarouter-oauth":`OrcaRouter - Auth`,packycode:`PackyCode`,mistral:`Mistral`,groq:`Groq`,"meta-model":`Meta Model API`,"meta-muse":`Muse Code`,alibaba:`Alibaba Coding Plan`,"alibaba-token-plan":`Alibaba Token Plan`,"alibaba-token-plan-intl":`Alibaba Token Plan (Intl)`,kimi:`Kimi`,"kimi-code":`Kimi`,moonshot:`Moonshot`,google:`Google`,"google-vertex":`Google Vertex`,"lm-studio":`LM Studio`,huggingface:`Hugging Face`,"qwen-cloud":`Qwen Cloud`,siliconflow:`SiliconFlow`,stepfun:`StepFun`,"tencent-coding-plan":`Tencent Cloud Coding Plan`,codebuddy:`CodeBuddy`,"codebuddy-cn":`CodeBuddy CN`,qoder:`Qoder`,"qoder-cn":`Qoder CN`,"vercel-ai-gateway":`Vercel AI Gateway`,vllm:`vLLM`,litellm:`LiteLLM`},r={"command-code":`provider.name.commandCodeAuth`,commandcode:`provider.name.commandCodeApi`,orcarouter:`provider.name.orcaRouterApi`,"orcarouter-oauth":`provider.name.orcaRouterAuth`,volcengine:`provider.name.volcengine`,"volcengine-coding-plan":`provider.name.volcengineCodingPlan`,"volcengine-agent-plan":`provider.name.volcengineAgentPlan`},i=new Set([...Object.keys(n),...Object.keys(r)]);function a(e){let n=e.toLowerCase();return Object.hasOwn(t,n)?t[n]:void 0}function o(e,t){let n=a(e);return n?`/provider-icons/${n}`:void 0}var s=new Set([`cerebras.svg`,`deepinfra.svg`,`neuralwatt.svg`,`nous.svg`,`novita.svg`,`packycode.svg`,`opper.svg`,`siliconflow.svg`,`synthetic.svg`,`zenmux.svg`,`grok.svg`,`kimi-color.svg`,`ollama-color.svg`,`opencode.svg`,`vercel-ai-gateway-color.svg`]),c=new Set([`baseten.svg`,`kilo.svg`,`sambanova.svg`,`venice.svg`,`zai.svg`]),l=new Set([`bizrouter.svg`,`featherless.svg`,`hyperbolic.svg`,`nebius.svg`,`parallel.svg`,`umans.svg`]);function u(e){if(!e)return`image`;let t=e.split(`/`).pop()??``;return s.has(t)?`mask`:c.has(t)?`plate`:l.has(t)?`dark-plate`:`image`}function d(e,t){let i=e.toLowerCase(),a=Object.hasOwn(r,i)?r[i]:void 0;return a?t(a):(Object.hasOwn(n,i)?n[i]:void 0)||(e===i&&/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(e)?e.split(`-`).map(e=>e&&e[0].toUpperCase()+e.slice(1)).join(` `):e)}function f(e){return i.has(e.toLowerCase())}function p(e){return e===`command-code`?`commandcode-auth`:e===`commandcode`?`commandcode-api`:e}function m(e,t){let n=e.indexOf(`/`);if(n<=0)return e;let r=e.slice(0,n),i=e.slice(n+1);if(r===`command-code`||r===`commandcode`){let e=i.match(/^([a-z0-9]+)-([a-z0-9]+(?:-[a-z0-9]+)+)$/i);return e&&i.startsWith(`${e[1]}-${e[1]}-`)&&(i=i.slice(e[1].length+1)),`${r===`command-code`?`commandcode-auth`:`commandcode-api`}/${i}`}return e}var h={ko:[{v:0x2386f26fc10000,s:`경`},{v:0xe8d4a51000,s:`조`},{v:1e8,s:`억`},{v:1e4,s:`만`}],zh:[{v:0x2386f26fc10000,s:`京`},{v:0xe8d4a51000,s:`兆`},{v:1e8,s:`亿`},{v:1e4,s:`万`}],"zh-TW":[{v:0x2386f26fc10000,s:`京`},{v:0xe8d4a51000,s:`兆`},{v:1e8,s:`億`},{v:1e4,s:`萬`}]};function g(e){return e.replace(/\.0+$/,``).replace(/(\.\d*?)0+$/,`$1`)}function _(e,t){let n=h[t];if(n){for(let t of n)if(e>=t.v)return`${g((e/t.v).toFixed(1))}${t.s}`;return String(e)}return e<1e4?String(e):e<1e6?`${g((e/1e3).toFixed(1))}K`:e<1e9?`${g((e/1e6).toFixed(1))}M`:e<0xe8d4a51000?`${g((e/1e9).toFixed(1))}B`:`${g((e/0xe8d4a51000).toFixed(1))}T`}var v=1e10;function y(e){return e<v?e*1e3:e}function b(e,t){let n=e=>typeof e==`number`&&Number.isFinite(e);if(!n(e.shortPercent)||e.shortPercent<100)return!1;let r=e.shortResetAt;if(!n(r)||r<=0){let r=e.shortObservedAt;if(!n(r))return!1;let i=t-r;return i>=0&&i<=3e5}return y(r)>t}function x(e){return{fiveHourAvailable:e?.shortWindowSeconds===18e3&&typeof e.shortResetAt==`number`,weeklyAvailable:typeof e?.weeklyResetAt==`number`}}function S(e){let t=e?.trim().toLowerCase();return t===`go`||t===`free`}function C(e,t){if(!e)return null;let n=e.shortPercent===void 0&&e.shortResetAt===void 0?e:{...e,fiveHourPercent:e.fiveHourPercent??e.shortPercent,fiveHourResetAt:e.fiveHourResetAt??e.shortResetAt};return S(t)?{...n.monthlyPercent===void 0?{}:{monthlyPercent:n.monthlyPercent},...n.monthlyResetAt===void 0?{}:{monthlyResetAt:n.monthlyResetAt},...n.creditsUsd===void 0?{}:{creditsUsd:n.creditsUsd},...n.resetCredits===void 0?{}:{resetCredits:n.resetCredits},updatedAt:n.updatedAt}:n}function w(e,t,n=Date.now()){if(!e)return null;let r=e=>typeof e==`number`&&Number.isFinite(e),i=r(e.fiveHourPercent)?e.fiveHourPercent:r(e.shortPercent)?e.shortPercent:void 0,a=(S(t)?[e.monthlyPercent]:[e.weeklyPercent,e.monthlyPercent]).filter(r);if(a.length===0)return b({...r(i)?{shortPercent:i}:{},...r(e.fiveHourResetAt??e.shortResetAt)?{shortResetAt:e.fiveHourResetAt??e.shortResetAt}:{},...r(e.shortObservedAt)?{shortObservedAt:e.shortObservedAt}:{}},n)?100:null;let o=r(i)?[...a,i]:a;return o.length?Math.max(...o):null}function T(e){let t=new URLSearchParams({hours:String(e.chartHours),bucketMinutes:String(e.bucketMinutes),metric:e.tokenMetric,aggregation:e.aggregation,grouping:e.chartGrouping});e.models?.length&&t.set(`models`,e.models.join(`,`));for(let n of e.hiddenProviders)t.append(`hiddenProvider`,n);return t}function E(e){return Array.isArray(e)&&e.length<=100&&e.every(e=>typeof e==`string`)?JSON.stringify([...new Set(e)].sort()):void 0}function D(e,t){let n=new Set(t.hiddenProviders),r=e.availableModels.filter(e=>!n.has(e.slice(0,e.indexOf(`/`))));if(t.models?.length===0)return{...e,series:[],availableModels:r};let i=t.models===null?null:new Set(t.models),a=i!==null||n.size>0,o=e.appliedFilters,s=!!o&&E(o.hiddenProviders)===E(t.hiddenProviders)&&(t.models===null?o.models===null:E(o.models)===E(t.models)),c=e.series.filter(e=>e.id===`other`&&e.provider===``?!a||s:!n.has(e.provider)&&(i===null||i.has(`${e.provider}/${e.model}`)||i.has(e.model)));return{...e,series:c,availableModels:r,truncated:e.truncated||(a||o!==void 0)&&!s}}var O={6:15,24:60,72:180,168:360};function k(e){return O[e]}function A(e){if(e<1e3)return String(Math.round(e));let t=[[0xe8d4a51000,`T`],[1e9,`B`],[1e6,`M`],[1e3,`K`]];for(let n=0;n<t.length;n+=1){let[r,i]=t[n];if(e>=r){let a=Math.round(e/r);if(a>=1e3&&n>0){let[r,i]=t[n-1];return`${Math.round(e/r)}${i}`}return`${a}${i}`}}return String(Math.round(e))}function j(e,t){let n=new Map;for(let r of e){let e=r.includes(`/`)?r.slice(0,r.indexOf(`/`)):r,i=n.get(e)??{provider:e,models:[],total:0},a=t.get(r)??0;i.models.push({id:r,total:a}),i.total+=a,n.set(e,i)}return Array.from(n.values()).map(e=>({...e,models:e.models.toSorted((e,t)=>t.total-e.total||e.id.localeCompare(t.id))})).toSorted((e,t)=>t.total-e.total||e.provider.localeCompare(t.provider))}function M(e,t,n,r){let i=new Set(t),a=new Set((e??t).filter(e=>i.has(e)));for(let e of n)r?a.add(e):a.delete(e);return t.length>0&&t.every(e=>a.has(e))?null:t.filter(e=>a.has(e))}function N(e,t=[]){let n={...e};if(typeof n.menuBarTemplate==`string`&&n.menuBarTemplate.trim()===``&&(n.menuBarTemplate=null),n.models!==void 0&&t.length>0){let e=n.models??[],r=new Set(e);e.length===t.length&&t.every(e=>r.has(e))&&(n.models=null)}return n}function P(e,t,n,r,i=8){let a=Math.max(0,t-i*2),o=Math.max(0,n-i*2),s=Math.max(r,1),c=Math.max(e.length-1,1);return e.map((e,t)=>`${i+a*t/c},${i+o*(1-Math.max(0,e)/s)}`).join(` `)}function F(e,t,n,r,i=8){let a=e[0]?.points.length??0;if(a===0)return[];let o=Math.max(0,t-i*2),s=Math.max(0,n-i*2),c=Math.max(r,1),l=Math.min(3,o/Math.max(a*8,1)),u=Math.max(0,o/a-l),d=[];for(let t=0;t<a;t+=1){let n=0;for(let r=0;r<e.length;r+=1){let f=s*Math.max(0,e[r]?.points[t]??0)/c;f>0&&d.push({x:i+o/a*t+l/2,y:i+s-n-f,width:u,height:f,seriesIndex:r,bucketIndex:t}),n+=f}}return d}var I=e(),L=[`#0A84FF`,`#FF9F0A`,`#30D158`,`#BF5AF2`,`#FF453A`,`#64D2FF`],R=640,z=160,B=28;function V(e,t){return Math.max(...t===`stackedBar`?Array.from({length:e.buckets},(t,n)=>e.series.reduce((e,t)=>e+(t.points[n]??0),0)):e.series.flatMap(e=>e.points),0)}function H(e,t){let n=new Intl.DateTimeFormat(t,{month:`short`,day:`numeric`}),r=Math.max(1,Math.floor((e.buckets-1)/3));return[0,1,2,3].map(t=>{let i=Math.min(e.buckets-1,t*r);return n.format(new Date((e.start+i*e.bucketSeconds)*1e3))})}function U({timeline:e,chartStyle:t,hours:n,loading:r,error:i,onRetry:a,locale:o,t:s}){if(r)return(0,I.jsx)(`div`,{className:`usage-companion-chart-skeleton`,"aria-busy":`true`,"aria-label":s(`usage.companion.loading`)});if(i)return(0,I.jsxs)(`div`,{className:`usage-companion-chart-state`,role:`alert`,children:[(0,I.jsx)(`span`,{children:s(`usage.companion.timelineUnavailable`)}),(0,I.jsx)(`button`,{type:`button`,className:`btn btn-ghost btn-sm`,onClick:a,children:s(`common.retry`)})]});if(!e||e.series.length===0)return(0,I.jsx)(`div`,{className:`usage-companion-chart-state`,children:s(`usage.companion.empty`,{hours:e?.buckets?Math.round(e.buckets*e.bucketSeconds/3600):n})});let c=V(e,t),l=H(e,o),u=B,d=l.map((e,t)=>(0,I.jsx)(`text`,{x:B+584*t/3,y:156,textAnchor:t===0?`start`:t===3?`end`:`middle`,className:`usage-companion-axis-label`,children:e},`${e}-${t}`)),f=t===`line`?e.series.map((e,t)=>(0,I.jsx)(`polyline`,{points:P(e.points,584,104,c,0),transform:`translate(${B},${B})`,fill:`none`,stroke:L[t%L.length],strokeWidth:`2`,strokeLinejoin:`round`,strokeLinecap:`round`},e.id)):F(e.series,584,104,c,0).map(e=>(0,I.jsx)(`rect`,{x:B+e.x,y:B+e.y,width:e.width,height:e.height,fill:L[e.seriesIndex%L.length],rx:`1`},`${e.bucketIndex}-${e.seriesIndex}`));return(0,I.jsxs)(`div`,{className:`usage-companion-chart`,children:[(0,I.jsxs)(`svg`,{viewBox:`0 0 ${R} ${z}`,role:`img`,"aria-label":s(`usage.companion.chartLabel`),children:[(0,I.jsx)(`line`,{x1:B,y1:u,x2:B,y2:132,className:`usage-companion-axis`}),(0,I.jsx)(`line`,{x1:B,y1:132,x2:612,y2:132,className:`usage-companion-axis`}),(0,I.jsx)(`text`,{x:24,y:32,textAnchor:`end`,className:`usage-companion-axis-label`,children:A(c)}),f,d]}),(0,I.jsx)(`div`,{className:`usage-companion-legend`,children:e.series.map((e,t)=>(0,I.jsxs)(`span`,{className:`usage-companion-legend-item`,children:[(0,I.jsx)(`span`,{className:`usage-companion-swatch`,style:{background:L[t%L.length]},"aria-hidden":`true`}),(0,I.jsx)(`span`,{children:e.id})]},e.id))}),e.truncated&&(0,I.jsx)(`p`,{className:`muted text-caption`,children:s(`usage.companion.olderRecordsSkipped`)})]})}export{u as _,T as a,M as c,x as d,_ as f,p as g,f as h,D as i,w as l,d as m,k as n,A as o,m as p,N as r,j as s,U as t,C as u,o as v};
Binary file
@@ -16,8 +16,8 @@
16
16
  } catch (e) {}
17
17
  })();
18
18
  </script>
19
- <script type="module" crossorigin src="/assets/index-DoBVdPHP.js"></script>
20
- <link rel="stylesheet" crossorigin href="/assets/index-BTuCbqQd.css">
19
+ <script type="module" crossorigin src="/assets/index-wpTOyepx.js"></script>
20
+ <link rel="stylesheet" crossorigin href="/assets/index-_bpvxJu0.css">
21
21
  </head>
22
22
  <body>
23
23
  <div id="root"></div>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>Stepfun</title><path d="M22.012 0h1.032v.927H24v.968h-.956V3.78h-1.032V1.896h-1.878v-.97h1.878V0zM2.6 12.371V1.87h.969v10.502h-.97zm10.423.66h10.95v.918h-6.208v9.579h-4.742V13.03zM5.629 3.333v12.356H0v4.51h10.386V8L20.859 8l-.003-4.668-15.227.001z" fill="url(#stepfun-gradient)" fill-rule="evenodd"/><defs><linearGradient id="stepfun-gradient" x1="1.646" y1="1.916" x2="18.342" y2="22.091" gradientUnits="userSpaceOnUse"><stop stop-color="#01A9FF"/><stop offset="1" stop-color="#0160FF"/></linearGradient></defs></svg>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bitkyc08/opencodex",
3
- "version": "2.60.0",
3
+ "version": "2.61.0",
4
4
  "description": "Universal provider proxy for OpenAI Codex & Claude Code — use any LLM with Codex CLI/App/SDK and Claude Code",
5
5
  "type": "module",
6
6
  "main": "./bin/package-main.mjs",
@@ -49,6 +49,8 @@
49
49
  "typecheck": "bun x tsc --noEmit",
50
50
  "audit:high": "bun audit --audit-level=high && cd gui && bun audit --audit-level=high",
51
51
  "privacy:scan": "bun scripts/privacy-scan.ts",
52
+ "favicons": "bun scripts/brand-favicons.ts",
53
+ "favicons:check": "bun scripts/brand-favicons.ts --check",
52
54
  "skill:surface": "bun scripts/generate-ocx-skill-surface.ts",
53
55
  "skill:surface:check": "bun scripts/generate-ocx-skill-surface.ts --check",
54
56
  "structure:index": "bun scripts/structure-ssot.ts --fix",
@@ -58,6 +60,8 @@
58
60
  "build:gui": "cd gui && bun install --frozen-lockfile && bun run build && cd .. && bun run prepare:package",
59
61
  "build:remote-workspace-helper": "cargo build --release --locked --manifest-path native/remote-workspace-helper/Cargo.toml",
60
62
  "test:remote-workspace-helper": "cargo test --locked --manifest-path native/remote-workspace-helper/Cargo.toml",
63
+ "build:standalone": "bun scripts/build-standalone.ts",
64
+ "test:macos": "swift run --package-path app MenuBarCoreTests",
61
65
  "prepare:package": "bun scripts/prepare-package.ts",
62
66
  "prepack": "bun run prepare:package",
63
67
  "prepublishOnly": "bun run audit:high && bun run typecheck && bun run build:gui",
@@ -39,6 +39,16 @@ function toAnthropicContentPart(p: OcxContentPart): unknown {
39
39
  : { type: "image", source: { type: "url", url: p.imageUrl } };
40
40
  }
41
41
  if (p.type === "video") return { type: "text", text: "[video]" };
42
+ // The block the caller sent, rebuilt. A Messages-to-Messages route used to reduce it to its
43
+ // title before any adapter ran, so the model was told a document existed rather than given
44
+ // one, and the answer came back looking the same (#5212).
45
+ if (p.type === "document") {
46
+ return {
47
+ type: "document",
48
+ source: { type: "base64", media_type: p.mediaType, data: p.data },
49
+ ...(p.filename !== undefined ? { title: p.filename } : {}),
50
+ };
51
+ }
42
52
  return { type: "text", text: p.text };
43
53
  }
44
54
 
@@ -857,6 +867,12 @@ function toolsToAnthropicFormat(parsed: OcxParsedRequest, toolNames: { toWire: (
857
867
  name: toolNames.toWire(namespacedToolName(t.namespace, t.name)),
858
868
  description: t.description,
859
869
  input_schema: normalizeAnthropicInputSchema(t.parameters),
870
+ // Anthropic is the target that DEFINES both of these, and both were dropped while the
871
+ // OpenAI Chat adapter already forwarded strict (#5210). Only an explicit `true` is
872
+ // emitted: the Messages inbound records an absent strict as `false`, so a false here
873
+ // cannot be distinguished from silence and must not become an opt-out on the wire.
874
+ ...(t.strict === true ? { strict: true } : {}),
875
+ ...(t.allowedCallers !== undefined ? { allowed_callers: [...t.allowedCallers] } : {}),
860
876
  }));
861
877
  return converted;
862
878
  }
@@ -21,6 +21,30 @@ export const MAX_STREAM_LINE_BYTES = 8 * 1024 * 1024;
21
21
  export const MAX_STREAM_TOTAL_BYTES = 64 * 1024 * 1024;
22
22
  /** Hard ceiling on projected conversation history text (characters) to prevent runaway memory. */
23
23
  export const MAX_PROJECTED_HISTORY_CHARS = 200_000;
24
+ /**
25
+ * Chars-per-token ratio for deriving the projected-history ceiling from the model context
26
+ * window. Sits between the English/code (~4 chars per token) and CJK (~1.5) extremes: the
27
+ * ceiling is a runaway-memory bound and a coarse guard against cutting history the window can
28
+ * hold, not a token accounting - the caller-side compaction line stays the token authority.
29
+ */
30
+ const PROJECTED_HISTORY_CHARS_PER_TOKEN = 3;
31
+ /** Absolute ceiling on a window-derived history cap, so runaway metadata cannot unbound stdin. */
32
+ const MAX_PROJECTED_HISTORY_DERIVED_CHARS = 4_000_000;
33
+
34
+ /**
35
+ * Projected-history character ceiling for a turn, derived from the declared model context
36
+ * window. A missing or non-finite window keeps the legacy flat cap, and the derivation never
37
+ * lowers the cap below it: small windows change nothing, while large windows scale (a 1M-token
38
+ * model keeps 3M characters) until the hard ceiling. The flat 200k cap predates window
39
+ * metadata and cut long replays to roughly 50k-130k tokens of content regardless of the model.
40
+ */
41
+ export function projectedHistoryCharLimit(contextWindowTokens: number | undefined): number {
42
+ if (typeof contextWindowTokens !== "number" || !Number.isFinite(contextWindowTokens) || contextWindowTokens <= 0) {
43
+ return MAX_PROJECTED_HISTORY_CHARS;
44
+ }
45
+ const derived = contextWindowTokens * PROJECTED_HISTORY_CHARS_PER_TOKEN;
46
+ return Math.min(Math.max(derived, MAX_PROJECTED_HISTORY_CHARS), MAX_PROJECTED_HISTORY_DERIVED_CHARS);
47
+ }
24
48
 
25
49
  export class CodingAgentStreamLimitError extends Error {
26
50
  constructor(message: string) {
@@ -308,7 +332,7 @@ function formatMessageForHistory(message: OcxMessage): string {
308
332
  if (message.role === "user") {
309
333
  const text = typeof message.content === "string"
310
334
  ? message.content
311
- : message.content.map(p => (p.type === "text" ? p.text : `[${p.type}]`)).join("\n");
335
+ : message.content.map(p => (p.type === "text" || p.type === "document" ? p.text : `[${p.type}]`)).join("\n");
312
336
  return `USER:\n${text}`;
313
337
  }
314
338
  if (message.role === "assistant") {
@@ -328,7 +352,7 @@ function formatMessageForHistory(message: OcxMessage): string {
328
352
  if (message.role === "toolResult") {
329
353
  const text = typeof message.content === "string"
330
354
  ? message.content
331
- : message.content.map(p => (p.type === "text" ? p.text : "[image]")).join("");
355
+ : message.content.map(p => (p.type === "text" || p.type === "document" ? p.text : "[image]")).join("");
332
356
  const status = message.isError ? " (error)" : "";
333
357
  return `TOOL RESULT (call_id: ${message.toolCallId})${status}:\n${text}`;
334
358
  }
@@ -355,6 +379,8 @@ export function buildInputLines(message: OcxMessage): string[] {
355
379
  else if (part.type === "image") {
356
380
  const image = imagePart(part.imageUrl);
357
381
  if (image) content.push(image);
382
+ } else if (part.type === "document") {
383
+ content.push(textPart(part.text));
358
384
  } else {
359
385
  content.push(textPart("[video]"));
360
386
  }
@@ -378,7 +404,7 @@ export function buildSystemPrompt(parsed: OcxParsedRequest): string | undefined
378
404
  if (message.role !== "developer") continue;
379
405
  const text = typeof message.content === "string"
380
406
  ? message.content
381
- : message.content.map(part => (part.type === "text" ? part.text : "")).join("");
407
+ : message.content.map(part => (part.type === "text" || part.type === "document" ? part.text : "")).join("");
382
408
  if (text.trim()) parts.push(text);
383
409
  }
384
410
  return parts.length > 0 ? parts.join("\n\n") : undefined;
@@ -395,7 +421,7 @@ export function buildSystemPrompt(parsed: OcxParsedRequest): string | undefined
395
421
  * prior conversation turns are structured as bounded context text with tool results as text,
396
422
  * clearly demarcated from the current user request. Codex retains tool control; vendor tools are never invoked.
397
423
  */
398
- export function buildConversationInput(parsed: OcxParsedRequest): string[] {
424
+ export function buildConversationInput(parsed: OcxParsedRequest, options: { maxHistoryChars?: number } = {}): string[] {
399
425
  const nonDev = parsed.context.messages.filter(m => m.role !== "developer");
400
426
  if (nonDev.length === 0) {
401
427
  return [JSON.stringify({ type: "user", message: { role: "user", content: [{ type: "text", text: "" }] } })];
@@ -440,6 +466,8 @@ export function buildConversationInput(parsed: OcxParsedRequest): string[] {
440
466
  const image = imagePart(part.imageUrl);
441
467
  if (image) currentImageBlocks.push(image);
442
468
  else textParts.push("[image omitted: unsupported reference]");
469
+ } else if (part.type === "document") {
470
+ textParts.push(part.text);
443
471
  } else {
444
472
  textParts.push("[video]");
445
473
  }
@@ -463,6 +491,7 @@ export function buildConversationInput(parsed: OcxParsedRequest): string[] {
463
491
  else segments.push("[image omitted: unsupported reference]");
464
492
  continue;
465
493
  }
494
+ if (part.type === "document") { segments.push(part.text); continue; }
466
495
  segments.push("[video]");
467
496
  }
468
497
  text = segments.join("");
@@ -475,10 +504,11 @@ export function buildConversationInput(parsed: OcxParsedRequest): string[] {
475
504
 
476
505
  const imageBlocks: WireContentPart[] = [...historyImageBlocks, ...currentImageBlocks];
477
506
 
507
+ const maxHistoryChars = options.maxHistoryChars ?? MAX_PROJECTED_HISTORY_CHARS;
478
508
  let historyText = historyMessages.map(formatMessageForHistory).filter(Boolean).join("\n\n");
479
- if (historyText.length > MAX_PROJECTED_HISTORY_CHARS) {
509
+ if (historyText.length > maxHistoryChars) {
480
510
  historyText = `[Earlier conversation history truncated for length...]\n\n` +
481
- historyText.slice(historyText.length - MAX_PROJECTED_HISTORY_CHARS);
511
+ historyText.slice(historyText.length - maxHistoryChars);
482
512
  }
483
513
 
484
514
  const combinedText = `Prior conversation context:\n\n${historyText}\n\nCurrent user request:\n\n${currentRequestText}`;
@@ -1,8 +1,9 @@
1
1
  import { execFileSync, spawn as nodeSpawn, type ChildProcess, type SpawnOptions } from "node:child_process";
2
2
  import type { AdapterEvent, OcxParsedRequest, OcxProviderConfig } from "../../types";
3
3
  import { commandInvocation } from "../../lib/win-exec";
4
+ import { modelRecordValue } from "../../reasoning-effort";
4
5
  import type { IncomingMeta } from "../base";
5
- import { buildConversationInput, CodingAgentProtocolError, mapStreamMessageToEvents, readJsonLines, type StreamParseState } from "./protocol";
6
+ import { buildConversationInput, CodingAgentProtocolError, mapStreamMessageToEvents, projectedHistoryCharLimit, readJsonLines, type StreamParseState } from "./protocol";
6
7
  import { resolveCodingAgentBinary, resolveProfileByBaseUrl, type CodingAgentProviderProfile, type WhichFn } from "./profile";
7
8
 
8
9
  /** Injectable spawn for tests; production uses node:child_process. */
@@ -264,7 +265,14 @@ export async function runCodingAgentTurn(input: CodingAgentTurnInput): Promise<v
264
265
  const stdin = child.stdin;
265
266
  if (stdin) {
266
267
  stdin.on("error", () => { /* EPIPE if the CLI exits early; surfaced via close/stderr */ });
267
- for (const line of buildConversationInput(parsed)) stdin.write(`${line}\n`);
268
+ // The projected history scales with the model context window on the routed provider row
269
+ // (catalog and config metadata merged): a 1M-token model keeps 3M characters of replay
270
+ // where the flat cap cut it near 50k-130k tokens of content. Absent metadata keeps the
271
+ // flat cap.
272
+ const historyCharLimit = projectedHistoryCharLimit(
273
+ modelRecordValue(provider.modelContextWindows, parsed.modelId) ?? provider.contextWindow,
274
+ );
275
+ for (const line of buildConversationInput(parsed, { maxHistoryChars: historyCharLimit })) stdin.write(`${line}\n`);
268
276
  stdin.end();
269
277
  }
270
278
  const stdout = child.stdout;
@@ -32,7 +32,7 @@ function canonicalCommandCodeModelId(modelId: string): string {
32
32
  /** Flatten tool-result content for the text-only wire output, keeping an `[image]` marker per image part in content order. */
33
33
  function toolResultText(content: string | OcxContentPart[]): string {
34
34
  if (typeof content === "string") return content;
35
- return content.map(part => (part.type === "text" ? part.text : "[image]")).join("");
35
+ return content.map(part => (part.type === "text" || part.type === "document" ? part.text : "[image]")).join("");
36
36
  }
37
37
 
38
38
  /** Best-effort media type from a remote https URL extension, e.g. image/png. */
@@ -149,6 +149,7 @@ function wireMessages(messages: OcxMessage[]): Array<Record<string, unknown>> {
149
149
  else for (const part of message.content) {
150
150
  if (part.type === "text") content.push({ type: "text", text: part.text });
151
151
  else if (part.type === "image") content.push(wireImagePart(part.imageUrl));
152
+ else if (part.type === "document") content.push({ type: "text", text: part.text });
152
153
  else content.push({ type: "text", text: "[video]" });
153
154
  }
154
155
  out.push({ role: "user", content });
@@ -3,6 +3,7 @@ import {
3
3
  normalizeCursorClaudeId,
4
4
  type NormalizedCursorClaudeId,
5
5
  } from "./claude-id";
6
+ import { createHash } from "node:crypto";
6
7
 
7
8
  /**
8
9
  * Cursor umbrella catalog — the single source of truth for cursor model
@@ -599,7 +600,7 @@ export function resolveCursorSelection(
599
600
  pickedId: string,
600
601
  reasoning: string | undefined,
601
602
  liveMaxModeIds?: ReadonlySet<string>,
602
- options: { fast?: boolean } = {},
603
+ options: { fast?: boolean; liveRosterScope?: string } = {},
603
604
  ): CursorResolvedSelection {
604
605
  const parsed = parseCursorVariantId(pickedId);
605
606
  if (!parsed.known) {
@@ -617,7 +618,10 @@ export function resolveCursorSelection(
617
618
  const requested = parsed.level ?? reasoning;
618
619
  const effort = cursorVariantEffort(spec, requested);
619
620
  const requestedClaude = normalizeCursorClaudeId(pickedId);
620
- const claudeIdentity = liveCursorClaudeWireIdentities.get(parsed.baseId)
621
+ const scopedClaudeIdentities = options.liveRosterScope
622
+ ? liveCursorClaudeWireIdentitiesByScope.get(options.liveRosterScope)
623
+ : liveCursorClaudeWireIdentities;
624
+ const claudeIdentity = scopedClaudeIdentities?.get(parsed.baseId)
621
625
  ?? (requestedClaude
622
626
  ? { sourceBaseId: requestedClaude.sourceBaseId, spelling: requestedClaude.spelling }
623
627
  : undefined);
@@ -626,7 +630,9 @@ export function resolveCursorSelection(
626
630
  ? `${capability.wirePrefix}${canonicalId}`
627
631
  : canonicalId;
628
632
  const ultraRequested = parsed.ultra || reasoning?.toLowerCase() === "ultra";
629
- const evidence = liveMaxModeIds ?? liveCursorMaxModeBases;
633
+ const evidence = liveMaxModeIds
634
+ ?? (options.liveRosterScope ? liveCursorMaxModeBasesByScope.get(options.liveRosterScope) : undefined)
635
+ ?? liveCursorMaxModeBases;
630
636
  const maxModeArmed = capability.maxModeVerified === true || evidence.has(parsed.baseId);
631
637
  return { wireId, canonicalId, maxMode: ultraRequested && maxModeArmed, known: true };
632
638
  }
@@ -639,15 +645,36 @@ export function resolveCursorSelection(
639
645
  */
640
646
  let liveCursorMaxModeBases: ReadonlySet<string> = new Set();
641
647
  let liveCursorClaudeWireIdentities: ReadonlyMap<string, CursorLiveClaudeWireIdentity> = new Map();
648
+ const liveCursorMaxModeBasesByScope = new Map<string, ReadonlySet<string>>();
649
+ const liveCursorClaudeWireIdentitiesByScope = new Map<string, ReadonlyMap<string, CursorLiveClaudeWireIdentity>>();
650
+ const liveCursorRosterScopesByProvider = new Map<string, Set<string>>();
651
+
652
+ /** Non-secret key binding live roster evidence to one upstream destination and credential. */
653
+ export function cursorLiveRosterScope(baseUrl: string | undefined, credential: string): string {
654
+ const destination = (baseUrl?.trim().replace(/\/+$/, "") || "https://api2.cursor.sh");
655
+ return createHash("sha256")
656
+ .update("ocx:cursor:live-roster\0")
657
+ .update(destination)
658
+ .update("\0")
659
+ .update(credential)
660
+ .digest("hex");
661
+ }
642
662
 
643
- export function recordLiveCursorClaudeModels(liveIds: readonly string[]): void {
663
+ export function recordLiveCursorClaudeModels(liveIds: readonly string[], scope?: { provider: string; key: string }): void {
644
664
  const next = new Map<string, CursorLiveClaudeWireIdentity>();
645
665
  for (const rawId of liveIds) {
646
666
  const n = normalizeCursorClaudeId(rawId.startsWith("cursor-") ? rawId.slice(7) : rawId);
647
667
  if (!n || !CURSOR_CAPABILITIES[n.canonicalBaseId]) continue;
648
668
  if (!next.has(n.canonicalBaseId)) next.set(n.canonicalBaseId, { sourceBaseId: n.sourceBaseId, spelling: n.spelling });
649
669
  }
650
- liveCursorClaudeWireIdentities = next;
670
+ if (scope) {
671
+ liveCursorClaudeWireIdentitiesByScope.set(scope.key, next);
672
+ const scopes = liveCursorRosterScopesByProvider.get(scope.provider) ?? new Set<string>();
673
+ scopes.add(scope.key);
674
+ liveCursorRosterScopesByProvider.set(scope.provider, scopes);
675
+ } else {
676
+ liveCursorClaudeWireIdentities = next;
677
+ }
651
678
  }
652
679
 
653
680
  export function liveCursorClaudeWireIdentitiesForTests(): ReadonlyMap<string, CursorLiveClaudeWireIdentity> {
@@ -656,15 +683,32 @@ export function liveCursorClaudeWireIdentitiesForTests(): ReadonlyMap<string, Cu
656
683
 
657
684
  export function resetLiveCursorClaudeWireIdentitiesForTests(): void {
658
685
  liveCursorClaudeWireIdentities = new Map();
686
+ liveCursorClaudeWireIdentitiesByScope.clear();
687
+ liveCursorRosterScopesByProvider.clear();
659
688
  }
660
689
 
661
- export function recordLiveCursorMaxModeModels(liveIds: readonly string[]): void {
690
+ export function recordLiveCursorMaxModeModels(liveIds: readonly string[], scope?: { provider: string; key: string }): void {
662
691
  const bases = new Set<string>();
663
692
  for (const id of liveIds) {
664
693
  const parsed = parseCursorVariantId(id);
665
694
  if (parsed.known) bases.add(parsed.baseId);
666
695
  }
667
- liveCursorMaxModeBases = bases;
696
+ if (scope) liveCursorMaxModeBasesByScope.set(scope.key, bases);
697
+ else liveCursorMaxModeBases = bases;
698
+ }
699
+
700
+ export function clearLiveCursorRosterState(provider?: string): void {
701
+ if (!provider) {
702
+ liveCursorClaudeWireIdentitiesByScope.clear();
703
+ liveCursorMaxModeBasesByScope.clear();
704
+ liveCursorRosterScopesByProvider.clear();
705
+ return;
706
+ }
707
+ for (const scope of liveCursorRosterScopesByProvider.get(provider) ?? []) {
708
+ liveCursorClaudeWireIdentitiesByScope.delete(scope);
709
+ liveCursorMaxModeBasesByScope.delete(scope);
710
+ }
711
+ liveCursorRosterScopesByProvider.delete(provider);
668
712
  }
669
713
 
670
714
  export function liveCursorMaxModeBasesForTests(): ReadonlySet<string> {
@@ -741,7 +741,7 @@ function contentText(message: OcxMessage): string {
741
741
  if (typeof message.content === "string") return message.content;
742
742
  return message.content
743
743
  .map(part => {
744
- if (part.type === "text") return part.text;
744
+ if (part.type === "text" || part.type === "document") return part.text;
745
745
  if (part.type === "thinking") return part.thinking;
746
746
  if (part.type === "image") return undefined;
747
747
  return undefined;
@@ -754,7 +754,7 @@ function contentToText(content: OcxToolResultMessage["content"]): string {
754
754
  if (typeof content === "string") return content;
755
755
  return content
756
756
  .map(part => {
757
- if (part.type === "text") return part.text;
757
+ if (part.type === "text" || part.type === "document") return part.text;
758
758
  if (part.type === "image") return CURSOR_VISION_IMAGE_HISTORY_MARKER;
759
759
  return undefined;
760
760
  })
@@ -767,7 +767,7 @@ function historyContentText(message: OcxMessage): string {
767
767
  if (message.role === "toolResult" || typeof message.content === "string") return contentText(message);
768
768
  return message.content
769
769
  .map(part => {
770
- if (part.type === "text") return part.text;
770
+ if (part.type === "text" || part.type === "document") return part.text;
771
771
  if (part.type === "thinking") return part.thinking;
772
772
  if (part.type === "image") return CURSOR_VISION_IMAGE_HISTORY_MARKER;
773
773
  return undefined;
@@ -836,6 +836,9 @@ function decodeResultParts(message: OcxToolResultMessage): DecodedResultPart[] |
836
836
  return content.map((part): DecodedResultPart => {
837
837
  if (part.type === "text") return { kind: "text", text: part.text };
838
838
  if (part.type === "video") return { kind: "text", text: "[video]" };
839
+ // Without this the document falls through to decodeInlineImage(part.imageUrl) below and is
840
+ // treated as an image it is not.
841
+ if (part.type === "document") return { kind: "text", text: part.text };
839
842
  const decoded = decodeInlineImage(part.imageUrl);
840
843
  return decoded ? { kind: "image", ...decoded } : { kind: "undecodable" };
841
844
  });
@@ -213,7 +213,7 @@ export function cursorRequestEmitsFastVariant(parsed: OcxParsedRequest): boolean
213
213
  * instead: current Cursor clients send the matching Grok base id plus `effort` and `fast` parameters.
214
214
  * A fully-qualified id (one that is not a known effort base) passes through unchanged.
215
215
  */
216
- function normalizeCursorModelId(modelId: string, reasoning?: string, fast?: boolean): {
216
+ function normalizeCursorModelId(modelId: string, reasoning?: string, fast?: boolean, liveRosterScope?: string): {
217
217
  modelId: string;
218
218
  requestedModelParameters?: readonly CursorRequestedModelParameter[];
219
219
  routingLevel?: CursorRoutingLevel;
@@ -239,7 +239,7 @@ function normalizeCursorModelId(modelId: string, reasoning?: string, fast?: bool
239
239
  ],
240
240
  };
241
241
  }
242
- const resolved = resolveCursorSelection(id, reasoning, undefined, { fast });
242
+ const resolved = resolveCursorSelection(id, reasoning, undefined, { fast, liveRosterScope });
243
243
  return {
244
244
  ...selection,
245
245
  ...(resolved.maxMode ? { maxMode: true } : {}),
@@ -251,6 +251,9 @@ function contentPartToText(part: OcxContentPart | OcxAssistantContentPart): stri
251
251
  switch (part.type) {
252
252
  case "text":
253
253
  return part.text;
254
+ case "document":
255
+ // Cursor has no document carrier; the marker keeps the turn from serializing to nothing.
256
+ return part.text;
254
257
  case "thinking":
255
258
  return part.thinking;
256
259
  case "image":
@@ -407,6 +410,8 @@ export function cursorCoveredPrefixDigest(parsed: OcxParsedRequest, coveredMessa
407
410
  export interface CreateCursorRequestOptions {
408
411
  /** Force a brand-new Cursor conversation id even when remembered state exists. */
409
412
  forceFreshConversation?: boolean;
413
+ /** Credential-bound scope for live Cursor model spelling and Max-Mode evidence. */
414
+ liveRosterScope?: string;
410
415
  }
411
416
 
412
417
  function lookupPrefixSnapshot(
@@ -499,7 +504,12 @@ export function createCursorRequest(
499
504
  const visibleTools = cursorToolsForActivePrompt(parsed.context.tools, activeText, parsed.options.toolChoice);
500
505
  const budget = applyCursorToolBudget(visibleTools, parsed.options.toolChoice);
501
506
  const limitNote = catalogLimitNote(budget.tools, budget.omitted);
502
- const model = normalizeCursorModelId(parsed.modelId, parsed.options.reasoning, cursorFastRequested(parsed));
507
+ const model = normalizeCursorModelId(
508
+ parsed.modelId,
509
+ parsed.options.reasoning,
510
+ cursorFastRequested(parsed),
511
+ options.liveRosterScope,
512
+ );
503
513
  const request: CursorRunRequest = {
504
514
  modelId: model.modelId,
505
515
  ...(model.requestedModelParameters ? { requestedModelParameters: model.requestedModelParameters } : {}),
@@ -61,6 +61,7 @@ import {
61
61
  CursorTransportDisabledError,
62
62
  type CursorTransportFactory,
63
63
  } from "./cursor/transport";
64
+ import { cursorLiveRosterScope } from "./cursor/catalog";
64
65
 
65
66
  export const CURSOR_API_URL = "https://api2.cursor.sh";
66
67
 
@@ -172,9 +173,11 @@ export function createCursorAdapter(provider: OcxProviderConfig, deps: CursorAda
172
173
  // Namespace thread→conversation derivation by the authenticated Cursor credential so
173
174
  // shared-proxy tenants with different Cursor accounts cannot collide on a parent thread id.
174
175
  // Prefer an already-set auth scope (e.g. Codex pool account) when present.
176
+ let liveRosterScope: string | undefined;
175
177
  if (!_parsed._cursorIdentityScope) {
176
178
  try {
177
179
  const token = resolveCursorToken(provider, incoming.headers);
180
+ liveRosterScope = cursorLiveRosterScope(provider.baseUrl, token);
178
181
  _parsed._cursorIdentityScope = createHash("sha256")
179
182
  .update("ocx:cursor:acct:")
180
183
  .update(token)
@@ -183,11 +186,17 @@ export function createCursorAdapter(provider: OcxProviderConfig, deps: CursorAda
183
186
  } catch {
184
187
  /* Missing credential is handled by the live transport path below. */
185
188
  }
189
+ } else {
190
+ try {
191
+ liveRosterScope = cursorLiveRosterScope(provider.baseUrl, resolveCursorToken(provider, incoming.headers));
192
+ } catch {
193
+ /* Missing credential is handled by the live transport path below. */
194
+ }
186
195
  }
187
196
  const inheritedCheckpointRef = _parsed._providerContinuation?.cursor?.checkpointRef;
188
197
  const previousConversationId = _parsed._cursorConversationId;
189
198
  let request = {
190
- ...createCursorRequest(_parsed),
199
+ ...createCursorRequest(_parsed, { liveRosterScope }),
191
200
  _cursorIdentityScope: _parsed._cursorIdentityScope?.trim() || "local",
192
201
  };
193
202
  requestSizeContext = cursorRequestSizeContext(request);
@@ -441,7 +450,7 @@ export function createCursorAdapter(provider: OcxProviderConfig, deps: CursorAda
441
450
  lastTransport = undefined;
442
451
  _parsed._cursorConversationId = undefined;
443
452
  const next = {
444
- ...createCursorRequest(_parsed, { forceFreshConversation: true }),
453
+ ...createCursorRequest(_parsed, { forceFreshConversation: true, liveRosterScope }),
445
454
  _cursorIdentityScope: _parsed._cursorIdentityScope?.trim() || "local",
446
455
  };
447
456
  rekeyContextUsage(failedConversationId, next.conversationId);
@@ -0,0 +1,45 @@
1
+ // Type-only: erased at compile time, so this does not create an import cycle with the registry.
2
+ import type { AdapterWire } from "./registry";
3
+ import type { OcxMessage, OcxParsedRequest } from "../types";
4
+ import { toolRestrictsCallers } from "../types";
5
+
6
+ /**
7
+ * Wires that can actually carry a constraint, as a default-deny allowlist.
8
+ *
9
+ * A per-adapter opt-in is the wrong shape for this: an adapter that never learned about a
10
+ * carrier rebuilds the declaration or the message without it and returns a normal completion,
11
+ * which is exactly the silent widening this batch exists to remove. Listing the wires that CAN
12
+ * carry it means a new wire refuses until someone teaches it, and adding a member to
13
+ * `AdapterWire` makes the omission visible here rather than at a customer's upstream.
14
+ */
15
+ const CALLER_RESTRICTION_WIRES: ReadonlySet<AdapterWire> = new Set<AdapterWire>(["anthropic"]);
16
+ const INLINE_DOCUMENT_WIRES: ReadonlySet<AdapterWire> = new Set<AdapterWire>([
17
+ "anthropic",
18
+ "openai-chat",
19
+ "google",
20
+ ]);
21
+
22
+ /** A fixed-vocabulary refusal, or `undefined` when this wire can hold everything the request carries. */
23
+ export function unrepresentableDeclaration(parsed: OcxParsedRequest, wire: AdapterWire): string | undefined {
24
+ if (!CALLER_RESTRICTION_WIRES.has(wire) && parsed.context.tools?.some(toolRestrictsCallers)) {
25
+ // The name is deliberately absent: it is caller-controlled and would put client metadata
26
+ // into an error body.
27
+ return "OpenCodex cannot express tools[].allowed_callers on this route. "
28
+ + "Route the request to an Anthropic-protocol provider, or remove the caller restriction.";
29
+ }
30
+ if (!INLINE_DOCUMENT_WIRES.has(wire) && parsed.context.messages.some(carriesDocument)) {
31
+ return "OpenCodex cannot translate document input on this route. "
32
+ + "Use a native input wire that supports the attachment, or convert it to text first.";
33
+ }
34
+ return undefined;
35
+ }
36
+
37
+ /**
38
+ * Typed structurally rather than as `OcxContentPart[]`: an assistant turn's content is
39
+ * `OcxAssistantContentPart[]`, which carries thinking and tool-call members and is not
40
+ * assignable to the user-content union. Only the discriminant is read here.
41
+ */
42
+ function carriesDocument(message: OcxMessage): boolean {
43
+ const content: unknown = message.content;
44
+ return Array.isArray(content) && (content as ReadonlyArray<{ type: string }>).some(part => part.type === "document");
45
+ }