@iislee/opencodex 2.11.0 → 2.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (733) hide show
  1. package/AGENTS_INSTALL.md +109 -0
  2. package/README.md +117 -22
  3. package/bin/ocx.mjs +245 -88
  4. package/bin/package-main.mjs +1 -1
  5. package/gui/dist/assets/index-Be1sDpWw.css +1 -0
  6. package/gui/dist/assets/index-D4OKgUV4.js +114 -0
  7. package/gui/dist/index.html +2 -2
  8. package/gui/dist/provider-icons/alibaba-color.svg +1 -1
  9. package/gui/dist/provider-icons/antigravity-color.svg +1 -1
  10. package/gui/dist/provider-icons/claude-color.svg +1 -1
  11. package/gui/dist/provider-icons/cline-color.svg +16 -0
  12. package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -1
  13. package/gui/dist/provider-icons/commandcode-color.svg +1 -0
  14. package/gui/dist/provider-icons/copilot-color.svg +1 -1
  15. package/gui/dist/provider-icons/cursor-color.svg +1 -1
  16. package/gui/dist/provider-icons/deepseek-color.svg +1 -1
  17. package/gui/dist/provider-icons/firepass-color.svg +1 -1
  18. package/gui/dist/provider-icons/fireworks-color.svg +1 -1
  19. package/gui/dist/provider-icons/gemini-color.svg +1 -1
  20. package/gui/dist/provider-icons/github-copilot-color.svg +1 -1
  21. package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -1
  22. package/gui/dist/provider-icons/grok.svg +1 -1
  23. package/gui/dist/provider-icons/groq-color.svg +1 -1
  24. package/gui/dist/provider-icons/huggingface-color.svg +1 -1
  25. package/gui/dist/provider-icons/kimi-color.svg +1 -1
  26. package/gui/dist/provider-icons/kiro-color.svg +2 -2
  27. package/gui/dist/provider-icons/lm-studio-color.svg +1 -1
  28. package/gui/dist/provider-icons/mistral-color.svg +1 -1
  29. package/gui/dist/provider-icons/moonshot-color.svg +1 -1
  30. package/gui/dist/provider-icons/nvidia-color.svg +1 -1
  31. package/gui/dist/provider-icons/ollama-color.svg +1 -1
  32. package/gui/dist/provider-icons/openai.svg +1 -1
  33. package/gui/dist/provider-icons/opencode.svg +2 -1
  34. package/gui/dist/provider-icons/openrouter-color.svg +1 -1
  35. package/gui/dist/provider-icons/pi.svg +2 -2
  36. package/gui/dist/provider-icons/qianfan-color.svg +1 -1
  37. package/gui/dist/provider-icons/qwen-portal-color.svg +1 -1
  38. package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -1
  39. package/gui/dist/provider-icons/vllm-color.svg +1 -1
  40. package/gui/dist/provider-icons/xiaomi-color.svg +1 -1
  41. package/package.json +21 -10
  42. package/src/adapters/anthropic-output-schema.ts +137 -0
  43. package/src/adapters/anthropic.ts +475 -62
  44. package/src/adapters/base.ts +82 -7
  45. package/src/adapters/client-fingerprint.ts +18 -12
  46. package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
  47. package/src/adapters/command-code.ts +637 -0
  48. package/src/adapters/cursor/call-id.ts +44 -0
  49. package/src/adapters/cursor/catalog.ts +541 -0
  50. package/src/adapters/cursor/checkpoint-store.ts +308 -0
  51. package/src/adapters/cursor/cursor-errors.ts +144 -6
  52. package/src/adapters/cursor/discovery.ts +122 -14
  53. package/src/adapters/cursor/effort-map.ts +106 -4
  54. package/src/adapters/cursor/envelope-echo.ts +290 -0
  55. package/src/adapters/cursor/framing.ts +39 -0
  56. package/src/adapters/cursor/h2-pool.ts +123 -0
  57. package/src/adapters/cursor/http1-bidi.ts +361 -0
  58. package/src/adapters/cursor/images.ts +704 -0
  59. package/src/adapters/cursor/live-models.ts +180 -59
  60. package/src/adapters/cursor/live-transport.ts +623 -170
  61. package/src/adapters/cursor/message-mapper.ts +4 -1
  62. package/src/adapters/cursor/native-exec-common.ts +23 -2
  63. package/src/adapters/cursor/native-exec-desktop.ts +23 -0
  64. package/src/adapters/cursor/native-exec-fs.ts +10 -7
  65. package/src/adapters/cursor/native-exec-network.ts +1 -1
  66. package/src/adapters/cursor/native-exec-shell.ts +5 -3
  67. package/src/adapters/cursor/native-exec.ts +121 -14
  68. package/src/adapters/cursor/protobuf-events.ts +829 -11
  69. package/src/adapters/cursor/protobuf-request.ts +447 -75
  70. package/src/adapters/cursor/request-builder.ts +271 -35
  71. package/src/adapters/cursor/tool-definitions.ts +252 -12
  72. package/src/adapters/cursor/tool-result-normalize.ts +115 -0
  73. package/src/adapters/cursor/transport.ts +22 -0
  74. package/src/adapters/cursor/types.ts +48 -1
  75. package/src/adapters/cursor.ts +336 -39
  76. package/src/adapters/exec-tool-result-normalize.ts +99 -0
  77. package/src/adapters/google-antigravity-replay.ts +713 -41
  78. package/src/adapters/google-antigravity-wire.ts +48 -10
  79. package/src/adapters/google-errors.ts +44 -12
  80. package/src/adapters/google-http.ts +38 -10
  81. package/src/adapters/google-tool-schema.ts +80 -15
  82. package/src/adapters/google-truncation.ts +11 -0
  83. package/src/adapters/google.ts +742 -86
  84. package/src/adapters/identity.ts +39 -6
  85. package/src/adapters/image.ts +1 -1
  86. package/src/adapters/kiro-constants.ts +27 -0
  87. package/src/adapters/kiro-errors.ts +11 -0
  88. package/src/adapters/kiro-events.ts +19 -1
  89. package/src/adapters/kiro-thinking.ts +18 -2
  90. package/src/adapters/kiro-tools.ts +65 -17
  91. package/src/adapters/kiro.ts +270 -73
  92. package/src/adapters/mimo-free.ts +17 -0
  93. package/src/adapters/openai-chat-url.ts +11 -0
  94. package/src/adapters/openai-chat.ts +1377 -344
  95. package/src/adapters/openai-responses-url.ts +16 -0
  96. package/src/adapters/openai-responses.ts +1164 -64
  97. package/src/adapters/registry.ts +175 -0
  98. package/src/adapters/responses-tool-schema.ts +67 -0
  99. package/src/adapters/run-turn-queue.ts +36 -1
  100. package/src/adapters/tool-call-id.ts +119 -0
  101. package/src/adapters/tool-catalog-nudge.ts +105 -21
  102. package/src/adapters/xai-tool-schema.ts +436 -0
  103. package/src/adapters/xai-web-search.ts +186 -0
  104. package/src/bridge.ts +512 -92
  105. package/src/chat/inbound.ts +57 -20
  106. package/src/chat/outbound.ts +97 -33
  107. package/src/claude/agents-inject.ts +40 -10
  108. package/src/claude/context-windows.ts +37 -14
  109. package/src/claude/desktop-3p.ts +243 -9
  110. package/src/claude/gateway-cache.ts +41 -4
  111. package/src/claude/inbound.ts +72 -3
  112. package/src/claude/model-info.ts +38 -15
  113. package/src/claude/outbound.ts +88 -21
  114. package/src/cli/access.ts +46 -3
  115. package/src/cli/account-api.ts +150 -19
  116. package/src/cli/account-auth.ts +33 -6
  117. package/src/cli/account-catalog-refresh.ts +14 -0
  118. package/src/cli/account-extended.ts +648 -28
  119. package/src/cli/account-main.ts +317 -0
  120. package/src/cli/account.ts +97 -19
  121. package/src/cli/agent.ts +53 -2
  122. package/src/cli/alias.ts +66 -0
  123. package/src/cli/capabilities-command.ts +94 -0
  124. package/src/cli/capabilities.ts +496 -0
  125. package/src/cli/claude-agent-startup-sync.ts +73 -0
  126. package/src/cli/claude-desktop.ts +60 -15
  127. package/src/cli/claude.ts +129 -35
  128. package/src/cli/codex-log-guard-doctor.ts +103 -0
  129. package/src/cli/codex-shim-autorestore.ts +2 -0
  130. package/src/cli/codex-shim-readiness.ts +76 -0
  131. package/src/cli/combo.ts +8 -0
  132. package/src/cli/config-command.ts +74 -10
  133. package/src/cli/dispatch.ts +821 -0
  134. package/src/cli/doctor.ts +440 -56
  135. package/src/cli/ensure-desired-integrations.ts +152 -0
  136. package/src/cli/export-command.ts +46 -20
  137. package/src/cli/help.ts +34 -274
  138. package/src/cli/index.ts +417 -528
  139. package/src/cli/init.ts +5 -17
  140. package/src/cli/inspect.ts +230 -0
  141. package/src/cli/integrations.ts +120 -2
  142. package/src/cli/lab.ts +607 -0
  143. package/src/cli/launcher-context.ts +77 -0
  144. package/src/cli/minimax.ts +497 -0
  145. package/src/cli/models-runtime.ts +130 -2
  146. package/src/cli/models.ts +118 -16
  147. package/src/cli/observe.ts +123 -11
  148. package/src/cli/opencode.ts +4 -2
  149. package/src/cli/provider-runtime.ts +34 -3
  150. package/src/cli/provider.ts +25 -3
  151. package/src/cli/ready.ts +301 -0
  152. package/src/cli/registry.ts +476 -0
  153. package/src/cli/root.ts +86 -0
  154. package/src/cli/route-policy.ts +92 -0
  155. package/src/cli/runtime-api.ts +57 -10
  156. package/src/cli/star-prompt.ts +71 -15
  157. package/src/cli/status.ts +51 -4
  158. package/src/cli/storage.ts +234 -0
  159. package/src/cli/system-command.ts +16 -0
  160. package/src/cli/system-restart-client.ts +146 -0
  161. package/src/cli/tray-proxy.ts +153 -6
  162. package/src/cli/usage-report.ts +184 -0
  163. package/src/cli/v2.ts +137 -18
  164. package/src/cli/version-skew.ts +46 -0
  165. package/src/cli.ts +1 -1
  166. package/src/clients/config-export.ts +1442 -23
  167. package/src/clients/effective-status.ts +30 -2
  168. package/src/clients/probes/cline.ts +81 -0
  169. package/src/codex/account-label.ts +35 -1
  170. package/src/codex/account-lifecycle.ts +130 -13
  171. package/src/codex/account-namespaces.ts +49 -3
  172. package/src/codex/account-pause.ts +2 -1
  173. package/src/codex/account-priority.ts +84 -0
  174. package/src/codex/account-store.ts +29 -2
  175. package/src/codex/account-usability.ts +25 -2
  176. package/src/codex/admission.ts +256 -0
  177. package/src/codex/affinity-debug.ts +162 -0
  178. package/src/codex/app-server-processes.ts +570 -107
  179. package/src/codex/app-server-restart-service.ts +232 -0
  180. package/src/codex/auth-api.ts +893 -246
  181. package/src/codex/auth-collision.ts +5 -3
  182. package/src/codex/auth-context.ts +348 -32
  183. package/src/codex/autostart-health.ts +24 -1
  184. package/src/codex/catalog/account-models.ts +67 -0
  185. package/src/codex/catalog/aggregation.ts +86 -10
  186. package/src/codex/catalog/bundled.ts +331 -33
  187. package/src/codex/catalog/effort.ts +138 -32
  188. package/src/codex/catalog/filesystem-evidence.ts +302 -0
  189. package/src/codex/catalog/kinds.ts +2 -0
  190. package/src/codex/catalog/metadata.ts +555 -45
  191. package/src/codex/catalog/native-models.ts +75 -0
  192. package/src/codex/catalog/parsing.ts +269 -30
  193. package/src/codex/catalog/provider-fetch.ts +1626 -144
  194. package/src/codex/catalog/sync.ts +1576 -187
  195. package/src/codex/catalog-admission.ts +199 -0
  196. package/src/codex/catalog-refresh-status.ts +105 -0
  197. package/src/codex/catalog-write-serialization.ts +242 -0
  198. package/src/codex/catalog.ts +6 -3
  199. package/src/codex/codex-write-lock.ts +386 -0
  200. package/src/codex/convergence-types.ts +614 -0
  201. package/src/codex/convergence.ts +676 -0
  202. package/src/codex/coordinator-doctor.ts +332 -0
  203. package/src/codex/custom-model-catalog-migration.ts +176 -0
  204. package/src/codex/desired-state.ts +230 -0
  205. package/src/codex/desktop-app-restart.ts +355 -0
  206. package/src/codex/features.ts +636 -39
  207. package/src/codex/generation.ts +202 -0
  208. package/src/codex/history-job.ts +436 -0
  209. package/src/codex/history-lock.ts +242 -0
  210. package/src/codex/history-manifest.ts +112 -0
  211. package/src/codex/history-migration-guardian.ts +30 -24
  212. package/src/codex/history-provider.ts +1016 -235
  213. package/src/codex/history-transition.ts +105 -0
  214. package/src/codex/history-worker.ts +223 -0
  215. package/src/codex/inject-coordination.ts +373 -0
  216. package/src/codex/inject.ts +1114 -152
  217. package/src/codex/injected-marker.ts +37 -3
  218. package/src/codex/integration-record.ts +266 -0
  219. package/src/codex/internal/catalog-writer.ts +203 -0
  220. package/src/codex/internal/history-writer.ts +81 -0
  221. package/src/codex/journal.ts +66 -4
  222. package/src/codex/log-guard/inspect.ts +524 -0
  223. package/src/codex/log-guard/lock.ts +150 -0
  224. package/src/codex/log-guard/maintenance.ts +403 -0
  225. package/src/codex/log-guard/path-safety.ts +88 -0
  226. package/src/codex/log-guard/policy.ts +44 -0
  227. package/src/codex/log-guard/processes.ts +205 -0
  228. package/src/codex/log-guard/protection.ts +489 -0
  229. package/src/codex/log-guard/sqlite-errors.ts +9 -0
  230. package/src/codex/main-account-cache.ts +24 -0
  231. package/src/codex/main-account.ts +29 -1
  232. package/src/codex/management-convergence.ts +167 -0
  233. package/src/codex/model-cache.ts +56 -10
  234. package/src/codex/model-entitlements.ts +360 -0
  235. package/src/codex/native-main-admission.ts +47 -0
  236. package/src/codex/native-main-auth-temp.ts +187 -0
  237. package/src/codex/native-main-claim.ts +178 -0
  238. package/src/codex/native-main-lock-file.ts +162 -0
  239. package/src/codex/native-main-owner.ts +329 -0
  240. package/src/codex/native-profile-api.ts +247 -0
  241. package/src/codex/native-profile-manager.ts +1531 -0
  242. package/src/codex/native-profile-processes.ts +121 -0
  243. package/src/codex/native-profile-recovery.ts +99 -0
  244. package/src/codex/native-profile-stage-store.ts +387 -0
  245. package/src/codex/native-profile-startup.ts +622 -0
  246. package/src/codex/native-profile-store.ts +855 -0
  247. package/src/codex/native-profile-types.ts +120 -0
  248. package/src/codex/native-residue.ts +675 -0
  249. package/src/codex/paths.ts +80 -1
  250. package/src/codex/plan-from-token.ts +140 -0
  251. package/src/codex/plan.ts +40 -0
  252. package/src/codex/plugins-doctor.ts +1 -1
  253. package/src/codex/pool-rotation.ts +74 -4
  254. package/src/codex/project-config-warnings.ts +20 -6
  255. package/src/codex/prompt-journal.ts +352 -0
  256. package/src/codex/prompt-layers.ts +1313 -0
  257. package/src/codex/prompt-lock.ts +143 -0
  258. package/src/codex/prompt-text-probe.ts +238 -0
  259. package/src/codex/quota-rejection.ts +298 -0
  260. package/src/codex/quota.ts +264 -21
  261. package/src/codex/refresh.ts +11 -2
  262. package/src/codex/reset-credit-recovery.ts +1044 -0
  263. package/src/codex/routing.ts +514 -94
  264. package/src/codex/runtime.ts +159 -38
  265. package/src/codex/shim.ts +1065 -31
  266. package/src/codex/subagent-model-fallback.ts +437 -43
  267. package/src/codex/sync.ts +191 -2
  268. package/src/codex/transition-state.ts +720 -0
  269. package/src/codex/upstream-host-health.ts +368 -0
  270. package/src/codex/user-identity.ts +557 -0
  271. package/src/codex/warmup.ts +187 -81
  272. package/src/codex/write-coordination.ts +114 -0
  273. package/src/combos/failover.ts +47 -0
  274. package/src/combos/index.ts +4 -0
  275. package/src/combos/request.ts +32 -0
  276. package/src/combos/types.ts +91 -9
  277. package/src/compatibility/index.ts +26 -0
  278. package/src/compatibility/manifest.ts +253 -0
  279. package/src/compatibility/openai-responses.ts +121 -0
  280. package/src/config/atomic-write.ts +219 -0
  281. package/src/config/paths.ts +40 -0
  282. package/src/config/process-state.ts +309 -0
  283. package/src/config/provider-name.ts +24 -0
  284. package/src/config/provider-validation.ts +177 -0
  285. package/src/config/rebase-provenance.ts +68 -0
  286. package/src/config.ts +1713 -709
  287. package/src/generated/compatibility-version.json +3324 -0
  288. package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +19 -17
  289. package/src/grok/inject.ts +16 -5
  290. package/src/grok/inspect.ts +45 -0
  291. package/src/grok/sync.ts +2 -2
  292. package/src/images/loop.ts +157 -33
  293. package/src/images/plan.ts +24 -13
  294. package/src/integrations/config-io.ts +269 -0
  295. package/src/integrations/journal.ts +315 -0
  296. package/src/integrations/merge.ts +135 -0
  297. package/src/integrations/mutation-flight.ts +71 -0
  298. package/src/integrations/native/ownership-preflight.ts +202 -0
  299. package/src/integrations/omp-yaml-source.ts +358 -0
  300. package/src/integrations/owned-refresh.ts +74 -0
  301. package/src/integrations/ownership-policy.ts +160 -0
  302. package/src/integrations/ownership.ts +155 -0
  303. package/src/integrations/registry.ts +166 -0
  304. package/src/integrations/serialize.ts +314 -0
  305. package/src/integrations/state.ts +433 -0
  306. package/src/integrations/store.ts +103 -0
  307. package/src/integrations/writer-lock.ts +98 -0
  308. package/src/integrations/writer.ts +715 -0
  309. package/src/lab/artifacts/sanitize.ts +586 -0
  310. package/src/lab/artifacts/secure-fs.ts +475 -0
  311. package/src/lab/artifacts/store.ts +310 -0
  312. package/src/lab/automation/budgets.ts +78 -0
  313. package/src/lab/automation/config-persistence.ts +256 -0
  314. package/src/lab/automation/constants.ts +39 -0
  315. package/src/lab/automation/cooldown.ts +103 -0
  316. package/src/lab/automation/dispatch.ts +211 -0
  317. package/src/lab/automation/index.ts +13 -0
  318. package/src/lab/automation/orchestrator.ts +499 -0
  319. package/src/lab/automation/persistence.ts +512 -0
  320. package/src/lab/automation/planner.ts +371 -0
  321. package/src/lab/automation/policy.ts +136 -0
  322. package/src/lab/automation/queue.ts +191 -0
  323. package/src/lab/automation/recovery.ts +24 -0
  324. package/src/lab/automation/route-context.ts +21 -0
  325. package/src/lab/automation/run-key.ts +44 -0
  326. package/src/lab/automation/runs-query.ts +34 -0
  327. package/src/lab/automation/types.ts +160 -0
  328. package/src/lab/conformance/assertion.ts +325 -0
  329. package/src/lab/conformance/digest.ts +22 -0
  330. package/src/lab/conformance/executor.ts +741 -0
  331. package/src/lab/conformance/fixture-provider.ts +27 -0
  332. package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
  333. package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
  334. package/src/lab/conformance/harness-budget.ts +47 -0
  335. package/src/lab/conformance/index.ts +5 -0
  336. package/src/lab/conformance/jcs.ts +64 -0
  337. package/src/lab/conformance/json-pointer.ts +39 -0
  338. package/src/lab/conformance/manifest.ts +180 -0
  339. package/src/lab/conformance/mcp-stub.ts +179 -0
  340. package/src/lab/conformance/negative-controls.ts +164 -0
  341. package/src/lab/conformance/observation.ts +355 -0
  342. package/src/lab/conformance/runner.ts +68 -0
  343. package/src/lab/conformance/sse-normalize.ts +59 -0
  344. package/src/lab/conformance/suite-manifest.ts +78 -0
  345. package/src/lab/conformance/types.ts +214 -0
  346. package/src/lab/constants.ts +126 -0
  347. package/src/lab/digest.ts +64 -0
  348. package/src/lab/events/errors.ts +9 -0
  349. package/src/lab/events/limits.ts +117 -0
  350. package/src/lab/events/types.ts +229 -0
  351. package/src/lab/events/validate.ts +781 -0
  352. package/src/lab/fabric/constants.ts +40 -0
  353. package/src/lab/fabric/executor.ts +492 -0
  354. package/src/lab/fabric/index.ts +80 -0
  355. package/src/lab/fabric/manifest.ts +222 -0
  356. package/src/lab/fabric/observe.ts +489 -0
  357. package/src/lab/fabric/patch.ts +79 -0
  358. package/src/lab/fabric/producer-child.ts +139 -0
  359. package/src/lab/fabric/producer-isolate.ts +276 -0
  360. package/src/lab/fabric/producer-protocol.ts +61 -0
  361. package/src/lab/fabric/scratch.ts +439 -0
  362. package/src/lab/fabric/subject.ts +106 -0
  363. package/src/lab/fabric/types.ts +134 -0
  364. package/src/lab/fabric/verifier.ts +98 -0
  365. package/src/lab/index.ts +54 -0
  366. package/src/lab/ledger/artifact-refs.ts +127 -0
  367. package/src/lab/ledger/invalidation.ts +136 -0
  368. package/src/lab/ledger/purge.ts +310 -0
  369. package/src/lab/ledger/store.ts +532 -0
  370. package/src/lab/live/credential-lease.ts +53 -0
  371. package/src/lab/live/destination.ts +155 -0
  372. package/src/lab/live/executor.ts +336 -0
  373. package/src/lab/live/inert-tools.ts +56 -0
  374. package/src/lab/live/manifest.ts +85 -0
  375. package/src/lab/live/mcp-loopback.ts +57 -0
  376. package/src/lab/live/runner.ts +19 -0
  377. package/src/lab/live/sandbox.ts +61 -0
  378. package/src/lab/live/suite-manifest.ts +41 -0
  379. package/src/lab/live/transport.ts +118 -0
  380. package/src/lab/live/types.ts +197 -0
  381. package/src/lab/observe/from-conformance.ts +301 -0
  382. package/src/lab/observe/from-live.ts +117 -0
  383. package/src/lab/paths.ts +153 -0
  384. package/src/lab/projection/rebuild.ts +495 -0
  385. package/src/lab/projection/schema.ts +135 -0
  386. package/src/lab/projection/verdicts.ts +474 -0
  387. package/src/lab/projection/verification.ts +412 -0
  388. package/src/lab/public/bundle.ts +217 -0
  389. package/src/lab/public/community-authority.ts +175 -0
  390. package/src/lab/public/community-files.ts +29 -0
  391. package/src/lab/public/community.ts +479 -0
  392. package/src/lab/public/file-safety.ts +155 -0
  393. package/src/lab/public/ids.ts +26 -0
  394. package/src/lab/public/index.ts +16 -0
  395. package/src/lab/public/mutation-lock.ts +424 -0
  396. package/src/lab/public/operator.ts +353 -0
  397. package/src/lab/public/origin-purge.ts +79 -0
  398. package/src/lab/public/origin.ts +203 -0
  399. package/src/lab/public/privacy.ts +143 -0
  400. package/src/lab/public/private-file.ts +261 -0
  401. package/src/lab/public/project.ts +124 -0
  402. package/src/lab/public/purge-test-fault.ts +21 -0
  403. package/src/lab/public/purge.ts +223 -0
  404. package/src/lab/public/registry.ts +44 -0
  405. package/src/lab/public/revocation.ts +252 -0
  406. package/src/lab/public/signature.ts +243 -0
  407. package/src/lab/public/storage.ts +105 -0
  408. package/src/lab/public/strict-json.ts +206 -0
  409. package/src/lab/public/time.ts +26 -0
  410. package/src/lab/public/types.ts +172 -0
  411. package/src/lab/public/validate.ts +391 -0
  412. package/src/lab/query/catalog.ts +101 -0
  413. package/src/lab/query/connection.ts +107 -0
  414. package/src/lab/query/constants.ts +4 -0
  415. package/src/lab/query/cursor.ts +132 -0
  416. package/src/lab/query/dto-map.ts +277 -0
  417. package/src/lab/query/errors.ts +22 -0
  418. package/src/lab/query/freshness.ts +53 -0
  419. package/src/lab/query/index.ts +45 -0
  420. package/src/lab/query/latest-observation.ts +59 -0
  421. package/src/lab/query/passive-production.ts +159 -0
  422. package/src/lab/query/queries.ts +444 -0
  423. package/src/lab/query/types.ts +266 -0
  424. package/src/lab/subject/behavior-fingerprint.ts +77 -0
  425. package/src/lab/subject/installation-salt.ts +112 -0
  426. package/src/lab/subject/protocol-subject.ts +80 -0
  427. package/src/lab/subject/route-subject.ts +74 -0
  428. package/src/lib/admin-secrets.ts +24 -0
  429. package/src/lib/app-owned-memory-stores.ts +22 -0
  430. package/src/lib/bounded-body.ts +167 -11
  431. package/src/lib/bun-runtime.ts +125 -12
  432. package/src/lib/bun-stream-caps.ts +13 -9
  433. package/src/lib/codex-restart-contract.ts +120 -0
  434. package/src/lib/config-ownership.ts +26 -2
  435. package/src/lib/destination-policy.ts +65 -1
  436. package/src/lib/errors.ts +80 -5
  437. package/src/lib/fabric-task-execution-authority.ts +7 -0
  438. package/src/lib/fabric-task-host.ts +29 -0
  439. package/src/lib/lab-activation.ts +223 -0
  440. package/src/lib/lab-live-execution-authority.ts +13 -0
  441. package/src/lib/lab-live-host.ts +30 -0
  442. package/src/lib/lab-live-pinned-sender.ts +56 -0
  443. package/src/lib/lab-live-route-production.ts +130 -0
  444. package/src/lib/lab-passive-linker-registration.ts +26 -0
  445. package/src/lib/local-management-attestation.ts +51 -0
  446. package/src/lib/local-management-capability.ts +100 -0
  447. package/src/lib/local-provider-reload-contract.ts +100 -0
  448. package/src/lib/optional-shutdown-hooks.ts +57 -0
  449. package/src/lib/package-tree-integrity.ts +101 -0
  450. package/src/lib/pinned-http.ts +145 -26
  451. package/src/lib/process-control.ts +6 -2
  452. package/src/lib/provider-outbound.ts +49 -9
  453. package/src/lib/redact.ts +419 -3
  454. package/src/lib/self-launch-argv.ts +15 -0
  455. package/src/lib/server-resource-ownership.ts +71 -0
  456. package/src/lib/service-secrets.ts +15 -0
  457. package/src/lib/shadow-call.ts +35 -4
  458. package/src/lib/sse-decoder.ts +41 -0
  459. package/src/lib/state-store-registrations.ts +10 -2
  460. package/src/lib/system-restart-contract.ts +73 -0
  461. package/src/lib/token-estimate.ts +19 -2
  462. package/src/lib/tool-argument-integers.ts +253 -0
  463. package/src/lib/translator-budget.ts +44 -0
  464. package/src/lib/upstream-http-version.ts +57 -0
  465. package/src/lib/upstream-reachability.ts +95 -0
  466. package/src/lib/upstream-retry.ts +156 -3
  467. package/src/lib/windows-atomic-replace.ts +155 -0
  468. package/src/lib/windows-elevation.ts +70 -2
  469. package/src/lib/windows-secret-acl.ts +409 -69
  470. package/src/lib/windows-service-wrappers.ts +72 -0
  471. package/src/lib/windows-text.ts +106 -0
  472. package/src/lib/windows-user-principal.ts +341 -0
  473. package/src/lib/winsw.ts +33 -5
  474. package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
  475. package/src/oauth/account-import/index.ts +15 -0
  476. package/src/oauth/account-import/parser.ts +83 -0
  477. package/src/oauth/account-import/registry.ts +18 -0
  478. package/src/oauth/account-import/service.ts +75 -0
  479. package/src/oauth/account-import/types.ts +91 -0
  480. package/src/oauth/anthropic.ts +12 -1
  481. package/src/oauth/callback-server.ts +30 -4
  482. package/src/oauth/chatgpt.ts +12 -1
  483. package/src/oauth/cline.ts +203 -0
  484. package/src/oauth/command-code.ts +239 -0
  485. package/src/oauth/cursor.ts +46 -5
  486. package/src/oauth/generic-account-failover.ts +231 -0
  487. package/src/oauth/google-antigravity.ts +35 -3
  488. package/src/oauth/health.ts +20 -12
  489. package/src/oauth/index.ts +462 -71
  490. package/src/oauth/key-providers.ts +25 -0
  491. package/src/oauth/kimi.ts +25 -3
  492. package/src/oauth/kiro.ts +95 -6
  493. package/src/oauth/local-token-detect.ts +13 -2
  494. package/src/oauth/log.ts +3 -1
  495. package/src/oauth/login-cli.ts +88 -28
  496. package/src/oauth/nous.ts +798 -0
  497. package/src/oauth/open-browser-choice.ts +26 -0
  498. package/src/oauth/store.ts +133 -26
  499. package/src/oauth/token-guardian.ts +9 -3
  500. package/src/oauth/types.ts +15 -0
  501. package/src/pi/models.ts +2 -2
  502. package/src/providers/alibaba-region-migration.ts +1 -1
  503. package/src/providers/antigravity-models.ts +521 -31
  504. package/src/providers/auto-compact-budget.ts +65 -0
  505. package/src/providers/base-url-choices.ts +10 -0
  506. package/src/providers/codex-capacity.ts +292 -0
  507. package/src/providers/command-code-efforts.ts +176 -0
  508. package/src/providers/context-cap.ts +26 -8
  509. package/src/providers/cursor-pool.ts +72 -0
  510. package/src/providers/default-aliases.ts +65 -0
  511. package/src/providers/derive.ts +281 -6
  512. package/src/providers/fastwire.ts +507 -0
  513. package/src/providers/free-directory.ts +10 -7
  514. package/src/providers/google-vertex-location.ts +14 -0
  515. package/src/providers/key-failover.ts +71 -3
  516. package/src/providers/label.ts +35 -2
  517. package/src/providers/model-discovery-limits.ts +16 -0
  518. package/src/providers/model-discovery.ts +115 -22
  519. package/src/providers/model-presets.ts +119 -0
  520. package/src/providers/model-rename-migration.ts +255 -0
  521. package/src/providers/model-rename-startup.ts +28 -0
  522. package/src/providers/new-model-policy.ts +146 -0
  523. package/src/providers/openai-sidecar.ts +72 -4
  524. package/src/providers/openai-tier-startup.ts +31 -2
  525. package/src/providers/openai-tiers.ts +119 -4
  526. package/src/providers/openai-virtual-models.ts +1 -0
  527. package/src/providers/opencode-zen-rate-limit.ts +102 -0
  528. package/src/providers/provider-id-rewrite.ts +30 -0
  529. package/src/providers/quota.ts +1379 -40
  530. package/src/providers/registry.ts +1545 -112
  531. package/src/providers/request-pacing.ts +310 -0
  532. package/src/providers/service-tier.ts +277 -0
  533. package/src/providers/slug-codec.ts +94 -6
  534. package/src/providers/static-model-discovery.ts +86 -0
  535. package/src/providers/xai-responses-opt-in.ts +15 -0
  536. package/src/providers/xai-transport.ts +32 -4
  537. package/src/reasoning-effort.ts +68 -3
  538. package/src/responses/apply-patch-envelope.ts +63 -0
  539. package/src/responses/code-mode-helper-compat.ts +50 -0
  540. package/src/responses/compaction.ts +26 -1
  541. package/src/responses/custom-tool-compat.ts +384 -0
  542. package/src/responses/hosted-tool-policy.ts +9 -0
  543. package/src/responses/namespace-tool-compat.ts +435 -0
  544. package/src/responses/parser.ts +225 -38
  545. package/src/responses/provider-continuation.ts +98 -0
  546. package/src/responses/provider-opaque-metadata.ts +73 -0
  547. package/src/responses/reasoning-envelope.ts +9 -1
  548. package/src/responses/reasoning-replay-cache.ts +504 -0
  549. package/src/responses/schema.ts +12 -2
  550. package/src/responses/spill-store.ts +75 -10
  551. package/src/responses/state.ts +659 -31
  552. package/src/responses/thought-signature-replay.ts +364 -0
  553. package/src/responses/tool-search-compat.ts +301 -0
  554. package/src/responses/truncated-stop-reason.ts +60 -0
  555. package/src/responses/turn-termination.ts +107 -0
  556. package/src/router.ts +405 -31
  557. package/src/routing/analytics.ts +379 -0
  558. package/src/routing/capability.ts +244 -0
  559. package/src/routing/compatibility/assemble.ts +73 -0
  560. package/src/routing/compatibility/behavior.ts +278 -0
  561. package/src/routing/compatibility/catalog.ts +99 -0
  562. package/src/routing/compatibility/endpoint.ts +52 -0
  563. package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
  564. package/src/routing/compatibility/policy.ts +181 -0
  565. package/src/routing/compatibility/provider-slot.ts +56 -0
  566. package/src/routing/compatibility/reader.ts +110 -0
  567. package/src/routing/compatibility/subject.ts +191 -0
  568. package/src/routing/compatibility/types.ts +64 -0
  569. package/src/routing/compatibility/version.ts +104 -0
  570. package/src/routing/cost.ts +77 -0
  571. package/src/routing/evaluator.ts +495 -0
  572. package/src/routing/health.ts +412 -0
  573. package/src/routing/history/cursor.ts +43 -0
  574. package/src/routing/history/indexer.ts +605 -0
  575. package/src/routing/history/schema.ts +72 -0
  576. package/src/routing/profile-namespace.ts +15 -0
  577. package/src/routing/profile.ts +547 -0
  578. package/src/routing/quota.ts +155 -0
  579. package/src/routing/request-evidence.ts +45 -0
  580. package/src/routing/trace.ts +776 -0
  581. package/src/server/adapter-resolve.ts +2 -29
  582. package/src/server/auth-cors.ts +289 -48
  583. package/src/server/background-lifecycle.ts +182 -0
  584. package/src/server/chat-completions.ts +154 -72
  585. package/src/server/chat-native-sse.ts +331 -0
  586. package/src/server/chat-native.ts +445 -0
  587. package/src/server/claude-messages.ts +160 -44
  588. package/src/server/direct-local-http.ts +347 -0
  589. package/src/server/effort-policy.ts +18 -0
  590. package/src/server/github-copilot-responses-repair.ts +338 -0
  591. package/src/server/gui-static.ts +39 -10
  592. package/src/server/images.ts +94 -12
  593. package/src/server/index.ts +1021 -190
  594. package/src/server/lifecycle.ts +336 -14
  595. package/src/server/live.ts +136 -17
  596. package/src/server/local-management-read-client.ts +90 -0
  597. package/src/server/local-provider-reload-client.ts +137 -0
  598. package/src/server/management/agent-settings-routes.ts +433 -116
  599. package/src/server/management/api-key-usage.ts +31 -5
  600. package/src/server/management/body.ts +6 -0
  601. package/src/server/management/codex-prompt-routes.ts +570 -0
  602. package/src/server/management/combo-routes.ts +64 -25
  603. package/src/server/management/config-routes.ts +511 -51
  604. package/src/server/management/context.ts +89 -2
  605. package/src/server/management/integration-routes.ts +498 -0
  606. package/src/server/management/lab-automation-routes.ts +206 -0
  607. package/src/server/management/lab-routes.ts +563 -0
  608. package/src/server/management/logs-usage-routes.ts +127 -52
  609. package/src/server/management/model-routes.ts +461 -133
  610. package/src/server/management/model-rows.ts +167 -0
  611. package/src/server/management/native-integration-routes.ts +770 -0
  612. package/src/server/management/oauth-account-routes.ts +90 -8
  613. package/src/server/management/provider-capability-config.ts +48 -0
  614. package/src/server/management/provider-routes.ts +836 -159
  615. package/src/server/management/request-history-routes.ts +191 -0
  616. package/src/server/management/route-registry.ts +332 -0
  617. package/src/server/management/routing-analytics-routes.ts +74 -0
  618. package/src/server/management/routing-profile-routes.ts +380 -0
  619. package/src/server/management/shared.ts +27 -11
  620. package/src/server/management/sidebar-routes.ts +47 -31
  621. package/src/server/management/storage-log-guard-routes.ts +186 -0
  622. package/src/server/management/sync-response.ts +69 -0
  623. package/src/server/management/system-restart.ts +289 -33
  624. package/src/server/management/system-routes.ts +77 -0
  625. package/src/server/management/usage-summary-cache.ts +9 -1
  626. package/src/server/management/vision-sidecar-options.ts +167 -0
  627. package/src/server/management/web-search-sidecar-options.ts +120 -0
  628. package/src/server/management-api.ts +126 -23
  629. package/src/server/management-auth.ts +220 -5
  630. package/src/server/passive-route-linker.ts +66 -0
  631. package/src/server/port-reclaim.ts +1 -1
  632. package/src/server/ports.ts +41 -1
  633. package/src/server/proxy-liveness.ts +161 -10
  634. package/src/server/readiness.ts +99 -0
  635. package/src/server/relay-eager.ts +221 -54
  636. package/src/server/relay.ts +479 -87
  637. package/src/server/request-decompress.ts +113 -6
  638. package/src/server/request-log-conversation.ts +51 -0
  639. package/src/server/request-log.ts +298 -31
  640. package/src/server/responses/agent-task-recovery-cache.ts +143 -0
  641. package/src/server/responses/agent-task-recovery.ts +465 -0
  642. package/src/server/responses/codex-auth-error.ts +55 -0
  643. package/src/server/responses/collaboration.ts +239 -36
  644. package/src/server/responses/combo-stream-preflight.ts +178 -0
  645. package/src/server/responses/compact.ts +444 -72
  646. package/src/server/responses/core.ts +3579 -436
  647. package/src/server/responses/empty-completion-guard.ts +333 -0
  648. package/src/server/responses/encrypted-payload.ts +62 -39
  649. package/src/server/responses/fetch-helpers.ts +89 -99
  650. package/src/server/responses/input-admission.ts +187 -0
  651. package/src/server/responses/pacing-overload.ts +13 -0
  652. package/src/server/responses/passthrough-error.ts +33 -9
  653. package/src/server/responses/policy-fallback.ts +178 -0
  654. package/src/server/responses/responses-field-backfill.ts +343 -0
  655. package/src/server/responses/terminal-guard.ts +26 -5
  656. package/src/server/responses/upstream-error.ts +5 -0
  657. package/src/server/responses/ws-upstream.ts +381 -0
  658. package/src/server/responses-custom-tool-repair.ts +363 -0
  659. package/src/server/responses-item-id-repair.ts +54 -6
  660. package/src/server/responses-json-events.ts +90 -0
  661. package/src/server/responses-model-rewrite.ts +29 -0
  662. package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
  663. package/src/server/responses-snapshot-repair.ts +621 -0
  664. package/src/server/responses-terminal-repair.ts +363 -0
  665. package/src/server/responses-tool-search-repair.ts +267 -0
  666. package/src/server/responses-undeclared-tool-guard.ts +376 -0
  667. package/src/server/responses.ts +18 -2
  668. package/src/server/search.ts +78 -13
  669. package/src/server/sse-frame-buffer.ts +319 -0
  670. package/src/server/sse-payload-rewrite.ts +110 -22
  671. package/src/server/startup-action-control.ts +8 -1
  672. package/src/server/startup-health-cache.ts +19 -1
  673. package/src/server/system-env.ts +80 -9
  674. package/src/server/ws-bridge.ts +53 -40
  675. package/src/service-manager-probe.ts +892 -0
  676. package/src/service.ts +1153 -94
  677. package/src/sidecar/auth.ts +92 -0
  678. package/src/sidecar/candidates.ts +83 -0
  679. package/src/storage/cleanup.ts +2 -2
  680. package/src/storage/policy-job.ts +14 -4
  681. package/src/storage/policy.ts +88 -23
  682. package/src/storage/scanner.ts +1 -1
  683. package/src/storage/worker-lifecycle.ts +14 -14
  684. package/src/tray/windows-tray.ps1 +83 -9
  685. package/src/tray/windows.ts +43 -16
  686. package/src/types/accounts.ts +37 -0
  687. package/src/types/config.ts +911 -0
  688. package/src/types/provider.ts +614 -0
  689. package/src/types/request.ts +397 -0
  690. package/src/types/tools.ts +236 -0
  691. package/src/types/wire.ts +80 -0
  692. package/src/types.ts +107 -1237
  693. package/src/update/index.ts +37 -23
  694. package/src/update/job.ts +445 -68
  695. package/src/update/notify.ts +12 -6
  696. package/src/update/npm-cache-preflight.d.mts +47 -0
  697. package/src/update/npm-cache-preflight.mjs +201 -0
  698. package/src/update/transactional-install.d.mts +22 -0
  699. package/src/update/transactional-install.mjs +266 -0
  700. package/src/usage/cost.ts +0 -0
  701. package/src/usage/expected-prices.ts +287 -16
  702. package/src/usage/log.ts +666 -50
  703. package/src/usage/summary.ts +388 -17
  704. package/src/usage/user-cost-overlay-reconciler.ts +313 -0
  705. package/src/usage/user-cost-overlays.ts +314 -0
  706. package/src/vision/anthropic-describe.ts +10 -6
  707. package/src/vision/backends.ts +97 -0
  708. package/src/vision/describe.ts +27 -16
  709. package/src/vision/eligibility.ts +250 -0
  710. package/src/vision/index.ts +238 -24
  711. package/src/vision/reasoning.ts +55 -0
  712. package/src/vision/routed-describe.ts +175 -0
  713. package/src/vision/timeout-bounds.ts +9 -0
  714. package/src/web-search/anthropic-executor.ts +13 -7
  715. package/src/web-search/backends.ts +108 -0
  716. package/src/web-search/exa-executor.ts +119 -0
  717. package/src/web-search/executor.ts +21 -6
  718. package/src/web-search/gemini-executor.ts +141 -0
  719. package/src/web-search/index.ts +150 -15
  720. package/src/web-search/loop.ts +284 -54
  721. package/src/web-search/parse.ts +125 -30
  722. package/src/web-search/sources.ts +60 -0
  723. package/src/web-search/xai-executor.ts +219 -0
  724. package/gui/dist/assets/index-DTpMHS4F.js +0 -67
  725. package/gui/dist/assets/index-ZNVDE3C7.css +0 -1
  726. package/gui/dist/provider-icons/antigravity.svg +0 -1
  727. package/gui/dist/provider-icons/claude.svg +0 -1
  728. package/gui/dist/provider-icons/copilot.svg +0 -1
  729. package/gui/dist/provider-icons/cursor.svg +0 -2
  730. package/gui/dist/provider-icons/gemini.svg +0 -1
  731. package/gui/dist/provider-icons/grok-color.svg +0 -1
  732. package/gui/dist/provider-icons/kiro.svg +0 -14
  733. package/src/cli/internal-dispatch.ts +0 -20
