@cartanova/qgrid-cli 2.10.9 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (318) 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 +22 -20
  10. package/bundle/dist/application/qgrid/qgrid-run-lifecycle.js +43 -34
  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 -69
  24. package/bundle/dist/application/request-log/request-log.types.js +8 -2
  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 +40 -16
  33. package/bundle/dist/application/sonamu.generated.sso.js +2 -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 +2 -1
  39. package/bundle/dist/migrations/20260807133553_manual__backfill_request_log_cost.js +8 -6
  40. package/bundle/dist/migrations/20261018215031_alter_request_logs_add1_alter1.js +15 -0
  41. package/bundle/dist/server-bootstrap.js +4 -2
  42. package/bundle/dist/server-error-handler.js +1 -1
  43. package/bundle/dist/sonamu.config.js +37 -8
  44. package/bundle/dist/startup-migrations.js +17 -7
  45. package/bundle/dist/testing/global.js +1 -1
  46. package/bundle/dist/testing/schema-bench.js +0 -0
  47. package/bundle/dist/testing/test-database-name.js +1 -1
  48. package/bundle/dist/utils/providers/anthropic/anthropic-constants.js +1 -1
  49. package/bundle/dist/utils/providers/anthropic/anthropic-dispatcher.js +11 -5
  50. package/bundle/dist/utils/providers/anthropic/anthropic-quota.js +4 -4
  51. package/bundle/dist/utils/providers/anthropic/anthropic-thinking.js +1 -1
  52. package/bundle/dist/utils/providers/anthropic/claude-session.js +20 -19
  53. package/bundle/dist/utils/providers/anthropic/fence-strip.js +1 -1
  54. package/bundle/dist/utils/providers/anthropic/stream-json-adapter.js +49 -23
  55. package/bundle/dist/utils/providers/common/credentials.js +5 -1
  56. package/bundle/dist/utils/providers/common/effort.js +3 -3
  57. package/bundle/dist/utils/providers/common/model-cost.js +69 -14
  58. package/bundle/dist/utils/providers/common/provider-types.js +29 -1
  59. package/bundle/dist/utils/providers/common/schema-validation.js +78 -40
  60. package/bundle/dist/utils/providers/common/smooth-weighted-round-robin.js +1 -1
  61. package/bundle/dist/utils/providers/common/strictifier.js +62 -43
  62. package/bundle/dist/utils/providers/common/thinking.js +3 -2
  63. package/bundle/dist/utils/providers/common/ttft.js +1 -1
  64. package/bundle/dist/utils/providers/grok/grok-backend-protocol.js +427 -0
  65. package/bundle/dist/utils/providers/grok/grok-constants.js +55 -0
  66. package/bundle/dist/utils/providers/grok/grok-direct-client.js +196 -0
  67. package/bundle/dist/utils/providers/grok/grok-dispatcher.js +1313 -0
  68. package/bundle/dist/utils/providers/grok/grok-headers.js +118 -0
  69. package/bundle/dist/utils/providers/grok/grok-media-client.js +420 -0
  70. package/bundle/dist/utils/providers/grok/grok-models.js +114 -0
  71. package/bundle/dist/utils/providers/grok/grok-oauth.js +455 -0
  72. package/bundle/dist/utils/providers/grok/grok-quota.js +119 -0
  73. package/bundle/dist/utils/providers/grok/grok-refresh.js +192 -0
  74. package/bundle/dist/utils/providers/grok/grok-types.js +186 -0
  75. package/bundle/dist/utils/providers/grok/grok-voice-client.js +199 -0
  76. package/bundle/dist/utils/providers/grok/index.js +11 -0
  77. package/bundle/dist/utils/providers/openai/openai-backend-protocol.js +111 -83
  78. package/bundle/dist/utils/providers/openai/openai-callback-relay.js +3 -2
  79. package/bundle/dist/utils/providers/openai/openai-constants.js +1 -1
  80. package/bundle/dist/utils/providers/openai/openai-direct-client.js +23 -7
  81. package/bundle/dist/utils/providers/openai/openai-dispatcher.js +143 -106
  82. package/bundle/dist/utils/providers/openai/openai-image-generation.js +48 -33
  83. package/bundle/dist/utils/providers/openai/openai-oauth.js +28 -13
  84. package/bundle/dist/utils/providers/openai/openai-quota.js +38 -27
  85. package/bundle/dist/utils/providers/openai/openai-refresh.js +17 -9
  86. package/bundle/dist/utils/providers/openai/openai-sse.js +1 -1
  87. package/bundle/dist/utils/providers/openai/openai-transport-config.js +1 -1
  88. package/bundle/dist/utils/quiet-hours.js +1 -1
  89. package/bundle/dist/utils/slack-notify.js +14 -8
  90. package/bundle/dist/utils/subset-loaders.js +4 -5
  91. package/bundle/src/application/monit/log-buffer.ts +23 -9
  92. package/bundle/src/application/monit/monit.frame.ts +22 -5
  93. package/bundle/src/application/monit/monit.types.ts +6 -0
  94. package/bundle/src/application/qgrid/conv-routing.test.ts +76 -4
  95. package/bundle/src/application/qgrid/conv-routing.ts +35 -9
  96. package/bundle/src/application/qgrid/expired-token-reminder.ts +9 -1
  97. package/bundle/src/application/qgrid/oauth.ts +51 -15
  98. package/bundle/src/application/qgrid/qgrid-image-generation.ts +9 -0
  99. package/bundle/src/application/qgrid/qgrid-response-format.ts +50 -25
  100. package/bundle/src/application/qgrid/qgrid-run-lifecycle.test.ts +33 -0
  101. package/bundle/src/application/qgrid/qgrid-run-lifecycle.ts +87 -40
  102. package/bundle/src/application/qgrid/qgrid.dispatcher.test.ts +97 -0
  103. package/bundle/src/application/qgrid/qgrid.dispatcher.ts +274 -126
  104. package/bundle/src/application/qgrid/qgrid.frame.test.ts +1013 -3
  105. package/bundle/src/application/qgrid/qgrid.frame.ts +1415 -60
  106. package/bundle/src/application/qgrid/qgrid.types.ts +335 -0
  107. package/bundle/src/application/qgrid/schema-prompt.ts +105 -41
  108. package/bundle/src/application/qgrid/slack-user-map.ts +5 -0
  109. package/bundle/src/application/qgrid/token-death.ts +4 -0
  110. package/bundle/src/application/qgrid/token-subscriber.test.ts +21 -19
  111. package/bundle/src/application/qgrid/token-subscriber.ts +186 -53
  112. package/bundle/src/application/qgrid/token-trigger-setup.ts +2 -0
  113. package/bundle/src/application/qgrid/token-window-keepalive.ts +49 -6
  114. package/bundle/src/application/qgrid/tool-emulation-schema.ts +33 -29
  115. package/bundle/src/application/qgrid/tool-emulation.ts +52 -27
  116. package/bundle/src/application/request-log/json-schema-type-text.test.ts +5 -5
  117. package/bundle/src/application/request-log/json-schema-type-text.ts +81 -26
  118. package/bundle/src/application/request-log/request-log.entity.json +8 -0
  119. package/bundle/src/application/request-log/request-log.model.test.ts +111 -4
  120. package/bundle/src/application/request-log/request-log.model.ts +279 -145
  121. package/bundle/src/application/request-log/request-log.types.ts +17 -0
  122. package/bundle/src/application/request-log/zod-code-format.ts +11 -0
  123. package/bundle/src/application/request-log-step/request-log-step.model.ts +1 -0
  124. package/bundle/src/application/request-log-step/request-log-step.types.ts +2 -0
  125. package/bundle/src/application/setting/setting.constant.ts +9 -0
  126. package/bundle/src/application/setting/setting.model.ts +12 -2
  127. package/bundle/src/application/setting/setting.store.ts +5 -1
  128. package/bundle/src/application/setting/setting.types.ts +8 -0
  129. package/bundle/src/application/sonamu.generated.http +135 -0
  130. package/bundle/src/application/sonamu.generated.sso.ts +3 -0
  131. package/bundle/src/application/sonamu.generated.ts +29 -0
  132. package/bundle/src/application/token/token.identity.test.ts +20 -0
  133. package/bundle/src/application/token/token.model.ts +66 -7
  134. package/bundle/src/application/token/token.types.ts +24 -1
  135. package/bundle/src/i18n/en.ts +19 -0
  136. package/bundle/src/i18n/ko.ts +21 -0
  137. package/bundle/src/i18n/sd.generated.ts +24 -2
  138. package/bundle/src/migrations/20260807133553_manual__backfill_request_log_cost.ts +16 -8
  139. package/bundle/src/migrations/20261018215031_alter_request_logs_add1_alter1.ts +15 -0
  140. package/bundle/src/server-bootstrap.ts +4 -2
  141. package/bundle/src/server-error-handler.ts +1 -0
  142. package/bundle/src/sonamu.config.ts +69 -7
  143. package/bundle/src/startup-migrations.ts +27 -9
  144. package/bundle/src/testing/global.ts +20 -0
  145. package/bundle/src/testing/schema-bench.ts +105 -30
  146. package/bundle/src/testing/test-database-name.ts +2 -0
  147. package/bundle/src/utils/providers/anthropic/anthropic-constants.ts +8 -0
  148. package/bundle/src/utils/providers/anthropic/anthropic-dispatcher.ts +41 -6
  149. package/bundle/src/utils/providers/anthropic/anthropic-quota.ts +7 -3
  150. package/bundle/src/utils/providers/anthropic/anthropic-thinking.ts +6 -0
  151. package/bundle/src/utils/providers/anthropic/claude-session.ts +58 -24
  152. package/bundle/src/utils/providers/anthropic/fence-strip.ts +11 -0
  153. package/bundle/src/utils/providers/anthropic/stream-json-adapter.ts +110 -45
  154. package/bundle/src/utils/providers/common/credentials.ts +8 -0
  155. package/bundle/src/utils/providers/common/effort.ts +9 -2
  156. package/bundle/src/utils/providers/common/model-cost.test.ts +48 -1
  157. package/bundle/src/utils/providers/common/model-cost.ts +259 -158
  158. package/bundle/src/utils/providers/common/provider-dispatcher.ts +98 -2
  159. package/bundle/src/utils/providers/common/provider-types.ts +46 -3
  160. package/bundle/src/utils/providers/common/schema-validation.test.ts +5 -1
  161. package/bundle/src/utils/providers/common/schema-validation.ts +124 -56
  162. package/bundle/src/utils/providers/common/smooth-weighted-round-robin.ts +4 -0
  163. package/bundle/src/utils/providers/common/strictifier.ts +185 -59
  164. package/bundle/src/utils/providers/common/thinking.ts +2 -2
  165. package/bundle/src/utils/providers/common/ttft.ts +4 -2
  166. package/bundle/src/utils/providers/grok/__fixtures__/golden-responses-1.0.60.json +164 -0
  167. package/bundle/src/utils/providers/grok/grok-backend-protocol.test.ts +325 -0
  168. package/bundle/src/utils/providers/grok/grok-backend-protocol.ts +721 -0
  169. package/bundle/src/utils/providers/grok/grok-constants.ts +105 -0
  170. package/bundle/src/utils/providers/grok/grok-direct-client.test.ts +179 -0
  171. package/bundle/src/utils/providers/grok/grok-direct-client.ts +313 -0
  172. package/bundle/src/utils/providers/grok/grok-dispatcher.test.ts +1395 -0
  173. package/bundle/src/utils/providers/grok/grok-dispatcher.ts +2105 -0
  174. package/bundle/src/utils/providers/grok/grok-headers.test.ts +149 -0
  175. package/bundle/src/utils/providers/grok/grok-headers.ts +199 -0
  176. package/bundle/src/utils/providers/grok/grok-media-client.test.ts +446 -0
  177. package/bundle/src/utils/providers/grok/grok-media-client.ts +703 -0
  178. package/bundle/src/utils/providers/grok/grok-models.test.ts +96 -0
  179. package/bundle/src/utils/providers/grok/grok-models.ts +161 -0
  180. package/bundle/src/utils/providers/grok/grok-oauth.test.ts +406 -0
  181. package/bundle/src/utils/providers/grok/grok-oauth.ts +771 -0
  182. package/bundle/src/utils/providers/grok/grok-quota.test.ts +142 -0
  183. package/bundle/src/utils/providers/grok/grok-quota.ts +198 -0
  184. package/bundle/src/utils/providers/grok/grok-refresh.test.ts +239 -0
  185. package/bundle/src/utils/providers/grok/grok-refresh.ts +325 -0
  186. package/bundle/src/utils/providers/grok/grok-types.test.ts +252 -0
  187. package/bundle/src/utils/providers/grok/grok-types.ts +374 -0
  188. package/bundle/src/utils/providers/grok/grok-voice-client.test.ts +321 -0
  189. package/bundle/src/utils/providers/grok/grok-voice-client.ts +284 -0
  190. package/bundle/src/utils/providers/grok/index.ts +20 -0
  191. package/bundle/src/utils/providers/openai/openai-backend-protocol.ts +221 -137
  192. package/bundle/src/utils/providers/openai/openai-callback-relay.ts +12 -1
  193. package/bundle/src/utils/providers/openai/openai-constants.ts +2 -0
  194. package/bundle/src/utils/providers/openai/openai-direct-client.ts +111 -21
  195. package/bundle/src/utils/providers/openai/openai-dispatcher.ts +322 -169
  196. package/bundle/src/utils/providers/openai/openai-image-generation.ts +116 -36
  197. package/bundle/src/utils/providers/openai/openai-oauth.ts +48 -20
  198. package/bundle/src/utils/providers/openai/openai-quota.ts +77 -46
  199. package/bundle/src/utils/providers/openai/openai-refresh.ts +42 -18
  200. package/bundle/src/utils/providers/openai/openai-sse.ts +27 -3
  201. package/bundle/src/utils/providers/openai/openai-transport-config.ts +2 -0
  202. package/bundle/src/utils/quiet-hours.ts +12 -1
  203. package/bundle/src/utils/slack-notify.ts +44 -13
  204. package/bundle/src/utils/subset-loaders.ts +7 -7
  205. package/bundle/web-dist/client/assets/clsx-5XM_pHvN.js +1 -0
  206. package/bundle/web-dist/client/assets/index-BA7uQ-6c.js +31 -0
  207. package/bundle/web-dist/client/assets/index-Be2qCZmI.css +1 -0
  208. package/bundle/web-dist/client/assets/logs-C47OJGEI.js +1 -0
  209. package/bundle/web-dist/client/assets/monit-BS9no0dU.js +4 -0
  210. package/bundle/web-dist/client/assets/plus-sdACTfH0.js +1 -0
  211. package/bundle/web-dist/client/assets/refresh-cw-CY8o_1cT.js +1 -0
  212. package/bundle/web-dist/client/assets/request-model-BRtFuZvj.js +1 -0
  213. package/bundle/web-dist/client/assets/routes-kqgkUxE8.js +5 -0
  214. package/bundle/web-dist/client/assets/{services.generated-BsieU37H.js → services.generated-CefiDXEv.js} +5 -5
  215. package/bundle/web-dist/client/assets/settings-DNDvFGSw.js +1 -0
  216. package/bundle/web-dist/client/assets/show-u8Np69q3.js +2 -0
  217. package/bundle/web-dist/client/assets/tokens-QvP410NW.js +1 -0
  218. package/bundle/web-dist/client/assets/total-cost-MnfKU8IN.js +1 -0
  219. package/bundle/web-dist/client/assets/use-oauth-login-flow-Bzdh28Ws.js +1 -0
  220. package/bundle/web-dist/client/assets/use-update-token-mutation-CfJKaxmR.js +1 -0
  221. package/bundle/web-dist/client/assets/vendor-react-CUFFrVN9.js +244 -0
  222. package/bundle/web-dist/client/assets/vendor-tanstack-Bf_jag9G.js +1 -0
  223. package/bundle/web-dist/client/index.html +6 -6
  224. package/bundle/web-dist/server/assets/clsx-D_N5PN_2.js +16 -0
  225. package/bundle/web-dist/server/assets/{lazyRouteComponent-Dtd0NR4r.js → lazyRouteComponent-D8iTIHyn.js} +129 -2
  226. package/bundle/web-dist/server/assets/logs-B3OTd9hy.js +825 -0
  227. package/bundle/web-dist/server/assets/{monit-DvzribSE.js → monit-B2hqorPe.js} +385 -126
  228. package/bundle/web-dist/server/assets/plus-DwG7h9kq.js +28 -0
  229. package/bundle/web-dist/server/assets/{react-dom-B4a6-uuR.js → react-dom-BIYQzBqG.js} +2 -129
  230. package/bundle/web-dist/server/assets/refresh-cw-mMBkwOq0.js +44 -0
  231. package/bundle/web-dist/server/assets/{request-model-CbqfnQOJ.js → request-model-I6OdODSH.js} +15 -6
  232. package/bundle/web-dist/server/assets/{routes-BAPWTizT.js → routes-B95YlUv2.js} +760 -213
  233. package/bundle/web-dist/server/assets/{services.generated-CEbGjsI0.js → services.generated-DQATJ6qf.js} +12821 -12194
  234. package/bundle/web-dist/server/assets/settings-C3gbGk0m.js +830 -0
  235. package/bundle/web-dist/server/assets/{show-CxVZTAdv.js → show-Bxaj3rhH.js} +1355 -419
  236. package/bundle/web-dist/server/assets/tokens-CZjH3cB7.js +1038 -0
  237. package/bundle/web-dist/server/assets/total-cost-fTE9Xy82.js +18 -0
  238. package/bundle/web-dist/server/assets/use-oauth-login-flow-CKDHgc3U.js +2379 -0
  239. package/bundle/web-dist/server/assets/{use-update-token-mutation-CfYmk_4_.js → use-update-token-mutation-BEbgOzaa.js} +1 -1
  240. package/bundle/web-dist/server/entry-server.generated.js +4303 -22810
  241. package/dist/cli.js +9 -9
  242. package/package.json +4 -11
  243. package/postinstall.mjs +12 -0
  244. package/skills/qgrid/SKILL.md +1 -0
  245. package/skills/qgrid/references/cli-env-and-server-boot.md +4 -1
  246. package/skills/qgrid/references/decision-rationale.md +24 -1
  247. package/skills/qgrid/references/grok-runtime.md +89 -0
  248. package/skills/qgrid/references/provider-runtime-differences.md +30 -29
  249. package/skills/qgrid/references/repo-map.md +10 -0
  250. package/skills/qgrid/references/request-log-run-lifecycle.md +1 -1
  251. package/skills/qgrid/references/sonamu-api-web-flow.md +1 -1
  252. package/skills/qgrid/references/token-auth-quota-lifecycle.md +56 -3
  253. package/bundle/dist/node_modules/.pnpm/@jridgewell_sourcemap-codec@1.5.5/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.js +0 -406
  254. package/bundle/dist/node_modules/.pnpm/@vitest_expect@4.1.10/node_modules/@vitest/expect/dist/index.js +0 -1507
  255. package/bundle/dist/node_modules/.pnpm/@vitest_pretty-format@4.1.10/node_modules/@vitest/pretty-format/dist/index.js +0 -887
  256. package/bundle/dist/node_modules/.pnpm/@vitest_runner@4.1.10/node_modules/@vitest/runner/dist/chunk-artifact.js +0 -2756
  257. package/bundle/dist/node_modules/.pnpm/@vitest_runner@4.1.10/node_modules/@vitest/runner/dist/index.js +0 -8
  258. package/bundle/dist/node_modules/.pnpm/@vitest_runner@4.1.10/node_modules/@vitest/runner/dist/utils.js +0 -8
  259. package/bundle/dist/node_modules/.pnpm/@vitest_snapshot@4.1.10/node_modules/@vitest/snapshot/dist/index.js +0 -922
  260. package/bundle/dist/node_modules/.pnpm/@vitest_spy@4.1.10/node_modules/@vitest/spy/dist/index.js +0 -386
  261. package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/chunk-pathe.M-eThtNZ.js +0 -121
  262. package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/constants.js +0 -51
  263. package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/diff.js +0 -1317
  264. package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/display.js +0 -561
  265. package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/error.js +0 -40
  266. package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/helpers.js +0 -272
  267. package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/offset.js +0 -27
  268. package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/serialize.js +0 -77
  269. package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/source-map.js +0 -381
  270. package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/timers.js +0 -51
  271. package/bundle/dist/node_modules/.pnpm/chai@6.2.2/node_modules/chai/index.js +0 -2986
  272. package/bundle/dist/node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/branding.js +0 -10
  273. package/bundle/dist/node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/index.js +0 -109
  274. package/bundle/dist/node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/messages.js +0 -10
  275. package/bundle/dist/node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/overloads.js +0 -10
  276. package/bundle/dist/node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/utils.js +0 -10
  277. package/bundle/dist/node_modules/.pnpm/magic-string@0.30.21/node_modules/magic-string/dist/magic-string.es.js +0 -1126
  278. package/bundle/dist/node_modules/.pnpm/pathe@2.0.3/node_modules/pathe/dist/index.js +0 -21
  279. package/bundle/dist/node_modules/.pnpm/pathe@2.0.3/node_modules/pathe/dist/shared/pathe.M-eThtNZ.js +0 -432
  280. package/bundle/dist/node_modules/.pnpm/tinybench@2.9.0/node_modules/tinybench/dist/index.js +0 -517
  281. package/bundle/dist/node_modules/.pnpm/tinyrainbow@3.1.0/node_modules/tinyrainbow/dist/index.js +0 -101
  282. 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
  283. 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
  284. 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
  285. 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
  286. 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
  287. 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
  288. 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
  289. 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
  290. 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
  291. 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
  292. package/bundle/dist/testing/setup-mocks.js +0 -16
  293. package/bundle/src/testing/setup-mocks.ts +0 -44
  294. package/bundle/web-dist/client/assets/index-Bw2s88Ot.css +0 -1
  295. package/bundle/web-dist/client/assets/index-CrXKu_J8.js +0 -106
  296. package/bundle/web-dist/client/assets/logs-B2SmWJXA.js +0 -1
  297. package/bundle/web-dist/client/assets/monit-D-JwXXoA.js +0 -1
  298. package/bundle/web-dist/client/assets/plus-B62CuqT8.js +0 -1
  299. package/bundle/web-dist/client/assets/refresh-cw-NNcU0Cmq.js +0 -1
  300. package/bundle/web-dist/client/assets/request-model-CojtinqK.js +0 -1
  301. package/bundle/web-dist/client/assets/routes-C5r4gTvY.js +0 -5
  302. package/bundle/web-dist/client/assets/settings-D3tDSAck.js +0 -1
  303. package/bundle/web-dist/client/assets/show-Cah_kUKC.js +0 -2
  304. package/bundle/web-dist/client/assets/tokens-DU2Iyxxd.js +0 -1
  305. package/bundle/web-dist/client/assets/use-oauth-login-flow-D0UaR9_e.js +0 -1
  306. package/bundle/web-dist/client/assets/use-update-token-mutation-DAq0F8p-.js +0 -1
  307. package/bundle/web-dist/client/assets/vendor-react-DH4kUfK2.js +0 -9
  308. package/bundle/web-dist/client/assets/vendor-tanstack-DJRQAbrY.js +0 -1
  309. package/bundle/web-dist/server/assets/logs-l91avYif.js +0 -509
  310. package/bundle/web-dist/server/assets/plus-KZrECewT.js +0 -19
  311. package/bundle/web-dist/server/assets/refresh-cw-BND93JrF.js +0 -23
  312. package/bundle/web-dist/server/assets/settings-BZR5HSAL.js +0 -461
  313. package/bundle/web-dist/server/assets/tokens-D1hb4eKk.js +0 -448
  314. package/bundle/web-dist/server/assets/use-oauth-login-flow-Cvdy0zF8.js +0 -178
  315. package/dist/cli.d.ts +0 -10
  316. /package/bundle/web-dist/client/assets/{cost-DH2jZbrw.js → cost-DB3HbU7T.js} +0 -0
  317. /package/bundle/web-dist/client/assets/{preload-helper-ca-nBW7U.js → preload-helper-DWTEM3RW.js} +0 -0
  318. /package/bundle/web-dist/server/assets/{cost-eEHeTRT5.js → cost-BC9yzyA9.js} +0 -0
