@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
|
@@ -2,6 +2,7 @@ import { PNG } from "pngjs";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
|
|
4
4
|
import { ImageGenerationMetadata } from "../../../application/qgrid/qgrid.types";
|
|
5
|
+
import { isJsonObject, type JsonValue, jsonString } from "../common/provider-types";
|
|
5
6
|
import {
|
|
6
7
|
CHATGPT_CODEX_RESPONSES_URL,
|
|
7
8
|
imageMimeType,
|
|
@@ -14,6 +15,21 @@ const MAX_PNG_BYTES = 32 * 1024 * 1024;
|
|
|
14
15
|
const MAX_IMAGE_PIXELS = 4096 * 4096;
|
|
15
16
|
const MAX_RESPONSE_BYTES = 64 * 1024 * 1024;
|
|
16
17
|
const PNG_SIGNATURE = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
|
|
18
|
+
const TEXT_PART_TYPES = new Set(["input_text", "output_text", "text"]);
|
|
19
|
+
|
|
20
|
+
/** Request body for the standalone Codex images endpoint; controls are forwarded only when set. */
|
|
21
|
+
type StandaloneImageBody = {
|
|
22
|
+
model: "gpt-image-2";
|
|
23
|
+
prompt: string;
|
|
24
|
+
background?: JsonValue;
|
|
25
|
+
quality?: JsonValue;
|
|
26
|
+
size?: JsonValue;
|
|
27
|
+
images?: Array<{ image_url: string }>;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function imageUrlOf(value: JsonValue | undefined): string | undefined {
|
|
31
|
+
return jsonString(value) ?? (isJsonObject(value) ? jsonString(value.url) : undefined);
|
|
32
|
+
}
|
|
17
33
|
|
|
18
34
|
export function buildStandaloneImageRequest(options: OpenAIResponsesOptions) {
|
|
19
35
|
if (options.outputSchema || options.tools?.length) {
|
|
@@ -21,88 +37,126 @@ export function buildStandaloneImageRequest(options: OpenAIResponsesOptions) {
|
|
|
21
37
|
"Standalone image generation does not support tools or structured output",
|
|
22
38
|
);
|
|
23
39
|
}
|
|
40
|
+
|
|
24
41
|
if (!options.imageGeneration) {
|
|
25
42
|
throw new OpenAIProtocolError("Standalone images require image generation to be enabled");
|
|
26
43
|
}
|
|
27
|
-
|
|
44
|
+
|
|
45
|
+
const controls = options.imageGeneration === true ? {} : options.imageGeneration;
|
|
46
|
+
|
|
28
47
|
if (options.model !== "gpt-image-2" && controls.background !== "transparent") {
|
|
29
48
|
throw new OpenAIProtocolError(
|
|
30
49
|
"Standalone images require an explicit transparent background request",
|
|
31
50
|
);
|
|
32
51
|
}
|
|
52
|
+
|
|
33
53
|
if (controls.output_format && controls.output_format !== "png") {
|
|
34
54
|
throw new OpenAIProtocolError("Standalone image generation only supports PNG output");
|
|
35
55
|
}
|
|
56
|
+
|
|
36
57
|
const text: string[] = [];
|
|
37
58
|
const images: Array<{ image_url: string }> = [];
|
|
59
|
+
|
|
38
60
|
if (options.instructions) text.push(`System instructions:\n${options.instructions}`);
|
|
61
|
+
|
|
39
62
|
for (const item of options.history) {
|
|
63
|
+
if (!isJsonObject(item)) {
|
|
64
|
+
throw new OpenAIProtocolError(
|
|
65
|
+
"Unsupported conversation item for standalone image generation",
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
40
69
|
if (item.type === "reasoning") continue;
|
|
41
|
-
|
|
70
|
+
|
|
71
|
+
const result = jsonString(item.result);
|
|
72
|
+
|
|
73
|
+
if (item.type === "image_generation_call" && result !== undefined) {
|
|
42
74
|
images.push({
|
|
43
|
-
image_url: `data:${imageMimeType(
|
|
75
|
+
image_url: `data:${imageMimeType(result, item.output_format)};base64,${result}`,
|
|
44
76
|
});
|
|
45
77
|
continue;
|
|
46
78
|
}
|
|
47
|
-
|
|
79
|
+
|
|
80
|
+
const role = jsonString(item.role);
|
|
81
|
+
|
|
82
|
+
if (item.type !== "message" && role === undefined) {
|
|
48
83
|
throw new OpenAIProtocolError(
|
|
49
84
|
"Unsupported conversation item for standalone image generation",
|
|
50
85
|
);
|
|
51
86
|
}
|
|
52
|
-
|
|
53
|
-
|
|
87
|
+
|
|
88
|
+
const speaker = role ?? "user";
|
|
89
|
+
const content = jsonString(item.content);
|
|
90
|
+
|
|
91
|
+
if (content !== undefined) {
|
|
92
|
+
text.push(`${speaker}:\n${content}`);
|
|
54
93
|
continue;
|
|
55
94
|
}
|
|
95
|
+
|
|
56
96
|
if (!Array.isArray(item.content)) {
|
|
57
97
|
throw new OpenAIProtocolError("Unsupported message content for standalone image generation");
|
|
58
98
|
}
|
|
99
|
+
|
|
59
100
|
for (const part of item.content) {
|
|
60
|
-
if (!part
|
|
101
|
+
if (!isJsonObject(part)) {
|
|
61
102
|
throw new OpenAIProtocolError("Unsupported message part for standalone image generation");
|
|
62
103
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
104
|
+
|
|
105
|
+
const partType = jsonString(part.type);
|
|
106
|
+
const partText = jsonString(part.text);
|
|
107
|
+
|
|
108
|
+
if (partType !== undefined && TEXT_PART_TYPES.has(partType) && partText !== undefined) {
|
|
109
|
+
text.push(`${speaker}:\n${partText}`);
|
|
68
110
|
} else if (part.type === "input_image" || part.type === "image_url") {
|
|
69
|
-
const url =
|
|
70
|
-
|
|
111
|
+
const url = imageUrlOf(part.image_url);
|
|
112
|
+
|
|
113
|
+
if (url === undefined || !/^(https:\/\/|data:image\/)/i.test(url)) {
|
|
71
114
|
throw new OpenAIProtocolError("Image references must be HTTPS URLs or image data URLs");
|
|
72
115
|
}
|
|
116
|
+
|
|
73
117
|
images.push({ image_url: url });
|
|
74
118
|
} else {
|
|
75
119
|
throw new OpenAIProtocolError("Unsupported message part for standalone image generation");
|
|
76
120
|
}
|
|
77
121
|
}
|
|
78
122
|
}
|
|
123
|
+
|
|
79
124
|
if (images.length > 5)
|
|
80
125
|
throw new OpenAIProtocolError("Standalone image editing supports at most 5 reference images");
|
|
126
|
+
|
|
81
127
|
if (!text.some((part) => part.trim())) throw new OpenAIProtocolError("Image prompt is empty");
|
|
128
|
+
|
|
129
|
+
const body: StandaloneImageBody = { model: "gpt-image-2", prompt: text.join("\n\n") };
|
|
130
|
+
|
|
131
|
+
if (controls.background) body.background = controls.background;
|
|
132
|
+
|
|
133
|
+
if (controls.quality) body.quality = controls.quality;
|
|
134
|
+
|
|
135
|
+
if (controls.size) body.size = controls.size;
|
|
136
|
+
|
|
137
|
+
if (images.length) body.images = images;
|
|
138
|
+
|
|
82
139
|
return {
|
|
83
140
|
url: CHATGPT_CODEX_RESPONSES_URL.replace(
|
|
84
141
|
/\/responses$/,
|
|
85
142
|
`/images/${images.length ? "edits" : "generations"}`,
|
|
86
143
|
),
|
|
87
|
-
body
|
|
88
|
-
model: "gpt-image-2",
|
|
89
|
-
prompt: text.join("\n\n"),
|
|
90
|
-
...(controls.background ? { background: controls.background } : {}),
|
|
91
|
-
...(controls.quality ? { quality: controls.quality } : {}),
|
|
92
|
-
...(controls.size ? { size: controls.size } : {}),
|
|
93
|
-
...(images.length ? { images } : {}),
|
|
94
|
-
},
|
|
144
|
+
body,
|
|
95
145
|
};
|
|
96
146
|
}
|
|
97
147
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
148
|
+
interface PngDimensions {
|
|
149
|
+
width: number;
|
|
150
|
+
height: number;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function inspectGeneratedPng(base64: string, requireTransparency: boolean): PngDimensions {
|
|
102
154
|
if (base64.length > Math.ceil(MAX_PNG_BYTES / 3) * 4 || !/^[A-Za-z0-9+/]+={0,2}$/.test(base64)) {
|
|
103
155
|
throw new OpenAIProtocolError("Invalid or oversized generated PNG");
|
|
104
156
|
}
|
|
157
|
+
|
|
105
158
|
const bytes = Buffer.from(base64, "base64");
|
|
159
|
+
|
|
106
160
|
if (
|
|
107
161
|
bytes.length < 33 ||
|
|
108
162
|
!bytes.subarray(0, 8).equals(PNG_SIGNATURE) ||
|
|
@@ -110,26 +164,34 @@ function inspectGeneratedPng(
|
|
|
110
164
|
) {
|
|
111
165
|
throw new OpenAIProtocolError("Image generation did not return a PNG");
|
|
112
166
|
}
|
|
167
|
+
|
|
113
168
|
const width = bytes.readUInt32BE(16);
|
|
114
169
|
const height = bytes.readUInt32BE(20);
|
|
170
|
+
|
|
115
171
|
if (!width || !height || width * height > MAX_IMAGE_PIXELS) {
|
|
116
172
|
throw new OpenAIProtocolError("Generated PNG dimensions exceed the decoding limit");
|
|
117
173
|
}
|
|
174
|
+
|
|
118
175
|
let image: PNG;
|
|
176
|
+
|
|
119
177
|
try {
|
|
120
178
|
image = PNG.sync.read(bytes);
|
|
121
179
|
} catch {
|
|
122
180
|
throw new OpenAIProtocolError("Image generation returned an invalid PNG");
|
|
123
181
|
}
|
|
182
|
+
|
|
124
183
|
if (!requireTransparency) return { width: image.width, height: image.height };
|
|
125
184
|
// An alpha channel alone can still be fully opaque or have no visible content.
|
|
126
185
|
let transparent = false;
|
|
127
186
|
let visible = false;
|
|
187
|
+
|
|
128
188
|
for (let i = 3; i < image.data.length; i += 4) {
|
|
129
189
|
transparent ||= image.data[i] === 0;
|
|
130
190
|
visible ||= image.data[i]! > 0;
|
|
191
|
+
|
|
131
192
|
if (transparent && visible) return { width: image.width, height: image.height };
|
|
132
193
|
}
|
|
194
|
+
|
|
133
195
|
throw new OpenAIProtocolError(
|
|
134
196
|
"Image generation did not return visible content on a transparent background",
|
|
135
197
|
);
|
|
@@ -143,20 +205,25 @@ export async function readStandaloneImageResponse(
|
|
|
143
205
|
const reader = response.body.getReader();
|
|
144
206
|
const chunks: Uint8Array[] = [];
|
|
145
207
|
let size = 0;
|
|
208
|
+
|
|
146
209
|
try {
|
|
147
210
|
while (true) {
|
|
148
211
|
const { done, value } = await reader.read();
|
|
212
|
+
|
|
149
213
|
if (done) break;
|
|
150
214
|
size += value.byteLength;
|
|
215
|
+
|
|
151
216
|
if (size > MAX_RESPONSE_BYTES) {
|
|
152
217
|
await reader.cancel();
|
|
153
218
|
throw new OpenAIProtocolError("Image response exceeded the size limit");
|
|
154
219
|
}
|
|
220
|
+
|
|
155
221
|
chunks.push(value);
|
|
156
222
|
}
|
|
157
223
|
} finally {
|
|
158
224
|
reader.releaseLock();
|
|
159
225
|
}
|
|
226
|
+
|
|
160
227
|
const schema = z.object({
|
|
161
228
|
data: z
|
|
162
229
|
.array(z.object({ b64_json: z.string().min(1), revised_prompt: z.string().nullish() }))
|
|
@@ -167,28 +234,41 @@ export async function readStandaloneImageResponse(
|
|
|
167
234
|
background: z.string().optional(),
|
|
168
235
|
usage: ImageGenerationMetadata.shape.usage,
|
|
169
236
|
});
|
|
237
|
+
|
|
170
238
|
let parsed: z.infer<typeof schema>;
|
|
239
|
+
|
|
171
240
|
try {
|
|
172
241
|
parsed = schema.parse(JSON.parse(Buffer.concat(chunks).toString("utf8")));
|
|
173
242
|
} catch {
|
|
174
243
|
throw new OpenAIProtocolError("Invalid standalone image response");
|
|
175
244
|
}
|
|
245
|
+
|
|
176
246
|
const background = parsed.background ?? (requireTransparency ? "transparent" : undefined);
|
|
247
|
+
|
|
177
248
|
return parsed.data.map((item, index) => {
|
|
178
249
|
const dimensions = inspectGeneratedPng(item.b64_json, requireTransparency);
|
|
179
|
-
|
|
250
|
+
|
|
251
|
+
const generation: ImageGenerationMetadata = {
|
|
252
|
+
route: "codex-images",
|
|
253
|
+
model: parsed.model ?? "gpt-image-2",
|
|
254
|
+
size: `${dimensions.width}x${dimensions.height}`,
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
if (background) generation.background = background;
|
|
258
|
+
|
|
259
|
+
if (parsed.quality) generation.quality = parsed.quality;
|
|
260
|
+
|
|
261
|
+
if (index === 0 && parsed.usage) generation.usage = parsed.usage;
|
|
262
|
+
|
|
263
|
+
const event: OpenAINormalizedEvent = {
|
|
180
264
|
type: "image",
|
|
181
265
|
base64: item.b64_json,
|
|
182
266
|
mimeType: "image/png",
|
|
183
|
-
|
|
184
|
-
generation: {
|
|
185
|
-
route: "codex-images",
|
|
186
|
-
model: parsed.model ?? "gpt-image-2",
|
|
187
|
-
...(background ? { background } : {}),
|
|
188
|
-
size: `${dimensions.width}x${dimensions.height}`,
|
|
189
|
-
...(parsed.quality ? { quality: parsed.quality } : {}),
|
|
190
|
-
...(index === 0 && parsed.usage ? { usage: parsed.usage } : {}),
|
|
191
|
-
},
|
|
267
|
+
generation,
|
|
192
268
|
};
|
|
269
|
+
|
|
270
|
+
if (item.revised_prompt) event.revisedPrompt = item.revised_prompt;
|
|
271
|
+
|
|
272
|
+
return event;
|
|
193
273
|
});
|
|
194
274
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
2
|
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
import { type JsonValue } from "../common/provider-types";
|
|
3
6
|
import {
|
|
4
7
|
OPENAI_AUTHORIZE_URL,
|
|
5
8
|
OPENAI_CLIENT_ID,
|
|
@@ -10,6 +13,25 @@ import {
|
|
|
10
13
|
|
|
11
14
|
const OPENAI_AUTH_CLAIM = "https://api.openai.com/auth";
|
|
12
15
|
|
|
16
|
+
// Mistyped claims read as absent, matching how the callers treat missing ones.
|
|
17
|
+
const openAITokenClaimsSchema = z.looseObject({
|
|
18
|
+
exp: z.number().optional().catch(undefined),
|
|
19
|
+
[OPENAI_AUTH_CLAIM]: z
|
|
20
|
+
.looseObject({
|
|
21
|
+
chatgpt_account_id: z.string().optional().catch(undefined),
|
|
22
|
+
chatgpt_plan_type: z.string().optional().catch(undefined),
|
|
23
|
+
})
|
|
24
|
+
.optional()
|
|
25
|
+
.catch(undefined),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const openAITokenExchangeResponseSchema = z.looseObject({
|
|
29
|
+
id_token: z.string().optional(),
|
|
30
|
+
access_token: z.string().optional(),
|
|
31
|
+
refresh_token: z.string().optional(),
|
|
32
|
+
expires_in: z.number().optional(),
|
|
33
|
+
});
|
|
34
|
+
|
|
13
35
|
export interface OpenAIPKCE {
|
|
14
36
|
codeVerifier: string;
|
|
15
37
|
codeChallenge: string;
|
|
@@ -34,6 +56,7 @@ export interface OpenAIExchangedCredentials {
|
|
|
34
56
|
|
|
35
57
|
export function generateOpenAIPKCE(): OpenAIPKCE {
|
|
36
58
|
const codeVerifier = crypto.randomBytes(64).toString("base64url");
|
|
59
|
+
|
|
37
60
|
return {
|
|
38
61
|
codeVerifier,
|
|
39
62
|
codeChallenge: crypto.createHash("sha256").update(codeVerifier).digest("base64url"),
|
|
@@ -59,32 +82,36 @@ export function buildOpenAIAuthUrl(
|
|
|
59
82
|
state,
|
|
60
83
|
originator: OPENAI_ORIGINATOR,
|
|
61
84
|
}).toString();
|
|
85
|
+
|
|
62
86
|
return url.toString();
|
|
63
87
|
}
|
|
64
88
|
|
|
65
89
|
export function parseOpenAITokenClaims(jwt: string): OpenAITokenClaims {
|
|
66
90
|
const parts = jwt.split(".");
|
|
67
91
|
const payloadPart = parts[1];
|
|
92
|
+
|
|
68
93
|
if (parts.length !== 3 || parts.some((part) => !part) || !payloadPart) {
|
|
69
94
|
throw new Error("invalid OpenAI JWT format");
|
|
70
95
|
}
|
|
71
96
|
|
|
72
|
-
let payload:
|
|
97
|
+
let payload: JsonValue;
|
|
98
|
+
|
|
73
99
|
try {
|
|
100
|
+
// JSON.parse without a reviver yields only JSON values.
|
|
74
101
|
payload = JSON.parse(Buffer.from(payloadPart, "base64url").toString("utf8"));
|
|
75
102
|
} catch {
|
|
76
103
|
throw new Error("invalid OpenAI JWT payload");
|
|
77
104
|
}
|
|
78
|
-
if (!payload || typeof payload !== "object") throw new Error("invalid OpenAI JWT payload");
|
|
79
105
|
|
|
80
|
-
const claims = payload
|
|
81
|
-
|
|
82
|
-
|
|
106
|
+
const claims = openAITokenClaimsSchema.safeParse(payload);
|
|
107
|
+
|
|
108
|
+
if (!claims.success) throw new Error("invalid OpenAI JWT payload");
|
|
109
|
+
const auth = claims.data[OPENAI_AUTH_CLAIM];
|
|
110
|
+
|
|
83
111
|
return {
|
|
84
|
-
expiresAt:
|
|
85
|
-
accountId:
|
|
86
|
-
|
|
87
|
-
planType: typeof nested.chatgpt_plan_type === "string" ? nested.chatgpt_plan_type : undefined,
|
|
112
|
+
expiresAt: claims.data.exp === undefined ? undefined : claims.data.exp * 1000,
|
|
113
|
+
accountId: auth?.chatgpt_account_id,
|
|
114
|
+
planType: auth?.chatgpt_plan_type,
|
|
88
115
|
};
|
|
89
116
|
}
|
|
90
117
|
|
|
@@ -104,18 +131,15 @@ export async function exchangeOpenAICode(
|
|
|
104
131
|
code_verifier: codeVerifier,
|
|
105
132
|
}),
|
|
106
133
|
});
|
|
134
|
+
|
|
107
135
|
if (!response.ok) {
|
|
108
136
|
// Token endpoint bodies can echo credentials or provider diagnostics; never expose them.
|
|
109
137
|
await response.body?.cancel().catch(() => undefined);
|
|
110
138
|
throw new Error(`OPENAI_TOKEN_EXCHANGE_HTTP_${response.status}`);
|
|
111
139
|
}
|
|
112
140
|
|
|
113
|
-
const tokens = (await response.json())
|
|
114
|
-
|
|
115
|
-
access_token?: string;
|
|
116
|
-
refresh_token?: string;
|
|
117
|
-
expires_in?: number;
|
|
118
|
-
};
|
|
141
|
+
const tokens = openAITokenExchangeResponseSchema.parse(await response.json());
|
|
142
|
+
|
|
119
143
|
if (!tokens.id_token || !tokens.access_token || !tokens.refresh_token) {
|
|
120
144
|
throw new Error("OpenAI token exchange returned incomplete credentials");
|
|
121
145
|
}
|
|
@@ -123,19 +147,23 @@ export async function exchangeOpenAICode(
|
|
|
123
147
|
const idClaims = parseOpenAITokenClaims(tokens.id_token);
|
|
124
148
|
const accessClaims = parseOpenAITokenClaims(tokens.access_token);
|
|
125
149
|
const accountId = idClaims.accountId ?? accessClaims.accountId;
|
|
150
|
+
|
|
126
151
|
if (!accountId) throw new Error("OpenAI token did not include a ChatGPT account id");
|
|
127
152
|
|
|
128
|
-
|
|
153
|
+
const credentials: OpenAIExchangedCredentials = {
|
|
129
154
|
accessToken: tokens.access_token,
|
|
130
155
|
refreshToken: tokens.refresh_token,
|
|
131
156
|
idToken: tokens.id_token,
|
|
132
157
|
accessTokenExpiresAt:
|
|
133
158
|
accessClaims.expiresAt ??
|
|
134
159
|
(tokens.expires_in ? Date.now() + tokens.expires_in * 1000 : Date.now()),
|
|
135
|
-
...(idClaims.expiresAt ? { idTokenExpiresAt: idClaims.expiresAt } : {}),
|
|
136
160
|
accountId,
|
|
137
|
-
...((idClaims.planType ?? accessClaims.planType)
|
|
138
|
-
? { planType: idClaims.planType ?? accessClaims.planType }
|
|
139
|
-
: {}),
|
|
140
161
|
};
|
|
162
|
+
|
|
163
|
+
if (idClaims.expiresAt) credentials.idTokenExpiresAt = idClaims.expiresAt;
|
|
164
|
+
const planType = idClaims.planType ?? accessClaims.planType;
|
|
165
|
+
|
|
166
|
+
if (planType) credentials.planType = planType;
|
|
167
|
+
|
|
168
|
+
return credentials;
|
|
141
169
|
}
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { type OpenAICredentials } from "../../../application/token/token.types";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
isJsonObject,
|
|
4
|
+
type JsonObject,
|
|
5
|
+
type JsonValue,
|
|
6
|
+
jsonString,
|
|
7
|
+
type QuotaRateLimits,
|
|
8
|
+
type QuotaRateLimitSnapshot,
|
|
9
|
+
type QuotaRateLimitWindow,
|
|
10
|
+
} from "../common/provider-types";
|
|
3
11
|
import { buildCodexIdentityHeaders } from "./openai-backend-protocol";
|
|
4
12
|
|
|
5
13
|
export const CHATGPT_WHAM_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage" as const;
|
|
@@ -33,9 +41,11 @@ async function readDirect(options: OpenAIQuotaHttpOptions): Promise<OpenAIRateLi
|
|
|
33
41
|
(options.fetch ?? fetch)(CHATGPT_WHAM_USAGE_URL, {
|
|
34
42
|
method: "GET",
|
|
35
43
|
headers: buildCodexIdentityHeaders(credentials.accessToken, credentials.accountId),
|
|
36
|
-
|
|
44
|
+
signal: options.signal,
|
|
37
45
|
});
|
|
46
|
+
|
|
38
47
|
let response = await read(options.credentials);
|
|
48
|
+
|
|
39
49
|
if (response.status === 401 && options.refreshCredentials) {
|
|
40
50
|
await response.body?.cancel();
|
|
41
51
|
options.signal?.throwIfAborted();
|
|
@@ -43,20 +53,29 @@ async function readDirect(options: OpenAIQuotaHttpOptions): Promise<OpenAIRateLi
|
|
|
43
53
|
options.signal?.throwIfAborted();
|
|
44
54
|
response = await read(credentials);
|
|
45
55
|
}
|
|
56
|
+
|
|
46
57
|
if (!response.ok) throw new Error(`OpenAI quota lookup failed: HTTP ${response.status}`);
|
|
47
|
-
|
|
58
|
+
// Response.json() parses JSON text, which yields only JSON values.
|
|
59
|
+
const body: JsonValue = await response.json();
|
|
48
60
|
const data = normalizeWhamUsage(body);
|
|
61
|
+
|
|
49
62
|
return { data, cachedAt: Date.now() };
|
|
50
63
|
}
|
|
51
64
|
|
|
52
|
-
function
|
|
53
|
-
|
|
54
|
-
|
|
65
|
+
function jsonObjectOrEmpty(value: JsonValue | undefined): JsonObject {
|
|
66
|
+
return isJsonObject(value) ? value : {};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function windowFrom(value: JsonValue | undefined): QuotaRateLimitWindow | null {
|
|
70
|
+
if (!isJsonObject(value)) return null;
|
|
71
|
+
const source = value;
|
|
55
72
|
const usedPercent = Number(source.usedPercent ?? source.used_percent);
|
|
73
|
+
|
|
56
74
|
if (!Number.isFinite(usedPercent)) return null;
|
|
57
75
|
const durationMins = source.windowDurationMins ?? source.window_minutes;
|
|
58
76
|
const durationSeconds = source.limitWindowSeconds ?? source.limit_window_seconds;
|
|
59
77
|
const resets = source.resetsAt ?? source.reset_at;
|
|
78
|
+
|
|
60
79
|
return {
|
|
61
80
|
usedPercent,
|
|
62
81
|
windowDurationMins:
|
|
@@ -69,62 +88,75 @@ function windowFrom(value: unknown) {
|
|
|
69
88
|
};
|
|
70
89
|
}
|
|
71
90
|
|
|
72
|
-
function snapshotFrom(value:
|
|
73
|
-
const source = (value
|
|
74
|
-
|
|
75
|
-
const limitName = source.limitName ?? source.limit_name;
|
|
91
|
+
function snapshotFrom(value: JsonValue | undefined): QuotaRateLimitSnapshot {
|
|
92
|
+
const source = jsonObjectOrEmpty(value);
|
|
93
|
+
|
|
76
94
|
return {
|
|
77
|
-
limitId:
|
|
78
|
-
limitName:
|
|
95
|
+
limitId: jsonString(source.limitId ?? source.limit_id) ?? null,
|
|
96
|
+
limitName: jsonString(source.limitName ?? source.limit_name) ?? null,
|
|
79
97
|
primary: windowFrom(source.primary ?? source.primary_window),
|
|
80
98
|
secondary: windowFrom(source.secondary ?? source.secondary_window),
|
|
81
|
-
credits:
|
|
82
|
-
planType:
|
|
83
|
-
typeof (source.planType ?? source.plan_type) === "string"
|
|
84
|
-
? String(source.planType ?? source.plan_type)
|
|
85
|
-
: null,
|
|
99
|
+
credits: source.credits ?? null,
|
|
100
|
+
planType: jsonString(source.planType ?? source.plan_type) ?? null,
|
|
86
101
|
rateLimitReachedType:
|
|
87
|
-
|
|
88
|
-
? String(source.rateLimitReachedType ?? source.rate_limit_reached)
|
|
89
|
-
: null,
|
|
102
|
+
jsonString(source.rateLimitReachedType ?? source.rate_limit_reached) ?? null,
|
|
90
103
|
};
|
|
91
104
|
}
|
|
92
105
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
106
|
+
/** Normalizes snapshots that already use the app-server (camelCase) shape. */
|
|
107
|
+
function normalizeAppServerUsage(body: JsonObject): QuotaRateLimits {
|
|
108
|
+
const byId = isJsonObject(body.rateLimitsByLimitId)
|
|
109
|
+
? Object.fromEntries(
|
|
110
|
+
Object.entries(body.rateLimitsByLimitId).map(([id, snapshot]) => [
|
|
111
|
+
id,
|
|
112
|
+
snapshotFrom(snapshot),
|
|
113
|
+
]),
|
|
114
|
+
)
|
|
115
|
+
: null;
|
|
116
|
+
|
|
117
|
+
return { rateLimits: snapshotFrom(body.rateLimits), rateLimitsByLimitId: byId };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function normalizeWhamUsage(value: JsonValue): QuotaRateLimits {
|
|
121
|
+
const body = jsonObjectOrEmpty(value);
|
|
122
|
+
|
|
123
|
+
if (isJsonObject(body.rateLimits)) return normalizeAppServerUsage(body);
|
|
124
|
+
|
|
125
|
+
const container = isJsonObject(body.rate_limits) ? body.rate_limits : body;
|
|
126
|
+
|
|
127
|
+
const shared: JsonObject = {
|
|
102
128
|
plan_type: body.plan_type ?? container.plan_type,
|
|
103
129
|
credits: body.credits ?? container.credits,
|
|
104
130
|
rate_limit_reached: body.rate_limit_reached ?? container.rate_limit_reached,
|
|
105
131
|
};
|
|
132
|
+
|
|
106
133
|
const primary = snapshotFrom({
|
|
107
134
|
...shared,
|
|
108
|
-
...(
|
|
135
|
+
...jsonObjectOrEmpty(container.rate_limit ?? container),
|
|
109
136
|
});
|
|
137
|
+
|
|
110
138
|
const additional = Array.isArray(container.additional_rate_limits)
|
|
111
139
|
? container.additional_rate_limits
|
|
112
140
|
: Array.isArray(body.additional_rate_limits)
|
|
113
141
|
? body.additional_rate_limits
|
|
114
142
|
: [];
|
|
115
|
-
|
|
143
|
+
|
|
144
|
+
const byId = new Map<string, QuotaRateLimitSnapshot>();
|
|
145
|
+
|
|
116
146
|
for (const entry of additional) {
|
|
117
|
-
if (!entry
|
|
118
|
-
|
|
147
|
+
if (!isJsonObject(entry)) continue;
|
|
148
|
+
|
|
119
149
|
const snapshot = snapshotFrom({
|
|
120
150
|
...shared,
|
|
121
|
-
...(
|
|
151
|
+
...jsonObjectOrEmpty(entry.rate_limit ?? entry),
|
|
122
152
|
});
|
|
123
|
-
|
|
153
|
+
|
|
154
|
+
if (snapshot.limitId) byId.set(snapshot.limitId, snapshot);
|
|
124
155
|
}
|
|
156
|
+
|
|
125
157
|
return {
|
|
126
158
|
rateLimits: primary,
|
|
127
|
-
rateLimitsByLimitId:
|
|
159
|
+
rateLimitsByLimitId: byId.size ? Object.fromEntries(byId) : null,
|
|
128
160
|
};
|
|
129
161
|
}
|
|
130
162
|
|
|
@@ -132,29 +164,28 @@ export async function readOpenAIQuotaUsage(
|
|
|
132
164
|
source: (() => Promise<OpenAIRateLimitsWithMeta>) | OpenAIQuotaHttpOptions,
|
|
133
165
|
): Promise<OpenAIQuotaUsageResult> {
|
|
134
166
|
try {
|
|
135
|
-
const direct =
|
|
136
|
-
const { data, cachedAt } = await (
|
|
167
|
+
const direct = "credentials" in source;
|
|
168
|
+
const { data, cachedAt } = await ("credentials" in source ? readDirect(source) : source());
|
|
137
169
|
const rateLimits = data.rateLimits;
|
|
138
170
|
const primary = rateLimits?.primary;
|
|
139
171
|
|
|
140
|
-
if (
|
|
141
|
-
!primary ||
|
|
142
|
-
typeof primary.usedPercent !== "number" ||
|
|
143
|
-
!Number.isFinite(primary.usedPercent)
|
|
144
|
-
) {
|
|
172
|
+
if (!primary || !Number.isFinite(primary.usedPercent)) {
|
|
145
173
|
return { kind: "lookup_failed", reason: "primary usedPercent unavailable" };
|
|
146
174
|
}
|
|
147
175
|
|
|
148
|
-
|
|
176
|
+
const result: OpenAIQuotaUsageResult = {
|
|
149
177
|
kind: "ok",
|
|
150
178
|
utilizationPct: primary.usedPercent,
|
|
151
179
|
cacheAgeMs: Math.max(Date.now() - cachedAt, 0),
|
|
152
180
|
windowDurationMins: primary.windowDurationMins,
|
|
153
181
|
resetsAt: primary.resetsAt,
|
|
154
182
|
limitId: rateLimits.limitId,
|
|
155
|
-
...(direct ? { raw: data } : {}),
|
|
156
183
|
};
|
|
184
|
+
|
|
185
|
+
if (direct) result.raw = data;
|
|
186
|
+
|
|
187
|
+
return result;
|
|
157
188
|
} catch (e) {
|
|
158
|
-
return { kind: "lookup_failed", reason:
|
|
189
|
+
return { kind: "lookup_failed", reason: e instanceof Error ? e.message : String(e) };
|
|
159
190
|
}
|
|
160
191
|
}
|