package/src/cli/models.ts CHANGED
@@ -5,15 +5,77 @@ import { randomUUID } from "node:crypto";
5
5
  import { createInterface } from "node:readline/promises";
6
6
  import { syncModelsToCodex } from "../codex/sync";
7
7
  import { hasOwnProvider, isValidProviderName, loadConfig, saveConfig } from "../config";
8
- import { routedSlug } from "../providers/slug-codec";
8
+ import {
9
+ canonicalizeReasoningEfforts,
10
+ configuredReasoningEfforts,
11
+ isDeclaredReasoningEffort,
12
+ modelRecordValue,
13
+ } from "../reasoning-effort";
14
+ import { encodedModelIdCollides, resolveSlugSelection, routedSlug } from "../providers/slug-codec";
15
+ import { knownModelIdsForProvider } from "../router";
9
16
  import { findLiveProxy } from "../server/proxy-liveness";
10
- import type { OcxConfig, OcxCustomModel } from "../types";
17
+ import { modelInList, type OcxConfig, type OcxCustomModel } from "../types";
11
18
 
12
- const ADD_USAGE = "Usage: ocx models add <provider> <modelId> [--display-name <name>] [--context-window <tokens>] [--modalities text,image,audio]";
19
+ const ADD_USAGE = "Usage: ocx models add <provider> <modelId> [--display-name <name>] [--context-window <tokens>] [--modalities text,image,audio] [--reasoning-efforts <none,minimal,low,medium,high,xhigh,max,ultra>] [--default-reasoning-effort <level>]";
13
20
  const REMOVE_USAGE = "Usage: ocx models remove <customId|provider/modelId> [--yes]";
