@bitkyc08/opencodex 2.84.0 → 2.87.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 (518) hide show
  1. package/README.md +0 -4
  2. package/bin/ocx.mjs +61 -3
  3. package/gui/dist/assets/App-ZfYbLHkB.js +58 -0
  4. package/gui/dist/assets/App-wT3mXqFz.css +1 -0
  5. package/gui/dist/assets/{Tray-C9TGtwad.js → Tray-DCd-A1az.js} +1 -1
  6. package/gui/dist/assets/index-CfYDMj34.js +94 -0
  7. package/gui/dist/assets/index-DPeB9HpV.css +1 -0
  8. package/gui/dist/assets/quota-summary-ChvmKGOk.js +1 -0
  9. package/gui/dist/index.html +2 -2
  10. package/package.json +1 -2
  11. package/src/adapters/anthropic/beta-allowlist.ts +91 -11
  12. package/src/adapters/anthropic/beta-body-pairs.ts +93 -0
  13. package/src/adapters/anthropic/client-identity.ts +1 -1
  14. package/src/adapters/anthropic/passthrough.ts +16 -3
  15. package/src/adapters/anthropic-progress.ts +84 -0
  16. package/src/adapters/anthropic.ts +108 -21
  17. package/src/adapters/azure.ts +19 -1
  18. package/src/adapters/base.ts +2 -0
  19. package/src/adapters/claude-cli/adapter.ts +111 -23
  20. package/src/adapters/claude-cli/profiles.ts +2 -1
  21. package/src/adapters/claude-cli/stable-replay.ts +108 -0
  22. package/src/adapters/claude-cli/usage-admission.ts +230 -0
  23. package/src/adapters/client-fingerprint.ts +24 -0
  24. package/src/adapters/coding-agent/protocol.ts +1 -1
  25. package/src/adapters/coding-agent/turn.ts +40 -11
  26. package/src/adapters/cursor/arg-codec.ts +17 -5
  27. package/src/adapters/cursor/catalog.ts +8 -0
  28. package/src/adapters/cursor/effort-map.ts +3 -0
  29. package/src/adapters/cursor/freeform-args.ts +75 -0
  30. package/src/adapters/cursor/protobuf-events.ts +36 -23
  31. package/src/adapters/dsh-account.ts +97 -0
  32. package/src/adapters/google-antigravity-wire.ts +22 -1
  33. package/src/adapters/google.ts +37 -10
  34. package/src/adapters/openai-chat/messages.ts +4 -3
  35. package/src/adapters/openai-chat/passthrough.ts +8 -2
  36. package/src/adapters/openai-chat/reasoning-replay.ts +52 -0
  37. package/src/adapters/openai-chat/response-events.ts +59 -1
  38. package/src/adapters/openai-chat.ts +8 -5
  39. package/src/adapters/openai-responses/compaction-search-history.ts +14 -7
  40. package/src/adapters/openai-responses/passthrough.ts +57 -125
  41. package/src/adapters/openai-responses/request-strips.ts +25 -3
  42. package/src/adapters/openai-responses/transport.ts +81 -0
  43. package/src/adapters/qoder/adapter.ts +44 -3
  44. package/src/adapters/registry.ts +5 -0
  45. package/src/adapters/zed.ts +26 -1
  46. package/src/advisor/activity.ts +55 -0
  47. package/src/advisor/budget.ts +62 -0
  48. package/src/advisor/consult.ts +223 -0
  49. package/src/advisor/context.ts +238 -0
  50. package/src/advisor/disclosure.ts +20 -0
  51. package/src/advisor/evidence-tool.ts +21 -0
  52. package/src/advisor/evidence.ts +117 -0
  53. package/src/advisor/runtime.ts +353 -0
  54. package/src/advisor/settings.ts +144 -0
  55. package/src/advisor/state.ts +369 -0
  56. package/src/advisor/synthetic-tool.ts +33 -0
  57. package/src/bridge/errors.ts +24 -1
  58. package/src/bridge/internal.ts +3 -1
  59. package/src/bridge/response-json.ts +18 -3
  60. package/src/bridge/sse.ts +58 -8
  61. package/src/bridge/terminal-refusal.ts +59 -0
  62. package/src/chat/inbound.ts +30 -6
  63. package/src/chat/outbound.ts +5 -0
  64. package/src/claude/desktop-3p-library.ts +72 -7
  65. package/src/claude/desktop-3p.ts +75 -19
  66. package/src/claude/desktop-first-party.ts +1 -1
  67. package/src/claude/desktop-picker-platform.ts +4 -0
  68. package/src/claude/desktop-picker-profile.ts +11 -4
  69. package/src/claude/desktop-picker.ts +15 -8
  70. package/src/claude/desktop-profile.ts +10 -1
  71. package/src/claude/desktop-remote-store-io.ts +5 -3
  72. package/src/claude/desktop-system-proxy.ts +50 -4
  73. package/src/claude/inbound-content-options.ts +11 -0
  74. package/src/claude/inbound.ts +154 -50
  75. package/src/claude/intercept/local-ca.ts +8 -1
  76. package/src/claude/intercept/picker-ca-startup.ts +2 -1
  77. package/src/claude/intercept/picker-runtime.ts +6 -5
  78. package/src/claude/intercept/picker-trust-windows.ts +338 -0
  79. package/src/claude/intercept/picker-trust.ts +19 -0
  80. package/src/claude/intercept/port-policy.ts +30 -0
  81. package/src/claude/intercept/runtime.ts +97 -94
  82. package/src/claude/model-default-settings.ts +154 -0
  83. package/src/claude/model-info.ts +7 -7
  84. package/src/claude/outbound.ts +51 -19
  85. package/src/cli/account-api.ts +1 -0
  86. package/src/cli/account-policy-dto.ts +6 -0
  87. package/src/cli/account-policy.ts +34 -5
  88. package/src/cli/account-preservation.ts +44 -0
  89. package/src/cli/account.ts +8 -2
  90. package/src/cli/advisor.ts +165 -0
  91. package/src/cli/auxiliary-listener-preflight.ts +37 -0
  92. package/src/cli/capabilities-accounts.ts +20 -1
  93. package/src/cli/capabilities-agents-routing.ts +23 -2
  94. package/src/cli/capabilities-base.ts +39 -11
  95. package/src/cli/capabilities-observe-system.ts +63 -3
  96. package/src/cli/capabilities-provider-models.ts +11 -0
  97. package/src/cli/chatgpt-command.ts +35 -14
  98. package/src/cli/claude.ts +46 -6
  99. package/src/cli/codex-cli-update.ts +151 -41
  100. package/src/cli/codex-provider.ts +44 -0
  101. package/src/cli/combo-input.ts +10 -1
  102. package/src/cli/combo-result.ts +1 -1
  103. package/src/cli/combo.ts +2 -1
  104. package/src/cli/cross-home-owner.ts +17 -14
  105. package/src/cli/dispatch.ts +30 -5
  106. package/src/cli/doctor.ts +15 -1
  107. package/src/cli/ensure-desktop-cold-start.ts +382 -0
  108. package/src/cli/export-command.ts +1 -1
  109. package/src/cli/help.ts +4 -2
  110. package/src/cli/index.ts +14 -5
  111. package/src/cli/integration-plan-dto.ts +3 -0
  112. package/src/cli/log-history.ts +172 -0
  113. package/src/cli/models-runtime-subcommands.ts +1 -0
  114. package/src/cli/models-runtime.ts +72 -8
  115. package/src/cli/observe-stream.ts +2 -0
  116. package/src/cli/observe.ts +16 -4
  117. package/src/cli/provider.ts +27 -5
  118. package/src/cli/registry.ts +41 -9
  119. package/src/cli/restart-scope.ts +3 -1
  120. package/src/cli/runtime-api.ts +5 -3
  121. package/src/cli/system-command.ts +40 -8
  122. package/src/cli/uninstall-client-state.ts +32 -0
  123. package/src/cli/usage-compare.ts +309 -0
  124. package/src/cli/usage-model-search.ts +4 -4
  125. package/src/cli/usage-report.ts +2 -2
  126. package/src/cli/v2.ts +1 -1
  127. package/src/client/machine-listener.ts +1 -3
  128. package/src/client/runtime.ts +1 -0
  129. package/src/clients/client-path.ts +51 -0
  130. package/src/clients/config-export/contracts.ts +3 -1
  131. package/src/clients/config-export/model-metadata.ts +3 -2
  132. package/src/clients/config-export/qoder.ts +93 -0
  133. package/src/clients/config-export.ts +35 -48
  134. package/src/clients/gentle-shell-path.ts +87 -0
  135. package/src/codex/account-auto-switch.ts +2 -0
  136. package/src/codex/account-identity.ts +29 -0
  137. package/src/codex/account-label.ts +1 -1
  138. package/src/codex/account-lifecycle.ts +21 -5
  139. package/src/codex/account-preservation.ts +28 -0
  140. package/src/codex/account-use-remaining.ts +23 -0
  141. package/src/codex/app-server-processes.ts +97 -7
  142. package/src/codex/app-server-restart-service.ts +55 -4
  143. package/src/codex/auth-api/account-list.ts +4 -0
  144. package/src/codex/auth-api/account-pause-group.ts +6 -50
  145. package/src/codex/auth-api/account-preservation.ts +57 -0
  146. package/src/codex/auth-api/pool-mode-gate.ts +5 -2
  147. package/src/codex/auth-api/routes.ts +8 -0
  148. package/src/codex/auth-api/use-remaining-route.ts +36 -0
  149. package/src/codex/auth-api.ts +0 -1
  150. package/src/codex/auth-context.ts +81 -7
  151. package/src/codex/autostart-health.ts +4 -0
  152. package/src/codex/catalog/auto-review.ts +20 -3
  153. package/src/codex/catalog/build-entries.ts +19 -2
  154. package/src/codex/catalog/combo-member.ts +14 -5
  155. package/src/codex/catalog/configured-tiers.ts +50 -0
  156. package/src/codex/catalog/effort.ts +4 -1
  157. package/src/codex/catalog/gather-capture.ts +2 -0
  158. package/src/codex/catalog/metadata.ts +11 -9
  159. package/src/codex/catalog/model-hints.ts +42 -19
  160. package/src/codex/catalog/model-visibility.ts +3 -1
  161. package/src/codex/catalog/native-ultrafast.ts +106 -0
  162. package/src/codex/catalog/parsing.ts +35 -86
  163. package/src/codex/catalog/provider-models.ts +40 -34
  164. package/src/codex/catalog/retained-sync.ts +2 -0
  165. package/src/codex/catalog/routed-gather.ts +33 -16
  166. package/src/codex/catalog/vendor-context.ts +82 -0
  167. package/src/codex/cli-install-provenance.ts +105 -17
  168. package/src/codex/cli-update-plan.ts +545 -0
  169. package/src/codex/config-write-lock.ts +63 -4
  170. package/src/codex/convergence.ts +2 -0
  171. package/src/codex/desktop-app/picker-freshness.ts +96 -0
  172. package/src/codex/desktop-app/types.ts +2 -0
  173. package/src/codex/desktop-app/windows.ts +29 -16
  174. package/src/codex/desktop-app-restart.ts +25 -2
  175. package/src/codex/desktop-compatibility/certificate-service.ts +99 -0
  176. package/src/codex/desktop-compatibility/certificate-store.ts +193 -0
  177. package/src/codex/desktop-compatibility/connection-store.ts +87 -0
  178. package/src/codex/desktop-compatibility/installed-build.ts +73 -0
  179. package/src/codex/desktop-compatibility/json-body.ts +29 -0
  180. package/src/codex/desktop-compatibility/native-identity.ts +62 -0
  181. package/src/codex/desktop-compatibility/relay-listener.ts +93 -0
  182. package/src/codex/desktop-compatibility/routing-binding.ts +10 -0
  183. package/src/codex/desktop-compatibility/routing-preflight.ts +69 -0
  184. package/src/codex/desktop-compatibility/runtime-ownership.ts +28 -0
  185. package/src/codex/desktop-compatibility/runtime.ts +182 -0
  186. package/src/codex/desktop-compatibility/service.ts +30 -0
  187. package/src/codex/desktop-compatibility/startup-settings.ts +50 -0
  188. package/src/codex/desktop-compatibility/usage-activation.ts +86 -0
  189. package/src/codex/desktop-compatibility/usage-controlled-fetch.ts +59 -0
  190. package/src/codex/desktop-compatibility/usage-controller.ts +107 -0
  191. package/src/codex/desktop-compatibility/usage-policy.ts +59 -0
  192. package/src/codex/desktop-compatibility/usage-refresh.ts +33 -0
  193. package/src/codex/desktop-compatibility/usage-sse-controller.ts +86 -0
  194. package/src/codex/desktop-compatibility/windows-activation-source.ts +44 -0
  195. package/src/codex/desktop-compatibility/windows-certificate-trust.ts +81 -0
  196. package/src/codex/desktop-compatibility/windows-key-protection.ts +165 -0
  197. package/src/codex/desktop-compatibility/windows-package-command.ts +86 -0
  198. package/src/codex/desktop-compatibility/windows-package-launch.ts +28 -0
  199. package/src/codex/features.ts +85 -45
  200. package/src/codex/history-provider.ts +1 -1
  201. package/src/codex/inject/config-write-section.ts +4 -1
  202. package/src/codex/inject/history-failure.ts +27 -0
  203. package/src/codex/inject/remove.ts +18 -6
  204. package/src/codex/inject/restore.ts +24 -2
  205. package/src/codex/inject/routing-classify.ts +4 -3
  206. package/src/codex/inject.ts +18 -27
  207. package/src/codex/journal.ts +16 -1
  208. package/src/codex/main-account-hard-lock.ts +30 -1
  209. package/src/codex/main-account.ts +99 -15
  210. package/src/codex/native-main-refresh-mode.ts +217 -0
  211. package/src/codex/native-profile-startup.ts +1 -1
  212. package/src/codex/ocx-compaction-history.ts +12 -10
  213. package/src/codex/paths.ts +86 -1
  214. package/src/codex/provider-routing.ts +174 -0
  215. package/src/codex/refresh.ts +2 -2
  216. package/src/codex/routing/health-store.ts +2 -2
  217. package/src/codex/routing/pool-recovery.ts +79 -0
  218. package/src/codex/routing/selection.ts +4 -3
  219. package/src/codex/routing/thread-affinity.ts +1 -1
  220. package/src/codex/shim-diagnostics.ts +6 -0
  221. package/src/codex/shim-overlay.ts +7 -2
  222. package/src/codex/signed-in-identity.ts +43 -0
  223. package/src/codex/subagent-model-fallback.ts +1 -1
  224. package/src/codex/sync.ts +6 -1
  225. package/src/codex/user-identity.ts +16 -5
  226. package/src/codex/windows-local-appdata.ts +84 -0
  227. package/src/combos/failover.ts +6 -1
  228. package/src/combos/index.ts +1 -0
  229. package/src/combos/resolve.ts +46 -10
  230. package/src/combos/types.ts +15 -0
  231. package/src/config/diagnostics.ts +35 -7
  232. package/src/config/live-reconcile.ts +51 -2
  233. package/src/config/load-degrade.ts +23 -1
  234. package/src/config/opencode-wire-upgrade.ts +54 -0
  235. package/src/config/opencode-zen-upgrade.ts +52 -0
  236. package/src/config/owner-registry.ts +22 -17
  237. package/src/config/paths.ts +10 -3
  238. package/src/config/provider-validation.ts +45 -2
  239. package/src/config/schema/config-schema.ts +39 -0
  240. package/src/config/schema/desktop-compatibility.ts +9 -0
  241. package/src/config/schema/leaf-validators.ts +39 -9
  242. package/src/config/schema/main-account-hard-lock-fallback.ts +26 -0
  243. package/src/config/schema/native-reasoning-retention.ts +26 -0
  244. package/src/config.ts +2 -0
  245. package/src/generated/compatibility-version.json +962 -350
  246. package/src/generated/model-metadata.ts +1 -1
  247. package/src/grok/sync.ts +1 -1
  248. package/src/images/loop.ts +46 -8
  249. package/src/integrations/catalog-refresh.ts +1 -1
  250. package/src/integrations/config-io.ts +20 -2
  251. package/src/integrations/journal.ts +15 -1
  252. package/src/integrations/mutation-plan.ts +2 -0
  253. package/src/integrations/registry.ts +15 -0
  254. package/src/integrations/store.ts +3 -0
  255. package/src/integrations/writer.ts +10 -1
  256. package/src/lib/advisor-activation.ts +16 -0
  257. package/src/lib/background-work.ts +65 -0
  258. package/src/lib/bounded-file-read.ts +58 -0
  259. package/src/lib/bun-path-runtime.d.mts +10 -1
  260. package/src/lib/bun-path-runtime.mjs +62 -3
  261. package/src/lib/codex-restart-contract.ts +15 -0
  262. package/src/lib/desktop-proxy-route.ts +30 -0
  263. package/src/lib/desktop-upstream-tunnel.ts +72 -0
  264. package/src/lib/empty-tool-catalog.ts +62 -0
  265. package/src/lib/errors.ts +26 -3
  266. package/src/lib/gcp-adc.ts +96 -9
  267. package/src/lib/gcp-credential-input.ts +12 -0
  268. package/src/lib/lab-live-route-production.ts +5 -0
  269. package/src/lib/local-internal-call-capability.ts +55 -0
  270. package/src/lib/opaque-proxy-tunnel.ts +217 -0
  271. package/src/lib/package-tree-integrity.ts +2 -2
  272. package/src/lib/redact.ts +2 -2
  273. package/src/lib/socks5-fetch.ts +48 -71
  274. package/src/lib/socks5-handshake.ts +112 -0
  275. package/src/lib/translator-budget.ts +7 -1
  276. package/src/oauth/account-quota-rank.ts +69 -2
  277. package/src/oauth/anthropic-account-refusal.ts +20 -2
  278. package/src/oauth/anthropic-oauth-definitions.ts +3 -1
  279. package/src/oauth/anthropic-routing.ts +1 -21
  280. package/src/oauth/anthropic.ts +38 -7
  281. package/src/oauth/callback-server.ts +27 -1
  282. package/src/oauth/chatgpt-device.ts +1 -1
  283. package/src/oauth/chatgpt-shared.ts +83 -0
  284. package/src/oauth/chatgpt.ts +8 -72
  285. package/src/oauth/dsh.ts +203 -0
  286. package/src/oauth/generic-account-failover.ts +62 -12
  287. package/src/oauth/generic-session-affinity.ts +125 -0
  288. package/src/oauth/index.ts +188 -111
  289. package/src/oauth/kiro-device-login.ts +2 -1
  290. package/src/oauth/kiro.ts +7 -1
  291. package/src/oauth/local-token-detect.ts +21 -1
  292. package/src/oauth/login-cli-errors.ts +44 -0
  293. package/src/oauth/login-cli.ts +69 -7
  294. package/src/oauth/login-flow-state.ts +371 -23
  295. package/src/oauth/meta-muse-errors.ts +41 -0
  296. package/src/oauth/meta-muse-keychain.ts +52 -0
  297. package/src/oauth/meta-muse.ts +51 -84
  298. package/src/oauth/model-discovery-auth.ts +3 -0
  299. package/src/oauth/pool-settings-capability.ts +4 -2
  300. package/src/oauth/refresh-flight-retirement.ts +48 -0
  301. package/src/oauth/session-affinity-key.ts +23 -0
  302. package/src/oauth/store-anthropic-instance.ts +9 -2
  303. package/src/oauth/store.ts +12 -5
  304. package/src/protocols/codecs/chat-ir.ts +467 -0
  305. package/src/protocols/codecs/chat.ts +10 -1
  306. package/src/protocols/codecs/messages-ir.ts +500 -0
  307. package/src/protocols/encoders/adapter-events.ts +5 -4
  308. package/src/protocols/encoders/messages.ts +9 -6
  309. package/src/protocols/features.ts +10 -2
  310. package/src/protocols/path.ts +11 -1
  311. package/src/protocols/plan-snapshot.ts +39 -16
  312. package/src/protocols/plan.ts +9 -1
  313. package/src/protocols/provider-summary.ts +2 -1
  314. package/src/protocols/settings.ts +8 -0
  315. package/src/providers/antigravity-account-roster.ts +102 -0
  316. package/src/providers/antigravity-models.ts +85 -7
  317. package/src/providers/api-key-cooldowns.ts +49 -0
  318. package/src/providers/api-key-resolve.ts +11 -6
  319. package/src/providers/api-key-response-ownership.ts +57 -0
  320. package/src/providers/api-key-selection.ts +31 -6
  321. package/src/providers/api-keys.ts +214 -3
  322. package/src/providers/claude-cli-usage.ts +96 -0
  323. package/src/providers/dsh-account-destination.ts +31 -0
  324. package/src/providers/dsh-account-models.ts +41 -0
  325. package/src/providers/gcp-credential-cleanup.ts +59 -0
  326. package/src/providers/key-failover.ts +54 -50
  327. package/src/providers/key-store.ts +7 -2
  328. package/src/providers/model-rename-fields.ts +5 -0
  329. package/src/providers/model-rename-migration.ts +7 -5
  330. package/src/providers/openai-tiers-destination.ts +11 -2
  331. package/src/providers/openai-tiers.ts +5 -12
  332. package/src/providers/opencode-go-transport.ts +3 -1
  333. package/src/providers/quota/account-cache.ts +6 -5
  334. package/src/providers/quota/account-row.ts +34 -0
  335. package/src/providers/quota/antigravity.ts +28 -7
  336. package/src/providers/quota/dsh-account.ts +70 -0
  337. package/src/providers/quota/vendor-probes-oauth.ts +1 -93
  338. package/src/providers/quota-types.ts +2 -0
  339. package/src/providers/quota.ts +14 -20
  340. package/src/providers/registry/entries-core.ts +44 -14
  341. package/src/providers/registry/entries-extended.ts +19 -0
  342. package/src/providers/registry/types.ts +1 -1
  343. package/src/providers/request-pacing.ts +77 -19
  344. package/src/providers/routed-config.ts +339 -0
  345. package/src/providers/service-tier.ts +2 -1
  346. package/src/quota/window-mapping.ts +5 -2
  347. package/src/responses/anthropic-progress.ts +34 -0
  348. package/src/responses/assistant-replay.ts +35 -0
  349. package/src/responses/compaction-history-framing.ts +65 -0
  350. package/src/responses/compaction.ts +6 -1
  351. package/src/responses/incomplete-reason.ts +31 -0
  352. package/src/responses/parser.ts +78 -22
  353. package/src/responses/reasoning-envelope.ts +4 -0
  354. package/src/responses/reasoning-replay-cache.ts +24 -2
  355. package/src/responses/reasoning-retention.ts +173 -0
  356. package/src/responses/retained-compaction.ts +46 -0
  357. package/src/responses/schema.ts +23 -0
  358. package/src/responses/state.ts +32 -29
  359. package/src/router.ts +8 -321
  360. package/src/routing/health.ts +37 -22
  361. package/src/routing/history/build-file.ts +92 -0
  362. package/src/routing/history/indexer.ts +254 -221
  363. package/src/routing/history/ingest.ts +242 -0
  364. package/src/routing/history/rebuild-job.ts +111 -0
  365. package/src/routing/history/rebuild-protocol.ts +15 -0
  366. package/src/routing/history/rebuild-worker.ts +34 -0
  367. package/src/routing/profile.ts +1 -1
  368. package/src/server/admission-model-scope.ts +21 -1
  369. package/src/server/antigravity-direct-eligibility.ts +208 -0
  370. package/src/server/auth-cors.ts +15 -0
  371. package/src/server/chat-completions.ts +82 -10
  372. package/src/server/chat-native-reasoning.ts +123 -0
  373. package/src/server/chat-native-sse.ts +110 -7
  374. package/src/server/chat-native.ts +76 -17
  375. package/src/server/claude-messages.ts +164 -433
  376. package/src/server/effort-policy.ts +1 -1
  377. package/src/server/images.ts +15 -2
  378. package/src/server/index/desktop-compatibility-startup.ts +56 -0
  379. package/src/server/index/model-alias-id.ts +36 -0
  380. package/src/server/index/optional-listeners.ts +2 -0
  381. package/src/server/index/serve-options.ts +11 -3
  382. package/src/server/index/startup-warnings.ts +11 -0
  383. package/src/server/index/websocket-handler.ts +9 -0
  384. package/src/server/index.ts +14 -11
  385. package/src/server/inference/advisor-diagnostics.ts +31 -0
  386. package/src/server/inference/client-encoder-delivery.ts +6 -5
  387. package/src/server/login-shell-path.ts +146 -0
  388. package/src/server/management/advisor-routes.ts +202 -0
  389. package/src/server/management/agent-settings-routes.ts +22 -4
  390. package/src/server/management/claude-desktop-picker-routes.ts +1 -1
  391. package/src/server/management/combo-routes.ts +176 -50
  392. package/src/server/management/companion-routes.ts +4 -0
  393. package/src/server/management/config-routes.ts +77 -6
  394. package/src/server/management/context.ts +4 -0
  395. package/src/server/management/cursor-integration-routes.ts +1 -1
  396. package/src/server/management/desktop-compatibility-routes.ts +46 -0
  397. package/src/server/management/desktop-compatibility-runtime-routes.ts +46 -0
  398. package/src/server/management/desktop-compatibility-settings-routes.ts +27 -0
  399. package/src/server/management/model-routes.ts +13 -8
  400. package/src/server/management/model-rows.ts +14 -0
  401. package/src/server/management/native-integration-routes.ts +2 -7
  402. package/src/server/management/native-reasoning-retention-routes.ts +41 -0
  403. package/src/server/management/oauth-account-routes.ts +87 -14
  404. package/src/server/management/protocol-settings-patch.ts +1 -0
  405. package/src/server/management/provider-gcp-credential-routes.ts +29 -0
  406. package/src/server/management/provider-key-pacing-routes.ts +31 -0
  407. package/src/server/management/provider-overwrite-carry.ts +7 -0
  408. package/src/server/management/provider-routes.ts +25 -30
  409. package/src/server/management/route-registry.ts +21 -1
  410. package/src/server/management/sibling-guard.ts +1 -0
  411. package/src/server/management-api.ts +9 -1
  412. package/src/server/messages-antigravity-direct.ts +195 -0
  413. package/src/server/messages-native-tool-guard.ts +185 -0
  414. package/src/server/messages-native.ts +106 -22
  415. package/src/server/messages-shared.ts +431 -0
  416. package/src/server/relay.ts +1 -1
  417. package/src/server/request-log-terminal-status.ts +1 -1
  418. package/src/server/request-log.ts +13 -1
  419. package/src/server/responses/account-change-state.ts +8 -2
  420. package/src/server/responses/adapter-continuation.ts +48 -19
  421. package/src/server/responses/adapter-delivery.ts +43 -8
  422. package/src/server/responses/adapter-dispatch.ts +48 -4
  423. package/src/server/responses/advisor-plan-slot.ts +27 -0
  424. package/src/server/responses/advisor-retention.ts +117 -0
  425. package/src/server/responses/advisor-slot.ts +368 -0
  426. package/src/server/responses/code-mode-collab-surface.ts +116 -8
  427. package/src/server/responses/codex-ws-wire.ts +15 -8
  428. package/src/server/responses/compact.ts +303 -125
  429. package/src/server/responses/compaction-recovery.ts +18 -10
  430. package/src/server/responses/compaction-retention.ts +61 -0
  431. package/src/server/responses/context-overflow.ts +11 -6
  432. package/src/server/responses/core-auth.ts +6 -0
  433. package/src/server/responses/core-codex-account.ts +11 -1
  434. package/src/server/responses/core-combo-failure.ts +12 -1
  435. package/src/server/responses/core-combo.ts +141 -36
  436. package/src/server/responses/core-errors.ts +3 -3
  437. package/src/server/responses/core-lifetime.ts +7 -2
  438. package/src/server/responses/core-normalize.ts +11 -0
  439. package/src/server/responses/core-opaque-recovery.ts +35 -0
  440. package/src/server/responses/core-options.ts +51 -0
  441. package/src/server/responses/core-pipeline.ts +141 -0
  442. package/src/server/responses/core-replay.ts +25 -6
  443. package/src/server/responses/core.ts +3 -116
  444. package/src/server/responses/fetch-helpers.ts +73 -19
  445. package/src/server/responses/main-hard-lock-routing.ts +109 -0
  446. package/src/server/responses/native-response-control.ts +4 -2
  447. package/src/server/responses/passthrough-delivery.ts +19 -11
  448. package/src/server/responses/passthrough-dispatch.ts +29 -16
  449. package/src/server/responses/request-prepare.ts +144 -17
  450. package/src/server/responses/request-sidecar-auth.ts +2 -2
  451. package/src/server/responses/request-transport.ts +120 -27
  452. package/src/server/responses/response-effects.ts +1 -0
  453. package/src/server/responses/run-turn-execution.ts +166 -38
  454. package/src/server/responses/sidecar-execution.ts +98 -5
  455. package/src/server/responses/terminal-guard.ts +4 -17
  456. package/src/server/responses-undeclared-tool-guard.ts +30 -9
  457. package/src/server/startup-action-control.ts +16 -2
  458. package/src/server/startup-health-cache.ts +1 -0
  459. package/src/service/auth-preflight.ts +71 -0
  460. package/src/service/cli.ts +4 -2
  461. package/src/service/definition.ts +97 -0
  462. package/src/service/desktop-startup.ts +14 -0
  463. package/src/service/guards.ts +1 -61
  464. package/src/service/health-sentinel-linux.ts +112 -0
  465. package/src/service/health-sentinel-main.ts +99 -61
  466. package/src/service/health-sentinel-probe.ts +24 -7
  467. package/src/service/health.ts +5 -89
  468. package/src/service/launchd.ts +27 -7
  469. package/src/service/logging.ts +135 -0
  470. package/src/service/repair.ts +2 -1
  471. package/src/service/state.ts +7 -8
  472. package/src/service/systemd.ts +1 -1
  473. package/src/service/windows-guarded-manager.ts +2 -1
  474. package/src/service/windows-ops.ts +1 -1
  475. package/src/service/windows-scheduler.ts +2 -7
  476. package/src/service/windows-taskxml.ts +2 -2
  477. package/src/service/windows-wscript.ts +14 -0
  478. package/src/service.ts +4 -2
  479. package/src/sidecar/auth.ts +2 -0
  480. package/src/tray/windows-tray.ps1 +43 -24
  481. package/src/types/config.ts +124 -6
  482. package/src/types/opencode-go.ts +61 -0
  483. package/src/types/opencode-zen.ts +29 -0
  484. package/src/types/provider.ts +39 -6
  485. package/src/types/request.ts +38 -2
  486. package/src/types/tools.ts +2 -0
  487. package/src/types/wire.ts +29 -37
  488. package/src/types.ts +1 -0
  489. package/src/update/async-check.ts +1 -1
  490. package/src/update/badge.ts +2 -2
  491. package/src/update/index.ts +88 -15
  492. package/src/update/job-supervised-restart.ts +107 -0
  493. package/src/update/job.ts +32 -32
  494. package/src/update/notify.ts +1 -1
  495. package/src/update/npm-invocation.mjs +12 -2
  496. package/src/update/refresh-scheduler.ts +2 -2
  497. package/src/update/supervised-recovery.d.mts +27 -0
  498. package/src/update/supervised-recovery.mjs +157 -0
  499. package/src/usage/cache-diagnostic.ts +2 -0
  500. package/src/usage/cost.ts +48 -12
  501. package/src/usage/expected-prices.ts +15 -0
  502. package/src/usage/ledger-retention.ts +2 -1
  503. package/src/usage/ledger-scanner.ts +6 -5
  504. package/src/usage/log.ts +17 -8
  505. package/src/usage/summary.ts +22 -9
  506. package/src/usage/user-cost-overlays.ts +14 -19
  507. package/src/usage/user-cost-validation.ts +38 -0
  508. package/src/vision/eligibility.ts +13 -0
  509. package/src/web-search/gemini-executor.ts +2 -2
  510. package/src/web-search/loop.ts +55 -5
  511. package/src/web-search/progress-stream.ts +4 -1
  512. package/src/web-search/run-turn-loop.ts +9 -1
  513. package/assets/sponsors/packycode.png +0 -0
  514. package/gui/dist/assets/App-CSJvmjNs.css +0 -1
  515. package/gui/dist/assets/App-CZtqiGcw.js +0 -56
  516. package/gui/dist/assets/index-BB6uK6tU.css +0 -1
  517. package/gui/dist/assets/index-CTm4l-HQ.js +0 -94
  518. package/gui/dist/assets/quota-summary-BbEeRfPY.js +0 -1
