@bitkyc08/opencodex 2.83.0 → 2.86.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 (405) hide show
  1. package/README.md +0 -4
  2. package/bin/ocx.mjs +61 -3
  3. package/gui/dist/assets/App-BjlttVLy.js +58 -0
  4. package/gui/dist/assets/Tray-DyR9yBee.js +1 -0
  5. package/gui/dist/assets/{index-BAbcj51J.css → index-BB6uK6tU.css} +1 -1
  6. package/gui/dist/assets/index-BMssP4k3.js +94 -0
  7. package/gui/dist/assets/quota-summary-D8M1bc_R.js +1 -0
  8. package/gui/dist/index.html +2 -2
  9. package/package.json +2 -3
  10. package/src/adapters/anthropic/beta-allowlist.ts +87 -8
  11. package/src/adapters/anthropic/client-identity.ts +1 -1
  12. package/src/adapters/anthropic/passthrough.ts +33 -4
  13. package/src/adapters/azure.ts +19 -1
  14. package/src/adapters/client-fingerprint.ts +24 -0
  15. package/src/adapters/coding-agent/protocol.ts +15 -8
  16. package/src/adapters/coding-agent/turn.ts +22 -9
  17. package/src/adapters/cursor/arg-codec.ts +17 -5
  18. package/src/adapters/cursor/catalog.ts +8 -0
  19. package/src/adapters/cursor/effort-map.ts +3 -0
  20. package/src/adapters/cursor/freeform-args.ts +75 -0
  21. package/src/adapters/cursor/protobuf-events.ts +36 -23
  22. package/src/adapters/google-antigravity-wire.ts +22 -1
  23. package/src/adapters/google.ts +2 -2
  24. package/src/adapters/ollama-native.ts +29 -1
  25. package/src/adapters/openai-chat/messages.ts +8 -4
  26. package/src/adapters/openai-chat/response-events.ts +59 -1
  27. package/src/adapters/openai-chat.ts +8 -5
  28. package/src/adapters/openai-responses/empty-assistant.ts +32 -0
  29. package/src/adapters/openai-responses/passthrough.ts +3 -0
  30. package/src/adapters/openai-responses/request-strips.ts +10 -2
  31. package/src/adapters/qoder/adapter.ts +56 -15
  32. package/src/advisor/consult.ts +220 -0
  33. package/src/advisor/context.ts +226 -0
  34. package/src/advisor/disclosure.ts +20 -0
  35. package/src/advisor/runtime.ts +305 -0
  36. package/src/advisor/settings.ts +135 -0
  37. package/src/advisor/state.ts +369 -0
  38. package/src/advisor/synthetic-tool.ts +33 -0
  39. package/src/bridge/errors.ts +24 -0
  40. package/src/bridge.ts +1 -1
  41. package/src/chat/inbound.ts +30 -6
  42. package/src/chat/outbound.ts +5 -0
  43. package/src/claude/context-windows.ts +54 -1
  44. package/src/claude/desktop-3p-library.ts +43 -3
  45. package/src/claude/desktop-3p.ts +22 -8
  46. package/src/claude/desktop-picker-profile.ts +11 -4
  47. package/src/claude/desktop-profile.ts +10 -1
  48. package/src/claude/desktop-remote-store-io.ts +5 -3
  49. package/src/claude/inbound.ts +121 -50
  50. package/src/claude/intercept/connect-proxy.ts +10 -3
  51. package/src/claude/intercept/local-ca.ts +8 -1
  52. package/src/claude/intercept/picker-ca-persistence.ts +3 -1
  53. package/src/claude/intercept/picker-listener.ts +49 -1
  54. package/src/claude/intercept/settings-no-proxy.ts +146 -0
  55. package/src/claude/intercept/settings.ts +101 -9
  56. package/src/claude/model-default-settings.ts +154 -0
  57. package/src/claude/model-info.ts +7 -7
  58. package/src/claude/outbound.ts +13 -6
  59. package/src/cli/account-api.ts +65 -0
  60. package/src/cli/account-auth.ts +26 -2
  61. package/src/cli/account.ts +41 -5
  62. package/src/cli/advisor.ts +151 -0
  63. package/src/cli/auxiliary-listener-preflight.ts +37 -0
  64. package/src/cli/capabilities-access-remote.ts +8 -0
  65. package/src/cli/capabilities-agents-routing.ts +18 -0
  66. package/src/cli/capabilities-base.ts +14 -2
  67. package/src/cli/catalog-command-result.ts +2 -1
  68. package/src/cli/chatgpt-command.ts +35 -14
  69. package/src/cli/claude.ts +60 -11
  70. package/src/cli/config-command.ts +8 -2
  71. package/src/cli/debug.ts +8 -0
  72. package/src/cli/dispatch.ts +21 -4
  73. package/src/cli/doctor.ts +9 -0
  74. package/src/cli/ensure-desktop-cold-start.ts +329 -0
  75. package/src/cli/export-command.ts +15 -2
  76. package/src/cli/help.ts +2 -1
  77. package/src/cli/hub-disable.ts +99 -0
  78. package/src/cli/hub.ts +4 -2
  79. package/src/cli/index.ts +26 -8
  80. package/src/cli/integration-plan-dto.ts +2 -0
  81. package/src/cli/link-child.ts +104 -0
  82. package/src/cli/link-enrollment.ts +96 -0
  83. package/src/cli/link.ts +39 -24
  84. package/src/cli/observe-stream.ts +6 -1
  85. package/src/cli/observe.ts +10 -2
  86. package/src/cli/provider.ts +12 -0
  87. package/src/cli/registry.ts +20 -7
  88. package/src/cli/restart-scope.ts +3 -1
  89. package/src/cli/runtime-api.ts +27 -2
  90. package/src/cli/status.ts +104 -20
  91. package/src/client/connect.ts +39 -4
  92. package/src/client/hub-client.ts +14 -7
  93. package/src/client/hub-state.ts +25 -14
  94. package/src/client/link-home.ts +248 -0
  95. package/src/client/link-management-relay.ts +61 -0
  96. package/src/client/link-relay.ts +6 -1
  97. package/src/client/link-status.ts +14 -0
  98. package/src/client/machine-api.ts +14 -7
  99. package/src/client/machine-listener.ts +29 -4
  100. package/src/clients/config-export/contracts.ts +1 -0
  101. package/src/clients/config-export/model-metadata.ts +3 -2
  102. package/src/clients/config-export/qoder.ts +93 -0
  103. package/src/clients/config-export.ts +59 -16
  104. package/src/clients/omo-role-models.ts +6 -3
  105. package/src/codex/agent-role-models.ts +26 -8
  106. package/src/codex/auth-api/account-list.ts +17 -2
  107. package/src/codex/auth-api/account-selection.ts +17 -7
  108. package/src/codex/auth-api/pool-mode-gate.ts +5 -2
  109. package/src/codex/auth-api/routes.ts +5 -1
  110. package/src/codex/auth-context.ts +79 -5
  111. package/src/codex/autostart-health.ts +7 -1
  112. package/src/codex/catalog/build-entries.ts +11 -2
  113. package/src/codex/catalog/bundled.ts +5 -1
  114. package/src/codex/catalog/configured-tiers.ts +50 -0
  115. package/src/codex/catalog/effort.ts +6 -3
  116. package/src/codex/catalog/metadata.ts +4 -2
  117. package/src/codex/catalog/model-hints.ts +21 -3
  118. package/src/codex/catalog/native-models.ts +16 -2
  119. package/src/codex/catalog/native-ultrafast.ts +106 -0
  120. package/src/codex/catalog/parsing.ts +5 -0
  121. package/src/codex/catalog/provider-models.ts +4 -0
  122. package/src/codex/catalog/retained-sync.ts +2 -0
  123. package/src/codex/catalog/routed-gather.ts +22 -4
  124. package/src/codex/catalog/vendor-context.ts +82 -0
  125. package/src/codex/config-write-lock.ts +40 -2
  126. package/src/codex/convergence.ts +2 -0
  127. package/src/codex/desktop-app/types.ts +2 -0
  128. package/src/codex/desktop-app/windows.ts +29 -16
  129. package/src/codex/desktop-app-restart.ts +4 -2
  130. package/src/codex/desktop-compatibility/certificate-service.ts +99 -0
  131. package/src/codex/desktop-compatibility/certificate-store.ts +193 -0
  132. package/src/codex/desktop-compatibility/connection-store.ts +87 -0
  133. package/src/codex/desktop-compatibility/installed-build.ts +55 -0
  134. package/src/codex/desktop-compatibility/json-body.ts +29 -0
  135. package/src/codex/desktop-compatibility/native-identity.ts +62 -0
  136. package/src/codex/desktop-compatibility/relay-listener.ts +93 -0
  137. package/src/codex/desktop-compatibility/routing-binding.ts +10 -0
  138. package/src/codex/desktop-compatibility/routing-preflight.ts +69 -0
  139. package/src/codex/desktop-compatibility/runtime-ownership.ts +28 -0
  140. package/src/codex/desktop-compatibility/runtime.ts +173 -0
  141. package/src/codex/desktop-compatibility/service.ts +30 -0
  142. package/src/codex/desktop-compatibility/startup-settings.ts +50 -0
  143. package/src/codex/desktop-compatibility/usage-activation.ts +86 -0
  144. package/src/codex/desktop-compatibility/usage-controlled-fetch.ts +59 -0
  145. package/src/codex/desktop-compatibility/usage-controller.ts +107 -0
  146. package/src/codex/desktop-compatibility/usage-policy.ts +59 -0
  147. package/src/codex/desktop-compatibility/usage-refresh.ts +33 -0
  148. package/src/codex/desktop-compatibility/usage-sse-controller.ts +86 -0
  149. package/src/codex/desktop-compatibility/windows-activation-source.ts +44 -0
  150. package/src/codex/desktop-compatibility/windows-certificate-trust.ts +81 -0
  151. package/src/codex/desktop-compatibility/windows-key-protection.ts +59 -0
  152. package/src/codex/desktop-compatibility/windows-package-command.ts +86 -0
  153. package/src/codex/desktop-compatibility/windows-package-launch.ts +28 -0
  154. package/src/codex/history-provider.ts +1 -1
  155. package/src/codex/inject/config-write-section.ts +4 -1
  156. package/src/codex/inject/remove.ts +18 -6
  157. package/src/codex/journal.ts +16 -1
  158. package/src/codex/main-account.ts +99 -15
  159. package/src/codex/native-main-refresh-mode.ts +217 -0
  160. package/src/codex/native-profile-startup.ts +4 -1
  161. package/src/codex/prompt-journal.ts +20 -2
  162. package/src/codex/prompt-layers/encoding.ts +49 -0
  163. package/src/codex/prompt-layers/import-source.ts +30 -0
  164. package/src/codex/prompt-layers/toml-edit.ts +269 -37
  165. package/src/codex/prompt-layers/toml-read.ts +52 -4
  166. package/src/codex/prompt-layers.ts +383 -20
  167. package/src/codex/prompt-lock-claim.ts +13 -3
  168. package/src/codex/prompt-lock-evidence.ts +3 -3
  169. package/src/codex/prompt-lock-owner.ts +14 -6
  170. package/src/codex/prompt-text-probe.ts +34 -5
  171. package/src/codex/quota.ts +13 -6
  172. package/src/codex/role-auto-assign.ts +2 -5
  173. package/src/codex/routing/active-account.ts +21 -0
  174. package/src/codex/routing/health-store.ts +1 -1
  175. package/src/codex/routing/pool-recovery.ts +79 -0
  176. package/src/codex/routing.ts +1 -1
  177. package/src/codex/runtime.ts +196 -8
  178. package/src/codex/shim-diagnostics.ts +6 -0
  179. package/src/codex/shim-overlay.ts +15 -6
  180. package/src/codex/shim.ts +1 -1
  181. package/src/combos/failover.ts +3 -0
  182. package/src/config/api-key-reconcile.ts +34 -0
  183. package/src/config/diagnostics.ts +18 -1
  184. package/src/config/feature-flags.ts +5 -0
  185. package/src/config/live-reconcile.ts +56 -29
  186. package/src/config/live-topology.ts +29 -0
  187. package/src/config/load-degrade.ts +8 -2
  188. package/src/config/paths.ts +13 -3
  189. package/src/config/salvage.ts +3 -1
  190. package/src/config/schema/config-schema.ts +19 -0
  191. package/src/config/schema/desktop-compatibility.ts +9 -0
  192. package/src/config/schema/leaf-validators.ts +1 -0
  193. package/src/generated/compatibility-version.json +713 -285
  194. package/src/generated/model-metadata.ts +1 -1
  195. package/src/grok/grpc-web.ts +23 -1
  196. package/src/grok/reset-coupon-ledger.ts +75 -5
  197. package/src/grok/reset-coupons.ts +4 -2
  198. package/src/integrations/catalog-refresh.ts +1 -1
  199. package/src/integrations/config-io.ts +20 -2
  200. package/src/integrations/journal.ts +15 -1
  201. package/src/integrations/mutation-plan.ts +14 -7
  202. package/src/integrations/registry.ts +52 -11
  203. package/src/integrations/state.ts +19 -7
  204. package/src/integrations/store.ts +3 -0
  205. package/src/integrations/target.ts +42 -5
  206. package/src/integrations/writer.ts +24 -7
  207. package/src/lib/advisor-activation.ts +16 -0
  208. package/src/lib/app-owned-memory-stores.ts +4 -0
  209. package/src/lib/app-owned-memory.ts +11 -0
  210. package/src/lib/background-work.ts +65 -0
  211. package/src/lib/bounded-file-read.ts +58 -0
  212. package/src/lib/bun-path-runtime.d.mts +10 -1
  213. package/src/lib/bun-path-runtime.mjs +62 -3
  214. package/src/lib/desktop-proxy-route.ts +30 -0
  215. package/src/lib/desktop-upstream-tunnel.ts +107 -0
  216. package/src/lib/errors.ts +7 -0
  217. package/src/lib/json-byte-size.ts +31 -0
  218. package/src/lib/local-internal-call-capability.ts +55 -0
  219. package/src/lib/optional-shutdown-hooks.ts +35 -0
  220. package/src/lib/outbound-credential-registry.ts +210 -0
  221. package/src/lib/package-tree-integrity.ts +2 -2
  222. package/src/lib/passive-read.ts +35 -0
  223. package/src/lib/shadow-call.ts +44 -5
  224. package/src/lib/socks5-fetch.ts +48 -71
  225. package/src/lib/socks5-handshake.ts +112 -0
  226. package/src/lib/translator-budget.ts +7 -1
  227. package/src/link/codex-settings.ts +155 -0
  228. package/src/link/management-routes.ts +89 -0
  229. package/src/link/remote-probe.ts +38 -0
  230. package/src/link/ssh-argv.ts +26 -5
  231. package/src/link/ssh-runner.ts +58 -27
  232. package/src/link/status-projection.ts +22 -4
  233. package/src/link/supervisor.ts +144 -49
  234. package/src/link/tunnel-state.ts +11 -4
  235. package/src/oauth/anthropic-continuity.ts +4 -3
  236. package/src/oauth/anthropic-identity.ts +13 -2
  237. package/src/oauth/anthropic-model-quota.ts +13 -1
  238. package/src/oauth/anthropic-routing.ts +230 -23
  239. package/src/oauth/anthropic.ts +18 -4
  240. package/src/oauth/generic-account-failover.ts +29 -5
  241. package/src/oauth/google-antigravity.ts +24 -7
  242. package/src/oauth/index.ts +28 -17
  243. package/src/oauth/kiro-account-identity.ts +106 -0
  244. package/src/oauth/kiro-device-login.ts +2 -1
  245. package/src/oauth/kiro.ts +55 -6
  246. package/src/oauth/plan.ts +29 -0
  247. package/src/oauth/store.ts +40 -3
  248. package/src/oauth/types.ts +2 -0
  249. package/src/plugins/loader.ts +11 -0
  250. package/src/protocols/codecs/chat-ir.ts +467 -0
  251. package/src/protocols/codecs/chat.ts +10 -1
  252. package/src/protocols/codecs/messages-ir.ts +500 -0
  253. package/src/protocols/features.ts +10 -2
  254. package/src/protocols/path.ts +11 -1
  255. package/src/protocols/plan-snapshot.ts +39 -16
  256. package/src/protocols/plan.ts +9 -1
  257. package/src/protocols/settings.ts +8 -0
  258. package/src/providers/anthropic-billing-ownership.ts +74 -0
  259. package/src/providers/anthropic-billing-recovery.ts +31 -0
  260. package/src/providers/anthropic-billing-refusal.ts +55 -0
  261. package/src/providers/anthropic-reset-admission.ts +62 -0
  262. package/src/providers/anthropic-reset-grant-ledger.ts +28 -6
  263. package/src/providers/antigravity-account-roster.ts +102 -0
  264. package/src/providers/antigravity-models.ts +85 -7
  265. package/src/providers/api-key-resolve.ts +17 -2
  266. package/src/providers/key-failover.ts +52 -5
  267. package/src/providers/model-rename-fields.ts +2 -0
  268. package/src/providers/model-rename-migration.ts +5 -3
  269. package/src/providers/model-rename-startup.ts +5 -3
  270. package/src/providers/quota/account-cache.ts +11 -0
  271. package/src/providers/quota/anthropic-account-quota.ts +4 -2
  272. package/src/providers/quota/report-cache.ts +14 -0
  273. package/src/providers/quota/vendor-probes-oauth.ts +3 -3
  274. package/src/providers/quota.ts +4 -1
  275. package/src/providers/registry/entries-core.ts +21 -20
  276. package/src/providers/registry/entries-extended.ts +19 -1
  277. package/src/providers/registry/model-seeds.ts +17 -0
  278. package/src/providers/request-pacing.ts +9 -0
  279. package/src/providers/stale-no-vision-seed-migration.ts +72 -0
  280. package/src/responses/compaction.ts +14 -0
  281. package/src/responses/parser.ts +37 -20
  282. package/src/responses/state.ts +32 -29
  283. package/src/routing/history/build-file.ts +92 -0
  284. package/src/routing/history/indexer.ts +248 -216
  285. package/src/routing/history/ingest.ts +238 -0
  286. package/src/routing/history/rebuild-job.ts +111 -0
  287. package/src/routing/history/rebuild-protocol.ts +15 -0
  288. package/src/routing/history/rebuild-worker.ts +34 -0
  289. package/src/server/admission-model-scope.ts +4 -1
  290. package/src/server/antigravity-direct-eligibility.ts +208 -0
  291. package/src/server/auth-cors.ts +46 -0
  292. package/src/server/chat-completions.ts +75 -9
  293. package/src/server/claude-messages.ts +110 -20
  294. package/src/server/configured-credentials.ts +194 -0
  295. package/src/server/index/desktop-compatibility-startup.ts +56 -0
  296. package/src/server/index/link-management.ts +58 -0
  297. package/src/server/index/optional-listeners.ts +5 -1
  298. package/src/server/index/serve-options.ts +15 -2
  299. package/src/server/index/startup-warnings.ts +9 -0
  300. package/src/server/index/websocket-handler.ts +9 -0
  301. package/src/server/index.ts +13 -10
  302. package/src/server/inference/client-encoder-delivery.ts +6 -5
  303. package/src/server/lifecycle.ts +2 -1
  304. package/src/server/login-shell-path.ts +146 -0
  305. package/src/server/management/advisor-routes.ts +191 -0
  306. package/src/server/management/agent-settings-routes.ts +50 -14
  307. package/src/server/management/anthropic-reset-grant-routes.ts +17 -1
  308. package/src/server/management/codex-agent-role-routes.ts +19 -6
  309. package/src/server/management/codex-prompt-routes.ts +136 -5
  310. package/src/server/management/codex-role-auto-assign.ts +1 -1
  311. package/src/server/management/companion-routes.ts +4 -0
  312. package/src/server/management/config-routes.ts +2 -2
  313. package/src/server/management/context.ts +4 -0
  314. package/src/server/management/desktop-compatibility-routes.ts +46 -0
  315. package/src/server/management/desktop-compatibility-runtime-routes.ts +46 -0
  316. package/src/server/management/desktop-compatibility-settings-routes.ts +27 -0
  317. package/src/server/management/grok-coupon-routes.ts +78 -27
  318. package/src/server/management/link-enrollment.ts +192 -0
  319. package/src/server/management/link-route-state.ts +185 -0
  320. package/src/server/management/link-routes.ts +82 -402
  321. package/src/server/management/model-routes.ts +2 -2
  322. package/src/server/management/model-rows.ts +3 -1
  323. package/src/server/management/protocol-settings-patch.ts +1 -0
  324. package/src/server/management/provider-overwrite-carry.ts +10 -0
  325. package/src/server/management/provider-routes.ts +1 -0
  326. package/src/server/management/route-registry.ts +16 -3
  327. package/src/server/management/shared.ts +3 -0
  328. package/src/server/management/sibling-guard.ts +1 -0
  329. package/src/server/management-api.ts +9 -1
  330. package/src/server/messages-antigravity-direct.ts +195 -0
  331. package/src/server/messages-native.ts +73 -43
  332. package/src/server/relay-eager.ts +2 -0
  333. package/src/server/request-log-filter.ts +6 -1
  334. package/src/server/request-log-terminal-status.ts +63 -6
  335. package/src/server/request-log.ts +7 -0
  336. package/src/server/responses/adapter-continuation.ts +35 -18
  337. package/src/server/responses/adapter-delivery.ts +24 -2
  338. package/src/server/responses/adapter-dispatch.ts +261 -81
  339. package/src/server/responses/advisor-plan-slot.ts +27 -0
  340. package/src/server/responses/advisor-slot.ts +336 -0
  341. package/src/server/responses/code-mode-collab-surface.ts +313 -0
  342. package/src/server/responses/codex-ws-exchange.ts +147 -6
  343. package/src/server/responses/codex-ws-wire.ts +203 -0
  344. package/src/server/responses/collaboration.ts +36 -7
  345. package/src/server/responses/compact.ts +126 -48
  346. package/src/server/responses/context-overflow.ts +13 -1
  347. package/src/server/responses/core-codex-account.ts +105 -1
  348. package/src/server/responses/core-combo-failure.ts +24 -1
  349. package/src/server/responses/core-combo.ts +28 -2
  350. package/src/server/responses/core-errors.ts +29 -1
  351. package/src/server/responses/core-lifetime.ts +2 -0
  352. package/src/server/responses/core-normalize.ts +11 -0
  353. package/src/server/responses/core-options.ts +48 -0
  354. package/src/server/responses/core-replay.ts +7 -4
  355. package/src/server/responses/core.ts +1 -1
  356. package/src/server/responses/deferred-collab-surface.ts +158 -0
  357. package/src/server/responses/deferred-send-reporter.ts +25 -0
  358. package/src/server/responses/fetch-helpers.ts +12 -1
  359. package/src/server/responses/passthrough-delivery.ts +20 -11
  360. package/src/server/responses/passthrough-dispatch.ts +29 -7
  361. package/src/server/responses/request-prepare.ts +74 -9
  362. package/src/server/responses/request-transport.ts +22 -8
  363. package/src/server/responses/run-turn-execution.ts +70 -8
  364. package/src/server/responses/sidecar-execution.ts +89 -5
  365. package/src/server/responses/terminal-guard.ts +2 -1
  366. package/src/server/responses/ws-upstream.ts +9 -1
  367. package/src/server/responses-undeclared-tool-guard.ts +30 -9
  368. package/src/server/startup-action-control.ts +38 -4
  369. package/src/server/startup-health-cache.ts +5 -0
  370. package/src/server/system-env-shell.ts +7 -2
  371. package/src/server/system-env.ts +11 -1
  372. package/src/service/cli.ts +5 -1
  373. package/src/service/desktop-startup.ts +161 -34
  374. package/src/service/desktop-supervision.d.mts +27 -1
  375. package/src/service/desktop-supervision.mjs +111 -5
  376. package/src/service/health-sentinel-launch.ts +176 -0
  377. package/src/service/health-sentinel-main.ts +466 -0
  378. package/src/service/health-sentinel-policy.ts +241 -0
  379. package/src/service/health-sentinel-probe.ts +71 -0
  380. package/src/service/health-sentinel-windows.ts +87 -0
  381. package/src/service/linux-linger.ts +20 -0
  382. package/src/service/logging.ts +72 -0
  383. package/src/tray/windows-tray.ps1 +43 -24
  384. package/src/types/config.ts +68 -6
  385. package/src/types/provider.ts +7 -0
  386. package/src/types/request.ts +15 -0
  387. package/src/types/tools.ts +2 -0
  388. package/src/update/async-check.ts +1 -1
  389. package/src/update/badge.ts +2 -2
  390. package/src/update/index.ts +88 -15
  391. package/src/update/job-supervised-restart.ts +107 -0
  392. package/src/update/job.ts +32 -32
  393. package/src/update/notify.ts +1 -1
  394. package/src/update/refresh-scheduler.ts +2 -2
  395. package/src/update/supervised-recovery.d.mts +27 -0
  396. package/src/update/supervised-recovery.mjs +157 -0
  397. package/src/usage/cache-diagnostic.ts +2 -0
  398. package/src/usage/expected-prices.ts +16 -0
  399. package/src/usage/log.ts +19 -1
  400. package/src/web-search/gemini-executor.ts +2 -2
  401. package/assets/sponsors/packycode.png +0 -0
  402. package/gui/dist/assets/App-Ba8uG696.js +0 -54
  403. package/gui/dist/assets/Tray-IImbNPRy.js +0 -1
  404. package/gui/dist/assets/index-BHRZdJuv.js +0 -94
  405. package/gui/dist/assets/quota-summary-DiJO6sYY.js +0 -1
