@remodex/rmx 1.0.2

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 (588) hide show
  1. package/AGENTS_INSTALL.md +91 -0
  2. package/LICENSE +21 -0
  3. package/README.md +242 -0
  4. package/assets/architecture.png +0 -0
  5. package/assets/banner.png +0 -0
  6. package/assets/claude-code-models.gif +0 -0
  7. package/assets/codex-app-picker.png +0 -0
  8. package/bin/ocx.mjs +584 -0
  9. package/bin/package-main.mjs +9 -0
  10. package/gui/dist/assets/index-CZqebSPQ.css +1 -0
  11. package/gui/dist/assets/index-CkETtt7P.js +71 -0
  12. package/gui/dist/favicon.png +0 -0
  13. package/gui/dist/fonts/google-sans-cyrillic.woff2 +0 -0
  14. package/gui/dist/fonts/google-sans-latin.woff2 +0 -0
  15. package/gui/dist/icons.svg +24 -0
  16. package/gui/dist/index.html +25 -0
  17. package/gui/dist/logo.png +0 -0
  18. package/gui/dist/provider-icons/alibaba-color.svg +1 -0
  19. package/gui/dist/provider-icons/antigravity-color.svg +1 -0
  20. package/gui/dist/provider-icons/claude-color.svg +1 -0
  21. package/gui/dist/provider-icons/cline-color.svg +16 -0
  22. package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -0
  23. package/gui/dist/provider-icons/commandcode-color.svg +1 -0
  24. package/gui/dist/provider-icons/copilot-color.svg +1 -0
  25. package/gui/dist/provider-icons/cursor-color.svg +2 -0
  26. package/gui/dist/provider-icons/deepseek-color.svg +1 -0
  27. package/gui/dist/provider-icons/discord.svg +1 -0
  28. package/gui/dist/provider-icons/firepass-color.svg +1 -0
  29. package/gui/dist/provider-icons/fireworks-color.svg +1 -0
  30. package/gui/dist/provider-icons/gemini-color.svg +1 -0
  31. package/gui/dist/provider-icons/github-copilot-color.svg +1 -0
  32. package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -0
  33. package/gui/dist/provider-icons/grok.svg +1 -0
  34. package/gui/dist/provider-icons/groq-color.svg +1 -0
  35. package/gui/dist/provider-icons/huggingface-color.svg +1 -0
  36. package/gui/dist/provider-icons/kimi-color.svg +1 -0
  37. package/gui/dist/provider-icons/kiro-color.svg +15 -0
  38. package/gui/dist/provider-icons/lm-studio-color.svg +1 -0
  39. package/gui/dist/provider-icons/mistral-color.svg +1 -0
  40. package/gui/dist/provider-icons/moonshot-color.svg +1 -0
  41. package/gui/dist/provider-icons/nvidia-color.svg +1 -0
  42. package/gui/dist/provider-icons/ollama-color.svg +1 -0
  43. package/gui/dist/provider-icons/openai.svg +1 -0
  44. package/gui/dist/provider-icons/opencode.svg +2 -0
  45. package/gui/dist/provider-icons/openrouter-color.svg +1 -0
  46. package/gui/dist/provider-icons/pi.svg +21 -0
  47. package/gui/dist/provider-icons/qianfan-color.svg +1 -0
  48. package/gui/dist/provider-icons/qwen-portal-color.svg +1 -0
  49. package/gui/dist/provider-icons/telegram.svg +1 -0
  50. package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -0
  51. package/gui/dist/provider-icons/vllm-color.svg +1 -0
  52. package/gui/dist/provider-icons/xiaomi-color.svg +1 -0
  53. package/package.json +118 -0
  54. package/src/AGENTS.md +28 -0
  55. package/src/adapters/anthropic-image-guard.ts +251 -0
  56. package/src/adapters/anthropic-image-normalize.ts +518 -0
  57. package/src/adapters/anthropic.ts +1205 -0
  58. package/src/adapters/azure.ts +36 -0
  59. package/src/adapters/base.ts +83 -0
  60. package/src/adapters/client-fingerprint.ts +59 -0
  61. package/src/adapters/command-code.ts +453 -0
  62. package/src/adapters/cursor/arg-codec.ts +38 -0
  63. package/src/adapters/cursor/arg-normalize.ts +104 -0
  64. package/src/adapters/cursor/cursor-errors.ts +165 -0
  65. package/src/adapters/cursor/discovery.ts +276 -0
  66. package/src/adapters/cursor/effort-map.ts +139 -0
  67. package/src/adapters/cursor/exec-policy.ts +88 -0
  68. package/src/adapters/cursor/framing.ts +250 -0
  69. package/src/adapters/cursor/gen/agent_pb.ts +15274 -0
  70. package/src/adapters/cursor/kv-store.ts +52 -0
  71. package/src/adapters/cursor/live-models.ts +153 -0
  72. package/src/adapters/cursor/live-smoke-gate.ts +41 -0
  73. package/src/adapters/cursor/live-transport.ts +1235 -0
  74. package/src/adapters/cursor/mcp-config.ts +42 -0
  75. package/src/adapters/cursor/mcp-manager.ts +333 -0
  76. package/src/adapters/cursor/message-mapper.ts +49 -0
  77. package/src/adapters/cursor/native-exec-common.ts +59 -0
  78. package/src/adapters/cursor/native-exec-desktop.ts +184 -0
  79. package/src/adapters/cursor/native-exec-fs.ts +332 -0
  80. package/src/adapters/cursor/native-exec-mcp.ts +153 -0
  81. package/src/adapters/cursor/native-exec-network.ts +43 -0
  82. package/src/adapters/cursor/native-exec-shell.ts +548 -0
  83. package/src/adapters/cursor/native-exec-tools.ts +118 -0
  84. package/src/adapters/cursor/native-exec.ts +604 -0
  85. package/src/adapters/cursor/protobuf-events.ts +735 -0
  86. package/src/adapters/cursor/protobuf-request.ts +719 -0
  87. package/src/adapters/cursor/request-builder.ts +280 -0
  88. package/src/adapters/cursor/thread-continuity.ts +67 -0
  89. package/src/adapters/cursor/tool-definitions.ts +621 -0
  90. package/src/adapters/cursor/transport-retry.ts +132 -0
  91. package/src/adapters/cursor/transport.ts +57 -0
  92. package/src/adapters/cursor/types.ts +59 -0
  93. package/src/adapters/cursor.ts +196 -0
  94. package/src/adapters/google-antigravity-replay.ts +520 -0
  95. package/src/adapters/google-antigravity-wire.ts +140 -0
  96. package/src/adapters/google-errors.ts +85 -0
  97. package/src/adapters/google-http.ts +100 -0
  98. package/src/adapters/google-tool-schema.ts +173 -0
  99. package/src/adapters/google-truncation.ts +24 -0
  100. package/src/adapters/google-wire-compiler.ts +232 -0
  101. package/src/adapters/google.ts +859 -0
  102. package/src/adapters/identity.ts +77 -0
  103. package/src/adapters/image.ts +23 -0
  104. package/src/adapters/kiro-constants.ts +16 -0
  105. package/src/adapters/kiro-errors.ts +208 -0
  106. package/src/adapters/kiro-events.ts +197 -0
  107. package/src/adapters/kiro-images.ts +129 -0
  108. package/src/adapters/kiro-retry.ts +312 -0
  109. package/src/adapters/kiro-thinking.ts +104 -0
  110. package/src/adapters/kiro-tool-fallback.ts +36 -0
  111. package/src/adapters/kiro-tools.ts +224 -0
  112. package/src/adapters/kiro-truncation.ts +33 -0
  113. package/src/adapters/kiro-wire.ts +129 -0
  114. package/src/adapters/kiro.ts +1924 -0
  115. package/src/adapters/mimo-free.ts +263 -0
  116. package/src/adapters/openai-chat.ts +1265 -0
  117. package/src/adapters/openai-responses.ts +1309 -0
  118. package/src/adapters/run-turn-queue.ts +114 -0
  119. package/src/adapters/tool-catalog-nudge.ts +71 -0
  120. package/src/adapters/upstream-http-error.ts +48 -0
  121. package/src/android-remote/assets.ts +218 -0
  122. package/src/android-remote/attachments.ts +168 -0
  123. package/src/android-remote/auth.ts +237 -0
  124. package/src/android-remote/cloudflare-provisioning.ts +409 -0
  125. package/src/android-remote/cloudflare-secret.ts +106 -0
  126. package/src/android-remote/cloudflare-tunnel.ts +488 -0
  127. package/src/android-remote/cloudflared.ts +286 -0
  128. package/src/android-remote/codex-app-server.ts +565 -0
  129. package/src/android-remote/desktop-history-page.ts +936 -0
  130. package/src/android-remote/desktop-ipc.ts +3643 -0
  131. package/src/android-remote/desktop-ownership-store.ts +98 -0
  132. package/src/android-remote/desktop-project-registration.ts +129 -0
  133. package/src/android-remote/desktop-session-stream.ts +1110 -0
  134. package/src/android-remote/desktop-workspace-state.ts +443 -0
  135. package/src/android-remote/file-change-parser.ts +112 -0
  136. package/src/android-remote/gateway.ts +9108 -0
  137. package/src/android-remote/mutation-store.ts +299 -0
  138. package/src/android-remote/projection.ts +1780 -0
  139. package/src/android-remote/queued-turn-store.ts +248 -0
  140. package/src/android-remote/session-command-recovery.ts +1384 -0
  141. package/src/android-remote/store.ts +466 -0
  142. package/src/android-remote/thread-reconciliation.ts +133 -0
  143. package/src/android-remote/thread-source-paths.ts +307 -0
  144. package/src/android-remote/thread-stream.ts +546 -0
  145. package/src/android-remote/turn-activity.ts +235 -0
  146. package/src/android-remote/user-message-identity.ts +94 -0
  147. package/src/bridge.ts +1793 -0
  148. package/src/chat/inbound.ts +295 -0
  149. package/src/chat/outbound.ts +821 -0
  150. package/src/claude/agents-inject.ts +267 -0
  151. package/src/claude/alias.ts +149 -0
  152. package/src/claude/auth-detect.ts +229 -0
  153. package/src/claude/auth-mode-migration.ts +32 -0
  154. package/src/claude/auth-mode.ts +62 -0
  155. package/src/claude/context-windows.ts +189 -0
  156. package/src/claude/desktop-3p-guard.ts +35 -0
  157. package/src/claude/desktop-3p-paths.ts +84 -0
  158. package/src/claude/desktop-3p.ts +601 -0
  159. package/src/claude/desktop-health.ts +26 -0
  160. package/src/claude/desktop-profile.ts +263 -0
  161. package/src/claude/gateway-cache.ts +70 -0
  162. package/src/claude/inbound-debug.ts +163 -0
  163. package/src/claude/inbound.ts +519 -0
  164. package/src/claude/model-info.ts +154 -0
  165. package/src/claude/outbound.ts +898 -0
  166. package/src/cli/access.ts +108 -0
  167. package/src/cli/account-api.ts +296 -0
  168. package/src/cli/account-auth.ts +250 -0
  169. package/src/cli/account-catalog-refresh.ts +14 -0
  170. package/src/cli/account-extended.ts +476 -0
  171. package/src/cli/account-main.ts +317 -0
  172. package/src/cli/account.ts +297 -0
  173. package/src/cli/agent-driven.ts +70 -0
  174. package/src/cli/agent.ts +184 -0
  175. package/src/cli/catalog-prewarm.ts +27 -0
  176. package/src/cli/claude-desktop.ts +211 -0
  177. package/src/cli/claude.ts +302 -0
  178. package/src/cli/codex-shim-autorestore.ts +45 -0
  179. package/src/cli/codex-shim-readiness.ts +69 -0
  180. package/src/cli/combo.ts +124 -0
  181. package/src/cli/config-command.ts +183 -0
  182. package/src/cli/debug.ts +228 -0
  183. package/src/cli/desktop-first-run.ts +25 -0
  184. package/src/cli/doctor.ts +1022 -0
  185. package/src/cli/export-command.ts +201 -0
  186. package/src/cli/help.ts +370 -0
  187. package/src/cli/index.ts +1565 -0
  188. package/src/cli/init.ts +224 -0
  189. package/src/cli/integrations.ts +225 -0
  190. package/src/cli/interactive-confirm.ts +133 -0
  191. package/src/cli/internal-dispatch.ts +35 -0
  192. package/src/cli/launcher-context.ts +77 -0
  193. package/src/cli/models-runtime.ts +224 -0
  194. package/src/cli/models.ts +340 -0
  195. package/src/cli/observe.ts +170 -0
  196. package/src/cli/opencode.ts +587 -0
  197. package/src/cli/provider-runtime.ts +179 -0
  198. package/src/cli/provider.ts +476 -0
  199. package/src/cli/ready.ts +301 -0
  200. package/src/cli/route-policy.ts +92 -0
  201. package/src/cli/runtime-api.ts +328 -0
  202. package/src/cli/star-prompt.ts +211 -0
  203. package/src/cli/status-oauth.ts +78 -0
  204. package/src/cli/status.ts +321 -0
  205. package/src/cli/system-command.ts +196 -0
  206. package/src/cli/system-restart-client.ts +146 -0
  207. package/src/cli/tray-proxy.ts +205 -0
  208. package/src/cli/v2.ts +200 -0
  209. package/src/cli.ts +10 -0
  210. package/src/clients/config-export.ts +1109 -0
  211. package/src/codex/account-id.ts +34 -0
  212. package/src/codex/account-label.ts +34 -0
  213. package/src/codex/account-lifecycle.ts +172 -0
  214. package/src/codex/account-namespace-match.ts +63 -0
  215. package/src/codex/account-namespaces.ts +195 -0
  216. package/src/codex/account-pause.ts +20 -0
  217. package/src/codex/account-priority.ts +83 -0
  218. package/src/codex/account-runtime-state.ts +31 -0
  219. package/src/codex/account-store.ts +517 -0
  220. package/src/codex/account-usability.ts +40 -0
  221. package/src/codex/admission.ts +263 -0
  222. package/src/codex/app-server-processes.ts +799 -0
  223. package/src/codex/auth-api.ts +2098 -0
  224. package/src/codex/auth-collision.ts +107 -0
  225. package/src/codex/auth-context.ts +480 -0
  226. package/src/codex/autostart-health.ts +156 -0
  227. package/src/codex/catalog/account-models.ts +67 -0
  228. package/src/codex/catalog/aggregation.ts +471 -0
  229. package/src/codex/catalog/bundled.ts +533 -0
  230. package/src/codex/catalog/effort.ts +432 -0
  231. package/src/codex/catalog/filesystem-evidence.ts +302 -0
  232. package/src/codex/catalog/kinds.ts +2 -0
  233. package/src/codex/catalog/metadata.ts +287 -0
  234. package/src/codex/catalog/native-models.ts +7 -0
  235. package/src/codex/catalog/parsing.ts +503 -0
  236. package/src/codex/catalog/provider-fetch.ts +2267 -0
  237. package/src/codex/catalog/sync.ts +1606 -0
  238. package/src/codex/catalog-admission.ts +197 -0
  239. package/src/codex/catalog-refresh-status.ts +87 -0
  240. package/src/codex/catalog-write-serialization.ts +242 -0
  241. package/src/codex/catalog.ts +15 -0
  242. package/src/codex/codex-write-lock.ts +384 -0
  243. package/src/codex/convergence-types.ts +593 -0
  244. package/src/codex/convergence.ts +580 -0
  245. package/src/codex/custom-model-catalog-migration.ts +176 -0
  246. package/src/codex/data/upstream-models.json +830 -0
  247. package/src/codex/desired-state.ts +230 -0
  248. package/src/codex/desktop-client-processes.ts +521 -0
  249. package/src/codex/exec-invocation.ts +22 -0
  250. package/src/codex/features.ts +1091 -0
  251. package/src/codex/generation.ts +202 -0
  252. package/src/codex/history-job.ts +347 -0
  253. package/src/codex/history-lock.ts +242 -0
  254. package/src/codex/history-migration-guardian.ts +115 -0
  255. package/src/codex/history-provider.ts +1075 -0
  256. package/src/codex/history-transition.ts +105 -0
  257. package/src/codex/history-worker.ts +204 -0
  258. package/src/codex/home.ts +206 -0
  259. package/src/codex/inject-coordination.ts +257 -0
  260. package/src/codex/inject.ts +1857 -0
  261. package/src/codex/injected-marker.ts +79 -0
  262. package/src/codex/integration-record.ts +266 -0
  263. package/src/codex/internal/catalog-writer.ts +203 -0
  264. package/src/codex/internal/history-writer.ts +105 -0
  265. package/src/codex/journal.ts +172 -0
  266. package/src/codex/main-account-cache.ts +56 -0
  267. package/src/codex/main-account.ts +40 -0
  268. package/src/codex/management-convergence.ts +114 -0
  269. package/src/codex/model-cache.ts +267 -0
  270. package/src/codex/native-main-admission.ts +47 -0
  271. package/src/codex/native-main-auth-temp.ts +187 -0
  272. package/src/codex/native-main-claim.ts +167 -0
  273. package/src/codex/native-main-lock-file.ts +162 -0
  274. package/src/codex/native-main-owner.ts +329 -0
  275. package/src/codex/native-profile-api.ts +247 -0
  276. package/src/codex/native-profile-manager.ts +1531 -0
  277. package/src/codex/native-profile-processes.ts +121 -0
  278. package/src/codex/native-profile-recovery.ts +99 -0
  279. package/src/codex/native-profile-stage-store.ts +387 -0
  280. package/src/codex/native-profile-startup.ts +348 -0
  281. package/src/codex/native-profile-store.ts +855 -0
  282. package/src/codex/native-profile-types.ts +120 -0
  283. package/src/codex/native-residue.ts +691 -0
  284. package/src/codex/paths.ts +78 -0
  285. package/src/codex/plugins-doctor.ts +242 -0
  286. package/src/codex/pool-rotation.ts +295 -0
  287. package/src/codex/project-config-warnings.ts +426 -0
  288. package/src/codex/prompt-journal.ts +311 -0
  289. package/src/codex/prompt-layers.ts +967 -0
  290. package/src/codex/prompt-lock.ts +143 -0
  291. package/src/codex/provider-adoption.ts +242 -0
  292. package/src/codex/quota-rejection.ts +224 -0
  293. package/src/codex/quota.ts +494 -0
  294. package/src/codex/refresh.ts +60 -0
  295. package/src/codex/routing.ts +1855 -0
  296. package/src/codex/runtime.ts +659 -0
  297. package/src/codex/shim.ts +1215 -0
  298. package/src/codex/subagent-defaults.ts +557 -0
  299. package/src/codex/subagent-model-fallback.ts +560 -0
  300. package/src/codex/sync.ts +238 -0
  301. package/src/codex/transition-state.ts +612 -0
  302. package/src/codex/upstream-host-health.ts +368 -0
  303. package/src/codex/user-identity.ts +374 -0
  304. package/src/codex/warmup.ts +192 -0
  305. package/src/codex/websocket-registry.ts +100 -0
  306. package/src/codex/write-coordination.ts +114 -0
  307. package/src/combos/failover.ts +140 -0
  308. package/src/combos/index.ts +44 -0
  309. package/src/combos/request.ts +64 -0
  310. package/src/combos/resolve.ts +232 -0
  311. package/src/combos/types.ts +392 -0
  312. package/src/config.ts +3270 -0
  313. package/src/generated/model-metadata.ts +144 -0
  314. package/src/github/star-state.ts +203 -0
  315. package/src/grok/inject.ts +540 -0
  316. package/src/grok/inspect.ts +45 -0
  317. package/src/grok/status.ts +127 -0
  318. package/src/grok/sync.ts +66 -0
  319. package/src/images/artifacts.ts +516 -0
  320. package/src/images/fulfill-video.ts +163 -0
  321. package/src/images/fulfill.ts +149 -0
  322. package/src/images/index.ts +4 -0
  323. package/src/images/loop.ts +922 -0
  324. package/src/images/plan.ts +133 -0
  325. package/src/images/synthetic-tool.ts +133 -0
  326. package/src/images/types.ts +41 -0
  327. package/src/images/xai-client.ts +141 -0
  328. package/src/images/xai-video-client.ts +163 -0
  329. package/src/index.ts +22 -0
  330. package/src/integrations/config-io.ts +151 -0
  331. package/src/integrations/journal.ts +315 -0
  332. package/src/integrations/merge.ts +135 -0
  333. package/src/integrations/native/ownership-preflight.ts +202 -0
  334. package/src/integrations/ownership.ts +111 -0
  335. package/src/integrations/registry.ts +108 -0
  336. package/src/integrations/serialize.ts +235 -0
  337. package/src/integrations/state.ts +290 -0
  338. package/src/integrations/store.ts +103 -0
  339. package/src/integrations/writer.ts +492 -0
  340. package/src/lib/abort.ts +146 -0
  341. package/src/lib/admin-secrets.ts +25 -0
  342. package/src/lib/admission.ts +83 -0
  343. package/src/lib/app-owned-memory-stores.ts +173 -0
  344. package/src/lib/app-owned-memory.ts +265 -0
  345. package/src/lib/bounded-body.ts +242 -0
  346. package/src/lib/bun-binary-validator.d.mts +3 -0
  347. package/src/lib/bun-binary-validator.mjs +18 -0
  348. package/src/lib/bun-runtime.ts +184 -0
  349. package/src/lib/bun-stream-caps.ts +127 -0
  350. package/src/lib/config-ownership.ts +438 -0
  351. package/src/lib/crash-guard.ts +344 -0
  352. package/src/lib/debug-log-buffer.ts +83 -0
  353. package/src/lib/debug-settings.ts +108 -0
  354. package/src/lib/debug.ts +31 -0
  355. package/src/lib/destination-policy.ts +316 -0
  356. package/src/lib/errors.ts +364 -0
  357. package/src/lib/eventstream-decoder.ts +253 -0
  358. package/src/lib/gcp-adc.ts +341 -0
  359. package/src/lib/injection-debug-log.ts +58 -0
  360. package/src/lib/local-management-attestation.ts +51 -0
  361. package/src/lib/open-url.ts +25 -0
  362. package/src/lib/pinned-http.ts +182 -0
  363. package/src/lib/privacy.ts +20 -0
  364. package/src/lib/process-control.ts +168 -0
  365. package/src/lib/provider-environment.ts +470 -0
  366. package/src/lib/provider-outbound.ts +203 -0
  367. package/src/lib/provider-url.ts +14 -0
  368. package/src/lib/proxy-env.ts +18 -0
  369. package/src/lib/redact.ts +510 -0
  370. package/src/lib/remodex-home.ts +616 -0
  371. package/src/lib/retry-after.ts +55 -0
  372. package/src/lib/service-secrets.ts +178 -0
  373. package/src/lib/shadow-call.ts +54 -0
  374. package/src/lib/sidecar-tracker.ts +52 -0
  375. package/src/lib/sse-decoder.ts +364 -0
  376. package/src/lib/state-store-registrations.ts +109 -0
  377. package/src/lib/state-store-sweeper.ts +184 -0
  378. package/src/lib/system-restart-contract.ts +73 -0
  379. package/src/lib/test-home-guard.ts +98 -0
  380. package/src/lib/token-estimate.ts +69 -0
  381. package/src/lib/translator-budget.ts +366 -0
  382. package/src/lib/upstream-reachability.ts +91 -0
  383. package/src/lib/upstream-retry.ts +508 -0
  384. package/src/lib/win-exec.ts +115 -0
  385. package/src/lib/win-paths.ts +68 -0
  386. package/src/lib/windows-elevation.ts +705 -0
  387. package/src/lib/windows-secret-acl.ts +817 -0
  388. package/src/lib/windows-user-principal.ts +283 -0
  389. package/src/lib/winsw.ts +402 -0
  390. package/src/model-sources.ts +73 -0
  391. package/src/oauth/anthropic-routing.ts +594 -0
  392. package/src/oauth/anthropic.ts +177 -0
  393. package/src/oauth/callback-server.ts +294 -0
  394. package/src/oauth/chatgpt.ts +150 -0
  395. package/src/oauth/command-code.ts +239 -0
  396. package/src/oauth/cursor.ts +231 -0
  397. package/src/oauth/github-copilot.ts +428 -0
  398. package/src/oauth/google-antigravity.ts +230 -0
  399. package/src/oauth/health.ts +443 -0
  400. package/src/oauth/index.ts +1280 -0
  401. package/src/oauth/key-providers.ts +128 -0
  402. package/src/oauth/kimi.ts +213 -0
  403. package/src/oauth/kiro-credentials.ts +726 -0
  404. package/src/oauth/kiro.ts +621 -0
  405. package/src/oauth/local-token-detect.ts +121 -0
  406. package/src/oauth/log.ts +48 -0
  407. package/src/oauth/login-cli.ts +163 -0
  408. package/src/oauth/pkce.ts +15 -0
  409. package/src/oauth/store.ts +655 -0
  410. package/src/oauth/token-guardian.ts +309 -0
  411. package/src/oauth/types.ts +62 -0
  412. package/src/oauth/xai.ts +241 -0
  413. package/src/providers/alibaba-region-backup.ts +75 -0
  414. package/src/providers/alibaba-region-migration.ts +156 -0
  415. package/src/providers/alibaba-region-startup.ts +36 -0
  416. package/src/providers/antigravity-models.ts +317 -0
  417. package/src/providers/api-keys.ts +140 -0
  418. package/src/providers/base-url-choices.ts +64 -0
  419. package/src/providers/codex-capacity.ts +288 -0
  420. package/src/providers/command-code-efforts.ts +85 -0
  421. package/src/providers/context-cap.ts +73 -0
  422. package/src/providers/derive.ts +451 -0
  423. package/src/providers/free-directory.ts +187 -0
  424. package/src/providers/github-copilot-transport.ts +56 -0
  425. package/src/providers/google-vertex-location.ts +14 -0
  426. package/src/providers/key-failover.ts +271 -0
  427. package/src/providers/kiro-models.ts +67 -0
  428. package/src/providers/label.ts +19 -0
  429. package/src/providers/model-discovery-limits.ts +16 -0
  430. package/src/providers/model-discovery.ts +361 -0
  431. package/src/providers/openai-sidecar.ts +235 -0
  432. package/src/providers/openai-tier-startup.ts +27 -0
  433. package/src/providers/openai-tiers.ts +301 -0
  434. package/src/providers/openai-virtual-models.ts +83 -0
  435. package/src/providers/openrouter-routing.ts +102 -0
  436. package/src/providers/provider-id-rewrite.ts +179 -0
  437. package/src/providers/quota.ts +1942 -0
  438. package/src/providers/reasoning-capabilities.ts +336 -0
  439. package/src/providers/registry.ts +2375 -0
  440. package/src/providers/slug-codec.ts +74 -0
  441. package/src/providers/xai-transport.ts +149 -0
  442. package/src/reasoning-effort.ts +243 -0
  443. package/src/responses/compaction.ts +124 -0
  444. package/src/responses/hosted-tool-policy.ts +9 -0
  445. package/src/responses/parser.ts +714 -0
  446. package/src/responses/reasoning-envelope.ts +60 -0
  447. package/src/responses/reasoning-replay-cache.ts +106 -0
  448. package/src/responses/schema.ts +159 -0
  449. package/src/responses/spill-store.ts +431 -0
  450. package/src/responses/state.ts +1039 -0
  451. package/src/responses/tool-groups.ts +19 -0
  452. package/src/router.ts +802 -0
  453. package/src/routing/analytics.ts +377 -0
  454. package/src/routing/capability.ts +205 -0
  455. package/src/routing/cost.ts +77 -0
  456. package/src/routing/evaluator.ts +444 -0
  457. package/src/routing/health.ts +401 -0
  458. package/src/routing/history/cursor.ts +43 -0
  459. package/src/routing/history/indexer.ts +605 -0
  460. package/src/routing/history/schema.ts +72 -0
  461. package/src/routing/profile-namespace.ts +15 -0
  462. package/src/routing/profile.ts +424 -0
  463. package/src/routing/quota.ts +145 -0
  464. package/src/routing/request-evidence.ts +45 -0
  465. package/src/routing/trace.ts +686 -0
  466. package/src/server/adapter-resolve.ts +83 -0
  467. package/src/server/auth-cors.ts +606 -0
  468. package/src/server/chat-completions.ts +379 -0
  469. package/src/server/claude-messages.ts +980 -0
  470. package/src/server/effort-policy.ts +251 -0
  471. package/src/server/github-copilot-responses-repair.ts +338 -0
  472. package/src/server/gui-static.ts +152 -0
  473. package/src/server/image-retry.ts +42 -0
  474. package/src/server/images.ts +485 -0
  475. package/src/server/index.ts +1633 -0
  476. package/src/server/lifecycle.ts +482 -0
  477. package/src/server/live.ts +609 -0
  478. package/src/server/management/agent-settings-routes.ts +1180 -0
  479. package/src/server/management/android-remote-routes.ts +390 -0
  480. package/src/server/management/api-access.ts +141 -0
  481. package/src/server/management/api-key-usage.ts +167 -0
  482. package/src/server/management/body.ts +35 -0
  483. package/src/server/management/combo-routes.ts +244 -0
  484. package/src/server/management/config-routes.ts +602 -0
  485. package/src/server/management/context.ts +88 -0
  486. package/src/server/management/integration-routes.ts +538 -0
  487. package/src/server/management/logs-usage-routes.ts +516 -0
  488. package/src/server/management/model-routes.ts +519 -0
  489. package/src/server/management/model-rows.ts +143 -0
  490. package/src/server/management/native-integration-routes.ts +781 -0
  491. package/src/server/management/oauth-account-routes.ts +573 -0
  492. package/src/server/management/provider-routes.ts +781 -0
  493. package/src/server/management/request-history-routes.ts +191 -0
  494. package/src/server/management/routing-analytics-routes.ts +74 -0
  495. package/src/server/management/routing-profile-routes.ts +384 -0
  496. package/src/server/management/shared.ts +277 -0
  497. package/src/server/management/sidebar-routes.ts +106 -0
  498. package/src/server/management/sync-response.ts +69 -0
  499. package/src/server/management/system-restart.ts +433 -0
  500. package/src/server/management/system-routes.ts +141 -0
  501. package/src/server/management/usage-summary-cache.ts +86 -0
  502. package/src/server/management-api.ts +269 -0
  503. package/src/server/management-auth.ts +353 -0
  504. package/src/server/memory-watchdog.ts +156 -0
  505. package/src/server/port-reclaim.ts +307 -0
  506. package/src/server/ports.ts +156 -0
  507. package/src/server/proxy-liveness.ts +326 -0
  508. package/src/server/proxy-stop.ts +92 -0
  509. package/src/server/readiness.ts +99 -0
  510. package/src/server/relay-eager.ts +353 -0
  511. package/src/server/relay.ts +1179 -0
  512. package/src/server/request-decompress.ts +132 -0
  513. package/src/server/request-log-conversation.ts +168 -0
  514. package/src/server/request-log.ts +1072 -0
  515. package/src/server/responses/collaboration.ts +409 -0
  516. package/src/server/responses/compact.ts +710 -0
  517. package/src/server/responses/core.ts +3561 -0
  518. package/src/server/responses/encrypted-payload.ts +308 -0
  519. package/src/server/responses/fetch-helpers.ts +171 -0
  520. package/src/server/responses/passthrough-error.ts +78 -0
  521. package/src/server/responses/policy-fallback.ts +152 -0
  522. package/src/server/responses/terminal-guard.ts +230 -0
  523. package/src/server/responses/upstream-error.ts +48 -0
  524. package/src/server/responses-image-gen-repair.ts +132 -0
  525. package/src/server/responses-item-id-repair.ts +272 -0
  526. package/src/server/responses-json-events.ts +52 -0
  527. package/src/server/responses-model-rewrite.ts +29 -0
  528. package/src/server/responses-snapshot-repair.ts +621 -0
  529. package/src/server/responses.ts +10 -0
  530. package/src/server/search.ts +181 -0
  531. package/src/server/sse-frame-buffer.ts +292 -0
  532. package/src/server/sse-payload-rewrite.ts +263 -0
  533. package/src/server/startup-action-control.ts +308 -0
  534. package/src/server/startup-health-cache.ts +119 -0
  535. package/src/server/system-env.ts +418 -0
  536. package/src/server/windows-tcp-drop.ts +184 -0
  537. package/src/server/windows-tray-control.ts +41 -0
  538. package/src/server/ws-bridge.ts +470 -0
  539. package/src/service-manager-probe.ts +824 -0
  540. package/src/service.ts +3011 -0
  541. package/src/stall-timeout.ts +20 -0
  542. package/src/storage/cleanup-job.ts +57 -0
  543. package/src/storage/cleanup.ts +3085 -0
  544. package/src/storage/policy-job.ts +457 -0
  545. package/src/storage/policy-scheduler.ts +40 -0
  546. package/src/storage/policy-worker.ts +59 -0
  547. package/src/storage/policy.ts +527 -0
  548. package/src/storage/restore-job.ts +299 -0
  549. package/src/storage/restore-worker.ts +58 -0
  550. package/src/storage/scanner.ts +238 -0
  551. package/src/storage/storage-mutation-coordinator.ts +139 -0
  552. package/src/storage/worker-lifecycle.ts +215 -0
  553. package/src/tray/assets/opencodex-tray-offline.ico +0 -0
  554. package/src/tray/assets/opencodex-tray-online.ico +0 -0
  555. package/src/tray/assets/opencodex-tray-warning.ico +0 -0
  556. package/src/tray/assets/opencodex-tray.png +0 -0
  557. package/src/tray/windows-tray.ps1 +364 -0
  558. package/src/tray/windows.ts +738 -0
  559. package/src/types.ts +1531 -0
  560. package/src/update/badge.ts +72 -0
  561. package/src/update/desktop-release.ts +1620 -0
  562. package/src/update/index.ts +402 -0
  563. package/src/update/job.ts +1906 -0
  564. package/src/update/notify.ts +261 -0
  565. package/src/update/npm-cache-preflight.d.mts +47 -0
  566. package/src/update/npm-cache-preflight.mjs +201 -0
  567. package/src/update/npm-invocation.d.mts +23 -0
  568. package/src/update/npm-invocation.mjs +94 -0
  569. package/src/update/tray-update-plan.d.mts +18 -0
  570. package/src/update/tray-update-plan.mjs +38 -0
  571. package/src/usage/cost.ts +0 -0
  572. package/src/usage/debug.ts +97 -0
  573. package/src/usage/expected-prices.ts +283 -0
  574. package/src/usage/log.ts +695 -0
  575. package/src/usage/summary.ts +585 -0
  576. package/src/usage/totals.ts +14 -0
  577. package/src/vision/anthropic-describe.ts +185 -0
  578. package/src/vision/describe.ts +127 -0
  579. package/src/vision/index.ts +558 -0
  580. package/src/vision/reasoning.ts +55 -0
  581. package/src/web-search/anthropic-executor.ts +189 -0
  582. package/src/web-search/executor.ts +105 -0
  583. package/src/web-search/format-result.ts +89 -0
  584. package/src/web-search/index.ts +196 -0
  585. package/src/web-search/loop.ts +791 -0
  586. package/src/web-search/parse.ts +235 -0
  587. package/src/web-search/progress-stream.ts +342 -0
  588. package/src/web-search/synthetic-tool.ts +47 -0