@@ -0,0 +1,1395 @@
1
+ import { zstdDecompressSync } from "node:zlib";
2
+
3
+ import { describe, expect, it, vi } from "vitest";
4
+
5
+ import { QuotaThresholdExceededError } from "../../../application/qgrid/qgrid.types";
6
+ import { type GenerateRequest } from "../common/provider-dispatcher";
7
+ import golden from "./__fixtures__/golden-responses-1.0.60.json";
8
+ import { GROK_OAUTH_CLIENT_ID, GROK_OAUTH_SCOPES } from "./grok-constants";
9
+ import { GrokHttpError } from "./grok-direct-client";
10
+ import {
11
+ GrokAttemptBudgetExceededError,
12
+ GrokCapacityError,
13
+ GrokDispatcher,
14
+ GrokInvalidRequestError,
15
+ GrokNoWorkersError,
16
+ GrokOnDemandExcludedError,
17
+ GrokRateLimitedError,
18
+ GrokVersionGateError,
19
+ GrokZdrVideoUnavailableError,
20
+ grokTicksToUsd,
21
+ } from "./grok-dispatcher";
22
+ import { GrokMediaClient, GrokMediaDeliveryError } from "./grok-media-client";
23
+ import { GrokVoiceClient } from "./grok-voice-client";
24
+ import {
25
+ type GrokCredentials,
26
+ type GrokMediaAuth,
27
+ type GrokMediaPort,
28
+ type GrokRefresher,
29
+ type GrokRefreshResult,
30
+ type GrokVoicePort,
31
+ } from "./grok-types";
32
+
33
+ // ---------------------------------------------------------------------------
34
+ // Fixtures
35
+ // ---------------------------------------------------------------------------
36
+
37
+ function segment(value: unknown): string {
38
+ return Buffer.from(JSON.stringify(value)).toString("base64url");
39
+ }
40
+
41
+ function jwt(sub: string): string {
42
+ return `${segment({ alg: "ES256", typ: "at+jwt" })}.${segment({
43
+ iss: "https://auth.x.ai",
44
+ sub,
45
+ exp: 2_000_000_000,
46
+ })}.c2ln`;
47
+ }
48
+
49
+ function guid(n: number, block: number): string {
50
+ const hex = n.toString(16).padStart(12, "0");
51
+ return `${String(block).repeat(8)}-0000-4000-8000-${hex}`;
52
+ }
53
+
54
+ function credentialsFor(id: number, version = 0): GrokCredentials {
55
+ return {
56
+ authMode: "oauth",
57
+ accessToken: jwt(`user-${id}-v${version}`),
58
+ refreshToken: `refresh-${id}`,
59
+ expiresAt: "2030-01-01T00:00:00Z",
60
+ issuer: "https://auth.x.ai",
61
+ clientId: GROK_OAUTH_CLIENT_ID,
62
+ scopes: [...GROK_OAUTH_SCOPES],
63
+ userId: guid(id, 1),
64
+ sessionId: guid(id, 2),
65
+ agentId: guid(id, 3),
66
+ };
67
+ }
68
+
69
+ const sseHeaders = { ...golden.response.headers };
70
+
71
+ function sseEvent(value: { type: string } & Record<string, unknown>): string {
72
+ return `event: ${value.type}\ndata: ${JSON.stringify(value)}\n\n`;
73
+ }
74
+
75
+ /** A stream built from the fixture's event types, ending at response.completed (no [DONE]). */
76
+ function goldenStream(text = "pong"): string {
77
+ const half = Math.ceil(text.length / 2);
78
+ return [
79
+ sseEvent({ type: "response.created", response: { id: "resp_1", model: "grok-4.7" } }),
80
+ sseEvent({ type: "response.in_progress", response: { id: "resp_1" } }),
81
+ sseEvent({ type: "response.output_item.added", item: { type: "reasoning", id: "rs_1" } }),
82
+ sseEvent({ type: "response.reasoning_summary_part.added", item_id: "rs_1" }),
83
+ sseEvent({ type: "response.reasoning_summary_text.delta", item_id: "rs_1", delta: "hm" }),
84
+ sseEvent({ type: "response.reasoning_summary_text.done", item_id: "rs_1" }),
85
+ sseEvent({ type: "response.reasoning_summary_part.done", item_id: "rs_1" }),
86
+ sseEvent({
87
+ type: "response.output_item.done",
88
+ item: { type: "reasoning", id: "rs_1", encrypted_content: "enc" },
89
+ }),
90
+ sseEvent({ type: "response.content_part.added", item_id: "msg_1" }),
91
+ sseEvent({ type: "response.output_text.delta", item_id: "msg_1", delta: text.slice(0, half) }),
92
+ sseEvent({ type: "response.output_text.delta", item_id: "msg_1", delta: text.slice(half) }),
93
+ sseEvent({ type: "response.output_text.done", item_id: "msg_1" }),
94
+ sseEvent({ type: "response.content_part.done", item_id: "msg_1" }),
95
+ sseEvent({
96
+ type: "response.completed",
97
+ response: { id: "resp_1", model: "grok-4.7", usage: golden.response.terminalUsage },
98
+ }),
99
+ ].join("");
100
+ }
101
+
102
+ type ChatReply =
103
+ | { kind: "ok"; body?: string; headers?: Record<string, string> }
104
+ | { kind: "http"; status: number; body?: string; headers?: Record<string, string> }
105
+ | { kind: "connect" };
106
+
107
+ interface CapturedRequest {
108
+ url: string;
109
+ headers: Headers;
110
+ body: unknown;
111
+ }
112
+
113
+ interface Harness {
114
+ dispatcher: GrokDispatcher;
115
+ clock: { now: number };
116
+ chat: Map<number, ChatReply[]>;
117
+ settings: Map<number, unknown>;
118
+ /** GET /user?include=subscription bodies; absent ids answer 404 (lookup_failed). */
119
+ users: Map<number, unknown>;
120
+ billing: Map<number, unknown>;
121
+ requests: CapturedRequest[];
122
+ /** "<path> <access token>" for every /settings, /user and /billing lookup, in call order. */
123
+ events: string[];
124
+ /** Access tokens the lookup endpoints answer with 401. */
125
+ revoked: Set<string>;
126
+ reauth: ReturnType<typeof vi.fn>;
127
+ refresher: GrokRefresher & {
128
+ ensureFresh: ReturnType<typeof vi.fn>;
129
+ refreshAfterUnauthorized: ReturnType<typeof vi.fn>;
130
+ };
131
+ add(id: number, weight?: number, threshold?: number | null): void;
132
+ chatCalls(): number[];
133
+ }
134
+
135
+ function tokenIdOf(headers: Headers, harness: () => Harness): number {
136
+ const auth = headers.get("authorization") ?? "";
137
+ for (const [id, token] of harness().dispatcher.tokens) {
138
+ if (token.credentials && auth === `Bearer ${token.credentials.accessToken}`) return id;
139
+ }
140
+ for (const id of harness().dispatcher.tokens.keys()) {
141
+ for (let v = 0; v < 5; v++) if (auth === `Bearer ${credentialsFor(id, v).accessToken}`) return id;
142
+ }
143
+ return -1;
144
+ }
145
+
146
+ function json(value: unknown, status = 200): Response {
147
+ return new Response(JSON.stringify(value), {
148
+ status,
149
+ headers: { "content-type": "application/json" },
150
+ });
151
+ }
152
+
153
+ function billingAt(percent: number, end = "2030-01-08T00:00:00Z") {
154
+ return {
155
+ config: {
156
+ currentPeriod: { type: "USAGE_PERIOD_TYPE_WEEKLY", start: "2030-01-01T00:00:00Z", end },
157
+ creditUsagePercent: percent,
158
+ prepaidBalance: { val: 0 },
159
+ },
160
+ };
161
+ }
162
+
163
+ function createHarness(
164
+ options: {
165
+ maxInflightPerToken?: number;
166
+ media?: GrokMediaPort;
167
+ voice?: GrokVoicePort;
168
+ sleep?: boolean;
169
+ } = {},
170
+ ): Harness {
171
+ const clock = { now: Date.parse("2030-01-02T00:00:00Z") };
172
+ const chat = new Map<number, ChatReply[]>();
173
+ const settings = new Map<number, unknown>();
174
+ const users = new Map<number, unknown>();
175
+ const billing = new Map<number, unknown>();
176
+ const requests: CapturedRequest[] = [];
177
+ const events: string[] = [];
178
+ const revoked = new Set<string>();
179
+ let harness: Harness | undefined;
180
+ const self = () => harness!;
181
+
182
+ const fakeFetch: typeof fetch = async (input, init) => {
183
+ const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
184
+ const headers = new Headers(init?.headers);
185
+ const id = tokenIdOf(headers, self);
186
+ const lookup = ["/settings", "/user?", "/billing"].find((path) => url.includes(path));
187
+ if (lookup) {
188
+ const bearer = (headers.get("authorization") ?? "").replace(/^Bearer /, "");
189
+ events.push(`${lookup} ${bearer}`);
190
+ if (revoked.has(bearer)) return json({ error: "unauthorized" }, 401);
191
+ }
192
+ if (url.endsWith("/settings")) return json(settings.get(id) ?? { on_demand_enabled: null });
193
+ if (url.includes("/user?")) return users.has(id) ? json(users.get(id)) : json({}, 404);
194
+ if (url.includes("/billing")) return json(billing.get(id) ?? billingAt(1));
195
+ if (url.endsWith("/models")) return json({ error: "not found" }, 404);
196
+ if (url.endsWith("/responses")) {
197
+ const raw = init?.body instanceof Uint8Array ? init.body : new Uint8Array();
198
+ const body: unknown =
199
+ headers.get("content-encoding") === "zstd"
200
+ ? JSON.parse(zstdDecompressSync(raw).toString("utf8"))
201
+ : JSON.parse(new TextDecoder().decode(raw));
202
+ requests.push({ url, headers, body });
203
+ const reply = chat.get(id)?.shift() ?? { kind: "ok" };
204
+ if (reply.kind === "connect") throw new TypeError("fetch failed");
205
+ if (reply.kind === "http") {
206
+ return new Response(reply.body ?? "", {
207
+ status: reply.status,
208
+ headers: reply.headers ?? {},
209
+ });
210
+ }
211
+ return new Response(reply.body ?? goldenStream(), {
212
+ status: 200,
213
+ headers: reply.headers ?? sseHeaders,
214
+ });
215
+ }
216
+ return json({}, 404);
217
+ };
218
+
219
+ const refresher = {
220
+ ensureFresh: vi.fn(async (token: { credentials: GrokCredentials }) => token.credentials),
221
+ refreshAfterUnauthorized: vi.fn(
222
+ async (token: { tokenId: number }): Promise<GrokRefreshResult> => ({
223
+ status: "refreshed",
224
+ credentials: credentialsFor(token.tokenId, 1),
225
+ }),
226
+ ),
227
+ };
228
+ const reauth = vi.fn(async () => true);
229
+
230
+ const dispatcher = new GrokDispatcher({
231
+ refresher,
232
+ fetch: fakeFetch,
233
+ now: () => clock.now,
234
+ maxInflightPerToken: options.maxInflightPerToken,
235
+ reportReauthRequired: reauth,
236
+ ...(options.media ? { media: options.media } : {}),
237
+ ...(options.voice ? { voice: options.voice } : {}),
238
+ sleep: async (ms) => {
239
+ clock.now += ms;
240
+ },
241
+ });
242
+
243
+ harness = {
244
+ dispatcher,
245
+ clock,
246
+ chat,
247
+ settings,
248
+ users,
249
+ billing,
250
+ requests,
251
+ events,
252
+ revoked,
253
+ reauth,
254
+ refresher,
255
+ add(id, weight = 1, threshold = null) {
256
+ dispatcher.onTokenAdded(id, `t${id}`, credentialsFor(id), threshold, weight);
257
+ },
258
+ chatCalls() {
259
+ return requests.map((r) => tokenIdOf(r.headers, self));
260
+ },
261
+ };
262
+ return harness;
263
+ }
264
+
265
+ function request(overrides: Partial<GenerateRequest> = {}): GenerateRequest {
266
+ return {
267
+ model: "grok-4.7",
268
+ coldInput: [{ type: "text", text: "ping", text_elements: [] }],
269
+ ...overrides,
270
+ };
271
+ }
272
+
273
+ const exhausted = JSON.stringify({ code: "subscription:free-usage-exhausted", error: "exhausted" });
274
+
275
+ // ---------------------------------------------------------------------------
276
+ // Selection and token sink
277
+ // ---------------------------------------------------------------------------
278
+
279
+ describe("GrokDispatcher selection", () => {
280
+ it("distributes 800 selections 5:2:1 through SmoothWeightedRoundRobin", async () => {
281
+ const h = createHarness();
282
+ h.add(1, 5);
283
+ h.add(2, 2);
284
+ h.add(3, 1);
285
+ const counts = new Map<number, number>();
286
+ for (let i = 0; i < 800; i++) {
287
+ const reservation = await h.dispatcher.selectToken("chat");
288
+ counts.set(reservation.token.id, (counts.get(reservation.token.id) ?? 0) + 1);
289
+ reservation.release();
290
+ }
291
+ expect(counts.get(1)).toBe(500);
292
+ expect(counts.get(2)).toBe(200);
293
+ expect(counts.get(3)).toBe(100);
294
+ });
295
+
296
+ it("feeds the selector from onTokenAdded/Updated/Removed and replaceTokens", () => {
297
+ const h = createHarness();
298
+ h.add(1, 5);
299
+ h.add(2, 2);
300
+ expect([...h.dispatcher.selector.weights]).toEqual([
301
+ [1, 5],
302
+ [2, 2],
303
+ ]);
304
+ h.dispatcher.onTokenUpdated(2, "t2", credentialsFor(2), null, 7);
305
+ expect(h.dispatcher.selector.weights.get(2)).toBe(7);
306
+ h.dispatcher.onTokenRemoved(1);
307
+ expect(h.dispatcher.selector.weights.has(1)).toBe(false);
308
+ expect(h.dispatcher.tokens.has(1)).toBe(false);
309
+ h.dispatcher.replaceTokens([
310
+ { id: 3, name: "t3", credentials: credentialsFor(3), quotaThreshold: null, weight: 4 },
311
+ ]);
312
+ expect([...h.dispatcher.selector.weights]).toEqual([[3, 4]]);
313
+ expect([...h.dispatcher.tokens.keys()]).toEqual([3]);
314
+ });
315
+
316
+ it("treats an invalid credentials row as inactive without throwing", async () => {
317
+ const h = createHarness();
318
+ expect(() =>
319
+ h.dispatcher.onTokenAdded(9, "bad", { accessToken: "xai-api-key" }, null, 1),
320
+ ).not.toThrow();
321
+ const [state] = h.dispatcher.tokenStates();
322
+ expect(state).toMatchObject({ id: 9, active: false });
323
+ expect(state!.invalidReason).toContain("accessToken");
324
+ expect(state!.invalidReason).not.toContain("xai-api-key");
325
+ await expect(h.dispatcher.selectToken("chat")).rejects.toBeInstanceOf(GrokNoWorkersError);
326
+ // A later valid update reactivates it.
327
+ h.dispatcher.onTokenUpdated(9, "bad", credentialsFor(9), null, 1);
328
+ const reservation = await h.dispatcher.selectToken("chat");
329
+ expect(reservation.token.id).toBe(9);
330
+ });
331
+
332
+ it("never exceeds the in-flight cap under 50 concurrent selections", async () => {
333
+ const h = createHarness({ maxInflightPerToken: 1 });
334
+ h.add(1, 5);
335
+ h.add(2, 2);
336
+ h.add(3, 1);
337
+ const results = await Promise.allSettled(
338
+ Array.from({ length: 50 }, () => h.dispatcher.selectToken("chat")),
339
+ );
340
+ const fulfilled = results.filter((r) => r.status === "fulfilled");
341
+ const rejected = results.filter((r) => r.status === "rejected");
342
+ expect(fulfilled).toHaveLength(3);
343
+ expect(new Set(fulfilled.map((r) => r.value.token.id))).toEqual(new Set([1, 2, 3]));
344
+ for (const r of rejected) expect(r.reason).toBeInstanceOf(GrokCapacityError);
345
+ for (const token of h.dispatcher.tokens.values()) expect(token.inFlight).toBe(1);
346
+ for (const r of fulfilled) r.value.release();
347
+ for (const token of h.dispatcher.tokens.values()) expect(token.inFlight).toBe(0);
348
+ });
349
+
350
+ it("honors preferredTokenId and requirePreferredToken", async () => {
351
+ const h = createHarness();
352
+ h.add(1, 5, 50);
353
+ h.add(2, 1);
354
+ const preferred = await h.dispatcher.selectToken("chat", { preferredTokenId: 2 });
355
+ expect(preferred.token.id).toBe(2);
356
+ preferred.release();
357
+ h.billing.set(1, billingAt(90));
358
+ await expect(
359
+ h.dispatcher.selectToken("chat", { preferredTokenId: 1, requirePreferredToken: true }),
360
+ ).rejects.toBeInstanceOf(QuotaThresholdExceededError);
361
+ const fallback = await h.dispatcher.selectToken("chat", { preferredTokenId: 1 });
362
+ expect(fallback.token.id).toBe(2);
363
+ });
364
+
365
+ it("excludes on-demand accounts (D6) and allows on_demand_enabled null", async () => {
366
+ const h = createHarness();
367
+ h.add(1);
368
+ h.settings.set(1, { on_demand_enabled: true });
369
+ await expect(h.dispatcher.selectToken("chat")).rejects.toBeInstanceOf(
370
+ GrokOnDemandExcludedError,
371
+ );
372
+ expect(h.dispatcher.tokenStates()[0]!.onDemandExcluded).toBe(true);
373
+
374
+ const h2 = createHarness();
375
+ h2.add(1);
376
+ h2.settings.set(1, { on_demand_enabled: null });
377
+ const reservation = await h2.dispatcher.selectToken("chat");
378
+ expect(reservation.token.id).toBe(1);
379
+ });
380
+
381
+ it("fails open when billing and settings lookups fail", async () => {
382
+ const h = createHarness();
383
+ h.add(1, 1, 10);
384
+ h.billing.set(1, { unexpected: true });
385
+ h.settings.set(1, "not an object");
386
+ const reservation = await h.dispatcher.selectToken("chat");
387
+ expect(reservation.token.id).toBe(1);
388
+ });
389
+
390
+ it("refreshes an expired token before the gating lookups and gates on the refreshed values", async () => {
391
+ const h = createHarness();
392
+ h.add(1, 1, 50);
393
+ const fresh = { ...credentialsFor(1, 1), expiresAt: "2031-01-01T00:00:00Z" };
394
+ // The stored bearer is expired: a lookup with it would fail and open the gates.
395
+ h.revoked.add(credentialsFor(1).accessToken);
396
+ h.settings.set(1, { on_demand_enabled: true });
397
+ h.refresher.ensureFresh.mockImplementation(async () => {
398
+ h.events.push("refresh");
399
+ return fresh;
400
+ });
401
+ await expect(h.dispatcher.generate(request())).rejects.toBeInstanceOf(
402
+ GrokOnDemandExcludedError,
403
+ );
404
+ expect(h.refresher.ensureFresh).toHaveBeenCalledTimes(1);
405
+ expect(h.events[0]).toBe("refresh");
406
+ expect(h.events.slice(1).sort()).toEqual(
407
+ [
408
+ `/billing ${fresh.accessToken}`,
409
+ `/settings ${fresh.accessToken}`,
410
+ `/user? ${fresh.accessToken}`,
411
+ ].sort(),
412
+ );
413
+ expect(h.requests).toHaveLength(0);
414
+ });
415
+
416
+ it("persists reauth and skips the lookups when the pre-lookup refresh finds a dead token", async () => {
417
+ const h = createHarness();
418
+ h.add(1);
419
+ h.add(2);
420
+ h.refresher.ensureFresh.mockImplementationOnce(async () => {
421
+ throw Object.assign(new Error("dead"), {
422
+ name: "GrokReauthRequiredError",
423
+ oauthError: "invalid_grant",
424
+ });
425
+ });
426
+ const reservation = await h.dispatcher.selectToken("chat", { preferredTokenId: 1 });
427
+ expect(reservation.token.id).toBe(2);
428
+ expect(h.dispatcher.tokens.get(1)!.reauthRequired).toBe(true);
429
+ expect(h.events.some((event) => event.endsWith(credentialsFor(1).accessToken))).toBe(false);
430
+ expect(h.reauth).toHaveBeenCalledTimes(1);
431
+ expect(h.reauth).toHaveBeenCalledWith(
432
+ { id: 1, name: "t1", credentials: credentialsFor(1) },
433
+ "grok:invalid_grant",
434
+ );
435
+ });
436
+
437
+ it("re-reads the cached lookups after a refresh replaces the bearer", async () => {
438
+ const h = createHarness();
439
+ h.add(1);
440
+ h.chat.set(1, [{ kind: "http", status: 401 }]);
441
+ await h.dispatcher.generate(request());
442
+ const token = h.dispatcher.tokens.get(1)!;
443
+ expect(token.credentials?.accessToken).toBe(credentialsFor(1, 1).accessToken);
444
+ // The same-token retry re-gates with the refreshed bearer before it is sent.
445
+ expect(h.events).toContain(`/settings ${credentialsFor(1, 1).accessToken}`);
446
+ expect(h.events).toContain(`/user? ${credentialsFor(1, 1).accessToken}`);
447
+ h.events.length = 0;
448
+ await h.dispatcher.prefetch(token);
449
+ expect(h.events).toEqual([]);
450
+ });
451
+
452
+ it("blocks over-threshold tokens and reports QuotaThresholdExceededError", async () => {
453
+ const h = createHarness();
454
+ h.add(1, 1, 50);
455
+ h.billing.set(1, billingAt(75));
456
+ await expect(h.dispatcher.selectToken("chat")).rejects.toBeInstanceOf(
457
+ QuotaThresholdExceededError,
458
+ );
459
+ });
460
+ });
461
+
462
+ // ---------------------------------------------------------------------------
463
+ // Chat failure handling (§5.4)
464
+ // ---------------------------------------------------------------------------
465
+
466
+ describe("GrokDispatcher chat", () => {
467
+ it("parses the fixture stream and maps usage and cost", async () => {
468
+ const h = createHarness();
469
+ h.add(7);
470
+ const deltas: string[] = [];
471
+ const result = await new Promise<Awaited<ReturnType<GrokDispatcher["generate"]>>>(
472
+ (resolve, reject) => {
473
+ h.dispatcher
474
+ .generateStream(request({ promptCacheKey: "conv-key", systemPrompt: "be brief" }), {
475
+ onDelta: (text) => deltas.push(text),
476
+ onComplete: resolve,
477
+ onError: reject,
478
+ })
479
+ .catch(reject);
480
+ },
481
+ );
482
+ expect(deltas.join("")).toBe("pong");
483
+ expect(result).toMatchObject({
484
+ text: "pong",
485
+ tokenName: "t7",
486
+ model: "grok-4.7",
487
+ costUsd: 0.046524,
488
+ costSource: "provider",
489
+ usage: {
490
+ inputTokens: 27150,
491
+ cachedInputTokens: 6528,
492
+ outputTokens: 336,
493
+ reasoningOutputTokens: 335,
494
+ totalTokens: 27486,
495
+ contextTokens: 27150 + 344,
496
+ },
497
+ providerMetadata: { xai: { costInUsdTicks: 465240000, contextWindow: 256000 } },
498
+ threadCoord: { workerId: 7, threadId: "conv-key", epoch: -1 },
499
+ });
500
+ expect(grokTicksToUsd(465240000)).toBe(0.046524);
501
+
502
+ const sent = h.requests[0]!;
503
+ expect(sent.headers.get("content-encoding")).toBe("zstd");
504
+ expect(sent.headers.get("x-grok-model-override")).toBe("grok-4.7");
505
+ expect(sent.headers.get("x-grok-user-id")).toBe(credentialsFor(7).userId);
506
+ expect(sent.headers.get("x-grok-session-id")).toBe(credentialsFor(7).sessionId);
507
+ expect(sent.headers.get("x-grok-turn-idx")).toBe("1");
508
+ const convId = sent.headers.get("x-grok-conv-id");
509
+ expect(convId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/);
510
+ expect(sent.body).toMatchObject({
511
+ model: "grok-4.7",
512
+ prompt_cache_key: convId,
513
+ store: false,
514
+ stream: true,
515
+ input: [
516
+ { type: "message", role: "system", content: "be brief" },
517
+ { type: "message", role: "user", content: [{ type: "input_text", text: "ping" }] },
518
+ ],
519
+ });
520
+
521
+ // Same promptCacheKey -> same conv id, fresh request id.
522
+ await h.dispatcher.generate(request({ promptCacheKey: "conv-key" }));
523
+ expect(h.requests[1]!.headers.get("x-grok-conv-id")).toBe(convId);
524
+ expect(h.requests[1]!.headers.get("x-grok-req-id")).not.toBe(
525
+ sent.headers.get("x-grok-req-id"),
526
+ );
527
+ });
528
+
529
+ it("rejects fields Grok does not support", async () => {
530
+ const h = createHarness();
531
+ h.add(1);
532
+ await expect(h.dispatcher.generate(request({ imageGeneration: true }))).rejects.toBeInstanceOf(
533
+ GrokInvalidRequestError,
534
+ );
535
+ await expect(h.dispatcher.generate(request({ serviceTier: "fast" }))).rejects.toBeInstanceOf(
536
+ GrokInvalidRequestError,
537
+ );
538
+ for (const xai of [{ serviceTier: "priority" }, { parallelToolCalls: false }]) {
539
+ await expect(
540
+ h.dispatcher.generate(request({ providerOptions: { xai } })),
541
+ ).rejects.toBeInstanceOf(GrokInvalidRequestError);
542
+ }
543
+ expect(h.requests).toHaveLength(0);
544
+ });
545
+
546
+ it("forwards an unlisted model id and reports the serving model", async () => {
547
+ const h = createHarness();
548
+ h.add(1);
549
+ h.chat.set(1, [{ kind: "ok", body: goldenStream().replaceAll("grok-4.7", "grok-4.5-build") }]);
550
+ const result = await h.dispatcher.generate(request({ model: "grok/grok-composer-2.5" }));
551
+ expect(result.model).toBe("grok-4.5-build");
552
+ expect(result.requestedModel).toBe("grok-composer-2.5");
553
+ expect(result.providerMetadata).toEqual({
554
+ xai: { costInUsdTicks: 465240000, contextWindow: 256000 },
555
+ });
556
+ const sent = h.requests[0]!;
557
+ expect(sent.headers.get("x-grok-model-override")).toBe("grok-composer-2.5");
558
+ expect(sent.body).toMatchObject({ model: "grok-composer-2.5", reasoning: { effort: "high" } });
559
+ });
560
+
561
+ it("puts a 429 token in cooldown, fails over, and readmits it after expiry", async () => {
562
+ const h = createHarness();
563
+ h.add(1, 5);
564
+ h.add(2, 1);
565
+ h.chat.set(1, [{ kind: "http", status: 429, headers: { "retry-after": "5" } }]);
566
+ const result = await h.dispatcher.generate(request({ preferredTokenId: 1 }));
567
+ expect(result.tokenName).toBe("t2");
568
+ expect(h.chatCalls()).toEqual([1, 2]);
569
+ expect(h.dispatcher.isEligible(h.dispatcher.tokens.get(1)!, "chat")).toBe(false);
570
+ // Cooldown is scoped to the request class.
571
+ expect(h.dispatcher.isEligible(h.dispatcher.tokens.get(1)!, "image")).toBe(true);
572
+ h.clock.now += 5_001;
573
+ expect(h.dispatcher.isEligible(h.dispatcher.tokens.get(1)!, "chat")).toBe(true);
574
+ });
575
+
576
+ it("caps Retry-After at 120 s and reports GrokRateLimitedError after one wait", async () => {
577
+ const h = createHarness();
578
+ h.add(1);
579
+ h.chat.set(1, [{ kind: "http", status: 429, headers: { "retry-after": "600" } }]);
580
+ const startedAt = h.clock.now;
581
+ const error = await h.dispatcher.generate(request()).catch((e: unknown) => e);
582
+ expect(error).toBeInstanceOf(GrokRateLimitedError);
583
+ // 120 s cooldown, one 30 s in-place wait -> 90 s left.
584
+ expect((error as GrokRateLimitedError).retryAfterSec).toBe(90);
585
+ expect(h.clock.now - startedAt).toBe(30_000);
586
+ });
587
+
588
+ it("blocks an exhausted token until a fresh billing read below threshold", async () => {
589
+ const h = createHarness();
590
+ h.add(1, 5, 80);
591
+ h.add(2, 1);
592
+ h.chat.set(1, [{ kind: "http", status: 429, body: exhausted }]);
593
+ const result = await h.dispatcher.generate(request({ preferredTokenId: 1 }));
594
+ expect(result.tokenName).toBe("t2");
595
+ const token = h.dispatcher.tokens.get(1)!;
596
+ expect(token.quotaBlocks.get("chat")).toBe(Date.parse("2030-01-08T00:00:00Z"));
597
+ // Within one cache period no billing read is made, so the block holds.
598
+ await h.dispatcher.prefetch(token);
599
+ expect(h.dispatcher.isEligible(token, "chat")).toBe(false);
600
+ h.clock.now += 61_000;
601
+ h.billing.set(1, billingAt(50));
602
+ await h.dispatcher.prefetch(token);
603
+ expect(h.dispatcher.isEligible(token, "chat")).toBe(true);
604
+ });
605
+
606
+ it("refreshes once on 401 and retries the same token", async () => {
607
+ const h = createHarness();
608
+ h.add(1);
609
+ h.add(2);
610
+ h.chat.set(1, [{ kind: "http", status: 401 }]);
611
+ const result = await h.dispatcher.generate(request({ preferredTokenId: 1 }));
612
+ expect(result.tokenName).toBe("t1");
613
+ expect(h.refresher.refreshAfterUnauthorized).toHaveBeenCalledTimes(1);
614
+ expect(h.requests[1]!.headers.get("authorization")).toBe(
615
+ `Bearer ${credentialsFor(1, 1).accessToken}`,
616
+ );
617
+ });
618
+
619
+ it("marks reauthRequired after a second 401", async () => {
620
+ const h = createHarness();
621
+ h.add(1);
622
+ h.chat.set(1, [
623
+ { kind: "http", status: 401 },
624
+ { kind: "http", status: 401 },
625
+ ]);
626
+ const error = await h.dispatcher.generate(request()).catch((e: unknown) => e);
627
+ expect(error).toBeInstanceOf(GrokHttpError);
628
+ expect((error as GrokHttpError).status).toBe(401);
629
+ expect(h.refresher.refreshAfterUnauthorized).toHaveBeenCalledTimes(1);
630
+ expect(h.dispatcher.tokenStates()[0]!.reauthRequired).toBe(true);
631
+ expect(h.reauth).toHaveBeenCalledTimes(1);
632
+ expect(h.reauth).toHaveBeenCalledWith(
633
+ { id: 1, name: "t1", credentials: credentialsFor(1, 1) },
634
+ "grok:unauthorized_after_refresh",
635
+ );
636
+ });
637
+
638
+ it("marks reauthRequired when the refresher reports reauth_required", async () => {
639
+ const h = createHarness();
640
+ h.add(1);
641
+ h.add(2);
642
+ h.chat.set(1, [{ kind: "http", status: 401 }]);
643
+ h.refresher.refreshAfterUnauthorized.mockResolvedValueOnce({
644
+ status: "reauth_required",
645
+ reason: "invalid_grant",
646
+ });
647
+ const result = await h.dispatcher.generate(request({ preferredTokenId: 1 }));
648
+ expect(result.tokenName).toBe("t2");
649
+ expect(h.dispatcher.tokens.get(1)!.reauthRequired).toBe(true);
650
+ expect(h.reauth).toHaveBeenCalledTimes(1);
651
+ expect(h.reauth).toHaveBeenCalledWith(
652
+ { id: 1, name: "t1", credentials: credentialsFor(1) },
653
+ "grok:invalid_grant",
654
+ );
655
+ // A dead token is not selected again, so nothing is persisted twice.
656
+ await h.dispatcher.generate(request({ preferredTokenId: 1 }));
657
+ expect(h.reauth).toHaveBeenCalledTimes(1);
658
+ });
659
+
660
+ it("fails over and marks reauth when ensureFresh reports a dead refresh token", async () => {
661
+ const h = createHarness();
662
+ h.add(1);
663
+ h.add(2);
664
+ h.refresher.ensureFresh.mockImplementationOnce(async () => {
665
+ throw Object.assign(new Error("invalid_grant"), { name: "GrokReauthRequiredError" });
666
+ });
667
+ const result = await h.dispatcher.generate(request({ preferredTokenId: 1 }));
668
+ expect(result.tokenName).toBe("t2");
669
+ expect(h.dispatcher.tokens.get(1)!.reauthRequired).toBe(true);
670
+ expect(h.chatCalls()).toEqual([2]);
671
+ });
672
+
673
+ it("fails over without marking reauth when the forced refresh throws", async () => {
674
+ const h = createHarness();
675
+ h.add(1);
676
+ h.add(2);
677
+ h.chat.set(1, [{ kind: "http", status: 401 }]);
678
+ h.refresher.refreshAfterUnauthorized.mockRejectedValueOnce(new Error("network"));
679
+ const result = await h.dispatcher.generate(request({ preferredTokenId: 1 }));
680
+ expect(result.tokenName).toBe("t2");
681
+ expect(h.dispatcher.tokens.get(1)!.reauthRequired).toBe(false);
682
+ expect(h.reauth).not.toHaveBeenCalled();
683
+ });
684
+
685
+ it("treats a chat 403 as permanent for the request", async () => {
686
+ const h = createHarness();
687
+ h.add(1);
688
+ h.add(2);
689
+ h.chat.set(1, [{ kind: "http", status: 403, body: "forbidden" }]);
690
+ const error = await h.dispatcher
691
+ .generate(request({ preferredTokenId: 1 }))
692
+ .catch((e: unknown) => e);
693
+ expect(error).toBeInstanceOf(GrokHttpError);
694
+ expect((error as GrokHttpError).bodyText).toBe("forbidden");
695
+ expect(h.chatCalls()).toEqual([1]);
696
+ expect(h.refresher.refreshAfterUnauthorized).not.toHaveBeenCalled();
697
+ });
698
+
699
+ it("does not retry or fail over when x-should-retry is false", async () => {
700
+ const h = createHarness();
701
+ h.add(1);
702
+ h.add(2);
703
+ h.chat.set(1, [
704
+ { kind: "http", status: 429, headers: { "x-should-retry": "false", "retry-after": "3" } },
705
+ ]);
706
+ await expect(
707
+ h.dispatcher.generate(request({ preferredTokenId: 1 })),
708
+ ).rejects.toBeInstanceOf(GrokHttpError);
709
+ expect(h.chatCalls()).toEqual([1]);
710
+ });
711
+
712
+ it("trips the version gate on 426, naming both versions, and re-probes after 10 min", async () => {
713
+ const generateImages = vi.fn(async () => ({ data: [{ b64_json: "aGk=" }] }));
714
+ const media: GrokMediaPort = {
715
+ generateImages,
716
+ editImages: vi.fn(),
717
+ startVideo: vi.fn(),
718
+ getVideo: vi.fn(),
719
+ };
720
+ const h = createHarness({ media });
721
+ h.add(1);
722
+ h.add(2);
723
+ h.chat.set(1, [
724
+ {
725
+ kind: "http",
726
+ status: 426,
727
+ body: JSON.stringify({
728
+ error:
729
+ "Your Grok CLI version (1.0.60) is outdated. Please update to version 1.0.13 or later.",
730
+ }),
731
+ },
732
+ ]);
733
+ const error = await h.dispatcher
734
+ .generate(request({ preferredTokenId: 1 }))
735
+ .catch((e: unknown) => e);
736
+ expect(error).toBeInstanceOf(GrokVersionGateError);
737
+ expect((error as Error).message).toContain("1.0.60");
738
+ expect((error as Error).message).toContain("1.0.13");
739
+ expect(h.chatCalls()).toEqual([1]);
740
+
741
+ // Fail fast without a request while the gate is tripped.
742
+ await expect(h.dispatcher.generate(request())).rejects.toBeInstanceOf(GrokVersionGateError);
743
+ expect(h.requests).toHaveLength(1);
744
+ // api.x.ai traffic is unaffected.
745
+ await expect(h.dispatcher.generateImages({ model: "m", prompt: "p" })).resolves.toMatchObject({
746
+ result: { data: [{ b64_json: "aGk=" }] },
747
+ });
748
+
749
+ h.clock.now += 600_000;
750
+ const result = await h.dispatcher.generate(request());
751
+ expect(result.text).toBe("pong");
752
+ expect(h.dispatcher.versionGateState().tripped).toBe(false);
753
+ });
754
+
755
+ it("fails over on 5xx before the first event", async () => {
756
+ const h = createHarness();
757
+ h.add(1);
758
+ h.add(2);
759
+ h.chat.set(1, [{ kind: "http", status: 503 }]);
760
+ const result = await h.dispatcher.generate(request({ preferredTokenId: 1 }));
761
+ expect(result.tokenName).toBe("t2");
762
+ expect(h.dispatcher.tokens.get(1)!.failoverCount).toBe(1);
763
+ });
764
+
765
+ it("never fails over after an event was forwarded", async () => {
766
+ const h = createHarness();
767
+ h.add(1);
768
+ h.add(2);
769
+ const truncated = goldenStream().split("event: response.output_text.done")[0]!;
770
+ h.chat.set(1, [{ kind: "ok", body: truncated }]);
771
+ const deltas: string[] = [];
772
+ const errors: Error[] = [];
773
+ await expect(
774
+ h.dispatcher.generateStream(request({ preferredTokenId: 1 }), {
775
+ onDelta: (text) => deltas.push(text),
776
+ onComplete: () => undefined,
777
+ onError: (e) => errors.push(e),
778
+ }),
779
+ ).rejects.toThrow(/terminal event/);
780
+ expect(deltas.join("")).toBe("pong");
781
+ expect(errors).toHaveLength(1);
782
+ expect(h.chatCalls()).toEqual([1]);
783
+ });
784
+
785
+ it("does not fail over after response.created even when nothing was forwarded", async () => {
786
+ const h = createHarness();
787
+ h.add(1);
788
+ h.add(2);
789
+ const createdOnly = sseEvent({ type: "response.created", response: { id: "r" } });
790
+ h.chat.set(1, [{ kind: "ok", body: createdOnly }]);
791
+ await expect(h.dispatcher.generate(request({ preferredTokenId: 1 }))).rejects.toThrow();
792
+ expect(h.chatCalls()).toEqual([1]);
793
+ });
794
+
795
+ it("stops after the attempt budget is exhausted", async () => {
796
+ const h = createHarness();
797
+ for (const id of [1, 2, 3, 4]) {
798
+ h.add(id);
799
+ h.chat.set(id, [{ kind: "http", status: 429, headers: { "retry-after": "60" } }]);
800
+ }
801
+ const error = await h.dispatcher.generate(request()).catch((e: unknown) => e);
802
+ expect(error).toBeInstanceOf(GrokAttemptBudgetExceededError);
803
+ expect(h.requests).toHaveLength(3);
804
+ });
805
+
806
+ it("fails over at most once on transient errors", async () => {
807
+ const h = createHarness();
808
+ for (const id of [1, 2, 3]) {
809
+ h.add(id);
810
+ h.chat.set(id, [{ kind: "connect" }]);
811
+ }
812
+ const error = await h.dispatcher.generate(request()).catch((e: unknown) => e);
813
+ expect(error).toBeInstanceOf(GrokHttpError);
814
+ expect((error as GrokHttpError).classification.kind).toBe("transient");
815
+ expect(h.requests).toHaveLength(2);
816
+ });
817
+
818
+ it("releases reservations after success and failure", async () => {
819
+ const h = createHarness({ maxInflightPerToken: 1 });
820
+ h.add(1);
821
+ await h.dispatcher.generate(request());
822
+ h.chat.set(1, [{ kind: "http", status: 400, body: "bad" }]);
823
+ await expect(h.dispatcher.generate(request())).rejects.toBeInstanceOf(GrokHttpError);
824
+ expect(h.dispatcher.tokens.get(1)!.inFlight).toBe(0);
825
+ });
826
+ });
827
+
828
+ // ---------------------------------------------------------------------------
829
+ // Media routing
830
+ // ---------------------------------------------------------------------------
831
+
832
+ describe("GrokDispatcher media", () => {
833
+ it("blocks the request class on media 403 and fails over once", async () => {
834
+ const seen: string[] = [];
835
+ const media: GrokMediaPort = {
836
+ generateImages: vi.fn(async (auth: GrokMediaAuth) => {
837
+ seen.push(auth.bearer);
838
+ if (auth.bearer === credentialsFor(1).accessToken) {
839
+ throw new GrokHttpError({ kind: "entitlement", status: 403 }, "tier");
840
+ }
841
+ return { data: [{ b64_json: "aGk=" }] };
842
+ }),
843
+ editImages: vi.fn(),
844
+ startVideo: vi.fn(),
845
+ getVideo: vi.fn(),
846
+ };
847
+ const h = createHarness({ media });
848
+ h.add(1);
849
+ h.add(2);
850
+ const result = await h.dispatcher.generateImages({
851
+ model: "m",
852
+ prompt: "p",
853
+ preferredTokenId: 1,
854
+ });
855
+ expect(result.tokenId).toBe(2);
856
+ expect(seen).toHaveLength(2);
857
+ const token = h.dispatcher.tokens.get(1)!;
858
+ expect(h.dispatcher.isEligible(token, "image")).toBe(false);
859
+ expect(h.dispatcher.isEligible(token, "stt")).toBe(true);
860
+ expect(h.dispatcher.isEligible(token, "chat")).toBe(true);
861
+ });
862
+
863
+ it("never replays a media POST after a transient failure", async () => {
864
+ const startVideo = vi.fn(async () => {
865
+ throw new GrokHttpError({ kind: "transient", status: 502 }, "");
866
+ });
867
+ const media: GrokMediaPort = {
868
+ generateImages: vi.fn(),
869
+ editImages: vi.fn(),
870
+ startVideo,
871
+ getVideo: vi.fn(),
872
+ };
873
+ const h = createHarness({ media });
874
+ h.add(1);
875
+ h.add(2);
876
+ await expect(h.dispatcher.startVideo({ model: "v", prompt: "p" })).rejects.toBeInstanceOf(
877
+ GrokHttpError,
878
+ );
879
+ expect(startVideo).toHaveBeenCalledTimes(1);
880
+ });
881
+
882
+ it("polls a video on the token that started it", async () => {
883
+ const getVideo = vi.fn(async () => ({ status: "done", video: { url: "https://x/v.mp4" } }));
884
+ const media: GrokMediaPort = {
885
+ generateImages: vi.fn(),
886
+ editImages: vi.fn(),
887
+ startVideo: vi.fn(),
888
+ getVideo,
889
+ };
890
+ const h = createHarness({ media });
891
+ h.add(1, 100);
892
+ h.add(2, 1);
893
+ const status = await h.dispatcher.getVideo(2, "req-1");
894
+ expect(status.tokenId).toBe(2);
895
+ expect(getVideo).toHaveBeenCalledWith(
896
+ { bearer: credentialsFor(2).accessToken, sessionId: credentialsFor(2).sessionId },
897
+ "req-1",
898
+ undefined,
899
+ );
900
+ });
901
+
902
+ it("polls a started video past the quota, cooldown and capacity gates", async () => {
903
+ const getVideo = vi.fn(async () => ({ status: "pending" }));
904
+ const media: GrokMediaPort = {
905
+ generateImages: vi.fn(),
906
+ editImages: vi.fn(),
907
+ startVideo: vi.fn(),
908
+ getVideo,
909
+ };
910
+ const h = createHarness({ media, maxInflightPerToken: 1 });
911
+ h.add(1, 1, 50);
912
+ h.billing.set(1, billingAt(75));
913
+ const token = h.dispatcher.tokens.get(1)!;
914
+ token.cooldowns.set("video", h.clock.now + 120_000);
915
+ token.inFlight = 1;
916
+
917
+ await expect(h.dispatcher.startVideo({ model: "v", prompt: "p" })).rejects.toBeInstanceOf(
918
+ Error,
919
+ );
920
+ await expect(h.dispatcher.getVideo(1, "req-1")).resolves.toMatchObject({ tokenId: 1 });
921
+ expect(getVideo).toHaveBeenCalledTimes(1);
922
+ });
923
+
924
+ function imageFetch(failToken1: Error) {
925
+ const calls: Array<{ authorization: string | null; sessionId: string | null }> = [];
926
+ const impl: typeof fetch = async (_input, init) => {
927
+ const headers = new Headers(init?.headers);
928
+ const authorization = headers.get("authorization");
929
+ calls.push({ authorization, sessionId: headers.get("x-grok-session-id") });
930
+ if (authorization === `Bearer ${credentialsFor(1).accessToken}`) throw failToken1;
931
+ return new Response(JSON.stringify({ data: [{ b64_json: "aGk=" }] }), {
932
+ status: 200,
933
+ headers: { "content-type": "application/json" },
934
+ });
935
+ };
936
+ return { impl, calls };
937
+ }
938
+
939
+ function codedError(code: string): TypeError {
940
+ return new TypeError("fetch failed", { cause: Object.assign(new Error(code), { code }) });
941
+ }
942
+
943
+ it("fails an image over once after a provable pre-send connect failure", async () => {
944
+ const { impl, calls } = imageFetch(codedError("ECONNREFUSED"));
945
+ const h = createHarness({ media: new GrokMediaClient({ fetch: impl }) });
946
+ h.add(1);
947
+ h.add(2);
948
+ const result = await h.dispatcher.generateImages({
949
+ model: "m",
950
+ prompt: "p",
951
+ preferredTokenId: 1,
952
+ });
953
+ expect(result.tokenId).toBe(2);
954
+ expect(calls).toEqual([
955
+ {
956
+ authorization: `Bearer ${credentialsFor(1).accessToken}`,
957
+ sessionId: credentialsFor(1).sessionId,
958
+ },
959
+ {
960
+ authorization: `Bearer ${credentialsFor(2).accessToken}`,
961
+ sessionId: credentialsFor(2).sessionId,
962
+ },
963
+ ]);
964
+ });
965
+
966
+ it("allows only one pre-send failover", async () => {
967
+ const calls: string[] = [];
968
+ const media = new GrokMediaClient({
969
+ fetch: async (_input, init) => {
970
+ calls.push(new Headers(init?.headers).get("authorization") ?? "");
971
+ throw codedError("ENOTFOUND");
972
+ },
973
+ });
974
+ const h = createHarness({ media });
975
+ h.add(1);
976
+ h.add(2);
977
+ h.add(3);
978
+ const error = await h.dispatcher
979
+ .generateImages({ model: "m", prompt: "p" })
980
+ .catch((e: unknown) => e);
981
+ expect(error).toBeInstanceOf(GrokHttpError);
982
+ expect((error as GrokHttpError).status).toBe(0);
983
+ expect(calls).toHaveLength(2);
984
+ });
985
+
986
+ it("never fails an image over after a possibly-sent failure", async () => {
987
+ const { impl, calls } = imageFetch(codedError("ECONNRESET"));
988
+ const h = createHarness({ media: new GrokMediaClient({ fetch: impl }) });
989
+ h.add(1);
990
+ h.add(2);
991
+ await expect(
992
+ h.dispatcher.generateImages({ model: "m", prompt: "p", preferredTokenId: 1 }),
993
+ ).rejects.toBeInstanceOf(GrokMediaDeliveryError);
994
+ expect(calls).toHaveLength(1);
995
+ });
996
+
997
+ it("fails STT over once after a provable pre-send connect failure", async () => {
998
+ const calls: string[] = [];
999
+ const voice = new GrokVoiceClient({
1000
+ fetch: async (_input, init) => {
1001
+ const authorization = new Headers(init?.headers).get("authorization") ?? "";
1002
+ calls.push(authorization);
1003
+ if (authorization === `Bearer ${credentialsFor(1).accessToken}`)
1004
+ throw codedError("ECONNREFUSED");
1005
+ return new Response(JSON.stringify({ text: "hi" }), {
1006
+ status: 200,
1007
+ headers: { "content-type": "application/json" },
1008
+ });
1009
+ },
1010
+ });
1011
+ const h = createHarness({ voice });
1012
+ h.add(1);
1013
+ h.add(2);
1014
+ const result = await h.dispatcher.transcribe({
1015
+ audio: new Uint8Array([1]),
1016
+ mediaType: "audio/wav",
1017
+ preferredTokenId: 1,
1018
+ });
1019
+ expect(result.tokenId).toBe(2);
1020
+ expect(calls).toEqual([
1021
+ `Bearer ${credentialsFor(1).accessToken}`,
1022
+ `Bearer ${credentialsFor(2).accessToken}`,
1023
+ ]);
1024
+ });
1025
+
1026
+ it("never fails voice over on a status-0 error marked shouldRetry false", async () => {
1027
+ const transcribe = vi.fn(async () => {
1028
+ throw new GrokHttpError({ kind: "transient", status: 0, shouldRetry: false }, "");
1029
+ });
1030
+ const h = createHarness({ voice: { transcribe, speak: vi.fn() } });
1031
+ h.add(1);
1032
+ h.add(2);
1033
+ await expect(
1034
+ h.dispatcher.transcribe({ audio: new Uint8Array([1]), mediaType: "audio/wav" }),
1035
+ ).rejects.toBeInstanceOf(GrokHttpError);
1036
+ expect(transcribe).toHaveBeenCalledTimes(1);
1037
+ });
1038
+ });
1039
+
1040
+ // ---------------------------------------------------------------------------
1041
+ // Zero Data Retention and video (plan §11)
1042
+ // ---------------------------------------------------------------------------
1043
+
1044
+ describe("GrokDispatcher zero data retention", () => {
1045
+ const zdrMessage =
1046
+ "Zero Data Retention teams must provide output.upload_url for video generation.";
1047
+ const zdrRejection = () =>
1048
+ new GrokHttpError(
1049
+ { kind: "invalid_request", status: 400, message: zdrMessage },
1050
+ JSON.stringify({ code: "invalid_request", error: zdrMessage }),
1051
+ );
1052
+ const user = (id: number, codingDataRetentionOptOut: boolean) => ({
1053
+ userId: guid(id, 1),
1054
+ subscriptionTier: "SuperGrok",
1055
+ codingDataRetentionOptOut,
1056
+ });
1057
+ const { "x-zero-data-retention": _zdr, "x-data-retention": _retention, ...plainHeaders } =
1058
+ sseHeaders;
1059
+
1060
+ function videoMedia(startVideo: GrokMediaPort["startVideo"]): GrokMediaPort {
1061
+ return { generateImages: vi.fn(), editImages: vi.fn(), startVideo, getVideo: vi.fn() };
1062
+ }
1063
+
1064
+ it("excludes a ZDR token from video only", async () => {
1065
+ const h = createHarness();
1066
+ h.add(1);
1067
+ h.users.set(1, user(1, true));
1068
+ await expect(h.dispatcher.selectToken("video")).rejects.toThrow(GrokZdrVideoUnavailableError);
1069
+ for (const requestClass of ["chat", "image", "stt", "tts"] as const) {
1070
+ const reservation = await h.dispatcher.selectToken(requestClass);
1071
+ expect(reservation.token.id).toBe(1);
1072
+ reservation.release();
1073
+ }
1074
+ expect(h.dispatcher.tokenStates()[0]!.zdrVideoExcluded).toBe(true);
1075
+ expect(h.dispatcher.getQuotaSnapshot()[0]!.zdrVideoExcluded).toBe(true);
1076
+ });
1077
+
1078
+ it("routes video to a token whose ZDR state is unknown or off", async () => {
1079
+ const h = createHarness();
1080
+ h.add(1);
1081
+ const unknown = await h.dispatcher.selectToken("video");
1082
+ expect(unknown.token.id).toBe(1);
1083
+ expect(unknown.token.zdr).toBeNull();
1084
+ unknown.release();
1085
+
1086
+ const h2 = createHarness();
1087
+ h2.add(1, 100);
1088
+ h2.add(2, 1);
1089
+ h2.users.set(1, user(1, true));
1090
+ h2.users.set(2, user(2, false));
1091
+ const off = await h2.dispatcher.selectToken("video");
1092
+ expect(off.token.id).toBe(2);
1093
+ expect(h2.dispatcher.tokenStates().map((s) => s.zdrVideoExcluded)).toEqual([true, false]);
1094
+ });
1095
+
1096
+ it("names the ZDR tokens when no video token is eligible", async () => {
1097
+ const h = createHarness();
1098
+ h.add(1);
1099
+ h.add(2);
1100
+ h.users.set(1, user(1, true));
1101
+ h.users.set(2, user(2, true));
1102
+ const error = await h.dispatcher.selectToken("video").catch((e: unknown) => e);
1103
+ expect(error).toBeInstanceOf(GrokZdrVideoUnavailableError);
1104
+ expect((error as GrokZdrVideoUnavailableError).tokenNames).toEqual(["t1", "t2"]);
1105
+ expect((error as Error).message).toContain("t1, t2");
1106
+ expect((error as Error).message).toContain("data-retention opt-out");
1107
+ await expect(
1108
+ h.dispatcher.selectToken("video", { preferredTokenId: 1, requirePreferredToken: true }),
1109
+ ).rejects.toThrow(GrokZdrVideoUnavailableError);
1110
+ });
1111
+
1112
+ it("reports on-demand before ZDR when both exclude tokens", async () => {
1113
+ const h = createHarness();
1114
+ h.add(1);
1115
+ h.add(2);
1116
+ h.settings.set(1, { on_demand_enabled: true });
1117
+ h.users.set(2, user(2, true));
1118
+ await expect(h.dispatcher.selectToken("video")).rejects.toBeInstanceOf(
1119
+ GrokOnDemandExcludedError,
1120
+ );
1121
+ });
1122
+
1123
+ it("flips ZDR on the upstream 400 and fails over to another token exactly once", async () => {
1124
+ const startVideo = vi.fn(async (auth: GrokMediaAuth) => {
1125
+ if (auth.bearer === credentialsFor(1).accessToken) throw zdrRejection();
1126
+ return { request_id: "req-2" };
1127
+ });
1128
+ const h = createHarness({ media: videoMedia(startVideo) });
1129
+ h.add(1);
1130
+ h.add(2);
1131
+ const result = await h.dispatcher.startVideo({ model: "v", prompt: "p", preferredTokenId: 1 });
1132
+ expect(result.tokenId).toBe(2);
1133
+ expect(startVideo).toHaveBeenCalledTimes(2);
1134
+ const token = h.dispatcher.tokens.get(1)!;
1135
+ expect(token.zdr).toBe(true);
1136
+ expect(h.dispatcher.isEligible(token, "video")).toBe(false);
1137
+ expect(h.dispatcher.isEligible(token, "chat")).toBe(true);
1138
+ expect(h.dispatcher.isEligible(token, "image")).toBe(true);
1139
+ });
1140
+
1141
+ it("surfaces GrokZdrVideoUnavailableError after every token refuses with the ZDR 400", async () => {
1142
+ const startVideo = vi.fn(async () => {
1143
+ throw zdrRejection();
1144
+ });
1145
+ const h = createHarness({ media: videoMedia(startVideo) });
1146
+ h.add(1);
1147
+ h.add(2);
1148
+ await expect(h.dispatcher.startVideo({ model: "v", prompt: "p" })).rejects.toThrow(
1149
+ GrokZdrVideoUnavailableError,
1150
+ );
1151
+ expect(startVideo).toHaveBeenCalledTimes(2);
1152
+ });
1153
+
1154
+ it("does not treat other video 400s as ZDR", async () => {
1155
+ const startVideo = vi.fn(async () => {
1156
+ throw new GrokHttpError({ kind: "invalid_request", status: 400, message: "bad prompt" }, "");
1157
+ });
1158
+ const h = createHarness({ media: videoMedia(startVideo) });
1159
+ h.add(1);
1160
+ h.add(2);
1161
+ await expect(h.dispatcher.startVideo({ model: "v", prompt: "p" })).rejects.toBeInstanceOf(
1162
+ GrokHttpError,
1163
+ );
1164
+ expect(startVideo).toHaveBeenCalledTimes(1);
1165
+ for (const token of h.dispatcher.tokens.values()) expect(token.zdr).toBeNull();
1166
+ });
1167
+
1168
+ it("learns ZDR from x-zero-data-retention on a chat response and never clears it from absence", async () => {
1169
+ const h = createHarness();
1170
+ h.add(1);
1171
+ h.chat.set(1, [{ kind: "ok", headers: plainHeaders }, { kind: "ok" }, { kind: "ok", headers: plainHeaders }]);
1172
+ await h.dispatcher.generate(request());
1173
+ expect(h.dispatcher.tokens.get(1)!.zdr).toBeNull();
1174
+ await h.dispatcher.generate(request());
1175
+ expect(h.dispatcher.tokens.get(1)!.zdr).toBe(true);
1176
+ await h.dispatcher.generate(request());
1177
+ expect(h.dispatcher.tokens.get(1)!.zdr).toBe(true);
1178
+ await expect(h.dispatcher.selectToken("video")).rejects.toThrow(GrokZdrVideoUnavailableError);
1179
+ });
1180
+
1181
+ it("keeps the last ZDR value when the /user lookup fails and clears it on a false read", async () => {
1182
+ const h = createHarness();
1183
+ h.add(1);
1184
+ h.users.set(1, user(1, true));
1185
+ await expect(h.dispatcher.selectToken("video")).rejects.toThrow(GrokZdrVideoUnavailableError);
1186
+
1187
+ h.users.delete(1);
1188
+ h.clock.now += 61_000;
1189
+ await expect(h.dispatcher.selectToken("video")).rejects.toThrow(GrokZdrVideoUnavailableError);
1190
+ expect(h.dispatcher.tokens.get(1)!.subscription?.value.kind).toBe("lookup_failed");
1191
+
1192
+ h.users.set(1, user(1, false));
1193
+ h.clock.now += 61_000;
1194
+ const reservation = await h.dispatcher.selectToken("video");
1195
+ expect(reservation.token.id).toBe(1);
1196
+ expect(reservation.token.zdr).toBe(false);
1197
+ });
1198
+
1199
+ it("resets ZDR when the token's credentials change", async () => {
1200
+ const h = createHarness();
1201
+ h.add(1);
1202
+ h.users.set(1, user(1, true));
1203
+ await expect(h.dispatcher.selectToken("video")).rejects.toThrow(GrokZdrVideoUnavailableError);
1204
+ h.users.delete(1);
1205
+ h.dispatcher.onTokenUpdated(1, "t1", credentialsFor(1, 2), null, 1);
1206
+ const reservation = await h.dispatcher.selectToken("video");
1207
+ expect(reservation.token.zdr).toBeNull();
1208
+ });
1209
+ });
1210
+
1211
+ // ---------------------------------------------------------------------------
1212
+ // Credential snapshots, re-gating, and reservations across refreshes
1213
+ // ---------------------------------------------------------------------------
1214
+
1215
+ describe("GrokDispatcher refresh recovery", () => {
1216
+ const far = "2031-01-01T00:00:00Z";
1217
+
1218
+ /** Records "send <bearer>" in h.events for every chat request, and runs `after` once it settles. */
1219
+ function traceSends(h: Harness, after?: (send: number) => void): void {
1220
+ const client = h.dispatcher.client;
1221
+ const original = client.stream.bind(client);
1222
+ let sends = 0;
1223
+ vi.spyOn(client, "stream").mockImplementation(async (args) => {
1224
+ const send = ++sends;
1225
+ h.events.push(`send ${args.headers.authorization ?? ""}`);
1226
+ try {
1227
+ return await original(args);
1228
+ } finally {
1229
+ after?.(send);
1230
+ }
1231
+ });
1232
+ }
1233
+
1234
+ it("keeps a token whose pre-lookup refresh failed out of selection until its gates are read", async () => {
1235
+ const h = createHarness();
1236
+ h.add(1);
1237
+ const fresh = { ...credentialsFor(1, 1), expiresAt: far };
1238
+ h.revoked.add(credentialsFor(1).accessToken);
1239
+ h.settings.set(1, { on_demand_enabled: true });
1240
+ h.refresher.ensureFresh
1241
+ .mockImplementationOnce(async () => {
1242
+ throw new Error("network");
1243
+ })
1244
+ .mockImplementation(async (token) =>
1245
+ token.credentials.accessToken === credentialsFor(1).accessToken ? fresh : token.credentials,
1246
+ );
1247
+ traceSends(h);
1248
+ await expect(h.dispatcher.generate(request())).rejects.toBeInstanceOf(
1249
+ GrokOnDemandExcludedError,
1250
+ );
1251
+ expect(h.refresher.ensureFresh).toHaveBeenCalledTimes(2);
1252
+ expect(h.events).toContain(`/settings ${fresh.accessToken}`);
1253
+ expect(h.events.some((event) => event.startsWith("send"))).toBe(false);
1254
+ expect(h.dispatcher.tokens.get(1)!.inFlight).toBe(0);
1255
+
1256
+ // With the gate open, the lookups still precede the send.
1257
+ const ok = createHarness();
1258
+ ok.add(1);
1259
+ ok.refresher.ensureFresh
1260
+ .mockImplementationOnce(async () => {
1261
+ throw new Error("network");
1262
+ })
1263
+ .mockImplementation(async (token) =>
1264
+ token.credentials.accessToken === credentialsFor(1).accessToken ? fresh : token.credentials,
1265
+ );
1266
+ traceSends(ok);
1267
+ await ok.dispatcher.generate(request());
1268
+ const settingsAt = ok.events.indexOf(`/settings ${fresh.accessToken}`);
1269
+ const sendAt = ok.events.indexOf(`send Bearer ${fresh.accessToken}`);
1270
+ expect(settingsAt).toBeGreaterThanOrEqual(0);
1271
+ expect(sendAt).toBeGreaterThan(settingsAt);
1272
+ });
1273
+
1274
+ it("re-gates a token whose bearer the attempt refreshed after selection", async () => {
1275
+ const h = createHarness();
1276
+ const a0 = { ...credentialsFor(1), expiresAt: far };
1277
+ const a1 = { ...credentialsFor(1, 1), expiresAt: far };
1278
+ h.dispatcher.onTokenAdded(1, "t1", a0, null, 1);
1279
+ h.add(2);
1280
+ h.refresher.ensureFresh.mockImplementation(async (token) => {
1281
+ if (token.credentials.accessToken !== a0.accessToken) return token.credentials;
1282
+ // The refresh lands between selection and send; the account turned on-demand meanwhile.
1283
+ h.settings.set(1, { on_demand_enabled: true });
1284
+ return a1;
1285
+ });
1286
+ traceSends(h);
1287
+ const result = await h.dispatcher.generate(request({ preferredTokenId: 1 }));
1288
+ expect(result.tokenName).toBe("t2");
1289
+ expect(h.chatCalls()).toEqual([2]);
1290
+ expect(h.events).toContain(`/settings ${a1.accessToken}`);
1291
+ expect(h.events).not.toContain(`send Bearer ${a1.accessToken}`);
1292
+ expect(h.dispatcher.tokens.get(1)!.inFlight).toBe(0);
1293
+ });
1294
+
1295
+ it("ignores a 401 for a bearer a concurrent refresh already replaced and retries the new one", async () => {
1296
+ const h = createHarness();
1297
+ h.add(1);
1298
+ const token = h.dispatcher.tokens.get(1)!;
1299
+ const a2 = credentialsFor(1, 2);
1300
+ h.chat.set(1, [
1301
+ { kind: "http", status: 401 },
1302
+ { kind: "http", status: 401 },
1303
+ ]);
1304
+ // Request A presents A1 after its forced refresh; A2 is installed while that send is in flight.
1305
+ traceSends(h, (send) => {
1306
+ if (send === 2) h.dispatcher.storeCredentials(token, a2);
1307
+ });
1308
+ const result = await h.dispatcher.generate(request());
1309
+ expect(result.tokenName).toBe("t1");
1310
+ expect(token.reauthRequired).toBe(false);
1311
+ expect(h.reauth).not.toHaveBeenCalled();
1312
+ expect(h.refresher.refreshAfterUnauthorized).toHaveBeenCalledTimes(1);
1313
+ expect(h.requests.map((r) => r.headers.get("authorization"))).toEqual([
1314
+ `Bearer ${credentialsFor(1).accessToken}`,
1315
+ `Bearer ${credentialsFor(1, 1).accessToken}`,
1316
+ `Bearer ${a2.accessToken}`,
1317
+ ]);
1318
+ });
1319
+
1320
+ it("does not mark newer credentials dead when a stale refresh reports reauth", async () => {
1321
+ const h = createHarness();
1322
+ h.add(1);
1323
+ h.add(2);
1324
+ const token = h.dispatcher.tokens.get(1)!;
1325
+ const a2 = credentialsFor(1, 2);
1326
+ let calls = 0;
1327
+ h.refresher.ensureFresh.mockImplementation(async (ref) => {
1328
+ if (ref.credentials.accessToken !== credentialsFor(1).accessToken) return ref.credentials;
1329
+ if (++calls === 1) return ref.credentials;
1330
+ // The attempt's refresh of A0 dies after another path already installed A2.
1331
+ h.dispatcher.storeCredentials(token, a2);
1332
+ throw Object.assign(new Error("invalid_grant"), { name: "GrokReauthRequiredError" });
1333
+ });
1334
+ const result = await h.dispatcher.generate(request({ preferredTokenId: 1 }));
1335
+ expect(result.tokenName).toBe("t2");
1336
+ expect(token.reauthRequired).toBe(false);
1337
+ expect(h.reauth).not.toHaveBeenCalled();
1338
+ });
1339
+
1340
+
1341
+ it("fails over to another token once the stale-401 retry allowance is spent", async () => {
1342
+ const h = createHarness();
1343
+ h.add(1);
1344
+ h.add(2);
1345
+ const token = h.dispatcher.tokens.get(1)!;
1346
+ // Every send on token 1 is answered 401, and a concurrent refresh keeps replacing its bearer
1347
+ // before the 401 lands, so each failure looks stale.
1348
+ h.chat.set(1, [
1349
+ { kind: "http", status: 401 },
1350
+ { kind: "http", status: 401 },
1351
+ { kind: "http", status: 401 },
1352
+ { kind: "http", status: 401 },
1353
+ ]);
1354
+ let generation = 0;
1355
+ traceSends(h, (send) => {
1356
+ if (send >= 2) h.dispatcher.storeCredentials(token, credentialsFor(1, ++generation + 1));
1357
+ });
1358
+ const result = await h.dispatcher.generate(request({ preferredTokenId: 1 }));
1359
+ expect(result.tokenName).toBe("t2");
1360
+ const sendsOnTokenOne = h.requests.filter((r) =>
1361
+ r.headers.get("x-grok-user-id") === credentialsFor(1).userId,
1362
+ ).length;
1363
+ // Initial send, forced-refresh retry, one stale retry; never a fourth on the same token.
1364
+ expect(sendsOnTokenOne).toBeLessThanOrEqual(3);
1365
+ expect(token.reauthRequired).toBe(false);
1366
+ });
1367
+
1368
+ it("holds the reservation across the 401 refresh so the retry stays within the in-flight cap", async () => {
1369
+ const h = createHarness({ maxInflightPerToken: 1 });
1370
+ h.add(1);
1371
+ const token = h.dispatcher.tokens.get(1)!;
1372
+ h.chat.set(1, [{ kind: "http", status: 401 }]);
1373
+ let finishRefresh: (() => void) | undefined;
1374
+ h.refresher.refreshAfterUnauthorized.mockImplementationOnce(
1375
+ () =>
1376
+ new Promise<GrokRefreshResult>((resolve) => {
1377
+ finishRefresh = () => resolve({ status: "refreshed", credentials: credentialsFor(1, 1) });
1378
+ }),
1379
+ );
1380
+ const observed: number[] = [];
1381
+ traceSends(h, () => observed.push(token.inFlight));
1382
+ const a = h.dispatcher.generate(request());
1383
+ await vi.waitFor(() => expect(finishRefresh).toBeDefined());
1384
+ observed.push(token.inFlight);
1385
+ // B arrives while A refreshes: the cap is still held by A.
1386
+ await expect(h.dispatcher.selectToken("chat")).rejects.toBeInstanceOf(GrokCapacityError);
1387
+ observed.push(token.inFlight);
1388
+ finishRefresh!();
1389
+ const result = await a;
1390
+ expect(result.tokenName).toBe("t1");
1391
+ // 401 send, during the refresh, B's rejected selection, the retry send.
1392
+ expect(observed).toEqual([1, 1, 1, 1]);
1393
+ expect(token.inFlight).toBe(0);
1394
+ });
1395
+ });