@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
|
@@ -6,6 +6,7 @@ import { getPrettyFormatter } from "@logtape/pretty";
|
|
|
6
6
|
import { type FastifyReply, type FastifyRequest } from "fastify";
|
|
7
7
|
import { CachePresets, defineConfig } from "sonamu";
|
|
8
8
|
import { drivers as cacheDrivers, store } from "sonamu/cache";
|
|
9
|
+
import { z } from "zod";
|
|
9
10
|
|
|
10
11
|
import { monitLogSink } from "./application/monit/log-buffer";
|
|
11
12
|
import {
|
|
@@ -14,7 +15,12 @@ import {
|
|
|
14
15
|
stopExpiredTokenReminder,
|
|
15
16
|
} from "./application/qgrid/expired-token-reminder";
|
|
16
17
|
import { QgridDispatcher } from "./application/qgrid/qgrid.dispatcher";
|
|
17
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
mapRequestBodyTooLarge,
|
|
20
|
+
persistGrokCredentials,
|
|
21
|
+
QgridFrame,
|
|
22
|
+
} from "./application/qgrid/qgrid.frame";
|
|
23
|
+
import { REQUEST_BODY_LIMIT_BYTES } from "./application/qgrid/qgrid.types";
|
|
18
24
|
import { TokenSubscriber } from "./application/qgrid/token-subscriber";
|
|
19
25
|
import { ensureTokensTrigger } from "./application/qgrid/token-trigger-setup";
|
|
20
26
|
import {
|
|
@@ -25,9 +31,18 @@ import {
|
|
|
25
31
|
import { loadSettings, setSettingChangeHandler } from "./application/setting/setting.store";
|
|
26
32
|
import { handleServerError } from "./server-error-handler";
|
|
27
33
|
import { AnthropicDispatcher } from "./utils/providers/anthropic/anthropic-dispatcher";
|
|
34
|
+
import { GrokDispatcher, GrokRefreshCoordinator } from "./utils/providers/grok";
|
|
35
|
+
import { GrokMediaClient } from "./utils/providers/grok/grok-media-client";
|
|
36
|
+
import { GrokVoiceClient } from "./utils/providers/grok/grok-voice-client";
|
|
28
37
|
import { stopOpenAICallbackRelay } from "./utils/providers/openai/openai-callback-relay";
|
|
29
38
|
import { OpenAIDispatcher } from "./utils/providers/openai/openai-dispatcher";
|
|
30
39
|
|
|
40
|
+
// Repeated query keys arrive as arrays; the callback then treats the request as missing params.
|
|
41
|
+
const oauthCallbackQuerySchema = z.looseObject({
|
|
42
|
+
code: z.string().optional(),
|
|
43
|
+
state: z.string().optional(),
|
|
44
|
+
});
|
|
45
|
+
|
|
31
46
|
const host = process.env.HOST ?? "localhost";
|
|
32
47
|
const port = Number(process.env.PORT ?? 44900);
|
|
33
48
|
|
|
@@ -93,7 +108,8 @@ export default defineConfig({
|
|
|
93
108
|
server: {
|
|
94
109
|
listen: { port, host },
|
|
95
110
|
fastify: {
|
|
96
|
-
|
|
111
|
+
// Grok media input limits are derived from this; see MEDIA_MAX_INLINE_BYTES.
|
|
112
|
+
bodyLimit: REQUEST_BODY_LIMIT_BYTES,
|
|
97
113
|
},
|
|
98
114
|
plugins: {
|
|
99
115
|
formbody: true,
|
|
@@ -106,16 +122,19 @@ export default defineConfig({
|
|
|
106
122
|
},
|
|
107
123
|
custom: (server) => {
|
|
108
124
|
const oauthCallback = async (request: FastifyRequest, reply: FastifyReply) => {
|
|
109
|
-
const { code, state } = request.query
|
|
125
|
+
const { code, state } = oauthCallbackQuerySchema.safeParse(request.query).data ?? {};
|
|
126
|
+
|
|
110
127
|
if (!code || !state) {
|
|
111
128
|
return reply.redirect("/?oauth=error&reason=missing_params");
|
|
112
129
|
}
|
|
130
|
+
|
|
113
131
|
try {
|
|
114
132
|
return QgridFrame.handleOAuthCallback(code, state, reply);
|
|
115
133
|
} catch {
|
|
116
134
|
return reply.redirect("/?oauth=error&reason=callback_failed");
|
|
117
135
|
}
|
|
118
136
|
};
|
|
137
|
+
|
|
119
138
|
server.get("/callback", oauthCallback);
|
|
120
139
|
server.get("/auth/callback", oauthCallback);
|
|
121
140
|
},
|
|
@@ -131,7 +150,9 @@ export default defineConfig({
|
|
|
131
150
|
guardHandler: () => {},
|
|
132
151
|
cacheControlHandler: (req) => {
|
|
133
152
|
if (req.type === "api") return CachePresets.noCache;
|
|
153
|
+
|
|
134
154
|
if (req.type === "csr") return CachePresets.shortLived;
|
|
155
|
+
|
|
135
156
|
// Sonamu serves fingerprinted assets with its default immutable policy.
|
|
136
157
|
return undefined;
|
|
137
158
|
},
|
|
@@ -155,17 +176,19 @@ export default defineConfig({
|
|
|
155
176
|
if (key === "slack.expiryReminderMinutes" || key === "slack.remindersEnabled") {
|
|
156
177
|
rescheduleExpiredTokenReminder();
|
|
157
178
|
}
|
|
179
|
+
|
|
158
180
|
if (key === "qgrid.tokenWindowKeepaliveEnabled") {
|
|
159
181
|
void rescheduleTokenWindowKeepalive();
|
|
160
182
|
}
|
|
161
183
|
});
|
|
162
184
|
|
|
163
185
|
let triggerReady = true;
|
|
186
|
+
|
|
164
187
|
try {
|
|
165
188
|
await ensureTokensTrigger(connConfig);
|
|
166
189
|
} catch (e) {
|
|
167
190
|
triggerReady = false;
|
|
168
|
-
log.warn(`trigger setup failed: ${
|
|
191
|
+
log.warn(`trigger setup failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
169
192
|
}
|
|
170
193
|
|
|
171
194
|
const subscriber = new TokenSubscriber(connConfig, QgridDispatcher);
|
|
@@ -182,7 +205,7 @@ export default defineConfig({
|
|
|
182
205
|
QgridDispatcher.startupState.openai = "ready";
|
|
183
206
|
} catch (e) {
|
|
184
207
|
QgridDispatcher.startupState.openai = "failed";
|
|
185
|
-
log.warn(`openai dispatcher failed: ${
|
|
208
|
+
log.warn(`openai dispatcher failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
186
209
|
}
|
|
187
210
|
|
|
188
211
|
try {
|
|
@@ -192,12 +215,40 @@ export default defineConfig({
|
|
|
192
215
|
QgridDispatcher.startupState.anthropic = "ready";
|
|
193
216
|
} catch (e) {
|
|
194
217
|
QgridDispatcher.startupState.anthropic = "failed";
|
|
195
|
-
log.warn(`anthropic dispatcher failed: ${
|
|
218
|
+
log.warn(`anthropic dispatcher failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
try {
|
|
222
|
+
const grokRefresher = new GrokRefreshCoordinator({ persist: persistGrokCredentials });
|
|
223
|
+
|
|
224
|
+
const grokDispatcher = new GrokDispatcher({
|
|
225
|
+
refresher: grokRefresher,
|
|
226
|
+
media: new GrokMediaClient(),
|
|
227
|
+
voice: new GrokVoiceClient(),
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
await grokDispatcher.start();
|
|
231
|
+
QgridFrame.grokRefresher = grokRefresher;
|
|
232
|
+
QgridDispatcher.grokDispatcher = grokDispatcher;
|
|
233
|
+
QgridDispatcher.startupState.grok = "ready";
|
|
234
|
+
// The subscriber's startup reconcile ran before this dispatcher existed; a failure here
|
|
235
|
+
// is retried by the periodic reconcile and does not make the runtime unusable.
|
|
236
|
+
await subscriber
|
|
237
|
+
.reconcile()
|
|
238
|
+
.catch((e) =>
|
|
239
|
+
log.warn(
|
|
240
|
+
`grok initial reconcile failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
241
|
+
),
|
|
242
|
+
);
|
|
243
|
+
} catch (e) {
|
|
244
|
+
QgridDispatcher.startupState.grok = "failed";
|
|
245
|
+
log.warn(`grok dispatcher failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
196
246
|
}
|
|
197
247
|
|
|
198
248
|
subscriber.tokenChangeHandler = () => {
|
|
199
249
|
void rescheduleTokenWindowKeepalive();
|
|
200
250
|
};
|
|
251
|
+
|
|
201
252
|
startExpiredTokenReminder();
|
|
202
253
|
void startTokenWindowKeepalive();
|
|
203
254
|
|
|
@@ -217,20 +268,31 @@ export default defineConfig({
|
|
|
217
268
|
stopTokenWindowKeepalive();
|
|
218
269
|
stopOpenAICallbackRelay();
|
|
219
270
|
setSettingChangeHandler(null);
|
|
271
|
+
|
|
220
272
|
if (QgridDispatcher.subscriber) QgridDispatcher.subscriber.tokenChangeHandler = null;
|
|
273
|
+
|
|
221
274
|
if (QgridDispatcher.openaiDispatcher) {
|
|
222
275
|
await QgridDispatcher.openaiDispatcher.stop();
|
|
223
276
|
}
|
|
277
|
+
|
|
224
278
|
if (QgridDispatcher.anthropicDispatcher) {
|
|
225
279
|
await QgridDispatcher.anthropicDispatcher.stop();
|
|
226
280
|
}
|
|
281
|
+
|
|
282
|
+
const grokDispatcher = QgridDispatcher.grokDispatcher;
|
|
283
|
+
|
|
284
|
+
if (grokDispatcher instanceof GrokDispatcher) {
|
|
285
|
+
await grokDispatcher.stop();
|
|
286
|
+
}
|
|
287
|
+
|
|
227
288
|
if (QgridDispatcher.subscriber) {
|
|
228
289
|
await QgridDispatcher.subscriber.stop();
|
|
229
290
|
}
|
|
291
|
+
|
|
230
292
|
log.info("graceful shutdown");
|
|
231
293
|
},
|
|
232
294
|
onError: (error, _request, reply) => {
|
|
233
|
-
handleServerError(error, reply);
|
|
295
|
+
handleServerError(mapRequestBodyTooLarge(error), reply);
|
|
234
296
|
},
|
|
235
297
|
},
|
|
236
298
|
},
|
|
@@ -5,6 +5,7 @@ import { pathToFileURL } from "node:url";
|
|
|
5
5
|
|
|
6
6
|
import { getLogger } from "@logtape/logtape";
|
|
7
7
|
import { type Knex } from "knex";
|
|
8
|
+
import { z } from "zod";
|
|
8
9
|
|
|
9
10
|
import { TokenModel } from "./application/token/token.model";
|
|
10
11
|
|
|
@@ -30,10 +31,13 @@ export function resolveStartupMigrationDirs(
|
|
|
30
31
|
// Sonamu 가 만드는 파일명 규칙: `YYYYMMDDHHmmss_<title>.ts`. 타입 선언이나 README 는 제외한다.
|
|
31
32
|
const MIGRATION_FILE_RE = /^\d{14}_.+\.ts$/;
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
// z.function() checks callability; getMigration wraps the calls in knex's migration contract.
|
|
35
|
+
const migrationFunctionSchema = z.function({ input: [z.unknown()], output: z.unknown() });
|
|
36
|
+
|
|
37
|
+
const migrationModuleSchema = z.object({
|
|
38
|
+
up: migrationFunctionSchema,
|
|
39
|
+
down: migrationFunctionSchema.optional(),
|
|
40
|
+
});
|
|
37
41
|
|
|
38
42
|
/**
|
|
39
43
|
* 패키지된 CLI 는 `bundle/dist` 에서 실행되는데, knex 기본 FsMigrations 에 `../src/migrations` 를
|
|
@@ -48,6 +52,7 @@ export function createStartupMigrationSource(
|
|
|
48
52
|
return {
|
|
49
53
|
async getMigrations() {
|
|
50
54
|
const entries = await readdir(dirs.sourceDir);
|
|
55
|
+
|
|
51
56
|
return entries
|
|
52
57
|
.filter((name) => MIGRATION_FILE_RE.test(name) && !name.endsWith(".d.ts"))
|
|
53
58
|
.toSorted();
|
|
@@ -59,11 +64,18 @@ export function createStartupMigrationSource(
|
|
|
59
64
|
const compiled = path.join(dirs.compiledDir, name.replace(/\.ts$/, ".js"));
|
|
60
65
|
const source = path.join(dirs.sourceDir, name);
|
|
61
66
|
const target = existsSync(compiled) ? compiled : source;
|
|
62
|
-
const module = (await import(pathToFileURL(target).href))
|
|
63
|
-
|
|
67
|
+
const module = migrationModuleSchema.safeParse(await import(pathToFileURL(target).href));
|
|
68
|
+
|
|
69
|
+
if (!module.success) {
|
|
64
70
|
throw new Error(`migration ${name} has no up() export (${target})`);
|
|
65
71
|
}
|
|
66
|
-
|
|
72
|
+
|
|
73
|
+
const { up, down } = module.data;
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
up: async (knex: Knex) => up(knex),
|
|
77
|
+
down: async (knex: Knex) => down?.(knex),
|
|
78
|
+
};
|
|
67
79
|
},
|
|
68
80
|
};
|
|
69
81
|
}
|
|
@@ -77,19 +89,25 @@ export type StartupMigrationDeps = {
|
|
|
77
89
|
// 실제 process.exit(1) 이 worker 를 죽여 남은 테스트가 조용히 pending 으로 빠진다.
|
|
78
90
|
export async function runRequiredMigrations(
|
|
79
91
|
dirs: StartupMigrationDirs = resolveStartupMigrationDirs(),
|
|
80
|
-
deps: StartupMigrationDeps = {
|
|
92
|
+
deps: StartupMigrationDeps = {
|
|
93
|
+
getKnex: () => TokenModel.getDB("w"),
|
|
94
|
+
exit: (code) => process.exit(code),
|
|
95
|
+
},
|
|
81
96
|
): Promise<void> {
|
|
82
97
|
const log = getLogger(["qgrid", "startup"]);
|
|
98
|
+
|
|
83
99
|
try {
|
|
84
100
|
const knex = deps.getKnex();
|
|
101
|
+
|
|
85
102
|
const [batch, migrations] = await knex.migrate.latest({
|
|
86
103
|
migrationSource: createStartupMigrationSource(dirs),
|
|
87
104
|
});
|
|
105
|
+
|
|
88
106
|
if (migrations.length > 0) {
|
|
89
107
|
log.info(`migration: ${migrations.length} applied (batch ${batch})`);
|
|
90
108
|
}
|
|
91
109
|
} catch (error) {
|
|
92
|
-
log.error(`migration failed: ${
|
|
110
|
+
log.error(`migration failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
93
111
|
deps.exit(1);
|
|
94
112
|
}
|
|
95
113
|
}
|
|
@@ -7,14 +7,18 @@ const MAX_WORKERS = 4;
|
|
|
7
7
|
const TEMPLATE_DB = process.env.QGRID_TEST_DB_NAME ?? "qgrid_test";
|
|
8
8
|
|
|
9
9
|
type QueryClient = Pick<Client, "query">;
|
|
10
|
+
|
|
10
11
|
type AdminClient = Pick<Client, "connect" | "end" | "query">;
|
|
12
|
+
|
|
11
13
|
type AdminClientFactory = () => AdminClient;
|
|
14
|
+
|
|
12
15
|
type VitestProject = { config?: { maxWorkers?: number } };
|
|
13
16
|
|
|
14
17
|
function quoteIdentifier(value: string): string {
|
|
15
18
|
if (!/^[A-Za-z0-9_]+$/.test(value)) {
|
|
16
19
|
throw new Error(`invalid database identifier: ${value}`);
|
|
17
20
|
}
|
|
21
|
+
|
|
18
22
|
return `"${value}"`;
|
|
19
23
|
}
|
|
20
24
|
|
|
@@ -41,15 +45,18 @@ export async function recreateWorkerDatabases(
|
|
|
41
45
|
maxWorkers: number,
|
|
42
46
|
): Promise<string[]> {
|
|
43
47
|
const workers = workerDatabaseNames(runDatabase, maxWorkers);
|
|
48
|
+
|
|
44
49
|
for (const database of [...workers, templateDb]) {
|
|
45
50
|
await terminateConnections(client, database);
|
|
46
51
|
}
|
|
52
|
+
|
|
47
53
|
for (const database of workers) {
|
|
48
54
|
await client.query(`DROP DATABASE IF EXISTS ${quoteIdentifier(database)}`);
|
|
49
55
|
await client.query(
|
|
50
56
|
`CREATE DATABASE ${quoteIdentifier(database)} TEMPLATE ${quoteIdentifier(templateDb)} STRATEGY FILE_COPY`,
|
|
51
57
|
);
|
|
52
58
|
}
|
|
59
|
+
|
|
53
60
|
return workers;
|
|
54
61
|
}
|
|
55
62
|
|
|
@@ -59,6 +66,7 @@ export async function dropWorkerDatabases(
|
|
|
59
66
|
maxWorkers: number,
|
|
60
67
|
): Promise<void> {
|
|
61
68
|
const errors: unknown[] = [];
|
|
69
|
+
|
|
62
70
|
for (const database of workerDatabaseNames(templateDb, maxWorkers)) {
|
|
63
71
|
await terminateConnections(client, database).catch((error) => {
|
|
64
72
|
errors.push(error);
|
|
@@ -67,10 +75,12 @@ export async function dropWorkerDatabases(
|
|
|
67
75
|
errors.push(error);
|
|
68
76
|
});
|
|
69
77
|
}
|
|
78
|
+
|
|
70
79
|
if (errors.length > 0) {
|
|
71
80
|
const cleanupError = new AggregateError(errors, "failed to clean one or more test databases", {
|
|
72
81
|
cause: errors[0],
|
|
73
82
|
});
|
|
83
|
+
|
|
74
84
|
throw cleanupError;
|
|
75
85
|
}
|
|
76
86
|
}
|
|
@@ -88,6 +98,7 @@ function createAdminClient(): Client {
|
|
|
88
98
|
|
|
89
99
|
export function resolveMaxWorkers(project?: VitestProject): number {
|
|
90
100
|
const maxWorkers = project?.config?.maxWorkers;
|
|
101
|
+
|
|
91
102
|
return Number.isInteger(maxWorkers) && maxWorkers !== undefined && maxWorkers > 0
|
|
92
103
|
? maxWorkers
|
|
93
104
|
: MAX_WORKERS;
|
|
@@ -101,12 +112,14 @@ export async function provisionWorkerDatabases(
|
|
|
101
112
|
): Promise<void> {
|
|
102
113
|
const client = createClient();
|
|
103
114
|
await client.connect();
|
|
115
|
+
|
|
104
116
|
try {
|
|
105
117
|
await recreateWorkerDatabases(client, templateDb, runDatabase, maxWorkers);
|
|
106
118
|
} catch (setupError) {
|
|
107
119
|
await client.end().catch(() => {});
|
|
108
120
|
const cleanupClient = createClient();
|
|
109
121
|
let cleanupError: unknown;
|
|
122
|
+
|
|
110
123
|
try {
|
|
111
124
|
await cleanupClient.connect();
|
|
112
125
|
await dropWorkerDatabases(cleanupClient, runDatabase, maxWorkers);
|
|
@@ -117,30 +130,37 @@ export async function provisionWorkerDatabases(
|
|
|
117
130
|
cleanupError ??= error;
|
|
118
131
|
});
|
|
119
132
|
}
|
|
133
|
+
|
|
120
134
|
if (cleanupError) {
|
|
121
135
|
const combinedError = new AggregateError(
|
|
122
136
|
[setupError, cleanupError],
|
|
123
137
|
"test database setup and cleanup both failed",
|
|
124
138
|
{ cause: setupError },
|
|
125
139
|
);
|
|
140
|
+
|
|
126
141
|
throw combinedError;
|
|
127
142
|
}
|
|
143
|
+
|
|
128
144
|
throw setupError;
|
|
129
145
|
}
|
|
146
|
+
|
|
130
147
|
await client.end();
|
|
131
148
|
}
|
|
132
149
|
|
|
133
150
|
export async function setup(project?: VitestProject): Promise<() => Promise<void>> {
|
|
134
151
|
const runDatabase = process.env.QGRID_TEST_RUN_DB;
|
|
152
|
+
|
|
135
153
|
if (!runDatabase || process.env.SONAMU_DB_NAME !== runDatabase) {
|
|
136
154
|
throw new Error("Vitest test database name was not configured consistently");
|
|
137
155
|
}
|
|
156
|
+
|
|
138
157
|
const maxWorkers = resolveMaxWorkers(project);
|
|
139
158
|
await provisionWorkerDatabases(createAdminClient, TEMPLATE_DB, runDatabase, maxWorkers);
|
|
140
159
|
|
|
141
160
|
return async () => {
|
|
142
161
|
const teardownClient = createAdminClient();
|
|
143
162
|
await teardownClient.connect();
|
|
163
|
+
|
|
144
164
|
try {
|
|
145
165
|
await dropWorkerDatabases(teardownClient, runDatabase, maxWorkers);
|
|
146
166
|
} finally {
|
|
@@ -12,6 +12,13 @@
|
|
|
12
12
|
* syntax/contract 와 겹칠 수 있고, 게이트 계수는 이 축 기준이다.
|
|
13
13
|
*/
|
|
14
14
|
import Ajv2020, { type ValidateFunction } from "ajv/dist/2020";
|
|
15
|
+
import { z } from "zod";
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
isJsonObject,
|
|
19
|
+
type JsonObject,
|
|
20
|
+
type JsonValue,
|
|
21
|
+
} from "../utils/providers/common/provider-types";
|
|
15
22
|
|
|
16
23
|
export type BenchMode = "generate" | "stream";
|
|
17
24
|
|
|
@@ -26,15 +33,18 @@ export interface BenchClassifyOptions {
|
|
|
26
33
|
* 검증한다 — top-level 키 존재만 보면 `{"scenes":[{},{},{}]}` 같은 속 빈 응답이
|
|
27
34
|
* 통과한다(SON-495 교훈: 상위 키 판정은 쓰레기를 통과시킨다).
|
|
28
35
|
*/
|
|
29
|
-
schema?:
|
|
36
|
+
schema?: JsonValue;
|
|
30
37
|
/** 최상위에 반드시 있어야 하는 키 (schema 미지정 시의 최소 검증) */
|
|
31
38
|
requiredTopLevelKeys?: string[];
|
|
32
39
|
/** 허용되는 최상위 키 전체 — 래퍼 키 판정에 사용 */
|
|
33
40
|
allowedTopLevelKeys?: string[];
|
|
34
41
|
/** fixture 별 추가 계약 검증. 실패 사유 문자열을 반환하면 contract fail. */
|
|
35
|
-
validate?: (parsed:
|
|
42
|
+
validate?: (parsed: JsonValue, rawText: string) => string | undefined;
|
|
36
43
|
}
|
|
37
44
|
|
|
45
|
+
// Ajv reports enum violations with the allowed values in `params.allowedValues`.
|
|
46
|
+
const EnumErrorParams = z.looseObject({ allowedValues: z.array(z.json()).optional() });
|
|
47
|
+
|
|
38
48
|
/**
|
|
39
49
|
* 표준 JSON Schema 검증 (Ajv, Draft 2020-12). 위반 경로 목록을 반환한다.
|
|
40
50
|
*
|
|
@@ -43,21 +53,27 @@ export interface BenchClassifyOptions {
|
|
|
43
53
|
* 뒤집혔다(anyOf 브랜치 내부의 enum 위반을 통째로 건너뜀). 임의 스키마(BENCH_SCHEMA_FILE)
|
|
44
54
|
* 를 게이트로 쓰려면 표준 검증기가 유일하게 신뢰 가능하다.
|
|
45
55
|
*/
|
|
46
|
-
export function validateAgainstSchema(node:
|
|
47
|
-
if (
|
|
48
|
-
const validate = compileSchema(schema
|
|
56
|
+
export function validateAgainstSchema(node: JsonValue, schema: JsonValue): string[] {
|
|
57
|
+
if (!isJsonObject(schema)) return [];
|
|
58
|
+
const validate = compileSchema(schema);
|
|
59
|
+
|
|
49
60
|
if (validate(node)) return [];
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
61
|
+
|
|
62
|
+
return (validate.errors ?? []).map((e) => {
|
|
63
|
+
const allowed = EnumErrorParams.safeParse(e.params).data?.allowedValues;
|
|
64
|
+
|
|
65
|
+
const enumDetail = e.keyword === "enum" ? ` (${JSON.stringify(allowed?.slice(0, 8))})` : "";
|
|
66
|
+
|
|
67
|
+
return `${e.instancePath || "$"}: ${e.message ?? e.keyword}${enumDetail}`;
|
|
68
|
+
});
|
|
54
69
|
}
|
|
55
70
|
|
|
56
71
|
const compiledSchemas = new WeakMap<object, ValidateFunction>();
|
|
57
72
|
let ajvInstance: Ajv2020 | undefined;
|
|
58
73
|
|
|
59
|
-
function compileSchema(schema:
|
|
74
|
+
function compileSchema(schema: JsonObject): ValidateFunction {
|
|
60
75
|
const cached = compiledSchemas.get(schema);
|
|
76
|
+
|
|
61
77
|
if (cached) return cached;
|
|
62
78
|
// strict:false — 소비자 스키마의 비표준 어노테이션(description 변형 등)을 컴파일
|
|
63
79
|
// 에러로 만들지 않는다. 검증 시맨틱은 표준 그대로다.
|
|
@@ -65,13 +81,16 @@ function compileSchema(schema: Record<string, unknown>): ValidateFunction {
|
|
|
65
81
|
// $id 가 있는 스키마를 두 번 compile 하면 Ajv 가 중복 등록으로 던진다 — WeakMap 캐시가
|
|
66
82
|
// 같은 객체의 재컴파일을 막고, 다른 객체로 온 같은 $id 는 인스턴스를 새로 만들어 회피한다.
|
|
67
83
|
let validate: ValidateFunction;
|
|
84
|
+
|
|
68
85
|
try {
|
|
69
86
|
validate = ajvInstance.compile(schema);
|
|
70
87
|
} catch {
|
|
71
88
|
ajvInstance = new Ajv2020({ strict: false, allErrors: true });
|
|
72
89
|
validate = ajvInstance.compile(schema);
|
|
73
90
|
}
|
|
91
|
+
|
|
74
92
|
compiledSchemas.set(schema, validate);
|
|
93
|
+
|
|
75
94
|
return validate;
|
|
76
95
|
}
|
|
77
96
|
|
|
@@ -101,17 +120,24 @@ export function classifyBenchText(
|
|
|
101
120
|
|
|
102
121
|
if (options.plainText) {
|
|
103
122
|
const reason = options.validate?.(text, text);
|
|
104
|
-
|
|
123
|
+
|
|
124
|
+
const classification: BenchClassification = {
|
|
105
125
|
syntax: "ok",
|
|
106
126
|
contract: reason === undefined ? "ok" : "fail",
|
|
107
|
-
...(reason === undefined ? {} : { contractDetail: reason }),
|
|
108
127
|
topLevel: { wrapperKey: false, fenceResidue, prose: false },
|
|
109
128
|
};
|
|
129
|
+
|
|
130
|
+
if (reason !== undefined) classification.contractDetail = reason;
|
|
131
|
+
|
|
132
|
+
return classification;
|
|
110
133
|
}
|
|
111
134
|
|
|
112
|
-
|
|
135
|
+
// Placeholder until parsing succeeds; it is read only when `syntax` stays "ok".
|
|
136
|
+
let parsed: JsonValue = null;
|
|
113
137
|
let syntax: "ok" | "fail" = "ok";
|
|
138
|
+
|
|
114
139
|
try {
|
|
140
|
+
// JSON.parse without a reviver yields only JSON values.
|
|
115
141
|
parsed = JSON.parse(text);
|
|
116
142
|
} catch {
|
|
117
143
|
syntax = "fail";
|
|
@@ -124,27 +150,24 @@ export function classifyBenchText(
|
|
|
124
150
|
let contractDetail: string | undefined;
|
|
125
151
|
|
|
126
152
|
if (syntax === "ok") {
|
|
127
|
-
const record =
|
|
128
|
-
parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)
|
|
129
|
-
? (parsed as Record<string, unknown>)
|
|
130
|
-
: undefined;
|
|
153
|
+
const record = isJsonObject(parsed) ? parsed : undefined;
|
|
131
154
|
|
|
132
155
|
if (record && options.allowedTopLevelKeys) {
|
|
133
156
|
const keys = Object.keys(record);
|
|
134
157
|
const allowed = new Set(options.allowedTopLevelKeys);
|
|
158
|
+
const only = record[keys[0]!];
|
|
135
159
|
wrapperKey =
|
|
136
|
-
keys.length === 1 &&
|
|
137
|
-
!allowed.has(keys[0]!) &&
|
|
138
|
-
typeof record[keys[0]!] === "object" &&
|
|
139
|
-
record[keys[0]!] !== null;
|
|
160
|
+
keys.length === 1 && !allowed.has(keys[0]!) && (isJsonObject(only) || Array.isArray(only));
|
|
140
161
|
}
|
|
141
162
|
|
|
142
163
|
contract = "ok";
|
|
164
|
+
|
|
143
165
|
if (record === undefined) {
|
|
144
166
|
contract = "fail";
|
|
145
167
|
contractDetail = "top-level is not an object";
|
|
146
168
|
} else if (options.schema !== undefined) {
|
|
147
169
|
const violations = validateAgainstSchema(parsed, options.schema);
|
|
170
|
+
|
|
148
171
|
if (violations.length > 0) {
|
|
149
172
|
contract = "fail";
|
|
150
173
|
contractDetail = `${violations.length} schema violations: ${violations.slice(0, 5).join("; ")}`;
|
|
@@ -153,13 +176,16 @@ export function classifyBenchText(
|
|
|
153
176
|
const missing = (options.requiredTopLevelKeys ?? []).filter(
|
|
154
177
|
(key) => !Object.hasOwn(record, key),
|
|
155
178
|
);
|
|
179
|
+
|
|
156
180
|
if (missing.length > 0) {
|
|
157
181
|
contract = "fail";
|
|
158
182
|
contractDetail = `missing required keys: ${missing.join(", ")}`;
|
|
159
183
|
}
|
|
160
184
|
}
|
|
185
|
+
|
|
161
186
|
if (contract === "ok" && options.validate) {
|
|
162
187
|
const reason = options.validate(parsed, text);
|
|
188
|
+
|
|
163
189
|
if (reason !== undefined) {
|
|
164
190
|
contract = "fail";
|
|
165
191
|
contractDetail = reason;
|
|
@@ -201,9 +227,11 @@ export function fingerprintFixture(content: {
|
|
|
201
227
|
}): string {
|
|
202
228
|
const text = `${content.prompt}${content.jsonSchema ?? ""}${JSON.stringify(content.tools ?? null)}`;
|
|
203
229
|
let hash = 5381;
|
|
230
|
+
|
|
204
231
|
for (let i = 0; i < text.length; i += 1) {
|
|
205
232
|
hash = ((hash << 5) + hash + text.charCodeAt(i)) >>> 0;
|
|
206
233
|
}
|
|
234
|
+
|
|
207
235
|
return hash.toString(16).padStart(8, "0");
|
|
208
236
|
}
|
|
209
237
|
|
|
@@ -213,18 +241,50 @@ export function checkpointKey(
|
|
|
213
241
|
return `${record.model}|${record.fixture}|${record.fixtureHash ?? "-"}|${record.mode}|${record.iteration}`;
|
|
214
242
|
}
|
|
215
243
|
|
|
244
|
+
const TopLevelViolationsSchema = z.object({
|
|
245
|
+
wrapperKey: z.boolean(),
|
|
246
|
+
fenceResidue: z.boolean(),
|
|
247
|
+
prose: z.boolean(),
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
const BenchRecordSchema = z.object({
|
|
251
|
+
fixture: z.string(),
|
|
252
|
+
fixtureHash: z.string().optional(),
|
|
253
|
+
model: z.string(),
|
|
254
|
+
mode: z.enum(["generate", "stream"]),
|
|
255
|
+
iteration: z.number(),
|
|
256
|
+
transportError: z.string().optional(),
|
|
257
|
+
classification: z
|
|
258
|
+
.object({
|
|
259
|
+
syntax: z.enum(["ok", "fail"]),
|
|
260
|
+
contract: z.enum(["ok", "fail", "n/a"]),
|
|
261
|
+
contractDetail: z.string().optional(),
|
|
262
|
+
topLevel: TopLevelViolationsSchema,
|
|
263
|
+
})
|
|
264
|
+
.optional(),
|
|
265
|
+
deltaDoneMismatch: z.boolean().optional(),
|
|
266
|
+
durationMs: z.number(),
|
|
267
|
+
outputTokens: z.number().optional(),
|
|
268
|
+
costUsd: z.number().optional(),
|
|
269
|
+
}) satisfies z.ZodType<BenchRecord>;
|
|
270
|
+
|
|
216
271
|
/** JSONL checkpoint 파일 파싱. 깨진 줄(중단 시 부분 기록)은 건너뛴다. */
|
|
217
272
|
export function parseCheckpointLines(jsonl: string): Map<string, BenchRecord> {
|
|
218
273
|
const map = new Map<string, BenchRecord>();
|
|
274
|
+
|
|
219
275
|
for (const line of jsonl.split("\n")) {
|
|
220
276
|
if (!line.trim()) continue;
|
|
277
|
+
|
|
221
278
|
try {
|
|
222
|
-
const record = JSON.parse(line)
|
|
223
|
-
|
|
279
|
+
const record = BenchRecordSchema.safeParse(JSON.parse(line));
|
|
280
|
+
|
|
281
|
+
// A line that does not match the record shape is skipped like a partial write.
|
|
282
|
+
if (record.success) map.set(checkpointKey(record.data), record.data);
|
|
224
283
|
} catch {
|
|
225
284
|
// 부분 기록된 마지막 줄 — resume 시 해당 조합만 재발사된다
|
|
226
285
|
}
|
|
227
286
|
}
|
|
287
|
+
|
|
228
288
|
return map;
|
|
229
289
|
}
|
|
230
290
|
|
|
@@ -251,20 +311,32 @@ export interface BenchAggregateRow {
|
|
|
251
311
|
}
|
|
252
312
|
|
|
253
313
|
export function aggregateBench(records: BenchRecord[]): BenchAggregateRow[] {
|
|
254
|
-
const groups = new Map<
|
|
314
|
+
const groups = new Map<
|
|
315
|
+
string,
|
|
316
|
+
{ model: string; fixture: string; mode: BenchMode; bucket: BenchRecord[] }
|
|
317
|
+
>();
|
|
318
|
+
|
|
255
319
|
for (const record of records) {
|
|
256
320
|
const key = `${record.model}|${record.fixture}|${record.mode}`;
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
321
|
+
const group = groups.get(key);
|
|
322
|
+
|
|
323
|
+
if (group) group.bucket.push(record);
|
|
324
|
+
else {
|
|
325
|
+
groups.set(key, {
|
|
326
|
+
model: record.model,
|
|
327
|
+
fixture: record.fixture,
|
|
328
|
+
mode: record.mode,
|
|
329
|
+
bucket: [record],
|
|
330
|
+
});
|
|
331
|
+
}
|
|
260
332
|
}
|
|
261
333
|
|
|
262
|
-
return [...groups.
|
|
263
|
-
.map((
|
|
264
|
-
const [model, fixture, mode] = key.split("|") as [string, string, BenchMode];
|
|
334
|
+
return [...groups.values()]
|
|
335
|
+
.map(({ model, fixture, mode, bucket }) => {
|
|
265
336
|
const transportErrors = bucket.filter((r) => r.transportError !== undefined).length;
|
|
266
337
|
const syntaxFails = bucket.filter((r) => r.classification?.syntax === "fail").length;
|
|
267
338
|
const contractFails = bucket.filter((r) => r.classification?.contract === "fail").length;
|
|
339
|
+
|
|
268
340
|
// pass = 모든 축이 깨끗한 응답 — 형태 위반(래퍼/펜스/프로즈)과 스트림 델타≠done 도
|
|
269
341
|
// 실패다. syntax/contract 만 보면 "통과인데 펜스가 남은" 응답이 성공으로 집계된다
|
|
270
342
|
// (2차 검토 #7).
|
|
@@ -278,6 +350,7 @@ export function aggregateBench(records: BenchRecord[]): BenchAggregateRow[] {
|
|
|
278
350
|
!r.classification.topLevel.prose &&
|
|
279
351
|
r.deltaDoneMismatch !== true,
|
|
280
352
|
).length;
|
|
353
|
+
|
|
281
354
|
const count = (pick: (v: TopLevelViolations) => boolean) =>
|
|
282
355
|
bucket.filter((r) => r.classification !== undefined && pick(r.classification.topLevel))
|
|
283
356
|
.length;
|
|
@@ -328,6 +401,7 @@ export function formatAggregateTable(rows: BenchAggregateRow[]): string {
|
|
|
328
401
|
"outTok",
|
|
329
402
|
"cost$",
|
|
330
403
|
];
|
|
404
|
+
|
|
331
405
|
const lines = rows.map((r) =>
|
|
332
406
|
[
|
|
333
407
|
r.model,
|
|
@@ -348,5 +422,6 @@ export function formatAggregateTable(rows: BenchAggregateRow[]): string {
|
|
|
348
422
|
r.totalCostUsd.toFixed(4),
|
|
349
423
|
].join("\t"),
|
|
350
424
|
);
|
|
425
|
+
|
|
351
426
|
return [header.join("\t"), ...lines].join("\n");
|
|
352
427
|
}
|
|
@@ -6,6 +6,7 @@ export function createTestRunDatabaseName(prefix: string, entropy: string): stri
|
|
|
6
6
|
const safePrefix = prefix.replaceAll(/[^A-Za-z0-9_]/g, "_");
|
|
7
7
|
const safeEntropy = entropy.replaceAll(/[^A-Za-z0-9_]/g, "_");
|
|
8
8
|
const maxPrefixLength = 63 - safeEntropy.length - 1;
|
|
9
|
+
|
|
9
10
|
return `${safePrefix.slice(0, maxPrefixLength)}_${safeEntropy}`;
|
|
10
11
|
}
|
|
11
12
|
|
|
@@ -18,5 +19,6 @@ export function configureTestRunDatabaseName(): string {
|
|
|
18
19
|
|
|
19
20
|
process.env.QGRID_TEST_RUN_DB = configuredRunDatabase;
|
|
20
21
|
process.env.SONAMU_DB_NAME = configuredRunDatabase;
|
|
22
|
+
|
|
21
23
|
return configuredRunDatabase;
|
|
22
24
|
}
|