@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.
- package/bundle/dist/_virtual/_rolldown/runtime.js +1 -10
- package/bundle/dist/application/monit/log-buffer.js +12 -6
- package/bundle/dist/application/monit/monit.frame.js +11 -3
- package/bundle/dist/application/monit/monit.types.js +1 -1
- package/bundle/dist/application/qgrid/conv-routing.js +9 -6
- package/bundle/dist/application/qgrid/expired-token-reminder.js +2 -2
- package/bundle/dist/application/qgrid/oauth.js +31 -4
- package/bundle/dist/application/qgrid/qgrid-image-generation.js +1 -1
- package/bundle/dist/application/qgrid/qgrid-response-format.js +21 -19
- package/bundle/dist/application/qgrid/qgrid-run-lifecycle.js +40 -33
- package/bundle/dist/application/qgrid/qgrid.dispatcher.js +128 -93
- package/bundle/dist/application/qgrid/qgrid.frame.js +930 -49
- package/bundle/dist/application/qgrid/qgrid.types.js +212 -5
- package/bundle/dist/application/qgrid/schema-prompt.js +75 -48
- package/bundle/dist/application/qgrid/slack-user-map.js +1 -1
- package/bundle/dist/application/qgrid/token-death.js +1 -1
- package/bundle/dist/application/qgrid/token-subscriber.js +83 -30
- package/bundle/dist/application/qgrid/token-trigger-setup.js +1 -1
- package/bundle/dist/application/qgrid/token-window-keepalive.js +8 -6
- package/bundle/dist/application/qgrid/tool-emulation-schema.js +13 -13
- package/bundle/dist/application/qgrid/tool-emulation.js +19 -15
- package/bundle/dist/application/request-log/json-schema-type-text.js +29 -12
- package/bundle/dist/application/request-log/request-log.model.js +83 -70
- package/bundle/dist/application/request-log/request-log.types.js +1 -1
- package/bundle/dist/application/request-log/zod-code-format.js +1 -1
- package/bundle/dist/application/request-log-step/request-log-step.model.js +1 -1
- package/bundle/dist/application/request-log-step/request-log-step.types.js +1 -1
- package/bundle/dist/application/setting/setting.constant.js +1 -1
- package/bundle/dist/application/setting/setting.model.js +2 -2
- package/bundle/dist/application/setting/setting.store.js +2 -2
- package/bundle/dist/application/setting/setting.types.js +1 -1
- package/bundle/dist/application/sonamu.generated.js +32 -16
- package/bundle/dist/application/sonamu.generated.sso.js +1 -1
- package/bundle/dist/application/token/token.model.js +26 -9
- package/bundle/dist/application/token/token.types.js +20 -3
- package/bundle/dist/i18n/en.js +15 -1
- package/bundle/dist/i18n/ko.js +15 -1
- package/bundle/dist/i18n/sd.generated.js +1 -1
- package/bundle/dist/migrations/20260807133553_manual__backfill_request_log_cost.js +8 -6
- package/bundle/dist/server-bootstrap.js +4 -2
- package/bundle/dist/server-error-handler.js +1 -1
- package/bundle/dist/sonamu.config.js +37 -8
- package/bundle/dist/startup-migrations.js +17 -7
- package/bundle/dist/testing/global.js +1 -1
- package/bundle/dist/testing/schema-bench.js +0 -0
- package/bundle/dist/testing/test-database-name.js +1 -1
- package/bundle/dist/utils/providers/anthropic/anthropic-constants.js +1 -1
- package/bundle/dist/utils/providers/anthropic/anthropic-dispatcher.js +11 -5
- package/bundle/dist/utils/providers/anthropic/anthropic-quota.js +4 -4
- package/bundle/dist/utils/providers/anthropic/anthropic-thinking.js +1 -1
- package/bundle/dist/utils/providers/anthropic/claude-session.js +20 -19
- package/bundle/dist/utils/providers/anthropic/fence-strip.js +1 -1
- package/bundle/dist/utils/providers/anthropic/stream-json-adapter.js +49 -23
- package/bundle/dist/utils/providers/common/credentials.js +5 -1
- package/bundle/dist/utils/providers/common/effort.js +3 -3
- package/bundle/dist/utils/providers/common/model-cost.js +69 -14
- package/bundle/dist/utils/providers/common/provider-types.js +29 -1
- package/bundle/dist/utils/providers/common/schema-validation.js +78 -40
- package/bundle/dist/utils/providers/common/smooth-weighted-round-robin.js +1 -1
- package/bundle/dist/utils/providers/common/strictifier.js +62 -43
- package/bundle/dist/utils/providers/common/thinking.js +3 -2
- package/bundle/dist/utils/providers/common/ttft.js +1 -1
- package/bundle/dist/utils/providers/grok/grok-backend-protocol.js +427 -0
- package/bundle/dist/utils/providers/grok/grok-constants.js +55 -0
- package/bundle/dist/utils/providers/grok/grok-direct-client.js +196 -0
- package/bundle/dist/utils/providers/grok/grok-dispatcher.js +1313 -0
- package/bundle/dist/utils/providers/grok/grok-headers.js +118 -0
- package/bundle/dist/utils/providers/grok/grok-media-client.js +420 -0
- package/bundle/dist/utils/providers/grok/grok-models.js +114 -0
- package/bundle/dist/utils/providers/grok/grok-oauth.js +455 -0
- package/bundle/dist/utils/providers/grok/grok-quota.js +119 -0
- package/bundle/dist/utils/providers/grok/grok-refresh.js +192 -0
- package/bundle/dist/utils/providers/grok/grok-types.js +186 -0
- package/bundle/dist/utils/providers/grok/grok-voice-client.js +199 -0
- package/bundle/dist/utils/providers/grok/index.js +11 -0
- package/bundle/dist/utils/providers/openai/openai-backend-protocol.js +111 -83
- package/bundle/dist/utils/providers/openai/openai-callback-relay.js +3 -2
- package/bundle/dist/utils/providers/openai/openai-constants.js +1 -1
- package/bundle/dist/utils/providers/openai/openai-direct-client.js +23 -7
- package/bundle/dist/utils/providers/openai/openai-dispatcher.js +143 -106
- package/bundle/dist/utils/providers/openai/openai-image-generation.js +48 -33
- package/bundle/dist/utils/providers/openai/openai-oauth.js +28 -13
- package/bundle/dist/utils/providers/openai/openai-quota.js +38 -27
- package/bundle/dist/utils/providers/openai/openai-refresh.js +17 -9
- package/bundle/dist/utils/providers/openai/openai-sse.js +1 -1
- package/bundle/dist/utils/providers/openai/openai-transport-config.js +1 -1
- package/bundle/dist/utils/quiet-hours.js +1 -1
- package/bundle/dist/utils/slack-notify.js +14 -8
- package/bundle/dist/utils/subset-loaders.js +4 -5
- package/bundle/src/application/monit/log-buffer.ts +23 -9
- package/bundle/src/application/monit/monit.frame.ts +22 -5
- package/bundle/src/application/monit/monit.types.ts +6 -0
- package/bundle/src/application/qgrid/conv-routing.test.ts +76 -4
- package/bundle/src/application/qgrid/conv-routing.ts +35 -9
- package/bundle/src/application/qgrid/expired-token-reminder.ts +9 -1
- package/bundle/src/application/qgrid/oauth.ts +51 -15
- package/bundle/src/application/qgrid/qgrid-image-generation.ts +9 -0
- package/bundle/src/application/qgrid/qgrid-response-format.ts +50 -25
- package/bundle/src/application/qgrid/qgrid-run-lifecycle.test.ts +19 -0
- package/bundle/src/application/qgrid/qgrid-run-lifecycle.ts +84 -40
- package/bundle/src/application/qgrid/qgrid.dispatcher.test.ts +97 -0
- package/bundle/src/application/qgrid/qgrid.dispatcher.ts +274 -126
- package/bundle/src/application/qgrid/qgrid.frame.test.ts +1013 -3
- package/bundle/src/application/qgrid/qgrid.frame.ts +1415 -60
- package/bundle/src/application/qgrid/qgrid.types.ts +335 -0
- package/bundle/src/application/qgrid/schema-prompt.ts +105 -41
- package/bundle/src/application/qgrid/slack-user-map.ts +5 -0
- package/bundle/src/application/qgrid/token-death.ts +4 -0
- package/bundle/src/application/qgrid/token-subscriber.test.ts +21 -19
- package/bundle/src/application/qgrid/token-subscriber.ts +186 -53
- package/bundle/src/application/qgrid/token-trigger-setup.ts +2 -0
- package/bundle/src/application/qgrid/token-window-keepalive.ts +49 -6
- package/bundle/src/application/qgrid/tool-emulation-schema.ts +33 -29
- package/bundle/src/application/qgrid/tool-emulation.ts +52 -27
- package/bundle/src/application/request-log/json-schema-type-text.test.ts +5 -5
- package/bundle/src/application/request-log/json-schema-type-text.ts +81 -26
- package/bundle/src/application/request-log/request-log.model.test.ts +111 -4
- package/bundle/src/application/request-log/request-log.model.ts +277 -146
- package/bundle/src/application/request-log/request-log.types.ts +6 -0
- package/bundle/src/application/request-log/zod-code-format.ts +11 -0
- package/bundle/src/application/request-log-step/request-log-step.model.ts +1 -0
- package/bundle/src/application/request-log-step/request-log-step.types.ts +2 -0
- package/bundle/src/application/setting/setting.constant.ts +9 -0
- package/bundle/src/application/setting/setting.model.ts +12 -2
- package/bundle/src/application/setting/setting.store.ts +5 -1
- package/bundle/src/application/setting/setting.types.ts +8 -0
- package/bundle/src/application/sonamu.generated.http +133 -0
- package/bundle/src/application/sonamu.generated.sso.ts +2 -0
- package/bundle/src/application/sonamu.generated.ts +15 -0
- package/bundle/src/application/token/token.identity.test.ts +20 -0
- package/bundle/src/application/token/token.model.ts +66 -7
- package/bundle/src/application/token/token.types.ts +24 -1
- package/bundle/src/i18n/en.ts +19 -0
- package/bundle/src/i18n/ko.ts +21 -0
- package/bundle/src/i18n/sd.generated.ts +23 -2
- package/bundle/src/migrations/20260807133553_manual__backfill_request_log_cost.ts +16 -8
- package/bundle/src/server-bootstrap.ts +4 -2
- package/bundle/src/server-error-handler.ts +1 -0
- package/bundle/src/sonamu.config.ts +69 -7
- package/bundle/src/startup-migrations.ts +27 -9
- package/bundle/src/testing/global.ts +20 -0
- package/bundle/src/testing/schema-bench.ts +105 -30
- package/bundle/src/testing/test-database-name.ts +2 -0
- package/bundle/src/utils/providers/anthropic/anthropic-constants.ts +8 -0
- package/bundle/src/utils/providers/anthropic/anthropic-dispatcher.ts +41 -6
- package/bundle/src/utils/providers/anthropic/anthropic-quota.ts +7 -3
- package/bundle/src/utils/providers/anthropic/anthropic-thinking.ts +6 -0
- package/bundle/src/utils/providers/anthropic/claude-session.ts +58 -24
- package/bundle/src/utils/providers/anthropic/fence-strip.ts +11 -0
- package/bundle/src/utils/providers/anthropic/stream-json-adapter.ts +110 -45
- package/bundle/src/utils/providers/common/credentials.ts +8 -0
- package/bundle/src/utils/providers/common/effort.ts +9 -2
- package/bundle/src/utils/providers/common/model-cost.test.ts +48 -1
- package/bundle/src/utils/providers/common/model-cost.ts +259 -158
- package/bundle/src/utils/providers/common/provider-dispatcher.ts +98 -2
- package/bundle/src/utils/providers/common/provider-types.ts +46 -3
- package/bundle/src/utils/providers/common/schema-validation.test.ts +5 -1
- package/bundle/src/utils/providers/common/schema-validation.ts +124 -56
- package/bundle/src/utils/providers/common/smooth-weighted-round-robin.ts +4 -0
- package/bundle/src/utils/providers/common/strictifier.ts +185 -59
- package/bundle/src/utils/providers/common/thinking.ts +2 -2
- package/bundle/src/utils/providers/common/ttft.ts +4 -2
- package/bundle/src/utils/providers/grok/__fixtures__/golden-responses-1.0.60.json +164 -0
- package/bundle/src/utils/providers/grok/grok-backend-protocol.test.ts +325 -0
- package/bundle/src/utils/providers/grok/grok-backend-protocol.ts +721 -0
- package/bundle/src/utils/providers/grok/grok-constants.ts +105 -0
- package/bundle/src/utils/providers/grok/grok-direct-client.test.ts +179 -0
- package/bundle/src/utils/providers/grok/grok-direct-client.ts +313 -0
- package/bundle/src/utils/providers/grok/grok-dispatcher.test.ts +1395 -0
- package/bundle/src/utils/providers/grok/grok-dispatcher.ts +2105 -0
- package/bundle/src/utils/providers/grok/grok-headers.test.ts +149 -0
- package/bundle/src/utils/providers/grok/grok-headers.ts +199 -0
- package/bundle/src/utils/providers/grok/grok-media-client.test.ts +446 -0
- package/bundle/src/utils/providers/grok/grok-media-client.ts +703 -0
- package/bundle/src/utils/providers/grok/grok-models.test.ts +96 -0
- package/bundle/src/utils/providers/grok/grok-models.ts +161 -0
- package/bundle/src/utils/providers/grok/grok-oauth.test.ts +406 -0
- package/bundle/src/utils/providers/grok/grok-oauth.ts +771 -0
- package/bundle/src/utils/providers/grok/grok-quota.test.ts +142 -0
- package/bundle/src/utils/providers/grok/grok-quota.ts +198 -0
- package/bundle/src/utils/providers/grok/grok-refresh.test.ts +239 -0
- package/bundle/src/utils/providers/grok/grok-refresh.ts +325 -0
- package/bundle/src/utils/providers/grok/grok-types.test.ts +252 -0
- package/bundle/src/utils/providers/grok/grok-types.ts +374 -0
- package/bundle/src/utils/providers/grok/grok-voice-client.test.ts +321 -0
- package/bundle/src/utils/providers/grok/grok-voice-client.ts +284 -0
- package/bundle/src/utils/providers/grok/index.ts +20 -0
- package/bundle/src/utils/providers/openai/openai-backend-protocol.ts +221 -137
- package/bundle/src/utils/providers/openai/openai-callback-relay.ts +12 -1
- package/bundle/src/utils/providers/openai/openai-constants.ts +2 -0
- package/bundle/src/utils/providers/openai/openai-direct-client.ts +111 -21
- package/bundle/src/utils/providers/openai/openai-dispatcher.ts +322 -169
- package/bundle/src/utils/providers/openai/openai-image-generation.ts +116 -36
- package/bundle/src/utils/providers/openai/openai-oauth.ts +48 -20
- package/bundle/src/utils/providers/openai/openai-quota.ts +77 -46
- package/bundle/src/utils/providers/openai/openai-refresh.ts +42 -18
- package/bundle/src/utils/providers/openai/openai-sse.ts +27 -3
- package/bundle/src/utils/providers/openai/openai-transport-config.ts +2 -0
- package/bundle/src/utils/quiet-hours.ts +12 -1
- package/bundle/src/utils/slack-notify.ts +44 -13
- package/bundle/src/utils/subset-loaders.ts +7 -7
- package/bundle/web-dist/client/assets/clsx-5XM_pHvN.js +1 -0
- package/bundle/web-dist/client/assets/index-Be2qCZmI.css +1 -0
- package/bundle/web-dist/client/assets/index-C96h3feE.js +32 -0
- package/bundle/web-dist/client/assets/logs-DBM6U0QD.js +1 -0
- package/bundle/web-dist/client/assets/monit-BBjXa7vB.js +1 -0
- package/bundle/web-dist/client/assets/routes-Crz_PQzi.js +5 -0
- package/bundle/web-dist/client/assets/{services.generated-NP-ValHd.js → services.generated-B5lc3aIC.js} +5 -5
- package/bundle/web-dist/client/assets/settings-Cdwm3OBB.js +1 -0
- package/bundle/web-dist/client/assets/show-DBUhALT1.js +2 -0
- package/bundle/web-dist/client/assets/tokens-CVQAVJTo.js +1 -0
- package/bundle/web-dist/client/assets/total-cost-MnfKU8IN.js +1 -0
- package/bundle/web-dist/client/assets/use-oauth-login-flow-BfxYI4Oh.js +1 -0
- package/bundle/web-dist/client/assets/use-update-token-mutation-qKBC2sn7.js +1 -0
- package/bundle/web-dist/client/assets/{vendor-tanstack-DJRQAbrY.js → vendor-tanstack-DRPs09ai.js} +1 -1
- package/bundle/web-dist/client/index.html +5 -5
- package/bundle/web-dist/server/assets/clsx-D_N5PN_2.js +16 -0
- package/bundle/web-dist/server/assets/{lazyRouteComponent-CgBdabgd.js → lazyRouteComponent-AKiHfZWp.js} +129 -2
- package/bundle/web-dist/server/assets/{logs-BMyR98rU.js → logs-B7QmGJq3.js} +18 -14
- package/bundle/web-dist/server/assets/{monit-ZPt66W5M.js → monit-DFt7x3bh.js} +13 -12
- package/bundle/web-dist/server/assets/{plus-BZ9I0VgC.js → plus-DaYQRpBt.js} +1 -1
- package/bundle/web-dist/server/assets/{react-dom-Ds75e4Jl.js → react-dom-Cv23mm1K.js} +2 -129
- package/bundle/web-dist/server/assets/{refresh-cw-CztCFis-.js → refresh-cw-BpXNsAy-.js} +1 -1
- package/bundle/web-dist/server/assets/{request-model-DjGldb-h.js → request-model-eHE6YbaF.js} +1 -1
- package/bundle/web-dist/server/assets/{routes-Ct-k7nBA.js → routes-_Y-PJfgO.js} +110 -39
- package/bundle/web-dist/server/assets/{services.generated-BA4AcAf5.js → services.generated-CMJ8AHbO.js} +582 -172
- package/bundle/web-dist/server/assets/{settings-CBUZ47Xx.js → settings-JwJKxn2e.js} +14 -14
- package/bundle/web-dist/server/assets/{show-LSFIzvO2.js → show-BEv6z1v_.js} +90 -60
- package/bundle/web-dist/server/assets/{tokens-DxrBW512.js → tokens-CNT4mI-f.js} +162 -50
- package/bundle/web-dist/server/assets/total-cost-fTE9Xy82.js +18 -0
- package/bundle/web-dist/server/assets/use-oauth-login-flow-jkAkT3X6.js +2345 -0
- package/bundle/web-dist/server/assets/{use-update-token-mutation-pkArIzCP.js → use-update-token-mutation-Sm6kh02m.js} +1 -1
- package/bundle/web-dist/server/entry-server.generated.js +1644 -21655
- package/dist/cli.js +9 -9
- package/package.json +4 -11
- package/postinstall.mjs +12 -0
- package/skills/qgrid/SKILL.md +1 -0
- package/skills/qgrid/references/cli-env-and-server-boot.md +4 -1
- package/skills/qgrid/references/decision-rationale.md +23 -0
- package/skills/qgrid/references/grok-runtime.md +89 -0
- package/skills/qgrid/references/provider-runtime-differences.md +30 -29
- package/skills/qgrid/references/repo-map.md +10 -0
- package/skills/qgrid/references/token-auth-quota-lifecycle.md +56 -3
- package/bundle/dist/node_modules/.pnpm/@jridgewell_sourcemap-codec@1.5.5/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.js +0 -406
- package/bundle/dist/node_modules/.pnpm/@vitest_expect@4.1.10/node_modules/@vitest/expect/dist/index.js +0 -1507
- package/bundle/dist/node_modules/.pnpm/@vitest_pretty-format@4.1.10/node_modules/@vitest/pretty-format/dist/index.js +0 -887
- package/bundle/dist/node_modules/.pnpm/@vitest_runner@4.1.10/node_modules/@vitest/runner/dist/chunk-artifact.js +0 -2756
- package/bundle/dist/node_modules/.pnpm/@vitest_runner@4.1.10/node_modules/@vitest/runner/dist/index.js +0 -8
- package/bundle/dist/node_modules/.pnpm/@vitest_runner@4.1.10/node_modules/@vitest/runner/dist/utils.js +0 -8
- package/bundle/dist/node_modules/.pnpm/@vitest_snapshot@4.1.10/node_modules/@vitest/snapshot/dist/index.js +0 -922
- package/bundle/dist/node_modules/.pnpm/@vitest_spy@4.1.10/node_modules/@vitest/spy/dist/index.js +0 -386
- package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/chunk-pathe.M-eThtNZ.js +0 -121
- package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/constants.js +0 -51
- package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/diff.js +0 -1317
- package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/display.js +0 -561
- package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/error.js +0 -40
- package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/helpers.js +0 -272
- package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/offset.js +0 -27
- package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/serialize.js +0 -77
- package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/source-map.js +0 -381
- package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/timers.js +0 -51
- package/bundle/dist/node_modules/.pnpm/chai@6.2.2/node_modules/chai/index.js +0 -2986
- package/bundle/dist/node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/branding.js +0 -10
- package/bundle/dist/node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/index.js +0 -109
- package/bundle/dist/node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/messages.js +0 -10
- package/bundle/dist/node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/overloads.js +0 -10
- package/bundle/dist/node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/utils.js +0 -10
- package/bundle/dist/node_modules/.pnpm/magic-string@0.30.21/node_modules/magic-string/dist/magic-string.es.js +0 -1126
- package/bundle/dist/node_modules/.pnpm/pathe@2.0.3/node_modules/pathe/dist/index.js +0 -21
- package/bundle/dist/node_modules/.pnpm/pathe@2.0.3/node_modules/pathe/dist/shared/pathe.M-eThtNZ.js +0 -432
- package/bundle/dist/node_modules/.pnpm/tinybench@2.9.0/node_modules/tinybench/dist/index.js +0 -517
- package/bundle/dist/node_modules/.pnpm/tinyrainbow@3.1.0/node_modules/tinyrainbow/dist/index.js +0 -101
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- package/bundle/dist/testing/setup-mocks.js +0 -16
- package/bundle/src/testing/setup-mocks.ts +0 -44
- package/bundle/web-dist/client/assets/index-Bw2s88Ot.css +0 -1
- package/bundle/web-dist/client/assets/index-DZwzUfI-.js +0 -106
- package/bundle/web-dist/client/assets/logs-Cog79DU5.js +0 -1
- package/bundle/web-dist/client/assets/monit-2auznBys.js +0 -1
- package/bundle/web-dist/client/assets/routes-B0lTeLK-.js +0 -5
- package/bundle/web-dist/client/assets/settings-D8ZxZGSJ.js +0 -1
- package/bundle/web-dist/client/assets/show-CFSERlnM.js +0 -2
- package/bundle/web-dist/client/assets/tokens-CZXUyI7O.js +0 -1
- package/bundle/web-dist/client/assets/use-oauth-login-flow-hboOs3wG.js +0 -1
- package/bundle/web-dist/client/assets/use-update-token-mutation-BQHS0byG.js +0 -1
- package/bundle/web-dist/server/assets/use-oauth-login-flow-Gn8flMK5.js +0 -178
- package/dist/cli.d.ts +0 -10
- /package/bundle/web-dist/client/assets/{cost-DH2jZbrw.js → cost-DB3HbU7T.js} +0 -0
- /package/bundle/web-dist/client/assets/{plus-B62CuqT8.js → plus-BsQdY7PS.js} +0 -0
- /package/bundle/web-dist/client/assets/{preload-helper-ca-nBW7U.js → preload-helper-DWTEM3RW.js} +0 -0
- /package/bundle/web-dist/client/assets/{refresh-cw-NNcU0Cmq.js → refresh-cw-D9IUmT23.js} +0 -0
- /package/bundle/web-dist/client/assets/{request-model-CojtinqK.js → request-model-Bpp_rkK8.js} +0 -0
- /package/bundle/web-dist/server/assets/{cost-eEHeTRT5.js → cost-BC9yzyA9.js} +0 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
// Grok (xAI) token refresh: single-flight per token, early refresh with a stable per-account
|
|
2
|
+
// jitter, refresh-token retention when the IdP does not rotate, persist-before-resolve.
|
|
3
|
+
// Mechanism follows grok-build 1.0.45 xai-grok-login/src (oidc/protocol.rs:413-520,
|
|
4
|
+
// oidc/refresh.rs:21-27,260-264, model.rs:9). grok-build retries transient failures in place
|
|
5
|
+
// (3 attempts); qgrid does not, because the dispatcher owns the per-request attempt budget.
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
|
+
|
|
8
|
+
import { getLogger } from "@logtape/logtape";
|
|
9
|
+
|
|
10
|
+
import { grokShellUserAgent } from "./grok-headers";
|
|
11
|
+
import {
|
|
12
|
+
type GrokFetch,
|
|
13
|
+
GrokTokenResponse,
|
|
14
|
+
getGrokOAuthEndpoints,
|
|
15
|
+
readGrokOAuthErrorCode,
|
|
16
|
+
} from "./grok-oauth";
|
|
17
|
+
import {
|
|
18
|
+
GrokCredentials,
|
|
19
|
+
type GrokRefreshResult,
|
|
20
|
+
type GrokRefresher,
|
|
21
|
+
type GrokTokenRef,
|
|
22
|
+
decodeGrokJwt,
|
|
23
|
+
} from "./grok-types";
|
|
24
|
+
|
|
25
|
+
const logger = getLogger(["qgrid", "grok-refresh"]);
|
|
26
|
+
|
|
27
|
+
/** model.rs:9 DEFAULT_EARLY_INVALIDATION_SECS. */
|
|
28
|
+
export const GROK_REFRESH_MARGIN_MS = 300_000;
|
|
29
|
+
|
|
30
|
+
/** Upper bound of the per-account jitter added to the margin (inclusive). */
|
|
31
|
+
export const GROK_REFRESH_JITTER_MAX_MS = 60_000;
|
|
32
|
+
const REFRESH_TIMEOUT_MS = 15_000;
|
|
33
|
+
|
|
34
|
+
/** oidc/refresh.rs:21-27: the only codes that end a refresh token's life. */
|
|
35
|
+
const TERMINAL_ERROR_CODES = new Set(["invalid_grant", "invalid_client"]);
|
|
36
|
+
|
|
37
|
+
/** The refresh token is dead; only a new login recovers. Never retry. */
|
|
38
|
+
export class GrokReauthRequiredError extends Error {
|
|
39
|
+
override readonly name = "GrokReauthRequiredError";
|
|
40
|
+
constructor(
|
|
41
|
+
readonly oauthError: string,
|
|
42
|
+
readonly status: number,
|
|
43
|
+
) {
|
|
44
|
+
super(`Grok refresh rejected: HTTP ${status} ${oauthError}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Network failure, timeout, 5xx, 429, or an unrecognized error; the refresh token still lives. */
|
|
49
|
+
export class GrokRefreshTransientError extends Error {
|
|
50
|
+
override readonly name = "GrokRefreshTransientError";
|
|
51
|
+
constructor(
|
|
52
|
+
message: string,
|
|
53
|
+
readonly status?: number,
|
|
54
|
+
readonly oauthError?: string,
|
|
55
|
+
) {
|
|
56
|
+
super(message);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Stable jitter in [0, 60 s] derived from a per-account seed. */
|
|
61
|
+
export function grokRefreshJitterMs(seed: string): number {
|
|
62
|
+
const digest = createHash("sha256").update(`grok-refresh:${seed}`).digest();
|
|
63
|
+
|
|
64
|
+
return digest.readUInt32BE(0) % (GROK_REFRESH_JITTER_MAX_MS + 1);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** True once `expiresAt - 300 s - jitter(userId)` is at or before `now`. */
|
|
68
|
+
export function grokNeedsRefresh(credentials: GrokCredentials, now: number = Date.now()): boolean {
|
|
69
|
+
const expiresAt = Date.parse(credentials.expiresAt);
|
|
70
|
+
|
|
71
|
+
if (!Number.isFinite(expiresAt)) return true;
|
|
72
|
+
|
|
73
|
+
return expiresAt - GROK_REFRESH_MARGIN_MS - grokRefreshJitterMs(credentials.userId) <= now;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function abortable<T>(promise: Promise<T>, signal: AbortSignal | undefined): Promise<T> {
|
|
77
|
+
if (!signal) return promise;
|
|
78
|
+
|
|
79
|
+
if (signal.aborted) return Promise.reject(signal.reason);
|
|
80
|
+
|
|
81
|
+
return new Promise<T>((resolve, reject) => {
|
|
82
|
+
const onAbort = (): void => reject(signal.reason);
|
|
83
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
84
|
+
promise.then(
|
|
85
|
+
(value) => {
|
|
86
|
+
signal.removeEventListener("abort", onAbort);
|
|
87
|
+
resolve(value);
|
|
88
|
+
},
|
|
89
|
+
(cause: unknown) => {
|
|
90
|
+
signal.removeEventListener("abort", onAbort);
|
|
91
|
+
reject(cause);
|
|
92
|
+
},
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export type GrokPersistCredentials = (next: GrokCredentials) => Promise<void>;
|
|
98
|
+
|
|
99
|
+
export interface GrokRefreshCoordinatorOptions {
|
|
100
|
+
fetch?: GrokFetch;
|
|
101
|
+
now?: () => number;
|
|
102
|
+
/** Persistence used by the `GrokRefresher` port methods (`ensureFresh`, `refreshAfterUnauthorized`). */
|
|
103
|
+
persist?: (tokenId: number, next: GrokCredentials) => Promise<void>;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export class GrokRefreshCoordinator implements GrokRefresher {
|
|
107
|
+
private readonly fetchImpl: GrokFetch;
|
|
108
|
+
private readonly now: () => number;
|
|
109
|
+
private readonly persistPort: GrokRefreshCoordinatorOptions["persist"];
|
|
110
|
+
private readonly inflight = new Map<number, Promise<GrokCredentials>>();
|
|
111
|
+
// Last credentials this process persisted per token. A caller holding an older snapshot must not
|
|
112
|
+
// present a refresh token the IdP has already rotated away (that reads as invalid_grant).
|
|
113
|
+
private readonly latest = new Map<number, GrokCredentials>();
|
|
114
|
+
|
|
115
|
+
constructor(options: GrokRefreshCoordinatorOptions = {}) {
|
|
116
|
+
this.fetchImpl = options.fetch ?? globalThis.fetch;
|
|
117
|
+
this.now = options.now ?? Date.now;
|
|
118
|
+
this.persistPort = options.persist;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
needsRefresh(credentials: GrokCredentials, now: number = this.now()): boolean {
|
|
122
|
+
return grokNeedsRefresh(credentials, now);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Refreshes `tokenId`, coalescing concurrent calls into one HTTP request. Resolves only after
|
|
127
|
+
* `persist` has stored the new credentials.
|
|
128
|
+
*/
|
|
129
|
+
refresh(
|
|
130
|
+
tokenId: number,
|
|
131
|
+
credentials: GrokCredentials,
|
|
132
|
+
persist: GrokPersistCredentials,
|
|
133
|
+
): Promise<GrokCredentials> {
|
|
134
|
+
const existing = this.inflight.get(tokenId);
|
|
135
|
+
|
|
136
|
+
if (existing) return existing;
|
|
137
|
+
|
|
138
|
+
const promise = this.doRefresh(tokenId, this.newest(tokenId, credentials), persist).finally(
|
|
139
|
+
() => this.inflight.delete(tokenId),
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
this.inflight.set(tokenId, promise);
|
|
143
|
+
|
|
144
|
+
return promise;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async ensureFresh(token: GrokTokenRef, signal?: AbortSignal): Promise<GrokCredentials> {
|
|
148
|
+
const current = this.newest(token.tokenId, token.credentials);
|
|
149
|
+
|
|
150
|
+
if (!this.needsRefresh(current)) return current;
|
|
151
|
+
|
|
152
|
+
return abortable(this.refresh(token.tokenId, current, this.portPersist(token.tokenId)), signal);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async refreshAfterUnauthorized(
|
|
156
|
+
token: GrokTokenRef,
|
|
157
|
+
signal?: AbortSignal,
|
|
158
|
+
): Promise<GrokRefreshResult> {
|
|
159
|
+
const inflight = this.inflight.get(token.tokenId);
|
|
160
|
+
const latest = this.latest.get(token.tokenId);
|
|
161
|
+
|
|
162
|
+
// Another caller already replaced the rejected access token: reuse it instead of refreshing.
|
|
163
|
+
if (
|
|
164
|
+
!inflight &&
|
|
165
|
+
latest &&
|
|
166
|
+
latest.accessToken !== token.credentials.accessToken &&
|
|
167
|
+
!this.needsRefresh(latest)
|
|
168
|
+
)
|
|
169
|
+
return { status: "refreshed", credentials: latest };
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
const credentials = await abortable(
|
|
173
|
+
this.refresh(token.tokenId, token.credentials, this.portPersist(token.tokenId)),
|
|
174
|
+
signal,
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
return { status: "refreshed", credentials };
|
|
178
|
+
} catch (error) {
|
|
179
|
+
if (error instanceof GrokReauthRequiredError)
|
|
180
|
+
return { status: "reauth_required", reason: error.oauthError };
|
|
181
|
+
throw error;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private portPersist(tokenId: number): GrokPersistCredentials {
|
|
186
|
+
const persist = this.persistPort;
|
|
187
|
+
|
|
188
|
+
if (!persist)
|
|
189
|
+
return () =>
|
|
190
|
+
Promise.reject(new Error("GrokRefreshCoordinator was created without a persist port"));
|
|
191
|
+
|
|
192
|
+
return (next) => persist(tokenId, next);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private newest(tokenId: number, credentials: GrokCredentials): GrokCredentials {
|
|
196
|
+
const latest = this.latest.get(tokenId);
|
|
197
|
+
|
|
198
|
+
if (!latest || latest.userId !== credentials.userId) return credentials;
|
|
199
|
+
|
|
200
|
+
return Date.parse(latest.expiresAt) > Date.parse(credentials.expiresAt) ? latest : credentials;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private async doRefresh(
|
|
204
|
+
tokenId: number,
|
|
205
|
+
credentials: GrokCredentials,
|
|
206
|
+
persist: GrokPersistCredentials,
|
|
207
|
+
): Promise<GrokCredentials> {
|
|
208
|
+
const { tokenEndpoint } = await getGrokOAuthEndpoints({
|
|
209
|
+
fetch: this.fetchImpl,
|
|
210
|
+
now: this.now,
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
let response: Response;
|
|
214
|
+
|
|
215
|
+
try {
|
|
216
|
+
response = await this.fetchImpl(tokenEndpoint, {
|
|
217
|
+
method: "POST",
|
|
218
|
+
// oidc/protocol.rs:493-512: form body only; the shared client contributes the UA.
|
|
219
|
+
headers: {
|
|
220
|
+
"user-agent": grokShellUserAgent(),
|
|
221
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
222
|
+
accept: "application/json",
|
|
223
|
+
},
|
|
224
|
+
body: new URLSearchParams([
|
|
225
|
+
["grant_type", "refresh_token"],
|
|
226
|
+
["refresh_token", credentials.refreshToken],
|
|
227
|
+
["client_id", credentials.clientId],
|
|
228
|
+
]),
|
|
229
|
+
signal: AbortSignal.timeout(REFRESH_TIMEOUT_MS),
|
|
230
|
+
});
|
|
231
|
+
} catch (error) {
|
|
232
|
+
const reason = error instanceof Error ? error.name : "unknown";
|
|
233
|
+
logger.warn(`grok refresh network failure token=${tokenId} reason=${reason}`);
|
|
234
|
+
throw new GrokRefreshTransientError(`Grok refresh request failed (${reason})`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (!response.ok) {
|
|
238
|
+
const oauthError = await readGrokOAuthErrorCode(response);
|
|
239
|
+
logger.warn(
|
|
240
|
+
`grok refresh failed token=${tokenId} status=${response.status} code=${oauthError ?? "unknown"}`,
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
// oidc/protocol.rs:413-430: 5xx and 429 are always transient; otherwise only a terminal code
|
|
244
|
+
// ends the token.
|
|
245
|
+
if (
|
|
246
|
+
response.status < 500 &&
|
|
247
|
+
response.status !== 429 &&
|
|
248
|
+
oauthError !== undefined &&
|
|
249
|
+
TERMINAL_ERROR_CODES.has(oauthError)
|
|
250
|
+
)
|
|
251
|
+
throw new GrokReauthRequiredError(oauthError, response.status);
|
|
252
|
+
throw new GrokRefreshTransientError(
|
|
253
|
+
`Grok refresh failed: HTTP ${response.status} ${oauthError ?? "unknown"}`,
|
|
254
|
+
response.status,
|
|
255
|
+
oauthError,
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
let body: unknown;
|
|
260
|
+
|
|
261
|
+
try {
|
|
262
|
+
body = await response.json();
|
|
263
|
+
} catch {
|
|
264
|
+
throw new GrokRefreshTransientError("Grok refresh response is not JSON", response.status);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const tokens = GrokTokenResponse.safeParse(body);
|
|
268
|
+
|
|
269
|
+
if (!tokens.success)
|
|
270
|
+
throw new GrokRefreshTransientError(
|
|
271
|
+
"Grok refresh response failed validation",
|
|
272
|
+
response.status,
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
const rotated = Boolean(tokens.data.refresh_token);
|
|
276
|
+
const exp = decodeGrokJwt(tokens.data.access_token)?.payload.exp;
|
|
277
|
+
|
|
278
|
+
const expiresIn = tokens.data.expires_in;
|
|
279
|
+
|
|
280
|
+
const expiresAtMs =
|
|
281
|
+
expiresIn !== undefined && expiresIn !== null
|
|
282
|
+
? this.now() + expiresIn * 1000
|
|
283
|
+
: exp !== undefined
|
|
284
|
+
? exp * 1000
|
|
285
|
+
: undefined;
|
|
286
|
+
|
|
287
|
+
if (expiresAtMs === undefined || !Number.isFinite(expiresAtMs))
|
|
288
|
+
throw new GrokRefreshTransientError("Grok refresh response has no expiry", response.status);
|
|
289
|
+
|
|
290
|
+
const next = GrokCredentials.safeParse({
|
|
291
|
+
...credentials,
|
|
292
|
+
accessToken: tokens.data.access_token,
|
|
293
|
+
// oidc/refresh.rs:260-264: keep the old refresh token when the IdP did not rotate it.
|
|
294
|
+
refreshToken: tokens.data.refresh_token ?? credentials.refreshToken,
|
|
295
|
+
expiresAt: new Date(expiresAtMs).toISOString(),
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
if (!next.success) {
|
|
299
|
+
logger.error(
|
|
300
|
+
`grok refresh returned unusable credentials token=${tokenId} rotated=${rotated}`,
|
|
301
|
+
);
|
|
302
|
+
throw new GrokRefreshTransientError(
|
|
303
|
+
"Grok refresh returned an access token that is not an auth.x.ai at+jwt",
|
|
304
|
+
response.status,
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
try {
|
|
309
|
+
await persist(next.data);
|
|
310
|
+
} catch (error) {
|
|
311
|
+
// A rotated refresh token that is not persisted is lost: the next refresh with the old one
|
|
312
|
+
// fails with invalid_grant. Keep it in memory so this process can still use it.
|
|
313
|
+
if (rotated) this.latest.set(tokenId, next.data);
|
|
314
|
+
logger.error(
|
|
315
|
+
`grok refresh persist FAILED token=${tokenId} rotated=${rotated} err=${error instanceof Error ? error.name : "unknown"}`,
|
|
316
|
+
);
|
|
317
|
+
throw error;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
this.latest.set(tokenId, next.data);
|
|
321
|
+
logger.info(`grok token ${tokenId} refreshed (rotated=${rotated})`);
|
|
322
|
+
|
|
323
|
+
return next.data;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { GROK_OAUTH_CLIENT_ID, GROK_OAUTH_SCOPES } from "./grok-constants";
|
|
4
|
+
import {
|
|
5
|
+
GrokApiImageModelCatalog,
|
|
6
|
+
GrokApiLanguageModelCatalog,
|
|
7
|
+
GrokBillingConfig,
|
|
8
|
+
GrokCredentials,
|
|
9
|
+
GrokImageResponse,
|
|
10
|
+
GrokModelsResponse,
|
|
11
|
+
GrokSettings,
|
|
12
|
+
GrokSttResponse,
|
|
13
|
+
GrokTtsRequest,
|
|
14
|
+
GrokUserSubscription,
|
|
15
|
+
GrokVideoStatus,
|
|
16
|
+
decodeGrokJwt,
|
|
17
|
+
grokCatalogModels,
|
|
18
|
+
grokVideoUrl,
|
|
19
|
+
isGrokAccessToken,
|
|
20
|
+
} from "./grok-types";
|
|
21
|
+
|
|
22
|
+
function segment(value: unknown): string {
|
|
23
|
+
return Buffer.from(JSON.stringify(value)).toString("base64url");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function fakeJwt(header: Record<string, unknown>, payload: Record<string, unknown>): string {
|
|
27
|
+
return `${segment(header)}.${segment(payload)}.c2lnbmF0dXJl`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const validJwt = fakeJwt(
|
|
31
|
+
{ alg: "RS256", typ: "at+jwt" },
|
|
32
|
+
{ iss: "https://auth.x.ai", sub: "user", exp: 2_000_000_000 },
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const credentials = {
|
|
36
|
+
authMode: "oauth",
|
|
37
|
+
accessToken: validJwt,
|
|
38
|
+
refreshToken: "refresh",
|
|
39
|
+
expiresAt: "2026-10-16T09:00:00Z",
|
|
40
|
+
issuer: "https://auth.x.ai",
|
|
41
|
+
clientId: GROK_OAUTH_CLIENT_ID,
|
|
42
|
+
scopes: [...GROK_OAUTH_SCOPES],
|
|
43
|
+
userId: "19bfa1b2-a163-9e0c-8de1-832a73e2695e",
|
|
44
|
+
sessionId: "22222222-2222-4222-8222-222222222222",
|
|
45
|
+
agentId: "33333333-3333-4333-8333-333333333333",
|
|
46
|
+
subscriptionTier: "SuperGrokPlus",
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
describe("GrokCredentials", () => {
|
|
50
|
+
it("accepts a fabricated at+jwt from auth.x.ai", () => {
|
|
51
|
+
expect(GrokCredentials.parse(credentials)).toEqual(credentials);
|
|
52
|
+
expect(decodeGrokJwt(validJwt)?.payload.iss).toBe("https://auth.x.ai");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("rejects an API key", () => {
|
|
56
|
+
expect(isGrokAccessToken("xai-0123456789abcdefghijklmnop")).toBe(false);
|
|
57
|
+
expect(GrokCredentials.safeParse({ ...credentials, accessToken: "xai-abc" }).success).toBe(
|
|
58
|
+
false,
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("rejects a JWT from another issuer or of another type", () => {
|
|
63
|
+
const foreign = fakeJwt({ alg: "RS256", typ: "at+jwt" }, { iss: "https://accounts.example" });
|
|
64
|
+
const idToken = fakeJwt({ alg: "RS256", typ: "JWT" }, { iss: "https://auth.x.ai" });
|
|
65
|
+
expect(GrokCredentials.safeParse({ ...credentials, accessToken: foreign }).success).toBe(false);
|
|
66
|
+
expect(GrokCredentials.safeParse({ ...credentials, accessToken: idToken }).success).toBe(false);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("rejects malformed JWT segments", () => {
|
|
70
|
+
expect(isGrokAccessToken("a.b")).toBe(false);
|
|
71
|
+
expect(isGrokAccessToken("!!!.???.sig")).toBe(false);
|
|
72
|
+
expect(isGrokAccessToken(`${segment("x")}.${segment("y")}.sig`)).toBe(false);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("requires userId, an RFC 3339 expiry and the auth.x.ai issuer", () => {
|
|
76
|
+
const { userId: _userId, ...withoutUser } = credentials;
|
|
77
|
+
expect(GrokCredentials.safeParse(withoutUser).success).toBe(false);
|
|
78
|
+
expect(GrokCredentials.safeParse({ ...credentials, expiresAt: "tomorrow" }).success).toBe(
|
|
79
|
+
false,
|
|
80
|
+
);
|
|
81
|
+
expect(GrokCredentials.safeParse({ ...credentials, expiresAt: 1_800_000_000 }).success).toBe(
|
|
82
|
+
false,
|
|
83
|
+
);
|
|
84
|
+
expect(
|
|
85
|
+
GrokCredentials.safeParse({ ...credentials, issuer: "https://auth.example" }).success,
|
|
86
|
+
).toBe(false);
|
|
87
|
+
expect(GrokCredentials.safeParse({ ...credentials, authMode: "api_key" }).success).toBe(false);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe("Grok proxy response schemas (U0 shapes, redacted)", () => {
|
|
92
|
+
it("parses /billing?format=credits", () => {
|
|
93
|
+
const billing = GrokBillingConfig.parse({
|
|
94
|
+
config: {
|
|
95
|
+
currentPeriod: {
|
|
96
|
+
type: "USAGE_PERIOD_TYPE_WEEKLY",
|
|
97
|
+
start: "2026-10-12T00:00:00Z",
|
|
98
|
+
end: "2026-10-19T00:00:00Z",
|
|
99
|
+
},
|
|
100
|
+
creditUsagePercent: 1,
|
|
101
|
+
productUsage: [{ product: "GrokBot", usagePercent: 1 }],
|
|
102
|
+
isUnifiedBillingUser: true,
|
|
103
|
+
prepaidBalance: { val: 0 },
|
|
104
|
+
topUpMethod: "TOP_UP_METHOD_UNSPECIFIED",
|
|
105
|
+
billingPeriodStart: "2026-10-12T00:00:00Z",
|
|
106
|
+
billingPeriodEnd: "2026-10-19T00:00:00Z",
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
expect(billing.config.creditUsagePercent).toBe(1);
|
|
110
|
+
expect(billing.config).not.toHaveProperty("onDemandEnabled");
|
|
111
|
+
expect(billing.config.topUpMethod).toBe("TOP_UP_METHOD_UNSPECIFIED");
|
|
112
|
+
expect(GrokBillingConfig.safeParse({ config: { credit_usage_percent: 1 } }).success).toBe(
|
|
113
|
+
false,
|
|
114
|
+
);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("parses /settings with on_demand_enabled null", () => {
|
|
118
|
+
const settings = GrokSettings.parse({
|
|
119
|
+
min_client_version: "1.0.13",
|
|
120
|
+
force_update: true,
|
|
121
|
+
default_model: "grok-4.7",
|
|
122
|
+
subscription_tier_display: "SuperGrok Plus",
|
|
123
|
+
on_demand_enabled: null,
|
|
124
|
+
image_gen_enabled: true,
|
|
125
|
+
video_gen_enabled: true,
|
|
126
|
+
voice_mode_enabled: true,
|
|
127
|
+
accept_request_encodings: ["zstd"],
|
|
128
|
+
tips: ["passthrough"],
|
|
129
|
+
});
|
|
130
|
+
expect(settings.on_demand_enabled).toBeNull();
|
|
131
|
+
expect(settings.tips).toEqual(["passthrough"]);
|
|
132
|
+
expect(GrokSettings.safeParse({ on_demand_enabled: "yes" }).success).toBe(false);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("parses /user?include=subscription", () => {
|
|
136
|
+
const user = GrokUserSubscription.parse({
|
|
137
|
+
userId: "19bfa1b2-a163-9e0c-8de1-832a73e2695e",
|
|
138
|
+
email: "user@example.com",
|
|
139
|
+
subscriptionTier: "SuperGrokPlus",
|
|
140
|
+
hasGrokCodeAccess: true,
|
|
141
|
+
teamId: null,
|
|
142
|
+
});
|
|
143
|
+
expect(user.subscriptionTier).toBe("SuperGrokPlus");
|
|
144
|
+
expect(user.codingDataRetentionOptOut).toBeUndefined();
|
|
145
|
+
expect(
|
|
146
|
+
GrokUserSubscription.parse({ userId: user.userId, codingDataRetentionOptOut: true })
|
|
147
|
+
.codingDataRetentionOptOut,
|
|
148
|
+
).toBe(true);
|
|
149
|
+
expect(
|
|
150
|
+
GrokUserSubscription.safeParse({ userId: user.userId, codingDataRetentionOptOut: "yes" })
|
|
151
|
+
.success,
|
|
152
|
+
).toBe(false);
|
|
153
|
+
expect(GrokUserSubscription.safeParse({ subscriptionTier: "x" }).success).toBe(false);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("parses proxy /models", () => {
|
|
157
|
+
const efforts = ["xhigh", "high", "medium", "low"].map((id) => ({
|
|
158
|
+
id,
|
|
159
|
+
value: id,
|
|
160
|
+
label: id,
|
|
161
|
+
description: id,
|
|
162
|
+
default: id === "high",
|
|
163
|
+
}));
|
|
164
|
+
const models = GrokModelsResponse.parse({
|
|
165
|
+
object: "list",
|
|
166
|
+
data: ["grok-4.7", "grok-4.7-build-fast", "grok-4.6", "grok-4.5"].map((id) => ({
|
|
167
|
+
id,
|
|
168
|
+
object: "model",
|
|
169
|
+
context_window: 256000,
|
|
170
|
+
context_windows: [256000, 500000],
|
|
171
|
+
api_backend: "responses",
|
|
172
|
+
reasoning_effort: "high",
|
|
173
|
+
supports_reasoning_effort: true,
|
|
174
|
+
reasoning_efforts: efforts,
|
|
175
|
+
})),
|
|
176
|
+
});
|
|
177
|
+
expect(models.data.map((model) => model.id)).toEqual([
|
|
178
|
+
"grok-4.7",
|
|
179
|
+
"grok-4.7-build-fast",
|
|
180
|
+
"grok-4.6",
|
|
181
|
+
"grok-4.5",
|
|
182
|
+
]);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
describe("api.x.ai schemas", () => {
|
|
187
|
+
it("parses the language-model price catalog under either envelope", () => {
|
|
188
|
+
const model = {
|
|
189
|
+
id: "grok-4.7",
|
|
190
|
+
aliases: ["grok-4.7-latest"],
|
|
191
|
+
prompt_text_token_price: 20000,
|
|
192
|
+
cached_prompt_text_token_price: 5000,
|
|
193
|
+
completion_text_token_price: 60000,
|
|
194
|
+
prompt_image_token_price: 20000,
|
|
195
|
+
prompt_text_token_price_long_context: 40000,
|
|
196
|
+
long_context_threshold: 200000,
|
|
197
|
+
};
|
|
198
|
+
for (const envelope of [{ models: [model] }, { data: [model], object: "list" }]) {
|
|
199
|
+
const catalog = GrokApiLanguageModelCatalog.parse(envelope);
|
|
200
|
+
expect(grokCatalogModels(catalog)[0]?.prompt_text_token_price).toBe(20000);
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("parses the image-model price catalog", () => {
|
|
205
|
+
const catalog = GrokApiImageModelCatalog.parse({
|
|
206
|
+
models: [
|
|
207
|
+
{
|
|
208
|
+
id: "grok-imagine-image-quality",
|
|
209
|
+
image_price: 500000000,
|
|
210
|
+
pricing: [{ quality: "quality", resolution: "1k", price_per_image: 500000000 }],
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
});
|
|
214
|
+
expect(grokCatalogModels(catalog)[0]?.pricing?.[0]?.price_per_image).toBe(500000000);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it("parses image, video, STT and TTS shapes", () => {
|
|
218
|
+
expect(
|
|
219
|
+
GrokImageResponse.parse({
|
|
220
|
+
data: [{ b64_json: "AAAA", mime_type: "image/jpeg" }],
|
|
221
|
+
usage: { cost_in_usd_ticks: 500000000 },
|
|
222
|
+
}).usage?.cost_in_usd_ticks,
|
|
223
|
+
).toBe(500000000);
|
|
224
|
+
expect(GrokImageResponse.safeParse({ data: [] }).success).toBe(false);
|
|
225
|
+
expect(
|
|
226
|
+
grokVideoUrl(GrokVideoStatus.parse({ status: "done", video: { url: "https://v/1.mp4" } })),
|
|
227
|
+
).toBe("https://v/1.mp4");
|
|
228
|
+
expect(
|
|
229
|
+
grokVideoUrl(
|
|
230
|
+
GrokVideoStatus.parse({
|
|
231
|
+
status: "done",
|
|
232
|
+
video: { url: null, file_output: { public_url: "https://v/2.mp4" } },
|
|
233
|
+
}),
|
|
234
|
+
),
|
|
235
|
+
).toBe("https://v/2.mp4");
|
|
236
|
+
// @ai-sdk/xai never reads a top-level file_output.
|
|
237
|
+
expect(
|
|
238
|
+
grokVideoUrl(
|
|
239
|
+
GrokVideoStatus.parse({ status: "done", file_output: { public_url: "https://v/3.mp4" } }),
|
|
240
|
+
),
|
|
241
|
+
).toBeUndefined();
|
|
242
|
+
expect(grokVideoUrl(GrokVideoStatus.parse({ status: "pending" }))).toBeUndefined();
|
|
243
|
+
expect(GrokSttResponse.parse({ text: "", language: "", duration: 1 }).duration).toBe(1);
|
|
244
|
+
expect(
|
|
245
|
+
GrokTtsRequest.safeParse({ text: "hi", voice: "ara", language: "en", format: "mp3" })
|
|
246
|
+
.success,
|
|
247
|
+
).toBe(true);
|
|
248
|
+
expect(GrokTtsRequest.safeParse({ text: "hi", voice: "ara", format: "mp3" }).success).toBe(
|
|
249
|
+
false,
|
|
250
|
+
);
|
|
251
|
+
});
|
|
252
|
+
});
|