@@ -39,17 +39,25 @@ function defaultEntryFactory(service: string, account: string): ProviderKeychain
39
39
  let entryFactory: ProviderKeychainEntryFactory = defaultEntryFactory;
40
40
  const resolvedCache = new Map<string, string>();
41
41
  const warnedAccounts = new Set<string>();
42
+ let resolutionGeneration = 0;
43
+
44
+ /** Advances whenever cached secrets may have changed, so derived snapshots can rebuild. */
45
+ export function providerKeyResolutionGeneration(): number {
46
+ return resolutionGeneration;
47
+ }
42
48
 
43
49
  /** Test seam: swap the OS entry for an in-memory one and drop caches. */
44
50
  export function setProviderKeychainEntryFactoryForTests(factory: ProviderKeychainEntryFactory | null): void {
45
51
  entryFactory = factory ?? defaultEntryFactory;
46
52
  resolvedCache.clear();
53
+ resolutionGeneration++;
47
54
  warnedAccounts.clear();
48
55
  }
49
56
 
50
57
  /** Write-path seam: a store/restore mutated secrets, so cached reads and warnings are stale. */
51
58
  export function invalidateResolvedProviderKeyCache(): void {
52
59
  resolvedCache.clear();
60
+ resolutionGeneration++;
53
61
  warnedAccounts.clear();
54
62
  }