@@ -0,0 +1,519 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { readFileSync } from "node:fs";
3
+
4
+ /**
5
+ * Codex parses a catalog entry's `input_modalities` as a closed enum, and one out-of-enum
6
+ * value makes it reject the ENTIRE catalog file — plugins, apps and MCP servers all stop
7
+ * loading over one model's metadata (#759).
8
+ *
9
+ * The catalog writer normalizes on the way out, but a rejected value stored here would still
10
+ * be handed back to the GUI and CLI as if it were real, and the offline `rmx models add` path
11
+ * already refuses it. Validate at ingress so all three paths agree.
12
+ */
13
+ const ALLOWED_INPUT_MODALITIES = new Set(["text", "image", "audio"]);
14
+
15
+ function readInputModalities(raw: unknown): { values?: string[]; error?: string } {
16
+ if (raw === undefined) return {};
17
+ if (!Array.isArray(raw)) return { error: "inputModalities must be an array" };
18
+ // Reject non-strings rather than filtering them out. Dropping them silently accepted a
19
+ // malformed POST and, worse, let a PUT of `[42]` clear the stored modalities while
20
+ // answering 200 — the opposite of the contract this validator exists to state. An empty
21
+ // array stays valid: that is how `rmx models edit --modalities -` clears the field.
22
+ const rejected: string[] = [];
23
+ for (const value of raw) {
24
+ if (typeof value !== "string") return { error: "inputModalities must contain only strings" };
25
+ if (!ALLOWED_INPUT_MODALITIES.has(value)) rejected.push(value);
26
+ }
27
+ if (rejected.length > 0) {
28
+ return { error: `unsupported input modality: ${rejected.join(", ")} (allowed: text, image, audio)` };
29
+ }
30
+ return { values: raw as string[] };
31
+ }
32
+ import type { CatalogModel } from "../../codex/catalog";
33
+ import { catalogModelSlug, configuredNativeAliasSlugs, disabledNativeSlugs, invalidateCodexModelsCache, nativeModelRows, uniqueCatalogModelsForPublicList } from "../../codex/catalog";
34
+ import { CatalogGatherBusyError } from "../../codex/catalog/provider-fetch";
35
+ import { getProviderLiveModelCount } from "../../codex/model-cache";
36
+ import {
37
+ ConfigMutationLockError,
38
+ DEFAULT_SUBAGENT_MODELS,
39
+ codexAutoStartEnabled,
40
+ hasOwnProvider,
41
+ isValidProviderName,
42
+ multiAgentGuidanceEnabled,
43
+ providerBaseUrlConfigError,
44
+ providerHeadersConfigError,
45
+ saveConfigPreservingClaudeCode,
46
+ withConfigMutationLockSync,
47
+ } from "../../config";
48
+ import {
49
+ clearLoginState,
50
+ getLoginStatus,
51
+ isPublicOAuthProvider,
52
+ listOAuthProviders,
53
+ startLoginFlow,
54
+ submitManualLoginCode,
55
+ upsertOAuthProvider,
56
+ } from "../../oauth";
57
+ import { removeCredential } from "../../oauth/store";
58
+ import { providerDestinationResolvedError } from "../../lib/destination-policy";
59
+ import { enrichProviderFromCatalog, listKeyLoginProviders } from "../../oauth/key-providers";
60
+ import { deriveProviderPresets } from "../../providers/derive";
61
+ import { providerCodexAccountMode } from "../../providers/registry";
62
+ import { routedSlug, slugEquals } from "../../providers/slug-codec";
63
+ import { COMBO_NAMESPACE, comboDisabledModelSelectors, comboModelId, preservesPhysicalComboProvider } from "../../combos";
64
+ import { clearProviderQuotaCache, fetchProviderQuotaReports } from "../../providers/quota";
65
+ import { isCanonicalOpenAiForwardProvider } from "../../providers/openai-tiers";
66
+ import { clearThreadAccountMap } from "../../codex/routing";
67
+ import { primeCodexPoolQuotas } from "../../codex/auth-api";
68
+ import { DEFAULT_PROVIDER_CONTEXT_CAP, globalContextCapValue, providerContextCap, providerContextCaps, setAllProviderContextCaps, setGlobalContextCapValue, setProviderContextCap } from "../../providers/context-cap";
69
+ import { resolveCodexHomeDir } from "../../codex/home";
70
+ import { readUsageEntries } from "../../usage/log";
71
+ import { getUsageDebugLogEntries } from "../../usage/debug";
72
+ import { parseRange, parseUsageSurface, summarizeUsage } from "../../usage/summary";
73
+ import { stripCodexRuntimeProviderFields } from "../../codex/auth-context";
74
+ import { getProviderRegistryEntry } from "../../providers/registry";
75
+ import { getDebugLogEntries } from "../../lib/debug-log-buffer";
76
+ import { getInjectionDebugLogEntries } from "../../lib/injection-debug-log";
77
+ import {
78
+ clearDebugSettings,
79
+ clearDebugSetting,
80
+ getDebugSettings,
81
+ setDebugSettings,
82
+ type DebugFlag,
83
+ } from "../../lib/debug-settings";
84
+ import type { OcxClaudeCodeConfig, OcxConfig, OcxCustomModel, OcxProviderConfig } from "../../types";
85
+ import { drainAndShutdown } from "../lifecycle";
86
+ import { filterRequestLogs, getRequestLogEntries, type RequestLogEntry } from "../request-log";
87
+ import { estimateComboCost, estimateRequestCost, normalizeCostTokens, tokensPerSecond } from "../../usage/cost";
88
+ import type { PersistedUsageAttempt } from "../../usage/log";
89
+ import { isAllowedRequestOrigin, jsonResponse, providerManagementConfigError, publicProviderBaseUrl, safeConfigDTO, corsHeaders } from "../auth-cors";
90
+ import { applySystemEnvToggle } from "../system-env";
91
+ import {
92
+ EXPORT_CLIENTS,
93
+ EXPORT_CLIENT_IDS,
94
+ OPENCODE_PROVIDER_ID,
95
+ buildClientConfigText,
96
+ isExportClientId,
97
+ opencodeProxyBaseUrl,
98
+ } from "../../clients/config-export";
99
+ import type {
100
+ ExportClientId,
101
+ ExportModel,
102
+ OpencodeGeneratedConfig,
103
+ PiGeneratedConfig,
104
+ } from "../../clients/config-export";
105
+
106
+ import { isPlainRecord, parseDebugLogQuery, tokPerSecondResult, unavailableCostReason, costResult, requestLogDto, stripRegistryOnlyStaticHeaders, fetchAllModels } from "./shared";
107
+ import type { MetricUnavailableReason, TokPerSecondResult, CostEstimateReason, CostResult, MetricSource } from "./shared";
108
+ import type { ManagementContext } from "./context";
109
+ import { listManagementModelRows, loadExportModels } from "./model-rows";
110
+ import { readManagementJsonBody, rethrowManagementBodyTooLarge } from "./body";
111
+ import { modelSourceDisplayName, modelSourceVisible } from "../../model-sources";
112
+
113
+ /**
114
+ * Counts read back off the SERIALIZED document rather than recomputed from the input rows.
115
+ * `modelsWithoutLimits` drives a GUI line claiming "these models ship without limits", so it
116
+ * has to describe the bytes the user actually receives — a parallel reimplementation of the
117
+ * core's "authoritative context window" rule would be free to drift from it silently.
118
+ */
119
+ function summarizeExportedModels(client: ExportClientId, document: unknown): { modelCount: number; modelsWithoutLimits: number } {
120
+ // Each client counts its own document shape. The previous branch assumed
121
+ // "anything that is not OpenCode must be Pi", which silently misread the
122
+ // moment a third client existed.
123
+ return EXPORT_CLIENTS[client].summarize(document);
124
+ }
125
+
126
+ export async function handleModelRoutes(ctx: ManagementContext): Promise<Response | null> {
127
+ const { req, url, config, deps, convergeCodexCatalog, syncClaudeAgentDefsBestEffort } = ctx;
128
+ // A handler persists the exact config object passed in. Production defaults to
129
+ // the real store; tests that pass an in-memory fixture inject a no-op/spy. Do not
130
+ // bypass this seam with a dynamic config import — doing so replaced a user's
131
+ // ~/.opencodex/config.json with the `existing-uuid` test fixture.
132
+ const persistConfig = deps.saveConfigPreservingClaudeCode ?? saveConfigPreservingClaudeCode;
133
+
134
+ if (url.pathname === "/api/catalog" && req.method === "GET") {
135
+ const { readCatalog, readCodexCatalogPath } = await import("../../codex/catalog");
136
+ const catalog = readCatalog(readCodexCatalogPath());
137
+ if (!catalog) return jsonResponse({ error: "catalog not found" }, 404, req, config);
138
+ const headers: Record<string, string> = {
139
+ "Content-Type": "application/json",
140
+ ...corsHeaders(req, config),
141
+ };
142
+ const { loadPersistedCodexRuntime } = await import("../../codex/runtime");
143
+ const version = loadPersistedCodexRuntime()?.selectedVersion;
144
+ if (version) headers["x-opencodex-codex-version"] = version;
145
+ return new Response(JSON.stringify(catalog), { status: 200, headers });
146
+ }
147
+
148
+ if (url.pathname === "/api/models" && req.method === "GET") {
149
+ return jsonResponse(await listManagementModelRows(config));
150
+ }
151
+
152
+ if (url.pathname === "/api/model-sources" && req.method === "GET") {
153
+ const rows = await listManagementModelRows(config);
154
+ const counts = new Map<string, number>();
155
+ for (const row of rows) counts.set(row.provider, (counts.get(row.provider) ?? 0) + 1);
156
+ const sources = Object.entries(config.providers)
157
+ .map(([provider, providerConfig]) => ({
158
+ provider,
159
+ displayName: modelSourceDisplayName(provider, provider === "openai"),
160
+ kind: provider === "openai" ? "chatgpt" as const : "provider" as const,
161
+ visible: modelSourceVisible(config, provider),
162
+ available: providerConfig.disabled !== true,
163
+ configured: true,
164
+ modelCount: counts.get(provider) ?? 0,
165
+ }))
166
+ .sort((left, right) => {
167
+ if (left.kind !== right.kind) return left.kind === "chatgpt" ? -1 : 1;
168
+ return left.displayName.localeCompare(right.displayName);
169
+ });
170
+ return jsonResponse(sources);
171
+ }
172
+
173
+ if (url.pathname === "/api/model-source-visibility" && req.method === "PUT") {
174
+ let body: { provider?: unknown; visible?: unknown };
175
+ try {
176
+ body = await readManagementJsonBody(req);
177
+ } catch (error) {
178
+ rethrowManagementBodyTooLarge(error);
179
+ return jsonResponse({ error: "invalid JSON body" }, 400);
180
+ }
181
+ const provider = typeof body.provider === "string" ? body.provider.trim() : "";
182
+ if (!provider || !hasOwnProvider(config.providers, provider)) {
183
+ return jsonResponse({ error: provider ? "unknown provider" : "provider is required" }, provider ? 404 : 400);
184
+ }
185
+ if (typeof body.visible !== "boolean") {
186
+ return jsonResponse({ error: "visible must be a boolean" }, 400);
187
+ }
188
+ try {
189
+ withConfigMutationLockSync(() => {
190
+ const visibility = { ...(config.modelSourceVisibility ?? {}) };
191
+ if (body.visible) delete visibility[provider];
192
+ else visibility[provider] = false;
193
+ if (Object.keys(visibility).length > 0) config.modelSourceVisibility = visibility;
194
+ else delete config.modelSourceVisibility;
195
+ persistConfig(config);
196
+ });
197
+ } catch (error) {
198
+ if (error instanceof ConfigMutationLockError) {
199
+ return jsonResponse({ error: "model source update already in progress" }, 409);
200
+ }
201
+ throw error;
202
+ }
203
+ invalidateCodexModelsCache();
204
+ const catalogRefresh = await convergeCodexCatalog();
205
+ deps.androidRemoteController?.notifyModelCatalogChanged();
206
+ await syncClaudeAgentDefsBestEffort();
207
+ return jsonResponse({
208
+ ok: true,
209
+ provider,
210
+ visible: modelSourceVisible(config, provider),
211
+ catalogRefresh,
212
+ });
213
+ }
214
+
215
+ /**
216
+ * Client config document for OpenCode / Pi, built from the SAME function `rmx export`
217
+ * calls, so the bytes a user downloads here and the bytes they pipe from the CLI cannot
218
+ * disagree. Read-only: this route never writes the user's client config.
219
+ */
220
+ if (url.pathname === "/api/client-config" && req.method === "GET") {
221
+ const requested = url.searchParams.get("client")?.trim() ?? "";
222
+ if (!isExportClientId(requested)) {
223
+ return jsonResponse(
224
+ { error: `client must be one of: ${EXPORT_CLIENT_IDS.join(", ")}` },
225
+ 400,
226
+ req,
227
+ config,
228
+ );
229
+ }
230
+ const spec = EXPORT_CLIENTS[requested];
231
+ let models: ExportModel[];
232
+ try {
233
+ // The ONE loader every export surface uses. It carries the visibility
234
+ // filter with it, so this route and the integration routes cannot end up
235
+ // telling a client about different models.
236
+ models = await loadExportModels(config);
237
+ } catch (error) {
238
+ // A partial or empty `models` block reads as a valid config while offering nothing,
239
+ // so a catalog failure is surfaced as unavailable rather than serialized. The
240
+ // catalog-busy error keeps its own 503 from handleManagementAPI.
241
+ if (error instanceof CatalogGatherBusyError) throw error;
242
+ return jsonResponse(
243
+ { error: `model catalog unavailable: ${error instanceof Error ? error.message : String(error)}` },
244
+ 503,
245
+ req,
246
+ config,
247
+ );
248
+ }
249
+ const built = buildClientConfigText(requested, {
250
+ baseUrl: opencodeProxyBaseUrl(Number(url.port) || config.port, config.hostname),
251
+ models,
252
+ config,
253
+ });
254
+ const document = built.document;
255
+ return jsonResponse({
256
+ client: spec.id,
257
+ filename: spec.filename,
258
+ destination: spec.destination(process.env),
259
+ apiKeyEnv: spec.apiKeyEnv,
260
+ exportHint: spec.exportHint,
261
+ // The client's own format and the exact bytes for it. The GUI previously
262
+ // re-serialized `config` as JSON, which is wrong for four of the six
263
+ // clients; `mediaType` also drives the download blob.
264
+ format: built.format,
265
+ mediaType: built.mediaType,
266
+ text: built.text,
267
+ ...summarizeExportedModels(requested, document),
268
+ config: document,
269
+ }, 200, req, config);
270
+ }
271
+
272
+ // Enable/disable models: which routed models Codex sees. PUT hides them from the catalog +
273
+ // /v1/models and invalidates Codex's 5-min models cache so it applies on the next turn.
274
+ if (url.pathname === "/api/disabled-models" && req.method === "PUT") {
275
+ let body: { models?: unknown };
276
+ try { body = await readManagementJsonBody(req); } catch (error) { rethrowManagementBodyTooLarge(error); return jsonResponse({ error: "invalid JSON body" }, 400); }
277
+ const disabled = Array.isArray(body.models) ? body.models.filter((m): m is string => typeof m === "string") : [];
278
+ config.disabledModels = disabled;
279
+ persistConfig(config);
280
+ const catalogRefresh = await convergeCodexCatalog();
281
+ deps.androidRemoteController?.notifyModelCatalogChanged();
282
+ return jsonResponse({ ok: true, disabled, catalogRefresh });
283
+ }
284
+
285
+ // One user-facing visibility switch spans two persisted filters: a provider allowlist and the
286
+ // shared blocklist. Keep the update atomic so an interrupted request cannot expose a half-applied
287
+ // state. Native rows only use the blocklist; routed/custom rows also join a non-empty allowlist.
288
+ if (url.pathname === "/api/model-visibility" && req.method === "PUT") {
289
+ let parsedBody: unknown;
290
+ try { parsedBody = await readManagementJsonBody(req); } catch (error) { rethrowManagementBodyTooLarge(error); return jsonResponse({ error: "invalid JSON body" }, 400); }
291
+ if (!isPlainRecord(parsedBody)) return jsonResponse({ error: "invalid model visibility request" }, 400);
292
+ const body = parsedBody;
293
+ const scope = body.scope === "models" || body.scope === "provider" ? body.scope : null;
294
+ const provider = typeof body.provider === "string" ? body.provider.trim() : "";
295
+ if (!scope || !provider || !isValidProviderName(provider) || typeof body.enabled !== "boolean" || !Array.isArray(body.targets)) {
296
+ return jsonResponse({ error: "invalid model visibility request" }, 400);
297
+ }
298
+
299
+ const providerConfig = hasOwnProvider(config.providers, provider) ? config.providers[provider] : undefined;
300
+ const isVirtualComboNamespace = provider === COMBO_NAMESPACE && !preservesPhysicalComboProvider(config);
301
+ if (!providerConfig && provider !== "openai" && !isVirtualComboNamespace) {
302
+ return jsonResponse({ error: "unknown model visibility provider" }, 400);
303
+ }
304
+ const supportedNative = new Set(nativeModelRows(config).map(row => row.slug));
305
+ const targets: Array<{ id: string; native: boolean }> = [];
306
+ const seen = new Set<string>();
307
+ for (const value of body.targets) {
308
+ if (!isPlainRecord(value) || typeof value.id !== "string" || (value.native !== undefined && typeof value.native !== "boolean")) {
309
+ return jsonResponse({ error: "invalid model visibility target" }, 400);
310
+ }
311
+ const id = value.id.trim();
312
+ const native = value.native === true;
313
+ if (!id || (provider === "openai") !== native || (native && !supportedNative.has(id))) {
314
+ return jsonResponse({ error: "invalid model visibility target" }, 400);
315
+ }
316
+ const key = `${native ? "native" : "routed"}:${id}`;
317
+ if (!seen.has(key)) {
318
+ seen.add(key);
319
+ targets.push({ id, native });
320
+ }
321
+ }
322
+ if (targets.length === 0) return jsonResponse({ error: "model visibility targets required" }, 400);
323
+
324
+ const knownComboSelectors = new Set(
325
+ Object.entries(config.combos ?? {}).flatMap(([id, combo]) => (
326
+ comboDisabledModelSelectors(id, combo)
327
+ )),
328
+ );
329
+ const targetComboSelectors = new Map<string, Set<string>>();
330
+ if (isVirtualComboNamespace) {
331
+ for (const target of targets) {
332
+ const combo = config.combos && Object.hasOwn(config.combos, target.id) ? config.combos[target.id] : undefined;
333
+ if (!combo) return jsonResponse({ error: "invalid model visibility target" }, 400);
334
+ targetComboSelectors.set(target.id, new Set(comboDisabledModelSelectors(target.id, combo)));
335
+ }
336
+ }
337
+ const matchesTarget = (stored: string, target: { id: string; native: boolean }) => target.native
338
+ ? stored === target.id
339
+ : isVirtualComboNamespace
340
+ ? targetComboSelectors.get(target.id)!.has(stored)
341
+ : slugEquals(stored, provider, target.id);
342
+
343
+ let disabled = [...new Set(config.disabledModels ?? [])];
344
+ if (body.enabled) {
345
+ if (scope === "provider") {
346
+ if (providerConfig && !isVirtualComboNamespace) delete providerConfig.selectedModels;
347
+ if (isVirtualComboNamespace) {
348
+ disabled = disabled.filter(stored => !knownComboSelectors.has(stored));
349
+ } else {
350
+ const nativeIds = provider === "openai"
351
+ ? disabledNativeSlugs({ disabledModels: disabled })
352
+ : new Set<string>();
353
+ const nativeAliasSlugs = provider === "openai"
354
+ ? configuredNativeAliasSlugs(config)
355
+ : new Set<string>();
356
+ disabled = disabled.filter(stored => (
357
+ knownComboSelectors.has(stored)
358
+ || nativeAliasSlugs.has(stored)
359
+ || (!stored.startsWith(`${provider}/`) && !nativeIds.has(stored))
360
+ ));
361
+ }
362
+ } else {
363
+ if (!isVirtualComboNamespace && providerConfig?.selectedModels && providerConfig.selectedModels.length > 0) {
364
+ const additions = targets.filter(target => !target.native).map(target => target.id);
365
+ providerConfig.selectedModels = [...new Set([...providerConfig.selectedModels, ...additions])];
366
+ }
367
+ disabled = disabled.filter(stored => !targets.some(target => matchesTarget(stored, target)));
368
+ }
369
+ } else {
370
+ for (const target of targets) {
371
+ const canonical = target.native
372
+ ? target.id
373
+ : isVirtualComboNamespace
374
+ ? comboModelId(target.id)
375
+ : routedSlug(provider, target.id);
376
+ const alreadyDisabled = disabled.some(stored => matchesTarget(stored, target));
377
+ if (!alreadyDisabled) disabled.push(canonical);
378
+ }
379
+ }
380
+
381
+ config.disabledModels = disabled;
382
+ persistConfig(config);
383
+ const catalogRefresh = await convergeCodexCatalog();
384
+ deps.androidRemoteController?.notifyModelCatalogChanged();
385
+ return jsonResponse({ ok: true, scope, provider, enabled: body.enabled, disabled, catalogRefresh });
386
+ }
387
+
388
+ if (url.pathname === "/api/custom-models" && req.method === "GET") {
389
+ return jsonResponse(config.customModels ?? []);
390
+ }
391
+
392
+ if (url.pathname === "/api/custom-models" && req.method === "POST") {
393
+ let body: { provider?: unknown; modelId?: unknown; displayName?: unknown; contextWindow?: unknown; inputModalities?: unknown };
394
+ try { body = await readManagementJsonBody(req); } catch (error) { rethrowManagementBodyTooLarge(error); return jsonResponse({ error: "invalid JSON body" }, 400); }
395
+ const provider = typeof body.provider === "string" ? body.provider.trim() : "";
396
+ const modelId = typeof body.modelId === "string" ? body.modelId.trim() : "";
397
+ if (!provider || !modelId) return jsonResponse({ error: "provider and modelId are required" }, 400);
398
+ if (modelId.includes("/")) return jsonResponse({ error: "modelId must not contain /" }, 400);
399
+ if (!isValidProviderName(provider)) return jsonResponse({ error: "invalid provider name" }, 400);
400
+ if (!hasOwnProvider(config.providers, provider)) return jsonResponse({ error: "provider not configured" }, 404);
401
+ const displayName = typeof body.displayName === "string" && body.displayName.trim() ? body.displayName.trim() : undefined;
402
+ if (displayName?.includes("/")) return jsonResponse({ error: "displayName must not contain /" }, 400);
403
+ const contextWindow = typeof body.contextWindow === "number" && body.contextWindow > 0 ? Math.floor(body.contextWindow) : undefined;
404
+ const modalities = readInputModalities(body.inputModalities);
405
+ if (modalities.error) return jsonResponse({ error: modalities.error }, 400);
406
+ const inputModalities = modalities.values;
407
+ const existing = config.customModels ?? [];
408
+ const newSlug = routedSlug(provider, modelId);
409
+ if (existing.some(cm => routedSlug(cm.provider, cm.modelId) === newSlug)) {
410
+ return jsonResponse({ error: "duplicate model" }, 409);
411
+ }
412
+ const entry: OcxCustomModel = {
413
+ id: randomUUID(),
414
+ provider,
415
+ modelId,
416
+ ...(displayName ? { displayName } : {}),
417
+ ...(contextWindow ? { contextWindow } : {}),
418
+ ...(inputModalities && inputModalities.length > 0 ? { inputModalities } : {}),
419
+ addedAt: new Date().toISOString(),
420
+ };
421
+ config.customModels = [...existing, entry];
422
+ persistConfig(config);
423
+ const catalogRefresh = await convergeCodexCatalog();
424
+ deps.androidRemoteController?.notifyModelCatalogChanged();
425
+ return jsonResponse({ ...entry, catalogRefresh }, 201);
426
+ }
427
+
428
+ const customPutMatch = url.pathname.match(/^\/api\/custom-models\/([^/]+)$/);
429
+ if (customPutMatch && req.method === "PUT") {
430
+ let id: string;
431
+ try { id = decodeURIComponent(customPutMatch[1]); } catch { return jsonResponse({ error: "invalid id encoding" }, 400); }
432
+ let body: { displayName?: unknown; contextWindow?: unknown; inputModalities?: unknown; modelId?: unknown };
433
+ try { body = await readManagementJsonBody(req); } catch (error) { rethrowManagementBodyTooLarge(error); return jsonResponse({ error: "invalid JSON body" }, 400); }
434
+ const list = config.customModels ?? [];
435
+ const idx = list.findIndex(cm => cm.id === id);
436
+ if (idx === -1) return jsonResponse({ error: "not found" }, 404);
437
+ const cm = { ...list[idx] };
438
+ if (typeof body.modelId === "string" && body.modelId.trim()) {
439
+ if (body.modelId.includes("/")) return jsonResponse({ error: "modelId must not contain /" }, 400);
440
+ cm.modelId = body.modelId.trim();
441
+ }
442
+ if (body.displayName !== undefined) {
443
+ const dn = typeof body.displayName === "string" ? body.displayName.trim() : "";
444
+ if (dn.includes("/")) return jsonResponse({ error: "displayName must not contain /" }, 400);
445
+ cm.displayName = dn || undefined;
446
+ }
447
+ if (body.contextWindow !== undefined) {
448
+ cm.contextWindow = typeof body.contextWindow === "number" && body.contextWindow > 0 ? Math.floor(body.contextWindow) : undefined;
449
+ }
450
+ if (body.inputModalities !== undefined) {
451
+ const edited = readInputModalities(body.inputModalities);
452
+ if (edited.error) return jsonResponse({ error: edited.error }, 400);
453
+ cm.inputModalities = edited.values && edited.values.length > 0 ? edited.values : undefined;
454
+ }
455
+ const updatedSlug = routedSlug(cm.provider, cm.modelId);
456
+ if (list.some((other, i) => i !== idx && routedSlug(other.provider, other.modelId) === updatedSlug)) {
457
+ return jsonResponse({ error: "duplicate model" }, 409);
458
+ }
459
+ list[idx] = cm;
460
+ config.customModels = list;
461
+ persistConfig(config);
462
+ const catalogRefresh = await convergeCodexCatalog();
463
+ deps.androidRemoteController?.notifyModelCatalogChanged();
464
+ return jsonResponse({ ...cm, catalogRefresh });
465
+ }
466
+
467
+ const customDelMatch = url.pathname.match(/^\/api\/custom-models\/([^/]+)$/);
468
+ if (customDelMatch && req.method === "DELETE") {
469
+ let id: string;
470
+ try { id = decodeURIComponent(customDelMatch[1]); } catch { return jsonResponse({ error: "invalid id encoding" }, 400); }
471
+ const list = config.customModels ?? [];
472
+ const idx = list.findIndex(cm => cm.id === id);
473
+ if (idx === -1) return jsonResponse({ error: "not found" }, 404);
474
+ list.splice(idx, 1);
475
+ config.customModels = list.length > 0 ? list : undefined;
476
+ persistConfig(config);
477
+ const catalogRefresh = await convergeCodexCatalog();
478
+ deps.androidRemoteController?.notifyModelCatalogChanged();
479
+ return jsonResponse({ ok: true, catalogRefresh });
480
+ }
481
+
482
+ // Per-provider catalog allowlist (issue #52): when a provider has a non-empty selectedModels list,
483
+ // only those ids ship to Codex's catalog / /v1/models. GET returns the CURRENT selection plus the
484
+ // FULL available set per provider (unfiltered — the picker needs everything to choose from).
485
+ if (url.pathname === "/api/selected-models" && req.method === "GET") {
486
+ const models = await fetchAllModels(config);
487
+ const available: Record<string, string[]> = {};
488
+ for (const m of models) (available[m.provider] ??= []).push(m.id);
489
+ const selected: Record<string, string[]> = {};
490
+ // Live-catalog provenance. The GUI cannot infer this by subtracting known custom ids: an id
491
+ // that is both custom and discovered would make a real live catalog look custom-only.
492
+ const liveModelCounts: Record<string, number> = {};
493
+ for (const [name, prov] of Object.entries(config.providers)) {
494
+ if (Array.isArray(prov.selectedModels) && prov.selectedModels.length > 0) selected[name] = [...prov.selectedModels];
495
+ const liveCount = getProviderLiveModelCount(name);
496
+ if (liveCount !== undefined) liveModelCounts[name] = liveCount;
497
+ }
498
+ return jsonResponse({ selected, available, liveModelCounts });
499
+ }
500
+ if (url.pathname === "/api/selected-models" && req.method === "PUT") {
501
+ let body: { provider?: unknown; models?: unknown };
502
+ try { body = await readManagementJsonBody(req); } catch (error) { rethrowManagementBodyTooLarge(error); return jsonResponse({ error: "invalid JSON body" }, 400); }
503
+ const provider = typeof body.provider === "string" ? body.provider : "";
504
+ if (!provider || !hasOwnProvider(config.providers, provider)) {
505
+ return jsonResponse({ error: "unknown provider" }, provider ? 404 : 400);
506
+ }
507
+ const models = Array.isArray(body.models)
508
+ ? [...new Set(body.models.filter((m): m is string => typeof m === "string"))]
509
+ : [];
510
+ // Empty list clears the allowlist (provider reverts to exposing all models).
511
+ if (models.length > 0) config.providers[provider].selectedModels = models;
512
+ else delete config.providers[provider].selectedModels;
513
+ persistConfig(config);
514
+ const catalogRefresh = await convergeCodexCatalog();
515
+ deps.androidRemoteController?.notifyModelCatalogChanged();
516
+ return jsonResponse({ ok: true, provider, selected: models, catalogRefresh });
517
+ }
518
+ return null;
519
+ }
@@ -0,0 +1,143 @@
1
+ /**
2
+ * The `/api/models` row list and its projection into export models.
3
+ *
4
+ * Extracted from model-routes.ts so `/api/client-config` and the integration
5
+ * routes read the SAME visible-model list. Two callers computing "which models
6
+ * does this user actually have" independently is how the export and the toggle
7
+ * would quietly disagree about what a client was told.
8
+ *
9
+ * Bodies are unchanged from their previous home; only `export` was added.
10
+ */
11
+ import type { CatalogModel } from "../../codex/catalog";
12
+ import { catalogModelSlug, nativeModelRows, nativeReasoningEfforts, uniqueCatalogModelsForPublicList } from "../../codex/catalog";
13
+ import type { ExportModel } from "../../clients/config-export";
14
+ import { providerContextCap } from "../../providers/context-cap";
15
+ import { routedSlug, slugEquals } from "../../providers/slug-codec";
16
+ import type { OcxConfig } from "../../types";
17
+ import { modelSourceVisible, sourceAwareModelDisplayName } from "../../model-sources";
18
+ import { fetchAllModels } from "./shared";
19
+
20
+ /**
21
+ * One row of the `/api/models` list. Routed rows spread a `CatalogModel`, so the shape is
22
+ * that model plus the identity/visibility fields this boundary computes for every row
23
+ * regardless of source. `disabled` is always present; the rest vary by row origin.
24
+ */
25
+ export type ManagementModelRow = Partial<CatalogModel> & {
26
+ provider: string;
27
+ id: string;
28
+ namespaced: string;
29
+ disabled: boolean;
30
+ sourceVisible: boolean;
31
+ pickerDisplayName: string;
32
+ native?: boolean;
33
+ custom?: boolean;
34
+ customId?: string;
35
+ };
36
+
37
+ /**
38
+ * The exact row list `/api/models` returns. Extracted so `/api/client-config` exports the
39
+ * models the GUI's Models tab shows — including this function's `disabled` computation,
40
+ * which the export core (src/clients/config-export.ts) deliberately does not perform.
41
+ */
42
+ export async function listManagementModelRows(config: OcxConfig): Promise<ManagementModelRow[]> {
43
+ const models = await fetchAllModels(config);
44
+ const disabled = new Set(config.disabledModels ?? []);
45
+ // Native GPT passthrough rows lead (provider "openai", bare-slug namespaced ids): sourced
46
+ // from the static supported set so a disabled model stays listed and re-enableable.
47
+ const native: ManagementModelRow[] = nativeModelRows(config).map(row => ({
48
+ provider: "openai",
49
+ id: row.slug,
50
+ namespaced: row.slug,
51
+ disabled: row.disabled,
52
+ sourceVisible: modelSourceVisible(config, "openai"),
53
+ pickerDisplayName: sourceAwareModelDisplayName({
54
+ provider: "openai",
55
+ modelId: row.slug,
56
+ native: true,
57
+ }),
58
+ native: true,
59
+ // This is the shared model catalogue, not the vision-sidecar request boundary. Preserve the
60
+ // complete Codex ladder here; the image describer validates its narrower effort vocabulary
61
+ // independently when it builds its own request.
62
+ reasoningEfforts: nativeReasoningEfforts(row.slug),
63
+ ...(row.contextWindow !== undefined ? { contextWindow: row.contextWindow } : {}),
64
+ }));
65
+ const customModels: ManagementModelRow[] = (config.customModels ?? []).map(cm => {
66
+ const namespaced = routedSlug(cm.provider, cm.modelId);
67
+ return {
68
+ provider: cm.provider,
69
+ id: cm.modelId,
70
+ namespaced,
71
+ disabled: [...disabled].some(stored => slugEquals(stored, cm.provider, cm.modelId)),
72
+ sourceVisible: modelSourceVisible(config, cm.provider),
73
+ pickerDisplayName: sourceAwareModelDisplayName({
74
+ provider: cm.provider,
75
+ modelId: cm.modelId,
76
+ displayName: cm.displayName,
77
+ }),
78
+ custom: true,
79
+ customId: cm.id,
80
+ displayName: cm.displayName,
81
+ ...(cm.contextWindow ? { contextWindow: cm.contextWindow } : {}),
82
+ ...(cm.inputModalities ? { inputModalities: cm.inputModalities } : {}),
83
+ };
84
+ });
85
+ const publicModels = uniqueCatalogModelsForPublicList(models);
86
+ const comboNamespaced = new Set(
87
+ publicModels.filter(model => model.provider === "combo").map(catalogModelSlug),
88
+ );
89
+ const visibleCustomModels = customModels.filter(model => !comboNamespaced.has(model.namespaced));
90
+ // Custom metadata wins when a physical live/static row resolves to the same Codex-facing
91
+ // slug, while a combo keeps the same precedence it has in routing and /v1/models.
92
+ const customNamespaced = new Set(visibleCustomModels.map(c => c.namespaced));
93
+ const dedupedRouted = publicModels.map((m): ManagementModelRow | null => {
94
+ // Codex-facing slug (one "/", slug-codec); disabledModels compares tolerate both forms.
95
+ const namespaced = catalogModelSlug(m);
96
+ if (m.provider !== "combo" && customNamespaced.has(namespaced)) return null;
97
+ const contextCap = providerContextCap(config, m.provider);
98
+ const nativeAlias = m.provider === "combo" && m.nativeAlias === true;
99
+ return {
100
+ ...m,
101
+ namespaced,
102
+ disabled: [...disabled].some(stored => (
103
+ (!nativeAlias && stored === namespaced) || slugEquals(stored, m.provider, m.id)
104
+ )),
105
+ sourceVisible: modelSourceVisible(config, m.provider),
106
+ pickerDisplayName: sourceAwareModelDisplayName({
107
+ provider: m.provider,
108
+ modelId: m.id,
109
+ displayName: m.displayName,
110
+ }),
111
+ ...(contextCap !== undefined ? { contextCap, contextCapped: m.contextCapped === true } : {}),
112
+ };
113
+ }).filter((row): row is ManagementModelRow => row !== null);
114
+ return [...native, ...dedupedRouted, ...visibleCustomModels];
115
+ }
116
+
117
+ /** `/api/models` row → the narrower input the client-config serializers accept. */
118
+ export function toExportModel(row: ManagementModelRow): ExportModel {
119
+ return {
120
+ namespaced: row.namespaced,
121
+ provider: row.provider,
122
+ id: row.id,
123
+ ...(row.native ? { native: true } : {}),
124
+ ...(row.displayName ? { displayName: row.displayName } : {}),
125
+ ...(row.contextWindow !== undefined ? { contextWindow: row.contextWindow } : {}),
126
+ ...(row.inputModalities ? { inputModalities: row.inputModalities } : {}),
127
+ };
128
+ }
129
+
130
+ /**
131
+ * Visible (non-disabled) rows as export models — the ONE loader both
132
+ * `/api/client-config` and the integration routes use, so the two can never
133
+ * disagree about which models a client is told about.
134
+ *
135
+ * The visibility filter lives HERE rather than at each call site: the export
136
+ * core serializes what it is given, so a model the user disabled in the Models
137
+ * tab is absent from `/v1/models` and exporting it would hand the client a
138
+ * selector the proxy refuses to route.
139
+ */
140
+ export async function loadExportModels(config: OcxConfig): Promise<ExportModel[]> {
141
+ const rows = await listManagementModelRows(config);
142
+ return rows.filter(row => !row.disabled && row.sourceVisible).map(toExportModel);
143
+ }