@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,96 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import { GrokModelCatalog } from "./grok-models";
4
+
5
+ const credentials = {
6
+ accessToken: "header.payload.sig",
7
+ userId: "19bfa1b2-a163-9e0c-8de1-832a73e2695e",
8
+ };
9
+
10
+ describe("GrokModelCatalog", () => {
11
+ it("resolves seeded models with or without the grok/ prefix", () => {
12
+ const catalog = new GrokModelCatalog();
13
+ expect(catalog.resolve("grok/grok-4.7")).toEqual({
14
+ id: "grok-4.7",
15
+ contextWindow: 256000,
16
+ efforts: ["xhigh", "high", "medium", "low"],
17
+ defaultEffort: "high",
18
+ known: true,
19
+ });
20
+ expect(catalog.resolve("grok-4.5").id).toBe("grok-4.5");
21
+ expect(catalog.resolve(undefined).id).toBe("grok-4.7");
22
+ });
23
+
24
+ it("forwards an unlisted id with every effort instead of rejecting it", () => {
25
+ const catalog = new GrokModelCatalog();
26
+ expect(catalog.resolve("grok/grok-composer-2.5")).toEqual({
27
+ id: "grok-composer-2.5",
28
+ contextWindow: undefined,
29
+ efforts: ["xhigh", "high", "medium", "low"],
30
+ defaultEffort: "high",
31
+ known: false,
32
+ });
33
+ expect(catalog.validateEffort("grok-composer-2.5", "low")).toBe("low");
34
+ expect(catalog.validateEffort("grok-composer-2.5", "max")).toBeUndefined();
35
+ expect(catalog.models.has("grok-composer-2.5")).toBe(false);
36
+ });
37
+
38
+ it("validates efforts per model", () => {
39
+ const catalog = new GrokModelCatalog();
40
+ expect(catalog.validateEffort("grok-4.5", "xhigh")).toBe("xhigh");
41
+ expect(catalog.validateEffort("grok-4.5", "max")).toBeUndefined();
42
+ });
43
+
44
+ it("merges GET /models and stores the etag", async () => {
45
+ const catalog = new GrokModelCatalog();
46
+ let url = "";
47
+ const result = await catalog.refresh(credentials, {
48
+ fetch: async (input) => {
49
+ url = String(input);
50
+ return new Response(
51
+ JSON.stringify({
52
+ object: "list",
53
+ data: [
54
+ {
55
+ id: "grok-4.8",
56
+ api_backend: "responses",
57
+ context_window: 500000,
58
+ reasoning_efforts: [
59
+ { id: "h", value: "high" },
60
+ { id: "m", value: "medium", default: true },
61
+ ],
62
+ },
63
+ { id: "legacy", api_backend: "chat_completions", context_window: 1000 },
64
+ ],
65
+ }),
66
+ { headers: { "x-models-etag": '"42"' } },
67
+ );
68
+ },
69
+ });
70
+ expect(url).toBe("https://cli-chat-proxy.grok.com/v1/models");
71
+ expect(result).toEqual({ kind: "ok", models: ["grok-4.8"], etag: '"42"' });
72
+ expect(catalog.resolve("grok/grok-4.8")).toEqual({
73
+ id: "grok-4.8",
74
+ contextWindow: 500000,
75
+ efforts: ["high", "medium"],
76
+ defaultEffort: "medium",
77
+ known: true,
78
+ });
79
+ expect(catalog.validateEffort("grok-4.8", "xhigh")).toBeUndefined();
80
+ // A non-responses backend is not merged; the id is forwarded unlisted.
81
+ expect(catalog.resolve("legacy").known).toBe(false);
82
+ // Seeds absent from the live list remain.
83
+ expect(catalog.resolve("grok-4.6").id).toBe("grok-4.6");
84
+ expect(catalog.isStale('"42"')).toBe(false);
85
+ expect(catalog.isStale('"43"')).toBe(true);
86
+ });
87
+
88
+ it("reports lookup_failed without changing the catalog", async () => {
89
+ const catalog = new GrokModelCatalog();
90
+ const result = await catalog.refresh(credentials, {
91
+ fetch: async () => new Response("{}", { status: 500 }),
92
+ });
93
+ expect(result.kind).toBe("lookup_failed");
94
+ expect(catalog.models.size).toBe(4);
95
+ });
96
+ });
@@ -0,0 +1,161 @@
1
+ import {
2
+ GROK_DEFAULT_MODEL,
3
+ GROK_REASONING_EFFORTS,
4
+ GROK_SEED_MODELS,
5
+ type GrokReasoningEffort,
6
+ } from "./grok-constants";
7
+ import { grokProxyGet } from "./grok-direct-client";
8
+ import { buildGrokProxyGetHeaders } from "./grok-headers";
9
+ import { GrokModelsResponse, type GrokCredentials, type GrokProxyModel } from "./grok-types";
10
+
11
+ export interface GrokResolvedModel {
12
+ id: string;
13
+ /** Undefined for an unlisted id; the response `x-grok-context-window` header may fill it. */
14
+ contextWindow: number | undefined;
15
+ efforts: readonly GrokReasoningEffort[];
16
+ defaultEffort: GrokReasoningEffort;
17
+ /** False when the id is in neither the seed nor the live catalog and is forwarded as-is. */
18
+ known: boolean;
19
+ }
20
+
21
+ function isGrokEffort(value: string): value is GrokReasoningEffort {
22
+ return GROK_REASONING_EFFORTS.some((effort) => effort === value);
23
+ }
24
+
25
+ /** Strips the qgrid route prefix; an empty id means the default model. */
26
+ export function canonicalGrokModelId(modelId: string | undefined): string {
27
+ const stripped = (modelId ?? "").startsWith("grok/") ? (modelId ?? "").slice(5) : (modelId ?? "");
28
+
29
+ return stripped || GROK_DEFAULT_MODEL;
30
+ }
31
+
32
+ function fromProxyModel(model: GrokProxyModel): GrokResolvedModel | undefined {
33
+ if (model.api_backend !== "responses") return undefined;
34
+ const listed = (model.reasoning_efforts ?? []).map((entry) => entry.value).filter(isGrokEffort);
35
+ const efforts: readonly GrokReasoningEffort[] = listed.length ? listed : GROK_REASONING_EFFORTS;
36
+ const flagged = model.reasoning_efforts?.find((entry) => entry.default)?.value;
37
+ const candidates = [flagged, model.reasoning_effort ?? undefined, "high"];
38
+
39
+ const defaultEffort =
40
+ candidates.find(
41
+ (value): value is GrokReasoningEffort =>
42
+ value !== undefined && isGrokEffort(value) && efforts.includes(value),
43
+ ) ?? efforts[0]!;
44
+
45
+ return { id: model.id, contextWindow: model.context_window, efforts, defaultEffort, known: true };
46
+ }
47
+
48
+ export type GrokModelRefreshResult =
49
+ | { kind: "ok"; models: string[]; etag: string | undefined }
50
+ | { kind: "lookup_failed"; reason: string };
51
+
52
+ /**
53
+ * Model catalog seeded from the U0 capture and merged with live `GET {proxy}/models`. Like the
54
+ * OpenAI runtime, it never rejects an id: an unlisted id is forwarded and the upstream decides.
55
+ */
56
+ export class GrokModelCatalog {
57
+ readonly models = new Map<string, GrokResolvedModel>();
58
+ etag: string | undefined;
59
+ refreshedAt: number | undefined;
60
+ pendingRefresh: Promise<GrokModelRefreshResult> | undefined;
61
+
62
+ constructor() {
63
+ for (const seed of GROK_SEED_MODELS) {
64
+ this.models.set(seed.id, {
65
+ id: seed.id,
66
+ contextWindow: seed.contextWindow,
67
+ efforts: seed.reasoningEfforts,
68
+ defaultEffort: seed.defaultEffort,
69
+ known: true,
70
+ });
71
+ }
72
+ }
73
+
74
+ /** True when a response advertised a catalog etag different from the one last merged. */
75
+ isStale(observedEtag: string | undefined): boolean {
76
+ return observedEtag !== undefined && observedEtag !== this.etag;
77
+ }
78
+
79
+ /** Single-flight refresh. Live entries replace seeds; seeds absent from the live list stay. */
80
+ async refresh(
81
+ credentials: Pick<GrokCredentials, "accessToken" | "userId">,
82
+ options: { fetch?: typeof fetch; signal?: AbortSignal; baseUrl?: string; now?: number } = {},
83
+ ): Promise<GrokModelRefreshResult> {
84
+ this.pendingRefresh ??= this.refreshNow(credentials, options).finally(() => {
85
+ this.pendingRefresh = undefined;
86
+ });
87
+
88
+ return this.pendingRefresh;
89
+ }
90
+
91
+ async refreshNow(
92
+ credentials: Pick<GrokCredentials, "accessToken" | "userId">,
93
+ options: { fetch?: typeof fetch; signal?: AbortSignal; baseUrl?: string; now?: number },
94
+ ): Promise<GrokModelRefreshResult> {
95
+ try {
96
+ const getOptions: Parameters<typeof grokProxyGet>[1] = {
97
+ headers: buildGrokProxyGetHeaders({
98
+ bearer: credentials.accessToken,
99
+ userId: credentials.userId,
100
+ }),
101
+ };
102
+
103
+ if (options.fetch) getOptions.fetch = options.fetch;
104
+
105
+ if (options.signal) getOptions.signal = options.signal;
106
+
107
+ if (options.baseUrl) getOptions.baseUrl = options.baseUrl;
108
+ const { json, headers } = await grokProxyGet("/models", getOptions);
109
+
110
+ const parsed = GrokModelsResponse.safeParse(json);
111
+
112
+ if (!parsed.success) {
113
+ return {
114
+ kind: "lookup_failed",
115
+ reason: `unexpected /models shape: ${parsed.error.message}`,
116
+ };
117
+ }
118
+
119
+ const merged: string[] = [];
120
+
121
+ for (const entry of parsed.data.data) {
122
+ const model = fromProxyModel(entry);
123
+
124
+ if (!model) continue;
125
+ this.models.set(model.id, model);
126
+ merged.push(model.id);
127
+ }
128
+
129
+ this.etag = headers.modelsEtag;
130
+ this.refreshedAt = options.now ?? Date.now();
131
+
132
+ return { kind: "ok", models: merged, etag: this.etag };
133
+ } catch (error) {
134
+ return {
135
+ kind: "lookup_failed",
136
+ reason: error instanceof Error ? error.message : String(error),
137
+ };
138
+ }
139
+ }
140
+
141
+ resolve(modelId: string | undefined): GrokResolvedModel {
142
+ const id = canonicalGrokModelId(modelId);
143
+
144
+ return (
145
+ this.models.get(id) ?? {
146
+ id,
147
+ contextWindow: undefined,
148
+ efforts: GROK_REASONING_EFFORTS,
149
+ defaultEffort: "high",
150
+ known: false,
151
+ }
152
+ );
153
+ }
154
+
155
+ /** The effort if the model accepts it, otherwise undefined. Unlisted ids accept every effort. */
156
+ validateEffort(modelId: string | undefined, effort: string): GrokReasoningEffort | undefined {
157
+ const model = this.resolve(modelId);
158
+
159
+ return isGrokEffort(effort) && model.efforts.includes(effort) ? effort : undefined;
160
+ }
161
+ }
@@ -0,0 +1,406 @@
1
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
2
+
3
+ import { GROK_BUILD_VERSION, GROK_OAUTH_CLIENT_ID, GROK_OAUTH_SCOPES } from "./grok-constants";
4
+ import {
5
+ GROK_FALLBACK_AUTHORIZATION_ENDPOINT,
6
+ GROK_FALLBACK_TOKEN_ENDPOINT,
7
+ GrokOAuthError,
8
+ buildGrokAuthorizeUrl,
9
+ completeGrokOAuth,
10
+ getGrokOAuthEndpoints,
11
+ parseGrokCallbackInput,
12
+ pollGrokDeviceCode,
13
+ resetGrokDiscoveryCache,
14
+ startGrokDeviceCode,
15
+ startGrokOAuth,
16
+ } from "./grok-oauth";
17
+
18
+ const USER_ID = "11111111-2222-3333-4444-555555555555";
19
+
20
+ function segment(value: unknown): string {
21
+ return Buffer.from(JSON.stringify(value)).toString("base64url");
22
+ }
23
+
24
+ function accessToken(
25
+ payload: Record<string, unknown> = {},
26
+ header: Record<string, unknown> = { typ: "at+jwt", alg: "ES256" },
27
+ ): string {
28
+ return `${segment(header)}.${segment({ iss: "https://auth.x.ai", sub: USER_ID, ...payload })}.c2ln`;
29
+ }
30
+
31
+ interface Call {
32
+ url: string;
33
+ method: string;
34
+ headers: Record<string, string>;
35
+ body: string;
36
+ }
37
+
38
+ type Responder = (call: Call) => Response | Promise<Response>;
39
+
40
+ function fakeFetch(routes: Record<string, Responder>): { fetch: typeof fetch; calls: Call[] } {
41
+ const calls: Call[] = [];
42
+ const impl = async (input: string | URL | Request, init?: RequestInit): Promise<Response> => {
43
+ const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
44
+ const headers = Object.fromEntries(new Headers(init?.headers).entries());
45
+ const body =
46
+ init?.body instanceof URLSearchParams ? init.body.toString() : String(init?.body ?? "");
47
+ const call = { url, method: init?.method ?? "GET", headers, body };
48
+ calls.push(call);
49
+ const key = Object.keys(routes).find((route) => url.startsWith(route));
50
+ if (!key) throw new TypeError(`unexpected fetch ${url}`);
51
+ const responder = routes[key];
52
+ if (!responder) throw new TypeError(`unexpected fetch ${url}`);
53
+ return responder(call);
54
+ };
55
+ return { fetch: impl, calls };
56
+ }
57
+
58
+ const json = (status: number, body: unknown): Response =>
59
+ new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
60
+
61
+ const DISCOVERY = "https://auth.x.ai/.well-known/openid-configuration";
62
+ const USER = "https://cli-chat-proxy.grok.com/v1/user?include=subscription";
63
+
64
+ const discoveryOk = (): Response =>
65
+ json(200, {
66
+ authorization_endpoint: "https://auth.x.ai/oauth2/auth",
67
+ token_endpoint: "https://auth.x.ai/oauth2/token",
68
+ });
69
+ const userOk = (): Response =>
70
+ json(200, { userId: USER_ID, subscriptionTier: "SuperGrokPlus", hasGrokCodeAccess: true });
71
+ const tokensOk = (overrides: Record<string, unknown> = {}): Response =>
72
+ json(200, {
73
+ access_token: accessToken(),
74
+ refresh_token: "rt-1",
75
+ expires_in: 21_600,
76
+ token_type: "Bearer",
77
+ ...overrides,
78
+ });
79
+
80
+ beforeEach(() => resetGrokDiscoveryCache());
81
+ afterEach(() => resetGrokDiscoveryCache());
82
+
83
+ describe("authorize URL", () => {
84
+ it("emits grok-build's exact query", () => {
85
+ const url = buildGrokAuthorizeUrl({
86
+ authorizationEndpoint: "https://auth.x.ai/oauth2/auth",
87
+ redirectUri: "http://127.0.0.1:54321/callback",
88
+ codeChallenge: "challenge-abc",
89
+ state: "state-1",
90
+ nonce: "nonce-1",
91
+ });
92
+ expect(url).toBe(
93
+ "https://auth.x.ai/oauth2/auth?response_type=code" +
94
+ `&client_id=${GROK_OAUTH_CLIENT_ID}` +
95
+ "&redirect_uri=http%3A%2F%2F127.0.0.1%3A54321%2Fcallback" +
96
+ "&scope=openid%20profile%20email%20offline_access%20grok-cli%3Aaccess%20api%3Aaccess" +
97
+ "%20conversations%3Aread%20conversations%3Awrite%20workspaces%3Aread%20workspaces%3Awrite" +
98
+ "&code_challenge=challenge-abc&code_challenge_method=S256&state=state-1&nonce=nonce-1" +
99
+ "&referrer=grok-build",
100
+ );
101
+ expect(new URL(url).searchParams.get("scope")).toBe(GROK_OAUTH_SCOPES.join(" "));
102
+ });
103
+
104
+ it("startGrokOAuth binds a loopback redirect, state, and an S256 challenge of the verifier", async () => {
105
+ const { fetch } = fakeFetch({ [DISCOVERY]: discoveryOk });
106
+ const started = await startGrokOAuth({ state: "s-xyz", redirectPort: 40_123, fetch });
107
+ const url = new URL(started.authorizeUrl);
108
+ expect(started.redirectUri).toBe("http://127.0.0.1:40123/callback");
109
+ expect(url.origin + url.pathname).toBe("https://auth.x.ai/oauth2/auth");
110
+ expect(url.searchParams.get("redirect_uri")).toBe(started.redirectUri);
111
+ expect(url.searchParams.get("state")).toBe("s-xyz");
112
+ expect(url.searchParams.get("code_challenge_method")).toBe("S256");
113
+ expect(Buffer.from(started.verifier, "base64url")).toHaveLength(32);
114
+ const { createHash } = await import("node:crypto");
115
+ expect(url.searchParams.get("code_challenge")).toBe(
116
+ createHash("sha256").update(started.verifier).digest("base64url"),
117
+ );
118
+ expect(url.searchParams.get("nonce")).toMatch(/^[0-9a-f-]{36}$/);
119
+ });
120
+ });
121
+
122
+ describe("discovery", () => {
123
+ it("uses the fallback endpoints when the well-known fetch fails, without caching them", async () => {
124
+ let attempts = 0;
125
+ const { fetch } = fakeFetch({
126
+ [DISCOVERY]: () => {
127
+ attempts += 1;
128
+ throw new TypeError("fetch failed");
129
+ },
130
+ });
131
+ const endpoints = await getGrokOAuthEndpoints({ fetch });
132
+ expect(endpoints).toEqual({
133
+ authorizationEndpoint: GROK_FALLBACK_AUTHORIZATION_ENDPOINT,
134
+ tokenEndpoint: GROK_FALLBACK_TOKEN_ENDPOINT,
135
+ source: "fallback",
136
+ });
137
+ await getGrokOAuthEndpoints({ fetch });
138
+ expect(attempts).toBe(2);
139
+ });
140
+
141
+ it("falls back on HTTP errors and on endpoints outside x.ai", async () => {
142
+ const http = fakeFetch({ [DISCOVERY]: () => json(503, {}) });
143
+ expect((await getGrokOAuthEndpoints({ fetch: http.fetch })).source).toBe("fallback");
144
+ const foreign = fakeFetch({
145
+ [DISCOVERY]: () =>
146
+ json(200, {
147
+ authorization_endpoint: "https://auth.x.ai/oauth2/auth",
148
+ token_endpoint: "https://evil.example/token",
149
+ }),
150
+ });
151
+ expect((await getGrokOAuthEndpoints({ fetch: foreign.fetch })).source).toBe("fallback");
152
+ });
153
+
154
+ it("caches a discovered document for 24 h", async () => {
155
+ let now = 1_000;
156
+ const { fetch, calls } = fakeFetch({ [DISCOVERY]: discoveryOk });
157
+ await getGrokOAuthEndpoints({ fetch, now: () => now });
158
+ now += 24 * 60 * 60 * 1000 - 1;
159
+ expect((await getGrokOAuthEndpoints({ fetch, now: () => now })).source).toBe("discovery");
160
+ expect(calls).toHaveLength(1);
161
+ now += 1;
162
+ await getGrokOAuthEndpoints({ fetch, now: () => now });
163
+ expect(calls).toHaveLength(2);
164
+ });
165
+ });
166
+
167
+ describe("callback input", () => {
168
+ it("accepts a full callback URL or a bare code", () => {
169
+ expect(parseGrokCallbackInput(" http://127.0.0.1:5/callback?code=abc&state=s1 ")).toEqual({
170
+ code: "abc",
171
+ state: "s1",
172
+ });
173
+ expect(parseGrokCallbackInput("bare-code_123")).toEqual({ code: "bare-code_123", state: "" });
174
+ });
175
+
176
+ it("surfaces IdP errors and rejects empty input", () => {
177
+ expect(() =>
178
+ parseGrokCallbackInput("http://127.0.0.1:5/callback?error=access_denied&state=s"),
179
+ ).toThrow(expect.objectContaining({ code: "authorization_failed", oauthError: "access_denied" }));
180
+ expect(() => parseGrokCallbackInput(" ")).toThrow(GrokOAuthError);
181
+ });
182
+ });
183
+
184
+ describe("completeGrokOAuth", () => {
185
+ const base = {
186
+ verifier: "verifier-1",
187
+ redirectUri: "http://127.0.0.1:40123/callback",
188
+ state: "s1",
189
+ };
190
+
191
+ it("exchanges a pasted callback URL and resolves the account through /user", async () => {
192
+ const { fetch, calls } = fakeFetch({
193
+ [DISCOVERY]: discoveryOk,
194
+ "https://auth.x.ai/oauth2/token": () => tokensOk({ scope: "openid grok-cli:access" }),
195
+ [USER]: userOk,
196
+ });
197
+ const credentials = await completeGrokOAuth({
198
+ ...base,
199
+ codeOrUrl: "http://127.0.0.1:40123/callback?code=the-code&state=s1",
200
+ fetch,
201
+ now: () => Date.parse("2026-10-16T00:00:00Z"),
202
+ });
203
+
204
+ const exchange = calls.find((call) => call.url === "https://auth.x.ai/oauth2/token");
205
+ expect(exchange?.method).toBe("POST");
206
+ expect(exchange?.headers["content-type"]).toBe("application/x-www-form-urlencoded");
207
+ expect(exchange?.headers["x-grok-client-version"]).toBe(GROK_BUILD_VERSION);
208
+ expect(exchange?.headers["user-agent"]).toMatch(/^grok-shell\/1\.0\.60 \(/);
209
+ expect(Object.fromEntries(new URLSearchParams(exchange?.body))).toEqual({
210
+ grant_type: "authorization_code",
211
+ code: "the-code",
212
+ redirect_uri: base.redirectUri,
213
+ client_id: GROK_OAUTH_CLIENT_ID,
214
+ code_verifier: "verifier-1",
215
+ });
216
+ const user = calls.find((call) => call.url === USER);
217
+ expect(user?.headers["x-userid"]).toBe(USER_ID);
218
+ expect(user?.headers["x-xai-token-auth"]).toBe("xai-grok-cli");
219
+
220
+ expect(credentials).toMatchObject({
221
+ authMode: "oauth",
222
+ refreshToken: "rt-1",
223
+ expiresAt: "2026-10-16T06:00:00.000Z",
224
+ issuer: "https://auth.x.ai",
225
+ clientId: GROK_OAUTH_CLIENT_ID,
226
+ scopes: ["openid", "grok-cli:access"],
227
+ userId: USER_ID,
228
+ subscriptionTier: "SuperGrokPlus",
229
+ });
230
+ expect(credentials.sessionId).not.toBe(credentials.agentId);
231
+ });
232
+
233
+ it("accepts a bare code and defaults scopes to the requested set", async () => {
234
+ const { fetch, calls } = fakeFetch({
235
+ [DISCOVERY]: discoveryOk,
236
+ "https://auth.x.ai/oauth2/token": () => tokensOk(),
237
+ [USER]: userOk,
238
+ });
239
+ const credentials = await completeGrokOAuth({ ...base, codeOrUrl: "bare-code", fetch });
240
+ expect(new URLSearchParams(calls[1]?.body).get("code")).toBe("bare-code");
241
+ expect(credentials.scopes).toEqual([...GROK_OAUTH_SCOPES]);
242
+ });
243
+
244
+ it("rejects a state mismatch before any token request", async () => {
245
+ const { fetch, calls } = fakeFetch({});
246
+ await expect(
247
+ completeGrokOAuth({
248
+ ...base,
249
+ codeOrUrl: "http://127.0.0.1:40123/callback?code=c&state=other",
250
+ fetch,
251
+ }),
252
+ ).rejects.toMatchObject({ code: "state_mismatch" });
253
+ expect(calls).toHaveLength(0);
254
+ });
255
+
256
+ it("fails registration when the user id cannot be resolved", async () => {
257
+ const noSub = fakeFetch({
258
+ [DISCOVERY]: discoveryOk,
259
+ "https://auth.x.ai/oauth2/token": () =>
260
+ tokensOk({ access_token: accessToken({ sub: undefined }) }),
261
+ });
262
+ await expect(
263
+ completeGrokOAuth({ ...base, codeOrUrl: "c", fetch: noSub.fetch }),
264
+ ).rejects.toMatchObject({ code: "user_id_unresolved" });
265
+ expect(noSub.calls.some((call) => call.url === USER)).toBe(false);
266
+
267
+ const badUser = fakeFetch({
268
+ [DISCOVERY]: discoveryOk,
269
+ "https://auth.x.ai/oauth2/token": () => tokensOk(),
270
+ [USER]: () => json(200, { subscriptionTier: "Super" }),
271
+ });
272
+ await expect(
273
+ completeGrokOAuth({ ...base, codeOrUrl: "c", fetch: badUser.fetch }),
274
+ ).rejects.toMatchObject({ code: "user_id_unresolved" });
275
+
276
+ const deniedUser = fakeFetch({
277
+ [DISCOVERY]: discoveryOk,
278
+ "https://auth.x.ai/oauth2/token": () => tokensOk(),
279
+ [USER]: () => json(401, {}),
280
+ });
281
+ await expect(
282
+ completeGrokOAuth({ ...base, codeOrUrl: "c", fetch: deniedUser.fetch }),
283
+ ).rejects.toMatchObject({ code: "user_lookup_failed", status: 401 });
284
+ });
285
+
286
+ it("rejects tokens that are not an auth.x.ai at+jwt", async () => {
287
+ for (const token of [
288
+ "xai-api-key-123",
289
+ accessToken({}, { typ: "JWT", alg: "ES256" }),
290
+ accessToken({ iss: "https://evil.example" }),
291
+ ]) {
292
+ const { fetch, calls } = fakeFetch({
293
+ [DISCOVERY]: discoveryOk,
294
+ "https://auth.x.ai/oauth2/token": () => tokensOk({ access_token: token }),
295
+ [USER]: userOk,
296
+ });
297
+ await expect(completeGrokOAuth({ ...base, codeOrUrl: "c", fetch })).rejects.toMatchObject({
298
+ code: "invalid_access_token",
299
+ });
300
+ expect(calls.some((call) => call.url === USER)).toBe(false);
301
+ }
302
+ });
303
+
304
+ it("does not echo token endpoint bodies in errors", async () => {
305
+ const { fetch } = fakeFetch({
306
+ [DISCOVERY]: discoveryOk,
307
+ "https://auth.x.ai/oauth2/token": () =>
308
+ json(400, { error: "invalid_grant", error_description: "secret-echo" }),
309
+ });
310
+ const error = await completeGrokOAuth({ ...base, codeOrUrl: "c", fetch }).catch(
311
+ (caught: unknown) => caught,
312
+ );
313
+ expect(error).toMatchObject({ code: "token_exchange_failed", oauthError: "invalid_grant" });
314
+ expect(error).toBeInstanceOf(GrokOAuthError);
315
+ expect(String(error)).toContain("invalid_grant");
316
+ expect(String(error)).not.toContain("secret-echo");
317
+ });
318
+ });
319
+
320
+ describe("device code", () => {
321
+ it("starts with the frozen scope and validates the response", async () => {
322
+ const { fetch, calls } = fakeFetch({
323
+ "https://auth.x.ai/oauth2/device/code": () =>
324
+ json(200, {
325
+ device_code: "dc",
326
+ user_code: "ABCD-1234",
327
+ verification_uri: "https://accounts.x.ai/device",
328
+ verification_uri_complete: "https://accounts.x.ai/device?user_code=ABCD-1234",
329
+ expires_in: 900,
330
+ }),
331
+ });
332
+ expect(await startGrokDeviceCode({ fetch })).toEqual({
333
+ deviceCode: "dc",
334
+ userCode: "ABCD-1234",
335
+ verificationUri: "https://accounts.x.ai/device",
336
+ verificationUriComplete: "https://accounts.x.ai/device?user_code=ABCD-1234",
337
+ expiresIn: 900,
338
+ interval: 5,
339
+ });
340
+ expect(Object.fromEntries(new URLSearchParams(calls[0]?.body))).toEqual({
341
+ client_id: GROK_OAUTH_CLIENT_ID,
342
+ scope: GROK_OAUTH_SCOPES.join(" "),
343
+ referrer: "grok-build",
344
+ });
345
+ expect(calls[0]?.headers["x-grok-client-version"]).toBe(GROK_BUILD_VERSION);
346
+ });
347
+
348
+ it("rejects unsafe verification URIs", async () => {
349
+ const { fetch } = fakeFetch({
350
+ "https://auth.x.ai/oauth2/device/code": () =>
351
+ json(200, {
352
+ device_code: "dc",
353
+ user_code: "ABCD",
354
+ verification_uri: "javascript:alert(1)",
355
+ expires_in: 900,
356
+ }),
357
+ });
358
+ await expect(startGrokDeviceCode({ fetch })).rejects.toMatchObject({
359
+ code: "invalid_device_code_response",
360
+ });
361
+ });
362
+
363
+ it("polls once per call and honors authorization_pending and slow_down", async () => {
364
+ const replies = [
365
+ json(400, { error: "authorization_pending" }),
366
+ json(400, { error: "slow_down" }),
367
+ tokensOk(),
368
+ ];
369
+ const { fetch, calls } = fakeFetch({
370
+ "https://auth.x.ai/oauth2/token": () => {
371
+ const reply = replies.shift();
372
+ if (!reply) throw new TypeError("no more replies");
373
+ return reply;
374
+ },
375
+ [USER]: userOk,
376
+ });
377
+ expect(await pollGrokDeviceCode({ deviceCode: "dc", interval: 5, fetch })).toEqual({
378
+ status: "pending",
379
+ interval: 5,
380
+ });
381
+ expect(await pollGrokDeviceCode({ deviceCode: "dc", interval: 5, fetch })).toEqual({
382
+ status: "pending",
383
+ interval: 10,
384
+ });
385
+ const done = await pollGrokDeviceCode({ deviceCode: "dc", interval: 10, fetch });
386
+ expect(done.status).toBe("complete");
387
+ expect(done.status === "complete" ? done.credentials.userId : undefined).toBe(USER_ID);
388
+ expect(Object.fromEntries(new URLSearchParams(calls[0]?.body))).toEqual({
389
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
390
+ device_code: "dc",
391
+ client_id: GROK_OAUTH_CLIENT_ID,
392
+ });
393
+ });
394
+
395
+ it("treats expired_token and access_denied as terminal", async () => {
396
+ for (const [error, code] of [
397
+ ["expired_token", "expired_token"],
398
+ ["access_denied", "access_denied"],
399
+ ] as const) {
400
+ const { fetch } = fakeFetch({ "https://auth.x.ai/oauth2/token": () => json(400, { error }) });
401
+ await expect(pollGrokDeviceCode({ deviceCode: "dc", interval: 5, fetch })).rejects.toMatchObject(
402
+ { code, retryable: false },
403
+ );
404
+ }
405
+ });
406
+ });