@cartanova/qgrid-cli 2.10.10 → 2.11.1

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 (302) hide show
  1. package/bundle/dist/_virtual/_rolldown/runtime.js +1 -10
  2. package/bundle/dist/application/monit/log-buffer.js +12 -6
  3. package/bundle/dist/application/monit/monit.frame.js +11 -3
  4. package/bundle/dist/application/monit/monit.types.js +1 -1
  5. package/bundle/dist/application/qgrid/conv-routing.js +9 -6
  6. package/bundle/dist/application/qgrid/expired-token-reminder.js +2 -2
  7. package/bundle/dist/application/qgrid/oauth.js +31 -4
  8. package/bundle/dist/application/qgrid/qgrid-image-generation.js +1 -1
  9. package/bundle/dist/application/qgrid/qgrid-response-format.js +21 -19
  10. package/bundle/dist/application/qgrid/qgrid-run-lifecycle.js +40 -33
  11. package/bundle/dist/application/qgrid/qgrid.dispatcher.js +128 -93
  12. package/bundle/dist/application/qgrid/qgrid.frame.js +930 -49
  13. package/bundle/dist/application/qgrid/qgrid.types.js +212 -5
  14. package/bundle/dist/application/qgrid/schema-prompt.js +75 -48
  15. package/bundle/dist/application/qgrid/slack-user-map.js +1 -1
  16. package/bundle/dist/application/qgrid/token-death.js +1 -1
  17. package/bundle/dist/application/qgrid/token-subscriber.js +83 -30
  18. package/bundle/dist/application/qgrid/token-trigger-setup.js +1 -1
  19. package/bundle/dist/application/qgrid/token-window-keepalive.js +8 -6
  20. package/bundle/dist/application/qgrid/tool-emulation-schema.js +13 -13
  21. package/bundle/dist/application/qgrid/tool-emulation.js +19 -15
  22. package/bundle/dist/application/request-log/json-schema-type-text.js +29 -12
  23. package/bundle/dist/application/request-log/request-log.model.js +83 -70
  24. package/bundle/dist/application/request-log/request-log.types.js +1 -1
  25. package/bundle/dist/application/request-log/zod-code-format.js +1 -1
  26. package/bundle/dist/application/request-log-step/request-log-step.model.js +1 -1
  27. package/bundle/dist/application/request-log-step/request-log-step.types.js +1 -1
  28. package/bundle/dist/application/setting/setting.constant.js +1 -1
  29. package/bundle/dist/application/setting/setting.model.js +2 -2
  30. package/bundle/dist/application/setting/setting.store.js +2 -2
  31. package/bundle/dist/application/setting/setting.types.js +1 -1
  32. package/bundle/dist/application/sonamu.generated.js +32 -16
  33. package/bundle/dist/application/sonamu.generated.sso.js +1 -1
  34. package/bundle/dist/application/token/token.model.js +26 -9
  35. package/bundle/dist/application/token/token.types.js +20 -3
  36. package/bundle/dist/i18n/en.js +15 -1
  37. package/bundle/dist/i18n/ko.js +15 -1
  38. package/bundle/dist/i18n/sd.generated.js +1 -1
  39. package/bundle/dist/migrations/20260807133553_manual__backfill_request_log_cost.js +8 -6
  40. package/bundle/dist/server-bootstrap.js +4 -2
  41. package/bundle/dist/server-error-handler.js +1 -1
  42. package/bundle/dist/sonamu.config.js +37 -8
  43. package/bundle/dist/startup-migrations.js +17 -7
  44. package/bundle/dist/testing/global.js +1 -1
  45. package/bundle/dist/testing/schema-bench.js +0 -0
  46. package/bundle/dist/testing/test-database-name.js +1 -1
  47. package/bundle/dist/utils/providers/anthropic/anthropic-constants.js +1 -1
  48. package/bundle/dist/utils/providers/anthropic/anthropic-dispatcher.js +11 -5
  49. package/bundle/dist/utils/providers/anthropic/anthropic-quota.js +4 -4
  50. package/bundle/dist/utils/providers/anthropic/anthropic-thinking.js +1 -1
  51. package/bundle/dist/utils/providers/anthropic/claude-session.js +20 -19
  52. package/bundle/dist/utils/providers/anthropic/fence-strip.js +1 -1
  53. package/bundle/dist/utils/providers/anthropic/stream-json-adapter.js +49 -23
  54. package/bundle/dist/utils/providers/common/credentials.js +5 -1
  55. package/bundle/dist/utils/providers/common/effort.js +3 -3
  56. package/bundle/dist/utils/providers/common/model-cost.js +69 -14
  57. package/bundle/dist/utils/providers/common/provider-types.js +29 -1
  58. package/bundle/dist/utils/providers/common/schema-validation.js +78 -40
  59. package/bundle/dist/utils/providers/common/smooth-weighted-round-robin.js +1 -1
  60. package/bundle/dist/utils/providers/common/strictifier.js +62 -43
  61. package/bundle/dist/utils/providers/common/thinking.js +3 -2
  62. package/bundle/dist/utils/providers/common/ttft.js +1 -1
  63. package/bundle/dist/utils/providers/grok/grok-backend-protocol.js +427 -0
  64. package/bundle/dist/utils/providers/grok/grok-constants.js +55 -0
  65. package/bundle/dist/utils/providers/grok/grok-direct-client.js +196 -0
  66. package/bundle/dist/utils/providers/grok/grok-dispatcher.js +1313 -0
  67. package/bundle/dist/utils/providers/grok/grok-headers.js +118 -0
  68. package/bundle/dist/utils/providers/grok/grok-media-client.js +420 -0
  69. package/bundle/dist/utils/providers/grok/grok-models.js +114 -0
  70. package/bundle/dist/utils/providers/grok/grok-oauth.js +455 -0
  71. package/bundle/dist/utils/providers/grok/grok-quota.js +119 -0
  72. package/bundle/dist/utils/providers/grok/grok-refresh.js +192 -0
  73. package/bundle/dist/utils/providers/grok/grok-types.js +186 -0
  74. package/bundle/dist/utils/providers/grok/grok-voice-client.js +199 -0
  75. package/bundle/dist/utils/providers/grok/index.js +11 -0
  76. package/bundle/dist/utils/providers/openai/openai-backend-protocol.js +111 -83
  77. package/bundle/dist/utils/providers/openai/openai-callback-relay.js +3 -2
  78. package/bundle/dist/utils/providers/openai/openai-constants.js +1 -1
  79. package/bundle/dist/utils/providers/openai/openai-direct-client.js +23 -7
  80. package/bundle/dist/utils/providers/openai/openai-dispatcher.js +143 -106
  81. package/bundle/dist/utils/providers/openai/openai-image-generation.js +48 -33
  82. package/bundle/dist/utils/providers/openai/openai-oauth.js +28 -13
  83. package/bundle/dist/utils/providers/openai/openai-quota.js +38 -27
  84. package/bundle/dist/utils/providers/openai/openai-refresh.js +17 -9
  85. package/bundle/dist/utils/providers/openai/openai-sse.js +1 -1
  86. package/bundle/dist/utils/providers/openai/openai-transport-config.js +1 -1
  87. package/bundle/dist/utils/quiet-hours.js +1 -1
  88. package/bundle/dist/utils/slack-notify.js +14 -8
  89. package/bundle/dist/utils/subset-loaders.js +4 -5
  90. package/bundle/src/application/monit/log-buffer.ts +23 -9
  91. package/bundle/src/application/monit/monit.frame.ts +22 -5
  92. package/bundle/src/application/monit/monit.types.ts +6 -0
  93. package/bundle/src/application/qgrid/conv-routing.test.ts +76 -4
  94. package/bundle/src/application/qgrid/conv-routing.ts +35 -9
  95. package/bundle/src/application/qgrid/expired-token-reminder.ts +9 -1
  96. package/bundle/src/application/qgrid/oauth.ts +51 -15
  97. package/bundle/src/application/qgrid/qgrid-image-generation.ts +9 -0
  98. package/bundle/src/application/qgrid/qgrid-response-format.ts +50 -25
  99. package/bundle/src/application/qgrid/qgrid-run-lifecycle.test.ts +19 -0
  100. package/bundle/src/application/qgrid/qgrid-run-lifecycle.ts +84 -40
  101. package/bundle/src/application/qgrid/qgrid.dispatcher.test.ts +97 -0
  102. package/bundle/src/application/qgrid/qgrid.dispatcher.ts +274 -126
  103. package/bundle/src/application/qgrid/qgrid.frame.test.ts +1013 -3
  104. package/bundle/src/application/qgrid/qgrid.frame.ts +1415 -60
  105. package/bundle/src/application/qgrid/qgrid.types.ts +335 -0
  106. package/bundle/src/application/qgrid/schema-prompt.ts +105 -41
  107. package/bundle/src/application/qgrid/slack-user-map.ts +5 -0
  108. package/bundle/src/application/qgrid/token-death.ts +4 -0
  109. package/bundle/src/application/qgrid/token-subscriber.test.ts +21 -19
  110. package/bundle/src/application/qgrid/token-subscriber.ts +186 -53
  111. package/bundle/src/application/qgrid/token-trigger-setup.ts +2 -0
  112. package/bundle/src/application/qgrid/token-window-keepalive.ts +49 -6
  113. package/bundle/src/application/qgrid/tool-emulation-schema.ts +33 -29
  114. package/bundle/src/application/qgrid/tool-emulation.ts +52 -27
  115. package/bundle/src/application/request-log/json-schema-type-text.test.ts +5 -5
  116. package/bundle/src/application/request-log/json-schema-type-text.ts +81 -26
  117. package/bundle/src/application/request-log/request-log.model.test.ts +111 -4
  118. package/bundle/src/application/request-log/request-log.model.ts +277 -146
  119. package/bundle/src/application/request-log/request-log.types.ts +6 -0
  120. package/bundle/src/application/request-log/zod-code-format.ts +11 -0
  121. package/bundle/src/application/request-log-step/request-log-step.model.ts +1 -0
  122. package/bundle/src/application/request-log-step/request-log-step.types.ts +2 -0
  123. package/bundle/src/application/setting/setting.constant.ts +9 -0
  124. package/bundle/src/application/setting/setting.model.ts +12 -2
  125. package/bundle/src/application/setting/setting.store.ts +5 -1
  126. package/bundle/src/application/setting/setting.types.ts +8 -0
  127. package/bundle/src/application/sonamu.generated.http +133 -0
  128. package/bundle/src/application/sonamu.generated.sso.ts +2 -0
  129. package/bundle/src/application/sonamu.generated.ts +15 -0
  130. package/bundle/src/application/token/token.identity.test.ts +20 -0
  131. package/bundle/src/application/token/token.model.ts +66 -7
  132. package/bundle/src/application/token/token.types.ts +24 -1
  133. package/bundle/src/i18n/en.ts +19 -0
  134. package/bundle/src/i18n/ko.ts +21 -0
  135. package/bundle/src/i18n/sd.generated.ts +23 -2
  136. package/bundle/src/migrations/20260807133553_manual__backfill_request_log_cost.ts +16 -8
  137. package/bundle/src/server-bootstrap.ts +4 -2
  138. package/bundle/src/server-error-handler.ts +1 -0
  139. package/bundle/src/sonamu.config.ts +69 -7
  140. package/bundle/src/startup-migrations.ts +27 -9
  141. package/bundle/src/testing/global.ts +20 -0
  142. package/bundle/src/testing/schema-bench.ts +105 -30
  143. package/bundle/src/testing/test-database-name.ts +2 -0
  144. package/bundle/src/utils/providers/anthropic/anthropic-constants.ts +8 -0
  145. package/bundle/src/utils/providers/anthropic/anthropic-dispatcher.ts +41 -6
  146. package/bundle/src/utils/providers/anthropic/anthropic-quota.ts +7 -3
  147. package/bundle/src/utils/providers/anthropic/anthropic-thinking.ts +6 -0
  148. package/bundle/src/utils/providers/anthropic/claude-session.ts +58 -24
  149. package/bundle/src/utils/providers/anthropic/fence-strip.ts +11 -0
  150. package/bundle/src/utils/providers/anthropic/stream-json-adapter.ts +110 -45
  151. package/bundle/src/utils/providers/common/credentials.ts +8 -0
  152. package/bundle/src/utils/providers/common/effort.ts +9 -2
  153. package/bundle/src/utils/providers/common/model-cost.test.ts +48 -1
  154. package/bundle/src/utils/providers/common/model-cost.ts +259 -158
  155. package/bundle/src/utils/providers/common/provider-dispatcher.ts +98 -2
  156. package/bundle/src/utils/providers/common/provider-types.ts +46 -3
  157. package/bundle/src/utils/providers/common/schema-validation.test.ts +5 -1
  158. package/bundle/src/utils/providers/common/schema-validation.ts +124 -56
  159. package/bundle/src/utils/providers/common/smooth-weighted-round-robin.ts +4 -0
  160. package/bundle/src/utils/providers/common/strictifier.ts +185 -59
  161. package/bundle/src/utils/providers/common/thinking.ts +2 -2
  162. package/bundle/src/utils/providers/common/ttft.ts +4 -2
  163. package/bundle/src/utils/providers/grok/__fixtures__/golden-responses-1.0.60.json +164 -0
  164. package/bundle/src/utils/providers/grok/grok-backend-protocol.test.ts +325 -0
  165. package/bundle/src/utils/providers/grok/grok-backend-protocol.ts +721 -0
  166. package/bundle/src/utils/providers/grok/grok-constants.ts +105 -0
  167. package/bundle/src/utils/providers/grok/grok-direct-client.test.ts +179 -0
  168. package/bundle/src/utils/providers/grok/grok-direct-client.ts +313 -0
  169. package/bundle/src/utils/providers/grok/grok-dispatcher.test.ts +1395 -0
  170. package/bundle/src/utils/providers/grok/grok-dispatcher.ts +2105 -0
  171. package/bundle/src/utils/providers/grok/grok-headers.test.ts +149 -0
  172. package/bundle/src/utils/providers/grok/grok-headers.ts +199 -0
  173. package/bundle/src/utils/providers/grok/grok-media-client.test.ts +446 -0
  174. package/bundle/src/utils/providers/grok/grok-media-client.ts +703 -0
  175. package/bundle/src/utils/providers/grok/grok-models.test.ts +96 -0
  176. package/bundle/src/utils/providers/grok/grok-models.ts +161 -0
  177. package/bundle/src/utils/providers/grok/grok-oauth.test.ts +406 -0
  178. package/bundle/src/utils/providers/grok/grok-oauth.ts +771 -0
  179. package/bundle/src/utils/providers/grok/grok-quota.test.ts +142 -0
  180. package/bundle/src/utils/providers/grok/grok-quota.ts +198 -0
  181. package/bundle/src/utils/providers/grok/grok-refresh.test.ts +239 -0
  182. package/bundle/src/utils/providers/grok/grok-refresh.ts +325 -0
  183. package/bundle/src/utils/providers/grok/grok-types.test.ts +252 -0
  184. package/bundle/src/utils/providers/grok/grok-types.ts +374 -0
  185. package/bundle/src/utils/providers/grok/grok-voice-client.test.ts +321 -0
  186. package/bundle/src/utils/providers/grok/grok-voice-client.ts +284 -0
  187. package/bundle/src/utils/providers/grok/index.ts +20 -0
  188. package/bundle/src/utils/providers/openai/openai-backend-protocol.ts +221 -137
  189. package/bundle/src/utils/providers/openai/openai-callback-relay.ts +12 -1
  190. package/bundle/src/utils/providers/openai/openai-constants.ts +2 -0
  191. package/bundle/src/utils/providers/openai/openai-direct-client.ts +111 -21
  192. package/bundle/src/utils/providers/openai/openai-dispatcher.ts +322 -169
  193. package/bundle/src/utils/providers/openai/openai-image-generation.ts +116 -36
  194. package/bundle/src/utils/providers/openai/openai-oauth.ts +48 -20
  195. package/bundle/src/utils/providers/openai/openai-quota.ts +77 -46
  196. package/bundle/src/utils/providers/openai/openai-refresh.ts +42 -18
  197. package/bundle/src/utils/providers/openai/openai-sse.ts +27 -3
  198. package/bundle/src/utils/providers/openai/openai-transport-config.ts +2 -0
  199. package/bundle/src/utils/quiet-hours.ts +12 -1
  200. package/bundle/src/utils/slack-notify.ts +44 -13
  201. package/bundle/src/utils/subset-loaders.ts +7 -7
  202. package/bundle/web-dist/client/assets/clsx-5XM_pHvN.js +1 -0
  203. package/bundle/web-dist/client/assets/index-Be2qCZmI.css +1 -0
  204. package/bundle/web-dist/client/assets/index-C96h3feE.js +32 -0
  205. package/bundle/web-dist/client/assets/logs-DBM6U0QD.js +1 -0
  206. package/bundle/web-dist/client/assets/monit-BBjXa7vB.js +1 -0
  207. package/bundle/web-dist/client/assets/routes-Crz_PQzi.js +5 -0
  208. package/bundle/web-dist/client/assets/{services.generated-NP-ValHd.js → services.generated-B5lc3aIC.js} +5 -5
  209. package/bundle/web-dist/client/assets/settings-Cdwm3OBB.js +1 -0
  210. package/bundle/web-dist/client/assets/show-DBUhALT1.js +2 -0
  211. package/bundle/web-dist/client/assets/tokens-CVQAVJTo.js +1 -0
  212. package/bundle/web-dist/client/assets/total-cost-MnfKU8IN.js +1 -0
  213. package/bundle/web-dist/client/assets/use-oauth-login-flow-BfxYI4Oh.js +1 -0
  214. package/bundle/web-dist/client/assets/use-update-token-mutation-qKBC2sn7.js +1 -0
  215. package/bundle/web-dist/client/assets/{vendor-tanstack-DJRQAbrY.js → vendor-tanstack-DRPs09ai.js} +1 -1
  216. package/bundle/web-dist/client/index.html +5 -5
  217. package/bundle/web-dist/server/assets/clsx-D_N5PN_2.js +16 -0
  218. package/bundle/web-dist/server/assets/{lazyRouteComponent-CgBdabgd.js → lazyRouteComponent-AKiHfZWp.js} +129 -2
  219. package/bundle/web-dist/server/assets/{logs-BMyR98rU.js → logs-B7QmGJq3.js} +18 -14
  220. package/bundle/web-dist/server/assets/{monit-ZPt66W5M.js → monit-DFt7x3bh.js} +13 -12
  221. package/bundle/web-dist/server/assets/{plus-BZ9I0VgC.js → plus-DaYQRpBt.js} +1 -1
  222. package/bundle/web-dist/server/assets/{react-dom-Ds75e4Jl.js → react-dom-Cv23mm1K.js} +2 -129
  223. package/bundle/web-dist/server/assets/{refresh-cw-CztCFis-.js → refresh-cw-BpXNsAy-.js} +1 -1
  224. package/bundle/web-dist/server/assets/{request-model-DjGldb-h.js → request-model-eHE6YbaF.js} +1 -1
  225. package/bundle/web-dist/server/assets/{routes-Ct-k7nBA.js → routes-_Y-PJfgO.js} +110 -39
  226. package/bundle/web-dist/server/assets/{services.generated-BA4AcAf5.js → services.generated-CMJ8AHbO.js} +582 -172
  227. package/bundle/web-dist/server/assets/{settings-CBUZ47Xx.js → settings-JwJKxn2e.js} +14 -14
  228. package/bundle/web-dist/server/assets/{show-LSFIzvO2.js → show-BEv6z1v_.js} +90 -60
  229. package/bundle/web-dist/server/assets/{tokens-DxrBW512.js → tokens-CNT4mI-f.js} +162 -50
  230. package/bundle/web-dist/server/assets/total-cost-fTE9Xy82.js +18 -0
  231. package/bundle/web-dist/server/assets/use-oauth-login-flow-jkAkT3X6.js +2345 -0
  232. package/bundle/web-dist/server/assets/{use-update-token-mutation-pkArIzCP.js → use-update-token-mutation-Sm6kh02m.js} +1 -1
  233. package/bundle/web-dist/server/entry-server.generated.js +1644 -21655
  234. package/dist/cli.js +9 -9
  235. package/package.json +4 -11
  236. package/postinstall.mjs +12 -0
  237. package/skills/qgrid/SKILL.md +1 -0
  238. package/skills/qgrid/references/cli-env-and-server-boot.md +4 -1
  239. package/skills/qgrid/references/decision-rationale.md +23 -0
  240. package/skills/qgrid/references/grok-runtime.md +89 -0
  241. package/skills/qgrid/references/provider-runtime-differences.md +30 -29
  242. package/skills/qgrid/references/repo-map.md +10 -0
  243. package/skills/qgrid/references/token-auth-quota-lifecycle.md +56 -3
  244. package/bundle/dist/node_modules/.pnpm/@jridgewell_sourcemap-codec@1.5.5/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.js +0 -406
  245. package/bundle/dist/node_modules/.pnpm/@vitest_expect@4.1.10/node_modules/@vitest/expect/dist/index.js +0 -1507
  246. package/bundle/dist/node_modules/.pnpm/@vitest_pretty-format@4.1.10/node_modules/@vitest/pretty-format/dist/index.js +0 -887
  247. package/bundle/dist/node_modules/.pnpm/@vitest_runner@4.1.10/node_modules/@vitest/runner/dist/chunk-artifact.js +0 -2756
  248. package/bundle/dist/node_modules/.pnpm/@vitest_runner@4.1.10/node_modules/@vitest/runner/dist/index.js +0 -8
  249. package/bundle/dist/node_modules/.pnpm/@vitest_runner@4.1.10/node_modules/@vitest/runner/dist/utils.js +0 -8
  250. package/bundle/dist/node_modules/.pnpm/@vitest_snapshot@4.1.10/node_modules/@vitest/snapshot/dist/index.js +0 -922
  251. package/bundle/dist/node_modules/.pnpm/@vitest_spy@4.1.10/node_modules/@vitest/spy/dist/index.js +0 -386
  252. package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/chunk-pathe.M-eThtNZ.js +0 -121
  253. package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/constants.js +0 -51
  254. package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/diff.js +0 -1317
  255. package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/display.js +0 -561
  256. package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/error.js +0 -40
  257. package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/helpers.js +0 -272
  258. package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/offset.js +0 -27
  259. package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/serialize.js +0 -77
  260. package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/source-map.js +0 -381
  261. package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/timers.js +0 -51
  262. package/bundle/dist/node_modules/.pnpm/chai@6.2.2/node_modules/chai/index.js +0 -2986
  263. package/bundle/dist/node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/branding.js +0 -10
  264. package/bundle/dist/node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/index.js +0 -109
  265. package/bundle/dist/node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/messages.js +0 -10
  266. package/bundle/dist/node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/overloads.js +0 -10
  267. package/bundle/dist/node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/utils.js +0 -10
  268. package/bundle/dist/node_modules/.pnpm/magic-string@0.30.21/node_modules/magic-string/dist/magic-string.es.js +0 -1126
  269. package/bundle/dist/node_modules/.pnpm/pathe@2.0.3/node_modules/pathe/dist/index.js +0 -21
  270. package/bundle/dist/node_modules/.pnpm/pathe@2.0.3/node_modules/pathe/dist/shared/pathe.M-eThtNZ.js +0 -432
  271. package/bundle/dist/node_modules/.pnpm/tinybench@2.9.0/node_modules/tinybench/dist/index.js +0 -517
  272. package/bundle/dist/node_modules/.pnpm/tinyrainbow@3.1.0/node_modules/tinyrainbow/dist/index.js +0 -101
  273. package/bundle/dist/node_modules/.pnpm/vite@8.0.3_@emnapi_core@1.11.2_@emnapi_runtime@1.11.2_@types_node@25.0.7_esbuild@0.27.4_5b35fc1fb85d2af4d105e0d74f940f50/node_modules/vite/dist/node/module-runner.js +0 -1265
  274. package/bundle/dist/node_modules/.pnpm/vitest@4.1.10_@opentelemetry_api@1.9.0_@types_node@25.0.7_@vitest_coverage-v8@4.1.10_vi_a1977b93af7bb69308bcb1dbab39b618/node_modules/vitest/dist/chunks/_commonjsHelpers.D26ty3Ew.js +0 -9
  275. package/bundle/dist/node_modules/.pnpm/vitest@4.1.10_@opentelemetry_api@1.9.0_@types_node@25.0.7_@vitest_coverage-v8@4.1.10_vi_a1977b93af7bb69308bcb1dbab39b618/node_modules/vitest/dist/chunks/benchmark.CX_oY03V.js +0 -41
  276. package/bundle/dist/node_modules/.pnpm/vitest@4.1.10_@opentelemetry_api@1.9.0_@types_node@25.0.7_@vitest_coverage-v8@4.1.10_vi_a1977b93af7bb69308bcb1dbab39b618/node_modules/vitest/dist/chunks/evaluatedModules.Dg1zASAC.js +0 -19
  277. package/bundle/dist/node_modules/.pnpm/vitest@4.1.10_@opentelemetry_api@1.9.0_@types_node@25.0.7_@vitest_coverage-v8@4.1.10_vi_a1977b93af7bb69308bcb1dbab39b618/node_modules/vitest/dist/chunks/index.Chj8NDwU.js +0 -202
  278. package/bundle/dist/node_modules/.pnpm/vitest@4.1.10_@opentelemetry_api@1.9.0_@types_node@25.0.7_@vitest_coverage-v8@4.1.10_vi_a1977b93af7bb69308bcb1dbab39b618/node_modules/vitest/dist/chunks/index.DdgEv5B1.js +0 -46
  279. package/bundle/dist/node_modules/.pnpm/vitest@4.1.10_@opentelemetry_api@1.9.0_@types_node@25.0.7_@vitest_coverage-v8@4.1.10_vi_a1977b93af7bb69308bcb1dbab39b618/node_modules/vitest/dist/chunks/rpc.MzXet3jl.js +0 -119
  280. package/bundle/dist/node_modules/.pnpm/vitest@4.1.10_@opentelemetry_api@1.9.0_@types_node@25.0.7_@vitest_coverage-v8@4.1.10_vi_a1977b93af7bb69308bcb1dbab39b618/node_modules/vitest/dist/chunks/test.DNmyFkvJ.js +0 -3044
  281. package/bundle/dist/node_modules/.pnpm/vitest@4.1.10_@opentelemetry_api@1.9.0_@types_node@25.0.7_@vitest_coverage-v8@4.1.10_vi_a1977b93af7bb69308bcb1dbab39b618/node_modules/vitest/dist/chunks/utils.BX5Fg8C4.js +0 -59
  282. package/bundle/dist/node_modules/.pnpm/vitest@4.1.10_@opentelemetry_api@1.9.0_@types_node@25.0.7_@vitest_coverage-v8@4.1.10_vi_a1977b93af7bb69308bcb1dbab39b618/node_modules/vitest/dist/index.js +0 -28
  283. package/bundle/dist/testing/setup-mocks.js +0 -16
  284. package/bundle/src/testing/setup-mocks.ts +0 -44
  285. package/bundle/web-dist/client/assets/index-Bw2s88Ot.css +0 -1
  286. package/bundle/web-dist/client/assets/index-DZwzUfI-.js +0 -106
  287. package/bundle/web-dist/client/assets/logs-Cog79DU5.js +0 -1
  288. package/bundle/web-dist/client/assets/monit-2auznBys.js +0 -1
  289. package/bundle/web-dist/client/assets/routes-B0lTeLK-.js +0 -5
  290. package/bundle/web-dist/client/assets/settings-D8ZxZGSJ.js +0 -1
  291. package/bundle/web-dist/client/assets/show-CFSERlnM.js +0 -2
  292. package/bundle/web-dist/client/assets/tokens-CZXUyI7O.js +0 -1
  293. package/bundle/web-dist/client/assets/use-oauth-login-flow-hboOs3wG.js +0 -1
  294. package/bundle/web-dist/client/assets/use-update-token-mutation-BQHS0byG.js +0 -1
  295. package/bundle/web-dist/server/assets/use-oauth-login-flow-Gn8flMK5.js +0 -178
  296. package/dist/cli.d.ts +0 -10
  297. /package/bundle/web-dist/client/assets/{cost-DH2jZbrw.js → cost-DB3HbU7T.js} +0 -0
  298. /package/bundle/web-dist/client/assets/{plus-B62CuqT8.js → plus-BsQdY7PS.js} +0 -0
  299. /package/bundle/web-dist/client/assets/{preload-helper-ca-nBW7U.js → preload-helper-DWTEM3RW.js} +0 -0
  300. /package/bundle/web-dist/client/assets/{refresh-cw-NNcU0Cmq.js → refresh-cw-D9IUmT23.js} +0 -0
  301. /package/bundle/web-dist/client/assets/{request-model-CojtinqK.js → request-model-Bpp_rkK8.js} +0 -0
  302. /package/bundle/web-dist/server/assets/{cost-eEHeTRT5.js → cost-BC9yzyA9.js} +0 -0
