@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,703 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import { type JsonValue } from "../common/provider-types";
|
|
4
|
+
import {
|
|
5
|
+
classifyGrokHttpError,
|
|
6
|
+
GrokProtocolError,
|
|
7
|
+
readGrokResponseHeaders,
|
|
8
|
+
} from "./grok-backend-protocol";
|
|
9
|
+
import {
|
|
10
|
+
GROK_IMAGE_DEFAULT_MODEL,
|
|
11
|
+
GROK_PUBLIC_API_URL,
|
|
12
|
+
GROK_VIDEO_DEFAULT_MODEL,
|
|
13
|
+
} from "./grok-constants";
|
|
14
|
+
import { GrokHttpError } from "./grok-direct-client";
|
|
15
|
+
import { GrokInvalidRequestError } from "./grok-dispatcher";
|
|
16
|
+
import { buildGrokMediaHeaders } from "./grok-headers";
|
|
17
|
+
import {
|
|
18
|
+
GrokImageResponse,
|
|
19
|
+
GrokVideoStart,
|
|
20
|
+
GrokVideoStatus,
|
|
21
|
+
grokVideoUrl,
|
|
22
|
+
type GrokImageEditRequest,
|
|
23
|
+
type GrokImageGenerateRequest,
|
|
24
|
+
type GrokMediaAuth,
|
|
25
|
+
type GrokMediaPort,
|
|
26
|
+
type GrokVideoStartRequest,
|
|
27
|
+
} from "./grok-types";
|
|
28
|
+
|
|
29
|
+
// api.x.ai Imagine client (images + video). Bodies are plain JSON: grok-build sends them with
|
|
30
|
+
// reqwest `.json(payload)` and no content-encoding (1.0.45 xai-grok-tools image_gen/mod.rs:199-214,
|
|
31
|
+
// image_edit/mod.rs:398, video_gen/mod.rs:364-367). Every request carries `content-type:
|
|
32
|
+
// application/json` because grok-build installs it as a client default header, GET included
|
|
33
|
+
// (image_gen/mod.rs:104-105, video_gen/mod.rs:185-186).
|
|
34
|
+
//
|
|
35
|
+
// Retry safety (plan §5.5): this client never retries. A POST that may have reached upstream
|
|
36
|
+
// fails with GrokMediaDeliveryError or GrokMediaTimeoutError, which the dispatcher cannot
|
|
37
|
+
// classify and therefore never replays. Only a connect failure that provably happened before
|
|
38
|
+
// the request was written becomes a transient GrokHttpError (status 0); the dispatcher fails
|
|
39
|
+
// over on exactly that error once. GETs (video polls) also map every transport failure to
|
|
40
|
+
// status 0: they are idempotent, and the dispatcher pins polls to their token.
|
|
41
|
+
|
|
42
|
+
// image_gen/mod.rs:37-38 (the proxy buffers the whole image before the first byte)
|
|
43
|
+
export const GROK_IMAGE_TOTAL_TIMEOUT_MS = 300_000;
|
|
44
|
+
export const GROK_IMAGE_READ_TIMEOUT_MS = 240_000;
|
|
45
|
+
|
|
46
|
+
// video_gen/mod.rs:37,40
|
|
47
|
+
export const GROK_VIDEO_START_TIMEOUT_MS = 60_000;
|
|
48
|
+
export const GROK_VIDEO_POLL_TIMEOUT_MS = 30_000;
|
|
49
|
+
|
|
50
|
+
// @ai-sdk/xai xai-image-model.ts maxImagesPerCall
|
|
51
|
+
export const GROK_IMAGE_MAX_PER_CALL = 3;
|
|
52
|
+
|
|
53
|
+
// grok-build image_gen/mod.rs:236 and image_edit/mod.rs:367 always send "1k"
|
|
54
|
+
const GROK_IMAGE_DEFAULT_RESOLUTION = "1k";
|
|
55
|
+
|
|
56
|
+
// image_edit/mod.rs:28 (XAI_IMAGINE_EDIT_MODEL equals the generation default)
|
|
57
|
+
const GROK_IMAGE_EDIT_DEFAULT_MODEL = GROK_IMAGE_DEFAULT_MODEL;
|
|
58
|
+
|
|
59
|
+
// @ai-sdk/xai video duration range 1-15 s (plan §3.6)
|
|
60
|
+
const GROK_VIDEO_MAX_DURATION_SEC = 15;
|
|
61
|
+
|
|
62
|
+
const MAX_IMAGE_RESPONSE_BYTES = 64 * 1024 * 1024;
|
|
63
|
+
const MAX_JSON_RESPONSE_BYTES = 1024 * 1024;
|
|
64
|
+
const MAX_ERROR_BODY_BYTES = 64 * 1024;
|
|
65
|
+
|
|
66
|
+
export interface GrokMediaTimeouts {
|
|
67
|
+
imageTotalMs: number;
|
|
68
|
+
imageReadMs: number;
|
|
69
|
+
videoStartMs: number;
|
|
70
|
+
videoPollMs: number;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const DEFAULT_TIMEOUTS: GrokMediaTimeouts = {
|
|
74
|
+
imageTotalMs: GROK_IMAGE_TOTAL_TIMEOUT_MS,
|
|
75
|
+
imageReadMs: GROK_IMAGE_READ_TIMEOUT_MS,
|
|
76
|
+
videoStartMs: GROK_VIDEO_START_TIMEOUT_MS,
|
|
77
|
+
videoPollMs: GROK_VIDEO_POLL_TIMEOUT_MS,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/** Upstream flagged the output with `respect_moderation: false`. Never retried. */
|
|
81
|
+
export class GrokContentPolicyError extends Error {
|
|
82
|
+
constructor(kind: "image" | "video") {
|
|
83
|
+
super(
|
|
84
|
+
`${kind === "image" ? "Image" : "Video"} generation was blocked due to a content policy violation.`,
|
|
85
|
+
);
|
|
86
|
+
this.name = "GrokContentPolicyError";
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** A total or read timeout fired. For a POST, upstream may already be executing the job. */
|
|
91
|
+
export class GrokMediaTimeoutError extends Error {
|
|
92
|
+
readonly phase: "total" | "read";
|
|
93
|
+
readonly timeoutMs: number;
|
|
94
|
+
|
|
95
|
+
constructor(phase: "total" | "read", timeoutMs: number) {
|
|
96
|
+
super(
|
|
97
|
+
`Grok media request ${phase === "total" ? "timed out" : "stalled"} after ${timeoutMs} ms`,
|
|
98
|
+
);
|
|
99
|
+
this.name = "GrokMediaTimeoutError";
|
|
100
|
+
this.phase = phase;
|
|
101
|
+
this.timeoutMs = timeoutMs;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** A POST failed after it may have been written; replaying it could run the job twice. */
|
|
106
|
+
export class GrokMediaDeliveryError extends Error {
|
|
107
|
+
constructor(message: string, options?: ErrorOptions) {
|
|
108
|
+
super(`Grok media request failed after it may have been sent: ${message}`, options);
|
|
109
|
+
this.name = "GrokMediaDeliveryError";
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface GrokMediaClientOptions {
|
|
114
|
+
fetch?: typeof fetch;
|
|
115
|
+
baseUrl?: string;
|
|
116
|
+
timeouts?: Partial<GrokMediaTimeouts>;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Error codes raised while resolving or connecting, i.e. before any request byte was written.
|
|
120
|
+
const PRE_SEND_ERROR_CODES = new Set([
|
|
121
|
+
"ECONNREFUSED",
|
|
122
|
+
"ENOTFOUND",
|
|
123
|
+
"EAI_AGAIN",
|
|
124
|
+
"EAI_FAIL",
|
|
125
|
+
"EHOSTUNREACH",
|
|
126
|
+
"EHOSTDOWN",
|
|
127
|
+
"ENETUNREACH",
|
|
128
|
+
"ENETDOWN",
|
|
129
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
130
|
+
"ERR_TLS_CERT_ALTNAME_INVALID",
|
|
131
|
+
"CERT_HAS_EXPIRED",
|
|
132
|
+
"DEPTH_ZERO_SELF_SIGNED_CERT",
|
|
133
|
+
"SELF_SIGNED_CERT_IN_CHAIN",
|
|
134
|
+
"UNABLE_TO_GET_ISSUER_CERT_LOCALLY",
|
|
135
|
+
"UNABLE_TO_VERIFY_LEAF_SIGNATURE",
|
|
136
|
+
]);
|
|
137
|
+
|
|
138
|
+
const ErrorWithCode = z.looseObject({ code: z.string() });
|
|
139
|
+
const ErrorWithCause = z.looseObject({ cause: z.unknown() });
|
|
140
|
+
|
|
141
|
+
/** The first `code` along the `cause` chain (undici wraps socket errors in `TypeError`). */
|
|
142
|
+
function errorCode(cause: unknown): string | undefined {
|
|
143
|
+
let current: unknown = cause;
|
|
144
|
+
|
|
145
|
+
for (let depth = 0; depth < 5 && current !== undefined && current !== null; depth++) {
|
|
146
|
+
const withCode = ErrorWithCode.safeParse(current);
|
|
147
|
+
|
|
148
|
+
if (withCode.success) return withCode.data.code;
|
|
149
|
+
const withCause = ErrorWithCause.safeParse(current);
|
|
150
|
+
current = withCause.success ? withCause.data.cause : undefined;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function errorMessage(cause: unknown): string {
|
|
157
|
+
return cause instanceof Error ? cause.message : String(cause);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function abortReason(signal: AbortSignal): Error {
|
|
161
|
+
const reason: unknown = signal.reason;
|
|
162
|
+
|
|
163
|
+
return reason instanceof Error ? reason : new DOMException("Aborted", "AbortError");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function connectFailure(cause: unknown): GrokHttpError {
|
|
167
|
+
const code = errorCode(cause);
|
|
168
|
+
const message = `${errorMessage(cause)}${code ? ` (${code})` : ""}`;
|
|
169
|
+
|
|
170
|
+
return new GrokHttpError({ ...classifyGrokHttpError(0, "", {}), message }, "", {}, { cause });
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const BASE64 = /^[A-Za-z0-9+/]*={0,2}$/;
|
|
174
|
+
|
|
175
|
+
function sniffImageMediaType(bytes: Uint8Array): string | undefined {
|
|
176
|
+
if (bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff) return "image/jpeg";
|
|
177
|
+
|
|
178
|
+
if (bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47)
|
|
179
|
+
return "image/png";
|
|
180
|
+
|
|
181
|
+
if (bytes[0] === 0x47 && bytes[1] === 0x49 && bytes[2] === 0x46) return "image/gif";
|
|
182
|
+
const ascii = (from: number, to: number) => String.fromCharCode(...bytes.subarray(from, to));
|
|
183
|
+
|
|
184
|
+
if (ascii(0, 4) === "RIFF" && ascii(8, 12) === "WEBP") return "image/webp";
|
|
185
|
+
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export interface GrokDecodedImage {
|
|
190
|
+
bytes: Uint8Array;
|
|
191
|
+
mediaType: string;
|
|
192
|
+
revisedPrompt?: string;
|
|
193
|
+
respectModeration?: boolean;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export interface GrokDecodedImages {
|
|
197
|
+
images: GrokDecodedImage[];
|
|
198
|
+
costInUsdTicks?: number;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function assertImagesAllowed(response: GrokImageResponse): void {
|
|
202
|
+
if (response.data.some((image) => image.respect_moderation === false))
|
|
203
|
+
throw new GrokContentPolicyError("image");
|
|
204
|
+
|
|
205
|
+
for (const image of response.data) {
|
|
206
|
+
if (image.b64_json.length % 4 !== 0 || !BASE64.test(image.b64_json))
|
|
207
|
+
throw new GrokProtocolError("Grok image response carried invalid base64 data");
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** Decodes a validated image response. `mime_type` wins; otherwise the bytes are sniffed. */
|
|
212
|
+
export function decodeGrokImages(response: GrokImageResponse): GrokDecodedImages {
|
|
213
|
+
assertImagesAllowed(response);
|
|
214
|
+
|
|
215
|
+
const images = response.data.map((image): GrokDecodedImage => {
|
|
216
|
+
const bytes = new Uint8Array(Buffer.from(image.b64_json, "base64"));
|
|
217
|
+
|
|
218
|
+
const mediaType = image.mime_type?.startsWith("image/")
|
|
219
|
+
? image.mime_type
|
|
220
|
+
: (sniffImageMediaType(bytes) ?? "image/jpeg");
|
|
221
|
+
|
|
222
|
+
const decoded: GrokDecodedImage = { bytes, mediaType };
|
|
223
|
+
|
|
224
|
+
if (image.revised_prompt) decoded.revisedPrompt = image.revised_prompt;
|
|
225
|
+
|
|
226
|
+
if (image.respect_moderation !== undefined) {
|
|
227
|
+
decoded.respectModeration = image.respect_moderation;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return decoded;
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
const ticks = response.usage?.cost_in_usd_ticks;
|
|
234
|
+
const result: GrokDecodedImages = { images };
|
|
235
|
+
|
|
236
|
+
if (ticks !== undefined) result.costInUsdTicks = ticks;
|
|
237
|
+
|
|
238
|
+
return result;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Fields @ai-sdk/xai reads that grok-types' GrokVideoStatus leaves loose
|
|
242
|
+
// (xai-video-model.ts:782-812): `respect_moderation` nests under `video`.
|
|
243
|
+
const VideoStatusExtras = z.looseObject({
|
|
244
|
+
video: z.looseObject({ respect_moderation: z.boolean().nullish() }).nullish(),
|
|
245
|
+
error: z.looseObject({ code: z.string().nullish(), message: z.string().nullish() }).nullish(),
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
export interface GrokVideoSummary {
|
|
249
|
+
status: "pending" | "done" | "failed";
|
|
250
|
+
url?: string;
|
|
251
|
+
mimeType: "video/mp4";
|
|
252
|
+
usageTicks?: number;
|
|
253
|
+
error?: string;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Collapses a poll response to the SDK's three outcomes. The URL is returned unchanged (D4);
|
|
258
|
+
* `expired` and a `done` without a URL are failures (xai-video-model.ts:643-705).
|
|
259
|
+
*/
|
|
260
|
+
export function summarizeGrokVideoStatus(status: GrokVideoStatus): GrokVideoSummary {
|
|
261
|
+
const extras = VideoStatusExtras.parse(status);
|
|
262
|
+
|
|
263
|
+
if (extras.video?.respect_moderation === false) throw new GrokContentPolicyError("video");
|
|
264
|
+
const ticks = status.usage?.cost_in_usd_ticks;
|
|
265
|
+
|
|
266
|
+
const base: Omit<GrokVideoSummary, "status"> = { mimeType: "video/mp4" };
|
|
267
|
+
|
|
268
|
+
if (ticks !== undefined) base.usageTicks = ticks;
|
|
269
|
+
|
|
270
|
+
if (status.status === "expired")
|
|
271
|
+
return { ...base, status: "failed", error: "Video generation request expired." };
|
|
272
|
+
|
|
273
|
+
if (status.status === "failed") {
|
|
274
|
+
const error = extras.error?.message ?? extras.error?.code ?? "Video generation failed.";
|
|
275
|
+
|
|
276
|
+
return { ...base, status: "failed", error };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (status.status === "done") {
|
|
280
|
+
const url = grokVideoUrl(status);
|
|
281
|
+
|
|
282
|
+
if (!url)
|
|
283
|
+
return {
|
|
284
|
+
...base,
|
|
285
|
+
status: "failed",
|
|
286
|
+
error: "Video generation completed but no video URL was returned.",
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
return { ...base, status: "done", url };
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return { ...base, status: "pending" };
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
interface MediaCall {
|
|
296
|
+
method: "GET" | "POST";
|
|
297
|
+
path: string;
|
|
298
|
+
auth: GrokMediaAuth;
|
|
299
|
+
body?: GrokImageBody | GrokVideoBody;
|
|
300
|
+
totalMs: number;
|
|
301
|
+
readMs: number;
|
|
302
|
+
maxBytes: number;
|
|
303
|
+
signal: AbortSignal | undefined;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
interface MediaReply {
|
|
307
|
+
response: Response;
|
|
308
|
+
text: string;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
class BodyLimitError extends Error {}
|
|
312
|
+
|
|
313
|
+
export class GrokMediaClient implements GrokMediaPort {
|
|
314
|
+
readonly fetchImpl: typeof fetch;
|
|
315
|
+
readonly baseUrl: string;
|
|
316
|
+
readonly timeouts: GrokMediaTimeouts;
|
|
317
|
+
|
|
318
|
+
constructor(options: GrokMediaClientOptions = {}) {
|
|
319
|
+
this.fetchImpl = options.fetch ?? fetch;
|
|
320
|
+
this.baseUrl = (options.baseUrl ?? GROK_PUBLIC_API_URL).replace(/\/+$/, "");
|
|
321
|
+
this.timeouts = { ...DEFAULT_TIMEOUTS, ...options.timeouts };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
async generateImages(
|
|
325
|
+
auth: GrokMediaAuth,
|
|
326
|
+
request: GrokImageGenerateRequest,
|
|
327
|
+
): Promise<GrokImageResponse> {
|
|
328
|
+
const body = imageBody(request, request.model.trim() || GROK_IMAGE_DEFAULT_MODEL);
|
|
329
|
+
|
|
330
|
+
return this.postImages("/images/generations", auth, body, request.signal);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
async editImages(auth: GrokMediaAuth, request: GrokImageEditRequest): Promise<GrokImageResponse> {
|
|
334
|
+
if (request.images.length === 0)
|
|
335
|
+
throw new GrokInvalidRequestError("Grok image edit needs at least one reference image");
|
|
336
|
+
|
|
337
|
+
const urls = request.images.map((url) => {
|
|
338
|
+
if (!url.trim())
|
|
339
|
+
throw new GrokInvalidRequestError("Grok image edit reference must be a non-empty URL");
|
|
340
|
+
|
|
341
|
+
return { url };
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
const body = imageBody(request, request.model.trim() || GROK_IMAGE_EDIT_DEFAULT_MODEL);
|
|
345
|
+
|
|
346
|
+
// image_edit/mod.rs:371-396: one reference is `image: {url}`, several are `images: [{url}]`.
|
|
347
|
+
if (urls.length === 1) body.image = urls[0];
|
|
348
|
+
else body.images = urls;
|
|
349
|
+
|
|
350
|
+
return this.postImages("/images/edits", auth, body, request.signal);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
async startVideo(auth: GrokMediaAuth, request: GrokVideoStartRequest): Promise<GrokVideoStart> {
|
|
354
|
+
const body = videoBody(request);
|
|
355
|
+
|
|
356
|
+
const { text } = await this.call({
|
|
357
|
+
method: "POST",
|
|
358
|
+
path: "/videos/generations",
|
|
359
|
+
auth,
|
|
360
|
+
body,
|
|
361
|
+
totalMs: this.timeouts.videoStartMs,
|
|
362
|
+
readMs: this.timeouts.videoStartMs,
|
|
363
|
+
maxBytes: MAX_JSON_RESPONSE_BYTES,
|
|
364
|
+
signal: request.signal,
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
return parseJson(GrokVideoStart, text, "video start");
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
async getVideo(
|
|
371
|
+
auth: GrokMediaAuth,
|
|
372
|
+
requestId: string,
|
|
373
|
+
signal?: AbortSignal,
|
|
374
|
+
): Promise<GrokVideoStatus> {
|
|
375
|
+
if (!requestId.trim()) throw new TypeError("Grok video request id must be non-empty");
|
|
376
|
+
|
|
377
|
+
const { response, text } = await this.call({
|
|
378
|
+
method: "GET",
|
|
379
|
+
path: `/videos/${encodeURIComponent(requestId)}`,
|
|
380
|
+
auth,
|
|
381
|
+
totalMs: this.timeouts.videoPollMs,
|
|
382
|
+
readMs: this.timeouts.videoPollMs,
|
|
383
|
+
maxBytes: MAX_JSON_RESPONSE_BYTES,
|
|
384
|
+
signal,
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
const status = parseVideoStatus(response.status, text);
|
|
388
|
+
|
|
389
|
+
if (VideoStatusExtras.parse(status).video?.respect_moderation === false)
|
|
390
|
+
throw new GrokContentPolicyError("video");
|
|
391
|
+
|
|
392
|
+
return status;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
async postImages(
|
|
396
|
+
path: string,
|
|
397
|
+
auth: GrokMediaAuth,
|
|
398
|
+
body: GrokImageBody,
|
|
399
|
+
signal: AbortSignal | undefined,
|
|
400
|
+
): Promise<GrokImageResponse> {
|
|
401
|
+
const { text } = await this.call({
|
|
402
|
+
method: "POST",
|
|
403
|
+
path,
|
|
404
|
+
auth,
|
|
405
|
+
body,
|
|
406
|
+
totalMs: this.timeouts.imageTotalMs,
|
|
407
|
+
readMs: this.timeouts.imageReadMs,
|
|
408
|
+
maxBytes: MAX_IMAGE_RESPONSE_BYTES,
|
|
409
|
+
signal,
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
const response = parseJson(GrokImageResponse, text, "image");
|
|
413
|
+
assertImagesAllowed(response);
|
|
414
|
+
|
|
415
|
+
return response;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/** One HTTP exchange with total and read timeouts composed with the caller's signal. */
|
|
419
|
+
async call(call: MediaCall): Promise<MediaReply> {
|
|
420
|
+
const { signal } = call;
|
|
421
|
+
|
|
422
|
+
if (signal?.aborted) throw abortReason(signal);
|
|
423
|
+
|
|
424
|
+
const headers = {
|
|
425
|
+
"content-type": "application/json",
|
|
426
|
+
...buildGrokMediaHeaders({ bearer: call.auth.bearer, sessionId: call.auth.sessionId }),
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
const controller = new AbortController();
|
|
430
|
+
let timeout: GrokMediaTimeoutError | undefined;
|
|
431
|
+
|
|
432
|
+
const fire = (error: GrokMediaTimeoutError) => {
|
|
433
|
+
if (timeout || controller.signal.aborted) return;
|
|
434
|
+
timeout = error;
|
|
435
|
+
controller.abort(error);
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
const totalTimer = setTimeout(
|
|
439
|
+
() => fire(new GrokMediaTimeoutError("total", call.totalMs)),
|
|
440
|
+
call.totalMs,
|
|
441
|
+
);
|
|
442
|
+
|
|
443
|
+
let readTimer: ReturnType<typeof setTimeout> | undefined;
|
|
444
|
+
|
|
445
|
+
const armRead = () => {
|
|
446
|
+
clearTimeout(readTimer);
|
|
447
|
+
readTimer = setTimeout(
|
|
448
|
+
() => fire(new GrokMediaTimeoutError("read", call.readMs)),
|
|
449
|
+
call.readMs,
|
|
450
|
+
);
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
const onAbort = () => controller.abort(signal?.reason);
|
|
454
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
455
|
+
|
|
456
|
+
// Called for every failure: a caller abort or a timeout wins over the transport error.
|
|
457
|
+
const settle = (cause: unknown, responded: boolean): Error => {
|
|
458
|
+
if (signal?.aborted) return abortReason(signal);
|
|
459
|
+
|
|
460
|
+
if (timeout) return timeout;
|
|
461
|
+
|
|
462
|
+
if (cause instanceof GrokProtocolError || cause instanceof GrokHttpError) return cause;
|
|
463
|
+
|
|
464
|
+
if (call.method === "GET") return connectFailure(cause);
|
|
465
|
+
|
|
466
|
+
if (!responded && PRE_SEND_ERROR_CODES.has(errorCode(cause) ?? ""))
|
|
467
|
+
return connectFailure(cause);
|
|
468
|
+
|
|
469
|
+
return new GrokMediaDeliveryError(errorMessage(cause), { cause });
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
try {
|
|
473
|
+
armRead();
|
|
474
|
+
let response: Response;
|
|
475
|
+
|
|
476
|
+
try {
|
|
477
|
+
response = await this.fetchImpl(`${this.baseUrl}${call.path}`, {
|
|
478
|
+
method: call.method,
|
|
479
|
+
headers,
|
|
480
|
+
body: call.body === undefined ? undefined : JSON.stringify(call.body),
|
|
481
|
+
signal: controller.signal,
|
|
482
|
+
});
|
|
483
|
+
} catch (error) {
|
|
484
|
+
throw settle(error, false);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
armRead();
|
|
488
|
+
|
|
489
|
+
try {
|
|
490
|
+
if (!response.ok) {
|
|
491
|
+
const bodyText = await readBody(response, MAX_ERROR_BODY_BYTES, armRead, true);
|
|
492
|
+
throw new GrokHttpError(
|
|
493
|
+
classifyGrokHttpError(response.status, bodyText, response.headers),
|
|
494
|
+
bodyText,
|
|
495
|
+
readGrokResponseHeaders(response.headers),
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const text = await readBody(response, call.maxBytes, armRead, false);
|
|
500
|
+
|
|
501
|
+
return { response, text };
|
|
502
|
+
} catch (error) {
|
|
503
|
+
if (error instanceof BodyLimitError) throw new GrokProtocolError(error.message);
|
|
504
|
+
throw settle(error, true);
|
|
505
|
+
}
|
|
506
|
+
} finally {
|
|
507
|
+
clearTimeout(totalTimer);
|
|
508
|
+
clearTimeout(readTimer);
|
|
509
|
+
signal?.removeEventListener("abort", onAbort);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Image request body. Optional members are left `undefined`, which JSON.stringify omits, so the
|
|
516
|
+
* serialized key order stays that of the verified request.
|
|
517
|
+
*/
|
|
518
|
+
interface GrokImageBody {
|
|
519
|
+
model: string;
|
|
520
|
+
prompt: string;
|
|
521
|
+
n: number;
|
|
522
|
+
aspect_ratio?: GrokImageGenerateRequest["aspectRatio"];
|
|
523
|
+
resolution: NonNullable<GrokImageGenerateRequest["resolution"]>;
|
|
524
|
+
response_format: "b64_json";
|
|
525
|
+
quality?: GrokImageGenerateRequest["quality"];
|
|
526
|
+
output_format?: GrokImageGenerateRequest["outputFormat"];
|
|
527
|
+
image?: { url: string };
|
|
528
|
+
images?: Array<{ url: string }>;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/** Video request body; `undefined` members are omitted from the serialized JSON. */
|
|
532
|
+
interface GrokVideoBody {
|
|
533
|
+
model: string;
|
|
534
|
+
prompt: string;
|
|
535
|
+
image?: { url: string };
|
|
536
|
+
duration?: number;
|
|
537
|
+
aspect_ratio?: GrokVideoStartRequest["aspectRatio"];
|
|
538
|
+
resolution?: GrokVideoStartRequest["resolution"];
|
|
539
|
+
reference_images?: Array<{ url: string }>;
|
|
540
|
+
last_frame?: { url: string };
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function imageBody(request: GrokImageGenerateRequest, model: string): GrokImageBody {
|
|
544
|
+
const n = request.n ?? 1;
|
|
545
|
+
|
|
546
|
+
if (!Number.isSafeInteger(n) || n < 1 || n > GROK_IMAGE_MAX_PER_CALL)
|
|
547
|
+
throw new RangeError(
|
|
548
|
+
`Grok image count must be an integer from 1 to ${GROK_IMAGE_MAX_PER_CALL}`,
|
|
549
|
+
);
|
|
550
|
+
|
|
551
|
+
// Key order of the U0-verified request (image_gen/mod.rs:231-238); SDK options follow.
|
|
552
|
+
const body: GrokImageBody = {
|
|
553
|
+
model,
|
|
554
|
+
prompt: request.prompt,
|
|
555
|
+
n,
|
|
556
|
+
aspect_ratio: request.aspectRatio,
|
|
557
|
+
resolution: request.resolution ?? GROK_IMAGE_DEFAULT_RESOLUTION,
|
|
558
|
+
response_format: "b64_json",
|
|
559
|
+
quality: request.quality,
|
|
560
|
+
output_format: request.outputFormat,
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
return body;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function videoBody(request: GrokVideoStartRequest): GrokVideoBody {
|
|
567
|
+
// `edit-video` / `extend-video` are not in the port type: they post to /videos/edits and
|
|
568
|
+
// /videos/extensions with a source `video: {url}` (xai-video-model.ts:586-593, 391-396), and the
|
|
569
|
+
// port request carries no video URL.
|
|
570
|
+
const references = request.referenceImages ?? [];
|
|
571
|
+
|
|
572
|
+
if (request.mode === "reference-to-video" && references.length === 0)
|
|
573
|
+
throw new GrokInvalidRequestError("Grok reference-to-video needs at least one reference image");
|
|
574
|
+
const { duration } = request;
|
|
575
|
+
|
|
576
|
+
if (
|
|
577
|
+
duration !== undefined &&
|
|
578
|
+
(!Number.isSafeInteger(duration) || duration < 1 || duration > GROK_VIDEO_MAX_DURATION_SEC)
|
|
579
|
+
)
|
|
580
|
+
throw new GrokInvalidRequestError(
|
|
581
|
+
`Grok video duration must be an integer from 1 to ${GROK_VIDEO_MAX_DURATION_SEC}`,
|
|
582
|
+
);
|
|
583
|
+
|
|
584
|
+
// Field order of grok-build GenerateVideoPayload (video_gen/mod.rs:811-827). `mode` selects the
|
|
585
|
+
// endpoint in @ai-sdk/xai and is never a body field; reference-to-video is inferred upstream
|
|
586
|
+
// from `reference_images` (xai-video-model.ts:169-194).
|
|
587
|
+
const body: GrokVideoBody = {
|
|
588
|
+
model: request.model.trim() || GROK_VIDEO_DEFAULT_MODEL,
|
|
589
|
+
prompt: request.prompt,
|
|
590
|
+
image: request.image === undefined ? undefined : { url: request.image },
|
|
591
|
+
duration,
|
|
592
|
+
aspect_ratio: request.aspectRatio,
|
|
593
|
+
resolution: request.resolution,
|
|
594
|
+
reference_images: references.length === 0 ? undefined : references.map((url) => ({ url })),
|
|
595
|
+
last_frame: request.lastFrame === undefined ? undefined : { url: request.lastFrame },
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
return body;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function parseJsonText(text: string, what: string): JsonValue {
|
|
602
|
+
try {
|
|
603
|
+
// JSON.parse without a reviver yields only JSON values.
|
|
604
|
+
const json: JsonValue = JSON.parse(text);
|
|
605
|
+
|
|
606
|
+
return json;
|
|
607
|
+
} catch {
|
|
608
|
+
throw new GrokProtocolError(`Grok ${what} response is not JSON`);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function parseJson<T>(schema: z.ZodType<T>, text: string, what: string): T {
|
|
613
|
+
const parsed = schema.safeParse(parseJsonText(text, what));
|
|
614
|
+
|
|
615
|
+
if (!parsed.success) throw new GrokProtocolError(`Grok ${what} response has an unexpected shape`);
|
|
616
|
+
|
|
617
|
+
return parsed.data;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
const JsonObject = z.record(z.string(), z.unknown());
|
|
621
|
+
|
|
622
|
+
const LooseVideoStatus = z.looseObject({
|
|
623
|
+
status: z.string().nullish(),
|
|
624
|
+
video: z.looseObject({ url: z.string().nullish() }).nullish(),
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* 202 means pending, possibly with an empty or partial body (xai-video-model.ts:876-894;
|
|
629
|
+
* grok-build video_gen/mod.rs:442 accepts it too). A 200 without `status` but with a video URL
|
|
630
|
+
* is done (xai-video-model.ts:673-676).
|
|
631
|
+
*/
|
|
632
|
+
function parseVideoStatus(httpStatus: number, text: string): GrokVideoStatus {
|
|
633
|
+
if (httpStatus === 202) {
|
|
634
|
+
if (!text.trim()) return { status: "pending" };
|
|
635
|
+
let json: unknown;
|
|
636
|
+
|
|
637
|
+
try {
|
|
638
|
+
json = JSON.parse(text);
|
|
639
|
+
} catch {
|
|
640
|
+
return { status: "pending" };
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
const object = JsonObject.safeParse(json);
|
|
644
|
+
const loose = LooseVideoStatus.safeParse(json);
|
|
645
|
+
|
|
646
|
+
if (!object.success || !loose.success) return { status: "pending" };
|
|
647
|
+
|
|
648
|
+
const parsed = GrokVideoStatus.safeParse({
|
|
649
|
+
...object.data,
|
|
650
|
+
status: loose.data.status ?? "pending",
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
return parsed.success ? parsed.data : { status: "pending" };
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
const json = parseJsonText(text, "video status");
|
|
657
|
+
const object = JsonObject.safeParse(json);
|
|
658
|
+
const loose = LooseVideoStatus.safeParse(json);
|
|
659
|
+
|
|
660
|
+
if (!object.success || !loose.success)
|
|
661
|
+
throw new GrokProtocolError("Grok video status response has an unexpected shape");
|
|
662
|
+
const status = loose.data.status ?? (loose.data.video?.url ? "done" : "pending");
|
|
663
|
+
const parsed = GrokVideoStatus.safeParse({ ...object.data, status });
|
|
664
|
+
|
|
665
|
+
if (!parsed.success)
|
|
666
|
+
throw new GrokProtocolError("Grok video status response has an unexpected shape");
|
|
667
|
+
|
|
668
|
+
return parsed.data;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/** Reads the body, re-arming the read timer per chunk. `truncate` caps instead of failing. */
|
|
672
|
+
async function readBody(
|
|
673
|
+
response: Response,
|
|
674
|
+
maxBytes: number,
|
|
675
|
+
onChunk: () => void,
|
|
676
|
+
truncate: boolean,
|
|
677
|
+
): Promise<string> {
|
|
678
|
+
if (!response.body) return "";
|
|
679
|
+
const reader = response.body.getReader();
|
|
680
|
+
const decoder = new TextDecoder();
|
|
681
|
+
let text = "";
|
|
682
|
+
let total = 0;
|
|
683
|
+
|
|
684
|
+
for (;;) {
|
|
685
|
+
const { done, value } = await reader.read();
|
|
686
|
+
|
|
687
|
+
if (done) break;
|
|
688
|
+
onChunk();
|
|
689
|
+
total += value.byteLength;
|
|
690
|
+
|
|
691
|
+
if (total > maxBytes) {
|
|
692
|
+
await reader.cancel().catch(() => undefined);
|
|
693
|
+
|
|
694
|
+
if (truncate)
|
|
695
|
+
return text + decoder.decode(value.subarray(0, maxBytes - (total - value.byteLength)));
|
|
696
|
+
throw new BodyLimitError(`Grok media response exceeded ${maxBytes} bytes`);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
text += decoder.decode(value, { stream: true });
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
return text + decoder.decode();
|
|
703
|
+
}
|