@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,560 @@
1
+ /**
2
+ * Quota-aware subagent model fallback (issue #374).
3
+ *
4
+ * codex-rs spawns children with the agent-role TOML `model` pinned; when that model's
5
+ * provider quota is exhausted the child fails immediately. This module rewrites thread_spawn
6
+ * requests at the proxy choke point to the next healthy model in a configured fallback chain.
7
+ */
8
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
9
+ import { join } from "node:path";
10
+ import { hasOwnProvider } from "../config";
11
+ import { isRateLimitOrQuotaFailureMessage } from "../lib/errors";
12
+ import type { OcxParsedRequest, OcxConfig } from "../types";
13
+ import { slugsEquivalent } from "../providers/slug-codec";
14
+ import { CODEX_HOME, getCodexHome } from "./paths";
15
+ import { CODEX_UNKNOWN_USAGE_SCORE, getAccountQuota } from "./quota";
16
+ import {
17
+ canAcquireCodexQuotaProbeLease,
18
+ codexQuotaScopeForModel,
19
+ computeCodexUsageScore,
20
+ getCodexQuotaHealthSnapshot,
21
+ getEffectiveActiveCodexAccountId,
22
+ getPoolAccountPlan,
23
+ isCodexAccountInCooldown,
24
+ } from "./routing";
25
+ import {
26
+ isCodexAccountUsable,
27
+ type CodexAccountUsabilityOptions,
28
+ } from "./account-usability";
29
+ import { isCodexAccountPaused } from "./account-pause";
30
+ import { slugEquals } from "../providers/slug-codec";
31
+ import { isThreadSpawnRequest } from "../server/effort-policy";
32
+ import { PROVIDER_REGISTRY } from "../providers/registry";
33
+ import {
34
+ CODEX_FORWARD_BASE_URL,
35
+ OPENAI_CODEX_PROVIDER_ID,
36
+ isCanonicalOpenAiForwardProvider,
37
+ } from "../providers/openai-tiers";
38
+ import { routeModel, type RouteResult } from "../router";
39
+ import { sweepExpiredOnWrite } from "../lib/state-store-sweeper";
40
+ import { codexAccountNamespaceForModel } from "./account-namespace-match";
41
+ import {
42
+ getUpstreamHostHealth,
43
+ normalizeUpstreamHostCircuitThreshold,
44
+ upstreamHostHealthKey,
45
+ } from "./upstream-host-health";
46
+ export const DEFAULT_SUBAGENT_MODEL_FALLBACK_POLL_MS = 60_000;
47
+
48
+ const CODEX_FORWARD_ORIGIN = new URL(CODEX_FORWARD_BASE_URL).origin.toLowerCase();
49
+
50
+ type SubagentQuotaPrimeFn = (config: OcxConfig, reason: string) => Promise<void>;
51
+ let subagentQuotaPrimeForTests: SubagentQuotaPrimeFn | null = null;
52
+ let quotaPrimeInFlight: Promise<void> | null = null;
53
+
54
+ type ModelHealth = {
55
+ unavailableUntil: number;
56
+ reason: string;
57
+ };
58
+
59
+ const modelHealth = new Map<string, ModelHealth>();
60
+ const quotaPrimedAt = new Map<string, number>();
61
+ const knownProviderIdSet = new Set(PROVIDER_REGISTRY.map(entry => entry.id.toLowerCase()));
62
+
63
+ function tryRouteFallbackModel(config: OcxConfig, model: string): RouteResult | null {
64
+ try {
65
+ return routeModel(config, model);
66
+ } catch {
67
+ return null;
68
+ }
69
+ }
70
+
71
+ function isPoolCodexRoute(route: RouteResult): boolean {
72
+ return route.codexAccountMode === "pool";
73
+ }
74
+
75
+ function healthKey(model: string, accountId: string | null, poolScoped: boolean): string {
76
+ const scopedAccountId = poolScoped ? accountId : null;
77
+ return `${scopedAccountId ?? "none"}::${model.toLowerCase()}`;
78
+ }
79
+
80
+ function isDisabledFallbackModel(model: string, config: OcxConfig): boolean {
81
+ const disabled = config.disabledModels ?? [];
82
+ if (disabled.length === 0) return false;
83
+ if (!model.includes("/")) {
84
+ return disabled.some(stored => stored === model || slugEquals(stored, "openai", model));
85
+ }
86
+ const slash = model.indexOf("/");
87
+ const provider = model.slice(0, slash);
88
+ const modelId = model.slice(slash + 1);
89
+ if (codexAccountNamespaceForModel(config.codexAccountNamespaces, model)) {
90
+ return disabled.some(stored =>
91
+ stored === model
92
+ || stored === modelId
93
+ || slugEquals(stored, "openai", modelId)
94
+ );
95
+ }
96
+ return disabled.some(stored => stored === model || slugEquals(stored, provider, modelId));
97
+ }
98
+
99
+ function pollIntervalMs(config: OcxConfig): number {
100
+ const configured = config.subagentModelFallbackPollMs;
101
+ if (typeof configured !== "number" || !Number.isFinite(configured) || configured < 1_000) {
102
+ return DEFAULT_SUBAGENT_MODEL_FALLBACK_POLL_MS;
103
+ }
104
+ return configured;
105
+ }
106
+
107
+ function fallbackChainKey(model: string, namespaces: unknown): string {
108
+ const selector = codexAccountNamespaceForModel(namespaces, model);
109
+ if (!selector) return JSON.stringify(["model", model.toLowerCase()]);
110
+ const slash = model.indexOf("/");
111
+ // Selector keys are exact-case account boundaries. Keep that segment distinct while
112
+ // retaining legacy case-insensitive de-duplication for the native model suffix.
113
+ return JSON.stringify(["account", selector, model.slice(slash + 1).toLowerCase()]);
114
+ }
115
+
116
+ function normalizedChain(primary: string, config: OcxConfig, extra: readonly string[] = []): string[] {
117
+ const chain: string[] = [];
118
+ const seen = new Set<string>();
119
+ const push = (model: string | undefined) => {
120
+ if (!model || model.trim() === "") return;
121
+ const trimmed = model.trim();
122
+ const key = fallbackChainKey(trimmed, config.codexAccountNamespaces);
123
+ if (seen.has(key)) return;
124
+ seen.add(key);
125
+ chain.push(trimmed);
126
+ };
127
+ push(primary);
128
+ for (const model of extra) push(model);
129
+ for (const model of config.subagentModelFallback ?? []) push(model);
130
+ return chain;
131
+ }
132
+
133
+ export function buildSubagentModelChain(
134
+ primary: string,
135
+ config: OcxConfig,
136
+ extraFallback: readonly string[] = [],
137
+ ): string[] {
138
+ return normalizedChain(primary, config, extraFallback);
139
+ }
140
+
141
+ function quotaThreshold(config: OcxConfig): number {
142
+ const threshold = config.autoSwitchThreshold ?? 80;
143
+ return threshold > 0 ? threshold : Number.POSITIVE_INFINITY;
144
+ }
145
+
146
+ /**
147
+ * The account routing would actually use, not just the persisted operator
148
+ * selection: round-robin, fill-first, failover, and priority preemption all
149
+ * move the cursor in memory only, so reading the raw field would check quota
150
+ * against an account this request is not going to touch.
151
+ */
152
+ function activeCodexAccountId(config: OcxConfig): string | null {
153
+ return getEffectiveActiveCodexAccountId(config) ?? null;
154
+ }
155
+
156
+ /**
157
+ * Resolve the account id used for pool-scoped quota/health checks.
158
+ * Explicit `null` means the pre-fallback preview found no usable account — do not
159
+ * substitute `activeCodexAccountId` (that active id may itself be unusable).
160
+ */
161
+ function resolvePoolFallbackAccountId(
162
+ config: OcxConfig,
163
+ accountId?: string | null,
164
+ ): string | null {
165
+ if (typeof accountId === "string") return accountId;
166
+ if (accountId === null) return null;
167
+ return activeCodexAccountId(config);
168
+ }
169
+
170
+ function resolveRouteFallbackAccountId(
171
+ route: RouteResult | null,
172
+ config: OcxConfig,
173
+ accountId?: string | null,
174
+ ): string | null {
175
+ return route?.codexAccountId ?? resolvePoolFallbackAccountId(config, accountId);
176
+ }
177
+
178
+ function isRoutableFallbackModel(model: string, config: OcxConfig): boolean {
179
+ const slash = model.indexOf("/");
180
+ if (slash > 0) {
181
+ if (codexAccountNamespaceForModel(config.codexAccountNamespaces, model)) return true;
182
+ const providerName = model.slice(0, slash);
183
+ if (!hasOwnProvider(config.providers, providerName)) {
184
+ // Allow well-known "vendor/model" ids (e.g. anthropic/claude-*) to flow as
185
+ // raw model ids through the default provider, but reject stale/typo prefixes.
186
+ return knownProviderIdSet.has(providerName.toLowerCase());
187
+ }
188
+ const provider = config.providers[providerName];
189
+ if (provider?.disabled === true) return false;
190
+ }
191
+ return true;
192
+ }
193
+
194
+ export function isNativeModelQuotaExhausted(
195
+ model: string,
196
+ config: OcxConfig,
197
+ accountId?: string | null,
198
+ now = Date.now(),
199
+ ): boolean {
200
+ const route = tryRouteFallbackModel(config, model);
201
+ if (!route || !isPoolCodexRoute(route)) return false;
202
+ const resolvedAccountId = resolveRouteFallbackAccountId(route, config, accountId);
203
+ if (!resolvedAccountId) return false;
204
+ const quota = getAccountQuota(resolvedAccountId);
205
+ const usage = computeCodexUsageScore(quota, getPoolAccountPlan(config, resolvedAccountId));
206
+ if (usage >= CODEX_UNKNOWN_USAGE_SCORE) return false;
207
+ return usage >= quotaThreshold(config);
208
+ }
209
+
210
+ export function isModelHealthBlocked(
211
+ model: string,
212
+ config: OcxConfig,
213
+ accountId?: string | null,
214
+ now = Date.now(),
215
+ ): boolean {
216
+ const route = tryRouteFallbackModel(config, model);
217
+ const poolScoped = !!route && isPoolCodexRoute(route);
218
+ const health = modelHealth.get(
219
+ healthKey(
220
+ model,
221
+ resolveRouteFallbackAccountId(route, config, accountId),
222
+ poolScoped,
223
+ ),
224
+ );
225
+ return !!health && health.unavailableUntil > now;
226
+ }
227
+
228
+ export function isSubagentModelUnavailable(
229
+ model: string,
230
+ config: OcxConfig,
231
+ accountId?: string | null,
232
+ now = Date.now(),
233
+ accountUsabilityOptions?: CodexAccountUsabilityOptions,
234
+ ): boolean {
235
+ if (isDisabledFallbackModel(model, config)) return true;
236
+ if (!isRoutableFallbackModel(model, config)) return true;
237
+ const route = tryRouteFallbackModel(config, model);
238
+ if (!route || route.provider.disabled === true) return true;
239
+ if (isModelHealthBlocked(model, config, accountId, now)) return true;
240
+ if (!isPoolCodexRoute(route)) return false;
241
+
242
+ // Pool candidates need a usable account. Derive requirement from the resolved
243
+ // route (canonical openai defaults to pool even when codexAccountMode is omitted).
244
+ const resolvedAccountId = resolveRouteFallbackAccountId(route, config, accountId);
245
+ if (!resolvedAccountId) return true;
246
+ if (isCodexAccountPaused(config, resolvedAccountId)) return true;
247
+ if (!isCodexAccountUsable(config, resolvedAccountId, accountUsabilityOptions)) return true;
248
+ if (route.codexAccountId !== undefined) {
249
+ // An account-qualified route is pinned and cannot consume Pool's recovery-probe
250
+ // escape hatch. Honor both account-wide and model-scoped cooldowns so fallback
251
+ // advances instead of selecting a candidate that exact auth will reject.
252
+ const quotaScope = codexQuotaScopeForModel(route.modelId);
253
+ if (getCodexQuotaHealthSnapshot(resolvedAccountId, quotaScope, now) !== null) return true;
254
+ } else if (
255
+ isCodexAccountInCooldown(resolvedAccountId, now)
256
+ && !canAcquireCodexQuotaProbeLease(resolvedAccountId, now)
257
+ ) {
258
+ return true;
259
+ }
260
+ return isNativeModelQuotaExhausted(model, config, accountId, now);
261
+ }
262
+
263
+ export function selectAvailableSubagentModel(
264
+ primary: string,
265
+ config: OcxConfig,
266
+ extraFallback: readonly string[] = [],
267
+ accountId?: string | null,
268
+ now = Date.now(),
269
+ nativeFallbackOnly = false,
270
+ accountUsabilityOptions?: CodexAccountUsabilityOptions,
271
+ ): { model: string; rewritten: boolean; skipped: string[] } {
272
+ const chain = normalizedChain(primary, config, extraFallback);
273
+ const skipped: string[] = [];
274
+ for (const candidate of chain) {
275
+ if (nativeFallbackOnly) {
276
+ const route = tryRouteFallbackModel(config, candidate);
277
+ if (!route || !isCanonicalOpenAiForwardProvider(route.provider)) {
278
+ skipped.push(candidate);
279
+ continue;
280
+ }
281
+ }
282
+ if (isSubagentModelUnavailable(candidate, config, accountId, now, accountUsabilityOptions)) {
283
+ skipped.push(candidate);
284
+ continue;
285
+ }
286
+ return { model: candidate, rewritten: !slugsEquivalent(candidate, primary), skipped };
287
+ }
288
+ return { model: primary, rewritten: false, skipped };
289
+ }
290
+
291
+ export function noteSubagentModelFailure(
292
+ model: string,
293
+ message: string,
294
+ config: OcxConfig,
295
+ accountId?: string | null,
296
+ now = Date.now(),
297
+ ttlMs?: number,
298
+ ): void {
299
+ const interval = ttlMs ?? DEFAULT_SUBAGENT_MODEL_FALLBACK_POLL_MS;
300
+ if (!isRateLimitOrQuotaFailureMessage(message)) return;
301
+ const route = tryRouteFallbackModel(config, model);
302
+ const poolScoped = !!route && isPoolCodexRoute(route);
303
+ modelHealth.set(
304
+ healthKey(
305
+ model,
306
+ resolveRouteFallbackAccountId(route, config, accountId),
307
+ poolScoped,
308
+ ),
309
+ {
310
+ unavailableUntil: now + interval,
311
+ reason: "quota_exhausted",
312
+ },
313
+ );
314
+ sweepExpiredOnWrite(now);
315
+ }
316
+
317
+ export function sweepExpiredSubagentModelHealth(now = Date.now()): number {
318
+ let removed = 0;
319
+ for (const [key, health] of modelHealth) {
320
+ if (health.unavailableUntil > now) continue;
321
+ modelHealth.delete(key);
322
+ removed += 1;
323
+ }
324
+ return removed;
325
+ }
326
+
327
+ export function resetSubagentModelFallbackStateForTests(): void {
328
+ modelHealth.clear();
329
+ quotaPrimedAt.clear();
330
+ quotaPrimeInFlight = null;
331
+ subagentQuotaPrimeForTests = null;
332
+ }
333
+
334
+ /** Test-only: inject the quota prime implementation used by {@link maybePrimeSubagentQuota}. */
335
+ export function setSubagentQuotaPrimeForTests(fn: SubagentQuotaPrimeFn | null): void {
336
+ subagentQuotaPrimeForTests = fn;
337
+ }
338
+
339
+ /** Test-only: inspect shared prime TTL / in-flight state. */
340
+ export function getSubagentQuotaPrimeStateForTests(): {
341
+ primedAt: number;
342
+ inFlight: boolean;
343
+ } {
344
+ return {
345
+ primedAt: quotaPrimedAt.get("global") ?? 0,
346
+ inFlight: quotaPrimeInFlight !== null,
347
+ };
348
+ }
349
+
350
+ function rewriteParsedModel(parsed: OcxParsedRequest, model: string): void {
351
+ parsed.modelId = model;
352
+ if (parsed._rawBody && typeof parsed._rawBody === "object") {
353
+ (parsed._rawBody as { model?: string }).model = model;
354
+ }
355
+ }
356
+
357
+ const TOML_MODEL = /^(model)\s*=\s*("(?:\\.|[^"\\])*")\s*$/;
358
+
359
+ function parseTomlQuotedString(raw: string): string {
360
+ const trimmed = raw.trim();
361
+ if ((trimmed.startsWith("\"") && trimmed.endsWith("\""))
362
+ || (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
363
+ return trimmed.slice(1, -1).replace(/\\"/g, "\"");
364
+ }
365
+ return trimmed;
366
+ }
367
+
368
+ function readAgentModel(filePath: string): string | null {
369
+ try {
370
+ const content = readFileSync(filePath, "utf8");
371
+ for (const line of content.split(/\r?\n/)) {
372
+ const match = line.match(TOML_MODEL);
373
+ if (!match) continue;
374
+ const model = parseTomlQuotedString(match[2] ?? "");
375
+ return model.trim() === "" ? null : model.trim();
376
+ }
377
+ } catch {
378
+ return null;
379
+ }
380
+ return null;
381
+ }
382
+
383
+ export function readCodexAgentModel(role: string, codexHome = CODEX_HOME): string | null {
384
+ const file = join(codexHome, "agents", `${role}.toml`);
385
+ if (!existsSync(file)) return null;
386
+ return readAgentModel(file);
387
+ }
388
+
389
+ export function resolveAgentModelFallbackForPrimary(
390
+ primary: string,
391
+ codexHome = CODEX_HOME,
392
+ namespaces?: unknown,
393
+ ): string[] {
394
+ const merged: string[] = [];
395
+ const seen = new Set<string>();
396
+ const push = (model: string | null | undefined) => {
397
+ if (!model || model.trim() === "") return;
398
+ const trimmed = model.trim();
399
+ // Match global-chain de-dupe: keep account-selector prefixes case-sensitive when
400
+ // those selectors are configured, while ordinary provider/model ids stay case-insensitive.
401
+ const key = fallbackChainKey(trimmed, namespaces);
402
+ if (seen.has(key)) return;
403
+ seen.add(key);
404
+ merged.push(trimmed);
405
+ };
406
+ for (const role of listCodexAgentRoles(codexHome)) {
407
+ const model = readCodexAgentModel(role, codexHome);
408
+ if (!model || !slugsEquivalent(model, primary)) continue;
409
+ for (const fallback of readCodexAgentModelFallback(role, codexHome)) push(fallback);
410
+ }
411
+ return merged;
412
+ }
413
+
414
+ function subagentQuotaPrimeBlockedByHostCircuit(config: OcxConfig): boolean {
415
+ if (normalizeUpstreamHostCircuitThreshold(config.upstreamHostCircuitThreshold) === 0) return false;
416
+ const key = upstreamHostHealthKey(OPENAI_CODEX_PROVIDER_ID, CODEX_FORWARD_ORIGIN);
417
+ return getUpstreamHostHealth(key)?.cooldownUntil !== undefined;
418
+ }
419
+
420
+ /**
421
+ * Best-effort quota refresh before subagent model selection.
422
+ * Concurrent callers share one in-flight promise. The success TTL is updated only
423
+ * after a successful refresh so failures remain retryable. Errors are swallowed so
424
+ * spawn routing can continue. When the canonical ChatGPT origin is circuit-blocked,
425
+ * cached quota is used instead of sending credential-bearing usage probes to the
426
+ * same origin before the request's final host admission check.
427
+ */
428
+ export function maybePrimeSubagentQuota(
429
+ config: OcxConfig,
430
+ now = Date.now(),
431
+ options: { nativeMainReadsForbidden?: boolean } = {},
432
+ ): Promise<void> {
433
+ if (options.nativeMainReadsForbidden) return Promise.resolve();
434
+ if (subagentQuotaPrimeBlockedByHostCircuit(config)) return Promise.resolve();
435
+ if (quotaPrimeInFlight) return quotaPrimeInFlight;
436
+ if (!shouldPrimeSubagentQuota(config, now)) return Promise.resolve();
437
+
438
+ quotaPrimeInFlight = (async () => {
439
+ try {
440
+ // Re-check after claiming single-flight ownership so a circuit opened by
441
+ // a concurrent request cannot race us into a fresh usage-probe pass.
442
+ if (subagentQuotaPrimeBlockedByHostCircuit(config)) return;
443
+ if (subagentQuotaPrimeForTests) {
444
+ await subagentQuotaPrimeForTests(config, "subagent-spawn");
445
+ } else {
446
+ const { primeCodexPoolQuotas } = await import("./auth-api");
447
+ await primeCodexPoolQuotas(config, "subagent-spawn");
448
+ }
449
+ quotaPrimedAt.set("global", Date.now());
450
+ } catch {
451
+ // Owning boundary: do not fail the spawn path when priming is unavailable.
452
+ // Leave quotaPrimedAt untouched so a later spawn can retry.
453
+ } finally {
454
+ quotaPrimeInFlight = null;
455
+ }
456
+ })();
457
+ return quotaPrimeInFlight;
458
+ }
459
+
460
+ export function recordSubagentQuotaFailureForThreadSpawn(
461
+ headers: Headers,
462
+ model: string,
463
+ message: string | number,
464
+ config: OcxConfig,
465
+ accountId?: string | null,
466
+ now = Date.now(),
467
+ ): void {
468
+ if (!isThreadSpawnRequest(headers)) return;
469
+ noteSubagentModelFailure(model, String(message), config, accountId, now, pollIntervalMs(config));
470
+ }
471
+
472
+ export function applySubagentModelFallback(
473
+ parsed: OcxParsedRequest,
474
+ headers: Headers,
475
+ config: OcxConfig,
476
+ accountId?: string | null,
477
+ now = Date.now(),
478
+ nativeFallbackOnly = false,
479
+ accountUsabilityOptions?: CodexAccountUsabilityOptions,
480
+ ): { from?: string; to?: string; skipped?: string[] } | null {
481
+ if (!isThreadSpawnRequest(headers)) return null;
482
+ const roleFallback = resolveAgentModelFallbackForPrimary(
483
+ parsed.modelId,
484
+ getCodexHome(),
485
+ config.codexAccountNamespaces,
486
+ );
487
+ const globalFallback = config.subagentModelFallback ?? [];
488
+ if (globalFallback.length === 0 && roleFallback.length === 0) return null;
489
+ const selection = selectAvailableSubagentModel(
490
+ parsed.modelId,
491
+ config,
492
+ roleFallback,
493
+ accountId,
494
+ now,
495
+ nativeFallbackOnly,
496
+ accountUsabilityOptions,
497
+ );
498
+ if (!selection.rewritten) return selection.skipped.length > 0
499
+ ? { from: parsed.modelId, to: parsed.modelId, skipped: selection.skipped }
500
+ : null;
501
+ const from = parsed.modelId;
502
+ rewriteParsedModel(parsed, selection.model);
503
+ return { from, to: selection.model, skipped: selection.skipped };
504
+ }
505
+
506
+ export function subagentFallbackGuidanceText(config: OcxConfig): string {
507
+ const chain = config.subagentModelFallback ?? [];
508
+ if (chain.length === 0) return "";
509
+ const quoted = chain.map(model => `"${model}"`).join(", ");
510
+ return ` Subagent model fallback chain (priority order): ${quoted}. When the primary model is quota-exhausted, Remodex rewrites thread_spawn requests to the next available model automatically.`;
511
+ }
512
+
513
+ const TOML_STRING_ARRAY = /^(model_fallback)\s*=\s*\[(.*)\]\s*$/s;
514
+
515
+ function parseTomlStringArray(raw: string): string[] {
516
+ const matches = [...raw.matchAll(/"((?:\\.|[^"\\])*)"/g)];
517
+ return matches.map(match => match[1]!.replace(/\\"/g, "\""));
518
+ }
519
+
520
+ function parseTomlModelFallback(content: string): string[] | null {
521
+ const match = content.match(/^\s*model_fallback\s*=\s*\[(.*?)\]\s*$/ms);
522
+ if (!match) return null;
523
+ return parseTomlStringArray(match[1] ?? "");
524
+ }
525
+
526
+ export function readAgentModelFallback(filePath: string): string[] | null {
527
+ try {
528
+ const content = readFileSync(filePath, "utf8");
529
+ const multiline = parseTomlModelFallback(content);
530
+ if (multiline) return multiline;
531
+ for (const line of content.split(/\r?\n/)) {
532
+ const match = line.match(TOML_STRING_ARRAY);
533
+ if (!match) continue;
534
+ return parseTomlStringArray(match[2] ?? "");
535
+ }
536
+ } catch {
537
+ return null;
538
+ }
539
+ return null;
540
+ }
541
+
542
+ export function readCodexAgentModelFallback(role: string, codexHome = CODEX_HOME): string[] {
543
+ const file = join(codexHome, "agents", `${role}.toml`);
544
+ if (!existsSync(file)) return [];
545
+ return readAgentModelFallback(file) ?? [];
546
+ }
547
+
548
+ export function listCodexAgentRoles(codexHome = CODEX_HOME): string[] {
549
+ const dir = join(codexHome, "agents");
550
+ if (!existsSync(dir)) return [];
551
+ return readdirSync(dir)
552
+ .filter(name => name.endsWith(".toml"))
553
+ .map(name => name.slice(0, -".toml".length));
554
+ }
555
+
556
+ /** True when a new quota prime should start (no success within the poll interval). */
557
+ export function shouldPrimeSubagentQuota(config: OcxConfig, now = Date.now()): boolean {
558
+ const last = quotaPrimedAt.get("global") ?? 0;
559
+ return now - last >= pollIntervalMs(config);
560
+ }