@@ -0,0 +1,2105 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+
3
+ import { getLogger } from "@logtape/logtape";
4
+ import { z } from "zod";
5
+
6
+ import {
7
+ type GrokProviderDispatcher,
8
+ type GrokTokenRow,
9
+ } from "../../../application/qgrid/qgrid.dispatcher";
10
+ import { QuotaThresholdExceededError } from "../../../application/qgrid/qgrid.types";
11
+ import {
12
+ type GenerateRequest,
13
+ type GenerateResult,
14
+ type GenerateStreamCallbacks,
15
+ type MediaDispatcher,
16
+ type MediaDispatchResult,
17
+ type MediaTokenSelection,
18
+ type ProviderToolCall,
19
+ type ToolDefinition,
20
+ type VoiceDispatcher,
21
+ type XaiProviderMetadata,
22
+ } from "../common/provider-dispatcher";
23
+ import { isJsonObject, type JsonValue, jsonString } from "../common/provider-types";
24
+ import { SmoothWeightedRoundRobin } from "../common/smooth-weighted-round-robin";
25
+ import { ThinkingValidationError } from "../common/thinking";
26
+ import {
27
+ buildGrokResponsesRequest,
28
+ type GrokFunctionTool,
29
+ type GrokInputContentPart,
30
+ type GrokInputItem,
31
+ type GrokResponsesRequest,
32
+ type GrokTool,
33
+ type GrokUsage,
34
+ } from "./grok-backend-protocol";
35
+ import { GROK_BUILD_VERSION, GROK_USD_PER_TICK, type GrokReasoningEffort } from "./grok-constants";
36
+ import { GrokChatClient, GrokHttpError, GrokIdleTimeoutError } from "./grok-direct-client";
37
+ import { buildGrokChatHeaders } from "./grok-headers";
38
+ import { GrokModelCatalog, type GrokResolvedModel } from "./grok-models";
39
+ import {
40
+ readGrokBilling,
41
+ readGrokSettings,
42
+ readGrokSubscription,
43
+ type GrokQuotaUsageResult,
44
+ type GrokSettingsResult,
45
+ type GrokSubscriptionResult,
46
+ } from "./grok-quota";
47
+ import { grokNeedsRefresh } from "./grok-refresh";
48
+ import {
49
+ GrokCredentials,
50
+ type GrokImageEditRequest,
51
+ type GrokImageGenerateRequest,
52
+ type GrokImageResponse,
53
+ type GrokMediaAuth,
54
+ type GrokMediaPort,
55
+ type GrokRefresher,
56
+ type GrokSpeechResult,
57
+ type GrokSttResponse,
58
+ type GrokTranscribeRequest,
59
+ type GrokTtsRequest,
60
+ type GrokVideoStart,
61
+ type GrokVideoStartRequest,
62
+ type GrokVideoStatus,
63
+ type GrokVoicePort,
64
+ } from "./grok-types";
65
+
66
+ const logger = getLogger(["qgrid", "grok-dispatcher"]);
67
+
68
+ export type GrokRequestClass = "chat" | "image" | "video" | "stt" | "tts";
69
+
70
+ export const GROK_LOOKUP_CACHE_TTL_MS = 60_000;
71
+ export const GROK_MAX_COOLDOWN_MS = 120_000;
72
+ export const GROK_DEFAULT_COOLDOWN_MS = 10_000;
73
+ export const GROK_MAX_COOLDOWN_WAIT_MS = 30_000;
74
+ export const GROK_QUOTA_BLOCK_FALLBACK_MS = 3_600_000;
75
+ export const GROK_ENTITLEMENT_BLOCK_MS = 3_600_000;
76
+ export const GROK_VERSION_GATE_REPROBE_MS = 600_000;
77
+ export const GROK_MAX_HOPS = 2;
78
+ export const GROK_MAX_REFRESHES = 1;
79
+ export const GROK_MAX_TRANSIENT_FAILOVERS = 1;
80
+
81
+ /** A token whose pre-lookup refresh failed transiently sits out selection this long (§5.1). */
82
+ export const GROK_REFRESH_FAILED_COOLDOWN_MS = 5_000;
83
+
84
+ /** Retries with the token's current credentials after a 401 that a newer bearer superseded. */
85
+ export const GROK_MAX_STALE_AUTH_RETRIES = 1;
86
+ const DEFAULT_REQUEST_TIMEOUT_MS = 600_000;
87
+ const DEFAULT_IDLE_TIMEOUT_MS = 300_000;
88
+
89
+ // ---------------------------------------------------------------------------
90
+ // Errors
91
+ // ---------------------------------------------------------------------------
92
+
93
+ export class GrokInvalidRequestError extends Error {
94
+ readonly code = "GROK_INVALID_REQUEST" as const;
95
+
96
+ constructor(message: string) {
97
+ super(message);
98
+ this.name = "GrokInvalidRequestError";
99
+ }
100
+ }
101
+
102
+ export class GrokRateLimitedError extends Error {
103
+ readonly code = "GROK_RATE_LIMITED" as const;
104
+ readonly retryAfterSec: number;
105
+
106
+ constructor(retryAfterSec: number, message?: string) {
107
+ super(message ?? `All grok tokens are rate limited; retry after ${retryAfterSec}s`);
108
+ this.name = "GrokRateLimitedError";
109
+ this.retryAfterSec = retryAfterSec;
110
+ }
111
+ }
112
+
113
+ export class GrokCapacityError extends Error {
114
+ readonly code = "GROK_CAPACITY" as const;
115
+
116
+ constructor(message = "All grok tokens are at their in-flight request cap") {
117
+ super(message);
118
+ this.name = "GrokCapacityError";
119
+ }
120
+ }
121
+
122
+ export class GrokOnDemandExcludedError extends Error {
123
+ readonly code = "GROK_ON_DEMAND_EXCLUDED" as const;
124
+ readonly tokenNames: string[];
125
+
126
+ constructor(tokenNames: string[]) {
127
+ super(
128
+ `All grok tokens are excluded because on-demand billing is enabled: ${tokenNames.join(", ")}`,
129
+ );
130
+ this.name = "GrokOnDemandExcludedError";
131
+ this.tokenNames = tokenNames;
132
+ }
133
+ }
134
+
135
+ /**
136
+ * xAI refuses video generation for Zero Data Retention accounts unless the caller supplies its own
137
+ * `output.upload_url` (plan §11), which qgrid does not implement.
138
+ */
139
+ export class GrokZdrVideoUnavailableError extends Error {
140
+ readonly code = "GROK_ZDR_VIDEO_UNAVAILABLE" as const;
141
+ readonly tokenNames: string[];
142
+
143
+ constructor(tokenNames: string[]) {
144
+ super(
145
+ `Grok video is unavailable because Zero Data Retention is on for: ${tokenNames.join(", ")}. Turn off the data-retention opt-out at xAI, or use a non-ZDR account.`,
146
+ );
147
+ this.name = "GrokZdrVideoUnavailableError";
148
+ this.tokenNames = tokenNames;
149
+ }
150
+ }
151
+
152
+ /** The upstream 400 body for a ZDR account on POST /v1/videos/generations. */
153
+ const ZDR_VIDEO_REJECTION = /zero data retention/i;
154
+
155
+ export function isGrokZdrVideoRejection(error: GrokHttpError): boolean {
156
+ return (
157
+ error.status === 400 &&
158
+ (ZDR_VIDEO_REJECTION.test(error.classification.message ?? "") ||
159
+ ZDR_VIDEO_REJECTION.test(error.bodyText))
160
+ );
161
+ }
162
+
163
+ export class GrokVersionGateError extends Error {
164
+ readonly code = "GROK_VERSION_GATE" as const;
165
+ readonly minClientVersion: string | undefined;
166
+
167
+ constructor(minClientVersion: string | undefined) {
168
+ super(
169
+ `Grok chat proxy rejected client version ${GROK_BUILD_VERSION} (HTTP 426; requires ${
170
+ minClientVersion ?? "a newer version"
171
+ } or later). Bump GROK_BUILD_VERSION in grok-constants.ts and re-capture the golden fixture.`,
172
+ );
173
+ this.name = "GrokVersionGateError";
174
+ this.minClientVersion = minClientVersion;
175
+ }
176
+ }
177
+
178
+ export class GrokAttemptBudgetExceededError extends Error {
179
+ readonly code = "GROK_ATTEMPT_BUDGET_EXCEEDED" as const;
180
+
181
+ constructor(attempts: number, cause: unknown) {
182
+ super(
183
+ `Grok request failed after ${attempts} attempts: ${
184
+ cause instanceof Error ? cause.message : String(cause)
185
+ }`,
186
+ { cause },
187
+ );
188
+ this.name = "GrokAttemptBudgetExceededError";
189
+ }
190
+ }
191
+
192
+ export class GrokNoWorkersError extends Error {
193
+ readonly code = "NO_GROK_WORKERS" as const;
194
+
195
+ constructor(message = "NO_GROK_WORKERS") {
196
+ super(message);
197
+ this.name = "GrokNoWorkersError";
198
+ }
199
+ }
200
+
201
+ export class GrokCapabilityUnavailableError extends Error {
202
+ readonly code = "GROK_CAPABILITY_UNAVAILABLE" as const;
203
+
204
+ constructor(capability: string) {
205
+ super(`Grok ${capability} is not configured on this server`);
206
+ this.name = "GrokCapabilityUnavailableError";
207
+ }
208
+ }
209
+
210
+ /** The refresher could not produce usable credentials for a token before the request was sent. */
211
+ export class GrokCredentialRefreshError extends Error {
212
+ readonly code = "GROK_CREDENTIAL_REFRESH" as const;
213
+ readonly reauthRequired: boolean;
214
+
215
+ /**
216
+ * `reauthRequired` defaults to the cause's convention: the port signals a dead refresh token with
217
+ * GrokReauthRequiredError (grok-refresh.ts), checked by name so any GrokRefresher implementation
218
+ * can use it.
219
+ */
220
+ constructor(
221
+ cause: unknown,
222
+ reauthRequired = cause instanceof Error && cause.name === "GrokReauthRequiredError",
223
+ ) {
224
+ super(
225
+ `Grok credential refresh failed: ${cause instanceof Error ? cause.message : String(cause)}`,
226
+ { cause },
227
+ );
228
+ this.name = "GrokCredentialRefreshError";
229
+ this.reauthRequired = reauthRequired;
230
+ }
231
+
232
+ /** The refresher answered `reauth_required`: the refresh token is dead. */
233
+ static reauthRequired(reason: string): GrokCredentialRefreshError {
234
+ return new GrokCredentialRefreshError(reason, true);
235
+ }
236
+ }
237
+
238
+ export class GrokIncompleteResponseError extends Error {
239
+ readonly code = "GROK_INCOMPLETE_RESPONSE" as const;
240
+ readonly reason: string | undefined;
241
+
242
+ constructor(reason: string | undefined) {
243
+ super(`Grok response incomplete${reason ? `: ${reason}` : ""}`);
244
+ this.name = "GrokIncompleteResponseError";
245
+ this.reason = reason;
246
+ }
247
+ }
248
+
249
+ // ---------------------------------------------------------------------------
250
+ // Token state (§5.1)
251
+ // ---------------------------------------------------------------------------
252
+
253
+ type Cached<T> = { value: T; fetchedAt: number; generation: number };
254
+
255
+ type IneligibleReason =
256
+ | "inactive"
257
+ | "reauth"
258
+ | "refresh_failed"
259
+ | "on_demand"
260
+ | "zdr"
261
+ | "entitlement"
262
+ | "quota"
263
+ | "cooldown"
264
+ | "capacity";
265
+
266
+ export interface GrokTokenState {
267
+ id: number;
268
+ name: string;
269
+ weight: number;
270
+ quotaThreshold: number | null;
271
+ credentials: GrokCredentials | undefined;
272
+ invalidReason: string | undefined;
273
+ generation: number;
274
+ inFlight: number;
275
+ cooldowns: Map<GrokRequestClass, number>;
276
+ quotaBlocks: Map<GrokRequestClass, number>;
277
+ entitlementBlocks: Map<GrokRequestClass, number>;
278
+ reauthRequired: boolean;
279
+ /**
280
+ * Until when the token is out of selection after a transient pre-lookup refresh failure: its
281
+ * gating lookups could not run with a valid bearer, so the gates are unknown.
282
+ */
283
+ refreshFailedUntil: number;
284
+ refreshFailure: unknown;
285
+ billing: Cached<GrokQuotaUsageResult> | undefined;
286
+ billingNotBefore: number;
287
+ settings: Cached<GrokSettingsResult> | undefined;
288
+ subscription: Cached<GrokSubscriptionResult> | undefined;
289
+ /**
290
+ * Zero Data Retention (plan §11): true excludes the token from video. Learned from
291
+ * /user codingDataRetentionOptOut, confirmed by `x-zero-data-retention: true` or the upstream
292
+ * ZDR 400; null is unknown and eligible.
293
+ */
294
+ zdr: boolean | null;
295
+ overThreshold: boolean;
296
+ lastTier: string | undefined;
297
+ failoverCount: number;
298
+ }
299
+
300
+ export interface GrokTokenStateSnapshot {
301
+ id: number;
302
+ name: string;
303
+ weight: number;
304
+ active: boolean;
305
+ invalidReason: string | null;
306
+ inFlight: number;
307
+ cooldownUntil: Partial<Record<GrokRequestClass, number>>;
308
+ quotaBlockedUntil: Partial<Record<GrokRequestClass, number>>;
309
+ entitlementBlocks: Partial<Record<GrokRequestClass, number>>;
310
+ lastBilling: {
311
+ utilizationPct: number;
312
+ periodType: string;
313
+ periodEnd: string;
314
+ fetchedAt: number;
315
+ } | null;
316
+ quotaThreshold: number | null;
317
+ overThreshold: boolean;
318
+ onDemandExcluded: boolean;
319
+ zdrVideoExcluded: boolean;
320
+ lastTier: string | null;
321
+ reauthRequired: boolean;
322
+ failoverCount: number;
323
+ }
324
+
325
+ interface Reservation {
326
+ token: GrokTokenState;
327
+ release(): void;
328
+ }
329
+
330
+ // ---------------------------------------------------------------------------
331
+ // Request mapping
332
+ // ---------------------------------------------------------------------------
333
+
334
+ const ContentPart = z.discriminatedUnion("type", [
335
+ z.object({ type: z.literal("input_text"), text: z.string() }),
336
+ z.object({ type: z.literal("input_image"), image_url: z.string().min(1) }),
337
+ z.object({ type: z.literal("output_text"), text: z.string() }),
338
+ ]);
339
+
340
+ const HistoryItem = z.union([
341
+ z.object({
342
+ type: z.literal("message").optional(),
343
+ role: z.enum(["user", "assistant"]),
344
+ content: z.union([z.string(), z.array(ContentPart)]),
345
+ }),
346
+ z.object({
347
+ type: z.literal("function_call"),
348
+ call_id: z.string().min(1),
349
+ name: z.string().min(1),
350
+ arguments: z.string(),
351
+ id: z.string().optional(),
352
+ }),
353
+ z.object({
354
+ type: z.literal("function_call_output"),
355
+ call_id: z.string().min(1),
356
+ output: z.string(),
357
+ }),
358
+ z.object({
359
+ type: z.literal("reasoning"),
360
+ id: z.string().optional(),
361
+ summary: z.array(z.object({ type: z.literal("summary_text"), text: z.string() })).optional(),
362
+ encrypted_content: z.string().optional(),
363
+ }),
364
+ ]);
365
+
366
+ function historyItems(history: readonly JsonValue[] | undefined): GrokInputItem[] {
367
+ return (history ?? []).map((raw, index) => {
368
+ const parsed = HistoryItem.safeParse(raw);
369
+
370
+ if (!parsed.success) {
371
+ throw new GrokInvalidRequestError(`Unsupported Grok history item at index ${index}`);
372
+ }
373
+
374
+ const item = parsed.data;
375
+
376
+ if ("role" in item) return { type: "message", role: item.role, content: item.content };
377
+
378
+ return item;
379
+ });
380
+ }
381
+
382
+ function userMessage(req: GenerateRequest): GrokInputItem {
383
+ const content: GrokInputContentPart[] = req.coldInput.map((input) => {
384
+ if (input.type === "text") return { type: "input_text", text: input.text };
385
+
386
+ if (input.type === "image") return { type: "input_image", image_url: input.url };
387
+
388
+ if (input.type === "localImage") {
389
+ throw new GrokInvalidRequestError("Grok does not accept local image paths; send a data URL");
390
+ }
391
+
392
+ return { type: "input_text", text: `${input.name}: ${input.path}` };
393
+ });
394
+
395
+ return { type: "message", role: "user", content };
396
+ }
397
+
398
+ function grokTools(tools: readonly ToolDefinition[] | undefined): GrokTool[] | undefined {
399
+ if (!tools?.length) return undefined;
400
+
401
+ return tools.map((tool): GrokTool => {
402
+ if (tool.type !== "function") return { type: tool.type };
403
+
404
+ const fn: GrokFunctionTool = {
405
+ type: "function",
406
+ name: tool.name,
407
+ parameters: tool.parameters,
408
+ strict: tool.strict ?? false,
409
+ };
410
+
411
+ if (tool.description !== undefined) fn.description = tool.description;
412
+
413
+ return fn;
414
+ });
415
+ }
416
+
417
+ /** Deterministic UUID-shaped id, so one qgrid conversation maps to one Grok conversation. */
418
+ function derivedGuid(namespace: string, key: string): string {
419
+ const hex = createHash("sha256").update(`${namespace}\0${key}`).digest("hex");
420
+ const variant = ((Number.parseInt(hex[16]!, 16) & 0x3) | 0x8).toString(16);
421
+
422
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-4${hex.slice(13, 16)}-${variant}${hex.slice(17, 20)}-${hex.slice(20, 32)}`;
423
+ }
424
+
425
+ export interface GrokConversationIds {
426
+ convId: string;
427
+ convGroupId: string;
428
+ }
429
+
430
+ export function grokConversationIds(promptCacheKey: string | undefined): GrokConversationIds {
431
+ const convId = promptCacheKey ? derivedGuid("grok-conv", promptCacheKey) : randomUUID();
432
+
433
+ return { convId, convGroupId: derivedGuid("grok-conv-group", convId) };
434
+ }
435
+
436
+ /** USD from cost_in_usd_ticks, rounded to the tick resolution (1e-10). */
437
+ export function grokTicksToUsd(ticks: number): number {
438
+ return Number((ticks * GROK_USD_PER_TICK).toFixed(10));
439
+ }
440
+
441
+ function parseMaxInflight(value: string | undefined): number | undefined {
442
+ if (value === undefined || value.trim() === "") return undefined;
443
+ const parsed = Number(value);
444
+
445
+ if (!Number.isSafeInteger(parsed) || parsed < 1) {
446
+ logger.warn(`ignoring QGRID_GROK_MAX_INFLIGHT_PER_TOKEN=${value}: not a positive integer`);
447
+
448
+ return undefined;
449
+ }
450
+
451
+ return parsed;
452
+ }
453
+
454
+ function abortReason(signal: AbortSignal): Error {
455
+ const reason: unknown = signal.reason;
456
+
457
+ return reason instanceof Error ? reason : new DOMException("Aborted", "AbortError");
458
+ }
459
+
460
+ /** Imagine calls carry the token's session id like grok-build's per-request session header. */
461
+ function mediaAuth(credentials: GrokCredentials): GrokMediaAuth {
462
+ return { bearer: credentials.accessToken, sessionId: credentials.sessionId };
463
+ }
464
+
465
+ function defaultSleep(ms: number, signal?: AbortSignal): Promise<void> {
466
+ return new Promise((resolve, reject) => {
467
+ if (signal?.aborted) {
468
+ reject(abortReason(signal));
469
+
470
+ return;
471
+ }
472
+
473
+ const onAbort = () => {
474
+ clearTimeout(timer);
475
+ reject(abortReason(signal!));
476
+ };
477
+
478
+ const timer = setTimeout(() => {
479
+ signal?.removeEventListener("abort", onAbort);
480
+ resolve();
481
+ }, ms);
482
+
483
+ signal?.addEventListener("abort", onAbort, { once: true });
484
+ });
485
+ }
486
+
487
+ function quotaMessage(entries: Array<{ name: string; threshold: number | null }>): string {
488
+ const detail = entries
489
+ .map((e) =>
490
+ e.threshold === null ? `${e.name} (exhausted)` : `${e.name} (threshold ${e.threshold}%)`,
491
+ )
492
+ .join(", ");
493
+
494
+ return detail
495
+ ? `All grok tokens exceeded quota threshold: ${detail}`
496
+ : "All grok tokens exceeded quota threshold";
497
+ }
498
+
499
+ function mapRecord(map: Map<GrokRequestClass, number>, now: number) {
500
+ return Object.fromEntries([...map].filter(([, until]) => until > now));
501
+ }
502
+
503
+ /**
504
+ * Persists a proven re-login requirement (`tokens.reauth_required`), mirroring the other providers'
505
+ * auth-death path. `credentials` are the ones the failed attempt presented; the store skips the
506
+ * write when the row has rotated since.
507
+ */
508
+ export type GrokReauthReporter = (
509
+ token: { id: number; name: string; credentials: GrokCredentials },
510
+ reasonCode: string,
511
+ ) => Promise<boolean>;
512
+
513
+ async function persistGrokReauth(
514
+ token: { id: number; name: string; credentials: GrokCredentials },
515
+ reasonCode: string,
516
+ ): Promise<boolean> {
517
+ // Loaded lazily: the token model pulls in the database layer, which the runtime does not need
518
+ // until a token actually dies.
519
+ const { deactivateAuthDeadToken } = await import("../../../application/qgrid/token-death");
520
+
521
+ return deactivateAuthDeadToken({ ...token, provider: "grok" }, reasonCode);
522
+ }
523
+
524
+ /** Internal `grok:<code>` reason; provider text may carry account data, so only codes pass. */
525
+ function reauthReasonCode(code: string | undefined): string {
526
+ return code !== undefined && /^[a-z_0-9]{1,80}$/.test(code) ? `grok:${code}` : "grok:unknown";
527
+ }
528
+
529
+ const oauthErrorCodeSchema = z.string();
530
+
531
+ function oauthErrorOf(cause: unknown): string | undefined {
532
+ return cause instanceof Error && "oauthError" in cause
533
+ ? oauthErrorCodeSchema.safeParse(cause.oauthError).data
534
+ : undefined;
535
+ }
536
+
537
+ /** The same bearer and refresh token: a failure judged against `b` applies to `a`. */
538
+ function sameCredentials(a: GrokCredentials | undefined, b: GrokCredentials): boolean {
539
+ return a?.accessToken === b.accessToken && a.refreshToken === b.refreshToken;
540
+ }
541
+
542
+ // ---------------------------------------------------------------------------
543
+ // Dispatcher
544
+ // ---------------------------------------------------------------------------
545
+
546
+ export interface GrokDispatcherOptions {
547
+ refresher: GrokRefresher;
548
+ fetch?: typeof fetch;
549
+ media?: GrokMediaPort;
550
+ voice?: GrokVoicePort;
551
+ now?: () => number;
552
+ /** Default: QGRID_GROK_MAX_INFLIGHT_PER_TOKEN; undefined means uncapped (§5.6). */
553
+ maxInflightPerToken?: number;
554
+ sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
555
+ client?: GrokChatClient;
556
+ catalog?: GrokModelCatalog;
557
+ idleTimeoutMs?: number;
558
+ /** Default: deactivateAuthDeadToken (application/qgrid/token-death.ts). */
559
+ reportReauthRequired?: GrokReauthReporter;
560
+ }
561
+
562
+ type FailureAction =
563
+ | { kind: "fail"; error: unknown }
564
+ | { kind: "refresh"; presented: GrokCredentials }
565
+ | { kind: "retry"; error: unknown }
566
+ | { kind: "failover"; error: unknown; transient: boolean };
567
+
568
+ /** Names of the tokens that were ineligible for exactly `reason`. */
569
+ function tokenNamesWithReason(
570
+ reasons: ReadonlyMap<GrokTokenState, IneligibleReason>,
571
+ reason: IneligibleReason,
572
+ ): string[] {
573
+ return [...reasons].flatMap(([token, tokenReason]) =>
574
+ tokenReason === reason ? [token.name] : [],
575
+ );
576
+ }
577
+
578
+ /** A token row whose stored credentials are validated here, not trusted. */
579
+ type GrokTokenInput = Omit<GrokTokenRow, "credentials"> & { credentials: JsonValue };
580
+
581
+ export interface GrokChatBody {
582
+ body: GrokResponsesRequest;
583
+ model: GrokResolvedModel;
584
+ convId: string;
585
+ convGroupId: string;
586
+ turnIdx: number;
587
+ }
588
+
589
+ export interface GrokVersionGateState {
590
+ tripped: boolean;
591
+ minClientVersion: string | null;
592
+ trippedAt: number | null;
593
+ }
594
+
595
+ interface AttemptState {
596
+ upstreamStarted: boolean;
597
+ forwarded: boolean;
598
+ /** The exact credentials this attempt presented (or tried to refresh); failures judge these. */
599
+ presented: GrokCredentials | undefined;
600
+ }
601
+
602
+ export class GrokDispatcher implements GrokProviderDispatcher, MediaDispatcher, VoiceDispatcher {
603
+ readonly tokens = new Map<number, GrokTokenState>();
604
+ readonly selector = new SmoothWeightedRoundRobin();
605
+ readonly refresher: GrokRefresher;
606
+ readonly fetchImpl: typeof fetch;
607
+ readonly media: GrokMediaPort | undefined;
608
+ readonly voice: GrokVoicePort | undefined;
609
+ readonly now: () => number;
610
+ readonly maxInflightPerToken: number | undefined;
611
+ readonly sleep: (ms: number, signal?: AbortSignal) => Promise<void>;
612
+ readonly client: GrokChatClient;
613
+ readonly catalog: GrokModelCatalog;
614
+ readonly idleTimeoutMs: number;
615
+ readonly reportReauthRequired: GrokReauthReporter;
616
+ readonly pendingBilling = new Map<number, Promise<void>>();
617
+ readonly pendingSettings = new Map<number, Promise<void>>();
618
+ readonly pendingSubscription = new Map<number, Promise<void>>();
619
+ versionGate: { trippedAt: number; minClientVersion: string | undefined } | undefined;
620
+
621
+ constructor(options: GrokDispatcherOptions) {
622
+ this.refresher = options.refresher;
623
+ this.fetchImpl = options.fetch ?? fetch;
624
+ this.media = options.media;
625
+ this.voice = options.voice;
626
+ this.now = options.now ?? Date.now;
627
+ this.maxInflightPerToken =
628
+ "maxInflightPerToken" in options
629
+ ? options.maxInflightPerToken
630
+ : parseMaxInflight(process.env.QGRID_GROK_MAX_INFLIGHT_PER_TOKEN);
631
+ this.sleep = options.sleep ?? defaultSleep;
632
+ this.client = options.client ?? new GrokChatClient({ fetch: this.fetchImpl });
633
+ this.catalog = options.catalog ?? new GrokModelCatalog();
634
+ this.idleTimeoutMs = options.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;
635
+ this.reportReauthRequired = options.reportReauthRequired ?? persistGrokReauth;
636
+ }
637
+
638
+ // Tokens arrive through GrokTokenSink (TokenSubscriber reconciles on start).
639
+ async start(): Promise<void> {
640
+ logger.info(`started grok runtime with ${this.tokens.size} tokens`);
641
+ }
642
+
643
+ async stop(): Promise<void> {
644
+ this.tokens.clear();
645
+ this.pendingBilling.clear();
646
+ this.pendingSettings.clear();
647
+ this.pendingSubscription.clear();
648
+
649
+ for (const id of this.selector.weights.keys()) this.selector.removeToken(id);
650
+ this.versionGate = undefined;
651
+ }
652
+
653
+ // ---- GrokTokenSink -------------------------------------------------------
654
+
655
+ onTokenAdded(
656
+ id: number,
657
+ name: string,
658
+ credentials: JsonValue,
659
+ quotaThreshold: number | null,
660
+ weight: number,
661
+ ): void {
662
+ this.upsertToken(id, name, credentials, quotaThreshold, weight);
663
+ }
664
+
665
+ onTokenUpdated(
666
+ id: number,
667
+ name: string,
668
+ credentials: JsonValue,
669
+ quotaThreshold: number | null,
670
+ weight: number,
671
+ ): void {
672
+ this.upsertToken(id, name, credentials, quotaThreshold, weight);
673
+ }
674
+
675
+ onTokenRemoved(id: number): void {
676
+ this.tokens.delete(id);
677
+ this.selector.removeToken(id);
678
+ this.pendingBilling.delete(id);
679
+ this.pendingSettings.delete(id);
680
+ this.pendingSubscription.delete(id);
681
+ }
682
+
683
+ replaceTokens(rows: GrokTokenInput[]): void {
684
+ const ids = new Set(rows.map((row) => row.id));
685
+
686
+ for (const id of this.tokens.keys()) if (!ids.has(id)) this.onTokenRemoved(id);
687
+
688
+ for (const row of rows)
689
+ this.upsertToken(row.id, row.name, row.credentials, row.quotaThreshold, row.weight);
690
+ }
691
+
692
+ upsertToken(
693
+ id: number,
694
+ name: string,
695
+ rawCredentials: JsonValue,
696
+ quotaThreshold: number | null,
697
+ weight: number,
698
+ ): void {
699
+ const parsed = GrokCredentials.safeParse(rawCredentials);
700
+
701
+ // Only paths are logged; values may be secrets.
702
+ let invalidReason = parsed.success
703
+ ? undefined
704
+ : `invalid credentials at ${[...new Set(parsed.error.issues.map((i) => i.path.join(".") || "<root>"))].join(", ")}`;
705
+
706
+ if (!invalidReason && (!Number.isInteger(weight) || weight < 1 || weight > 100)) {
707
+ invalidReason = `invalid weight ${weight}`;
708
+ }
709
+
710
+ const credentials = parsed.success ? parsed.data : undefined;
711
+ const old = this.tokens.get(id);
712
+
713
+ if (old) {
714
+ const credentialsChanged =
715
+ old.credentials?.accessToken !== credentials?.accessToken ||
716
+ old.credentials?.refreshToken !== credentials?.refreshToken;
717
+
718
+ const thresholdChanged = old.quotaThreshold !== quotaThreshold;
719
+ old.name = name;
720
+ old.weight = weight;
721
+ old.quotaThreshold = quotaThreshold;
722
+ old.credentials = credentials;
723
+ old.invalidReason = invalidReason;
724
+
725
+ if (credentialsChanged || thresholdChanged) {
726
+ old.generation++;
727
+ old.billing = undefined;
728
+ old.settings = undefined;
729
+ old.subscription = undefined;
730
+ old.overThreshold = false;
731
+ }
732
+
733
+ if (credentialsChanged) {
734
+ old.reauthRequired = false;
735
+ old.refreshFailedUntil = 0;
736
+ old.refreshFailure = undefined;
737
+ // New credentials may belong to another account; ZDR is account-level.
738
+ old.zdr = null;
739
+ }
740
+ } else {
741
+ this.tokens.set(id, {
742
+ id,
743
+ name,
744
+ weight,
745
+ quotaThreshold,
746
+ credentials,
747
+ invalidReason,
748
+ generation: 1,
749
+ inFlight: 0,
750
+ cooldowns: new Map(),
751
+ quotaBlocks: new Map(),
752
+ entitlementBlocks: new Map(),
753
+ reauthRequired: false,
754
+ refreshFailedUntil: 0,
755
+ refreshFailure: undefined,
756
+ billing: undefined,
757
+ billingNotBefore: 0,
758
+ settings: undefined,
759
+ subscription: undefined,
760
+ zdr: null,
761
+ overThreshold: false,
762
+ lastTier: credentials?.subscriptionTier,
763
+ failoverCount: 0,
764
+ });
765
+ }
766
+
767
+ if (invalidReason) {
768
+ logger.warn(`grok token ${name}[${id}] treated as inactive: ${invalidReason}`);
769
+ this.selector.removeToken(id);
770
+ } else {
771
+ this.selector.setToken(id, weight);
772
+ }
773
+ }
774
+
775
+ // ---- Eligibility (§5.1) -------------------------------------------------
776
+
777
+ /**
778
+ * `held` counts reservations the caller already holds on `token` (re-checks mid-request).
779
+ * `ungated` skips the admission gates and checks only that the credentials are usable.
780
+ */
781
+ ineligibility(
782
+ token: GrokTokenState,
783
+ requestClass: GrokRequestClass,
784
+ now: number,
785
+ held = 0,
786
+ ungated = false,
787
+ ): IneligibleReason | undefined {
788
+ if (!token.credentials || token.invalidReason) return "inactive";
789
+
790
+ if (token.reauthRequired) return "reauth";
791
+
792
+ if (token.refreshFailedUntil > now) return "refresh_failed";
793
+
794
+ if (ungated) return undefined;
795
+ const settings = token.settings?.value;
796
+
797
+ if (settings?.kind === "ok" && settings.onDemandEnabled === true) return "on_demand";
798
+
799
+ if (requestClass === "video" && token.zdr === true) return "zdr";
800
+
801
+ if ((token.entitlementBlocks.get(requestClass) ?? 0) > now) return "entitlement";
802
+
803
+ if ((token.quotaBlocks.get(requestClass) ?? 0) > now) return "quota";
804
+
805
+ if (token.quotaThreshold !== null && token.overThreshold) return "quota";
806
+
807
+ if ((token.cooldowns.get(requestClass) ?? 0) > now) return "cooldown";
808
+
809
+ if (this.maxInflightPerToken !== undefined && token.inFlight - held >= this.maxInflightPerToken)
810
+ return "capacity";
811
+
812
+ return undefined;
813
+ }
814
+
815
+ isEligible(token: GrokTokenState, requestClass: GrokRequestClass): boolean {
816
+ return this.ineligibility(token, requestClass, this.now()) === undefined;
817
+ }
818
+
819
+ /**
820
+ * Refreshes the 60 s settings/subscription/billing caches. Lookup failures fail open, so an
821
+ * expired bearer is refreshed first: a lookup with it would fail and open every gate. A failed
822
+ * refresh therefore takes the token out of selection (`refresh_failed`) instead of failing open.
823
+ */
824
+ async prefetch(token: GrokTokenState, signal?: AbortSignal): Promise<void> {
825
+ if (!token.credentials || token.invalidReason || token.reauthRequired) return;
826
+ let now = this.now();
827
+ const tasks: Promise<void>[] = [];
828
+
829
+ const stale = (entry: Cached<unknown> | undefined) =>
830
+ !entry ||
831
+ entry.generation !== token.generation ||
832
+ now - entry.fetchedAt >= GROK_LOOKUP_CACHE_TTL_MS;
833
+
834
+ const billingDue = () =>
835
+ (token.quotaThreshold !== null || [...token.quotaBlocks.values()].some((u) => u > now)) &&
836
+ stale(token.billing) &&
837
+ now >= token.billingNotBefore;
838
+
839
+ if (!stale(token.settings) && !stale(token.subscription) && !billingDue()) return;
840
+
841
+ if (grokNeedsRefresh(token.credentials, now)) {
842
+ // Do not hammer the IdP: the cooldown expires before the next refresh is tried.
843
+ if (token.refreshFailedUntil > now) return;
844
+ const presented = token.credentials;
845
+
846
+ try {
847
+ await this.freshCredentials(token, signal ?? new AbortController().signal);
848
+ } catch (error) {
849
+ // Lookups with this bearer would fail open, so the gates stay unknown and the token sits
850
+ // out selection until a later refresh succeeds.
851
+ if (signal?.aborted) return;
852
+
853
+ if (error instanceof Error && error.name === "GrokReauthRequiredError")
854
+ this.markReauthRequired(token, presented, reauthReasonCode(oauthErrorOf(error)));
855
+ else this.markRefreshFailed(token, presented, error);
856
+
857
+ return;
858
+ }
859
+
860
+ if (!token.credentials || token.reauthRequired || this.tokens.get(token.id) !== token) return;
861
+ now = this.now();
862
+ }
863
+
864
+ if (stale(token.settings)) tasks.push(this.loadSettings(token));
865
+
866
+ if (stale(token.subscription)) tasks.push(this.loadSubscription(token));
867
+ const blocked = [...token.quotaBlocks.values()].some((until) => until > now);
868
+
869
+ if ((token.quotaThreshold !== null || blocked) && stale(token.billing)) {
870
+ if (now >= token.billingNotBefore) tasks.push(this.loadBilling(token));
871
+ }
872
+
873
+ await Promise.all(tasks);
874
+ }
875
+
876
+ /** Transient refresh failure for `presented`; ignored when the token already holds newer ones. */
877
+ markRefreshFailed(token: GrokTokenState, presented: GrokCredentials, cause: unknown): void {
878
+ if (this.tokens.get(token.id) !== token || !sameCredentials(token.credentials, presented))
879
+ return;
880
+ token.refreshFailedUntil = this.now() + GROK_REFRESH_FAILED_COOLDOWN_MS;
881
+ token.refreshFailure = cause;
882
+ logger.warn(
883
+ `grok token ${token.name}[${token.id}] refresh failed; out of selection for ${GROK_REFRESH_FAILED_COOLDOWN_MS}ms: ${cause instanceof Error ? cause.name : "unknown"}`,
884
+ );
885
+ }
886
+
887
+ loadSettings(token: GrokTokenState): Promise<void> {
888
+ const pending = this.pendingSettings.get(token.id);
889
+
890
+ if (pending) return pending;
891
+ const generation = token.generation;
892
+ const credentials = token.credentials!;
893
+
894
+ const promise = readGrokSettings(credentials, { fetch: this.fetchImpl })
895
+ .then((result) => {
896
+ if (this.tokens.get(token.id) !== token || token.generation !== generation) return;
897
+ token.settings = { value: result, fetchedAt: this.now(), generation };
898
+
899
+ if (result.kind === "lookup_failed") {
900
+ logger.warn("quota_threshold gate: settings_lookup_fail_open", {
901
+ tokenId: token.id,
902
+ tokenName: token.name,
903
+ lookupReason: result.reason,
904
+ });
905
+
906
+ return;
907
+ }
908
+
909
+ if (result.subscriptionTierDisplay) token.lastTier = result.subscriptionTierDisplay;
910
+
911
+ if (result.onDemandEnabled === true) {
912
+ logger.info(
913
+ `quota_threshold gate: on_demand_excluded ${token.name}[${token.id}] (on_demand_enabled=true)`,
914
+ );
915
+ }
916
+ })
917
+ .finally(() => {
918
+ if (this.pendingSettings.get(token.id) === promise) this.pendingSettings.delete(token.id);
919
+ });
920
+
921
+ this.pendingSettings.set(token.id, promise);
922
+
923
+ return promise;
924
+ }
925
+
926
+ /** /user codingDataRetentionOptOut is the primary ZDR signal; a failed read keeps the last value. */
927
+ loadSubscription(token: GrokTokenState): Promise<void> {
928
+ const pending = this.pendingSubscription.get(token.id);
929
+
930
+ if (pending) return pending;
931
+ const generation = token.generation;
932
+ const credentials = token.credentials!;
933
+
934
+ const promise = readGrokSubscription(credentials, { fetch: this.fetchImpl })
935
+ .then((result) => {
936
+ if (this.tokens.get(token.id) !== token || token.generation !== generation) return;
937
+ token.subscription = { value: result, fetchedAt: this.now(), generation };
938
+
939
+ if (result.kind === "lookup_failed") {
940
+ logger.debug(`grok subscription lookup failed for ${token.name}[${token.id}]`, {
941
+ lookupReason: result.reason,
942
+ });
943
+
944
+ return;
945
+ }
946
+
947
+ if (result.dataRetentionOptOut !== null) {
948
+ this.setZdr(token, result.dataRetentionOptOut, "codingDataRetentionOptOut");
949
+ }
950
+ })
951
+ .finally(() => {
952
+ if (this.pendingSubscription.get(token.id) === promise)
953
+ this.pendingSubscription.delete(token.id);
954
+ });
955
+
956
+ this.pendingSubscription.set(token.id, promise);
957
+
958
+ return promise;
959
+ }
960
+
961
+ setZdr(token: GrokTokenState, zdr: boolean, source: string): void {
962
+ if (this.tokens.get(token.id) !== token || token.zdr === zdr) return;
963
+ token.zdr = zdr;
964
+ logger.info(
965
+ zdr
966
+ ? `grok token ${token.name}[${token.id}] excluded from video: zero data retention (${source})`
967
+ : `grok token ${token.name}[${token.id}] video eligible: zero data retention off (${source})`,
968
+ );
969
+ }
970
+
971
+ /** `x-zero-data-retention: true` confirms ZDR; its absence never clears it (next /user read does). */
972
+ observeResponseHeaders(
973
+ token: GrokTokenState,
974
+ credentials: GrokCredentials,
975
+ headers: { zeroDataRetention?: boolean },
976
+ ): void {
977
+ if (token.credentials?.accessToken !== credentials.accessToken) return;
978
+
979
+ if (headers.zeroDataRetention === true) this.setZdr(token, true, "x-zero-data-retention");
980
+ }
981
+
982
+ loadBilling(token: GrokTokenState): Promise<void> {
983
+ const pending = this.pendingBilling.get(token.id);
984
+
985
+ if (pending) return pending;
986
+ const generation = token.generation;
987
+ const credentials = token.credentials!;
988
+
989
+ const promise = readGrokBilling(credentials, { fetch: this.fetchImpl })
990
+ .then((result) => {
991
+ if (this.tokens.get(token.id) !== token || token.generation !== generation) return;
992
+ const now = this.now();
993
+ token.billing = { value: result, fetchedAt: now, generation };
994
+
995
+ if (result.kind === "lookup_failed") {
996
+ token.overThreshold = false;
997
+ logger.warn("quota_threshold gate: lookup_fail_open", {
998
+ tokenId: token.id,
999
+ tokenName: token.name,
1000
+ lookupReason: result.reason,
1001
+ });
1002
+
1003
+ return;
1004
+ }
1005
+
1006
+ const limit = token.quotaThreshold ?? 100;
1007
+ const over = token.quotaThreshold !== null && result.utilizationPct >= token.quotaThreshold;
1008
+
1009
+ if (over && !token.overThreshold) {
1010
+ logger.info(
1011
+ `quota_threshold gate: over_threshold ${token.name}[${token.id}] (${result.utilizationPct}% >= ${token.quotaThreshold}%)`,
1012
+ );
1013
+ }
1014
+
1015
+ if (!over && token.overThreshold) {
1016
+ logger.info(
1017
+ `quota_threshold gate: recovered ${token.name}[${token.id}] (${result.utilizationPct}% < ${token.quotaThreshold}%)`,
1018
+ );
1019
+ }
1020
+
1021
+ token.overThreshold = over;
1022
+
1023
+ if (result.utilizationPct < limit && token.quotaBlocks.size) {
1024
+ token.quotaBlocks.clear();
1025
+ logger.info(
1026
+ `quota_threshold gate: exhaustion_cleared ${token.name}[${token.id}] (${result.utilizationPct}% < ${limit}%)`,
1027
+ );
1028
+ }
1029
+ })
1030
+ .finally(() => {
1031
+ if (this.pendingBilling.get(token.id) === promise) this.pendingBilling.delete(token.id);
1032
+ });
1033
+
1034
+ this.pendingBilling.set(token.id, promise);
1035
+
1036
+ return promise;
1037
+ }
1038
+
1039
+ // ---- Selection (§5.2) ---------------------------------------------------
1040
+
1041
+ reserve(token: GrokTokenState): Reservation {
1042
+ token.inFlight++;
1043
+ let released = false;
1044
+
1045
+ return {
1046
+ token,
1047
+ release: () => {
1048
+ if (released) return;
1049
+ released = true;
1050
+ token.inFlight = Math.max(0, token.inFlight - 1);
1051
+ },
1052
+ };
1053
+ }
1054
+
1055
+ checkVersionGate(requestClass: GrokRequestClass): void {
1056
+ if (requestClass !== "chat" || !this.versionGate) return;
1057
+
1058
+ if (this.now() - this.versionGate.trippedAt < GROK_VERSION_GATE_REPROBE_MS) {
1059
+ throw new GrokVersionGateError(this.versionGate.minClientVersion);
1060
+ }
1061
+ // Re-probe: let requests through; a fresh 426 trips the gate again.
1062
+ }
1063
+
1064
+ async selectToken(
1065
+ requestClass: GrokRequestClass,
1066
+ options: {
1067
+ preferredTokenId?: number;
1068
+ requirePreferredToken?: boolean;
1069
+ excluded?: ReadonlySet<number>;
1070
+ signal?: AbortSignal;
1071
+ deadline?: number;
1072
+ ungated?: boolean;
1073
+ } = {},
1074
+ ): Promise<Reservation> {
1075
+ this.checkVersionGate(requestClass);
1076
+ const excluded = options.excluded ?? new Set<number>();
1077
+
1078
+ if (options.preferredTokenId !== undefined) {
1079
+ const preferred = this.tokens.get(options.preferredTokenId);
1080
+
1081
+ if (preferred && !excluded.has(preferred.id)) {
1082
+ for (let round = 0; ; round++) {
1083
+ await this.prefetch(preferred, options.signal);
1084
+ options.signal?.throwIfAborted();
1085
+ const now = this.now();
1086
+ const reason = this.ineligibility(preferred, requestClass, now, 0, options.ungated);
1087
+
1088
+ if (!reason) return this.reserve(preferred);
1089
+
1090
+ if (!options.requirePreferredToken) break;
1091
+ // An exact target has no alternative: wait out one failed refresh, like a cooldown.
1092
+ const waitMs = preferred.refreshFailedUntil - now;
1093
+
1094
+ if (
1095
+ reason === "refresh_failed" &&
1096
+ round === 0 &&
1097
+ waitMs <= GROK_MAX_COOLDOWN_WAIT_MS &&
1098
+ (options.deadline === undefined || now + waitMs < options.deadline)
1099
+ ) {
1100
+ await this.sleep(Math.max(0, waitMs), options.signal);
1101
+ continue;
1102
+ }
1103
+
1104
+ throw this.preferredError(preferred, reason);
1105
+ }
1106
+ } else if (options.requirePreferredToken) {
1107
+ throw new GrokNoWorkersError(
1108
+ `Preferred grok token ${options.preferredTokenId} is not available`,
1109
+ );
1110
+ }
1111
+ }
1112
+
1113
+ for (let round = 0; ; round++) {
1114
+ const candidates = [...this.tokens.values()].filter((token) => !excluded.has(token.id));
1115
+ await Promise.all(candidates.map((token) => this.prefetch(token, options.signal)));
1116
+ options.signal?.throwIfAborted();
1117
+ // Synchronous from here to reserve(): concurrent selections cannot overshoot the cap.
1118
+ const now = this.now();
1119
+ const reasons = new Map<GrokTokenState, IneligibleReason>();
1120
+ const eligible = new Set<number>();
1121
+
1122
+ for (const token of candidates) {
1123
+ const reason = this.ineligibility(token, requestClass, now);
1124
+
1125
+ if (reason) reasons.set(token, reason);
1126
+ else eligible.add(token.id);
1127
+ }
1128
+
1129
+ if (eligible.size) {
1130
+ const selected = this.selector.select(eligible);
1131
+ const token = selected === null ? undefined : this.tokens.get(selected);
1132
+
1133
+ if (token) return this.reserve(token);
1134
+ }
1135
+
1136
+ const present = new Set(reasons.values());
1137
+
1138
+ if (present.has("capacity")) throw new GrokCapacityError();
1139
+
1140
+ if (present.has("cooldown") || present.has("refresh_failed")) {
1141
+ // A failed refresh is a short transient cooldown: wait once, then re-run the lookups.
1142
+ const until = (token: GrokTokenState, reason: IneligibleReason) =>
1143
+ reason === "cooldown"
1144
+ ? (token.cooldowns.get(requestClass) ?? now)
1145
+ : reason === "refresh_failed"
1146
+ ? token.refreshFailedUntil
1147
+ : Number.POSITIVE_INFINITY;
1148
+
1149
+ const soonest = Math.min(...[...reasons].map(([token, reason]) => until(token, reason)));
1150
+ const waitMs = Math.max(0, Math.min(soonest - now, GROK_MAX_COOLDOWN_WAIT_MS));
1151
+ const withinDeadline = options.deadline === undefined || now + waitMs < options.deadline;
1152
+
1153
+ if (round === 0 && withinDeadline) {
1154
+ await this.sleep(waitMs, options.signal);
1155
+ continue;
1156
+ }
1157
+
1158
+ const cooling = [...reasons].filter(([, reason]) => reason === "cooldown");
1159
+
1160
+ if (cooling.length) {
1161
+ const soonestCooldown = Math.min(...cooling.map(([token]) => until(token, "cooldown")));
1162
+ throw new GrokRateLimitedError(Math.max(1, Math.ceil((soonestCooldown - now) / 1000)));
1163
+ }
1164
+
1165
+ const failed = [...reasons].find(([, reason]) => reason === "refresh_failed");
1166
+ throw new GrokCredentialRefreshError(failed?.[0].refreshFailure ?? "refresh failed", false);
1167
+ }
1168
+
1169
+ if (present.has("quota")) {
1170
+ const over = [...reasons].flatMap(([token, reason]) =>
1171
+ reason === "quota"
1172
+ ? [{ name: token.name, threshold: token.overThreshold ? token.quotaThreshold : null }]
1173
+ : [],
1174
+ );
1175
+
1176
+ logger.warn("quota_threshold gate: all_exceeded", {
1177
+ provider: "grok",
1178
+ overThresholdTokens: over,
1179
+ });
1180
+ throw new QuotaThresholdExceededError(quotaMessage(over));
1181
+ }
1182
+
1183
+ if (present.has("on_demand")) {
1184
+ throw new GrokOnDemandExcludedError(tokenNamesWithReason(reasons, "on_demand"));
1185
+ }
1186
+
1187
+ if (present.has("zdr")) {
1188
+ throw new GrokZdrVideoUnavailableError(tokenNamesWithReason(reasons, "zdr"));
1189
+ }
1190
+
1191
+ throw new GrokNoWorkersError();
1192
+ }
1193
+ }
1194
+
1195
+ preferredError(token: GrokTokenState, reason: IneligibleReason): Error {
1196
+ const now = this.now();
1197
+
1198
+ if (reason === "quota") {
1199
+ return new QuotaThresholdExceededError(
1200
+ quotaMessage([
1201
+ { name: token.name, threshold: token.overThreshold ? token.quotaThreshold : null },
1202
+ ]),
1203
+ );
1204
+ }
1205
+
1206
+ if (reason === "cooldown") {
1207
+ const until = Math.max(...token.cooldowns.values());
1208
+
1209
+ return new GrokRateLimitedError(Math.max(1, Math.ceil((until - now) / 1000)));
1210
+ }
1211
+
1212
+ if (reason === "capacity") return new GrokCapacityError();
1213
+
1214
+ if (reason === "refresh_failed")
1215
+ return new GrokCredentialRefreshError(token.refreshFailure ?? "refresh failed", false);
1216
+
1217
+ if (reason === "on_demand") return new GrokOnDemandExcludedError([token.name]);
1218
+
1219
+ if (reason === "zdr") return new GrokZdrVideoUnavailableError([token.name]);
1220
+
1221
+ return new GrokNoWorkersError(`Preferred grok token ${token.id} is not available (${reason})`);
1222
+ }
1223
+
1224
+ // ---- Failure handling (§5.4) -------------------------------------------
1225
+
1226
+ applyHttpFailure(
1227
+ token: GrokTokenState,
1228
+ requestClass: GrokRequestClass,
1229
+ error: GrokHttpError,
1230
+ ): void {
1231
+ const now = this.now();
1232
+ const { classification } = error;
1233
+
1234
+ if (classification.kind === "rate_limited") {
1235
+ const cooldownMs = Math.min(
1236
+ classification.retryAfterSec === undefined
1237
+ ? GROK_DEFAULT_COOLDOWN_MS
1238
+ : classification.retryAfterSec * 1000,
1239
+ GROK_MAX_COOLDOWN_MS,
1240
+ );
1241
+
1242
+ token.cooldowns.set(requestClass, now + cooldownMs);
1243
+ logger.info(
1244
+ `grok token ${token.name}[${token.id}] cooldown ${cooldownMs}ms (${requestClass})`,
1245
+ );
1246
+ } else if (classification.kind === "quota_exhausted") {
1247
+ const billing = token.billing?.value;
1248
+ const periodEnd = billing?.kind === "ok" ? billing.periodEndMs : null;
1249
+
1250
+ const until =
1251
+ periodEnd !== null && periodEnd > now ? periodEnd : now + GROK_QUOTA_BLOCK_FALLBACK_MS;
1252
+
1253
+ token.quotaBlocks.set(requestClass, until);
1254
+ token.billing = undefined;
1255
+ // A billing read right after the 429 may still lag the proxy; wait one cache period.
1256
+ token.billingNotBefore = now + GROK_LOOKUP_CACHE_TTL_MS;
1257
+ logger.info(
1258
+ `quota_threshold gate: exhausted ${token.name}[${token.id}] until ${new Date(until).toISOString()} (${classification.code ?? "429"})`,
1259
+ );
1260
+ } else if (classification.kind === "entitlement" && requestClass !== "chat") {
1261
+ token.entitlementBlocks.set(requestClass, now + GROK_ENTITLEMENT_BLOCK_MS);
1262
+ } else if (classification.kind === "version_gate" && requestClass === "chat") {
1263
+ this.versionGate = { trippedAt: now, minClientVersion: classification.minClientVersion };
1264
+ logger.error(
1265
+ `grok chat proxy version gate tripped: ${GROK_BUILD_VERSION} < ${classification.minClientVersion ?? "?"}`,
1266
+ );
1267
+ }
1268
+ }
1269
+
1270
+ classifyFailure(
1271
+ token: GrokTokenState,
1272
+ requestClass: GrokRequestClass,
1273
+ cause: unknown,
1274
+ presented: GrokCredentials | undefined,
1275
+ refreshedThisRequest: boolean,
1276
+ refreshBudgetLeft: boolean,
1277
+ ): FailureAction {
1278
+ if (cause instanceof GrokIdleTimeoutError) {
1279
+ // Reached only before any event: no upstream execution was observed.
1280
+ return requestClass === "chat"
1281
+ ? { kind: "failover", error: cause, transient: true }
1282
+ : { kind: "fail", error: cause };
1283
+ }
1284
+
1285
+ if (cause instanceof GrokCredentialRefreshError) {
1286
+ // Nothing reached upstream; another token can serve the request.
1287
+ // Judged against the credentials this refresh started from: if a concurrent refresh or a
1288
+ // subscriber update already replaced them, the failure says nothing about the new ones.
1289
+ if (presented) {
1290
+ if (cause.reauthRequired)
1291
+ this.markReauthRequired(token, presented, reauthReasonCode(oauthErrorOf(cause.cause)));
1292
+ else this.markRefreshFailed(token, presented, cause.cause);
1293
+ }
1294
+
1295
+ return { kind: "failover", error: cause, transient: false };
1296
+ }
1297
+
1298
+ if (!(cause instanceof GrokHttpError)) return { kind: "fail", error: cause };
1299
+
1300
+ if (requestClass === "video" && isGrokZdrVideoRejection(cause)) {
1301
+ // The ZDR signal was stale or unknown. The 400 is a definite rejection (no job was created),
1302
+ // so the start may move to another token under the media entitlement rules.
1303
+ this.setZdr(token, true, "upstream video 400");
1304
+ token.entitlementBlocks.set("video", this.now() + GROK_ENTITLEMENT_BLOCK_MS);
1305
+
1306
+ return { kind: "failover", error: cause, transient: false };
1307
+ }
1308
+
1309
+ this.applyHttpFailure(token, requestClass, cause);
1310
+ const { classification } = cause;
1311
+
1312
+ if (classification.kind === "version_gate") {
1313
+ return { kind: "fail", error: new GrokVersionGateError(classification.minClientVersion) };
1314
+ }
1315
+
1316
+ if (classification.shouldRetry === false) return { kind: "fail", error: cause };
1317
+
1318
+ switch (classification.kind) {
1319
+ case "auth":
1320
+ if (!presented || !token.credentials) {
1321
+ return { kind: "failover", error: cause, transient: false };
1322
+ }
1323
+
1324
+ if (token.credentials.accessToken !== presented.accessToken) {
1325
+ // Stale 401: a newer bearer was installed while this attempt was in flight. It says
1326
+ // nothing about the current credentials, so neither refresh nor mark them; retry them.
1327
+ return { kind: "retry", error: cause };
1328
+ }
1329
+
1330
+ if (refreshedThisRequest) {
1331
+ // A 401 with just-refreshed credentials: only a new login recovers.
1332
+ this.markReauthRequired(token, presented, "grok:unauthorized_after_refresh");
1333
+
1334
+ return { kind: "failover", error: cause, transient: false };
1335
+ }
1336
+
1337
+ if (refreshBudgetLeft) return { kind: "refresh", presented };
1338
+
1339
+ return { kind: "failover", error: cause, transient: false };
1340
+ case "rate_limited":
1341
+ case "quota_exhausted":
1342
+ return { kind: "failover", error: cause, transient: false };
1343
+ case "entitlement":
1344
+ // Chat 403 is permanent for the request (grok-build rule); media fails over once.
1345
+ return requestClass === "chat"
1346
+ ? { kind: "fail", error: cause }
1347
+ : { kind: "failover", error: cause, transient: false };
1348
+ case "transient":
1349
+ if (requestClass === "chat") return { kind: "failover", error: cause, transient: true };
1350
+
1351
+ // Media/voice POSTs are never replayed once the body may have been executed. The media
1352
+ // and voice clients surface such failures as GrokMediaDeliveryError, GrokMediaTimeoutError
1353
+ // or `shouldRetry: false` (all handled above); a status-0 GrokHttpError that reaches this
1354
+ // point is a connect failure that provably happened before any byte was sent, so it fails
1355
+ // over once (transient: GROK_MAX_TRANSIENT_FAILOVERS). An HTTP 5xx never fails over.
1356
+ return cause.status === 0
1357
+ ? { kind: "failover", error: cause, transient: true }
1358
+ : { kind: "fail", error: cause };
1359
+ default:
1360
+ return { kind: "fail", error: cause };
1361
+ }
1362
+ }
1363
+
1364
+ /**
1365
+ * Marks `token` dead in memory and, on the first transition, persists `tokens.reauth_required`.
1366
+ * `credentials` are the ones the failed attempt presented; a token already holding newer ones
1367
+ * (another bearer or refresh token) is left alone.
1368
+ */
1369
+ markReauthRequired(
1370
+ token: GrokTokenState,
1371
+ credentials: GrokCredentials,
1372
+ reasonCode: string,
1373
+ ): void {
1374
+ if (this.tokens.get(token.id) !== token) return;
1375
+
1376
+ if (!sameCredentials(token.credentials, credentials)) return;
1377
+
1378
+ if (token.reauthRequired) return;
1379
+ token.reauthRequired = true;
1380
+ logger.warn(`grok token ${token.name}[${token.id}] requires re-authentication (${reasonCode})`);
1381
+ void this.reportReauthRequired(
1382
+ { id: token.id, name: token.name, credentials },
1383
+ reasonCode,
1384
+ ).catch((cause: unknown) => {
1385
+ logger.warn(
1386
+ `grok token ${token.name}[${token.id}] reauth_required persist failed: ${cause instanceof Error ? cause.name : "unknown"}`,
1387
+ );
1388
+ });
1389
+ }
1390
+
1391
+ /**
1392
+ * Runs `attempt` with the §5.1 attempt budget: one forced refresh, at most GROK_MAX_HOPS
1393
+ * failovers (one of them transient). `canRetry` reports whether upstream execution may have
1394
+ * started, in which case nothing is retried.
1395
+ *
1396
+ * The reservation is held from selection until the request leaves the token (failover) or ends,
1397
+ * so a refresh and its same-token retry never step outside the in-flight cap. Whenever the
1398
+ * attempt is about to present credentials other than the ones the token's gates were evaluated
1399
+ * with (a refresh happened), the lookups and gates are re-run before anything is sent.
1400
+ */
1401
+ async withAttempts<T>(
1402
+ requestClass: GrokRequestClass,
1403
+ selection: MediaTokenSelection & { signal: AbortSignal; deadline?: number; ungated?: boolean },
1404
+ attempt: (
1405
+ token: GrokTokenState,
1406
+ credentials: GrokCredentials,
1407
+ state: AttemptState,
1408
+ ) => Promise<T>,
1409
+ ): Promise<T> {
1410
+ const excluded = new Set<number>();
1411
+ const refreshed = new Set<number>();
1412
+ let refreshes = 0;
1413
+ let hops = 0;
1414
+ let transientFailovers = 0;
1415
+ let attempts = 0;
1416
+ let staleRetries = 0;
1417
+ let reservation = await this.selectToken(requestClass, { ...selection, excluded });
1418
+ // The credentials the current token's gates were last evaluated with.
1419
+ let gated = reservation.token.credentials;
1420
+
1421
+ try {
1422
+ for (;;) {
1423
+ const token = reservation.token;
1424
+
1425
+ const state: AttemptState = {
1426
+ upstreamStarted: false,
1427
+ forwarded: false,
1428
+ presented: token.credentials,
1429
+ };
1430
+
1431
+ let action: FailureAction;
1432
+ attempts++;
1433
+
1434
+ try {
1435
+ let credentials: GrokCredentials;
1436
+
1437
+ try {
1438
+ credentials = await this.freshCredentials(token, selection.signal);
1439
+ } catch (error) {
1440
+ if (selection.signal.aborted) throw error;
1441
+ throw new GrokCredentialRefreshError(error);
1442
+ }
1443
+
1444
+ state.presented = credentials;
1445
+
1446
+ const blocked =
1447
+ credentials.accessToken === gated?.accessToken
1448
+ ? undefined
1449
+ : await this.regate(token, requestClass, selection.signal, selection.ungated);
1450
+
1451
+ gated = credentials;
1452
+
1453
+ if (blocked) action = { kind: "failover", error: blocked, transient: false };
1454
+ else return await attempt(token, credentials, state);
1455
+ } catch (error) {
1456
+ if (selection.signal.aborted) throw abortReason(selection.signal);
1457
+
1458
+ if (state.forwarded || state.upstreamStarted) throw error;
1459
+ action = this.classifyFailure(
1460
+ token,
1461
+ requestClass,
1462
+ error,
1463
+ state.presented,
1464
+ refreshed.has(token.id),
1465
+ refreshes < GROK_MAX_REFRESHES,
1466
+ );
1467
+ }
1468
+
1469
+ if (action.kind === "fail") throw action.error;
1470
+
1471
+ if (action.kind === "retry") {
1472
+ // Same token, current credentials, reservation still held.
1473
+ if (staleRetries++ < GROK_MAX_STALE_AUTH_RETRIES) continue;
1474
+ // Allowance spent: this token must not be reselected by the failover below.
1475
+ excluded.add(token.id);
1476
+ action = { kind: "failover", error: action.error, transient: false };
1477
+ }
1478
+
1479
+ if (action.kind === "refresh") {
1480
+ refreshes++;
1481
+ refreshed.add(token.id);
1482
+ // The reservation stays held across the refresh: the retry is part of this attempt.
1483
+ const next = await this.tryRefresh(token, action.presented, selection.signal);
1484
+
1485
+ if (next.kind === "refreshed") continue;
1486
+ action = { kind: "failover", error: next.error, transient: false };
1487
+ }
1488
+
1489
+ // Failover: never past an exact target, the hop budget, or one transient retry.
1490
+ if (selection.requirePreferredToken) throw action.error;
1491
+
1492
+ if (action.transient && ++transientFailovers > GROK_MAX_TRANSIENT_FAILOVERS)
1493
+ throw action.error;
1494
+
1495
+ if (++hops > GROK_MAX_HOPS)
1496
+ throw new GrokAttemptBudgetExceededError(attempts, action.error);
1497
+
1498
+ // Rate-limit/quota/reauth state already excludes the token; failures that leave no state
1499
+ // behind must not be retried on the same token within this request.
1500
+ if (action.transient || action.error instanceof GrokCredentialRefreshError)
1501
+ excluded.add(token.id);
1502
+ token.failoverCount++;
1503
+ logger.info(`grok failover from ${token.name}[${token.id}] (${requestClass})`, {
1504
+ reason: action.error instanceof Error ? action.error.message : String(action.error),
1505
+ });
1506
+ reservation.release();
1507
+
1508
+ try {
1509
+ reservation = await this.selectToken(requestClass, {
1510
+ excluded,
1511
+ signal: selection.signal,
1512
+ deadline: selection.deadline,
1513
+ });
1514
+ } catch (selectionError) {
1515
+ if (selectionError instanceof GrokNoWorkersError) throw action.error;
1516
+ throw selectionError;
1517
+ }
1518
+
1519
+ gated = reservation.token.credentials;
1520
+ }
1521
+ } finally {
1522
+ reservation.release();
1523
+ }
1524
+ }
1525
+
1526
+ /**
1527
+ * Re-runs the lookups and gates for a token whose credentials changed after selection. Returns
1528
+ * the gate's error when the token is no longer eligible; the caller still holds one reservation.
1529
+ */
1530
+ private async regate(
1531
+ token: GrokTokenState,
1532
+ requestClass: GrokRequestClass,
1533
+ signal: AbortSignal,
1534
+ ungated?: boolean,
1535
+ ): Promise<Error | undefined> {
1536
+ await this.prefetch(token, signal);
1537
+ signal.throwIfAborted();
1538
+ const reason = this.ineligibility(token, requestClass, this.now(), 1, ungated);
1539
+
1540
+ return reason ? this.preferredError(token, reason) : undefined;
1541
+ }
1542
+
1543
+ /**
1544
+ * The one forced refresh after an upstream 401. A refreshed token is retried; otherwise the
1545
+ * request fails over. Only a `reauth_required` answer marks the token dead — a thrown refresh
1546
+ * failure is treated as transient and leaves the token alive.
1547
+ */
1548
+ private async tryRefresh(
1549
+ token: GrokTokenState,
1550
+ presented: GrokCredentials,
1551
+ signal: AbortSignal,
1552
+ ): Promise<{ kind: "refreshed" } | { kind: "failover"; error: GrokCredentialRefreshError }> {
1553
+ let result: Awaited<ReturnType<GrokRefresher["refreshAfterUnauthorized"]>>;
1554
+
1555
+ try {
1556
+ result = await this.refresher.refreshAfterUnauthorized(
1557
+ { tokenId: token.id, credentials: presented },
1558
+ signal,
1559
+ );
1560
+ } catch (error) {
1561
+ if (signal.aborted) throw abortReason(signal);
1562
+
1563
+ return { kind: "failover", error: new GrokCredentialRefreshError(error) };
1564
+ }
1565
+
1566
+ if (result.status === "refreshed") {
1567
+ this.storeCredentials(token, result.credentials);
1568
+
1569
+ return { kind: "refreshed" };
1570
+ }
1571
+
1572
+ this.markReauthRequired(token, presented, reauthReasonCode(result.reason));
1573
+
1574
+ return { kind: "failover", error: GrokCredentialRefreshError.reauthRequired(result.reason) };
1575
+ }
1576
+
1577
+ async freshCredentials(token: GrokTokenState, signal: AbortSignal): Promise<GrokCredentials> {
1578
+ const current = token.credentials;
1579
+
1580
+ if (!current) throw new GrokNoWorkersError(`grok token ${token.id} has no valid credentials`);
1581
+
1582
+ const fresh = await this.refresher.ensureFresh(
1583
+ { tokenId: token.id, credentials: current },
1584
+ signal,
1585
+ );
1586
+
1587
+ this.storeCredentials(token, fresh);
1588
+
1589
+ return fresh;
1590
+ }
1591
+
1592
+ storeCredentials(token: GrokTokenState, credentials: GrokCredentials): void {
1593
+ // A subscriber update may already have replaced the row; never overwrite newer credentials.
1594
+ if (this.tokens.get(token.id) !== token) return;
1595
+
1596
+ if (token.credentials?.accessToken === credentials.accessToken) return;
1597
+ token.credentials = credentials;
1598
+ token.reauthRequired = false;
1599
+ // Lookups cached (or in flight) under the old bearer may be fail-open lookup_failed results;
1600
+ // a new generation makes the next selection re-read them with this bearer. The cached values
1601
+ // still gate until then (same account), and the subscriber's echo of this row is a no-op.
1602
+ token.generation++;
1603
+ this.pendingBilling.delete(token.id);
1604
+ this.pendingSettings.delete(token.id);
1605
+ this.pendingSubscription.delete(token.id);
1606
+ }
1607
+
1608
+ // ---- Chat ---------------------------------------------------------------
1609
+
1610
+ async generate(req: GenerateRequest): Promise<GenerateResult> {
1611
+ return this.runChat(req);
1612
+ }
1613
+
1614
+ async generateStream(req: GenerateRequest, cb: GenerateStreamCallbacks): Promise<void> {
1615
+ try {
1616
+ cb.onComplete(await this.runChat(req, cb.onDelta));
1617
+ } catch (error) {
1618
+ cb.onError(error instanceof Error ? error : new Error(String(error)));
1619
+ throw error;
1620
+ }
1621
+ }
1622
+
1623
+ buildChatBody(req: GenerateRequest): GrokChatBody {
1624
+ if (req.imageGeneration !== undefined && req.imageGeneration !== false) {
1625
+ throw new GrokInvalidRequestError(
1626
+ "imageGeneration is not supported on grok chat models; use the Grok image endpoint",
1627
+ );
1628
+ }
1629
+
1630
+ if (req.imageGenerationOptions !== undefined) {
1631
+ throw new GrokInvalidRequestError(
1632
+ "imageGenerationOptions is not supported on grok chat models",
1633
+ );
1634
+ }
1635
+
1636
+ if (req.serviceTier !== undefined) {
1637
+ throw new GrokInvalidRequestError("serviceTier is not supported on grok chat models");
1638
+ }
1639
+
1640
+ // providerOptions.xai: the proxy's acceptance of these fields is unverified, so they are
1641
+ // rejected rather than forwarded or silently dropped.
1642
+ const xai = req.providerOptions?.xai;
1643
+
1644
+ if (isJsonObject(xai)) {
1645
+ for (const key of ["serviceTier", "parallelToolCalls"] as const) {
1646
+ if (xai[key] !== undefined && xai[key] !== null) {
1647
+ throw new GrokInvalidRequestError(
1648
+ `providerOptions.xai.${key} is not supported on grok chat models: the subscription proxy's handling is unverified`,
1649
+ );
1650
+ }
1651
+ }
1652
+ }
1653
+
1654
+ if (req.thinking === false) {
1655
+ throw new ThinkingValidationError(
1656
+ "thinking:false is unavailable for Grok models: thinking-off support has not been verified on the subscription proxy",
1657
+ );
1658
+ }
1659
+
1660
+ const model = this.catalog.resolve(req.model);
1661
+
1662
+ if (!model.known) {
1663
+ // The proxy may serve another model for an unlisted id; the result's model shows which.
1664
+ logger.info(`grok model "${model.id}" is not in the catalog; forwarding it unvalidated`);
1665
+ }
1666
+
1667
+ let effort: GrokReasoningEffort = model.defaultEffort;
1668
+
1669
+ if (req.effort !== undefined) {
1670
+ const validated = this.catalog.validateEffort(model.id, req.effort);
1671
+
1672
+ if (validated) effort = validated;
1673
+ else logger.debug(`ignoring effort "${req.effort}" for grok/${model.id}`);
1674
+ }
1675
+
1676
+ const history = historyItems(req.coldHistory);
1677
+ const { convId, convGroupId } = grokConversationIds(req.promptCacheKey);
1678
+
1679
+ const turnIdx =
1680
+ history.filter((item) => item.type === "message" && item.role === "user").length + 1;
1681
+
1682
+ const tools = grokTools(req.tools);
1683
+
1684
+ const body = buildGrokResponsesRequest({
1685
+ model: model.id,
1686
+ systemPrompt: req.systemPrompt,
1687
+ history: [...history, userMessage(req)],
1688
+ promptCacheKey: convId,
1689
+ effort,
1690
+ outputSchema: req.outputSchema === undefined ? undefined : { schema: req.outputSchema },
1691
+ tools,
1692
+ });
1693
+
1694
+ return { body, model, convId, convGroupId, turnIdx };
1695
+ }
1696
+
1697
+ async runChat(req: GenerateRequest, onDelta?: (text: string) => void): Promise<GenerateResult> {
1698
+ const { body, model, convId, convGroupId, turnIdx } = this.buildChatBody(req);
1699
+ const timeoutMs = req.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
1700
+ const timeout = AbortSignal.timeout(timeoutMs);
1701
+ const signal = req.abortSignal ? AbortSignal.any([req.abortSignal, timeout]) : timeout;
1702
+ signal.throwIfAborted();
1703
+ const deadline = this.now() + timeoutMs;
1704
+
1705
+ return this.withAttempts(
1706
+ "chat",
1707
+ {
1708
+ preferredTokenId: req.preferredTokenId,
1709
+ requirePreferredToken: req.requirePreferredToken,
1710
+ signal,
1711
+ deadline,
1712
+ },
1713
+ (token, credentials, state) =>
1714
+ this.chatAttempt(token, credentials, state, {
1715
+ req,
1716
+ body,
1717
+ model,
1718
+ convId,
1719
+ convGroupId,
1720
+ turnIdx,
1721
+ signal,
1722
+ onDelta,
1723
+ }),
1724
+ );
1725
+ }
1726
+
1727
+ async chatAttempt(
1728
+ token: GrokTokenState,
1729
+ credentials: GrokCredentials,
1730
+ state: AttemptState,
1731
+ context: {
1732
+ req: GenerateRequest;
1733
+ body: GrokResponsesRequest;
1734
+ model: GrokResolvedModel;
1735
+ convId: string;
1736
+ convGroupId: string;
1737
+ turnIdx: number;
1738
+ signal: AbortSignal;
1739
+ onDelta?: (text: string) => void;
1740
+ },
1741
+ ): Promise<GenerateResult> {
1742
+ const { req, model } = context;
1743
+ const startedAt = this.now();
1744
+
1745
+ const headers = buildGrokChatHeaders({
1746
+ bearer: credentials.accessToken,
1747
+ model: model.id,
1748
+ convId: context.convId,
1749
+ reqId: randomUUID(),
1750
+ sessionId: credentials.sessionId,
1751
+ agentId: credentials.agentId,
1752
+ userId: credentials.userId,
1753
+ convGroupId: context.convGroupId,
1754
+ turnIdx: context.turnIdx,
1755
+ zstd: true,
1756
+ });
1757
+
1758
+ let stream: Awaited<ReturnType<GrokChatClient["stream"]>>;
1759
+
1760
+ try {
1761
+ stream = await this.client.stream({
1762
+ body: context.body,
1763
+ headers,
1764
+ signal: context.signal,
1765
+ idleTimeoutMs: this.idleTimeoutMs,
1766
+ });
1767
+ } catch (error) {
1768
+ if (error instanceof GrokHttpError)
1769
+ this.observeResponseHeaders(token, credentials, error.responseHeaders);
1770
+ throw error;
1771
+ }
1772
+
1773
+ this.observeResponseHeaders(token, credentials, stream.headers);
1774
+ // A successful chat proves the client version is accepted again.
1775
+ this.versionGate = undefined;
1776
+
1777
+ if (this.catalog.isStale(stream.headers.modelsEtag)) {
1778
+ void this.catalog.refresh(credentials, { fetch: this.fetchImpl }).then((result) => {
1779
+ if (result.kind === "lookup_failed")
1780
+ logger.warn(`grok model catalog refresh failed: ${result.reason}`);
1781
+ });
1782
+ }
1783
+
1784
+ let text = "";
1785
+ let firstDeltaAt: number | undefined;
1786
+ let usage: GrokUsage | undefined;
1787
+ let servingModel = model.id;
1788
+ let outputMessageAccepted = false;
1789
+ const toolCalls: ProviderToolCall[] = [];
1790
+ let completed = false;
1791
+
1792
+ for await (const event of stream.events) {
1793
+ state.upstreamStarted = true;
1794
+
1795
+ if (event.type === "created") {
1796
+ if (event.model) servingModel = event.model;
1797
+ } else if (event.type === "text-delta") {
1798
+ if (outputMessageAccepted) continue;
1799
+ firstDeltaAt ??= this.now();
1800
+ text += event.text;
1801
+
1802
+ if (context.onDelta) {
1803
+ state.forwarded = true;
1804
+ context.onDelta(event.text);
1805
+ }
1806
+ } else if (event.type === "output-item" && event.completed) {
1807
+ const item = event.item;
1808
+
1809
+ if (item.type === "function_call") {
1810
+ const callId = jsonString(item.call_id);
1811
+ const name = jsonString(item.name);
1812
+ const args = jsonString(item.arguments);
1813
+
1814
+ if (callId !== undefined && name !== undefined && args !== undefined) {
1815
+ toolCalls.push({ toolCallId: callId, toolName: name, input: args });
1816
+ }
1817
+ } else if (
1818
+ item.type === "message" &&
1819
+ req.stopAfterOutputMessage &&
1820
+ !outputMessageAccepted &&
1821
+ Array.isArray(item.content)
1822
+ ) {
1823
+ const messageText = item.content
1824
+ .map((part) =>
1825
+ isJsonObject(part) && part.type === "output_text"
1826
+ ? (jsonString(part.text) ?? "")
1827
+ : "",
1828
+ )
1829
+ .join("");
1830
+
1831
+ if (messageText) outputMessageAccepted = req.stopAfterOutputMessage(messageText);
1832
+ }
1833
+ } else if (event.type === "completed") {
1834
+ completed = true;
1835
+
1836
+ if (event.model) servingModel = event.model;
1837
+ usage = event.usage;
1838
+ } else if (event.type === "incomplete") {
1839
+ throw new GrokIncompleteResponseError(event.reason);
1840
+ } else if (event.type === "error") {
1841
+ throw event.error;
1842
+ }
1843
+ }
1844
+
1845
+ if (!completed)
1846
+ throw new GrokIncompleteResponseError("stream ended without response.completed");
1847
+
1848
+ const ticks = usage?.costInUsdTicks;
1849
+
1850
+ const contextTokens =
1851
+ usage?.contextInputTokens === undefined && usage?.contextOutputTokens === undefined
1852
+ ? undefined
1853
+ : (usage.contextInputTokens ?? 0) + (usage.contextOutputTokens ?? 0);
1854
+
1855
+ const contextWindow = stream.headers.contextWindow ?? model.contextWindow;
1856
+
1857
+ const resultUsage: GenerateResult["usage"] = {
1858
+ totalTokens: usage?.totalTokens ?? 0,
1859
+ inputTokens: usage?.inputTokens ?? 0,
1860
+ cachedInputTokens: usage?.cachedInputTokens ?? 0,
1861
+ outputTokens: usage?.outputTokens ?? 0,
1862
+ reasoningOutputTokens: usage?.reasoningTokens ?? 0,
1863
+ };
1864
+
1865
+ if (contextTokens !== undefined) resultUsage.contextTokens = contextTokens;
1866
+
1867
+ const xai: XaiProviderMetadata = {};
1868
+
1869
+ if (ticks !== undefined) xai.costInUsdTicks = ticks;
1870
+
1871
+ if (contextWindow !== undefined) xai.contextWindow = contextWindow;
1872
+
1873
+ const result: GenerateResult = {
1874
+ text,
1875
+ tokenName: token.name,
1876
+ usage: resultUsage,
1877
+ durationMs: this.now() - startedAt,
1878
+ ttftMs: firstDeltaAt === undefined ? null : firstDeltaAt - startedAt,
1879
+ model: servingModel,
1880
+ threadCoord: { workerId: token.id, threadId: req.promptCacheKey ?? "", epoch: -1 },
1881
+ providerMetadata: { xai },
1882
+ };
1883
+
1884
+ if (servingModel !== model.id) result.requestedModel = model.id;
1885
+
1886
+ if (ticks !== undefined) {
1887
+ result.costUsd = grokTicksToUsd(ticks);
1888
+ result.costSource = "provider";
1889
+ }
1890
+
1891
+ if (toolCalls.length) result.toolCalls = toolCalls;
1892
+
1893
+ return result;
1894
+ }
1895
+
1896
+ // ---- Media and voice (§5.7) --------------------------------------------
1897
+
1898
+ async runPort<T>(
1899
+ requestClass: GrokRequestClass,
1900
+ selection: MediaTokenSelection & { ungated?: boolean },
1901
+ signal: AbortSignal | undefined,
1902
+ call: (credentials: GrokCredentials) => Promise<T>,
1903
+ ): Promise<MediaDispatchResult<T>> {
1904
+ const active = signal ?? new AbortController().signal;
1905
+
1906
+ return this.withAttempts(
1907
+ requestClass,
1908
+ {
1909
+ preferredTokenId: selection.preferredTokenId,
1910
+ requirePreferredToken: selection.requirePreferredToken,
1911
+ ungated: selection.ungated,
1912
+ signal: active,
1913
+ },
1914
+ async (token, credentials) => ({
1915
+ result: await call(credentials),
1916
+ tokenId: token.id,
1917
+ tokenName: token.name,
1918
+ }),
1919
+ );
1920
+ }
1921
+
1922
+ requireMedia(): GrokMediaPort {
1923
+ if (!this.media) throw new GrokCapabilityUnavailableError("media");
1924
+
1925
+ return this.media;
1926
+ }
1927
+
1928
+ requireVoice(): GrokVoicePort {
1929
+ if (!this.voice) throw new GrokCapabilityUnavailableError("voice");
1930
+
1931
+ return this.voice;
1932
+ }
1933
+
1934
+ async generateImages(
1935
+ request: GrokImageGenerateRequest & MediaTokenSelection,
1936
+ ): Promise<MediaDispatchResult<GrokImageResponse>> {
1937
+ const media = this.requireMedia();
1938
+ const { preferredTokenId, requirePreferredToken, ...portRequest } = request;
1939
+
1940
+ return this.runPort(
1941
+ "image",
1942
+ { preferredTokenId, requirePreferredToken },
1943
+ request.signal,
1944
+ (credentials) => media.generateImages(mediaAuth(credentials), portRequest),
1945
+ );
1946
+ }
1947
+
1948
+ async editImages(
1949
+ request: GrokImageEditRequest & MediaTokenSelection,
1950
+ ): Promise<MediaDispatchResult<GrokImageResponse>> {
1951
+ const media = this.requireMedia();
1952
+ const { preferredTokenId, requirePreferredToken, ...portRequest } = request;
1953
+
1954
+ return this.runPort(
1955
+ "image",
1956
+ { preferredTokenId, requirePreferredToken },
1957
+ request.signal,
1958
+ (credentials) => media.editImages(mediaAuth(credentials), portRequest),
1959
+ );
1960
+ }
1961
+
1962
+ async startVideo(
1963
+ request: GrokVideoStartRequest & MediaTokenSelection,
1964
+ ): Promise<MediaDispatchResult<GrokVideoStart>> {
1965
+ const media = this.requireMedia();
1966
+ const { preferredTokenId, requirePreferredToken, ...portRequest } = request;
1967
+
1968
+ return this.runPort(
1969
+ "video",
1970
+ { preferredTokenId, requirePreferredToken },
1971
+ request.signal,
1972
+ (credentials) => media.startVideo(mediaAuth(credentials), portRequest),
1973
+ );
1974
+ }
1975
+
1976
+ /**
1977
+ * Polls on the token that started the job; no selection, no failover. The job is already
1978
+ * admitted (and billed), so quota, cooldown and capacity gates must not strand its result.
1979
+ */
1980
+ async getVideo(
1981
+ tokenId: number,
1982
+ requestId: string,
1983
+ signal?: AbortSignal,
1984
+ ): Promise<MediaDispatchResult<GrokVideoStatus>> {
1985
+ const media = this.requireMedia();
1986
+
1987
+ return this.runPort(
1988
+ "video",
1989
+ { preferredTokenId: tokenId, requirePreferredToken: true, ungated: true },
1990
+ signal,
1991
+ (credentials) => media.getVideo(mediaAuth(credentials), requestId, signal),
1992
+ );
1993
+ }
1994
+
1995
+ async transcribe(
1996
+ request: GrokTranscribeRequest & MediaTokenSelection,
1997
+ ): Promise<MediaDispatchResult<GrokSttResponse>> {
1998
+ const voice = this.requireVoice();
1999
+ const { preferredTokenId, requirePreferredToken, ...portRequest } = request;
2000
+
2001
+ return this.runPort(
2002
+ "stt",
2003
+ { preferredTokenId, requirePreferredToken },
2004
+ request.signal,
2005
+ (credentials) => voice.transcribe(credentials.accessToken, portRequest),
2006
+ );
2007
+ }
2008
+
2009
+ async speak(
2010
+ request: GrokTtsRequest & { signal?: AbortSignal } & MediaTokenSelection,
2011
+ ): Promise<MediaDispatchResult<GrokSpeechResult>> {
2012
+ const voice = this.requireVoice();
2013
+ const { preferredTokenId, requirePreferredToken, ...portRequest } = request;
2014
+
2015
+ return this.runPort(
2016
+ "tts",
2017
+ { preferredTokenId, requirePreferredToken },
2018
+ request.signal,
2019
+ (credentials) => voice.speak(credentials.accessToken, portRequest),
2020
+ );
2021
+ }
2022
+
2023
+ // ---- Observability (§5.8) -----------------------------------------------
2024
+
2025
+ tokenStates(): GrokTokenStateSnapshot[] {
2026
+ const now = this.now();
2027
+
2028
+ return [...this.tokens.values()]
2029
+ .map((token): GrokTokenStateSnapshot => {
2030
+ const billing = token.billing?.value;
2031
+ const settings = token.settings?.value;
2032
+
2033
+ return {
2034
+ id: token.id,
2035
+ name: token.name,
2036
+ weight: token.weight,
2037
+ active: token.credentials !== undefined && token.invalidReason === undefined,
2038
+ invalidReason: token.invalidReason ?? null,
2039
+ inFlight: token.inFlight,
2040
+ cooldownUntil: mapRecord(token.cooldowns, now),
2041
+ quotaBlockedUntil: mapRecord(token.quotaBlocks, now),
2042
+ entitlementBlocks: mapRecord(token.entitlementBlocks, now),
2043
+ lastBilling:
2044
+ billing?.kind === "ok"
2045
+ ? {
2046
+ utilizationPct: billing.utilizationPct,
2047
+ periodType: billing.periodType,
2048
+ periodEnd: billing.periodEnd,
2049
+ fetchedAt: token.billing!.fetchedAt,
2050
+ }
2051
+ : null,
2052
+ quotaThreshold: token.quotaThreshold,
2053
+ overThreshold: token.overThreshold,
2054
+ onDemandExcluded: settings?.kind === "ok" && settings.onDemandEnabled === true,
2055
+ zdrVideoExcluded: token.zdr === true,
2056
+ lastTier: token.lastTier ?? null,
2057
+ reauthRequired: token.reauthRequired,
2058
+ failoverCount: token.failoverCount,
2059
+ };
2060
+ })
2061
+ .toSorted((a, b) => a.name.localeCompare(b.name));
2062
+ }
2063
+
2064
+ /** Chat-proxy version gate state for the usage endpoint. */
2065
+ versionGateState(): GrokVersionGateState {
2066
+ return {
2067
+ tripped: this.versionGate !== undefined,
2068
+ minClientVersion: this.versionGate?.minClientVersion ?? null,
2069
+ trippedAt: this.versionGate?.trippedAt ?? null,
2070
+ };
2071
+ }
2072
+
2073
+ /** Same shape as OpenAIDispatcher.getQuotaSnapshot; reads caches only. */
2074
+ getQuotaSnapshot(): Array<{
2075
+ name: string;
2076
+ usedPercent: number | null;
2077
+ threshold: number | null;
2078
+ blocked: boolean;
2079
+ resetsAt: number | null;
2080
+ zdrVideoExcluded: boolean;
2081
+ }> {
2082
+ const now = this.now();
2083
+
2084
+ return [...this.tokens.values()]
2085
+ .map((token) => {
2086
+ const billing = token.billing?.value;
2087
+
2088
+ const fresh =
2089
+ billing?.kind === "ok" &&
2090
+ token.billing!.generation === token.generation &&
2091
+ now - token.billing!.fetchedAt < GROK_LOOKUP_CACHE_TTL_MS;
2092
+
2093
+ return {
2094
+ name: token.name,
2095
+ usedPercent: fresh ? billing.utilizationPct : null,
2096
+ threshold: token.quotaThreshold,
2097
+ blocked:
2098
+ token.overThreshold || [...token.quotaBlocks.values()].some((until) => until > now),
2099
+ resetsAt: fresh ? billing.periodEndMs : null,
2100
+ zdrVideoExcluded: token.zdr === true,
2101
+ };
2102
+ })
2103
+ .toSorted((a, b) => a.name.localeCompare(b.name));
2104
+ }
2105
+ }