@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,143 @@
1
+ /**
2
+ * prompt-lock.ts — advisory cross-process lock for prompt-layer mutations.
3
+ *
4
+ * An in-process mutex only serialises browser tabs behind one service. A CLI
5
+ * invocation, a second service, or a stale process can all reach the same
6
+ * files, so the lock lives on disk.
7
+ *
8
+ * STALE TAKEOVER IS A RENAME, NOT AN UNLINK. Naive breaking is racy: A judges
9
+ * the lock stale, B removes it and acquires its own, then A unlinks *B's live
10
+ * lock* and both proceed. Unlinking a path you did not verify is the bug. Here
11
+ * the contender renames the observed stale lock to a token-quarantined name —
12
+ * an atomic operation exactly one contender can win — and only the winner
13
+ * creates the real lock.
14
+ *
15
+ * RELEASE ONLY DELETES A LOCK WHOSE TOKEN IS STILL OURS. A mismatch means we
16
+ * were superseded, and deleting it would hand the critical section to two
17
+ * writers at once.
18
+ *
19
+ * This does NOT cover Codex, which knows nothing about our lock. That residual
20
+ * is handled by the per-target byte checks in the write path, and the rename
21
+ * window itself is documented as irreducible from user space.
22
+ */
23
+ import { existsSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
24
+ import { randomBytes } from "node:crypto";
25
+
26
+ const FILE_MODE = 0o600;
27
+
28
+ /** A lock younger than this is respected even if its pid looks gone. */
29
+ export const STALE_AFTER_MS = 10_000;
30
+
31
+ export interface LockRecord {
32
+ token: string;
33
+ pid: number;
34
+ acquiredAt: number;
35
+ }
36
+
37
+ export interface LockHandle {
38
+ path: string;
39
+ token: string;
40
+ }
41
+
42
+ export type AcquireResult =
43
+ | { ok: true; handle: LockHandle }
44
+ | { ok: false; error: "locked" };
45
+
46
+ export interface LockDeps {
47
+ /** Injectable so tests can simulate a live or dead owner. */
48
+ isProcessAlive: (pid: number) => boolean;
49
+ now: () => number;
50
+ }
51
+
52
+ const defaultDeps: LockDeps = {
53
+ isProcessAlive: pid => {
54
+ try {
55
+ process.kill(pid, 0);
56
+ return true;
57
+ } catch (error) {
58
+ // EPERM means it exists but belongs to another user.
59
+ return (error as NodeJS.ErrnoException).code === "EPERM";
60
+ }
61
+ },
62
+ now: () => Date.now(),
63
+ };
64
+
65
+ function readRecord(path: string): LockRecord | null {
66
+ try {
67
+ const parsed = JSON.parse(readFileSync(path, "utf8")) as LockRecord;
68
+ if (typeof parsed?.token !== "string" || typeof parsed?.pid !== "number") return null;
69
+ return parsed;
70
+ } catch {
71
+ return null;
72
+ }
73
+ }
74
+
75
+ /** True when the holder is gone AND the lock is older than the grace window. */
76
+ function isStale(record: LockRecord | null, deps: LockDeps): boolean {
77
+ if (record === null) return true; // unparseable: treat as debris
78
+ if (deps.isProcessAlive(record.pid)) return false;
79
+ return deps.now() - record.acquiredAt > STALE_AFTER_MS;
80
+ }
81
+
82
+ /**
83
+ * One attempt. The caller decides whether to retry — a loser must go back to
84
+ * re-reading the lock rather than assuming its quarantine still applies.
85
+ */
86
+ export function tryAcquire(path: string, deps: LockDeps = defaultDeps): AcquireResult {
87
+ const token = randomBytes(8).toString("hex");
88
+ const record: LockRecord = { token, pid: process.pid, acquiredAt: deps.now() };
89
+ const body = JSON.stringify(record);
90
+
91
+ try {
92
+ writeFileSync(path, body, { encoding: "utf8", mode: FILE_MODE, flag: "wx" });
93
+ return { ok: true, handle: { path, token } };
94
+ } catch (error) {
95
+ if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error;
96
+ }
97
+
98
+ if (!isStale(readRecord(path), deps)) return { ok: false, error: "locked" };
99
+
100
+ // Quarantine by rename: atomic, and exactly one contender wins it.
101
+ const quarantine = `${path}.stale-${token}`;
102
+ try {
103
+ renameSync(path, quarantine);
104
+ } catch {
105
+ // Someone else won the rename, or the owner released between our checks.
106
+ // Either way we do NOT touch the path — retry from the top.
107
+ return { ok: false, error: "locked" };
108
+ }
109
+
110
+ try {
111
+ writeFileSync(path, body, { encoding: "utf8", mode: FILE_MODE, flag: "wx" });
112
+ } catch (error) {
113
+ // A successor acquired the real lock between our rename and this create.
114
+ // Its lock is live and is not ours to remove.
115
+ try { unlinkSync(quarantine); } catch { /* debris */ }
116
+ if ((error as NodeJS.ErrnoException).code === "EEXIST") return { ok: false, error: "locked" };
117
+ throw error;
118
+ }
119
+
120
+ try { unlinkSync(quarantine); } catch { /* debris */ }
121
+ return { ok: true, handle: { path, token } };
122
+ }
123
+
124
+ /**
125
+ * Release. Deletes nothing unless the on-disk token is still ours; returns false
126
+ * when we were superseded, which the caller surfaces as `write_superseded`.
127
+ */
128
+ export function release(handle: LockHandle): boolean {
129
+ const record = readRecord(handle.path);
130
+ if (record === null || record.token !== handle.token) return false;
131
+ try {
132
+ unlinkSync(handle.path);
133
+ return true;
134
+ } catch {
135
+ return false;
136
+ }
137
+ }
138
+
139
+ /** True when the on-disk lock is still the one this handle acquired. */
140
+ export function stillHeld(handle: LockHandle): boolean {
141
+ if (!existsSync(handle.path)) return false;
142
+ return readRecord(handle.path)?.token === handle.token;
143
+ }
@@ -0,0 +1,242 @@
1
+ import {
2
+ mutatePersistedConfig,
3
+ providerBaseUrlConfigError,
4
+ type PersistedConfigMutationOutcome,
5
+ } from "../config";
6
+ import { existsSync, readFileSync } from "node:fs";
7
+ import { providerDestinationConfigError } from "../lib/destination-policy";
8
+ import type { OcxConfig, OcxProviderConfig } from "../types";
9
+ import { rootTomlString } from "./injected-marker";
10
+ import { CODEX_CONFIG_PATH } from "./paths";
11
+
12
+ /** The one external Desktop provider Remodex can adopt without guessing its wire contract. */
13
+ export const CODEX_LB_PROVIDER_ID = "codex-lb";
14
+
15
+ const ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
16
+
17
+ export interface CodexLbDescriptor {
18
+ id: typeof CODEX_LB_PROVIDER_ID;
19
+ baseUrl: string;
20
+ envKey: string;
21
+ wireApi: "responses";
22
+ requiresOpenAiAuth: boolean;
23
+ }
24
+
25
+ export type CodexLbInspection =
26
+ | { kind: "inactive" }
27
+ | { kind: "adoptable"; descriptor: CodexLbDescriptor }
28
+ | { kind: "refused"; reason: string };
29
+
30
+ export type CodexLbAdoptionResult =
31
+ | { kind: "inactive" }
32
+ | {
33
+ kind: "adopted" | "already-managed";
34
+ descriptor: CodexLbDescriptor;
35
+ provider: OcxProviderConfig;
36
+ }
37
+ | { kind: "refused" | "unavailable"; reason: string };
38
+
39
+ type PersistMutation = typeof mutatePersistedConfig;
40
+
41
+ function plainRecord(value: unknown): Record<string, unknown> | null {
42
+ return value !== null && typeof value === "object" && !Array.isArray(value)
43
+ ? value as Record<string, unknown>
44
+ : null;
45
+ }
46
+
47
+ function normalizedHostname(value: string): string {
48
+ return value.trim().toLowerCase().replace(/^\[|\]$/g, "").replace(/\.$/, "");
49
+ }
50
+
51
+ function endpointPort(url: URL): number {
52
+ if (url.port) return Number(url.port);
53
+ return url.protocol === "https:" ? 443 : 80;
54
+ }
55
+
56
+ /**
57
+ * Reject a literal route back into this Remodex process. Destination policy separately rejects
58
+ * localhost, loopback, private, metadata, and link-local addresses; this comparison also covers a
59
+ * concrete hostname used as the Remodex bind target.
60
+ */
61
+ function isConfiguredRemodexEndpoint(baseUrl: string, config: Readonly<OcxConfig>): boolean {
62
+ let url: URL;
63
+ try {
64
+ url = new URL(baseUrl);
65
+ } catch {
66
+ return false;
67
+ }
68
+ const targetHost = normalizedHostname(url.hostname);
69
+ const configuredHost = normalizedHostname(config.hostname ?? "127.0.0.1");
70
+ if (!targetHost || targetHost !== configuredHost) return false;
71
+ const ports = new Set([config.port ?? 10100]);
72
+ if (config.unauthenticatedLoopbackListener?.enabled) {
73
+ ports.add(config.unauthenticatedLoopbackListener.port);
74
+ }
75
+ return ports.has(endpointPort(url));
76
+ }
77
+
78
+ /**
79
+ * Parse only the active `codex-lb` provider from Codex Desktop's TOML. The resolved environment
80
+ * value is deliberately never read: Remodex stores and routes with the reference `${NAME}`.
81
+ */
82
+ export function inspectActiveCodexLbProvider(
83
+ content: string,
84
+ config: Readonly<OcxConfig>,
85
+ ): CodexLbInspection {
86
+ if (rootTomlString(content, "model_provider") !== CODEX_LB_PROVIDER_ID) {
87
+ return { kind: "inactive" };
88
+ }
89
+
90
+ let document: Record<string, unknown>;
91
+ try {
92
+ document = plainRecord(Bun.TOML.parse(content.replace(/^\uFEFF/, ""))) ?? {};
93
+ } catch {
94
+ return { kind: "refused", reason: "Codex config.toml is not valid TOML" };
95
+ }
96
+ const providers = plainRecord(document.model_providers);
97
+ const table = plainRecord(providers?.[CODEX_LB_PROVIDER_ID]);
98
+ if (!table) {
99
+ return { kind: "refused", reason: "active codex-lb has no [model_providers.codex-lb] table" };
100
+ }
101
+
102
+ const baseUrl = typeof table.base_url === "string" ? table.base_url.trim().replace(/\/+$/, "") : "";
103
+ const wireApi = typeof table.wire_api === "string" ? table.wire_api.trim().toLowerCase() : "";
104
+ const envKey = typeof table.env_key === "string" ? table.env_key.trim() : "";
105
+ if (!baseUrl) return { kind: "refused", reason: "codex-lb base_url is missing" };
106
+ if (wireApi !== "responses") {
107
+ return { kind: "refused", reason: "codex-lb can be adopted only when wire_api is responses" };
108
+ }
109
+ if (!ENV_NAME.test(envKey)) {
110
+ return { kind: "refused", reason: "codex-lb env_key is missing or invalid" };
111
+ }
112
+ const baseUrlError = providerBaseUrlConfigError(baseUrl);
113
+ if (baseUrlError) return { kind: "refused", reason: `codex-lb ${baseUrlError}` };
114
+ const destinationError = providerDestinationConfigError(CODEX_LB_PROVIDER_ID, { baseUrl });
115
+ if (destinationError) return { kind: "refused", reason: `codex-lb ${destinationError}` };
116
+ if (isConfiguredRemodexEndpoint(baseUrl, config)) {
117
+ return { kind: "refused", reason: "codex-lb base_url points back to this Remodex server" };
118
+ }
119
+ if (table.requires_openai_auth !== undefined && typeof table.requires_openai_auth !== "boolean") {
120
+ return { kind: "refused", reason: "codex-lb requires_openai_auth must be a boolean" };
121
+ }
122
+
123
+ return {
124
+ kind: "adoptable",
125
+ descriptor: {
126
+ id: CODEX_LB_PROVIDER_ID,
127
+ baseUrl,
128
+ envKey,
129
+ wireApi: "responses",
130
+ requiresOpenAiAuth: table.requires_openai_auth === true,
131
+ },
132
+ };
133
+ }
134
+
135
+ export function providerForCodexLb(descriptor: CodexLbDescriptor): OcxProviderConfig {
136
+ return {
137
+ adapter: "openai-responses",
138
+ baseUrl: descriptor.baseUrl,
139
+ responsesPath: "/responses",
140
+ authMode: "key",
141
+ apiKey: `\${${descriptor.envKey}}`,
142
+ liveModels: true,
143
+ note: "Imported automatically from Codex Desktop's codex-lb provider.",
144
+ };
145
+ }
146
+
147
+ function sameEndpoint(left: string, right: string): boolean {
148
+ return left.trim().replace(/\/+$/, "") === right.trim().replace(/\/+$/, "");
149
+ }
150
+
151
+ function mergeManagedProvider(
152
+ existing: OcxProviderConfig | undefined,
153
+ expected: OcxProviderConfig,
154
+ ): { kind: "changed" | "unchanged"; provider: OcxProviderConfig } | { kind: "conflict" } {
155
+ if (!existing) return { kind: "changed", provider: expected };
156
+ if (
157
+ existing.adapter !== "openai-responses"
158
+ || !sameEndpoint(existing.baseUrl, expected.baseUrl)
159
+ || (existing.responsesPath !== undefined && existing.responsesPath !== "/responses")
160
+ || (existing.authMode !== undefined && existing.authMode !== "key")
161
+ ) {
162
+ return { kind: "conflict" };
163
+ }
164
+
165
+ // A user-managed credential already attached to the exact same upstream wins. Otherwise retain
166
+ // the environment reference imported from Codex Desktop. Never resolve or copy the env value.
167
+ const provider: OcxProviderConfig = {
168
+ ...existing,
169
+ responsesPath: existing.responsesPath ?? "/responses",
170
+ authMode: existing.authMode ?? "key",
171
+ apiKey: existing.apiKey?.trim() ? existing.apiKey : expected.apiKey,
172
+ };
173
+ const changed = JSON.stringify(provider) !== JSON.stringify(existing);
174
+ return { kind: changed ? "changed" : "unchanged", provider };
175
+ }
176
+
177
+ /**
178
+ * Import an active Codex Desktop codex-lb descriptor into the protected Remodex config. The
179
+ * mutation rebases under the shared config lock, so a concurrent provider edit is never silently
180
+ * overwritten. The caller's live config is updated only after a committed/unchanged result.
181
+ */
182
+ export function adoptActiveCodexLbProvider(
183
+ content: string,
184
+ config: OcxConfig,
185
+ mutate: PersistMutation = mutatePersistedConfig,
186
+ ): CodexLbAdoptionResult {
187
+ const inspection = inspectActiveCodexLbProvider(content, config);
188
+ if (inspection.kind !== "adoptable") return inspection;
189
+ const expected = providerForCodexLb(inspection.descriptor);
190
+ type MutationValue =
191
+ | { kind: "managed"; provider: OcxProviderConfig; changed: boolean }
192
+ | { kind: "conflict" };
193
+ const outcome: PersistedConfigMutationOutcome<MutationValue> = mutate<MutationValue>((persisted) => {
194
+ const merged = mergeManagedProvider(persisted.providers[CODEX_LB_PROVIDER_ID], expected);
195
+ if (merged.kind === "conflict") {
196
+ return { changed: false, value: { kind: "conflict" } };
197
+ }
198
+ if (merged.kind === "changed") persisted.providers[CODEX_LB_PROVIDER_ID] = merged.provider;
199
+ return {
200
+ changed: merged.kind === "changed",
201
+ value: { kind: "managed", provider: merged.provider, changed: merged.kind === "changed" },
202
+ };
203
+ });
204
+
205
+ if (outcome.status === "unavailable") {
206
+ return { kind: "unavailable", reason: `Remodex provider config is ${outcome.reason}` };
207
+ }
208
+ if (outcome.value.kind === "conflict") {
209
+ return {
210
+ kind: "refused",
211
+ reason: "Remodex already has an incompatible codex-lb provider; it was not overwritten",
212
+ };
213
+ }
214
+ config.providers[CODEX_LB_PROVIDER_ID] = structuredClone(outcome.value.provider);
215
+ return {
216
+ kind: outcome.status === "committed" && outcome.value.changed ? "adopted" : "already-managed",
217
+ descriptor: inspection.descriptor,
218
+ provider: outcome.value.provider,
219
+ };
220
+ }
221
+
222
+ export function adoptCurrentCodexLbProvider(
223
+ config: OcxConfig,
224
+ mutate: PersistMutation = mutatePersistedConfig,
225
+ ): CodexLbAdoptionResult {
226
+ if (!existsSync(CODEX_CONFIG_PATH)) return { kind: "inactive" };
227
+ return adoptActiveCodexLbProvider(readFileSync(CODEX_CONFIG_PATH, "utf8"), config, mutate);
228
+ }
229
+
230
+ /** Read-only admission predicate: true only when the active Desktop descriptor is already managed. */
231
+ export function isManagedActiveCodexLbProvider(
232
+ content: string,
233
+ config: Readonly<OcxConfig>,
234
+ ): boolean {
235
+ const inspection = inspectActiveCodexLbProvider(content, config);
236
+ if (inspection.kind !== "adoptable") return false;
237
+ const merged = mergeManagedProvider(
238
+ config.providers[CODEX_LB_PROVIDER_ID],
239
+ providerForCodexLb(inspection.descriptor),
240
+ );
241
+ return merged.kind !== "conflict" && merged.kind === "unchanged";
242
+ }
@@ -0,0 +1,224 @@
1
+ import { readBoundedResponseBody } from "../lib/bounded-body";
2
+
3
+ const RESET_ELIGIBLE_CODE_VALUES = [
4
+ "usage_limit_exceeded",
5
+ "insufficient_quota",
6
+ ] as const;
7
+
8
+ export type CodexResetEligibleExhaustionCode =
9
+ (typeof RESET_ELIGIBLE_CODE_VALUES)[number];
10
+
11
+ export type CodexPreStreamRejectionKind =
12
+ | "reset-eligible-exhaustion"
13
+ | "generic-rate-limit"
14
+ | "unverified-billing-or-quota"
15
+ | "transient-server-error"
16
+ | "authentication-error"
17
+ | "permission-error"
18
+ | "other";
19
+
20
+ export interface CodexPreStreamRejection {
21
+ kind: CodexPreStreamRejectionKind;
22
+ status: number;
23
+ alternateRetryEligible: boolean;
24
+ resetCreditEligible: boolean;
25
+ semanticCode?: CodexResetEligibleExhaustionCode;
26
+ }
27
+
28
+ const RESET_ELIGIBLE_CODES: ReadonlySet<string> = new Set(RESET_ELIGIBLE_CODE_VALUES);
29
+
30
+ const TRANSIENT_SERVER_STATUSES = new Set([500, 502, 503, 504, 520, 521, 522]);
31
+ const JSON_NUMBER_PATTERN = /-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/y;
32
+
33
+ function rejection(
34
+ status: number,
35
+ kind: CodexPreStreamRejectionKind,
36
+ options: {
37
+ alternateRetryEligible?: boolean;
38
+ semanticCode?: CodexResetEligibleExhaustionCode;
39
+ } = {},
40
+ ): CodexPreStreamRejection {
41
+ return {
42
+ kind,
43
+ status,
44
+ alternateRetryEligible: options.alternateRetryEligible === true,
45
+ resetCreditEligible: options.semanticCode !== undefined,
46
+ ...(options.semanticCode ? { semanticCode: options.semanticCode } : {}),
47
+ };
48
+ }
49
+
50
+ function hasOwnField(container: Record<string, unknown>, field: string): boolean {
51
+ return Object.prototype.hasOwnProperty.call(container, field);
52
+ }
53
+
54
+ type JsonScanResult = {
55
+ next: number;
56
+ duplicate: boolean;
57
+ };
58
+
59
+ function skipJsonWhitespace(text: string, index: number): number {
60
+ while (index < text.length && /[\t\n\r ]/.test(text[index] ?? "")) index += 1;
61
+ return index;
62
+ }
63
+
64
+ function scanJsonStringEnd(text: string, index: number): number {
65
+ if (text[index] !== '"') throw new SyntaxError("expected JSON string");
66
+ for (let cursor = index + 1; cursor < text.length; cursor += 1) {
67
+ const char = text[cursor];
68
+ if (char === '"') return cursor + 1;
69
+ if (char === "\\") cursor += 1;
70
+ }
71
+ throw new SyntaxError("unterminated JSON string");
72
+ }
73
+
74
+ function scanJsonValue(text: string, index: number): JsonScanResult {
75
+ const start = skipJsonWhitespace(text, index);
76
+ if (text[start] === "{") return scanJsonObject(text, start);
77
+ if (text[start] === "[") return scanJsonArray(text, start);
78
+ if (text[start] === '"') return { next: scanJsonStringEnd(text, start), duplicate: false };
79
+
80
+ for (const literal of ["true", "false", "null"]) {
81
+ if (text.startsWith(literal, start)) {
82
+ return { next: start + literal.length, duplicate: false };
83
+ }
84
+ }
85
+ JSON_NUMBER_PATTERN.lastIndex = start;
86
+ const number = JSON_NUMBER_PATTERN.exec(text);
87
+ if (!number) throw new SyntaxError("expected JSON value");
88
+ return { next: start + number[0].length, duplicate: false };
89
+ }
90
+
91
+ function scanJsonObject(text: string, index: number): JsonScanResult {
92
+ const keys = new Set<string>();
93
+ let duplicate = false;
94
+ let cursor = skipJsonWhitespace(text, index + 1);
95
+ if (text[cursor] === "}") return { next: cursor + 1, duplicate: false };
96
+
97
+ while (cursor < text.length) {
98
+ const keyEnd = scanJsonStringEnd(text, cursor);
99
+ const key = JSON.parse(text.slice(cursor, keyEnd)) as unknown;
100
+ if (typeof key !== "string") throw new SyntaxError("invalid JSON object key");
101
+ if (keys.has(key)) duplicate = true;
102
+ keys.add(key);
103
+
104
+ cursor = skipJsonWhitespace(text, keyEnd);
105
+ if (text[cursor] !== ":") throw new SyntaxError("expected JSON object colon");
106
+ const value = scanJsonValue(text, cursor + 1);
107
+ duplicate ||= value.duplicate;
108
+ cursor = skipJsonWhitespace(text, value.next);
109
+ if (text[cursor] === "}") return { next: cursor + 1, duplicate };
110
+ if (text[cursor] !== ",") throw new SyntaxError("expected JSON object separator");
111
+ cursor = skipJsonWhitespace(text, cursor + 1);
112
+ }
113
+ throw new SyntaxError("unterminated JSON object");
114
+ }
115
+
116
+ function scanJsonArray(text: string, index: number): JsonScanResult {
117
+ let duplicate = false;
118
+ let cursor = skipJsonWhitespace(text, index + 1);
119
+ if (text[cursor] === "]") return { next: cursor + 1, duplicate: false };
120
+
121
+ while (cursor < text.length) {
122
+ const value = scanJsonValue(text, cursor);
123
+ duplicate ||= value.duplicate;
124
+ cursor = skipJsonWhitespace(text, value.next);
125
+ if (text[cursor] === "]") return { next: cursor + 1, duplicate };
126
+ if (text[cursor] !== ",") throw new SyntaxError("expected JSON array separator");
127
+ cursor = skipJsonWhitespace(text, cursor + 1);
128
+ }
129
+ throw new SyntaxError("unterminated JSON array");
130
+ }
131
+
132
+ function isUnsafeJsonDocument(text: string): boolean {
133
+ try {
134
+ const result = scanJsonValue(text, 0);
135
+ return result.duplicate || skipJsonWhitespace(text, result.next) !== text.length;
136
+ } catch {
137
+ // Scanner disagreement is untrusted input, just like JSON.parse failure.
138
+ return true;
139
+ }
140
+ }
141
+
142
+ function exactResetEligibleCode(
143
+ container: Record<string, unknown>,
144
+ ): CodexResetEligibleExhaustionCode | undefined {
145
+ const hasCode = hasOwnField(container, "code");
146
+ const hasType = hasOwnField(container, "type");
147
+ if (!hasCode && !hasType) return undefined;
148
+
149
+ const code = hasCode ? container.code : undefined;
150
+ const type = hasType ? container.type : undefined;
151
+ if ((hasCode && typeof code !== "string") || (hasType && typeof type !== "string")) {
152
+ return undefined;
153
+ }
154
+ if (hasCode && hasType && code !== type) return undefined;
155
+
156
+ const value = hasCode ? code : type;
157
+ if (typeof value !== "string") return undefined;
158
+ return RESET_ELIGIBLE_CODES.has(value as CodexResetEligibleExhaustionCode)
159
+ ? value as CodexResetEligibleExhaustionCode
160
+ : undefined;
161
+ }
162
+
163
+ function structuredResetEligibleCode(payload: unknown): CodexResetEligibleExhaustionCode | undefined {
164
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) return undefined;
165
+ const root = payload as Record<string, unknown>;
166
+ const hasRootDiscriminator = hasOwnField(root, "code") || hasOwnField(root, "type");
167
+
168
+ if (!hasOwnField(root, "error")) return exactResetEligibleCode(root);
169
+ if (hasRootDiscriminator) return undefined;
170
+
171
+ const nested = root.error;
172
+ if (!nested || typeof nested !== "object" || Array.isArray(nested)) return undefined;
173
+ return exactResetEligibleCode(nested as Record<string, unknown>);
174
+ }
175
+
176
+ async function resetEligibleCodeFromResponse(
177
+ response: Response,
178
+ signal?: AbortSignal,
179
+ ): Promise<CodexResetEligibleExhaustionCode | undefined> {
180
+ try {
181
+ const body = await readBoundedResponseBody(response.clone(), { signal, fatalUtf8: true });
182
+ if (!body.displaySafe || body.truncated || !body.text.trim()) return undefined;
183
+ const payload = JSON.parse(body.text) as unknown;
184
+ // JSON.parse silently keeps the last duplicate key, making contradictory
185
+ // payloads order-dependent. Reject any duplicate at any object depth.
186
+ if (isUnsafeJsonDocument(body.text)) return undefined;
187
+ return structuredResetEligibleCode(payload);
188
+ } catch {
189
+ // Classification must fail closed. A malformed, oversized, consumed, or
190
+ // cancelled body cannot authorize an irreversible reset-credit operation.
191
+ return undefined;
192
+ }
193
+ }
194
+
195
+ /**
196
+ * Classify an upstream Codex rejection before any response event is exposed.
197
+ *
198
+ * Only an exact structured exhaustion code on HTTP 429/402 is reset-eligible.
199
+ * Status alone and message text are intentionally insufficient. The broad
200
+ * alternate-account retry remains eligible for 429/402 to preserve #584.
201
+ */
202
+ export async function classifyCodexPreStreamRejection(
203
+ response: Response,
204
+ options: { signal?: AbortSignal } = {},
205
+ ): Promise<CodexPreStreamRejection> {
206
+ const status = response.status;
207
+ if (status === 401) return rejection(status, "authentication-error");
208
+ if (status === 403) return rejection(status, "permission-error");
209
+ if (TRANSIENT_SERVER_STATUSES.has(status)) return rejection(status, "transient-server-error");
210
+ if (status !== 429 && status !== 402) return rejection(status, "other");
211
+
212
+ const semanticCode = await resetEligibleCodeFromResponse(response, options.signal);
213
+ if (semanticCode) {
214
+ return rejection(status, "reset-eligible-exhaustion", {
215
+ alternateRetryEligible: true,
216
+ semanticCode,
217
+ });
218
+ }
219
+ return rejection(
220
+ status,
221
+ status === 429 ? "generic-rate-limit" : "unverified-billing-or-quota",
222
+ { alternateRetryEligible: true },
223
+ );
224
+ }