55
63
 
@@ -76,7 +84,7 @@ export function providerKeychainEntry(account: string): ProviderKeychainEntry {
76
84
  return entryFactory(PROVIDER_KEYCHAIN_SERVICE, account);
77
85
  }
78
86
 
79
- function readKeychain(account: string): string | undefined {
87
+ function readKeychain(account: string, quiet = false): string | undefined {
80
88
  const cached = resolvedCache.get(account);
81
89
  if (cached !== undefined) return cached;
82
90
  try {
@@ -88,7 +96,7 @@ function readKeychain(account: string): string | undefined {
88
96
  } catch {
89
97
  // fall through to the single warning below
90
98
  }
91
- if (!warnedAccounts.has(account)) {
99
+ if (!quiet && !warnedAccounts.has(account)) {
92
100
  warnedAccounts.add(account);
93
101
  console.warn(`[opencodex] provider key reference keychain:${account} could not be read from the OS keychain; requests for this provider have no credential until the keychain is available (no plaintext fallback)`);
94
102
  }
@@ -105,6 +113,13 @@ export function resolveProviderApiKey(value: string | undefined): string | undef
105
113
  return resolveEnvValue(value);
106
114
  }
107
115
 
116
+ /** The same resolution without the unreadable-reference warning, for observe-only callers. */
117
+ export function resolveProviderApiKeyQuietly(value: string | undefined): string | undefined {
118
+ if (!value) return undefined;
119
+ if (isKeychainReference(value)) return readKeychain(keychainAccount(value), true);
120
+ return resolveEnvValue(value);
121
+ }
122
+
108
123
  export type ProviderKeyStoreKind = "keychain" | "env" | "file" | "none";
109
124
 
110
125
  export function providerKeyStoreKind(provider: Pick<OcxProviderConfig, "apiKey"> | undefined): ProviderKeyStoreKind {
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * Modelled after src/codex/routing.ts cooldown logic but scoped to plain API-key pools.
10
10
  */
11
- import { commitProviderApiKeySelection } from "./api-key-selection";
11
+ import { commitProviderApiKeySelection, providerApiKeySelectionIsCurrent } from "./api-key-selection";
12
12
  import type { ProviderApiKeySelection } from "../types/provider";
13
13
  import { routedProviderConfig } from "../router";
14
14
  import { getProviderRegistryEntry } from "./registry";
@@ -30,6 +30,16 @@ interface KeyCooldown {
30
30
 
31
31
  const DEFAULT_COOLDOWN_MS = 60_000;
32
32
  const MAX_COOLDOWN_MS = 10 * 60_000; // cap at 10 min for api-key rotation
33
+ /**
34
+ * Anthropic credit-balance refusal (#6958). An empty prepaid balance is account state, not a
35
+ * rate signal: it clears only when someone buys credits or auto-reload fires, so the
36
+ * ten-minute credential cap would hand a dead key back to rotation six times an hour. One hour
37
+ * keeps it out of replacement and proactive selection while a funded sibling exists, and still
38
+ * notices a top-up without a restart. When every key is held, requests keep using the active
39
+ * key and return Anthropic's original refusal. The hold is in memory; dashboard key management
40
+ * clears it.
41
+ */
42
+ export const ANTHROPIC_BILLING_COOLDOWN_MS = 60 * 60_000;
33
43
 
34
44
  /**
35
45
  * Cap for a cooldown the upstream itself dated, as opposed to one we inferred.
@@ -688,12 +698,13 @@ export function rateLimitRetryDelayMs(
688
698
  function rotateKeyAfterFailure(
689
699
  config: OcxConfig,
690
700
  providerName: string,
691
- failureStatus: 401 | 429,
701
+ failureStatus: 401 | 429 | "billing",
692
702
  retryAfterHeader: string | null | undefined,
693
703
  now = Date.now(),
694
704
  attemptedKey?: string,
695
705
  attemptedSelection?: ProviderApiKeySelection,
696
706
  quotaResetAt?: number,
707
+ acceptsFreshProvider?: (provider: OcxProviderConfig) => boolean,
697
708
  ): OcxProviderConfig | null {
698
709
  const provider = config.providers[providerName];
699
710
  if (!provider) return null;
@@ -704,6 +715,11 @@ function rotateKeyAfterFailure(
704
715
  | { failedId?: string; candidateId?: string }
705
716
  | { exhaustedCount: number; failedId?: string };
706
717
  const outcome = commitProviderApiKeySelection<Rotation | null>(config, providerName, freshProvider => {
718
+ // Run on every persisted transaction attempt, including a freshness rebase.
719
+ if (acceptsFreshProvider) {
720
+ try { if (!acceptsFreshProvider(freshProvider)) return { changed: false, value: null }; }
721
+ catch { return { changed: false, value: null }; }
722
+ }
707
723
  const pool = freshProvider.apiKeyPool;
708
724
  if (!pool || pool.length < 2) return { changed: false, value: null };
709
725
 
@@ -741,6 +757,8 @@ function rotateKeyAfterFailure(
741
757
  }, attemptedSelection);
742
758
  if (outcome.status === "unavailable") return null;
743
759
  if (outcome.status === "superseded") {
760
+ // Billing authority is bound to the sent selection; a newer manual choice grants no replay.
761
+ if (acceptsFreshProvider) return null;
744
762
  // A newer manual selection (including A→B→A) owns subsequent dispatch. Reusing the
745
763
  // same failed key here would loop forever; preserve its original failure instead.
746
764
  return outcome.provider.apiKey !== failedKey ? structuredClone(outcome.provider) : null;
@@ -750,7 +768,9 @@ function rotateKeyAfterFailure(
750
768
  // A 401 is a verdict about the credential itself, not a timing signal: the key is rejected
751
769
  // until an operator replaces it, and upstreams send no Retry-After for it. Hold it for the
752
770
  // full cap instead of the 429 default so a dead key is not re-tried once a minute.
753
- const cooldownMs = failureStatus === 401
771
+ const cooldownMs = failureStatus === "billing"
772
+ ? ANTHROPIC_BILLING_COOLDOWN_MS
773
+ : failureStatus === 401
754
774
  ? MAX_COOLDOWN_MS
755
775
  // A reset instant the upstream dated outranks both the header and the
756
776
  // default: it is the only one of the three that knows when the quota
@@ -762,7 +782,7 @@ function rotateKeyAfterFailure(
762
782
  sweepExpiredOnWrite(now);
763
783
  }
764
784
  if ("exhaustedCount" in outcome.value) {
765
- console.warn(`[key-failover] ${providerName}: all ${outcome.value.exhaustedCount} keys in cooldown after ${failureStatus}; returning the upstream status to the client`);
785
+ console.warn(`[key-failover] ${providerName}: all ${outcome.value.exhaustedCount} keys in cooldown after ${failureLabel(failureStatus)}; returning the upstream status to the client`);
766
786
  return null;
767
787
  }
768
788
 
@@ -771,12 +791,16 @@ function rotateKeyAfterFailure(
771
791
  if (outcome.value.candidateId) {
772
792
  console.warn(
773
793
  // Log ids only — labels are user-supplied free text and could carry secret material.
774
- `[key-failover] ${providerName}: ${failureStatus} on key ${outcome.value.failedId ?? "?"}; rotating to key ${outcome.value.candidateId}`,
794
+ `[key-failover] ${providerName}: ${failureLabel(failureStatus)} on key ${outcome.value.failedId ?? "?"}; rotating to key ${outcome.value.candidateId}`,
775
795
  );
776
796
  }
777
797
  return structuredClone(committed);
778
798
  }
779
799
 
800
+ function failureLabel(failureStatus: 401 | 429 | "billing"): string {
801
+ return failureStatus === "billing" ? "credit-balance refusal" : String(failureStatus);
802
+ }
803
+
780
804
  export function rotateKeyOn429(
781
805
  config: OcxConfig,
782
806
  providerName: string,
@@ -865,6 +889,29 @@ export function rotateProviderTransportOn401(
865
889
  return applyRotatedTransport(providerName, routedProvider, rotated, options.promptCacheKey);
866
890
  }
867
891
 
892
+ /** Exact Anthropic billing refusal: cool the key for ANTHROPIC_BILLING_COOLDOWN_MS. */
893
+ export function rotateProviderTransportOnAnthropicBilling(
894
+ config: OcxConfig,
895
+ providerName: string,
896
+ routedProvider: OcxProviderTransport,
897
+ options: Omit<RotateProviderTransportOptions, "retryAfter"> = {},
898
+ ): OcxProviderTransport | null {
899
+ if (routedProvider.adapter !== "anthropic"
900
+ || (routedProvider.authMode !== undefined && routedProvider.authMode !== "key")
901
+ || !hasKeyPoolFailover(routedProvider)) return null;
902
+ const headerShape = (provider: OcxProviderConfig) => JSON.stringify(Object.entries(provider.headers ?? {}).sort(([a], [b]) => a.localeCompare(b)));
903
+ const sentHeaders = headerShape(routedProvider);
904
+ const acceptsFreshProvider = (fresh: OcxProviderConfig): boolean => fresh.adapter === routedProvider.adapter
905
+ && (fresh.authMode === undefined || fresh.authMode === "key") && hasKeyPoolFailover(fresh)
906
+ && (fresh.apiKeyTransport ?? "x-api-key") === (routedProvider.apiKeyTransport ?? "x-api-key")
907
+ && headerShape(fresh) === sentHeaders
908
+ && providerApiKeySelectionIsCurrent({ ...config, providers: { ...config.providers, [providerName]: fresh } },
909
+ providerName, routedProvider);
910
+ const rotated = rotateKeyAfterFailure(config, providerName, "billing", null, options.now,
911
+ options.attemptedKey, options.attemptedSelection ?? routedProvider._apiKeyAttempt, undefined, acceptsFreshProvider);
912
+ return rotated ? applyRotatedTransport(providerName, routedProvider, rotated, options.promptCacheKey) : null;
913
+ }
914
+
868
915
  function applyRotatedTransport(
869
916
  providerName: string,
870
917
  routedProvider: OcxProviderTransport,
@@ -59,6 +59,7 @@ export const PROVIDER_MODEL_RENAME_ROLES = {
59
59
  newModelPolicy: "none",
60
60
  modelPreset: "none",
61
61
  contextWindow: "none",
62
+ inferContextWindow: "none",
62
63
  modelContextWindows: "record",
63
64
  modelContextTiers: "record",
64
65
  modelInputModalities: "record",
@@ -66,6 +67,7 @@ export const PROVIDER_MODEL_RENAME_ROLES = {
66
67
  modelMaxInputTokens: "record",
67
68
  modelAutoCompactTokenLimits: "record",
68
69
  defaultMaxOutputTokens: "none",
70
+ minMaxOutputTokens: "none",
69
71
  modelMaxOutputTokens: "record",
70
72
  modelCosts: "record",
71
73
  autoReviewModel: "none",
@@ -124,16 +124,18 @@ export const MODEL_RENAMES: readonly ModelRename[] = [
124
124
  // Assist almost immediately when the next ships, so a saved 3.6 (or older 3.5) id is a
125
125
  // dead selection rather than a merely outdated one. Routing already redirects these ids
126
126
  // at request time; this migration repairs the saved config so the picker, the allowlist
127
- // and the capability maps stop naming a model the backend no longer serves.
127
+ // and the capability maps stop naming a model the backend no longer serves. The target is
128
+ // the live 3.8 base: 3.7 left the static lineup on 2026-10-14 (CCA 404s its wire id), and
129
+ // registrySeedsTarget refuses to write an id the registry no longer seeds.
128
130
  ...(["gemini-3.6-flash", "gemini-3.6-flash-low", "gemini-3.6-flash-medium", "gemini-3.6-flash-high",
129
131
  "gemini-3.5-flash-extra-low", "gemini-3.5-flash-low", "gemini-3.5-flash-mid", "gemini-3.5-flash-high",
130
132
  "gemini-3-flash-agent"] as const).map(from => ({
131
133
  provider: "google-antigravity",
132
134
  from,
133
- to: "gemini-3.7-flash",
135
+ to: "gemini-3.8-flash",
134
136
  reason: "Google retires the previous Antigravity Flash generation from Cloud Code Assist when its successor ships, so the saved id no longer resolves to a live model",
135
137
  // The retired Flash tiers were wire ids, so any saved per-model record keyed by one
136
- // may also hold one as a value. 3.7 expresses tiers as thinkingLevel names instead.
138
+ // may also hold one as a value. 3.8 resolves tiers from its own suffix ladder instead.
137
139
  dropReasoningEffortMap: true,
138
140
  })),
139
141
  ];
@@ -3,6 +3,7 @@ import { projectModelRenames } from "./model-rename-migration";
3
3
  import { projectStaleContextWindows } from "./stale-context-window-migration";
4
4
  import { projectStaleModelRosters } from "./stale-model-roster-migration";
5
5
  import { projectStaleVisionClassifications } from "./stale-vision-classification-migration";
6
+ import { projectStaleNoVisionSeeds } from "./stale-no-vision-seed-migration";
6
7
  import { projectDevinCliAuthMode } from "./devin-cli-authmode-migration";
7
8
  import type { OcxConfig } from "../types";
8
9
 
@@ -17,12 +18,13 @@ export function projectStartupConfigRepairs(config: OcxConfig): ReturnType<typeo
17
18
  const renames = projectModelRenames(config);
18
19
  const windows = projectStaleContextWindows(renames.config);
19
20
  const vision = projectStaleVisionClassifications(windows.config);
20
- const rosters = projectStaleModelRosters(vision.config);
21
+ const noVision = projectStaleNoVisionSeeds(vision.config);
22
+ const rosters = projectStaleModelRosters(noVision.config);
21
23
  const devinCli = projectDevinCliAuthMode(rosters.config);
22
24
  return {
23
25
  config: devinCli.config,
24
- changed: renames.changed || windows.changed || vision.changed || rosters.changed || devinCli.changed,
25
- warnings: [...renames.warnings, ...windows.warnings, ...vision.warnings, ...rosters.warnings, ...devinCli.warnings],
26
+ changed: renames.changed || windows.changed || vision.changed || noVision.changed || rosters.changed || devinCli.changed,
27
+ warnings: [...renames.warnings, ...windows.warnings, ...vision.warnings, ...noVision.warnings, ...rosters.warnings, ...devinCli.warnings],
26
28
  };
27
29
  }
28
30
 
@@ -473,6 +473,17 @@ export function clearAccountQuotaCache(provider?: string): void {
473
473
  persistAccountQuotaCache();
474
474
  }
475
475
 
476
+ /** Drop one account's cached bars and any joined probe so a reset is not ranked from the old window. */
477
+ export function forgetCachedProviderAccountQuota(provider: string, accountId: string): void {
478
+ hydrateAccountQuotaCache();
479
+ const key = accountCacheKey(provider, accountId);
480
+ accountQuotaCache.delete(key);
481
+ for (const flight of [...accountQuotaInflight.keys()]) {
482
+ if (flight === key || flight.startsWith(key + "\u0000")) accountQuotaInflight.delete(flight);
483
+ }
484
+ persistAccountQuotaCache();
485
+ }
486
+
476
487
  /**
477
488
  * Resolve a bearer for quota probing without silently adopting a newer global
478
489
  * Claude CLI credential into a background multiauth slot.
@@ -20,6 +20,8 @@ import {
20
20
  */
21
21
  export async function fetchAnthropicAccountQuota(
22
22
  instance: AnthropicInstanceId, accountId: string, force: boolean, config: OcxConfig = loadConfig(),
23
+ /** Never join an older account or vendor flight; a reset reread needs post-grant evidence. */
24
+ freshDispatch = false,
23
25
  ): Promise<AccountQuotaCacheEntry> {
24
26
  const unavailable = (): AccountQuotaCacheEntry => ({ ts: Date.now(), quota: null, unavailable: true });
25
27
  const target = config.providers[instance];
@@ -52,7 +54,7 @@ export async function fetchAnthropicAccountQuota(
52
54
  const flightKey = recovery.anthropicCredentialQuotaFlightKey(key, accountId);
53
55
  const flightCurrent = () => ownerCurrent()
54
56
  && recovery.anthropicCredentialQuotaFlightKey(key, accountId) === flightKey;
55
- const joinable = accountQuotaInflight.get(flightKey);
57
+ const joinable = freshDispatch ? undefined : accountQuotaInflight.get(flightKey);
56
58
  if (joinable) {
57
59
  const joined = await joinable;
58
60
  return ownerCurrent() && (joined.isCurrent?.() ?? flightCurrent()) ? joined : unavailable();
@@ -78,7 +80,7 @@ export async function fetchAnthropicAccountQuota(
78
80
  const result = await recovery.probeAnthropicQuotaWithRecovery(accountId, token, fresh => {
79
81
  if (!flightCurrent()) throw new AnthropicQuotaProbeOwnershipError("quota flight changed");
80
82
  recovery.assertAnthropicQuotaSendAllowed(accountId, token);
81
- return fetchAnthropicUsageQuotaForInstance(instance, token, fresh);
83
+ return fetchAnthropicUsageQuotaForInstance(instance, token, fresh || freshDispatch);
82
84
  }, () => ownerCurrent() && mayCommitAccountQuotaKey(key, writerGeneration));
83
85
  if (result && !result.isCurrent()) return unavailable();
84
86
  if (!result && !flightCurrent()) return unavailable();
@@ -344,3 +344,17 @@ export function isProviderQuotaReportCurrent(value: ProviderQuotaReport): boolea
344
344
  return (generation === undefined || isMainAccountIdentityGenerationLive(generation))
345
345
  && (accountReportCurrent.get(value)?.() ?? true);
346
346
  }
347
+
348
+ /**
349
+ * The reports this process already holds for the configured providers, for a passive read (#6959):
350
+ * no probe, token refresh, account snapshot or publication into routing evidence. The Home's own
351
+ * dashboard and traffic keep the cache current.
352
+ */
353
+ export function heldProviderQuotaReports(configuredProviders: readonly string[]): ProviderQuotaResponse {
354
+ const held = getProviderQuotaReportCache()?.response;
355
+ const configured = new Set(configuredProviders);
356
+ return {
357
+ generatedAt: held?.generatedAt ?? Date.now(),
358
+ reports: (held?.reports ?? []).filter(item => configured.has(item.provider) && isProviderQuotaReportCurrent(item)),
359
+ };
360
+ }
@@ -327,9 +327,9 @@ async function readAnthropicUsageQuota(accessToken: string): Promise<ProviderQuo
327
327
  * This endpoint reports quota only. Its body carries `five_hour`, `seven_day`, the
328
328
  * model-scoped weekly buckets (`seven_day_fable`/`_opus`/`_sonnet`) and a `limits` array,
329
329
  * and **no subscription or tier field** — nor does the OAuth token response, which yields only
330
- * `account.uuid` and `account.email_address` (`src/oauth/anthropic.ts`). That is why
331
- * `OAuthAccountSummary.plan` is `null` for Anthropic rather than populated here (#3777); it is
332
- * a missing upstream field, not an unfinished mapping.
330
+ * `account.uuid` and `account.email_address` (`src/oauth/anthropic.ts`). Subscription labels
331
+ * instead come from the authenticated profile in `src/oauth/anthropic-identity.ts`, never
332
+ * from this quota response.
333
333
  *
334
334
  * A tier must not be inferred from what is here. Percentages are normalized per account, so a
335
335
  * Max x5 seat at 50% is byte-identical to a Max x20 seat at 50%, and the presence of a
@@ -15,6 +15,7 @@ import {
15
15
  } from "./kiro-usage";
16
16
  import { mapQuotaRoster, readProviderApiKeyQuotas, type ProviderApiKeyQuota } from "./quota-key-accounts";
17
17
  import type { OcxConfig, OcxProviderConfig } from "../types";
18
+ import { isPassiveRead } from "../lib/passive-read";
18
19
  import type { ProviderQuota, QuotaFailureCode } from "./quota-types";
19
20
  import {
20
21
  accountReportCurrent,
@@ -23,7 +24,7 @@ import {
23
24
  captureProviderReportEpochs, providerReportEpochChanged,
24
25
  cacheKeyWithAggregationState,
25
26
  getProviderQuotaReportCache,
26
- hasCodexPoolProvider,
27
+ hasCodexPoolProvider, heldProviderQuotaReports,
27
28
  inflight,
28
29
  invalidationEpoch,
29
30
  isBuiltInChatGptForwardProvider,
@@ -229,6 +230,8 @@ export function flushProviderQuotaObservationsForTests(): Promise<void> {
229
230
  }
230
231
 
231
232
  export async function fetchProviderQuotaReports(config: OcxConfig, forceRefresh = false): Promise<ProviderQuotaResponse> {
233
+ // A passive read (a Remote Link Child's, #6959) shows the held reports and starts nothing.
234
+ if (isPassiveRead()) return heldProviderQuotaReports(Object.keys(config.providers));
232
235
  // A Pool report's cache signature and provider fetch must share one account snapshot.
233
236
  // Preserve force semantics when deciding whether that snapshot refreshes upstream data.
234
237
  const prefetchedCodexSnapshot = hasCodexPoolProvider(config)
@@ -32,12 +32,12 @@ import {
32
32
  OPENAI_API_GPT56_MAX_INPUT_TOKENS,
33
33
  OPENAI_API_GPT56_VIRTUAL_MODELS,
34
34
  OPENAI_API_GPT56_REASONING_EFFORTS,
35
- META_MUSE_REASONING_EFFORTS,
36
- META_MUSE_REASONING_EFFORT_MAP,
35
+ META_MODEL_API_REASONING_EFFORTS,
36
+ META_MODEL_API_REASONING_EFFORT_MAPS,
37
37
  META_MUSE_CODE_REASONING_EFFORTS,
38
38
  META_MUSE_CODE_REASONING_EFFORT_MAP,
39
39
  META_MUSE_CONTEXT_WINDOW,
40
- META_MUSE_MODELS,
40
+ META_MODEL_API_MODELS,
41
41
  OPENAI_DAYBREAK_MODELS,
42
42
  OPENAI_DAYBREAK_CONTEXT_WINDOWS,
43
43
  OPENAI_DAYBREAK_MAX_INPUT_TOKENS,
@@ -736,7 +736,7 @@ export const PROVIDER_REGISTRY_CORE: readonly ProviderRegistryEntry[] = [
736
736
  authKind: "key",
737
737
  dashboardUrl: "https://dev.meta.ai/docs/authentication",
738
738
  defaultModel: "muse-spark-1.3",
739
- models: META_MUSE_MODELS,
739
+ models: META_MODEL_API_MODELS,
740
740
  // Static roster: no authenticated /v1/models payload was ever observed (the only
741
741
  // contact was an unauthenticated GET returning 401 invalid_api_key), and Meta serves
742
742
  // non-agent families on this same base URL. Turning discovery on would publish an
@@ -746,20 +746,20 @@ export const PROVIDER_REGISTRY_CORE: readonly ProviderRegistryEntry[] = [
746
746
  // without this, registry transport canonicalization would retarget it and send their
747
747
  // saved key to Meta.
748
748
  preserveCustomDestination: true,
749
- modelContextWindows: Object.fromEntries(META_MUSE_MODELS.map(id => [id, META_MUSE_CONTEXT_WINDOW])),
749
+ modelContextWindows: Object.fromEntries(META_MODEL_API_MODELS.map(id => [id, META_MUSE_CONTEXT_WINDOW])),
750
750
  // text+image only. Meta also documents video, audio (degraded on 1.3), and PDF, but
751
751
  // the catalog modality enum is text/image and over-advertising poisons the exported
752
752
  // client config (see tests/codex-integration/catalog-input-modality-enum.test.ts).
753
- modelInputModalities: Object.fromEntries(META_MUSE_MODELS.map(id => [id, ["text", "image"] as ["text", "image"]])),
754
- modelReasoningEfforts: Object.fromEntries(META_MUSE_MODELS.map(id => [id, META_MUSE_REASONING_EFFORTS])),
755
- modelReasoningEffortMap: Object.fromEntries(META_MUSE_MODELS.map(id => [id, META_MUSE_REASONING_EFFORT_MAP])),
753
+ modelInputModalities: Object.fromEntries(META_MODEL_API_MODELS.map(id => [id, ["text", "image"] as ["text", "image"]])),
754
+ modelReasoningEfforts: META_MODEL_API_REASONING_EFFORTS,
755
+ modelReasoningEffortMap: META_MODEL_API_REASONING_EFFORT_MAPS,
756
756
  // No defaultMaxOutputTokens: Meta publishes none. The only number in its docs
757
757
  // (131072) appears inside a third-party config sample, and the protocol pages call
758
758
  // the real limit "model-dependent".
759
759
  // Meta names its variable MODEL_API_KEY, but the env var opencodex reads is derived
760
760
  // from the provider id (META_MODEL_API_KEY). Saying only Meta's name would send a
761
761
  // user to export a variable this proxy never reads.
762
- note: "Pay-as-you-go Meta Model API. Get a key at https://dev.meta.ai (Meta calls it MODEL_API_KEY; export it here as META_MODEL_API_KEY) — a Meta developer account needs a payment method before it can serve requests, and every call is metered per token. A Muse Code subscription does NOT work here: Meta scopes that credential to the Muse Code CLI and bills any other key pay-as-you-go (dev.meta.ai/docs/muse-code/subscriptions). The Contributor tier (muse-spark-1.3-contributor) is cheap because Meta trains on your prompts — about 92% off input, 95% off output, 99% off cached input; do not send confidential material through it. Muse Spark is also reachable through resellers: command-code carries both tiers, opencode-go serves only muse-spark-1.3-contributor.",
762
+ note: "Pay-as-you-go Meta Model API. Get a key at https://dev.meta.ai (Meta calls it MODEL_API_KEY; export it here as META_MODEL_API_KEY) — a Meta developer account needs a payment method before it can serve requests, and every call is metered per token. A Muse Code subscription does NOT work here: Meta scopes that credential to the Muse Code CLI and bills any other key pay-as-you-go (dev.meta.ai/docs/muse-code/subscriptions). The Contributor tier (muse-spark-1.3-contributor) is cheap because Meta trains on your prompts — about 92% off input, 95% off output, 99% off cached input; do not send confidential material through it. Muse Wonder (muse-wonder-1.0, preview) is Standard mode only on this API ($4 input / $20 output per 1M tokens, no Contributor tier) and accepts reasoning effort up to max; the Spark rows stop at xhigh. Muse Spark is also reachable through resellers: command-code carries both tiers, opencode-go serves only muse-spark-1.3-contributor.",
763
763
  },
764
764
  /* [Decision Log]
765
765
  - 목적과 의도: Let an operator who already signed the Muse Code CLI in reach Muse Spark with that credential, instead of provisioning a second key.
@@ -787,14 +787,14 @@ export const PROVIDER_REGISTRY_CORE: readonly ProviderRegistryEntry[] = [
787
787
  oauthId: "meta-muse",
788
788
  dashboardUrl: "https://dev.meta.ai",
789
789
  defaultModel: "muse-spark-1.3",
790
- models: META_MUSE_MODELS,
790
+ models: META_MODEL_API_MODELS,
791
791
  // Same reason as meta-model: the authenticated roster carries muse-image-1.0 and
792
792
  // muse-voice-transcribe-1.0, which this Responses-agent provider cannot drive.
793
793
  liveModels: false,
794
- modelContextWindows: Object.fromEntries(META_MUSE_MODELS.map(id => [id, META_MUSE_CONTEXT_WINDOW])),
795
- modelInputModalities: Object.fromEntries(META_MUSE_MODELS.map(id => [id, ["text", "image"] as ["text", "image"]])),
796
- modelReasoningEfforts: Object.fromEntries(META_MUSE_MODELS.map(id => [id, META_MUSE_CODE_REASONING_EFFORTS])),
797
- modelReasoningEffortMap: Object.fromEntries(META_MUSE_MODELS.map(id => [id, META_MUSE_CODE_REASONING_EFFORT_MAP])),
794
+ modelContextWindows: Object.fromEntries(META_MODEL_API_MODELS.map(id => [id, META_MUSE_CONTEXT_WINDOW])),
795
+ modelInputModalities: Object.fromEntries(META_MODEL_API_MODELS.map(id => [id, ["text", "image"] as ["text", "image"]])),
796
+ modelReasoningEfforts: Object.fromEntries(META_MODEL_API_MODELS.map(id => [id, META_MUSE_CODE_REASONING_EFFORTS])),
797
+ modelReasoningEffortMap: Object.fromEntries(META_MODEL_API_MODELS.map(id => [id, META_MUSE_CODE_REASONING_EFFORT_MAP])),
798
798
  note: "Signs in to Meta with a browser device code on any platform, then mints the Muse Code subscription key. That grant is reimplemented from the one the Muse Code CLI performs and has NOT been exercised against Meta from OpenCodex, so treat the first login as unverified. If the Muse Code CLI is already signed in on macOS, the existing key is imported instead of starting a new grant. A pasted key from https://dev.meta.ai still works as a fallback when a device login cannot complete, and faces the same format check and live validation. A device login authenticates as Meta own Muse Code client, which is a stronger claim than reusing a key the CLI already minted. Meta scopes that credential to the Muse Code CLI, so this is an UNSUPPORTED use: Meta does not authorize subscription coverage outside its own CLI, how these calls settle is not observable from the API, and you should treat every call as billable against your account. The key, imported or pasted, is copied into OpenCodex's auth store. For an account signed in with the device login, OpenCodex refreshes Meta's subscription windows on demand from the same key endpoint the login uses, at most once every five minutes. For an imported or pasted key there is no endpoint to query them on demand, so OpenCodex reads them from streaming responses and shows the last observed value with its age; refreshing one then requires another streaming turn, and translated (non-passthrough) turns report none. Rate limits apply per team, not per key. For a supported path use the meta-model provider with your own key (export it as META_MODEL_API_KEY).",
799
799
  },
800
800
  {
@@ -1140,10 +1140,8 @@ export const PROVIDER_REGISTRY_CORE: readonly ProviderRegistryEntry[] = [
1140
1140
  // https://cf.api.fan/v1 — GET /v1/models answers 401 without a key, so the host is live and
1141
1141
  // discovery narrows to what the key's token group allows. Model ids are bare OpenAI-style
1142
1142
  // ids (the Codex token group lists gpt-5.5 / gpt-5.1-codex).
1143
- // Standard sponsor under SPONSORS.md; the dashboardUrl carries their affiliate code.
1144
1143
  id: "packycode", label: "PackyCode", adapter: "openai-chat", baseUrl: "https://cf.api.fan/v1",
1145
- authKind: "key", dashboardUrl: "https://www.packyapi.com/register?aff=k5KT",
1146
- sponsor: { tier: "standard", url: "https://www.packyapi.com/register?aff=k5KT" },
1144
+ authKind: "key", dashboardUrl: "https://www.packyapi.com",
1147
1145
  defaultModel: "gpt-5.5",
1148
1146
  models: ["gpt-5.5", "gpt-5.1-codex"],
1149
1147
  liveModels: true,
@@ -1170,13 +1168,16 @@ export const PROVIDER_REGISTRY_CORE: readonly ProviderRegistryEntry[] = [
1170
1168
  // devlog/_plan/260710_provider_hardening/001_research_frontier.md.
1171
1169
  {
1172
1170
  id: "google", label: "Google Gemini", adapter: "google", baseUrl: "https://generativelanguage.googleapis.com", authKind: "key", featured: true,
1173
- dashboardUrl: "https://aistudio.google.com/apikey", defaultModel: "gemini-3.5-flash", models: ["gemini-3.8-flash", "gemini-3.6-flash", "gemini-3.5-flash", "gemini-3.5-flash-lite", "gemini-3.1-pro-preview", "gemini-3.7-flash"],
1174
- modelContextWindows: { "gemini-3.8-flash": 1_048_576, "gemini-3.6-flash": 1_048_576, "gemini-3.5-flash": 1_000_000, "gemini-3.5-flash-lite": 1_048_576, "gemini-3.7-flash": 1_048_576 },
1175
- modelInputModalities: { "gemini-3.8-flash": ["text", "image"], "gemini-3.6-flash": ["text", "image"], "gemini-3.5-flash-lite": ["text", "image"], "gemini-3.7-flash": ["text", "image"] },
1171
+ dashboardUrl: "https://aistudio.google.com/apikey", defaultModel: "gemini-3.5-flash", models: ["gemini-4-argon", "gemini-3.8-flash", "gemini-3.6-flash", "gemini-3.5-flash", "gemini-3.5-flash-lite", "gemini-3.1-pro-preview", "gemini-3.7-flash"],
1172
+ modelContextWindows: { "gemini-4-argon": 1_048_576, "gemini-3.8-flash": 1_048_576, "gemini-3.6-flash": 1_048_576, "gemini-3.5-flash": 1_000_000, "gemini-3.5-flash-lite": 1_048_576, "gemini-3.7-flash": 1_048_576 },
1173
+ modelInputModalities: { "gemini-4-argon": ["text", "image"], "gemini-3.8-flash": ["text", "image"], "gemini-3.6-flash": ["text", "image"], "gemini-3.5-flash-lite": ["text", "image"], "gemini-3.7-flash": ["text", "image"] },
1176
1174
  modelReasoningEfforts: {
1177
1175
  // 3.7 and 3.8 omit `minimal`: Google documents it as a validation error on both model
1178
1176
  // pages, so advertising it hands the user a rung the API rejects. 3.5/3.6 keep theirs —
1179
1177
  // their pages still list it, and this unit has no evidence to change them.
1178
+ // Gemini 4 Argon (GA 2026-10-13): low/medium(default)/high; `minimal` and thinking_budget
1179
+ // return HTTP 400 per ai.google.dev/gemini-api/docs/models/gemini-4-argon.
1180
+ "gemini-4-argon": ["low", "medium", "high"],
1180
1181
  "gemini-3.8-flash": ["low", "medium", "high"],
1181
1182
  "gemini-3.7-flash": ["low", "medium", "high"],
1182
1183
  "gemini-3.6-flash": ["minimal", "low", "medium", "high"],
@@ -1247,6 +1247,25 @@ export const PROVIDER_REGISTRY_EXTENDED: readonly ProviderRegistryEntry[] = [
1247
1247
  },
1248
1248
  note: "OpenAI-compatible API gateway. Create a workspace API key at tokenlab.sh. Live discovery lists tool-capable chat models available to your key and delivery policy.",
1249
1249
  },
1250
+ {
1251
+ // Modelflare: OpenAI-compatible relay at https://modelflare.dev/v1 (an Anthropic Messages
1252
+ // surface also answers at https://modelflare.dev/v1/messages). Live-checked 2026-10-13 through
1253
+ // the proxy: chat, streaming and tool calls succeed on Chat for all four seeded models, and a
1254
+ // real `codex exec` turn completed on glm-5.3-flash. GET /v1/models lists ~77 rows mixing chat,
1255
+ // image and video models, and includes deepseek-v4.1-flash, which answers 503 on every call,
1256
+ // so the preset seeds the verified chat models statically instead of trusting live discovery.
1257
+ // The conventional env reference derived from the id is ${MODELFLARE_API_KEY}.
1258
+ id: "modelflare",
1259
+ label: "Modelflare",
1260
+ adapter: "openai-chat",
1261
+ baseUrl: "https://modelflare.dev/v1",
1262
+ authKind: "key",
1263
+ dashboardUrl: "https://modelflare.dev",
1264
+ preserveCustomDestination: true,
1265
+ defaultModel: "glm-5.3-flash",
1266
+ models: ["glm-5.3-flash", "claude-sonnet-5-5", "gpt-6.1-sol", "deepseek-v4-flash"],
1267
+ note: "OpenAI-compatible relay for Claude, GPT, GLM and DeepSeek chat models. The seeded models were verified with chat, streaming and tool calls; deepseek-v4.1-flash is listed upstream but currently answers 503 and is left out. An ordinary key only reaches its own routing group's models; a Smart key reaches all seeded models. Image and video models are not served through this Chat preset. Upstream latency varies widely (a few seconds to about 30 s) with occasional transient 503s.",
1268
+ },
1250
1269
  {
1251
1270
  id: "opencode-free",
1252
1271
  label: "OpenCode Free",
@@ -1468,7 +1487,6 @@ export const PROVIDER_REGISTRY_EXTENDED: readonly ProviderRegistryEntry[] = [
1468
1487
  models: [...QODER_GLOBAL_MODELS],
1469
1488
  liveModels: true,
1470
1489
  reasoningEfforts: [...QODER_REASONING_EFFORTS],
1471
- noVisionModels: [...QODER_GLOBAL_MODELS],
1472
1490
  note: "Official Qoder Global CLI using QODER_PERSONAL_ACCESS_TOKEN. Models are discovered per account with `qoder --list-models`; the documented roster is a degraded fallback. The CLI runs single-turn with tools, MCP, settings hooks, and session persistence disabled. Requires `npm install -g @qoder-ai/qodercli`.",
1473
1491
  },
1474
1492
  {
@@ -255,6 +255,23 @@ export const META_MUSE_CODE_REASONING_EFFORT_MAP: Record<string, string> = Objec
255
255
  /** Both Muse Spark 1.3 tiers publish a 1,048,576-token window (dev.meta.ai/docs/models). */
256
256
  export const META_MUSE_CONTEXT_WINDOW = 1_048_576;
257
257
  export const META_MUSE_MODELS = ["muse-spark-1.3", "muse-spark-1.3-contributor"];
258
+ /*
259
+ * Muse Wonder (preview), announced 2026-10-12 (research.meta.ai/blog/muse-wonder). Per
260
+ * dev.meta.ai/docs/models and dev.meta.ai/docs/pricing-rate-limits, verified 2026-10-13:
261
+ * id muse-wonder-1.0, a 1,048,576-token window, reasoning effort "minimal" through "max",
262
+ * and Standard mode only on the Model API (no Contributor variant). Muse Wonder Full is
263
+ * reachable only inside Muse Code subscriptions and has no Model API id, so it is absent.
264
+ */
265
+ export const META_MUSE_WONDER_MODEL = "muse-wonder-1.0";
266
+ /** Roster both direct Meta providers serve: the Spark 1.3 tiers plus Muse Wonder. */
267
+ export const META_MODEL_API_MODELS = [...META_MUSE_MODELS, META_MUSE_WONDER_MODEL];
268
+ /** Model API ladders: Spark stops at xhigh; Wonder documents minimal..max. */
269
+ export const META_MODEL_API_REASONING_EFFORTS: Record<string, string[]> = Object.fromEntries(
270
+ META_MODEL_API_MODELS.map(id => [id, id === META_MUSE_WONDER_MODEL ? META_MUSE_CODE_REASONING_EFFORTS : META_MUSE_REASONING_EFFORTS]),
271
+ );
272
+ export const META_MODEL_API_REASONING_EFFORT_MAPS: Record<string, Record<string, string>> = Object.fromEntries(
273
+ META_MODEL_API_MODELS.map(id => [id, id === META_MUSE_WONDER_MODEL ? META_MUSE_CODE_REASONING_EFFORT_MAP : META_MUSE_REASONING_EFFORT_MAP]),
274
+ );
258
275
  /**
259
276
  * Daybreak program aliases. These `-latest` ids are the stable contract: OpenAI repoints
260
277
  * them at newer snapshots over time (red -> gpt-5.6-cyber, blue -> gpt-5.6-sol as of
@@ -33,6 +33,12 @@ export const REQUEST_PACING_CONCURRENCY_RETRY_AFTER_SECONDS = 5;
33
33
  let maxQueueDepth = REQUEST_PACING_MAX_QUEUE_DEPTH;
34
34
  let maxQueueAgeMs = REQUEST_PACING_MAX_QUEUE_AGE_MS;
35
35
 
36
+ // Optional owners register internal response markers; only this owned body rewrap carries them.
37
+ const responseProvenance = new Set<WeakMap<Response, unknown>>();
38
+ export function registerProviderResponseProvenance(markers: WeakMap<Response, unknown>): void {
39
+ responseProvenance.add(markers);
40
+ }
41
+
36
42
  export type RequestPacingQueueOverloadReason = "queue_full" | "queue_expired";
37
43
 
38
44
  export class RequestPacingQueueOverloadError extends Error {
@@ -588,6 +594,9 @@ export function trackProviderRequestSlotBody(
588
594
  // Retry helpers mark the response they return from, and recovery decisions key on these
589
595
  // identity markers; the rewrap must not make a non-replayable response look replayable.
590
596
  if (isNonReplayableResponse(response)) markResponseNonReplayable(wrapped);
597
+ for (const markers of responseProvenance) {
598
+ if (markers.has(response)) markers.set(wrapped, markers.get(response));
599
+ }
591
600
  return carryReplayRefusal(response, wrapped);
592
601
  }
593
602
 
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Drop a saved `noVisionModels` list that is still, byte for byte, a retired registry seed.
3
+ *
4
+ * `enrichProviderFromRegistry` fills `noVisionModels` all-or-nothing, so a provider row saved
5
+ * while the registry seeded a blanket list keeps that list after the registry stops seeding it.
6
+ * Qoder Global seeded its whole static discovery roster as text-only until the Global adapter
7
+ * began forwarding image input to the Qoder CLI (#6906). The saved copy would keep every image
8
+ * on the vision sidecar path, stripped or captioned before the adapter could forward it.
9
+ *
10
+ * This projection removes one thing: a saved list identical, in order, to a seed this file names,
11
+ * on a row the registry still owns (same adapter, and the registry transport rule enrichment uses),
12
+ * and only while the registry no longer seeds the list at all. Any edited list does not match and
13
+ * is left alone. `modelCapabilities` and `modelInputModalities` are untouched; they are where a
14
+ * deliberate per-model text-only declaration belongs.
15
+ */
16
+ import { PROVIDER_REGISTRY, providerMatchesRegistryTransport } from "./registry";
17
+ import type { OcxConfig } from "../types";
18
+
19
+ export interface StaleNoVisionSeed {
20
+ /** Registry provider id whose saved rows may carry the retired list. */
21
+ provider: string;
22
+ /** Every list the registry ever seeded, in seed order; only an exact match is removed. */
23
+ seeds: readonly (readonly string[])[];
24
+ }
25
+
26
+ export interface StaleNoVisionSeedProjection {
27
+ config: OcxConfig;
28
+ changed: boolean;
29
+ warnings: string[];
30
+ }
31
+
32
+ export const STALE_NO_VISION_SEEDS: readonly StaleNoVisionSeed[] = [
33
+ {
34
+ provider: "qoder",
35
+ seeds: [
36
+ // 2026-09-03 (Qoder Global PAT provider) until the DeepSeek V4.1 transition (#4282).
37
+ ["Qwen3.8-Max", "Qwen3.7-Max", "Qwen3.7-Plus", "Kimi-K3", "Kimi-K2.7-Code", "GLM-5.3", "GLM-5.2", "DeepSeek-V4-Pro"],
38
+ // #4282 until Global image input (#6906).
39
+ ["Qwen3.8-Max", "Qwen3.7-Max", "Qwen3.7-Plus", "Kimi-K3", "Kimi-K2.7-Code", "GLM-5.3", "GLM-5.2"],
40
+ ],
41
+ },
42
+ ];
43
+
44
+ function sameList(current: readonly unknown[], expected: readonly string[]): boolean {
45
+ return current.length === expected.length && current.every((value, index) => value === expected[index]);
46
+ }
47
+
48
+ /** Pure projection. The caller decides whether to persist. */
49
+ export function projectStaleNoVisionSeeds(
50
+ config: OcxConfig,
51
+ entries: readonly StaleNoVisionSeed[] = STALE_NO_VISION_SEEDS,
52
+ ): StaleNoVisionSeedProjection {
53
+ const warnings: string[] = [];
54
+ let changed = false;
55
+ for (const entry of entries) {
56
+ const prov = config.providers?.[entry.provider];
57
+ if (!prov || !Array.isArray(prov.noVisionModels)) continue;
58
+ const registry = PROVIDER_REGISTRY.find(row => row.id === entry.provider);
59
+ // While the registry still seeds a list, the saved one is not stale by this file's rule.
60
+ if (!registry || registry.noVisionModels !== undefined || registry.adapter !== prov.adapter) continue;
61
+ if (!providerMatchesRegistryTransport(entry.provider, prov)) continue;
62
+ const saved = prov.noVisionModels;
63
+ if (!entry.seeds.some(seed => sameList(saved, seed))) continue;
64
+ delete prov.noVisionModels;
65
+ changed = true;
66
+ warnings.push(
67
+ `removed the retired registry noVisionModels seed (${saved.length} model(s)) from "${entry.provider}"; `
68
+ + "image input now follows the provider's current classification.",
69
+ );
70
+ }
71
+ return { config, changed, warnings };
72
+ }