14
21
  const LIST_CUSTOM_USAGE = "Usage: ocx models list-custom [--json]";
15
22
  const ALLOWED_MODALITIES = new Set(["text", "image", "audio"]);
16
23
 
24
+ /**
25
+ * Parse and validate the reasoning flags shared by `ocx models add` (offline path).
26
+ * "-" means "inherit" and omits the field entirely; "" means an explicit empty ladder
27
+ * ("no reasoning" override, the same state the dashboard stores for the toggle-off
28
+ * checkbox set). Malformed CSV like `low,,high` or `,,` is rejected instead of being
29
+ * silently normalized. Values are canonicalized into Codex ladder order so the stored
30
+ * config matches what the API stores.
31
+ */
32
+ export function parseReasoningArgs(
33
+ reasoningEffortsValue: string | undefined,
34
+ defaultEffortValue: string | undefined,
35
+ ): { reasoningEfforts?: string[]; defaultReasoningEffort?: string; error?: string } {
36
+ if (reasoningEffortsValue === undefined && defaultEffortValue === undefined) return {};
37
+ let reasoningEfforts: string[] | undefined;
38
+ if (reasoningEffortsValue !== undefined) {
39
+ const trimmed = reasoningEffortsValue.trim();
40
+ if (trimmed === "-") {
41
+ reasoningEfforts = undefined;
42
+ } else if (trimmed === "") {
43
+ // Explicit no-reasoning override, exactly like the API's [] / the dashboard's
44
+ // uncheck-all state.
45
+ reasoningEfforts = [];
46
+ } else {
47
+ const parts = trimmed.split(",").map(value => value.trim());
48
+ if (parts.some(part => part === "")) {
49
+ return { error: "--reasoning-efforts must be comma-separated values from none, minimal, low, medium, high, xhigh, max, ultra (\"\" for no reasoning, \"-\" to inherit)" };
50
+ }
51
+ const invalid = parts.filter(value => !isDeclaredReasoningEffort(value));
52
+ if (invalid.length > 0) {
53
+ return { error: `unsupported reasoning effort: ${invalid.join(", ")} (allowed: none, minimal, low, medium, high, xhigh, max, ultra)` };
54
+ }
55
+ reasoningEfforts = canonicalizeReasoningEfforts(parts);
56
+ }
57
+ }
58
+ let defaultReasoningEffort: string | undefined;
59
+ if (defaultEffortValue !== undefined) {
60
+ const trimmed = defaultEffortValue.trim();
61
+ if (trimmed === "-") {
62
+ defaultReasoningEffort = undefined;
63
+ } else {
64
+ if (!isDeclaredReasoningEffort(trimmed)) {
65
+ return { error: `unsupported reasoning effort: ${trimmed} (allowed: none, minimal, low, medium, high, xhigh, max, ultra)` };
66
+ }
67
+ if (!reasoningEfforts || reasoningEfforts.length === 0) {
68
+ return { error: "--default-reasoning-effort requires --reasoning-efforts" };
69
+ }
70
+ if (!reasoningEfforts.includes(trimmed)) {
71
+ return { error: `--default-reasoning-effort "${trimmed}" is not in the declared reasoning efforts` };
72
+ }
73
+ defaultReasoningEffort = trimmed;
74
+ }
75
+ }
76
+ return { reasoningEfforts, defaultReasoningEffort };
77
+ }
78
+
17
79
  interface ModelEntry {
18
80
  provider: string;
19
81
  model: string;
@@ -34,22 +96,34 @@ function collectModels(config: OcxConfig, providerFilter?: string): ModelEntry[]
34
96
  const seen = new Set<string>();
35
97
  const contextWindows = prov.modelContextWindows ?? {};
36
98
  const inputModalities = prov.modelInputModalities ?? {};
37
- const reasoningEfforts = prov.modelReasoningEfforts ?? {};
38
99
  const globalContext = prov.contextWindow ?? null;
39
100
 
40
101
  const addModel = (model: string, isDefault: boolean) => {
41
102
  if (seen.has(model)) return;
42
103
  seen.add(model);
43
104
 
44
- const noVision = prov.noVisionModels?.includes(model);
45
- const modalities = inputModalities[model] ?? (noVision ? ["text"] : null);
46
- const efforts = reasoningEfforts[model] ?? prov.reasoningEfforts ?? null;
105
+ // Resolve exactly as the runtime does, or this command reports capabilities the
106
+ // proxy will not honour: `isModelTextOnly` matches noVisionModels with modelInList
107
+ // and reads modelInputModalities with modelRecordValue, so a `gpt-oss` entry covers
108
+ // `gpt-oss:120b`. A bare lookup reported that model as unclassified on every field.
109
+ // noVisionModels is checked first because `isModelTextOnly` returns true on that
110
+ // match before it ever reads modelInputModalities: a `gpt-oss` noVision entry beats
111
+ // an exact `gpt-oss:120b` entry that lists "image", and the proxy rejects the image.
112
+ const noVision = modelInList(prov.noVisionModels, model);
113
+ const modalities = noVision ? ["text"] : (modelRecordValue(inputModalities, model) ?? null);
114
+ // Same reason, for the ladder: `configuredReasoningEfforts` is what the catalog
115
+ // (`provider-fetch`) and the effort cap (`effort-policy`) resolve through, and it
116
+ // does three things this expression did not — it returns [] for a noReasoningModels
117
+ // match, drops levels Codex does not declare, and re-adds tiers the wire map proves
118
+ // the model emits. Restating two of its five lines here reported a ladder the proxy
119
+ // strips, and unsanitized junk as a supported level.
120
+ const efforts = configuredReasoningEfforts(prov, model) ?? null;
47
121
 
48
122
  entries.push({
49
123
  provider: provName,
50
124
  model,
51
125
  isDefault,
52
- contextWindow: contextWindows[model] ?? globalContext,
126
+ contextWindow: modelRecordValue(contextWindows, model) ?? globalContext,
53
127
  inputModalities: modalities,
54
128
  reasoningEfforts: efforts,
55
129
  });
@@ -102,9 +176,13 @@ function rejectUnexpectedArgs(args: string[], usage: string): void {
102
176
  async function syncCustomModelsIfLive(): Promise<void> {
103
177
  const live = await findLiveProxy();
104
178
  if (!live) return;
105
- await syncModelsToCodex(live.port).catch(error => {
179
+ const synced = await syncModelsToCodex(live.port).catch(error => {
106
180
  console.error(`Warning: custom model saved, but catalog sync failed: ${error instanceof Error ? error.message : String(error)}`);
181
+ return null;
107
182
  });
183
+ if (synced?.status === "skipped") {
184
+ console.log("Custom model saved; Codex integration is OFF, so its catalog was not changed.");
185
+ }
108
186
  }
109
187
 
110
188
  async function handleCustomAdd(args: string[]): Promise<void> {
@@ -114,11 +192,12 @@ async function handleCustomAdd(args: string[]): Promise<void> {
114
192
  const displayNameValue = consumeFlagValue(rest, "--display-name");
115
193
  const contextWindowValue = consumeFlagValue(rest, "--context-window");
116
194
  const modalitiesValue = consumeFlagValue(rest, "--modalities");
195
+ const reasoningEffortsValue = consumeFlagValue(rest, "--reasoning-efforts");
196
+ const defaultEffortValue = consumeFlagValue(rest, "--default-reasoning-effort");
117
197
  rejectUnexpectedArgs(rest, ADD_USAGE);
118
198
 
119
199
  if (!provider || !modelId) fail("provider and modelId are required", ADD_USAGE);
120
200
  if (!isValidProviderName(provider)) fail(`invalid provider name "${provider}"`);
121
- if (modelId.includes("/")) fail("modelId must not contain /");
122
201
 
123
202
  const config = loadConfig();
124
203
  if (!hasOwnProvider(config.providers, provider)) {
@@ -146,11 +225,18 @@ async function handleCustomAdd(args: string[]): Promise<void> {
146
225
  inputModalities = [...new Set(inputModalities)];
147
226
  }
148
227
 
228
+ const parsed = parseReasoningArgs(reasoningEffortsValue, defaultEffortValue);
229
+ if (parsed.error) fail(parsed.error);
230
+
149
231
  const existing = config.customModels ?? [];
150
232
  const slug = routedSlug(provider, modelId);
151
233
  if (existing.some(model => routedSlug(model.provider, model.modelId) === slug)) {
152
234
  fail(`custom model "${slug}" already exists`);
153
235
  }
236
+ const known = knownModelIdsForProvider(provider, config.providers[provider], config);
237
+ if (encodedModelIdCollides(modelId, known)) {
238
+ fail(`custom model "${slug}" is ambiguous; it encodes to an existing model id`);
239
+ }
154
240
 
155
241
  const entry: OcxCustomModel = {
156
242
  id: randomUUID(),
@@ -159,6 +245,8 @@ async function handleCustomAdd(args: string[]): Promise<void> {
159
245
  ...(displayName ? { displayName } : {}),
160
246
  ...(contextWindow ? { contextWindow } : {}),
161
247
  ...(inputModalities ? { inputModalities } : {}),
248
+ ...(parsed.reasoningEfforts ? { reasoningEfforts: parsed.reasoningEfforts } : {}),
249
+ ...(parsed.defaultReasoningEffort ? { defaultReasoningEffort: parsed.defaultReasoningEffort } : {}),
162
250
  addedAt: new Date().toISOString(),
163
251
  };
164
252
  config.customModels = [...existing, entry];
@@ -189,10 +277,22 @@ async function handleCustomRemove(args: string[]): Promise<void> {
189
277
 
190
278
  const config = loadConfig();
191
279
  const existing = config.customModels ?? [];
192
- const index = target.includes("/")
193
- ? existing.findIndex(model => routedSlug(model.provider, model.modelId) === target)
194
- : existing.findIndex(model => model.id === target);
195
- if (index === -1) fail(`custom model "${target}" not found`);
280
+ // Slug matching goes through the shared resolver so this command sees the same collision
281
+ // class catalog filtering and persisted sync see (#2491). `slugEquals` compares the raw and
282
+ // encoded spellings of ONE id, so a selector written in the native slash form matched only
283
+ // that row while the dash form matched both — the two relations disagreed on the same
284
+ // config. Removal stays exact-or-refuse: an ambiguous selector still aborts below, which is
285
+ // the right default for a destructive command.
286
+ const matchingIndexes = existing.flatMap((model, index) => {
287
+ if (!target.includes("/")) return model.id === target ? [index] : [];
288
+ const resolved = resolveSlugSelection(model.provider, target, [model.modelId]);
289
+ return resolved.matched.length > 0 ? [index] : [];
290
+ });
291
+ if (matchingIndexes.length === 0) fail(`custom model "${target}" not found`);
292
+ if (matchingIndexes.length > 1) {
293
+ fail(`custom model selector "${target}" is ambiguous; use the custom model id`);
294
+ }
295
+ const index = matchingIndexes[0]!;
196
296
 
197
297
  const model = existing[index];
198
298
  if (!confirmed && !(await confirmCustomRemoval(model))) {
@@ -214,12 +314,14 @@ function customModelCells(model: OcxCustomModel): string[] {
214
314
  model.displayName ?? "-",
215
315
  model.contextWindow ? `${Math.round(model.contextWindow / 1000)}k` : "-",
216
316
  model.inputModalities?.join(",") ?? "-",
317
+ model.reasoningEfforts?.join(",") ?? "-",
318
+ model.defaultReasoningEffort ?? "-",
217
319
  ];
218
320
  }
219
321
 
220
322
  function printCustomModelGroup(provider: string, models: OcxCustomModel[]): void {
221
323
  const rows = models.map(customModelCells);
222
- const headers = ["ID", "MODEL", "DISPLAY NAME", "CONTEXT", "MODALITIES"];
324
+ const headers = ["ID", "MODEL", "DISPLAY NAME", "CONTEXT", "MODALITIES", "EFFORTS", "DEFAULT EFFORT"];
223
325
  const widths = headers.map((header, column) => Math.max(header.length, ...rows.map(row => row[column].length)));
224
326
  const line = (cells: string[]) => cells.map((cell, column) => cell.padEnd(widths[column])).join(" ");
225
327
  console.log(`${provider}:`);
@@ -326,7 +428,7 @@ export async function handleModels(args: string[]): Promise<void> {
326
428
  handleCustomList(rest);
327
429
  return;
328
430
  }
329
- if (["live", "edit", "enable", "disable", "provider", "selected", "context", "shadow"].includes(subcommand ?? "")) {
431
+ if (["live", "edit", "enable", "disable", "provider", "selected", "preset", "context", "shadow"].includes(subcommand ?? "")) {
330
432
  const { handleModelsRuntimeCommand } = await import("./models-runtime");
331
433
  const code = await handleModelsRuntimeCommand(subcommand!, rest);
332
434
  if (code !== null) process.exitCode = code;
@@ -10,12 +10,18 @@ import {
10
10
  takeOption,
11
11
  type RuntimeApiDeps,
12
12
  } from "./runtime-api";
13
+ import { formatUsageReport } from "./usage-report";
14
+ import { USAGE_RANGES, USAGE_SURFACES } from "../usage/summary";
13
15
 
14
16
  const USAGE = `Usage:
15
17
  ocx observe logs [--provider <name>] [--model <id>] [--status <code>]
16
- [--limit <n>] [--follow] [--json|--jsonl]
17
- ocx observe usage [--range <7d|30d|all>] [--surface <all|codex|claude|grok>] [--json]
18
- ocx observe storage [--json]
18
+ [--conversation <id>] [--limit <n>] [--follow] [--json|--jsonl]
19
+ ocx logs explain <request-id> [--json]
20
+ ocx logs rebuild-index
21
+ ocx logs index-status
22
+ ocx observe usage [--range <today|1d|7d|30d|all>] [--surface <all|codex|claude|grok>]
23
+ [--provider <name>] [--model <id>] [--json]
24
+ ocx observe storage [codex-logs [status|protect|unprotect|repair|compact] [--mode <compat|quiet>]] [--json]
19
25
  ocx observe memory [--json]
20
26
  ocx observe debug [--json]
21
27
  ocx observe claude-inbound [--limit <n>] [--json]
@@ -44,7 +50,12 @@ function formatLog(row: LogEntry): string {
44
50
  const route = [row.provider, row.model].filter(Boolean).join("/");
45
51
  const status = row.status ?? row.statusCode ?? "?";
46
52
  const duration = row.durationMs !== undefined ? `${String(row.durationMs)}ms` : "";
47
- return [time, String(status), route, duration].filter(Boolean).join(" ");
53
+ // The conversation id is shown because a conversation FILTER whose output never names the
54
+ // conversation is hard to trust: an empty result and a wrong-id result look identical (#2704).
55
+ const conversation = typeof row.conversationId === "string" && row.conversationId.length > 0
56
+ ? `conv=${row.conversationId}`
57
+ : "";
58
+ return [time, String(status), route, duration, conversation].filter(Boolean).join(" ");
48
59
  }
49
60
 
50
61
  async function logs(argv: string[], deps: RuntimeApiDeps): Promise<void> {
@@ -55,13 +66,16 @@ async function logs(argv: string[], deps: RuntimeApiDeps): Promise<void> {
55
66
  const provider = takeOption(args, "--provider");
56
67
  const model = takeOption(args, "--model");
57
68
  const status = takeOption(args, "--status");
69
+ // Both spellings, because the server accepts both (`request-log.ts:1032`) and an operator
70
+ // should not have to remember which one this surface wanted.
71
+ const conversationId = takeOption(args, "--conversation") ?? takeOption(args, "--conversationId");
58
72
  const limit = takeIntegerOption(args, "--limit", { min: 1 }) ?? 200;
59
73
  rejectArgs(args, USAGE);
60
74
  if (wantsJson && wantsJsonl) throw new CliUsageError("--json and --jsonl cannot be combined", USAGE);
61
75
  if (follow && wantsJson) throw new CliUsageError("--follow uses --jsonl, not --json", USAGE);
62
76
  let seen = new Set<string>();
63
77
  do {
64
- const data = await runtimeRequest(`/api/logs${query({ provider, model, status, limit })}`, {}, deps);
78
+ const data = await runtimeRequest(`/api/logs${query({ provider, model, status, conversationId, limit })}`, {}, deps);
65
79
  const rows = logRows(data);
66
80
  if (!follow && wantsJson) printData(data, true);
67
81
  else {
@@ -79,16 +93,72 @@ async function logs(argv: string[], deps: RuntimeApiDeps): Promise<void> {
79
93
  } while (true);
80
94
  }
81
95
 
96
+ async function explain(argv: string[], deps: RuntimeApiDeps): Promise<void> {
97
+ const args = [...argv];
98
+ const requestId = args.shift();
99
+ const wantsJson = takeFlag(args, "--json");
100
+ if (!requestId) throw new CliUsageError("request id is required", USAGE);
101
+ rejectArgs(args, USAGE);
102
+ const encoded = encodeURIComponent(requestId);
103
+ const result = await runtimeRequest(`/api/request-history/${encoded}/route-decision`, {}, deps);
104
+ printData(result, wantsJson, wantsJson ? undefined : [JSON.stringify(result, null, 2)]);
105
+ }
106
+
107
+ async function rebuildIndex(argv: string[], deps: RuntimeApiDeps): Promise<void> {
108
+ const args = [...argv];
109
+ const wantsJson = takeFlag(args, "--json");
110
+ rejectArgs(args, USAGE);
111
+ const { rebuildRequestHistoryIndex } = await import("../routing/history/indexer");
112
+ const meta = await rebuildRequestHistoryIndex();
113
+ if (wantsJson) printData(meta, true);
114
+ else {
115
+ console.log(`Request-history index rebuilt (${meta.dbPath})`);
116
+ console.log(` schema version: ${meta.schemaVersion}`);
117
+ console.log(` indexed rows: ${meta.indexedRows}`);
118
+ console.log(` source size: ${meta.sourceSize} bytes`);
119
+ console.log(` last error: ${meta.lastError ?? "none"}`);
120
+ }
121
+ }
122
+
123
+ async function indexStatus(argv: string[], deps: RuntimeApiDeps): Promise<void> {
124
+ const args = [...argv];
125
+ const wantsJson = takeFlag(args, "--json");
126
+ rejectArgs(args, USAGE);
127
+ const { requestHistoryIndexStatus } = await import("../routing/history/indexer");
128
+ const meta = await requestHistoryIndexStatus();
129
+ if (wantsJson) printData(meta, true);
130
+ else {
131
+ console.log(`Request-history index (${meta.dbPath})`);
132
+ console.log(` schema version: ${meta.schemaVersion}`);
133
+ console.log(` indexed rows: ${meta.indexedRows}`);
134
+ console.log(` source size: ${meta.sourceSize} bytes`);
135
+ console.log(` indexed offset: ${meta.indexedOffset} bytes`);
136
+ console.log(` last error: ${meta.lastError ?? "none"}`);
137
+ }
138
+ }
139
+
82
140
  async function usage(argv: string[], deps: RuntimeApiDeps): Promise<void> {
83
141
  const args = [...argv];
84
142
  const wantsJson = takeFlag(args, "--json");
85
143
  const range = takeOption(args, "--range") ?? "30d";
86
144
  const surface = takeOption(args, "--surface") ?? "all";
87
- if (!["7d", "30d", "all"].includes(range)) throw new CliUsageError("--range must be 7d, 30d, or all", USAGE);
88
- if (!["all", "codex", "claude", "grok"].includes(surface)) throw new CliUsageError("--surface must be all, codex, claude, or grok", USAGE);
145
+ const provider = takeOption(args, "--provider");
146
+ const model = takeOption(args, "--model");
147
+ // `1d` is accepted here as well as server-side so the CLI does not reject an
148
+ // alias the API would have understood.
149
+ const ranges = [...USAGE_RANGES, "1d"];
150
+ if (!ranges.includes(range)) throw new CliUsageError(`--range must be one of ${USAGE_RANGES.join(", ")} (1d aliases today)`, USAGE);
151
+ if (!USAGE_SURFACES.includes(surface as (typeof USAGE_SURFACES)[number])) {
152
+ throw new CliUsageError(`--surface must be one of ${USAGE_SURFACES.join(", ")}`, USAGE);
153
+ }
89
154
  rejectArgs(args, USAGE);
90
- const result = await runtimeRequest(`/api/usage${query({ range, surface })}`, {}, deps);
91
- printData(result, wantsJson, summaryLines(result));
155
+ const result = await runtimeRequest(`/api/usage${query({ range, surface, provider, model })}`, {}, deps);
156
+ // Built only when it will be printed: JavaScript evaluates arguments before
157
+ // the call, so passing formatUsageReport(...) inline would run the human
158
+ // renderer during --json and let its assumptions affect a path that is meant
159
+ // to bypass it entirely.
160
+ if (wantsJson) printData(result, true);
161
+ else printData(result, false, formatUsageReport(result as Parameters<typeof formatUsageReport>[0]));
92
162
  }
93
163
 
94
164
  async function simple(path: string, argv: string[], deps: RuntimeApiDeps): Promise<void> {
@@ -100,12 +170,54 @@ async function simple(path: string, argv: string[], deps: RuntimeApiDeps): Promi
100
170
  printData(result, wantsJson, summaryLines(result));
101
171
  }
102
172
 
173
+ async function storage(argv: string[], deps: RuntimeApiDeps): Promise<void> {
174
+ if (argv[0] !== "codex-logs") {
175
+ await simple("/api/storage", argv, deps);
176
+ return;
177
+ }
178
+
179
+ const args = argv.slice(1);
180
+ const action = args[0] && !args[0].startsWith("-") ? args.shift()! : "status";
181
+ const wantsJson = takeFlag(args, "--json");
182
+ const mode = takeOption(args, "--mode");
183
+ rejectArgs(args, USAGE);
184
+
185
+ let result: unknown;
186
+ if (action === "status") {
187
+ if (mode !== undefined) throw new CliUsageError("--mode is only valid with codex-logs protect", USAGE);
188
+ result = await runtimeRequest("/api/storage/codex-logs", {}, deps);
189
+ } else if (action === "protect") {
190
+ const requestedMode = mode ?? "compat";
191
+ if (requestedMode !== "compat" && requestedMode !== "quiet") {
192
+ throw new CliUsageError("--mode must be compat or quiet", USAGE);
193
+ }
194
+ result = await runtimeRequest("/api/storage/codex-logs/protect", {
195
+ method: "POST",
196
+ headers: { "content-type": "application/json" },
197
+ body: JSON.stringify({ mode: requestedMode }),
198
+ }, deps);
199
+ } else if (action === "unprotect" || action === "repair" || action === "compact") {
200
+ if (mode !== undefined) throw new CliUsageError("--mode is only valid with codex-logs protect", USAGE);
201
+ result = await runtimeRequest(`/api/storage/codex-logs/${action}`, { method: "POST" }, deps);
202
+ } else {
203
+ throw new CliUsageError(`unknown codex-logs action ${action}`, USAGE);
204
+ }
205
+
206
+ printData(result, wantsJson, summaryLines(result));
207
+ }
208
+
103
209
  export async function handleObserveCommand(argv: string[], deps: RuntimeApiDeps = {}): Promise<number> {
104
210
  return runCliAction(async () => {
105
211
  const [sub = "logs", ...rest] = argv;
106
- if (sub === "logs") await logs(rest, deps);
212
+ if (sub === "logs") {
213
+ const action = rest[0];
214
+ if (action === "explain") await explain(rest.slice(1), deps);
215
+ else if (action === "rebuild-index") await rebuildIndex(rest.slice(1), deps);
216
+ else if (action === "index-status") await indexStatus(rest.slice(1), deps);
217
+ else await logs(rest, deps);
218
+ }
107
219
  else if (sub === "usage") await usage(rest, deps);
108
- else if (sub === "storage") await simple("/api/storage", rest, deps);
220
+ else if (sub === "storage") await storage(rest, deps);
109
221
  else if (sub === "memory") await simple("/api/system/memory", rest, deps);
110
222
  else if (sub === "debug") await simple("/api/debug", rest, deps);
111
223
  else if (sub === "claude-inbound") await simple("/api/claude/inbound-debug", rest, deps);
@@ -40,6 +40,8 @@ import { loadServiceTokenFromFile, serviceApiTokenFilePath } from "../lib/servic
40
40
  import { providerCodexAccountMode } from "../providers/registry";
41
41
  import { findLiveProxy, probeHostname, type LiveProxy } from "../server/proxy-liveness";
42
42
  import type { OcxConfig } from "../types";
43
+ import { withProcessRuntimeProvenance } from "../lib/bun-runtime";
44
+ import { selfLaunchArgv } from "../lib/self-launch-argv";
43
45
 
44
46
  /**
45
47
  * The provider-block serializer, its constants, and the config-path helpers now live in
@@ -493,11 +495,11 @@ async function ensureProxyForOpencode(config: OcxConfig): Promise<LiveProxy | nu
493
495
  if (live) return live;
494
496
  const cfgPort = config.port;
495
497
  const pinPort = typeof cfgPort === "number" && cfgPort > 0 ? cfgPort : 10100;
496
- const child = spawn(process.execPath, [process.argv[1], "start", "--port", String(pinPort)], {
498
+ const child = spawn(process.execPath, selfLaunchArgv(["start", "--port", String(pinPort)]), {
497
499
  detached: true,
498
500
  stdio: "ignore",
499
501
  windowsHide: true,
500
- env: opencodeProxyStartEnv(process.env) as NodeJS.ProcessEnv,
502
+ env: withProcessRuntimeProvenance(opencodeProxyStartEnv(process.env) as NodeJS.ProcessEnv),
501
503
  });
502
504
  // Without a listener an 'error' (bad argv[1], EMFILE, AV denial) throws synchronously
503
505
  // and kills this process; the health poll below already reports the failure properly.
@@ -11,12 +11,20 @@ import {
11
11
  takeOption,
12
12
  type RuntimeApiDeps,
13
13
  } from "./runtime-api";
14
+ import { providerQuotaLine } from "./account-extended";
15
+ import type { ProviderQuotaReportDto } from "./account-api";
16
+
17
+ interface ProviderQuotasDto {
18
+ generatedAt?: number;
19
+ reports?: ProviderQuotaReportDto[];
20
+ }
14
21
 
15
22
  const USAGE = `Usage:
16
23
  ocx provider edit <name> [--adapter <id>] [--base-url <url>] [--default-model <id|->]
17
24
  [--auth-mode <key|forward|oauth|local|->] [--note <text|->]
18
25
  [--api-key-transport <x-api-key|bearer|->]
19
- [--enabled <on|off>] [--live-models <on|off>] [--allow-private-network <on|off>] [--json]
26
+ [--headers <json>] [--enabled <on|off>] [--live-models <on|off>]
27
+ [--allow-private-network <on|off>] [--json]
20
28
  ocx provider test <name> [--json]
21
29
  ocx provider quota [--refresh] [--json]
22
30
  ocx provider presets [--json]
@@ -39,6 +47,7 @@ async function edit(argv: string[], deps: RuntimeApiDeps): Promise<void> {
39
47
  const authMode = cleared(takeOption(args, "--auth-mode"));
40
48
  const note = cleared(takeOption(args, "--note"));
41
49
  const apiKeyTransport = cleared(takeOption(args, "--api-key-transport"));
50
+ const headers = takeOption(args, "--headers");
42
51
  const enabled = takeBooleanOption(args, "--enabled");
43
52
  const liveModels = takeBooleanOption(args, "--live-models");
44
53
  const allowPrivateNetwork = takeBooleanOption(args, "--allow-private-network");
@@ -49,6 +58,21 @@ async function edit(argv: string[], deps: RuntimeApiDeps): Promise<void> {
49
58
  if (authMode !== undefined) patch.authMode = authMode;
50
59
  if (note !== undefined) patch.note = note;
51
60
  if (apiKeyTransport !== undefined) patch.apiKeyTransport = apiKeyTransport;
61
+ if (headers !== undefined) {
62
+ if (headers === "-") {
63
+ patch.headers = null;
64
+ } else {
65
+ let parsed: unknown;
66
+ try { parsed = JSON.parse(headers); } catch { throw new CliUsageError("--headers must be valid JSON"); }
67
+ if (parsed === null) {
68
+ patch.headers = null;
69
+ } else if (typeof parsed !== "object" || Array.isArray(parsed)) {
70
+ throw new CliUsageError("--headers must be a JSON object like {\"X-Custom\":\"value\"}");
71
+ } else {
72
+ patch.headers = parsed;
73
+ }
74
+ }
75
+ }
52
76
  if (enabled !== undefined) patch.disabled = !enabled;
53
77
  if (liveModels !== undefined) patch.liveModels = liveModels;
54
78
  if (allowPrivateNetwork !== undefined) patch.allowPrivateNetwork = allowPrivateNetwork;
@@ -90,8 +114,15 @@ async function quota(argv: string[], deps: RuntimeApiDeps): Promise<void> {
90
114
  const wantsJson = takeFlag(args, "--json");
91
115
  const refresh = takeFlag(args, "--refresh");
92
116
  rejectArgs(args, USAGE);
93
- const result = await runtimeRequest(`/api/provider-quotas${refresh ? "?refresh=1" : ""}`, {}, deps);
94
- printData(result, wantsJson, summaryLines(result));
117
+ const result = await runtimeRequest<ProviderQuotasDto>(`/api/provider-quotas${refresh ? "?refresh=1" : ""}`, {}, deps);
118
+ // `summaryLines` is a depth-1 flattener: it renders a non-scalar array as "N item(s)", which
119
+ // collapsed the whole report to a count and made the command useless for its stated purpose
120
+ // (#2565). Render one line per report with the same formatter `ocx account refresh` uses.
121
+ const reports = Array.isArray(result?.reports) ? result.reports : [];
122
+ const lines = reports.length > 0
123
+ ? reports.map(report => providerQuotaLine(report.provider, report))
124
+ : ["no quota reports available"];
125
+ printData(result, wantsJson, lines);
95
126
  }
96
127
 
97
128
  async function presets(argv: string[], deps: RuntimeApiDeps): Promise<void> {
@@ -8,10 +8,12 @@
8
8
  * show <name> Show provider config details (secrets masked)
9
9
  * set-default <name> Change the default provider
10
10
  */
11
- import { apiKeyTransportConfigError, hasOwnProvider, isValidProviderName, loadConfig, saveConfig } from "../config";
11
+ import { hasOwnProvider, isValidProviderName, loadConfig, sanitizeModelCostsForDisplay, saveConfig } from "../config";
12
+ import { apiKeyTransportConfigError } from "../config/provider-validation";
12
13
  import { hasHelpFlag } from "./help";
13
14
  import { getProviderRegistryEntry, PROVIDER_REGISTRY } from "../providers/registry";
14
15
  import { providerConfigSeed } from "../providers/derive";
16
+ import { dropProviderCustomModels } from "../providers/provider-id-rewrite";
15
17
  import type { OcxProviderConfig } from "../types";
16
18
  import { findLiveProxy } from "../server/proxy-liveness";
17
19
  import { syncModelsToCodex } from "../codex/sync";
@@ -209,7 +211,14 @@ async function handleAdd(args: string[]): Promise<void> {
209
211
  provConfig.apiKeyTransport = apiKeyTransport;
210
212
  }
211
213
 
214
+ const existingProvider = config.providers[name];
212
215
  config.providers[name] = provConfig;
216
+ // A --force overwrite rotates the key/endpoint but must not drop a
217
+ // user-configured price overlay (same rule as the /api/providers path and
218
+ // the login paths); there is no explicit clear/replace flag yet.
219
+ if (existingProvider?.modelCosts !== undefined && provConfig.modelCosts === undefined) {
220
+ provConfig.modelCosts = existingProvider.modelCosts;
221
+ }
213
222
  if (allowPrivateNetwork) provConfig.allowPrivateNetwork = true;
214
223
  if (setDefault) config.defaultProvider = name;
215
224
 
@@ -229,12 +238,18 @@ async function handleAdd(args: string[]): Promise<void> {
229
238
  return;
230
239
  }
231
240
 
241
+ let codexSyncSkipped = false;
232
242
  if (wantsSync) {
233
243
  const live = await findLiveProxy();
234
244
  if (live) {
235
- await syncModelsToCodex(live.port).catch(e => {
245
+ const synced = await syncModelsToCodex(live.port).catch(e => {
236
246
  console.error(`Warning: sync failed: ${e instanceof Error ? e.message : String(e)}`);
247
+ return null;
237
248
  });
249
+ if (synced?.status === "skipped") {
250
+ codexSyncSkipped = true;
251
+ console.log("Provider saved; Codex integration is OFF, so Codex sync was skipped.");
252
+ }
238
253
  }
239
254
  }
240
255
 
@@ -249,7 +264,7 @@ async function handleAdd(args: string[]): Promise<void> {
249
264
  console.log(` Set API key with: ocx provider add ${name} --api-key <key> --force`);
250
265
  console.log(` Or set env var: ${envKey}`);
251
266
  }
252
- if (wantsSync) {
267
+ if (wantsSync && !codexSyncSkipped) {
253
268
  console.log(` Models synced to Codex.`);
254
269
  } else {
255
270
  console.log(` Apply to Codex: ocx sync`);
@@ -296,6 +311,7 @@ function handleRemove(args: string[]): void {
296
311
  }
297
312
 
298
313
  delete config.providers[name];
314
+ const droppedCustomModels = dropProviderCustomModels(config, name);
299
315
  validateAndSave(config);
300
316
 
301
317
 
@@ -306,11 +322,16 @@ function handleRemove(args: string[]): void {
306
322
  remainingProviders: Object.keys(config.providers),
307
323
  defaultProvider: config.defaultProvider,
308
324
  needsSync: true,
325
+ ...(droppedCustomModels > 0 ? { droppedCustomModels } : {}),
309
326
  }, null, 2));
310
327
  return;
311
328
  }
312
329
 
313
330
  console.log(`✅ Provider "${name}" removed.`);
331
+ if (droppedCustomModels > 0) {
332
+ const plural = droppedCustomModels === 1 ? "model" : "models";
333
+ console.log(` Also removed ${droppedCustomModels} custom ${plural} that belonged to it.`);
334
+ }
314
335
  }
315
336
 
316
337
  // ---------------------------------------------------------------------------
@@ -336,6 +357,7 @@ function handleShow(args: string[]): void {
336
357
  const prov = config.providers[name];
337
358
  const display = {
338
359
  ...prov,
360
+ ...(prov.modelCosts !== undefined ? { modelCosts: sanitizeModelCostsForDisplay(prov.modelCosts) } : {}),
339
361
  ...(prov.apiKey ? { apiKey: maskSecret(prov.apiKey) } : {}),
340
362
  ...(prov.apiKeyPool ? { apiKeyPool: prov.apiKeyPool.map(e => ({ ...e, key: maskSecret(e.key) })) } : {}),
341
363
  };