@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,390 @@
1
+ import { networkInterfaces, hostname as readHostname } from "node:os";
2
+ import {
3
+ createAndroidRemoteStore,
4
+ type AndroidRemoteState,
5
+ type AndroidRemoteStore,
6
+ } from "../../android-remote/store";
7
+ import {
8
+ androidRemoteGatewayUrls,
9
+ type AndroidRemoteGatewayController,
10
+ type AndroidRemoteGatewayLifecycleStatus,
11
+ } from "../../android-remote/gateway";
12
+ import type {
13
+ AndroidRemoteCloudflareFailure,
14
+ AndroidRemoteCloudflareState,
15
+ } from "../../android-remote/cloudflare-tunnel";
16
+ import {
17
+ CloudflareProvisioningError,
18
+ ManagedAndroidRemoteCloudflareProvisioner,
19
+ type AndroidRemoteCloudflareProvisioner,
20
+ } from "../../android-remote/cloudflare-provisioning";
21
+ import { jsonResponse } from "../auth-cors";
22
+ import { readManagementJsonBodyOr } from "./body";
23
+ import type { ManagementContext } from "./context";
24
+ import { isPlainRecord } from "./shared";
25
+
26
+ export type AndroidRemoteClientDTO = {
27
+ id: string;
28
+ label: string;
29
+ deviceType: "mobile";
30
+ os: string;
31
+ address?: string;
32
+ scopes: string[];
33
+ createdAt: string;
34
+ lastSeenAt?: string;
35
+ online: boolean;
36
+ };
37
+
38
+ export type AndroidRemoteStatusDTO = {
39
+ version: 1;
40
+ controlEnabled: boolean;
41
+ keepAwake: boolean;
42
+ pairingAvailable: boolean;
43
+ gateway: {
44
+ status: AndroidRemoteGatewayLifecycleStatus;
45
+ backgroundServer: "current-process";
46
+ port: number;
47
+ error?: string;
48
+ };
49
+ reachableAddresses: string[];
50
+ tunnel: {
51
+ configuration: {
52
+ mode: "quick" | "named";
53
+ namedHostname?: string;
54
+ hasNamedTunnelToken: boolean;
55
+ };
56
+ runtime: {
57
+ mode: "quick" | "named";
58
+ status: AndroidRemoteCloudflareState["status"];
59
+ publicUrl: string | null;
60
+ error: AndroidRemoteCloudflareFailure | null;
61
+ };
62
+ };
63
+ desktop: {
64
+ id: "desktop";
65
+ label: string;
66
+ platform: NodeJS.Platform;
67
+ address: string;
68
+ online: true;
69
+ };
70
+ clients: AndroidRemoteClientDTO[];
71
+ };
72
+
73
+ export type AndroidRemotePairingDTO = {
74
+ version: 1;
75
+ id: string;
76
+ expiresAt: string;
77
+ qrPayload: string;
78
+ };
79
+
80
+ export function androidRemoteReachableAddresses(
81
+ port = 10105,
82
+ interfaces = networkInterfaces(),
83
+ ): string[] {
84
+ return androidRemoteGatewayUrls(port, interfaces);
85
+ }
86
+
87
+ function routeController(ctx: ManagementContext): AndroidRemoteGatewayController | undefined {
88
+ return ctx.deps.androidRemoteController;
89
+ }
90
+
91
+ function routeCloudflareProvisioner(ctx: ManagementContext): AndroidRemoteCloudflareProvisioner {
92
+ return ctx.deps.androidRemoteCloudflareProvisioner ?? new ManagedAndroidRemoteCloudflareProvisioner();
93
+ }
94
+
95
+ function cloudflareProvisioningError(error: unknown): { status: number; body: { error: string; code: string } } {
96
+ if (error instanceof CloudflareProvisioningError) {
97
+ const status = error.code === "invalid_input" || error.code === "hostname_outside_zone"
98
+ ? 400
99
+ : error.code === "authorization_failed" || error.code === "permission_missing"
100
+ ? 403
101
+ : error.code === "zone_not_found"
102
+ ? 404
103
+ : error.code === "rate_limited"
104
+ ? 429
105
+ : error.code === "cloudflare_unavailable" || error.code === "invalid_response"
106
+ ? 502
107
+ : 409;
108
+ return { status, body: { error: error.message, code: error.code } };
109
+ }
110
+ if (error instanceof TypeError) {
111
+ return { status: 400, body: { error: error.message, code: "invalid_input" } };
112
+ }
113
+ return {
114
+ status: 502,
115
+ body: { error: "Cloudflare could not complete the request. Try again.", code: "cloudflare_unavailable" },
116
+ };
117
+ }
118
+
119
+ async function statusDTO(
120
+ ctx: ManagementContext,
121
+ state: AndroidRemoteState,
122
+ ): Promise<AndroidRemoteStatusDTO> {
123
+ const controller = routeController(ctx);
124
+ const gateway = controller?.status() ?? {
125
+ status: "stopped" as const,
126
+ port: 10105,
127
+ backgroundServer: "current-process" as const,
128
+ };
129
+ const reachableAddresses = controller?.localUrls()
130
+ ?? androidRemoteReachableAddresses(gateway.port);
131
+ const pairingAddresses = controller?.pairingUrls() ?? [];
132
+ const onlineClientIds = controller?.onlineClientIds() ?? new Set<string>();
133
+ const configuration = controller
134
+ ? await controller.cloudflareConfiguration()
135
+ : {
136
+ mode: state.settings.tunnelMode,
137
+ ...(state.settings.namedTunnelHostname
138
+ ? { namedHostname: state.settings.namedTunnelHostname }
139
+ : {}),
140
+ hasNamedTunnelToken: false,
141
+ };
142
+ const tunnelState = controller?.cloudflareState() ?? {
143
+ mode: state.settings.tunnelMode,
144
+ status: "stopped" as const,
145
+ publicUrl: null,
146
+ error: null,
147
+ };
148
+ return {
149
+ version: 1,
150
+ controlEnabled: state.settings.controlEnabled,
151
+ keepAwake: state.settings.keepAwake,
152
+ pairingAvailable: gateway.status === "ready"
153
+ && (pairingAddresses.length > 0 || tunnelState.status === "ready"),
154
+ gateway,
155
+ reachableAddresses,
156
+ tunnel: {
157
+ configuration,
158
+ runtime: tunnelState,
159
+ },
160
+ desktop: {
161
+ id: "desktop",
162
+ label: readHostname() || "Remodex Desktop",
163
+ platform: process.platform,
164
+ address: reachableAddresses[0] ?? `http://127.0.0.1:${gateway.port}`,
165
+ online: true,
166
+ },
167
+ clients: state.clients.map(client => ({
168
+ id: client.id,
169
+ label: client.label,
170
+ deviceType: client.deviceType,
171
+ os: client.os,
172
+ scopes: [...client.scopes],
173
+ createdAt: client.createdAt,
174
+ online: onlineClientIds.has(client.id),
175
+ ...(client.address ? { address: client.address } : {}),
176
+ ...(client.lastSeenAt ? { lastSeenAt: client.lastSeenAt } : {}),
177
+ })),
178
+ };
179
+ }
180
+
181
+ function routeStore(ctx: ManagementContext): AndroidRemoteStore {
182
+ return ctx.deps.androidRemoteStore ?? createAndroidRemoteStore();
183
+ }
184
+
185
+ export async function handleAndroidRemoteRoutes(ctx: ManagementContext): Promise<Response | null> {
186
+ const { req, url, config } = ctx;
187
+ if (url.pathname === "/api/android-remote" && req.method === "GET") {
188
+ return jsonResponse(await statusDTO(ctx, routeStore(ctx).read()), 200, req, config);
189
+ }
190
+
191
+ if (url.pathname === "/api/android-remote/pairing" && req.method === "POST") {
192
+ const controller = routeController(ctx);
193
+ if (!controller) {
194
+ return jsonResponse({ error: "Android Remote gateway is unavailable" }, 503, req, config);
195
+ }
196
+ try {
197
+ const invitation = controller.createPairingInvitation(readHostname() || "Remodex Desktop");
198
+ const response: AndroidRemotePairingDTO = {
199
+ version: 1,
200
+ id: invitation.id,
201
+ expiresAt: invitation.expiresAt,
202
+ qrPayload: invitation.qrPayload,
203
+ };
204
+ return jsonResponse(response, 201, req, config);
205
+ } catch {
206
+ return jsonResponse({ error: "Android Remote gateway is not ready" }, 409, req, config);
207
+ }
208
+ }
209
+
210
+ if (url.pathname === "/api/android-remote/settings" && req.method === "PUT") {
211
+ const body = await readManagementJsonBodyOr(req, null);
212
+ if (!isPlainRecord(body)) {
213
+ return jsonResponse({ error: "settings body must be an object" }, 400, req, config);
214
+ }
215
+ const keys = Object.keys(body);
216
+ if (keys.length === 0 || keys.some(key => key !== "controlEnabled" && key !== "keepAwake")) {
217
+ return jsonResponse({ error: "provide controlEnabled and/or keepAwake only" }, 400, req, config);
218
+ }
219
+ if (("controlEnabled" in body && typeof body.controlEnabled !== "boolean")
220
+ || ("keepAwake" in body && typeof body.keepAwake !== "boolean")) {
221
+ return jsonResponse({ error: "Android Remote settings must be boolean" }, 400, req, config);
222
+ }
223
+ const next = routeStore(ctx).updateSettings({
224
+ ...(typeof body.controlEnabled === "boolean" ? { controlEnabled: body.controlEnabled } : {}),
225
+ ...(typeof body.keepAwake === "boolean" ? { keepAwake: body.keepAwake } : {}),
226
+ });
227
+ try {
228
+ await routeController(ctx)?.applySettings(next.settings);
229
+ } catch {
230
+ // The controller records a safe error state. Return that honest state so the
231
+ // dashboard can explain that the saved switch is on but the gateway failed.
232
+ }
233
+ return jsonResponse(await statusDTO(ctx, next), 200, req, config);
234
+ }
235
+
236
+ if (url.pathname === "/api/android-remote/cloudflare/discover" && req.method === "POST") {
237
+ const body = await readManagementJsonBodyOr(req, null);
238
+ if (!isPlainRecord(body)
239
+ || Object.keys(body).some(key => key !== "apiToken")
240
+ || typeof body.apiToken !== "string") {
241
+ return jsonResponse({ error: "provide a Cloudflare API token only", code: "invalid_input" }, 400, req, config);
242
+ }
243
+ try {
244
+ const discovery = await routeCloudflareProvisioner(ctx).discover(body.apiToken);
245
+ return jsonResponse(discovery, 200, req, config);
246
+ } catch (error) {
247
+ const failure = cloudflareProvisioningError(error);
248
+ return jsonResponse(failure.body, failure.status, req, config);
249
+ }
250
+ }
251
+
252
+ if (url.pathname === "/api/android-remote/cloudflare/provision" && req.method === "POST") {
253
+ // This call creates a tunnel and DNS record in the user's Cloudflare account.
254
+ // A raw admin token can be read by local agents, so only the browser session
255
+ // minted by the dashboard is accepted as evidence of the user's Connect click.
256
+ if (ctx.principal !== "gui-session") {
257
+ return jsonResponse({
258
+ error: "Open the dashboard and choose Connect to authorize changes to Cloudflare.",
259
+ code: "dashboard_session_required",
260
+ }, 403, req, config);
261
+ }
262
+ const controller = routeController(ctx);
263
+ if (!controller) {
264
+ return jsonResponse({ error: "Android Remote gateway is unavailable" }, 503, req, config);
265
+ }
266
+ const body = await readManagementJsonBodyOr(req, null);
267
+ const allowed = new Set(["apiToken", "accountId", "zoneId", "hostname"]);
268
+ if (!isPlainRecord(body)
269
+ || Object.keys(body).some(key => !allowed.has(key))
270
+ || typeof body.apiToken !== "string"
271
+ || typeof body.accountId !== "string"
272
+ || typeof body.zoneId !== "string"
273
+ || typeof body.hostname !== "string") {
274
+ return jsonResponse({ error: "provide the selected Cloudflare account, domain, and hostname", code: "invalid_input" }, 400, req, config);
275
+ }
276
+ try {
277
+ const provisioned = await routeCloudflareProvisioner(ctx).provision({
278
+ apiToken: body.apiToken,
279
+ accountId: body.accountId,
280
+ zoneId: body.zoneId,
281
+ hostname: body.hostname,
282
+ });
283
+ routeStore(ctx).updateSettings({ controlEnabled: true });
284
+ await controller.configureCloudflareTunnel({
285
+ mode: "named",
286
+ namedHostname: provisioned.hostname,
287
+ token: provisioned.connectorToken,
288
+ });
289
+ return jsonResponse(await statusDTO(ctx, routeStore(ctx).read()), 200, req, config);
290
+ } catch (error) {
291
+ const failure = cloudflareProvisioningError(error);
292
+ return jsonResponse(failure.body, failure.status, req, config);
293
+ }
294
+ }
295
+
296
+ if (url.pathname === "/api/android-remote/tunnel" && req.method === "PUT") {
297
+ const controller = routeController(ctx);
298
+ if (!controller) {
299
+ return jsonResponse({ error: "Android Remote gateway is unavailable" }, 503, req, config);
300
+ }
301
+ const body = await readManagementJsonBodyOr(req, null);
302
+ if (!isPlainRecord(body)) {
303
+ return jsonResponse({ error: "tunnel body must be an object" }, 400, req, config);
304
+ }
305
+ if (body.mode !== "quick" && body.mode !== "named") {
306
+ return jsonResponse({ error: "tunnel mode must be quick or named" }, 400, req, config);
307
+ }
308
+ const allowed = body.mode === "quick"
309
+ ? new Set(["mode"])
310
+ : new Set(["mode", "hostname", "token"]);
311
+ if (Object.keys(body).some(key => !allowed.has(key))) {
312
+ return jsonResponse({ error: "tunnel body contains unsupported fields" }, 400, req, config);
313
+ }
314
+ if (body.mode === "named" && typeof body.hostname !== "string") {
315
+ return jsonResponse({ error: "Named Tunnel requires a hostname" }, 400, req, config);
316
+ }
317
+ if ("token" in body && typeof body.token !== "string") {
318
+ return jsonResponse({ error: "connector token must be a string" }, 400, req, config);
319
+ }
320
+ try {
321
+ await controller.configureCloudflareTunnel({
322
+ mode: body.mode,
323
+ ...(typeof body.hostname === "string" ? { namedHostname: body.hostname } : {}),
324
+ ...(typeof body.token === "string" ? { token: body.token } : {}),
325
+ });
326
+ return jsonResponse(await statusDTO(ctx, routeStore(ctx).read()), 200, req, config);
327
+ } catch (error) {
328
+ const message = error instanceof Error ? error.message : "Tunnel configuration failed";
329
+ return jsonResponse({ error: message }, error instanceof TypeError ? 400 : 409, req, config);
330
+ }
331
+ }
332
+
333
+ if (url.pathname === "/api/android-remote/tunnel/retry" && req.method === "POST") {
334
+ const controller = routeController(ctx);
335
+ if (!controller) {
336
+ return jsonResponse({ error: "Android Remote gateway is unavailable" }, 503, req, config);
337
+ }
338
+ try {
339
+ await controller.retryCloudflareTunnel();
340
+ return jsonResponse(await statusDTO(ctx, routeStore(ctx).read()), 200, req, config);
341
+ } catch (error) {
342
+ return jsonResponse({ error: error instanceof Error ? error.message : "Tunnel retry failed" }, 409, req, config);
343
+ }
344
+ }
345
+
346
+ if (url.pathname === "/api/android-remote/tunnel/token" && req.method === "DELETE") {
347
+ const controller = routeController(ctx);
348
+ if (!controller) {
349
+ return jsonResponse({ error: "Android Remote gateway is unavailable" }, 503, req, config);
350
+ }
351
+ try {
352
+ await controller.removeCloudflareTunnelToken();
353
+ return jsonResponse(await statusDTO(ctx, routeStore(ctx).read()), 200, req, config);
354
+ } catch {
355
+ return jsonResponse({ error: "Could not remove the connector token" }, 409, req, config);
356
+ }
357
+ }
358
+
359
+ if (url.pathname === "/api/android-remote/tunnel/domain" && req.method === "DELETE") {
360
+ const controller = routeController(ctx);
361
+ if (!controller) {
362
+ return jsonResponse({ error: "Android Remote gateway is unavailable" }, 503, req, config);
363
+ }
364
+ try {
365
+ await controller.disconnectCloudflareNamedTunnel();
366
+ return jsonResponse(await statusDTO(ctx, routeStore(ctx).read()), 200, req, config);
367
+ } catch {
368
+ return jsonResponse({ error: "Could not disconnect the named domain" }, 409, req, config);
369
+ }
370
+ }
371
+
372
+ const revokeMatch = url.pathname.match(/^\/api\/android-remote\/clients\/([^/]+)$/);
373
+ if (revokeMatch && req.method === "DELETE") {
374
+ let id: string;
375
+ try {
376
+ id = decodeURIComponent(revokeMatch[1]);
377
+ } catch {
378
+ return jsonResponse({ error: "invalid client id encoding" }, 400, req, config);
379
+ }
380
+ if (!/^[a-zA-Z0-9._-]{1,128}$/.test(id)) {
381
+ return jsonResponse({ error: "invalid client id" }, 400, req, config);
382
+ }
383
+ const result = routeStore(ctx).revokeClient(id);
384
+ if (!result.removed) return jsonResponse({ error: "client not found" }, 404, req, config);
385
+ routeController(ctx)?.revokeClient(id);
386
+ return jsonResponse(await statusDTO(ctx, result.state), 200, req, config);
387
+ }
388
+
389
+ return null;
390
+ }
@@ -0,0 +1,141 @@
1
+ import type { OcxConfig } from "../../types";
2
+ import { probeHostname } from "../proxy-liveness";
3
+
4
+ export interface ApiAccessEndpoints {
5
+ baseUrl: string;
6
+ responsesEndpoint: string;
7
+ chatCompletionsEndpoint: string;
8
+ messagesEndpoint: string;
9
+ modelsEndpoint: string;
10
+ claudeCodeEnabled: boolean;
11
+ /** Back-compat alias for older GUI clients. */
12
+ endpoint: string;
13
+ }
14
+
15
+ export type BuildApiAccessEndpointsOptions = {
16
+ /** Full request URL (preferred when the bind host is a wildcard). */
17
+ requestUrl?: string | URL | null;
18
+ /** Raw `Host` header from the inbound request. */
19
+ requestHost?: string | null;
20
+ /** Raw `Origin` header from the inbound request. */
21
+ requestOrigin?: string | null;
22
+ };
23
+
24
+ function isWildcardBindHost(hostname: string | undefined): boolean {
25
+ const trimmed = (hostname ?? "").trim();
26
+ return !trimmed || trimmed === "0.0.0.0" || trimmed === "::" || trimmed === "[::]";
27
+ }
28
+
29
+ /** Bracket bare IPv6 literals for URL authority composition. */
30
+ export function formatAuthorityHost(hostname: string): string {
31
+ const trimmed = hostname.trim();
32
+ if (!trimmed) return "127.0.0.1";
33
+ if (trimmed.startsWith("[") && trimmed.endsWith("]")) return trimmed;
34
+ return trimmed.includes(":") ? `[${trimmed}]` : trimmed;
35
+ }
36
+
37
+ function hostnameFromAuthority(authority: string): string | null {
38
+ const raw = authority.trim();
39
+ if (!raw) return null;
40
+ if (raw.startsWith("[")) {
41
+ const end = raw.indexOf("]");
42
+ if (end > 0) return raw.slice(1, end) || null;
43
+ return null;
44
+ }
45
+ // Host headers for IPv6 are bracketed; treat multi-colon values as unusable.
46
+ if (raw.includes(":") && raw.split(":").length > 2) return null;
47
+ const colon = raw.lastIndexOf(":");
48
+ if (colon > 0 && /^\d+$/.test(raw.slice(colon + 1))) {
49
+ return raw.slice(0, colon) || null;
50
+ }
51
+ return raw;
52
+ }
53
+
54
+ function originBaseUrl(raw: string): string | null {
55
+ try {
56
+ const url = new URL(raw);
57
+ if (!url.hostname || isWildcardBindHost(url.hostname)) return null;
58
+ return `${url.protocol}//${url.host}/v1`;
59
+ } catch {
60
+ return null;
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Prefer the client's request host/origin when the proxy is bound to a wildcard.
66
+ * Falls back to loopback only when no usable request context is available.
67
+ */
68
+ export function resolveApiAccessBaseUrl(
69
+ config: Pick<OcxConfig, "hostname" | "port">,
70
+ opts: BuildApiAccessEndpointsOptions = {},
71
+ ): string {
72
+ const port = config.port ?? 10100;
73
+
74
+ if (!isWildcardBindHost(config.hostname)) {
75
+ return `http://${probeHostname(config.hostname)}:${port}/v1`;
76
+ }
77
+
78
+ const fromOrigin = opts.requestOrigin ? originBaseUrl(opts.requestOrigin) : null;
79
+ if (fromOrigin) return fromOrigin;
80
+
81
+ if (opts.requestUrl) {
82
+ try {
83
+ const url = typeof opts.requestUrl === "string" ? new URL(opts.requestUrl) : opts.requestUrl;
84
+ if (url.hostname && !isWildcardBindHost(url.hostname)) {
85
+ return `${url.protocol}//${url.host}/v1`;
86
+ }
87
+ } catch {
88
+ /* ignore malformed request URL */
89
+ }
90
+ }
91
+
92
+ const hostHeader = opts.requestHost?.trim();
93
+ if (hostHeader) {
94
+ const hostname = hostnameFromAuthority(hostHeader);
95
+ if (hostname && !isWildcardBindHost(hostname)) {
96
+ // Preserve an explicit port from the Host header; otherwise use the bind port.
97
+ const hasPort = hostHeader.startsWith("[")
98
+ ? /\]:\d+$/.test(hostHeader)
99
+ : /:\d+$/.test(hostHeader) && hostHeader.split(":").length === 2;
100
+ const authority = hasPort
101
+ ? (hostHeader.startsWith("[") ? hostHeader : hostHeader)
102
+ : `${formatAuthorityHost(hostname)}:${port}`;
103
+ return `http://${authority}/v1`;
104
+ }
105
+ }
106
+
107
+ return `http://127.0.0.1:${port}/v1`;
108
+ }
109
+
110
+ /** @deprecated Prefer resolveApiAccessBaseUrl; retained for focused host-format tests. */
111
+ export function resolveApiAccessDisplayHost(
112
+ configHostname: string | undefined,
113
+ opts: BuildApiAccessEndpointsOptions = {},
114
+ ): string {
115
+ if (!isWildcardBindHost(configHostname)) {
116
+ return probeHostname(configHostname);
117
+ }
118
+ try {
119
+ return new URL(resolveApiAccessBaseUrl({ hostname: configHostname, port: 10100 }, opts)).hostname
120
+ || "127.0.0.1";
121
+ } catch {
122
+ return "127.0.0.1";
123
+ }
124
+ }
125
+
126
+ export function buildApiAccessEndpoints(
127
+ config: OcxConfig,
128
+ opts: BuildApiAccessEndpointsOptions = {},
129
+ ): ApiAccessEndpoints {
130
+ const baseUrl = resolveApiAccessBaseUrl(config, opts);
131
+ const responsesEndpoint = `${baseUrl}/responses`;
132
+ return {
133
+ baseUrl,
134
+ responsesEndpoint,
135
+ chatCompletionsEndpoint: `${baseUrl}/chat/completions`,
136
+ messagesEndpoint: `${baseUrl}/messages`,
137
+ modelsEndpoint: `${baseUrl}/models`,
138
+ claudeCodeEnabled: config.claudeCode?.enabled !== false,
139
+ endpoint: responsesEndpoint,
140
+ };
141
+ }
@@ -0,0 +1,167 @@
1
+ import {
2
+ currentUsageLogRevision,
3
+ readUsageSnapshotForManagement,
4
+ usageLogRevisionKey,
5
+ type PersistedUsageEntry,
6
+ } from "../../usage/log";
7
+
8
+ /**
9
+ * Per-key usage as the API tab renders it.
10
+ *
11
+ * A discriminated union rather than numbers with a flag beside them: when two
12
+ * config entries share an id there IS no per-key total, and an optional marker
13
+ * next to `requests7d: 7` invites a consumer to render the 7 anyway.
14
+ */
15
+ export type ApiKeyUsage =
16
+ | { ambiguous: true }
17
+ | { ambiguous?: false; requests7d: number; totalRequests: number; lastUsedAt?: string };
18
+
19
+ export interface ApiKeyUsageSnapshot {
20
+ rollup: Map<string, ApiKeyUsage>;
21
+ historyTruncated?: true;
22
+ /**
23
+ * Earliest row carrying a recognized `admissionKind`. A property of the DATA
24
+ * SET, not of a key, so it is singular and lives beside the map: it is what
25
+ * lets the GUI tell "this key was used zero times" from "nothing is
26
+ * attributable yet". Keyed on the kind rather than on `apiKeyId`, because an
27
+ * environment or loopback row is attributed traffic with no configured key.
28
+ */
29
+ attributionSince?: string;
30
+ }
31
+
32
+ const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000;
33
+
34
+ /**
35
+ * A timestamp we can actually do date arithmetic with.
36
+ *
37
+ * `usage.jsonl` is hand-editable and JSON permits numbers outside the Date
38
+ * range: `1e309` survives normalization and then throws `RangeError` from
39
+ * `toISOString()`. Since the caller catches to protect key management, one bad
40
+ * row would have zeroed the rollup for EVERY key — active keys reported as
41
+ * unused is exactly the wrong answer to hand someone deciding what to delete.
42
+ */
43
+ function usableTimestamp(value: unknown): number | null {
44
+ if (typeof value !== "number" || !Number.isFinite(value)) return null;
45
+ return Number.isNaN(new Date(value).getTime()) ? null : value;
46
+ }
47
+
48
+ /**
49
+ * Pure: one pass over an already-read snapshot, so it is unit-testable without
50
+ * touching the filesystem.
51
+ *
52
+ * Rows are bucketed only when `admissionKind === "configured"`. Keying on
53
+ * `apiKeyId` alone would let a hand-edited entry whose id is `loopback` absorb
54
+ * traffic it never admitted.
55
+ */
56
+ export function rollupApiKeyUsage(
57
+ entries: PersistedUsageEntry[],
58
+ configuredIds: string[],
59
+ now: number = Date.now(),
60
+ ): ApiKeyUsageSnapshot {
61
+ const duplicated = new Set<string>();
62
+ const seen = new Set<string>();
63
+ for (const id of configuredIds) {
64
+ if (seen.has(id)) duplicated.add(id);
65
+ seen.add(id);
66
+ }
67
+
68
+ const totals = new Map<string, { requests7d: number; totalRequests: number; lastUsedAt?: string }>();
69
+ let attributionSince: number | undefined;
70
+ const cutoff = now - SEVEN_DAYS_MS;
71
+
72
+ for (const entry of entries) {
73
+ if (!entry.admissionKind) continue;
74
+ const timestamp = usableTimestamp(entry.timestamp);
75
+ if (timestamp !== null && (attributionSince === undefined || timestamp < attributionSince)) {
76
+ attributionSince = timestamp;
77
+ }
78
+ if (entry.admissionKind !== "configured" || !entry.apiKeyId) continue;
79
+
80
+ const bucket = totals.get(entry.apiKeyId) ?? { requests7d: 0, totalRequests: 0 };
81
+ // The request happened even if its clock reading is unusable, so it still
82
+ // counts toward the total; only the time-based fields are skipped.
83
+ bucket.totalRequests += 1;
84
+ if (timestamp !== null) {
85
+ if (timestamp >= cutoff) bucket.requests7d += 1;
86
+ const iso = new Date(timestamp).toISOString();
87
+ if (!bucket.lastUsedAt || iso > bucket.lastUsedAt) bucket.lastUsedAt = iso;
88
+ }
89
+ totals.set(entry.apiKeyId, bucket);
90
+ }
91
+
92
+ const rollup = new Map<string, ApiKeyUsage>();
93
+ for (const id of configuredIds) {
94
+ if (duplicated.has(id)) {
95
+ rollup.set(id, { ambiguous: true });
96
+ continue;
97
+ }
98
+ rollup.set(id, totals.get(id) ?? { requests7d: 0, totalRequests: 0 });
99
+ }
100
+ return {
101
+ rollup,
102
+ ...(attributionSince !== undefined ? { attributionSince: new Date(attributionSince).toISOString() } : {}),
103
+ };
104
+ }
105
+
106
+ /**
107
+ * Rollup cache keyed by the exact usage-log revision, mirroring the /api/usage
108
+ * summary cache. Without it, every key-list read reparses an append-only log
109
+ * that only ever grows — and the GUI fetches this route on mount and after every
110
+ * create/rename/delete. The compact rollup is a handful of counters per key, so
111
+ * caching it costs nothing; a new row changes the revision and invalidates it.
112
+ */
113
+ let rollupCache: { revisionKey: string; expiresAt: number; snapshot: ApiKeyUsageSnapshot } | null = null;
114
+
115
+ /**
116
+ * The rollup is a function of the log AND of the clock: a request ages out of
117
+ * the seven-day window with no write to bump the file revision, so a purely
118
+ * revision-keyed entry would report a stale `requests7d` indefinitely.
119
+ *
120
+ * A minute is the whole rule. Deriving the exact next-transition instant would
121
+ * mean tracking the OLDEST counted request per key, which the compact rollup
122
+ * deliberately does not keep — and a count that can be at most 60s stale is
123
+ * already far tighter than the window it describes.
124
+ */
125
+ const ROLLUP_CACHE_TTL_MS = 60_000;
126
+
127
+ /** Test seam: the cache is module state and would otherwise leak between cases. */
128
+ export function clearApiKeyUsageCacheForTests(): void {
129
+ rollupCache = null;
130
+ }
131
+
132
+ /**
133
+ * Reads the durable usage snapshot the way /api/usage does, then rolls it up.
134
+ *
135
+ * Never throws: an unreadable snapshot yields empty rollups and no
136
+ * `attributionSince`. Key management working matters more than usage numbers
137
+ * being present, and the GUI already treats an absent field as "no data".
138
+ */
139
+ export async function readApiKeyUsageRollup(configuredIds: string[], maxReadBytes?: number): Promise<ApiKeyUsageSnapshot> {
140
+ // JSON rather than a joined string: ids are only validated as non-empty
141
+ // strings, so `["a\0b","c"]` and `["a","b\0c"]` join to the same value and one
142
+ // config could be served the other's cached rollup.
143
+ const idsKey = JSON.stringify([configuredIds, maxReadBytes]);
144
+ const now = Date.now();
145
+ try {
146
+ const observedKey = `${usageLogRevisionKey(currentUsageLogRevision())}|${idsKey}`;
147
+ if (rollupCache?.revisionKey === observedKey && now < rollupCache.expiresAt) {
148
+ return rollupCache.snapshot;
149
+ }
150
+
151
+ const snapshot = await readUsageSnapshotForManagement(maxReadBytes);
152
+ const rolled = {
153
+ ...rollupApiKeyUsage(snapshot.entries, configuredIds, now),
154
+ ...(snapshot.truncatedPrefixBytes > 0 || snapshot.entriesTruncated ? { historyTruncated: true as const } : {}),
155
+ };
156
+ rollupCache = {
157
+ revisionKey: `${usageLogRevisionKey(snapshot.revision)}|${idsKey}`,
158
+ expiresAt: now + ROLLUP_CACHE_TTL_MS,
159
+ snapshot: rolled,
160
+ };
161
+ return rolled;
162
+ } catch {
163
+ const rollup = new Map<string, ApiKeyUsage>();
164
+ for (const id of configuredIds) rollup.set(id, { requests7d: 0, totalRequests: 0 });
165
+ return { rollup };
166
+ }
167
+ }