@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,142 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import { readGrokBilling, readGrokSettings, readGrokSubscription } from "./grok-quota";
4
+
5
+ const credentials = {
6
+ accessToken: "header.payload.sig",
7
+ userId: "19bfa1b2-a163-9e0c-8de1-832a73e2695e",
8
+ };
9
+
10
+ function fetchReturning(
11
+ body: unknown,
12
+ status = 200,
13
+ seen?: Array<{ url: string; headers: Headers }>,
14
+ ): typeof fetch {
15
+ return async (input, init) => {
16
+ seen?.push({ url: String(input), headers: new Headers(init?.headers) });
17
+ return new Response(typeof body === "string" ? body : JSON.stringify(body), { status });
18
+ };
19
+ }
20
+
21
+ describe("readGrokBilling", () => {
22
+ it("maps the camelCase credits schema to the shared quota shape", async () => {
23
+ const seen: Array<{ url: string; headers: Headers }> = [];
24
+ const result = await readGrokBilling(credentials, {
25
+ fetch: fetchReturning(
26
+ {
27
+ config: {
28
+ currentPeriod: {
29
+ type: "USAGE_PERIOD_TYPE_WEEKLY",
30
+ start: "2026-10-12T00:00:00Z",
31
+ end: "2026-10-19T00:00:00Z",
32
+ },
33
+ creditUsagePercent: 42,
34
+ productUsage: [{ product: "GrokBot", usagePercent: 42 }],
35
+ isUnifiedBillingUser: true,
36
+ prepaidBalance: { val: "0" },
37
+ },
38
+ },
39
+ 200,
40
+ seen,
41
+ ),
42
+ });
43
+ expect(seen[0]?.url).toBe("https://cli-chat-proxy.grok.com/v1/billing?format=credits");
44
+ expect(seen[0]?.headers.get("x-userid")).toBe(credentials.userId);
45
+ expect(seen[0]?.headers.get("x-xai-token-auth")).toBe("xai-grok-cli");
46
+ expect(result).toMatchObject({
47
+ kind: "ok",
48
+ utilizationPct: 42,
49
+ windowDurationMins: 7 * 24 * 60,
50
+ resetsAt: Date.parse("2026-10-19T00:00:00Z") / 1000,
51
+ limitId: "USAGE_PERIOD_TYPE_WEEKLY",
52
+ periodType: "USAGE_PERIOD_TYPE_WEEKLY",
53
+ periodEnd: "2026-10-19T00:00:00Z",
54
+ periodEndMs: Date.parse("2026-10-19T00:00:00Z"),
55
+ prepaidBalance: 0,
56
+ });
57
+ });
58
+
59
+ it("returns lookup_failed on HTTP errors and unexpected shapes", async () => {
60
+ expect(await readGrokBilling(credentials, { fetch: fetchReturning("nope", 500) })).toMatchObject({
61
+ kind: "lookup_failed",
62
+ });
63
+ expect(
64
+ await readGrokBilling(credentials, { fetch: fetchReturning({ credit_usage_percent: 3 }) }),
65
+ ).toMatchObject({ kind: "lookup_failed" });
66
+ expect(
67
+ await readGrokBilling(credentials, {
68
+ fetch: async () => {
69
+ throw new TypeError("offline");
70
+ },
71
+ }),
72
+ ).toMatchObject({ kind: "lookup_failed" });
73
+ });
74
+ });
75
+
76
+ describe("readGrokSettings", () => {
77
+ it("reads on_demand_enabled and the gate fields", async () => {
78
+ const result = await readGrokSettings(credentials, {
79
+ fetch: fetchReturning({
80
+ min_client_version: "1.0.13",
81
+ force_update: true,
82
+ default_model: "grok-4.7",
83
+ subscription_tier_display: "SuperGrok Plus",
84
+ on_demand_enabled: null,
85
+ image_gen_enabled: true,
86
+ video_gen_enabled: true,
87
+ voice_mode_enabled: true,
88
+ }),
89
+ });
90
+ expect(result).toEqual({
91
+ kind: "ok",
92
+ onDemandEnabled: null,
93
+ minClientVersion: "1.0.13",
94
+ defaultModel: "grok-4.7",
95
+ subscriptionTierDisplay: "SuperGrok Plus",
96
+ imageGenEnabled: true,
97
+ videoGenEnabled: true,
98
+ voiceModeEnabled: true,
99
+ });
100
+ });
101
+
102
+ it("treats an absent on_demand_enabled as null and true as true", async () => {
103
+ expect(await readGrokSettings(credentials, { fetch: fetchReturning({}) })).toMatchObject({
104
+ onDemandEnabled: null,
105
+ });
106
+ expect(
107
+ await readGrokSettings(credentials, { fetch: fetchReturning({ on_demand_enabled: true }) }),
108
+ ).toMatchObject({ onDemandEnabled: true });
109
+ });
110
+ });
111
+
112
+ describe("readGrokSubscription", () => {
113
+ it("returns the tier", async () => {
114
+ const seen: Array<{ url: string; headers: Headers }> = [];
115
+ const result = await readGrokSubscription(credentials, {
116
+ fetch: fetchReturning(
117
+ { userId: credentials.userId, subscriptionTier: "SuperGrokPlus", hasGrokCodeAccess: true },
118
+ 200,
119
+ seen,
120
+ ),
121
+ });
122
+ expect(seen[0]?.url).toBe("https://cli-chat-proxy.grok.com/v1/user?include=subscription");
123
+ expect(result).toEqual({
124
+ kind: "ok",
125
+ userId: credentials.userId,
126
+ tier: "SuperGrokPlus",
127
+ hasGrokCodeAccess: true,
128
+ dataRetentionOptOut: null,
129
+ });
130
+ });
131
+
132
+ it("returns codingDataRetentionOptOut", async () => {
133
+ const result = await readGrokSubscription(credentials, {
134
+ fetch: fetchReturning({ userId: credentials.userId, codingDataRetentionOptOut: true }),
135
+ });
136
+ expect(result).toMatchObject({ kind: "ok", dataRetentionOptOut: true });
137
+ const off = await readGrokSubscription(credentials, {
138
+ fetch: fetchReturning({ userId: credentials.userId, codingDataRetentionOptOut: false }),
139
+ });
140
+ expect(off).toMatchObject({ kind: "ok", dataRetentionOptOut: false });
141
+ });
142
+ });
@@ -0,0 +1,198 @@
1
+ import { grokProxyGet } from "./grok-direct-client";
2
+ import { buildGrokProxyGetHeaders } from "./grok-headers";
3
+ import {
4
+ GrokBillingConfig,
5
+ GrokSettings,
6
+ GrokUserSubscription,
7
+ type GrokCredentials,
8
+ } from "./grok-types";
9
+
10
+ // Chat-proxy reads for the quota gate (§5.3). Every reader returns a result union and never
11
+ // throws, so callers can fail open. The `ok` billing shape carries the same fields as
12
+ // OpenAIQuotaUsageResult so usage views can treat both providers alike.
13
+
14
+ export interface GrokProxyReadOptions {
15
+ fetch?: typeof fetch;
16
+ signal?: AbortSignal;
17
+ baseUrl?: string;
18
+ now?: () => number;
19
+ }
20
+
21
+ type ProxyCredentials = Pick<GrokCredentials, "accessToken" | "userId">;
22
+
23
+ export type GrokQuotaUsageResult =
24
+ | {
25
+ kind: "ok";
26
+ // Same names as OpenAIQuotaUsageResult.
27
+ utilizationPct: number;
28
+ cacheAgeMs: number;
29
+ windowDurationMins: number | null;
30
+ /** Unix seconds, like the OpenAI wham `resetsAt`. */
31
+ resetsAt: number | null;
32
+ limitId: string | null;
33
+ // Grok specifics.
34
+ periodType: string;
35
+ periodStart: string;
36
+ periodEnd: string;
37
+ /** Epoch ms of periodEnd, or null when the timestamp is unparseable. */
38
+ periodEndMs: number | null;
39
+ prepaidBalance: number | null;
40
+ raw: GrokBillingConfig;
41
+ }
42
+ | { kind: "lookup_failed"; reason: string };
43
+
44
+ function reasonOf(cause: unknown): string {
45
+ return cause instanceof Error ? cause.message : String(cause);
46
+ }
47
+
48
+ function proxyHeaders(credentials: ProxyCredentials): Record<string, string> {
49
+ return buildGrokProxyGetHeaders({ bearer: credentials.accessToken, userId: credentials.userId });
50
+ }
51
+
52
+ function numberOrNull(value: number | string | undefined): number | null {
53
+ if (value === undefined) return null;
54
+ // Number() returns a number argument unchanged and parses a numeric string.
55
+ const parsed = Number(value);
56
+
57
+ return Number.isFinite(parsed) ? parsed : null;
58
+ }
59
+
60
+ /** `GET {proxy}/billing?format=credits` (camelCase; U0 item 3). */
61
+ export async function readGrokBilling(
62
+ credentials: ProxyCredentials,
63
+ options: GrokProxyReadOptions = {},
64
+ ): Promise<GrokQuotaUsageResult> {
65
+ try {
66
+ const { json } = await grokProxyGet("/billing?format=credits", {
67
+ headers: proxyHeaders(credentials),
68
+ ...options,
69
+ });
70
+
71
+ const parsed = GrokBillingConfig.safeParse(json);
72
+
73
+ if (!parsed.success) {
74
+ return { kind: "lookup_failed", reason: `unexpected billing shape: ${parsed.error.message}` };
75
+ }
76
+
77
+ const config = parsed.data.config;
78
+
79
+ if (!Number.isFinite(config.creditUsagePercent)) {
80
+ return { kind: "lookup_failed", reason: "creditUsagePercent unavailable" };
81
+ }
82
+
83
+ const start = Date.parse(config.currentPeriod.start);
84
+ const end = Date.parse(config.currentPeriod.end);
85
+ const endMs = Number.isNaN(end) ? null : end;
86
+
87
+ return {
88
+ kind: "ok",
89
+ utilizationPct: config.creditUsagePercent,
90
+ cacheAgeMs: 0,
91
+ windowDurationMins:
92
+ endMs !== null && !Number.isNaN(start) && endMs > start
93
+ ? Math.round((endMs - start) / 60_000)
94
+ : null,
95
+ resetsAt: endMs === null ? null : Math.floor(endMs / 1000),
96
+ limitId: config.currentPeriod.type,
97
+ periodType: config.currentPeriod.type,
98
+ periodStart: config.currentPeriod.start,
99
+ periodEnd: config.currentPeriod.end,
100
+ periodEndMs: endMs,
101
+ prepaidBalance: numberOrNull(config.prepaidBalance?.val),
102
+ raw: parsed.data,
103
+ };
104
+ } catch (error) {
105
+ return { kind: "lookup_failed", reason: reasonOf(error) };
106
+ }
107
+ }
108
+
109
+ export type GrokSettingsResult =
110
+ | {
111
+ kind: "ok";
112
+ /** D6: `true` excludes the token; null/false/absent allows it. */
113
+ onDemandEnabled: boolean | null;
114
+ minClientVersion: string | null;
115
+ defaultModel: string | null;
116
+ subscriptionTierDisplay: string | null;
117
+ imageGenEnabled: boolean | null;
118
+ videoGenEnabled: boolean | null;
119
+ voiceModeEnabled: boolean | null;
120
+ }
121
+ | { kind: "lookup_failed"; reason: string };
122
+
123
+ /** `GET {proxy}/settings`. */
124
+ export async function readGrokSettings(
125
+ credentials: ProxyCredentials,
126
+ options: GrokProxyReadOptions = {},
127
+ ): Promise<GrokSettingsResult> {
128
+ try {
129
+ const { json } = await grokProxyGet("/settings", {
130
+ headers: proxyHeaders(credentials),
131
+ ...options,
132
+ });
133
+
134
+ const parsed = GrokSettings.safeParse(json);
135
+
136
+ if (!parsed.success) {
137
+ return {
138
+ kind: "lookup_failed",
139
+ reason: `unexpected settings shape: ${parsed.error.message}`,
140
+ };
141
+ }
142
+
143
+ const settings = parsed.data;
144
+
145
+ return {
146
+ kind: "ok",
147
+ onDemandEnabled: settings.on_demand_enabled ?? null,
148
+ minClientVersion: settings.min_client_version ?? null,
149
+ defaultModel: settings.default_model ?? null,
150
+ subscriptionTierDisplay: settings.subscription_tier_display ?? null,
151
+ imageGenEnabled: settings.image_gen_enabled ?? null,
152
+ videoGenEnabled: settings.video_gen_enabled ?? null,
153
+ voiceModeEnabled: settings.voice_mode_enabled ?? null,
154
+ };
155
+ } catch (error) {
156
+ return { kind: "lookup_failed", reason: reasonOf(error) };
157
+ }
158
+ }
159
+
160
+ export type GrokSubscriptionResult =
161
+ | {
162
+ kind: "ok";
163
+ userId: string;
164
+ tier: string | null;
165
+ hasGrokCodeAccess: boolean | null;
166
+ /** `codingDataRetentionOptOut`: true means Zero Data Retention is on; null when absent. */
167
+ dataRetentionOptOut: boolean | null;
168
+ }
169
+ | { kind: "lookup_failed"; reason: string };
170
+
171
+ /** `GET {proxy}/user?include=subscription`. */
172
+ export async function readGrokSubscription(
173
+ credentials: ProxyCredentials,
174
+ options: GrokProxyReadOptions = {},
175
+ ): Promise<GrokSubscriptionResult> {
176
+ try {
177
+ const { json } = await grokProxyGet("/user?include=subscription", {
178
+ headers: proxyHeaders(credentials),
179
+ ...options,
180
+ });
181
+
182
+ const parsed = GrokUserSubscription.safeParse(json);
183
+
184
+ if (!parsed.success) {
185
+ return { kind: "lookup_failed", reason: `unexpected user shape: ${parsed.error.message}` };
186
+ }
187
+
188
+ return {
189
+ kind: "ok",
190
+ userId: parsed.data.userId,
191
+ tier: parsed.data.subscriptionTier ?? null,
192
+ hasGrokCodeAccess: parsed.data.hasGrokCodeAccess ?? null,
193
+ dataRetentionOptOut: parsed.data.codingDataRetentionOptOut ?? null,
194
+ };
195
+ } catch (error) {
196
+ return { kind: "lookup_failed", reason: reasonOf(error) };
197
+ }
198
+ }
@@ -0,0 +1,239 @@
1
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
2
+
3
+ import { GROK_OAUTH_CLIENT_ID } from "./grok-constants";
4
+ import { resetGrokDiscoveryCache } from "./grok-oauth";
5
+ import {
6
+ GROK_REFRESH_MARGIN_MS,
7
+ GrokReauthRequiredError,
8
+ GrokRefreshCoordinator,
9
+ GrokRefreshTransientError,
10
+ grokNeedsRefresh,
11
+ grokRefreshJitterMs,
12
+ } from "./grok-refresh";
13
+ import type { GrokCredentials } from "./grok-types";
14
+
15
+ const USER_ID = "11111111-2222-3333-4444-555555555555";
16
+
17
+ function accessToken(tag: string): string {
18
+ const header = Buffer.from(JSON.stringify({ typ: "at+jwt", alg: "ES256" })).toString("base64url");
19
+ const payload = Buffer.from(
20
+ JSON.stringify({ iss: "https://auth.x.ai", sub: USER_ID, jti: tag }),
21
+ ).toString("base64url");
22
+ return `${header}.${payload}.c2ln`;
23
+ }
24
+
25
+ function credentials(overrides: Partial<GrokCredentials> = {}): GrokCredentials {
26
+ return {
27
+ authMode: "oauth",
28
+ accessToken: accessToken("old"),
29
+ refreshToken: "rt-old",
30
+ expiresAt: "2026-10-16T06:00:00.000Z",
31
+ issuer: "https://auth.x.ai",
32
+ clientId: GROK_OAUTH_CLIENT_ID,
33
+ scopes: ["openid"],
34
+ userId: USER_ID,
35
+ sessionId: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
36
+ agentId: "ffffffff-bbbb-cccc-dddd-eeeeeeeeeeee",
37
+ ...overrides,
38
+ };
39
+ }
40
+
41
+ const json = (status: number, body: unknown): Response =>
42
+ new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
43
+
44
+ const NOW = Date.parse("2026-10-16T05:00:00Z");
45
+
46
+ function tokenFetch(reply: () => Response | Promise<Response>): {
47
+ fetch: typeof fetch;
48
+ bodies: URLSearchParams[];
49
+ } {
50
+ const bodies: URLSearchParams[] = [];
51
+ const impl = async (input: string | URL | Request, init?: RequestInit): Promise<Response> => {
52
+ const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
53
+ if (url.endsWith("/.well-known/openid-configuration"))
54
+ return json(200, {
55
+ authorization_endpoint: "https://auth.x.ai/oauth2/auth",
56
+ token_endpoint: "https://auth.x.ai/oauth2/token",
57
+ });
58
+ if (url !== "https://auth.x.ai/oauth2/token") throw new TypeError(`unexpected ${url}`);
59
+ bodies.push(new URLSearchParams(init?.body instanceof URLSearchParams ? init.body : ""));
60
+ return reply();
61
+ };
62
+ return { fetch: impl, bodies };
63
+ }
64
+
65
+ beforeEach(() => resetGrokDiscoveryCache());
66
+ afterEach(() => resetGrokDiscoveryCache());
67
+
68
+ describe("refresh timing", () => {
69
+ it("is stable per account and bounded to 0..60 s", () => {
70
+ const jitter = grokRefreshJitterMs(USER_ID);
71
+ expect(grokRefreshJitterMs(USER_ID)).toBe(jitter);
72
+ expect(jitter).toBeGreaterThanOrEqual(0);
73
+ expect(jitter).toBeLessThanOrEqual(60_000);
74
+ });
75
+
76
+ it("flips exactly at expiresAt - 300 s - jitter", () => {
77
+ const creds = credentials();
78
+ const boundary =
79
+ Date.parse(creds.expiresAt) - GROK_REFRESH_MARGIN_MS - grokRefreshJitterMs(USER_ID);
80
+ expect(grokNeedsRefresh(creds, boundary - 1)).toBe(false);
81
+ expect(grokNeedsRefresh(creds, boundary)).toBe(true);
82
+ expect(new GrokRefreshCoordinator().needsRefresh(creds, boundary + 1)).toBe(true);
83
+ });
84
+ });
85
+
86
+ describe("GrokRefreshCoordinator.refresh", () => {
87
+ it("keeps the old refresh token when not rotated and persists before resolving", async () => {
88
+ const { fetch, bodies } = tokenFetch(() =>
89
+ json(200, { access_token: accessToken("new"), expires_in: 21_600 }),
90
+ );
91
+ const persisted: GrokCredentials[] = [];
92
+ const coordinator = new GrokRefreshCoordinator({ fetch, now: () => NOW });
93
+ const next = await coordinator.refresh(7, credentials(), async (value) => {
94
+ persisted.push(value);
95
+ });
96
+
97
+ expect(Object.fromEntries(bodies[0] ?? [])).toEqual({
98
+ grant_type: "refresh_token",
99
+ refresh_token: "rt-old",
100
+ client_id: GROK_OAUTH_CLIENT_ID,
101
+ });
102
+ expect(next.refreshToken).toBe("rt-old");
103
+ expect(next.accessToken).toBe(accessToken("new"));
104
+ expect(next.expiresAt).toBe(new Date(NOW + 21_600_000).toISOString());
105
+ expect(next.sessionId).toBe(credentials().sessionId);
106
+ expect(persisted).toEqual([next]);
107
+ });
108
+
109
+ it("stores a rotated refresh token", async () => {
110
+ const { fetch } = tokenFetch(() =>
111
+ json(200, { access_token: accessToken("new"), refresh_token: "rt-new", expires_in: 60 }),
112
+ );
113
+ const next = await new GrokRefreshCoordinator({ fetch, now: () => NOW }).refresh(
114
+ 7,
115
+ credentials(),
116
+ async () => undefined,
117
+ );
118
+ expect(next.refreshToken).toBe("rt-new");
119
+ });
120
+
121
+ it("classifies invalid_grant/invalid_client as terminal and 503/network as transient", async () => {
122
+ for (const error of ["invalid_grant", "invalid_client"]) {
123
+ const { fetch } = tokenFetch(() => json(400, { error }));
124
+ await expect(
125
+ new GrokRefreshCoordinator({ fetch }).refresh(1, credentials(), async () => undefined),
126
+ ).rejects.toBeInstanceOf(GrokReauthRequiredError);
127
+ }
128
+ const unavailable = tokenFetch(() => json(503, { error: "invalid_grant" }));
129
+ await expect(
130
+ new GrokRefreshCoordinator({ fetch: unavailable.fetch }).refresh(
131
+ 1,
132
+ credentials(),
133
+ async () => undefined,
134
+ ),
135
+ ).rejects.toMatchObject({ name: "GrokRefreshTransientError", status: 503 });
136
+ const offline = tokenFetch(() => {
137
+ throw new TypeError("fetch failed");
138
+ });
139
+ await expect(
140
+ new GrokRefreshCoordinator({ fetch: offline.fetch }).refresh(
141
+ 1,
142
+ credentials(),
143
+ async () => undefined,
144
+ ),
145
+ ).rejects.toBeInstanceOf(GrokRefreshTransientError);
146
+ });
147
+
148
+ it("coalesces concurrent refreshes of one token into one HTTP call", async () => {
149
+ let release: (() => void) | undefined;
150
+ const gate = new Promise<void>((resolve) => {
151
+ release = resolve;
152
+ });
153
+ const { fetch, bodies } = tokenFetch(async () => {
154
+ await gate;
155
+ return json(200, { access_token: accessToken("new"), expires_in: 60 });
156
+ });
157
+ let persists = 0;
158
+ const persist = async (): Promise<void> => {
159
+ persists += 1;
160
+ };
161
+ const coordinator = new GrokRefreshCoordinator({ fetch, now: () => NOW });
162
+ const first = coordinator.refresh(9, credentials(), persist);
163
+ const second = coordinator.refresh(9, credentials(), persist);
164
+ await new Promise((resolve) => setTimeout(resolve, 0));
165
+ release?.();
166
+ const [a, b] = await Promise.all([first, second]);
167
+ expect(a).toBe(b);
168
+ expect(bodies).toHaveLength(1);
169
+ expect(persists).toBe(1);
170
+
171
+ // After settling, a new call makes a new request.
172
+ await coordinator.refresh(9, a, persist);
173
+ expect(bodies).toHaveLength(2);
174
+ });
175
+
176
+ it("rejects when persistence fails", async () => {
177
+ const { fetch } = tokenFetch(() => json(200, { access_token: accessToken("new"), expires_in: 60 }));
178
+ await expect(
179
+ new GrokRefreshCoordinator({ fetch }).refresh(1, credentials(), async () => {
180
+ throw new Error("db down");
181
+ }),
182
+ ).rejects.toThrow("db down");
183
+ });
184
+
185
+ it("rejects a non-at+jwt access token in the refresh response", async () => {
186
+ const { fetch } = tokenFetch(() => json(200, { access_token: "xai-key", expires_in: 60 }));
187
+ await expect(
188
+ new GrokRefreshCoordinator({ fetch }).refresh(1, credentials(), async () => undefined),
189
+ ).rejects.toBeInstanceOf(GrokRefreshTransientError);
190
+ });
191
+ });
192
+
193
+ describe("GrokRefresher port", () => {
194
+ it("ensureFresh skips fresh tokens and refreshes stale ones", async () => {
195
+ const { fetch, bodies } = tokenFetch(() =>
196
+ json(200, { access_token: accessToken("new"), expires_in: 21_600 }),
197
+ );
198
+ const stored: number[] = [];
199
+ const coordinator = new GrokRefreshCoordinator({
200
+ fetch,
201
+ now: () => NOW,
202
+ persist: async (tokenId) => {
203
+ stored.push(tokenId);
204
+ },
205
+ });
206
+ const fresh = credentials({ expiresAt: new Date(NOW + 3_600_000).toISOString() });
207
+ expect(await coordinator.ensureFresh({ tokenId: 3, credentials: fresh })).toBe(fresh);
208
+ expect(bodies).toHaveLength(0);
209
+
210
+ const stale = credentials({ expiresAt: new Date(NOW + 60_000).toISOString() });
211
+ const next = await coordinator.ensureFresh({ tokenId: 3, credentials: stale });
212
+ expect(next.accessToken).toBe(accessToken("new"));
213
+ expect(stored).toEqual([3]);
214
+ });
215
+
216
+ it("refreshAfterUnauthorized reuses an already-replaced token and maps invalid_grant", async () => {
217
+ let reply = (): Response => json(200, { access_token: accessToken("new"), expires_in: 21_600 });
218
+ const { fetch, bodies } = tokenFetch(() => reply());
219
+ const coordinator = new GrokRefreshCoordinator({
220
+ fetch,
221
+ now: () => NOW,
222
+ persist: async () => undefined,
223
+ });
224
+ const first = await coordinator.refreshAfterUnauthorized({ tokenId: 4, credentials: credentials() });
225
+ expect(first.status).toBe("refreshed");
226
+ const second = await coordinator.refreshAfterUnauthorized({
227
+ tokenId: 4,
228
+ credentials: credentials(),
229
+ });
230
+ expect(second).toEqual(first);
231
+ expect(bodies).toHaveLength(1);
232
+
233
+ reply = () => json(400, { error: "invalid_grant" });
234
+ const fresh = first.status === "refreshed" ? first.credentials : credentials();
235
+ expect(
236
+ await coordinator.refreshAfterUnauthorized({ tokenId: 4, credentials: fresh }),
237
+ ).toEqual({ status: "reauth_required", reason: "invalid_grant" });
238
+ });
239
+ });