@@ -1,4 +1,5 @@
1
1
  import type { OcxProviderConfig, RequestPacingRule } from "../types";
2
+ import { createHash } from "node:crypto";
2
3
  import type { GenerationContext } from "../lib/state-store-sweeper";
3
4
  import { carryReplayRefusal, isNonReplayableResponse, markResponseNonReplayable } from "../lib/upstream-retry";
4
5
  import { redactSecretString } from "../lib/redact";
@@ -39,7 +40,7 @@ export function registerProviderResponseProvenance(markers: WeakMap<Response, un
39
40
  responseProvenance.add(markers);
40
41
  }
41
42
 
42
- export type RequestPacingQueueOverloadReason = "queue_full" | "queue_expired";
43
+ export type RequestPacingQueueOverloadReason = "queue_full" | "queue_expired" | "keys_exhausted";
43
44
 
44
45
  export class RequestPacingQueueOverloadError extends Error {
45
46
  constructor(
@@ -47,7 +48,7 @@ export class RequestPacingQueueOverloadError extends Error {
47
48
  public readonly reason: RequestPacingQueueOverloadReason,
48
49
  public readonly retryAfterSeconds: number,
49
50
  ) {
50
- super(reason === "queue_full"
51
+ super(reason === "keys_exhausted" ? "No eligible API key remains; wait for the provider cooldown before retrying" : reason === "queue_full"
51
52
  ? `request pacing queue for provider '${providerName}' is full`
52
53
  : `request pacing queue for provider '${providerName}' exceeded the maximum queued age`);
53
54
  this.name = "RequestPacingQueueOverloadError";
@@ -62,6 +63,10 @@ export class RequestPacingProviderRemovedError extends Error {
62
63
  }
63
64
 
64
65
  interface Waiter {
66
+ keyLease: boolean;
67
+ keyId?: string;
68
+ keyIntervalMs: number;
69
+ keyMaxConcurrent: number;
65
70
  modelId?: string;
66
71
  providerIntervalMs: number;
67
72
  modelIntervalMs: number;
@@ -75,6 +80,9 @@ interface Waiter {
75
80
  }
76
81
 
77
82
  interface ProviderPacer {
83
+ keyNextStartAt: Map<string, number>;
84
+ keyInFlight: Map<string, number>;
85
+ keyStarts: Map<string, number>;
78
86
  queue: Waiter[];
79
87
  providerNextStartAt: number;
80
88
  modelNextStartAt: Map<string, number>;
@@ -138,6 +146,7 @@ export interface ProviderRequestPacingStatus {
138
146
  inFlight?: number;
139
147
  lastStartedAt?: number;
140
148
  lastModelId?: string;
149
+ keys?: Array<{ id: string; starts: number; inFlight: number; queued: number; nextSlotInMs: number }>;
141
150
  }
142
151
 
143
152
  const pacers = new Map<string, ProviderPacer>();
@@ -165,6 +174,13 @@ function normalizedInterval(rule: RequestPacingRule | undefined): number {
165
174
  return Math.max(rpmInterval, fixedInterval);
166
175
  }
167
176
 
177
+ function keyBucket(provider: OcxProviderConfig): string | undefined {
178
+ if (provider.authMode === "oauth" || provider.authMode === "forward" || !provider.apiKey) return;
179
+ if (!provider.requestPacing?.enabled || (!provider.requestPacing.keys && provider.requestPacing.keySelection !== "round-robin")) return;
180
+ // Internal only: duplicate references share one bucket; never serialize a key or digest.
181
+ return createHash("sha256").update(provider._apiKeyAttempt?.reference ?? provider.apiKey).digest("hex");
182
+ }
183
+
168
184
  function normalizedMaxConcurrent(rule: RequestPacingRule | undefined): number {
169
185
  return typeof rule?.maxConcurrentRequests === "number" && rule.maxConcurrentRequests > 0
170
186
  // Floor so runtime-injected configs that bypass the integer schema cannot admit
@@ -178,10 +194,14 @@ export function requestPacingIntervalMs(provider: OcxProviderConfig, modelId?: s
178
194
  const policy = provider.requestPacing;
179
195
  if (!policy?.enabled) return 0;
180
196
  const override = modelId ? policy.models?.[modelId] : undefined;
181
- return Math.max(normalizedInterval(policy), normalizedInterval(override));
197
+ return Math.max(normalizedInterval(policy), normalizedInterval(override), keyBucket(provider) ? normalizedInterval(policy.keys) : 0);
182
198
  }
183
199
 
184
200
  function requestPacingIntervals(provider: OcxProviderConfig, modelId?: string): {
201
+ keyLease: boolean;
202
+ keyId?: string;
203
+ keyIntervalMs: number;
204
+ keyMaxConcurrent: number;
185
205
  providerIntervalMs: number;
186
206
  modelIntervalMs: number;
187
207
  providerMaxConcurrent: number;
@@ -189,9 +209,14 @@ function requestPacingIntervals(provider: OcxProviderConfig, modelId?: string):
189
209
  } {
190
210
  const policy = provider.requestPacing;
191
211
  if (!policy?.enabled) {
192
- return { providerIntervalMs: 0, modelIntervalMs: 0, providerMaxConcurrent: 0, modelMaxConcurrent: 0 };
212
+ return { keyLease: false, keyIntervalMs: 0, keyMaxConcurrent: 0, providerIntervalMs: 0, modelIntervalMs: 0, providerMaxConcurrent: 0, modelMaxConcurrent: 0 };
193
213
  }
214
+ const keyId = keyBucket(provider);
194
215
  return {
216
+ keyLease: true,
217
+ keyId,
218
+ keyIntervalMs: keyId ? normalizedInterval(policy.keys) : 0,
219
+ keyMaxConcurrent: keyId ? normalizedMaxConcurrent(policy.keys) : 0,
195
220
  providerIntervalMs: normalizedInterval(policy),
196
221
  modelIntervalMs: modelId ? normalizedInterval(policy.models?.[modelId]) : 0,
197
222
  providerMaxConcurrent: normalizedMaxConcurrent(policy),
@@ -209,13 +234,14 @@ function requestPacingIntervals(provider: OcxProviderConfig, modelId?: string):
209
234
  */
210
235
  export function requestPacingMaxConcurrentRequests(provider: OcxProviderConfig, modelId?: string): number {
211
236
  const limits = requestPacingIntervals(provider, modelId);
212
- return Math.max(limits.providerMaxConcurrent, limits.modelMaxConcurrent);
237
+ return Math.max(limits.providerMaxConcurrent, limits.modelMaxConcurrent, limits.keyMaxConcurrent);
213
238
  }
214
239
 
215
- function waiterReadyAt(state: ProviderPacer, modelId: string | undefined): number {
240
+ function waiterReadyAt(state: ProviderPacer, modelId: string | undefined, keyId?: string): number {
216
241
  return Math.max(
217
242
  state.providerNextStartAt,
218
243
  modelId ? (state.modelNextStartAt.get(modelId) ?? 0) : 0,
244
+ keyId ? (state.keyNextStartAt.get(keyId) ?? 0) : 0,
219
245
  );
220
246
  }
221
247
 
@@ -224,8 +250,9 @@ function pacingRetryAfterSeconds(
224
250
  modelId: string | undefined,
225
251
  now: number,
226
252
  concurrencyCapped = false,
253
+ keyId?: string,
227
254
  ): number {
228
- const intervalSeconds = Math.max(1, Math.ceil(Math.max(0, waiterReadyAt(state, modelId) - now) / 1000));
255
+ const intervalSeconds = Math.max(1, Math.ceil(Math.max(0, waiterReadyAt(state, modelId, keyId) - now) / 1000));
229
256
  // A waiter blocked by an in-flight lease has no interval to report: when the wait ends
230
257
  // in refusal, the honest answer is that the cap is saturated, not "retry every second".
231
258
  return concurrencyCapped
@@ -240,7 +267,7 @@ function makeProviderRequestSlot(
240
267
  ): BodyTrackableProviderRequestSlot {
241
268
  let released = false;
242
269
  let bodyTracked = false;
243
- const leased = waiter.providerMaxConcurrent > 0 || waiter.modelMaxConcurrent > 0;
270
+ const leased = (!!waiter.keyId && waiter.keyLease) || waiter.providerMaxConcurrent > 0 || waiter.modelMaxConcurrent > 0;
244
271
  const slot: BodyTrackableProviderRequestSlot = {
245
272
  providerName,
246
273
  leased,
@@ -258,6 +285,7 @@ function makeProviderRequestSlot(
258
285
  released = true;
259
286
  waiter.signal?.removeEventListener("abort", releaseOnAbort);
260
287
  if (waiter.providerMaxConcurrent > 0 && state.providerInFlight > 0) state.providerInFlight -= 1;
288
+ if (waiter.keyId && waiter.keyLease) state.keyInFlight.set(waiter.keyId, Math.max(0, (state.keyInFlight.get(waiter.keyId) ?? 0) - 1));
261
289
  if (waiter.modelId && waiter.modelMaxConcurrent > 0) {
262
290
  const current = state.modelInFlight.get(waiter.modelId) ?? 0;
263
291
  if (current <= 1) state.modelInFlight.delete(waiter.modelId);
@@ -293,7 +321,8 @@ function rejectExpiredWaiters(providerName: string, state: ProviderPacer, now: n
293
321
  state,
294
322
  waiter.modelId,
295
323
  now,
296
- waiter.providerMaxConcurrent > 0 || waiter.modelMaxConcurrent > 0,
324
+ waiter.providerMaxConcurrent > 0 || waiter.modelMaxConcurrent > 0 || waiter.keyMaxConcurrent > 0,
325
+ waiter.keyId,
297
326
  ),
298
327
  ));
299
328
  }
@@ -327,17 +356,18 @@ function runQueue(providerName: string, state: ProviderPacer): void {
327
356
 
328
357
  const providerReadyAt = Math.max(now, state.providerNextStartAt);
329
358
  const waiterIndex = state.queue.findIndex(waiter => {
359
+ if (waiter.keyId && waiter.keyMaxConcurrent > 0 && (state.keyInFlight.get(waiter.keyId) ?? 0) >= waiter.keyMaxConcurrent) return false;
330
360
  if (waiter.providerMaxConcurrent > 0 && state.providerInFlight >= waiter.providerMaxConcurrent) return false;
331
361
  if (waiter.modelId && waiter.modelMaxConcurrent > 0
332
362
  && (state.modelInFlight.get(waiter.modelId) ?? 0) >= waiter.modelMaxConcurrent) return false;
333
363
  const modelReadyAt = waiter.modelId ? (state.modelNextStartAt.get(waiter.modelId) ?? 0) : 0;
334
- return Math.max(providerReadyAt, modelReadyAt) <= now;
364
+ return Math.max(providerReadyAt, modelReadyAt, waiter.keyId ? state.keyNextStartAt.get(waiter.keyId) ?? 0 : 0) <= now;
335
365
  });
336
366
  if (waiterIndex < 0) {
337
367
  let earliestAt = Number.POSITIVE_INFINITY;
338
368
  for (const waiter of state.queue) {
339
369
  const modelReadyAt = waiter.modelId ? (state.modelNextStartAt.get(waiter.modelId) ?? 0) : 0;
340
- const readyAt = Math.max(providerReadyAt, modelReadyAt);
370
+ const readyAt = Math.max(providerReadyAt, modelReadyAt, waiter.keyId ? state.keyNextStartAt.get(waiter.keyId) ?? 0 : 0);
341
371
  const expiresAt = waiter.queuedAt + maxQueueAgeMs;
342
372
  // A waiter whose start time already passed is blocked on an in-flight lease; its release
343
373
  // re-runs this queue through a microtask, so the timer only needs its expiry backstop.
@@ -362,6 +392,11 @@ function runQueue(providerName: string, state: ProviderPacer): void {
362
392
  state.lastStartedAt = startedAt;
363
393
  state.lastModelId = waiter.modelId;
364
394
  state.providerNextStartAt = startedAt + waiter.providerIntervalMs;
395
+ if (waiter.keyId) {
396
+ state.keyNextStartAt.set(waiter.keyId, startedAt + waiter.keyIntervalMs);
397
+ if (waiter.keyLease) state.keyInFlight.set(waiter.keyId, (state.keyInFlight.get(waiter.keyId) ?? 0) + 1);
398
+ state.keyStarts.set(waiter.keyId, (state.keyStarts.get(waiter.keyId) ?? 0) + 1);
399
+ }
365
400
  if (waiter.modelId && waiter.modelIntervalMs > 0) {
366
401
  state.modelNextStartAt.set(waiter.modelId, startedAt + waiter.modelIntervalMs);
367
402
  }
@@ -386,22 +421,34 @@ export async function waitForProviderRequestSlot(
386
421
  // by interval only, or a follow-up would queue behind the lease its own turn holds.
387
422
  const concurrency = options?.concurrency !== false;
388
423
  const waiterIntervals = concurrency ? intervals : {
424
+ keyId: intervals.keyId, keyLease: false, keyIntervalMs: intervals.keyIntervalMs, keyMaxConcurrent: 0,
389
425
  providerIntervalMs: intervals.providerIntervalMs,
390
426
  modelIntervalMs: intervals.modelIntervalMs,
391
427
  providerMaxConcurrent: 0,
392
428
  modelMaxConcurrent: 0,
393
429
  };
394
430
  const paced = Math.max(intervals.providerIntervalMs, intervals.modelIntervalMs) > 0
431
+ || intervals.keyId !== undefined
395
432
  || waiterIntervals.providerMaxConcurrent > 0
396
433
  || waiterIntervals.modelMaxConcurrent > 0;
397
434
  if (!paced) return inertProviderRequestSlot;
398
435
  if (signal?.aborted) throw abortReason(signal);
399
436
 
400
437
  const state = pacers.get(providerName) ?? {
438
+ keyNextStartAt: new Map<string, number>(), keyInFlight: new Map<string, number>(), keyStarts: new Map<string, number>(),
401
439
  queue: [], providerNextStartAt: 0, modelNextStartAt: new Map<string, number>(),
402
440
  providerInFlight: 0, modelInFlight: new Map<string, number>(),
403
441
  };
404
442
  pacers.set(providerName, state);
443
+ // Retire removed credentials only after their queued work, leases and spacing debt end.
444
+ const currentKeys = new Set((provider.apiKeyPool ?? []).map(entry =>
445
+ keyBucket({ ...provider, apiKey: entry.key, _apiKeyAttempt: undefined })));
446
+ currentKeys.add(intervals.keyId);
447
+ for (const key of state.keyStarts.keys()) {
448
+ if (currentKeys.has(key) || (state.keyInFlight.get(key) ?? 0) > 0
449
+ || (state.keyNextStartAt.get(key) ?? 0) > runtime.now() || state.queue.some(waiter => waiter.keyId === key)) continue;
450
+ state.keyStarts.delete(key); state.keyInFlight.delete(key); state.keyNextStartAt.delete(key);
451
+ }
405
452
 
406
453
  // Give already-eligible or expired waiters a chance to leave before applying the
407
454
  // admission bound to the newest request. This preserves FIFO-ish fairness while
@@ -419,7 +466,8 @@ export async function waitForProviderRequestSlot(
419
466
  state,
420
467
  modelId,
421
468
  runtime.now(),
422
- waiterIntervals.providerMaxConcurrent > 0 || waiterIntervals.modelMaxConcurrent > 0,
469
+ waiterIntervals.providerMaxConcurrent > 0 || waiterIntervals.modelMaxConcurrent > 0 || waiterIntervals.keyMaxConcurrent > 0,
470
+ waiterIntervals.keyId,
423
471
  ),
424
472
  );
425
473
  }
@@ -464,14 +512,13 @@ export async function waitForProviderRequestSlot(
464
512
  * means the exchange is already finished, so the lease returns immediately. A body that
465
513
  * is neither read nor cancelled for REQUEST_PACING_UNCONSUMED_BODY_MS releases its lease
466
514
  * and cancels the body, so a dropped Response cannot hold a slot forever. The returned
467
- * Response preserves status, statusText, headers, and the identity-based replay markers.
515
+ * Response preserves status, statusText, headers, replay markers, and registered provenance.
468
516
  */
469
517
  export function trackProviderRequestSlotBody(
470
518
  slot: ProviderRequestSlot | undefined,
471
519
  response: Response,
472
520
  ): Response {
473
- // An unleased slot has nothing to return on body close, and rewrapping the Response
474
- // would break identity-based markers (the eager WS relay registry is a WeakSet).
521
+ // An unleased slot has nothing to return on body close; preserve the response identity.
475
522
  if (!slot?.leased) return response;
476
523
  // Slots built outside waitForProviderRequestSlot (test doubles) satisfy only the public
477
524
  // ProviderRequestSlot shape; their bodies still own the release through the wrapper below.
@@ -657,24 +704,35 @@ export function providerRequestPacingStatus(
657
704
  if (state && state.queue.length > 0) {
658
705
  let earliestQueuedSlotAt = Number.POSITIVE_INFINITY;
659
706
  for (const waiter of state.queue) {
660
- earliestQueuedSlotAt = Math.min(earliestQueuedSlotAt, waiterReadyAt(state, waiter.modelId));
707
+ earliestQueuedSlotAt = Math.min(earliestQueuedSlotAt, waiterReadyAt(state, waiter.modelId, waiter.keyId));
661
708
  }
662
709
  if (Number.isFinite(earliestQueuedSlotAt)) nextSlotAt = earliestQueuedSlotAt;
663
710
  }
664
- const providerConcurrencyCap = requestPacingMaxConcurrentRequests(provider);
711
+ const providerConcurrencyCap = normalizedMaxConcurrent(provider.requestPacing);
712
+ const hasKeyAccounting = provider.requestPacing?.enabled === true && keyBucket(provider) !== undefined;
665
713
  // Gate on enabled so the status surface agrees with enforcement: a disabled requestPacing
666
714
  // block must not report in-flight leases alongside enabled: false.
667
- const anyConcurrencyCap = provider.requestPacing?.enabled === true && (providerConcurrencyCap > 0
715
+ const anyConcurrencyCap = provider.requestPacing?.enabled === true && (hasKeyAccounting || providerConcurrencyCap > 0
668
716
  || Object.values(provider.requestPacing?.models ?? {}).some(rule => normalizedMaxConcurrent(rule) > 0));
669
717
  return {
670
718
  provider: providerName,
671
719
  enabled: provider.requestPacing?.enabled === true,
672
720
  queued: state?.queue.length ?? 0,
673
721
  nextSlotInMs: Math.max(0, Math.ceil(nextSlotAt - now)),
722
+ ...(provider.requestPacing?.enabled && (provider.requestPacing.keys || provider.requestPacing.keySelection === "round-robin") ? {
723
+ keys: (provider.apiKeyPool ?? []).map(entry => {
724
+ const bucket = keyBucket({ ...provider, apiKey: entry.key, _apiKeyAttempt: undefined })!;
725
+ return { id: entry.id, starts: state?.keyStarts.get(bucket) ?? 0, inFlight: state?.keyInFlight.get(bucket) ?? 0,
726
+ queued: state?.queue.filter(waiter => waiter.keyId === bucket).length ?? 0,
727
+ nextSlotInMs: Math.max(0, Math.ceil((state?.keyNextStartAt.get(bucket) ?? 0) - now)) };
728
+ }),
729
+ } : {}),
674
730
  // A provider cap counts every paced send in providerInFlight; model-only caps keep
675
731
  // providerInFlight at zero, so report the per-model sum for those providers instead.
676
732
  ...(anyConcurrencyCap ? {
677
- inFlight: providerConcurrencyCap > 0
733
+ inFlight: hasKeyAccounting
734
+ ? [...(state?.keyInFlight.values() ?? [])].reduce((sum, count) => sum + count, 0)
735
+ : providerConcurrencyCap > 0
678
736
  ? state?.providerInFlight ?? 0
679
737
  : [...(state?.modelInFlight.values() ?? [])].reduce((sum, count) => sum + count, 0),
680
738
  } : {}),
@@ -0,0 +1,339 @@
1
+ /**
2
+ * Request-time provider resolution: merge a saved config row with its registry entry.
3
+ *
4
+ * This lives outside `src/router.ts` because `providers/api-key-selection.ts` needs exactly
5
+ * one function from the router, and importing the router for it dragged the whole routing
6
+ * graph into an import cycle. The router re-exports both symbols below so every existing
7
+ * import keeps working.
8
+ */
9
+ import type { OcxProviderConfig } from "../types";
10
+ import { providerUsesKeyAuthOverride, resolveProviderApiKey } from "./key-store";
11
+ import { GCP_CREDENTIAL_MARKER_PREFIX } from "../lib/gcp-adc";
12
+ import { captureProviderApiKeySelection } from "./api-key-selection-capture";
13
+ import { assertProviderDestinationAllowed } from "../lib/destination-policy";
14
+ import { redactSecretString, redactUrlForLog } from "../lib/redact";
15
+ import { PROVIDER_REGISTRY, registryEntryForProviderDestination } from "./registry";
16
+ import { applyDirectReasoningEffortContracts, hasLegacyClinePassReasoningEfforts } from "./derive";
17
+ import { cloneFastWire } from "./fastwire";
18
+ import { fastSwitchOff } from "./fast-opt-in";
19
+ import {
20
+ providerMatchesRegistryTransportWithStaticGuards,
21
+ providerSupportsLiveModelDiscovery,
22
+ } from "./static-model-discovery";
23
+ import { resolveModelPolicy } from "./resolved-model-policy";
24
+
25
+ /** Same endpoint modulo surrounding space and trailing slashes — matches `matchBaseUrlChoice`. */
26
+ function isSameEndpoint(a: string, b: string): boolean {
27
+ return a.trim().replace(/\/+$/, "") === b.trim().replace(/\/+$/, "");
28
+ }
29
+
30
+ /**
31
+ * Origin of a user-configured URL, with the path withheld.
32
+ *
33
+ * A configured `baseUrl` is user-controlled and its path may itself be the credential — an
34
+ * account-scoped route token such as `https://proxy.example/v1/8fK2mP7qR4nV6x` is opaque and
35
+ * high-entropy, so it matches none of the prefix patterns in `redactSecretString`. Pattern
36
+ * redaction cannot be trusted for this value, so no path segment is logged at all. `URL.origin`
37
+ * also excludes userinfo, query and fragment.
38
+ *
39
+ * `…/…` marks that a path was present without revealing it, so a reader can tell an origin-only
40
+ * config apart from one whose path was dropped.
41
+ */
42
+ function configuredOriginForLog(url: string): string {
43
+ try {
44
+ const parsed = new URL(url.trim());
45
+ // "null" is what URL.origin yields for non-special schemes; treat it as unusable.
46
+ if (!parsed.origin || parsed.origin === "null") return "(unloggable URL)";
47
+ const hasPath = parsed.pathname !== "" && parsed.pathname !== "/";
48
+ return hasPath ? `${parsed.origin}/…` : parsed.origin;
49
+ } catch {
50
+ return "(unparseable URL)";
51
+ }
52
+ }
53
+
54
+ // `routedProviderConfig` runs per request, so warn once per (provider, discarded, effective) triple.
55
+ // Keyed by the URLs too: editing config.json to a different wrong value warns again.
56
+ const discardedBaseUrlWarnings = new Set<string>();
57
+ let lastWarningReconciledGeneration = 0;
58
+
59
+ export function reconcileRouterWarningMemos(generation: number): number {
60
+ if (generation <= lastWarningReconciledGeneration) return 0;
61
+ const removed = discardedBaseUrlWarnings.size;
62
+ discardedBaseUrlWarnings.clear();
63
+ lastWarningReconciledGeneration = generation;
64
+ return removed;
65
+ }
66
+
67
+ /**
68
+ * A pinned registry entry — non-template `baseUrl`, no `allowBaseUrlOverride` — outranks a saved
69
+ * `baseUrl`. Dropping it silently is a footgun: requests go to an endpoint the user never
70
+ * configured, and a wrong-region or wrong-account URL then surfaces only as a 401 with nothing
71
+ * pointing back at the discarded setting.
72
+ *
73
+ * Warns rather than throws. The effective route is exactly what it was before, so a hard error
74
+ * here would break configs that route fine today (a stale `baseUrl` left over from an earlier
75
+ * provider is harmless whenever it names the same endpoint the registry pins).
76
+ */
77
+ function warnIfBaseUrlDiscarded(providerName: string, userBaseUrl: string, effectiveBaseUrl: string): void {
78
+ if (isSameEndpoint(userBaseUrl, effectiveBaseUrl)) return;
79
+ // Asymmetric on purpose. Past the guard above, `effectiveBaseUrl` is necessarily
80
+ // `registryEntry.baseUrl`: the caller passes the resolved URL, and whenever that resolution
81
+ // kept the user's value the two are equal and we have already returned. So the effective side
82
+ // is a constant from this repo's registry and safe to print in full — it is also the useful
83
+ // half, naming the endpoint requests will actually use. The configured side is untrusted.
84
+ const discarded = configuredOriginForLog(userBaseUrl);
85
+ const effective = redactSecretString(redactUrlForLog(effectiveBaseUrl));
86
+ // Key off the logged forms: no raw credential is retained for the process lifetime, and
87
+ // rotating a key embedded in the URL no longer re-warns about the same endpoint mismatch.
88
+ // Coarser than the raw URLs — two bad paths on one host warn once, which is the right grain.
89
+ const key = `${providerName} | ${discarded} | ${effective}`;
90
+ if (discardedBaseUrlWarnings.has(key)) return;
91
+ discardedBaseUrlWarnings.add(key);
92
+ console.warn(
93
+ // Routing is what this warning speaks for: an adapter may adjust the endpoint again
94
+ // downstream (kiro re-derives the region), so do not promise where the request lands.
95
+ `⚠️ config.json provider "${providerName}": configured baseUrl ${discarded} is ignored`
96
+ + ` because this provider's endpoint is fixed at ${effective}. A URL saved for a different`
97
+ + ` account or region is a common cause of 401s here — drop it, or use the provider whose endpoint matches.`,
98
+ );
99
+ }
100
+
101
+ function usableResolvedApiKey(apiKey: string | undefined): string | undefined {
102
+ // A `gcp-sa:` marker must pass through UNCHANGED: it resolves to `undefined` here (it is not a
103
+ // keychain/env ref, and the marker itself is not a literal), and replacing it would silently
104
+ // drop the marker from the routed config — the vertex adapter would then read
105
+ // `gcpCredentialMarkerAccount(undefined)` and fall back to host ADC instead of the configured
106
+ // service account. Return the marker verbatim so the adapter passes its account per request.
107
+ if (typeof apiKey === "string" && apiKey.startsWith(GCP_CREDENTIAL_MARKER_PREFIX)) return apiKey;
108
+ const resolved = resolveProviderApiKey(apiKey);
109
+ return typeof resolved === "string" && resolved.trim().length > 0 ? resolved : undefined;
110
+ }
111
+
112
+ export function routedProviderConfig(providerName: string, provider: OcxProviderConfig): OcxProviderConfig {
113
+ provider = { ...provider, _apiKeyAttempt: provider._apiKeyAttempt ?? captureProviderApiKeySelection(provider) };
114
+ const registryEntry = PROVIDER_REGISTRY.find(entry => entry.id === providerName);
115
+ if (!registryEntry || !providerMatchesRegistryTransportWithStaticGuards(providerName, provider)) {
116
+ assertProviderDestinationAllowed(providerName, provider);
117
+ // A row whose adapter no longer matches its registry entry still reaches the Responses
118
+ // adapter when one model opts in through `modelAdapters` — a Volcengine Coding Plan config
119
+ // saved on Chat, for instance. The replay-drop flag belongs to the DESTINATION rather than
120
+ // to the provider-wide wire, so it is filled here too; without it that continuation forwards
121
+ // the reasoning item the upstream answers 400 to. Destination matching refuses templated and
122
+ // overridable base URLs, so this cannot follow a retargeted row, and an explicit value wins.
123
+ const destination = registryEntryForProviderDestination(provider);
124
+ return {
125
+ ...provider,
126
+ apiKey: usableResolvedApiKey(provider.apiKey),
127
+ ...(provider.dropResponsesReasoningItems === undefined && destination?.dropResponsesReasoningItems !== undefined
128
+ ? { dropResponsesReasoningItems: destination.dropResponsesReasoningItems }
129
+ : {}),
130
+ };
131
+ }
132
+ const resolvedApiKey = usableResolvedApiKey(provider.apiKey);
133
+ const staticModelCatalog = !providerSupportsLiveModelDiscovery(providerName, provider);
134
+ const repairLegacyMimoFreeAuth = providerName === "mimo-free"
135
+ && staticModelCatalog
136
+ && (provider.authMode === undefined || provider.authMode === "local");
137
+ const explicitKeyOverride = providerUsesKeyAuthOverride(registryEntry, provider, resolvedApiKey);
138
+ const canonicalAuthMode = explicitKeyOverride
139
+ ? "key"
140
+ : repairLegacyMimoFreeAuth
141
+ ? "key"
142
+ : registryEntry.authKind === "forward" || registryEntry.authKind === "oauth"
143
+ ? registryEntry.authKind
144
+ : provider.authMode === "forward" ? undefined : provider.authMode;
145
+ const staticPolicy = resolveModelPolicy({
146
+ providerName,
147
+ modelId: "__provider_static__",
148
+ provider,
149
+ registryEntry,
150
+ transportMatchedRegistry: true,
151
+ ...(canonicalAuthMode ? { effectiveAuth: { authMode: canonicalAuthMode } } : {}),
152
+ }).provider;
153
+ const reasoningEffortMap = staticPolicy.reasoningEffortMap;
154
+ const modelReasoningEffortMap = staticPolicy.modelReasoningEffortMap;
155
+ const modelReasoningEfforts = staticPolicy.modelReasoningEfforts;
156
+ const modelDefaultReasoningEfforts = staticPolicy.modelDefaultReasoningEfforts;
157
+ const modelContextWindows = staticPolicy.modelContextWindows;
158
+ const modelInputModalities = staticPolicy.modelInputModalities;
159
+ // Registry static headers are documented as applying to every upstream request, so they are
160
+ // filled at resolve time rather than only at seed time: a config written before a header
161
+ // existed, or one carrying any header of its own, would otherwise never receive it. User
162
+ // headers win, matched case-insensitively so an override replaces rather than duplicates.
163
+ const headers = staticPolicy.headers;
164
+ const modelMaxInputTokens = staticPolicy.modelMaxInputTokens;
165
+ const modelMaxOutputTokens = staticPolicy.modelMaxOutputTokens;
166
+ const modelSupportsServiceTier = staticPolicy.modelSupportsServiceTier;
167
+ const modelSupportsVerbosity = staticPolicy.modelSupportsVerbosity;
168
+ const noVisionModels = staticPolicy.noVisionModels;
169
+ const noReasoningModels = staticPolicy.noReasoningModels;
170
+ const noTemperatureModels = staticPolicy.noTemperatureModels;
171
+ const noTopPModels = staticPolicy.noTopPModels;
172
+ const noStopModels = staticPolicy.noStopModels;
173
+ const noPenaltyModels = staticPolicy.noPenaltyModels;
174
+ const noJsonSchemaModels = staticPolicy.noJsonSchemaModels;
175
+ const autoToolChoiceOnlyModels = staticPolicy.autoToolChoiceOnlyModels;
176
+ const preserveReasoningContentModels = staticPolicy.preserveReasoningContentModels;
177
+ const requiresReasoningPlaceholderModels = staticPolicy.requiresReasoningPlaceholderModels;
178
+ const reasoningSplitModels = staticPolicy.reasoningSplitModels;
179
+ const inlineThinkTagModels = staticPolicy.inlineThinkTagModels;
180
+ const reasoningDetailsModels = staticPolicy.reasoningDetailsModels;
181
+ const thinkingToggleModels = staticPolicy.thinkingToggleModels;
182
+ const thinkingBudgetModels = staticPolicy.thinkingBudgetModels;
183
+ const registryBaseUrlIsTemplate = /\{[^}]*\}/.test(registryEntry.baseUrl);
184
+ const userBaseUrl = typeof provider.baseUrl === "string" ? provider.baseUrl.trim() : "";
185
+ const userBaseUrlIsResolved = userBaseUrl.length > 0 && !/\{[^}]*\}/.test(userBaseUrl);
186
+ if (registryEntry.allowBaseUrlOverride && !userBaseUrlIsResolved) {
187
+ throw new Error(`Invalid baseUrl for provider "${providerName}": expected a nonblank URL without unresolved placeholders`);
188
+ }
189
+ // Registry template URLs are presets; local/self-hosted entries opt in explicitly.
190
+ const baseUrl = (registryBaseUrlIsTemplate || registryEntry.allowBaseUrlOverride) && userBaseUrlIsResolved
191
+ ? userBaseUrl
192
+ : registryEntry.baseUrl;
193
+ if (userBaseUrlIsResolved) warnIfBaseUrlDiscarded(providerName, userBaseUrl, baseUrl);
194
+ assertProviderDestinationAllowed(providerName, { baseUrl, allowPrivateNetwork: provider.allowPrivateNetwork });
195
+
196
+ const resolved: OcxProviderConfig = {
197
+ ...provider,
198
+ adapter: registryEntry.adapter,
199
+ baseUrl,
200
+ ...(provider.responsesPath === undefined && registryEntry.responsesPath !== undefined
201
+ ? { responsesPath: registryEntry.responsesPath }
202
+ : {}),
203
+ ...(provider.chatCompletionsPath === undefined && registryEntry.chatCompletionsPath !== undefined
204
+ ? { chatCompletionsPath: registryEntry.chatCompletionsPath }
205
+ : {}),
206
+ ...(provider.requiresAdjacentResponsesToolResults === undefined
207
+ && registryEntry.requiresAdjacentResponsesToolResults !== undefined
208
+ ? { requiresAdjacentResponsesToolResults: registryEntry.requiresAdjacentResponsesToolResults }
209
+ : {}),
210
+ ...(provider.requiresPairedResponsesToolResults === undefined
211
+ && registryEntry.requiresPairedResponsesToolResults !== undefined
212
+ ? { requiresPairedResponsesToolResults: registryEntry.requiresPairedResponsesToolResults }
213
+ : {}),
214
+ ...(provider.annotateEmptyToolOutputs === undefined
215
+ && registryEntry.annotateEmptyToolOutputs !== undefined
216
+ ? { annotateEmptyToolOutputs: registryEntry.annotateEmptyToolOutputs }
217
+ : {}),
218
+ ...(provider.fastWire === undefined && registryEntry.fastWire !== undefined
219
+ ? {
220
+ fastWire: cloneFastWire(registryEntry.fastWire),
221
+ }
222
+ : {}),
223
+ ...(provider.supportsServiceTier === undefined && registryEntry.supportsServiceTier !== undefined
224
+ ? { supportsServiceTier: registryEntry.supportsServiceTier }
225
+ : {}),
226
+ // An off Fast switch is a provider-wide denial on the runtime provider, so a Fast policy
227
+ // resolved without the provider name still refuses (providerFastSwitchOff).
228
+ ...(fastSwitchOff(provider, registryEntry) ? { supportsServiceTier: false } : {}),
229
+ // Registry-only web-search capability: without this backfill a saved provider row reaches
230
+ // the Responses adapter with the flag `undefined`, so the capability gate added in #2262
231
+ // reads "unclassified" and forwards Codex's OpenAI-only `web_search` config fields. xAI
232
+ // rejects the whole request before inference ("Argument not supported:
233
+ // external_web_access"), which killed every routed Grok turn on the Responses lane.
234
+ // enrichProviderFromRegistry() already fills this, but the request path resolves through
235
+ // routedProviderConfig() and never called it.
236
+ ...(provider.supportsOpenAiWebSearchToolFields === undefined
237
+ && registryEntry.supportsOpenAiWebSearchToolFields !== undefined
238
+ ? { supportsOpenAiWebSearchToolFields: registryEntry.supportsOpenAiWebSearchToolFields }
239
+ : {}),
240
+ ...(provider.supportsResponsesCustomTools === undefined && registryEntry.supportsResponsesCustomTools !== undefined
241
+ ? { supportsResponsesCustomTools: registryEntry.supportsResponsesCustomTools }
242
+ : {}),
243
+ ...(provider.preserveResponsesReasoningContent === undefined && registryEntry.preserveResponsesReasoningContent !== undefined
244
+ ? { preserveResponsesReasoningContent: registryEntry.preserveResponsesReasoningContent }
245
+ : {}),
246
+ ...(provider.preserveResponsesInputItemIds === undefined && registryEntry.preserveResponsesInputItemIds !== undefined
247
+ ? { preserveResponsesInputItemIds: registryEntry.preserveResponsesInputItemIds }
248
+ : {}),
249
+ ...(provider.preserveResponsesMessageMetadata === undefined && registryEntry.preserveResponsesMessageMetadata !== undefined
250
+ ? { preserveResponsesMessageMetadata: registryEntry.preserveResponsesMessageMetadata }
251
+ : {}),
252
+ ...(provider.dropResponsesReasoningItems === undefined && registryEntry.dropResponsesReasoningItems !== undefined
253
+ ? { dropResponsesReasoningItems: registryEntry.dropResponsesReasoningItems }
254
+ : {}),
255
+ // The request path resolves through routedProviderConfig() and never calls
256
+ // enrichProviderFromRegistry(), so a saved provider row written before the
257
+ // registry learned this flag must be backfilled here or route.provider never
258
+ // carries it and the showThinkingSummary opt-in stays dead.
259
+ ...(provider.showThinkingSummary === undefined && registryEntry.showThinkingSummary !== undefined
260
+ ? { showThinkingSummary: registryEntry.showThinkingSummary }
261
+ : {}),
262
+ // Registry-only client-facing repair policy (#938): fill only when the
263
+ // saved provider has no explicit policy; clone so runtime never aliases
264
+ // the registry constant.
265
+ ...(provider.responsesItemIdRepair === undefined && registryEntry.responsesItemIdRepair
266
+ ? {
267
+ responsesItemIdRepair: {
268
+ ...(registryEntry.responsesItemIdRepair.message ? { message: [...registryEntry.responsesItemIdRepair.message] } : {}),
269
+ ...(registryEntry.responsesItemIdRepair.reasoning ? { reasoning: [...registryEntry.responsesItemIdRepair.reasoning] } : {}),
270
+ ...(registryEntry.responsesItemIdRepair.repairMissingTerminalIds !== undefined
271
+ ? { repairMissingTerminalIds: registryEntry.responsesItemIdRepair.repairMissingTerminalIds }
272
+ : {}),
273
+ ...(registryEntry.responsesItemIdRepair.repairInvalidIds !== undefined
274
+ ? { repairInvalidIds: registryEntry.responsesItemIdRepair.repairInvalidIds }
275
+ : {}),
276
+ },
277
+ }
278
+ : {}),
279
+ authMode: canonicalAuthMode,
280
+ apiKey: resolvedApiKey,
281
+ ...(staticModelCatalog ? { liveModels: false } : {}),
282
+ ...(headers ? { headers } : {}),
283
+ // Backfill the Google wire mode + Vertex project/location from the registry when the user
284
+ // config omits them, so a minimal `google-vertex`/`google-antigravity` entry still routes
285
+ // through the correct branch (CCA/Vertex) instead of falling back to AI Studio.
286
+ ...(provider.googleMode === undefined && registryEntry.googleMode !== undefined ? { googleMode: registryEntry.googleMode } : {}),
287
+ ...(provider.project === undefined && registryEntry.project !== undefined ? { project: registryEntry.project } : {}),
288
+ ...(provider.location === undefined && registryEntry.location !== undefined ? { location: registryEntry.location } : {}),
289
+ ...(provider.contextWindow === undefined && registryEntry.contextWindow !== undefined ? { contextWindow: registryEntry.contextWindow } : {}),
290
+ ...((provider.reasoningEfforts === undefined || hasLegacyClinePassReasoningEfforts(providerName, provider))
291
+ && registryEntry.reasoningEfforts !== undefined
292
+ ? { reasoningEfforts: [...registryEntry.reasoningEfforts] }
293
+ : {}),
294
+ ...(provider.escapeBuiltinToolNames === undefined && registryEntry.escapeBuiltinToolNames !== undefined ? { escapeBuiltinToolNames: registryEntry.escapeBuiltinToolNames } : {}),
295
+ ...(provider.keyOptional === undefined && registryEntry.keyOptional !== undefined ? { keyOptional: registryEntry.keyOptional } : {}),
296
+ ...(provider.modelSuffixBracketStrip === undefined && registryEntry.modelSuffixBracketStrip !== undefined ? { modelSuffixBracketStrip: registryEntry.modelSuffixBracketStrip } : {}),
297
+ // Scalar backfill: a persisted config created before the flag shipped inherits the registry
298
+ // opt-in, while an explicit user `false` keeps overriding registry `true`.
299
+ ...(provider.parallelToolCalls === undefined && registryEntry.parallelToolCalls !== undefined ? { parallelToolCalls: registryEntry.parallelToolCalls } : {}),
300
+ ...(provider.promptCacheKey === undefined && registryEntry.promptCacheKey !== undefined ? { promptCacheKey: registryEntry.promptCacheKey } : {}),
301
+ ...(provider.chatServiceTier === undefined && registryEntry.chatServiceTier !== undefined ? { chatServiceTier: registryEntry.chatServiceTier } : {}),
302
+ ...(provider.openaiChatEofTolerance === undefined && registryEntry.openaiChatEofTolerance !== undefined
303
+ ? { openaiChatEofTolerance: registryEntry.openaiChatEofTolerance }
304
+ : {}),
305
+ ...(provider.reasoningWireFormat === undefined && registryEntry.reasoningWireFormat !== undefined
306
+ ? { reasoningWireFormat: registryEntry.reasoningWireFormat }
307
+ : {}),
308
+ ...(provider.defaultMaxOutputTokens === undefined && registryEntry.defaultMaxOutputTokens !== undefined
309
+ ? { defaultMaxOutputTokens: registryEntry.defaultMaxOutputTokens }
310
+ : {}),
311
+ ...(modelContextWindows ? { modelContextWindows } : {}),
312
+ ...(modelInputModalities ? { modelInputModalities } : {}),
313
+ ...(modelMaxInputTokens ? { modelMaxInputTokens } : {}),
314
+ ...(modelMaxOutputTokens ? { modelMaxOutputTokens } : {}),
315
+ ...(modelSupportsServiceTier ? { modelSupportsServiceTier } : {}),
316
+ ...(modelSupportsVerbosity ? { modelSupportsVerbosity } : {}),
317
+ ...(modelReasoningEfforts ? { modelReasoningEfforts } : {}),
318
+ ...(modelDefaultReasoningEfforts ? { modelDefaultReasoningEfforts } : {}),
319
+ ...(reasoningEffortMap ? { reasoningEffortMap } : {}),
320
+ ...(modelReasoningEffortMap ? { modelReasoningEffortMap } : {}),
321
+ ...(noVisionModels ? { noVisionModels } : {}),
322
+ ...(noReasoningModels ? { noReasoningModels } : {}),
323
+ ...(noTemperatureModels ? { noTemperatureModels } : {}),
324
+ ...(noTopPModels ? { noTopPModels } : {}),
325
+ ...(noStopModels ? { noStopModels } : {}),
326
+ ...(noPenaltyModels ? { noPenaltyModels } : {}),
327
+ ...(noJsonSchemaModels ? { noJsonSchemaModels } : {}),
328
+ ...(autoToolChoiceOnlyModels ? { autoToolChoiceOnlyModels } : {}),
329
+ ...(preserveReasoningContentModels ? { preserveReasoningContentModels } : {}),
330
+ ...(requiresReasoningPlaceholderModels ? { requiresReasoningPlaceholderModels } : {}),
331
+ ...(reasoningSplitModels ? { reasoningSplitModels } : {}),
332
+ ...(inlineThinkTagModels ? { inlineThinkTagModels } : {}),
333
+ ...(reasoningDetailsModels ? { reasoningDetailsModels } : {}),
334
+ ...(thinkingToggleModels ? { thinkingToggleModels } : {}),
335
+ ...(thinkingBudgetModels ? { thinkingBudgetModels } : {}),
336
+ };
337
+ applyDirectReasoningEffortContracts(registryEntry, resolved, provider);
338
+ return resolved;
339
+ }
@@ -33,6 +33,7 @@ type ServiceTierCapabilityProvider = Pick<
33
33
  | "modelAdapters"
34
34
  | "baseUrl"
35
35
  | "authMode"
36
+ | "googleMode"
36
37
  | "apiKeyTransport"
37
38
  | "chatServiceTier"
38
39
  | "fastWire"
@@ -119,7 +120,7 @@ function buildFastPolicyAuthority(
119
120
  : {}),
120
121
  }),
121
122
  modelAdapters: Object.freeze({ ...(provider.modelAdapters ?? {}) }),
122
- hardPins: captureWireAdapterHardPins(providerName),
123
+ hardPins: captureWireAdapterHardPins(providerName, provider),
123
124
  hardPinPrefixes: captureWireAdapterHardPinPrefixes(providerName, provider),
124
125
  registryWireDefaults: cloneRegistryWireDefaults(registry?.modelWireDefaults),
125
126
  });