@cartanova/qgrid-cli 2.10.9 → 2.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +22 -20
- package/bundle/dist/application/qgrid/qgrid-run-lifecycle.js +43 -34
- 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 -69
- package/bundle/dist/application/request-log/request-log.types.js +8 -2
- 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 +40 -16
- package/bundle/dist/application/sonamu.generated.sso.js +2 -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 +2 -1
- package/bundle/dist/migrations/20260807133553_manual__backfill_request_log_cost.js +8 -6
- package/bundle/dist/migrations/20261018215031_alter_request_logs_add1_alter1.js +15 -0
- 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 +33 -0
- package/bundle/src/application/qgrid/qgrid-run-lifecycle.ts +87 -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.entity.json +8 -0
- package/bundle/src/application/request-log/request-log.model.test.ts +111 -4
- package/bundle/src/application/request-log/request-log.model.ts +279 -145
- package/bundle/src/application/request-log/request-log.types.ts +17 -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 +135 -0
- package/bundle/src/application/sonamu.generated.sso.ts +3 -0
- package/bundle/src/application/sonamu.generated.ts +29 -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 +24 -2
- package/bundle/src/migrations/20260807133553_manual__backfill_request_log_cost.ts +16 -8
- package/bundle/src/migrations/20261018215031_alter_request_logs_add1_alter1.ts +15 -0
- 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-BA7uQ-6c.js +31 -0
- package/bundle/web-dist/client/assets/index-Be2qCZmI.css +1 -0
- package/bundle/web-dist/client/assets/logs-C47OJGEI.js +1 -0
- package/bundle/web-dist/client/assets/monit-BS9no0dU.js +4 -0
- package/bundle/web-dist/client/assets/plus-sdACTfH0.js +1 -0
- package/bundle/web-dist/client/assets/refresh-cw-CY8o_1cT.js +1 -0
- package/bundle/web-dist/client/assets/request-model-BRtFuZvj.js +1 -0
- package/bundle/web-dist/client/assets/routes-kqgkUxE8.js +5 -0
- package/bundle/web-dist/client/assets/{services.generated-BsieU37H.js → services.generated-CefiDXEv.js} +5 -5
- package/bundle/web-dist/client/assets/settings-DNDvFGSw.js +1 -0
- package/bundle/web-dist/client/assets/show-u8Np69q3.js +2 -0
- package/bundle/web-dist/client/assets/tokens-QvP410NW.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-Bzdh28Ws.js +1 -0
- package/bundle/web-dist/client/assets/use-update-token-mutation-CfJKaxmR.js +1 -0
- package/bundle/web-dist/client/assets/vendor-react-CUFFrVN9.js +244 -0
- package/bundle/web-dist/client/assets/vendor-tanstack-Bf_jag9G.js +1 -0
- package/bundle/web-dist/client/index.html +6 -6
- package/bundle/web-dist/server/assets/clsx-D_N5PN_2.js +16 -0
- package/bundle/web-dist/server/assets/{lazyRouteComponent-Dtd0NR4r.js → lazyRouteComponent-D8iTIHyn.js} +129 -2
- package/bundle/web-dist/server/assets/logs-B3OTd9hy.js +825 -0
- package/bundle/web-dist/server/assets/{monit-DvzribSE.js → monit-B2hqorPe.js} +385 -126
- package/bundle/web-dist/server/assets/plus-DwG7h9kq.js +28 -0
- package/bundle/web-dist/server/assets/{react-dom-B4a6-uuR.js → react-dom-BIYQzBqG.js} +2 -129
- package/bundle/web-dist/server/assets/refresh-cw-mMBkwOq0.js +44 -0
- package/bundle/web-dist/server/assets/{request-model-CbqfnQOJ.js → request-model-I6OdODSH.js} +15 -6
- package/bundle/web-dist/server/assets/{routes-BAPWTizT.js → routes-B95YlUv2.js} +760 -213
- package/bundle/web-dist/server/assets/{services.generated-CEbGjsI0.js → services.generated-DQATJ6qf.js} +12821 -12194
- package/bundle/web-dist/server/assets/settings-C3gbGk0m.js +830 -0
- package/bundle/web-dist/server/assets/{show-CxVZTAdv.js → show-Bxaj3rhH.js} +1355 -419
- package/bundle/web-dist/server/assets/tokens-CZjH3cB7.js +1038 -0
- package/bundle/web-dist/server/assets/total-cost-fTE9Xy82.js +18 -0
- package/bundle/web-dist/server/assets/use-oauth-login-flow-CKDHgc3U.js +2379 -0
- package/bundle/web-dist/server/assets/{use-update-token-mutation-CfYmk_4_.js → use-update-token-mutation-BEbgOzaa.js} +1 -1
- package/bundle/web-dist/server/entry-server.generated.js +4303 -22810
- 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 +24 -1
- 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/request-log-run-lifecycle.md +1 -1
- package/skills/qgrid/references/sonamu-api-web-flow.md +1 -1
- 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-CrXKu_J8.js +0 -106
- package/bundle/web-dist/client/assets/logs-B2SmWJXA.js +0 -1
- package/bundle/web-dist/client/assets/monit-D-JwXXoA.js +0 -1
- package/bundle/web-dist/client/assets/plus-B62CuqT8.js +0 -1
- package/bundle/web-dist/client/assets/refresh-cw-NNcU0Cmq.js +0 -1
- package/bundle/web-dist/client/assets/request-model-CojtinqK.js +0 -1
- package/bundle/web-dist/client/assets/routes-C5r4gTvY.js +0 -5
- package/bundle/web-dist/client/assets/settings-D3tDSAck.js +0 -1
- package/bundle/web-dist/client/assets/show-Cah_kUKC.js +0 -2
- package/bundle/web-dist/client/assets/tokens-DU2Iyxxd.js +0 -1
- package/bundle/web-dist/client/assets/use-oauth-login-flow-D0UaR9_e.js +0 -1
- package/bundle/web-dist/client/assets/use-update-token-mutation-DAq0F8p-.js +0 -1
- package/bundle/web-dist/client/assets/vendor-react-DH4kUfK2.js +0 -9
- package/bundle/web-dist/client/assets/vendor-tanstack-DJRQAbrY.js +0 -1
- package/bundle/web-dist/server/assets/logs-l91avYif.js +0 -509
- package/bundle/web-dist/server/assets/plus-KZrECewT.js +0 -19
- package/bundle/web-dist/server/assets/refresh-cw-BND93JrF.js +0 -23
- package/bundle/web-dist/server/assets/settings-BZR5HSAL.js +0 -461
- package/bundle/web-dist/server/assets/tokens-D1hb4eKk.js +0 -448
- package/bundle/web-dist/server/assets/use-oauth-login-flow-Cvdy0zF8.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/{preload-helper-ca-nBW7U.js → preload-helper-DWTEM3RW.js} +0 -0
- /package/bundle/web-dist/server/assets/{cost-eEHeTRT5.js → cost-BC9yzyA9.js} +0 -0
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import { type JsonValue } from "../common/provider-types";
|
|
4
|
+
import { GROK_AUTH_ISSUER } from "./grok-constants";
|
|
5
|
+
|
|
6
|
+
// External data (stored credentials, proxy and api.x.ai JSON) is validated with these schemas,
|
|
7
|
+
// never cast. Response schemas are loose: unknown keys pass through so the proxy can add
|
|
8
|
+
// fields without breaking qgrid.
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Credentials
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
const JwtHeader = z.looseObject({ typ: z.string(), alg: z.string().optional() });
|
|
15
|
+
|
|
16
|
+
const JwtPayload = z.looseObject({
|
|
17
|
+
iss: z.string(),
|
|
18
|
+
sub: z.string().optional(),
|
|
19
|
+
exp: z.number().optional(),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export interface DecodedGrokJwt {
|
|
23
|
+
header: z.infer<typeof JwtHeader>;
|
|
24
|
+
payload: z.infer<typeof JwtPayload>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const BASE64URL_SEGMENT = /^[A-Za-z0-9_-]+$/;
|
|
28
|
+
|
|
29
|
+
function decodeSegment(segment: string): JsonValue | undefined {
|
|
30
|
+
if (!BASE64URL_SEGMENT.test(segment)) return undefined;
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
// JSON.parse without a reviver yields only JSON values.
|
|
34
|
+
const decoded: JsonValue = JSON.parse(Buffer.from(segment, "base64url").toString("utf8"));
|
|
35
|
+
|
|
36
|
+
return decoded;
|
|
37
|
+
} catch {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Decodes a compact JWT's header and payload. The signature is never verified. */
|
|
43
|
+
export function decodeGrokJwt(token: string): DecodedGrokJwt | undefined {
|
|
44
|
+
const parts = token.split(".");
|
|
45
|
+
|
|
46
|
+
if (parts.length !== 3 || !parts[0] || !parts[1] || !parts[2]) return undefined;
|
|
47
|
+
const header = JwtHeader.safeParse(decodeSegment(parts[0]));
|
|
48
|
+
const payload = JwtPayload.safeParse(decodeSegment(parts[1]));
|
|
49
|
+
|
|
50
|
+
if (!header.success || !payload.success) return undefined;
|
|
51
|
+
|
|
52
|
+
return { header: header.data, payload: payload.data };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** An RFC 9068 access token (`typ: at+jwt`) issued by auth.x.ai. API keys are rejected. */
|
|
56
|
+
export function isGrokAccessToken(token: string): boolean {
|
|
57
|
+
const decoded = decodeGrokJwt(token);
|
|
58
|
+
|
|
59
|
+
if (!decoded) return false;
|
|
60
|
+
const typ = decoded.header.typ.toLowerCase();
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
(typ === "at+jwt" || typ === "application/at+jwt") && decoded.payload.iss === GROK_AUTH_ISSUER
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// xAI ids do not always carry RFC 9562 version/variant bits (observed x-request-id
|
|
68
|
+
// `19bfa1b2-a163-9e0c-...`), so ids are checked for the 8-4-4-4-12 hex shape only.
|
|
69
|
+
const GrokId = z.guid();
|
|
70
|
+
|
|
71
|
+
export const GrokCredentials = z.object({
|
|
72
|
+
authMode: z.literal("oauth"),
|
|
73
|
+
accessToken: z.string().refine(isGrokAccessToken, {
|
|
74
|
+
message: "accessToken must be an at+jwt access token issued by https://auth.x.ai",
|
|
75
|
+
}),
|
|
76
|
+
refreshToken: z.string().min(1),
|
|
77
|
+
expiresAt: z.iso.datetime({ offset: true }),
|
|
78
|
+
issuer: z.literal(GROK_AUTH_ISSUER),
|
|
79
|
+
clientId: z.string().min(1),
|
|
80
|
+
scopes: z.array(z.string().min(1)),
|
|
81
|
+
userId: GrokId,
|
|
82
|
+
sessionId: GrokId,
|
|
83
|
+
agentId: GrokId,
|
|
84
|
+
subscriptionTier: z.string().optional(),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
export type GrokCredentials = z.infer<typeof GrokCredentials>;
|
|
88
|
+
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
// Chat proxy JSON (cli-chat-proxy.grok.com/v1)
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
/** `GET /billing?format=credits` (camelCase, no on-demand field; U0 item 3). */
|
|
94
|
+
export const GrokBillingConfig = z.looseObject({
|
|
95
|
+
config: z.looseObject({
|
|
96
|
+
creditUsagePercent: z.number(),
|
|
97
|
+
currentPeriod: z.looseObject({
|
|
98
|
+
type: z.string(),
|
|
99
|
+
start: z.string(),
|
|
100
|
+
end: z.string(),
|
|
101
|
+
}),
|
|
102
|
+
prepaidBalance: z.looseObject({ val: z.union([z.number(), z.string()]) }).optional(),
|
|
103
|
+
isUnifiedBillingUser: z.boolean().optional(),
|
|
104
|
+
productUsage: z
|
|
105
|
+
.array(z.looseObject({ product: z.string(), usagePercent: z.number() }))
|
|
106
|
+
.optional(),
|
|
107
|
+
}),
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
export type GrokBillingConfig = z.infer<typeof GrokBillingConfig>;
|
|
111
|
+
|
|
112
|
+
/** `GET /settings`. `on_demand_enabled: true` excludes the token (D6); null/absent allows. */
|
|
113
|
+
export const GrokSettings = z.looseObject({
|
|
114
|
+
min_client_version: z.string().optional(),
|
|
115
|
+
force_update: z.boolean().nullish(),
|
|
116
|
+
default_model: z.string().nullish(),
|
|
117
|
+
subscription_tier_display: z.string().nullish(),
|
|
118
|
+
on_demand_enabled: z.boolean().nullish(),
|
|
119
|
+
image_gen_enabled: z.boolean().nullish(),
|
|
120
|
+
video_gen_enabled: z.boolean().nullish(),
|
|
121
|
+
voice_mode_enabled: z.boolean().nullish(),
|
|
122
|
+
accept_request_encodings: z.array(z.string()).nullish(),
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
export type GrokSettings = z.infer<typeof GrokSettings>;
|
|
126
|
+
|
|
127
|
+
/** `GET /user?include=subscription`. `userId` is the account identity key. */
|
|
128
|
+
export const GrokUserSubscription = z.looseObject({
|
|
129
|
+
userId: GrokId,
|
|
130
|
+
email: z.string().optional(),
|
|
131
|
+
subscriptionTier: z.string().nullish(),
|
|
132
|
+
hasGrokCodeAccess: z.boolean().optional(),
|
|
133
|
+
/** Zero Data Retention opt-out; `true` was observed while ZDR was on (plan §11). */
|
|
134
|
+
codingDataRetentionOptOut: z.boolean().nullish(),
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
export type GrokUserSubscription = z.infer<typeof GrokUserSubscription>;
|
|
138
|
+
|
|
139
|
+
export const GrokModelReasoningEffort = z.looseObject({
|
|
140
|
+
id: z.string(),
|
|
141
|
+
value: z.string(),
|
|
142
|
+
label: z.string().optional(),
|
|
143
|
+
default: z.boolean().optional(),
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
export const GrokProxyModel = z.looseObject({
|
|
147
|
+
id: z.string(),
|
|
148
|
+
context_window: z.number().int().positive(),
|
|
149
|
+
context_windows: z.array(z.number().int().positive()).optional(),
|
|
150
|
+
api_backend: z.string(),
|
|
151
|
+
reasoning_effort: z.string().nullish(),
|
|
152
|
+
reasoning_efforts: z.array(GrokModelReasoningEffort).optional(),
|
|
153
|
+
supports_reasoning_effort: z.boolean().optional(),
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
export type GrokProxyModel = z.infer<typeof GrokProxyModel>;
|
|
157
|
+
|
|
158
|
+
/** `GET /models` on the proxy (`{object: "list", data: [...]}`). */
|
|
159
|
+
export const GrokModelsResponse = z.looseObject({
|
|
160
|
+
object: z.string().optional(),
|
|
161
|
+
data: z.array(GrokProxyModel),
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
export type GrokModelsResponse = z.infer<typeof GrokModelsResponse>;
|
|
165
|
+
|
|
166
|
+
// ---------------------------------------------------------------------------
|
|
167
|
+
// Public API JSON (api.x.ai/v1). Prices are ticks (1e-10 USD) per token or per image.
|
|
168
|
+
// ---------------------------------------------------------------------------
|
|
169
|
+
|
|
170
|
+
const ticks = z.number().nonnegative();
|
|
171
|
+
|
|
172
|
+
export const GrokApiLanguageModel = z.looseObject({
|
|
173
|
+
id: z.string(),
|
|
174
|
+
aliases: z.array(z.string()).optional(),
|
|
175
|
+
context_length: z.number().int().positive().optional(),
|
|
176
|
+
prompt_text_token_price: ticks,
|
|
177
|
+
cached_prompt_text_token_price: ticks.optional(),
|
|
178
|
+
completion_text_token_price: ticks,
|
|
179
|
+
prompt_image_token_price: ticks.optional(),
|
|
180
|
+
prompt_text_token_price_long_context: ticks.optional(),
|
|
181
|
+
cached_prompt_text_token_price_long_context: ticks.optional(),
|
|
182
|
+
completion_text_token_price_long_context: ticks.optional(),
|
|
183
|
+
long_context_threshold: z.number().int().positive().optional(),
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
export type GrokApiLanguageModel = z.infer<typeof GrokApiLanguageModel>;
|
|
187
|
+
|
|
188
|
+
// The live envelope of /language-models was not captured; accept either list key.
|
|
189
|
+
export const GrokApiLanguageModelCatalog = z.union([
|
|
190
|
+
z.looseObject({ models: z.array(GrokApiLanguageModel) }),
|
|
191
|
+
z.looseObject({ data: z.array(GrokApiLanguageModel) }),
|
|
192
|
+
]);
|
|
193
|
+
|
|
194
|
+
export type GrokApiLanguageModelCatalog = z.infer<typeof GrokApiLanguageModelCatalog>;
|
|
195
|
+
|
|
196
|
+
export const GrokApiImageModel = z.looseObject({
|
|
197
|
+
id: z.string(),
|
|
198
|
+
aliases: z.array(z.string()).optional(),
|
|
199
|
+
image_price: ticks.optional(),
|
|
200
|
+
pricing: z
|
|
201
|
+
.array(
|
|
202
|
+
z.looseObject({
|
|
203
|
+
quality: z.string().optional(),
|
|
204
|
+
resolution: z.string().optional(),
|
|
205
|
+
price_per_image: ticks,
|
|
206
|
+
}),
|
|
207
|
+
)
|
|
208
|
+
.optional(),
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
export type GrokApiImageModel = z.infer<typeof GrokApiImageModel>;
|
|
212
|
+
|
|
213
|
+
export const GrokApiImageModelCatalog = z.union([
|
|
214
|
+
z.looseObject({ models: z.array(GrokApiImageModel) }),
|
|
215
|
+
z.looseObject({ data: z.array(GrokApiImageModel) }),
|
|
216
|
+
]);
|
|
217
|
+
|
|
218
|
+
export type GrokApiImageModelCatalog = z.infer<typeof GrokApiImageModelCatalog>;
|
|
219
|
+
|
|
220
|
+
/** Model list of either catalog, whichever envelope key the API used. */
|
|
221
|
+
export function grokCatalogModels<T>(catalog: { models: T[] } | { data: T[] }): T[] {
|
|
222
|
+
return "models" in catalog ? catalog.models : catalog.data;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const ticksUsage = z.looseObject({ cost_in_usd_ticks: z.number().int().nonnegative().optional() });
|
|
226
|
+
|
|
227
|
+
/** `POST /images/generations` and `/images/edits` with `response_format: "b64_json"`. */
|
|
228
|
+
export const GrokImageResponse = z.looseObject({
|
|
229
|
+
data: z
|
|
230
|
+
.array(
|
|
231
|
+
z.looseObject({
|
|
232
|
+
b64_json: z.string().min(1),
|
|
233
|
+
mime_type: z.string().optional(),
|
|
234
|
+
revised_prompt: z.string().nullish(),
|
|
235
|
+
respect_moderation: z.boolean().optional(),
|
|
236
|
+
}),
|
|
237
|
+
)
|
|
238
|
+
.min(1),
|
|
239
|
+
usage: ticksUsage.optional(),
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
export type GrokImageResponse = z.infer<typeof GrokImageResponse>;
|
|
243
|
+
|
|
244
|
+
/** `POST /videos/generations`. */
|
|
245
|
+
export const GrokVideoStart = z.looseObject({ request_id: z.string().min(1) });
|
|
246
|
+
|
|
247
|
+
export type GrokVideoStart = z.infer<typeof GrokVideoStart>;
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* `GET /videos/{request_id}`. The result is a URL: `video.url`, else
|
|
251
|
+
* `video.file_output.public_url` (@ai-sdk/xai xai-video-model.ts:693-694, 782-799).
|
|
252
|
+
*/
|
|
253
|
+
export const GrokVideoStatus = z.looseObject({
|
|
254
|
+
status: z.string(),
|
|
255
|
+
video: z
|
|
256
|
+
.looseObject({
|
|
257
|
+
url: z.string().nullish(),
|
|
258
|
+
duration: z.number().nullish(),
|
|
259
|
+
file_output: z.looseObject({ public_url: z.string().nullish() }).nullish(),
|
|
260
|
+
})
|
|
261
|
+
.nullish(),
|
|
262
|
+
usage: ticksUsage.nullish(),
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
export type GrokVideoStatus = z.infer<typeof GrokVideoStatus>;
|
|
266
|
+
|
|
267
|
+
export function grokVideoUrl(status: GrokVideoStatus): string | undefined {
|
|
268
|
+
return status.video?.url ?? status.video?.file_output?.public_url ?? undefined;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** `POST /stt` (multipart). */
|
|
272
|
+
export const GrokSttResponse = z.looseObject({
|
|
273
|
+
text: z.string(),
|
|
274
|
+
language: z.string().optional(),
|
|
275
|
+
duration: z.number().nonnegative().optional(),
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
export type GrokSttResponse = z.infer<typeof GrokSttResponse>;
|
|
279
|
+
|
|
280
|
+
/** `POST /tts` body; the API answers 422 without `language` (U0). */
|
|
281
|
+
export const GrokTtsRequest = z.object({
|
|
282
|
+
text: z.string().min(1),
|
|
283
|
+
voice: z.string().min(1),
|
|
284
|
+
language: z.string().min(1),
|
|
285
|
+
format: z.string().min(1),
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
export type GrokTtsRequest = z.infer<typeof GrokTtsRequest>;
|
|
289
|
+
|
|
290
|
+
// ---------------------------------------------------------------------------
|
|
291
|
+
// Ports (implemented by grok-refresh, grok-media-client, grok-voice-client)
|
|
292
|
+
// ---------------------------------------------------------------------------
|
|
293
|
+
|
|
294
|
+
export interface GrokTokenRef {
|
|
295
|
+
tokenId: number;
|
|
296
|
+
credentials: GrokCredentials;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export type GrokRefreshResult =
|
|
300
|
+
| { status: "refreshed"; credentials: GrokCredentials }
|
|
301
|
+
| { status: "reauth_required"; reason: string };
|
|
302
|
+
|
|
303
|
+
export interface GrokRefresher {
|
|
304
|
+
/** Single-flight per token; returns credentials still valid for at least the refresh margin. */
|
|
305
|
+
ensureFresh(token: GrokTokenRef, signal?: AbortSignal): Promise<GrokCredentials>;
|
|
306
|
+
/** Forced refresh after a 401; never reuses the rejected access token. */
|
|
307
|
+
refreshAfterUnauthorized(token: GrokTokenRef, signal?: AbortSignal): Promise<GrokRefreshResult>;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export interface GrokImageGenerateRequest {
|
|
311
|
+
model: string;
|
|
312
|
+
prompt: string;
|
|
313
|
+
n?: number;
|
|
314
|
+
aspectRatio?: string;
|
|
315
|
+
resolution?: string;
|
|
316
|
+
quality?: string;
|
|
317
|
+
outputFormat?: string;
|
|
318
|
+
signal?: AbortSignal;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export interface GrokImageEditRequest extends GrokImageGenerateRequest {
|
|
322
|
+
/** Data URLs or https URLs of the reference images. */
|
|
323
|
+
images: string[];
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export interface GrokVideoStartRequest {
|
|
327
|
+
model: string;
|
|
328
|
+
prompt: string;
|
|
329
|
+
duration?: number;
|
|
330
|
+
aspectRatio?: string;
|
|
331
|
+
resolution?: "480p" | "720p" | "1080p";
|
|
332
|
+
image?: string;
|
|
333
|
+
lastFrame?: string;
|
|
334
|
+
referenceImages?: string[];
|
|
335
|
+
mode?: "reference-to-video";
|
|
336
|
+
signal?: AbortSignal;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/** Per-request identity for api.x.ai Imagine calls: the bearer and the `x-grok-session-id`. */
|
|
340
|
+
export interface GrokMediaAuth {
|
|
341
|
+
bearer: string;
|
|
342
|
+
sessionId: string;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export interface GrokMediaPort {
|
|
346
|
+
generateImages(
|
|
347
|
+
auth: GrokMediaAuth,
|
|
348
|
+
request: GrokImageGenerateRequest,
|
|
349
|
+
): Promise<GrokImageResponse>;
|
|
350
|
+
editImages(auth: GrokMediaAuth, request: GrokImageEditRequest): Promise<GrokImageResponse>;
|
|
351
|
+
startVideo(auth: GrokMediaAuth, request: GrokVideoStartRequest): Promise<GrokVideoStart>;
|
|
352
|
+
getVideo(auth: GrokMediaAuth, requestId: string, signal?: AbortSignal): Promise<GrokVideoStatus>;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export interface GrokTranscribeRequest {
|
|
356
|
+
audio: Uint8Array;
|
|
357
|
+
mediaType: string;
|
|
358
|
+
model?: string;
|
|
359
|
+
language?: string;
|
|
360
|
+
signal?: AbortSignal;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export interface GrokSpeechResult {
|
|
364
|
+
audio: Uint8Array;
|
|
365
|
+
mediaType: string;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
export interface GrokVoicePort {
|
|
369
|
+
transcribe(bearer: string, request: GrokTranscribeRequest): Promise<GrokSttResponse>;
|
|
370
|
+
speak(
|
|
371
|
+
bearer: string,
|
|
372
|
+
request: GrokTtsRequest & { signal?: AbortSignal },
|
|
373
|
+
): Promise<GrokSpeechResult>;
|
|
374
|
+
}
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { GROK_STT_DEFAULT_MODEL } from "./grok-constants";
|
|
4
|
+
import { GrokHttpError } from "./grok-direct-client";
|
|
5
|
+
import { buildGrokVoiceHeaders, type GrokPlatform } from "./grok-headers";
|
|
6
|
+
import { GrokVoiceClient } from "./grok-voice-client";
|
|
7
|
+
|
|
8
|
+
const platform: GrokPlatform = { os: "macos", arch: "aarch64" };
|
|
9
|
+
const audio = new Uint8Array([1, 2, 3, 4]);
|
|
10
|
+
|
|
11
|
+
interface Call {
|
|
12
|
+
url: string;
|
|
13
|
+
init: RequestInit;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function fakeFetch(respond: (call: Call) => Response | Promise<Response>) {
|
|
17
|
+
const calls: Call[] = [];
|
|
18
|
+
const impl: typeof fetch = async (input, init = {}) => {
|
|
19
|
+
const call = { url: String(input), init };
|
|
20
|
+
calls.push(call);
|
|
21
|
+
return respond(call);
|
|
22
|
+
};
|
|
23
|
+
return { calls, impl };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function json(body: unknown, status = 200): Response {
|
|
27
|
+
return new Response(JSON.stringify(body), {
|
|
28
|
+
status,
|
|
29
|
+
headers: { "content-type": "application/json" },
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function rejection(promise: Promise<unknown>): Promise<unknown> {
|
|
34
|
+
try {
|
|
35
|
+
await promise;
|
|
36
|
+
} catch (error) {
|
|
37
|
+
return error;
|
|
38
|
+
}
|
|
39
|
+
throw new Error("expected rejection");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function httpError(promise: Promise<unknown>): Promise<GrokHttpError> {
|
|
43
|
+
const error = await rejection(promise);
|
|
44
|
+
if (!(error instanceof GrokHttpError)) throw new Error(`expected GrokHttpError, got ${error}`);
|
|
45
|
+
return error;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
describe("GrokVoiceClient.transcribe", () => {
|
|
49
|
+
it("posts multipart with model first and file last", async () => {
|
|
50
|
+
const fake = fakeFetch(() => json({ text: "hello", language: "en", duration: 1.5 }));
|
|
51
|
+
const client = new GrokVoiceClient({ fetch: fake.impl, platform });
|
|
52
|
+
await client.transcribe("tok", { audio, mediaType: "audio/mpeg", language: "en" });
|
|
53
|
+
|
|
54
|
+
expect(fake.calls).toHaveLength(1);
|
|
55
|
+
const { url, init } = fake.calls[0]!;
|
|
56
|
+
expect(url).toBe("https://api.x.ai/v1/stt");
|
|
57
|
+
expect(init.method).toBe("POST");
|
|
58
|
+
expect(init.headers).toEqual(buildGrokVoiceHeaders({ bearer: "tok", platform }));
|
|
59
|
+
const form = init.body;
|
|
60
|
+
if (!(form instanceof FormData)) throw new Error("expected FormData body");
|
|
61
|
+
expect([...form.keys()]).toEqual(["model", "language", "file"]);
|
|
62
|
+
expect(form.get("model")).toBe(GROK_STT_DEFAULT_MODEL);
|
|
63
|
+
const file = form.get("file");
|
|
64
|
+
if (!(file instanceof File)) throw new Error("expected File");
|
|
65
|
+
expect(file.name).toBe("audio.mp3");
|
|
66
|
+
expect(file.type).toBe("audio/mpeg");
|
|
67
|
+
expect(new Uint8Array(await file.arrayBuffer())).toEqual(audio);
|
|
68
|
+
|
|
69
|
+
// The serialized wire body also ends with the file part.
|
|
70
|
+
const wire = await new Response(form).text();
|
|
71
|
+
expect(wire.indexOf('name="model"')).toBeLessThan(wire.indexOf('name="file"'));
|
|
72
|
+
expect(wire).toContain('filename="audio.mp3"');
|
|
73
|
+
expect(wire).toContain("Content-Type: audio/mpeg");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("uses the requested model and omits language when absent", async () => {
|
|
77
|
+
const fake = fakeFetch(() => json({ text: "" }));
|
|
78
|
+
const client = new GrokVoiceClient({ fetch: fake.impl, platform });
|
|
79
|
+
await client.transcribe("tok", { audio, mediaType: "audio/wav", model: "m-2" });
|
|
80
|
+
const form = fake.calls[0]!.init.body;
|
|
81
|
+
if (!(form instanceof FormData)) throw new Error("expected FormData body");
|
|
82
|
+
expect([...form.keys()]).toEqual(["model", "file"]);
|
|
83
|
+
expect(form.get("model")).toBe("m-2");
|
|
84
|
+
const file = form.get("file");
|
|
85
|
+
expect(file instanceof File && file.name).toBe("audio.wav");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("parses the response, keeps extra fields, and drops empty or null language", async () => {
|
|
89
|
+
const words = [{ text: "hi", start: 0, end: 0.4 }];
|
|
90
|
+
const client = new GrokVoiceClient({
|
|
91
|
+
fetch: fakeFetch(() => json({ text: "hi", language: "en", duration: 0.4, words })).impl,
|
|
92
|
+
});
|
|
93
|
+
expect(await client.transcribe("tok", { audio, mediaType: "audio/mpeg" })).toEqual({
|
|
94
|
+
text: "hi",
|
|
95
|
+
language: "en",
|
|
96
|
+
duration: 0.4,
|
|
97
|
+
words,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const silent = new GrokVoiceClient({
|
|
101
|
+
fetch: fakeFetch(() => json({ text: "", language: "", duration: 1 })).impl,
|
|
102
|
+
});
|
|
103
|
+
expect(await silent.transcribe("tok", { audio, mediaType: "audio/mpeg" })).toEqual({
|
|
104
|
+
text: "",
|
|
105
|
+
duration: 1,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const nulls = new GrokVoiceClient({
|
|
109
|
+
fetch: fakeFetch(() => json({ text: "x", language: null, duration: null })).impl,
|
|
110
|
+
});
|
|
111
|
+
expect(await nulls.transcribe("tok", { audio, mediaType: "audio/mpeg" })).toEqual({
|
|
112
|
+
text: "x",
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("rejects an invalid response body without retrying", async () => {
|
|
117
|
+
const fake = fakeFetch(() => json({ transcript: "no text field" }));
|
|
118
|
+
const client = new GrokVoiceClient({ fetch: fake.impl });
|
|
119
|
+
const error = await httpError(client.transcribe("tok", { audio, mediaType: "audio/mpeg" }));
|
|
120
|
+
expect(error.classification).toMatchObject({ kind: "transient", status: 502 });
|
|
121
|
+
expect(error.classification.shouldRetry).toBe(false);
|
|
122
|
+
expect(fake.calls).toHaveLength(1);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("rejects empty audio before sending", async () => {
|
|
126
|
+
const fake = fakeFetch(() => json({ text: "" }));
|
|
127
|
+
const client = new GrokVoiceClient({ fetch: fake.impl });
|
|
128
|
+
await expect(
|
|
129
|
+
client.transcribe("tok", { audio: new Uint8Array(), mediaType: "audio/mpeg" }),
|
|
130
|
+
).rejects.toThrow(TypeError);
|
|
131
|
+
expect(fake.calls).toHaveLength(0);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("maps 401 to auth", async () => {
|
|
135
|
+
const client = new GrokVoiceClient({
|
|
136
|
+
fetch: fakeFetch(() => json({ error: "unauthorized" }, 401)).impl,
|
|
137
|
+
});
|
|
138
|
+
const error = await httpError(client.transcribe("tok", { audio, mediaType: "audio/mpeg" }));
|
|
139
|
+
expect(error.classification.kind).toBe("auth");
|
|
140
|
+
expect(error.status).toBe(401);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("aborts on the total timeout and does not retry", async () => {
|
|
144
|
+
const fake = fakeFetch(
|
|
145
|
+
({ init }) =>
|
|
146
|
+
new Promise<Response>((_, reject) => {
|
|
147
|
+
init.signal?.addEventListener("abort", () => reject(init.signal?.reason), {
|
|
148
|
+
once: true,
|
|
149
|
+
});
|
|
150
|
+
}),
|
|
151
|
+
);
|
|
152
|
+
const client = new GrokVoiceClient({ fetch: fake.impl, timeoutMs: 20 });
|
|
153
|
+
const error = await httpError(client.transcribe("tok", { audio, mediaType: "audio/mpeg" }));
|
|
154
|
+
expect(error.classification).toMatchObject({ kind: "transient", status: 0 });
|
|
155
|
+
expect(error.classification.shouldRetry).toBe(false);
|
|
156
|
+
expect(error.message).toContain("timed out after 20 ms");
|
|
157
|
+
expect(fake.calls).toHaveLength(1);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("rethrows the caller's abort reason", async () => {
|
|
161
|
+
const controller = new AbortController();
|
|
162
|
+
const fake = fakeFetch(
|
|
163
|
+
({ init }) =>
|
|
164
|
+
new Promise<Response>((_, reject) => {
|
|
165
|
+
init.signal?.addEventListener("abort", () => reject(init.signal?.reason), {
|
|
166
|
+
once: true,
|
|
167
|
+
});
|
|
168
|
+
}),
|
|
169
|
+
);
|
|
170
|
+
const client = new GrokVoiceClient({ fetch: fake.impl });
|
|
171
|
+
const pending = client.transcribe("tok", {
|
|
172
|
+
audio,
|
|
173
|
+
mediaType: "audio/mpeg",
|
|
174
|
+
signal: controller.signal,
|
|
175
|
+
});
|
|
176
|
+
const reason = new Error("caller cancelled");
|
|
177
|
+
controller.abort(reason);
|
|
178
|
+
expect(await rejection(pending)).toBe(reason);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
describe("GrokVoiceClient.speak", () => {
|
|
183
|
+
const mp3 = new Uint8Array([0xff, 0xfb, 0x90, 0x00]);
|
|
184
|
+
|
|
185
|
+
it("posts JSON with language and format defaults and returns the audio bytes", async () => {
|
|
186
|
+
const fake = fakeFetch(
|
|
187
|
+
() => new Response(mp3, { status: 200, headers: { "content-type": "audio/mpeg" } }),
|
|
188
|
+
);
|
|
189
|
+
const client = new GrokVoiceClient({ fetch: fake.impl, platform });
|
|
190
|
+
const result = await client.speak("tok", { text: "hello", voice: "ara" });
|
|
191
|
+
|
|
192
|
+
expect(result.mediaType).toBe("audio/mpeg");
|
|
193
|
+
expect(result.audio).toEqual(mp3);
|
|
194
|
+
expect(fake.calls).toHaveLength(1);
|
|
195
|
+
const { url, init } = fake.calls[0]!;
|
|
196
|
+
expect(url).toBe("https://api.x.ai/v1/tts");
|
|
197
|
+
expect(init.headers).toEqual({
|
|
198
|
+
...buildGrokVoiceHeaders({ bearer: "tok", platform }),
|
|
199
|
+
"content-type": "application/json",
|
|
200
|
+
});
|
|
201
|
+
expect(typeof init.body === "string" && JSON.parse(init.body)).toEqual({
|
|
202
|
+
text: "hello",
|
|
203
|
+
voice: "ara",
|
|
204
|
+
language: "en",
|
|
205
|
+
format: "mp3",
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("passes explicit language and format and strips content-type parameters", async () => {
|
|
210
|
+
const fake = fakeFetch(
|
|
211
|
+
() =>
|
|
212
|
+
new Response(mp3, { status: 200, headers: { "content-type": "audio/wav; codecs=1" } }),
|
|
213
|
+
);
|
|
214
|
+
const client = new GrokVoiceClient({ fetch: fake.impl });
|
|
215
|
+
const result = await client.speak("tok", {
|
|
216
|
+
text: "bonjour",
|
|
217
|
+
voice: "eve",
|
|
218
|
+
language: "fr",
|
|
219
|
+
format: "wav",
|
|
220
|
+
});
|
|
221
|
+
expect(result.mediaType).toBe("audio/wav");
|
|
222
|
+
const body = fake.calls[0]!.init.body;
|
|
223
|
+
expect(typeof body === "string" && JSON.parse(body)).toMatchObject({
|
|
224
|
+
language: "fr",
|
|
225
|
+
format: "wav",
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it("falls back to the format's media type when content-type is missing", async () => {
|
|
230
|
+
const client = new GrokVoiceClient({
|
|
231
|
+
fetch: fakeFetch(() => {
|
|
232
|
+
const response = new Response(mp3, { status: 200 });
|
|
233
|
+
response.headers.delete("content-type");
|
|
234
|
+
return response;
|
|
235
|
+
}).impl,
|
|
236
|
+
});
|
|
237
|
+
const result = await client.speak("tok", { text: "hi", voice: "ara" });
|
|
238
|
+
expect(result.mediaType).toBe("audio/mpeg");
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it("rejects a JSON success body instead of returning it as audio", async () => {
|
|
242
|
+
const client = new GrokVoiceClient({ fetch: fakeFetch(() => json({ audio: "x" })).impl });
|
|
243
|
+
const error = await httpError(client.speak("tok", { text: "hi", voice: "ara" }));
|
|
244
|
+
expect(error.classification.status).toBe(502);
|
|
245
|
+
expect(error.classification.shouldRetry).toBe(false);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it("validates the request before sending", async () => {
|
|
249
|
+
const fake = fakeFetch(() => new Response(mp3));
|
|
250
|
+
const client = new GrokVoiceClient({ fetch: fake.impl });
|
|
251
|
+
await expect(client.speak("tok", { text: "", voice: "ara" })).rejects.toThrow();
|
|
252
|
+
expect(fake.calls).toHaveLength(0);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it("maps 422 to invalid_request with the server message", async () => {
|
|
256
|
+
const client = new GrokVoiceClient({
|
|
257
|
+
fetch: fakeFetch(
|
|
258
|
+
() =>
|
|
259
|
+
new Response("Failed to deserialize the JSON body: missing field `language`", {
|
|
260
|
+
status: 422,
|
|
261
|
+
headers: { "content-type": "text/plain" },
|
|
262
|
+
}),
|
|
263
|
+
).impl,
|
|
264
|
+
});
|
|
265
|
+
const error = await httpError(client.speak("tok", { text: "hi", voice: "ara" }));
|
|
266
|
+
expect(error.classification.kind).toBe("invalid_request");
|
|
267
|
+
expect(error.classification.message).toContain("missing field `language`");
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it("maps 401 to auth", async () => {
|
|
271
|
+
const client = new GrokVoiceClient({
|
|
272
|
+
fetch: fakeFetch(() => json({ error: { message: "bad token" } }, 401)).impl,
|
|
273
|
+
});
|
|
274
|
+
const error = await httpError(client.speak("tok", { text: "hi", voice: "ara" }));
|
|
275
|
+
expect(error.classification.kind).toBe("auth");
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it("does not retry a POST that failed after send", async () => {
|
|
279
|
+
const fake = fakeFetch(() => {
|
|
280
|
+
throw new TypeError("fetch failed", {
|
|
281
|
+
cause: Object.assign(new Error("socket hang up"), { code: "ECONNRESET" }),
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
const client = new GrokVoiceClient({ fetch: fake.impl });
|
|
285
|
+
const error = await httpError(client.speak("tok", { text: "hi", voice: "ara" }));
|
|
286
|
+
expect(error.classification).toMatchObject({ kind: "transient", status: 0 });
|
|
287
|
+
expect(error.classification.shouldRetry).toBe(false);
|
|
288
|
+
expect(fake.calls).toHaveLength(1);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it("reports a connect failure before send as transient without a no-retry override", async () => {
|
|
292
|
+
const fake = fakeFetch(() => {
|
|
293
|
+
throw new TypeError("fetch failed", {
|
|
294
|
+
cause: Object.assign(new Error("connect ECONNREFUSED"), { code: "ECONNREFUSED" }),
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
const client = new GrokVoiceClient({ fetch: fake.impl });
|
|
298
|
+
const error = await httpError(client.speak("tok", { text: "hi", voice: "ara" }));
|
|
299
|
+
expect(error.classification).toMatchObject({ kind: "transient", status: 0 });
|
|
300
|
+
expect(error.classification.shouldRetry).toBeUndefined();
|
|
301
|
+
expect(fake.calls).toHaveLength(1);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it("times out while reading a stalled body", async () => {
|
|
305
|
+
const fake = fakeFetch(({ init }) => {
|
|
306
|
+
const stream = new ReadableStream<Uint8Array>({
|
|
307
|
+
start(controller) {
|
|
308
|
+
controller.enqueue(mp3);
|
|
309
|
+
init.signal?.addEventListener("abort", () => controller.error(init.signal?.reason), {
|
|
310
|
+
once: true,
|
|
311
|
+
});
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
return new Response(stream, { status: 200, headers: { "content-type": "audio/mpeg" } });
|
|
315
|
+
});
|
|
316
|
+
const client = new GrokVoiceClient({ fetch: fake.impl, timeoutMs: 20 });
|
|
317
|
+
const error = await httpError(client.speak("tok", { text: "hi", voice: "ara" }));
|
|
318
|
+
expect(error.message).toContain("timed out");
|
|
319
|
+
expect(fake.calls).toHaveLength(1);
|
|
320
|
+
});
|
|
321
|
+
});
|