@cartanova/qgrid-cli 2.10.9 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (318) hide show
  1. package/bundle/dist/_virtual/_rolldown/runtime.js +1 -10
  2. package/bundle/dist/application/monit/log-buffer.js +12 -6
  3. package/bundle/dist/application/monit/monit.frame.js +11 -3
  4. package/bundle/dist/application/monit/monit.types.js +1 -1
  5. package/bundle/dist/application/qgrid/conv-routing.js +9 -6
  6. package/bundle/dist/application/qgrid/expired-token-reminder.js +2 -2
  7. package/bundle/dist/application/qgrid/oauth.js +31 -4
  8. package/bundle/dist/application/qgrid/qgrid-image-generation.js +1 -1
  9. package/bundle/dist/application/qgrid/qgrid-response-format.js +22 -20
  10. package/bundle/dist/application/qgrid/qgrid-run-lifecycle.js +43 -34
  11. package/bundle/dist/application/qgrid/qgrid.dispatcher.js +128 -93
  12. package/bundle/dist/application/qgrid/qgrid.frame.js +930 -49
  13. package/bundle/dist/application/qgrid/qgrid.types.js +212 -5
  14. package/bundle/dist/application/qgrid/schema-prompt.js +75 -48
  15. package/bundle/dist/application/qgrid/slack-user-map.js +1 -1
  16. package/bundle/dist/application/qgrid/token-death.js +1 -1
  17. package/bundle/dist/application/qgrid/token-subscriber.js +83 -30
  18. package/bundle/dist/application/qgrid/token-trigger-setup.js +1 -1
  19. package/bundle/dist/application/qgrid/token-window-keepalive.js +8 -6
  20. package/bundle/dist/application/qgrid/tool-emulation-schema.js +13 -13
  21. package/bundle/dist/application/qgrid/tool-emulation.js +19 -15
  22. package/bundle/dist/application/request-log/json-schema-type-text.js +29 -12
  23. package/bundle/dist/application/request-log/request-log.model.js +83 -69
  24. package/bundle/dist/application/request-log/request-log.types.js +8 -2
  25. package/bundle/dist/application/request-log/zod-code-format.js +1 -1
  26. package/bundle/dist/application/request-log-step/request-log-step.model.js +1 -1
  27. package/bundle/dist/application/request-log-step/request-log-step.types.js +1 -1
  28. package/bundle/dist/application/setting/setting.constant.js +1 -1
  29. package/bundle/dist/application/setting/setting.model.js +2 -2
  30. package/bundle/dist/application/setting/setting.store.js +2 -2
  31. package/bundle/dist/application/setting/setting.types.js +1 -1
  32. package/bundle/dist/application/sonamu.generated.js +40 -16
  33. package/bundle/dist/application/sonamu.generated.sso.js +2 -1
  34. package/bundle/dist/application/token/token.model.js +26 -9
  35. package/bundle/dist/application/token/token.types.js +20 -3
  36. package/bundle/dist/i18n/en.js +15 -1
  37. package/bundle/dist/i18n/ko.js +15 -1
  38. package/bundle/dist/i18n/sd.generated.js +2 -1
  39. package/bundle/dist/migrations/20260807133553_manual__backfill_request_log_cost.js +8 -6
  40. package/bundle/dist/migrations/20261018215031_alter_request_logs_add1_alter1.js +15 -0
  41. package/bundle/dist/server-bootstrap.js +4 -2
  42. package/bundle/dist/server-error-handler.js +1 -1
  43. package/bundle/dist/sonamu.config.js +37 -8
  44. package/bundle/dist/startup-migrations.js +17 -7
  45. package/bundle/dist/testing/global.js +1 -1
  46. package/bundle/dist/testing/schema-bench.js +0 -0
  47. package/bundle/dist/testing/test-database-name.js +1 -1
  48. package/bundle/dist/utils/providers/anthropic/anthropic-constants.js +1 -1
  49. package/bundle/dist/utils/providers/anthropic/anthropic-dispatcher.js +11 -5
  50. package/bundle/dist/utils/providers/anthropic/anthropic-quota.js +4 -4
  51. package/bundle/dist/utils/providers/anthropic/anthropic-thinking.js +1 -1
  52. package/bundle/dist/utils/providers/anthropic/claude-session.js +20 -19
  53. package/bundle/dist/utils/providers/anthropic/fence-strip.js +1 -1
  54. package/bundle/dist/utils/providers/anthropic/stream-json-adapter.js +49 -23
  55. package/bundle/dist/utils/providers/common/credentials.js +5 -1
  56. package/bundle/dist/utils/providers/common/effort.js +3 -3
  57. package/bundle/dist/utils/providers/common/model-cost.js +69 -14
  58. package/bundle/dist/utils/providers/common/provider-types.js +29 -1
  59. package/bundle/dist/utils/providers/common/schema-validation.js +78 -40
  60. package/bundle/dist/utils/providers/common/smooth-weighted-round-robin.js +1 -1
  61. package/bundle/dist/utils/providers/common/strictifier.js +62 -43
  62. package/bundle/dist/utils/providers/common/thinking.js +3 -2
  63. package/bundle/dist/utils/providers/common/ttft.js +1 -1
  64. package/bundle/dist/utils/providers/grok/grok-backend-protocol.js +427 -0
  65. package/bundle/dist/utils/providers/grok/grok-constants.js +55 -0
  66. package/bundle/dist/utils/providers/grok/grok-direct-client.js +196 -0
  67. package/bundle/dist/utils/providers/grok/grok-dispatcher.js +1313 -0
  68. package/bundle/dist/utils/providers/grok/grok-headers.js +118 -0
  69. package/bundle/dist/utils/providers/grok/grok-media-client.js +420 -0
  70. package/bundle/dist/utils/providers/grok/grok-models.js +114 -0
  71. package/bundle/dist/utils/providers/grok/grok-oauth.js +455 -0
  72. package/bundle/dist/utils/providers/grok/grok-quota.js +119 -0
  73. package/bundle/dist/utils/providers/grok/grok-refresh.js +192 -0
  74. package/bundle/dist/utils/providers/grok/grok-types.js +186 -0
  75. package/bundle/dist/utils/providers/grok/grok-voice-client.js +199 -0
  76. package/bundle/dist/utils/providers/grok/index.js +11 -0
  77. package/bundle/dist/utils/providers/openai/openai-backend-protocol.js +111 -83
  78. package/bundle/dist/utils/providers/openai/openai-callback-relay.js +3 -2
  79. package/bundle/dist/utils/providers/openai/openai-constants.js +1 -1
  80. package/bundle/dist/utils/providers/openai/openai-direct-client.js +23 -7
  81. package/bundle/dist/utils/providers/openai/openai-dispatcher.js +143 -106
  82. package/bundle/dist/utils/providers/openai/openai-image-generation.js +48 -33
  83. package/bundle/dist/utils/providers/openai/openai-oauth.js +28 -13
  84. package/bundle/dist/utils/providers/openai/openai-quota.js +38 -27
  85. package/bundle/dist/utils/providers/openai/openai-refresh.js +17 -9
  86. package/bundle/dist/utils/providers/openai/openai-sse.js +1 -1
  87. package/bundle/dist/utils/providers/openai/openai-transport-config.js +1 -1
  88. package/bundle/dist/utils/quiet-hours.js +1 -1
  89. package/bundle/dist/utils/slack-notify.js +14 -8
  90. package/bundle/dist/utils/subset-loaders.js +4 -5
  91. package/bundle/src/application/monit/log-buffer.ts +23 -9
  92. package/bundle/src/application/monit/monit.frame.ts +22 -5
  93. package/bundle/src/application/monit/monit.types.ts +6 -0
  94. package/bundle/src/application/qgrid/conv-routing.test.ts +76 -4
  95. package/bundle/src/application/qgrid/conv-routing.ts +35 -9
  96. package/bundle/src/application/qgrid/expired-token-reminder.ts +9 -1
  97. package/bundle/src/application/qgrid/oauth.ts +51 -15
  98. package/bundle/src/application/qgrid/qgrid-image-generation.ts +9 -0
  99. package/bundle/src/application/qgrid/qgrid-response-format.ts +50 -25
  100. package/bundle/src/application/qgrid/qgrid-run-lifecycle.test.ts +33 -0
  101. package/bundle/src/application/qgrid/qgrid-run-lifecycle.ts +87 -40
  102. package/bundle/src/application/qgrid/qgrid.dispatcher.test.ts +97 -0
  103. package/bundle/src/application/qgrid/qgrid.dispatcher.ts +274 -126
  104. package/bundle/src/application/qgrid/qgrid.frame.test.ts +1013 -3
  105. package/bundle/src/application/qgrid/qgrid.frame.ts +1415 -60
  106. package/bundle/src/application/qgrid/qgrid.types.ts +335 -0
  107. package/bundle/src/application/qgrid/schema-prompt.ts +105 -41
  108. package/bundle/src/application/qgrid/slack-user-map.ts +5 -0
  109. package/bundle/src/application/qgrid/token-death.ts +4 -0
  110. package/bundle/src/application/qgrid/token-subscriber.test.ts +21 -19
  111. package/bundle/src/application/qgrid/token-subscriber.ts +186 -53
  112. package/bundle/src/application/qgrid/token-trigger-setup.ts +2 -0
  113. package/bundle/src/application/qgrid/token-window-keepalive.ts +49 -6
  114. package/bundle/src/application/qgrid/tool-emulation-schema.ts +33 -29
  115. package/bundle/src/application/qgrid/tool-emulation.ts +52 -27
  116. package/bundle/src/application/request-log/json-schema-type-text.test.ts +5 -5
  117. package/bundle/src/application/request-log/json-schema-type-text.ts +81 -26
  118. package/bundle/src/application/request-log/request-log.entity.json +8 -0
  119. package/bundle/src/application/request-log/request-log.model.test.ts +111 -4
  120. package/bundle/src/application/request-log/request-log.model.ts +279 -145
  121. package/bundle/src/application/request-log/request-log.types.ts +17 -0
  122. package/bundle/src/application/request-log/zod-code-format.ts +11 -0
  123. package/bundle/src/application/request-log-step/request-log-step.model.ts +1 -0
  124. package/bundle/src/application/request-log-step/request-log-step.types.ts +2 -0
  125. package/bundle/src/application/setting/setting.constant.ts +9 -0
  126. package/bundle/src/application/setting/setting.model.ts +12 -2
  127. package/bundle/src/application/setting/setting.store.ts +5 -1
  128. package/bundle/src/application/setting/setting.types.ts +8 -0
  129. package/bundle/src/application/sonamu.generated.http +135 -0
  130. package/bundle/src/application/sonamu.generated.sso.ts +3 -0
  131. package/bundle/src/application/sonamu.generated.ts +29 -0
  132. package/bundle/src/application/token/token.identity.test.ts +20 -0
  133. package/bundle/src/application/token/token.model.ts +66 -7
  134. package/bundle/src/application/token/token.types.ts +24 -1
  135. package/bundle/src/i18n/en.ts +19 -0
  136. package/bundle/src/i18n/ko.ts +21 -0
  137. package/bundle/src/i18n/sd.generated.ts +24 -2
  138. package/bundle/src/migrations/20260807133553_manual__backfill_request_log_cost.ts +16 -8
  139. package/bundle/src/migrations/20261018215031_alter_request_logs_add1_alter1.ts +15 -0
  140. package/bundle/src/server-bootstrap.ts +4 -2
  141. package/bundle/src/server-error-handler.ts +1 -0
  142. package/bundle/src/sonamu.config.ts +69 -7
  143. package/bundle/src/startup-migrations.ts +27 -9
  144. package/bundle/src/testing/global.ts +20 -0
  145. package/bundle/src/testing/schema-bench.ts +105 -30
  146. package/bundle/src/testing/test-database-name.ts +2 -0
  147. package/bundle/src/utils/providers/anthropic/anthropic-constants.ts +8 -0
  148. package/bundle/src/utils/providers/anthropic/anthropic-dispatcher.ts +41 -6
  149. package/bundle/src/utils/providers/anthropic/anthropic-quota.ts +7 -3
  150. package/bundle/src/utils/providers/anthropic/anthropic-thinking.ts +6 -0
  151. package/bundle/src/utils/providers/anthropic/claude-session.ts +58 -24
  152. package/bundle/src/utils/providers/anthropic/fence-strip.ts +11 -0
  153. package/bundle/src/utils/providers/anthropic/stream-json-adapter.ts +110 -45
  154. package/bundle/src/utils/providers/common/credentials.ts +8 -0
  155. package/bundle/src/utils/providers/common/effort.ts +9 -2
  156. package/bundle/src/utils/providers/common/model-cost.test.ts +48 -1
  157. package/bundle/src/utils/providers/common/model-cost.ts +259 -158
  158. package/bundle/src/utils/providers/common/provider-dispatcher.ts +98 -2
  159. package/bundle/src/utils/providers/common/provider-types.ts +46 -3
  160. package/bundle/src/utils/providers/common/schema-validation.test.ts +5 -1
  161. package/bundle/src/utils/providers/common/schema-validation.ts +124 -56
  162. package/bundle/src/utils/providers/common/smooth-weighted-round-robin.ts +4 -0
  163. package/bundle/src/utils/providers/common/strictifier.ts +185 -59
  164. package/bundle/src/utils/providers/common/thinking.ts +2 -2
  165. package/bundle/src/utils/providers/common/ttft.ts +4 -2
  166. package/bundle/src/utils/providers/grok/__fixtures__/golden-responses-1.0.60.json +164 -0
  167. package/bundle/src/utils/providers/grok/grok-backend-protocol.test.ts +325 -0
  168. package/bundle/src/utils/providers/grok/grok-backend-protocol.ts +721 -0
  169. package/bundle/src/utils/providers/grok/grok-constants.ts +105 -0
  170. package/bundle/src/utils/providers/grok/grok-direct-client.test.ts +179 -0
  171. package/bundle/src/utils/providers/grok/grok-direct-client.ts +313 -0
  172. package/bundle/src/utils/providers/grok/grok-dispatcher.test.ts +1395 -0
  173. package/bundle/src/utils/providers/grok/grok-dispatcher.ts +2105 -0
  174. package/bundle/src/utils/providers/grok/grok-headers.test.ts +149 -0
  175. package/bundle/src/utils/providers/grok/grok-headers.ts +199 -0
  176. package/bundle/src/utils/providers/grok/grok-media-client.test.ts +446 -0
  177. package/bundle/src/utils/providers/grok/grok-media-client.ts +703 -0
  178. package/bundle/src/utils/providers/grok/grok-models.test.ts +96 -0
  179. package/bundle/src/utils/providers/grok/grok-models.ts +161 -0
  180. package/bundle/src/utils/providers/grok/grok-oauth.test.ts +406 -0
  181. package/bundle/src/utils/providers/grok/grok-oauth.ts +771 -0
  182. package/bundle/src/utils/providers/grok/grok-quota.test.ts +142 -0
  183. package/bundle/src/utils/providers/grok/grok-quota.ts +198 -0
  184. package/bundle/src/utils/providers/grok/grok-refresh.test.ts +239 -0
  185. package/bundle/src/utils/providers/grok/grok-refresh.ts +325 -0
  186. package/bundle/src/utils/providers/grok/grok-types.test.ts +252 -0
  187. package/bundle/src/utils/providers/grok/grok-types.ts +374 -0
  188. package/bundle/src/utils/providers/grok/grok-voice-client.test.ts +321 -0
  189. package/bundle/src/utils/providers/grok/grok-voice-client.ts +284 -0
  190. package/bundle/src/utils/providers/grok/index.ts +20 -0
  191. package/bundle/src/utils/providers/openai/openai-backend-protocol.ts +221 -137
  192. package/bundle/src/utils/providers/openai/openai-callback-relay.ts +12 -1
  193. package/bundle/src/utils/providers/openai/openai-constants.ts +2 -0
  194. package/bundle/src/utils/providers/openai/openai-direct-client.ts +111 -21
  195. package/bundle/src/utils/providers/openai/openai-dispatcher.ts +322 -169
  196. package/bundle/src/utils/providers/openai/openai-image-generation.ts +116 -36
  197. package/bundle/src/utils/providers/openai/openai-oauth.ts +48 -20
  198. package/bundle/src/utils/providers/openai/openai-quota.ts +77 -46
  199. package/bundle/src/utils/providers/openai/openai-refresh.ts +42 -18
  200. package/bundle/src/utils/providers/openai/openai-sse.ts +27 -3
  201. package/bundle/src/utils/providers/openai/openai-transport-config.ts +2 -0
  202. package/bundle/src/utils/quiet-hours.ts +12 -1
  203. package/bundle/src/utils/slack-notify.ts +44 -13
  204. package/bundle/src/utils/subset-loaders.ts +7 -7
  205. package/bundle/web-dist/client/assets/clsx-5XM_pHvN.js +1 -0
  206. package/bundle/web-dist/client/assets/index-BA7uQ-6c.js +31 -0
  207. package/bundle/web-dist/client/assets/index-Be2qCZmI.css +1 -0
  208. package/bundle/web-dist/client/assets/logs-C47OJGEI.js +1 -0
  209. package/bundle/web-dist/client/assets/monit-BS9no0dU.js +4 -0
  210. package/bundle/web-dist/client/assets/plus-sdACTfH0.js +1 -0
  211. package/bundle/web-dist/client/assets/refresh-cw-CY8o_1cT.js +1 -0
  212. package/bundle/web-dist/client/assets/request-model-BRtFuZvj.js +1 -0
  213. package/bundle/web-dist/client/assets/routes-kqgkUxE8.js +5 -0
  214. package/bundle/web-dist/client/assets/{services.generated-BsieU37H.js → services.generated-CefiDXEv.js} +5 -5
  215. package/bundle/web-dist/client/assets/settings-DNDvFGSw.js +1 -0
  216. package/bundle/web-dist/client/assets/show-u8Np69q3.js +2 -0
  217. package/bundle/web-dist/client/assets/tokens-QvP410NW.js +1 -0
  218. package/bundle/web-dist/client/assets/total-cost-MnfKU8IN.js +1 -0
  219. package/bundle/web-dist/client/assets/use-oauth-login-flow-Bzdh28Ws.js +1 -0
  220. package/bundle/web-dist/client/assets/use-update-token-mutation-CfJKaxmR.js +1 -0
  221. package/bundle/web-dist/client/assets/vendor-react-CUFFrVN9.js +244 -0
  222. package/bundle/web-dist/client/assets/vendor-tanstack-Bf_jag9G.js +1 -0
  223. package/bundle/web-dist/client/index.html +6 -6
  224. package/bundle/web-dist/server/assets/clsx-D_N5PN_2.js +16 -0
  225. package/bundle/web-dist/server/assets/{lazyRouteComponent-Dtd0NR4r.js → lazyRouteComponent-D8iTIHyn.js} +129 -2
  226. package/bundle/web-dist/server/assets/logs-B3OTd9hy.js +825 -0
  227. package/bundle/web-dist/server/assets/{monit-DvzribSE.js → monit-B2hqorPe.js} +385 -126
  228. package/bundle/web-dist/server/assets/plus-DwG7h9kq.js +28 -0
  229. package/bundle/web-dist/server/assets/{react-dom-B4a6-uuR.js → react-dom-BIYQzBqG.js} +2 -129
  230. package/bundle/web-dist/server/assets/refresh-cw-mMBkwOq0.js +44 -0
  231. package/bundle/web-dist/server/assets/{request-model-CbqfnQOJ.js → request-model-I6OdODSH.js} +15 -6
  232. package/bundle/web-dist/server/assets/{routes-BAPWTizT.js → routes-B95YlUv2.js} +760 -213
  233. package/bundle/web-dist/server/assets/{services.generated-CEbGjsI0.js → services.generated-DQATJ6qf.js} +12821 -12194
  234. package/bundle/web-dist/server/assets/settings-C3gbGk0m.js +830 -0
  235. package/bundle/web-dist/server/assets/{show-CxVZTAdv.js → show-Bxaj3rhH.js} +1355 -419
  236. package/bundle/web-dist/server/assets/tokens-CZjH3cB7.js +1038 -0
  237. package/bundle/web-dist/server/assets/total-cost-fTE9Xy82.js +18 -0
  238. package/bundle/web-dist/server/assets/use-oauth-login-flow-CKDHgc3U.js +2379 -0
  239. package/bundle/web-dist/server/assets/{use-update-token-mutation-CfYmk_4_.js → use-update-token-mutation-BEbgOzaa.js} +1 -1
  240. package/bundle/web-dist/server/entry-server.generated.js +4303 -22810
  241. package/dist/cli.js +9 -9
  242. package/package.json +4 -11
  243. package/postinstall.mjs +12 -0
  244. package/skills/qgrid/SKILL.md +1 -0
  245. package/skills/qgrid/references/cli-env-and-server-boot.md +4 -1
  246. package/skills/qgrid/references/decision-rationale.md +24 -1
  247. package/skills/qgrid/references/grok-runtime.md +89 -0
  248. package/skills/qgrid/references/provider-runtime-differences.md +30 -29
  249. package/skills/qgrid/references/repo-map.md +10 -0
  250. package/skills/qgrid/references/request-log-run-lifecycle.md +1 -1
  251. package/skills/qgrid/references/sonamu-api-web-flow.md +1 -1
  252. package/skills/qgrid/references/token-auth-quota-lifecycle.md +56 -3
  253. package/bundle/dist/node_modules/.pnpm/@jridgewell_sourcemap-codec@1.5.5/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.js +0 -406
  254. package/bundle/dist/node_modules/.pnpm/@vitest_expect@4.1.10/node_modules/@vitest/expect/dist/index.js +0 -1507
  255. package/bundle/dist/node_modules/.pnpm/@vitest_pretty-format@4.1.10/node_modules/@vitest/pretty-format/dist/index.js +0 -887
  256. package/bundle/dist/node_modules/.pnpm/@vitest_runner@4.1.10/node_modules/@vitest/runner/dist/chunk-artifact.js +0 -2756
  257. package/bundle/dist/node_modules/.pnpm/@vitest_runner@4.1.10/node_modules/@vitest/runner/dist/index.js +0 -8
  258. package/bundle/dist/node_modules/.pnpm/@vitest_runner@4.1.10/node_modules/@vitest/runner/dist/utils.js +0 -8
  259. package/bundle/dist/node_modules/.pnpm/@vitest_snapshot@4.1.10/node_modules/@vitest/snapshot/dist/index.js +0 -922
  260. package/bundle/dist/node_modules/.pnpm/@vitest_spy@4.1.10/node_modules/@vitest/spy/dist/index.js +0 -386
  261. package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/chunk-pathe.M-eThtNZ.js +0 -121
  262. package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/constants.js +0 -51
  263. package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/diff.js +0 -1317
  264. package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/display.js +0 -561
  265. package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/error.js +0 -40
  266. package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/helpers.js +0 -272
  267. package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/offset.js +0 -27
  268. package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/serialize.js +0 -77
  269. package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/source-map.js +0 -381
  270. package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/timers.js +0 -51
  271. package/bundle/dist/node_modules/.pnpm/chai@6.2.2/node_modules/chai/index.js +0 -2986
  272. package/bundle/dist/node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/branding.js +0 -10
  273. package/bundle/dist/node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/index.js +0 -109
  274. package/bundle/dist/node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/messages.js +0 -10
  275. package/bundle/dist/node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/overloads.js +0 -10
  276. package/bundle/dist/node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/utils.js +0 -10
  277. package/bundle/dist/node_modules/.pnpm/magic-string@0.30.21/node_modules/magic-string/dist/magic-string.es.js +0 -1126
  278. package/bundle/dist/node_modules/.pnpm/pathe@2.0.3/node_modules/pathe/dist/index.js +0 -21
  279. package/bundle/dist/node_modules/.pnpm/pathe@2.0.3/node_modules/pathe/dist/shared/pathe.M-eThtNZ.js +0 -432
  280. package/bundle/dist/node_modules/.pnpm/tinybench@2.9.0/node_modules/tinybench/dist/index.js +0 -517
  281. package/bundle/dist/node_modules/.pnpm/tinyrainbow@3.1.0/node_modules/tinyrainbow/dist/index.js +0 -101
  282. 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
  283. 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
  284. 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
  285. 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
  286. 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
  287. 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
  288. 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
  289. 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
  290. 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
  291. 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
  292. package/bundle/dist/testing/setup-mocks.js +0 -16
  293. package/bundle/src/testing/setup-mocks.ts +0 -44
  294. package/bundle/web-dist/client/assets/index-Bw2s88Ot.css +0 -1
  295. package/bundle/web-dist/client/assets/index-CrXKu_J8.js +0 -106
  296. package/bundle/web-dist/client/assets/logs-B2SmWJXA.js +0 -1
  297. package/bundle/web-dist/client/assets/monit-D-JwXXoA.js +0 -1
  298. package/bundle/web-dist/client/assets/plus-B62CuqT8.js +0 -1
  299. package/bundle/web-dist/client/assets/refresh-cw-NNcU0Cmq.js +0 -1
  300. package/bundle/web-dist/client/assets/request-model-CojtinqK.js +0 -1
  301. package/bundle/web-dist/client/assets/routes-C5r4gTvY.js +0 -5
  302. package/bundle/web-dist/client/assets/settings-D3tDSAck.js +0 -1
  303. package/bundle/web-dist/client/assets/show-Cah_kUKC.js +0 -2
  304. package/bundle/web-dist/client/assets/tokens-DU2Iyxxd.js +0 -1
  305. package/bundle/web-dist/client/assets/use-oauth-login-flow-D0UaR9_e.js +0 -1
  306. package/bundle/web-dist/client/assets/use-update-token-mutation-DAq0F8p-.js +0 -1
  307. package/bundle/web-dist/client/assets/vendor-react-DH4kUfK2.js +0 -9
  308. package/bundle/web-dist/client/assets/vendor-tanstack-DJRQAbrY.js +0 -1
  309. package/bundle/web-dist/server/assets/logs-l91avYif.js +0 -509
  310. package/bundle/web-dist/server/assets/plus-KZrECewT.js +0 -19
  311. package/bundle/web-dist/server/assets/refresh-cw-BND93JrF.js +0 -23
  312. package/bundle/web-dist/server/assets/settings-BZR5HSAL.js +0 -461
  313. package/bundle/web-dist/server/assets/tokens-D1hb4eKk.js +0 -448
  314. package/bundle/web-dist/server/assets/use-oauth-login-flow-Cvdy0zF8.js +0 -178
  315. package/dist/cli.d.ts +0 -10
  316. /package/bundle/web-dist/client/assets/{cost-DH2jZbrw.js → cost-DB3HbU7T.js} +0 -0
  317. /package/bundle/web-dist/client/assets/{preload-helper-ca-nBW7U.js → preload-helper-DWTEM3RW.js} +0 -0
  318. /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 { QgridFrame } from "./application/qgrid/qgrid.frame";
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
- bodyLimit: 10 * 1024 * 1024,
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 as { code?: string; state?: string };
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: ${(e as Error).message}`);
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: ${(e as Error).message}`);
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: ${(e as Error).message}`);
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
- type MigrationModule = {
34
- up?: (knex: Knex) => Promise<unknown>;
35
- down?: (knex: Knex) => Promise<unknown>;
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)) as MigrationModule;
63
- if (typeof module.up !== "function") {
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
- return { up: module.up, down: module.down ?? (async () => undefined) };
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 = { getKnex: () => TokenModel.getDB("w"), exit: process.exit },
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: ${(error as Error).message}`);
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?: unknown;
36
+ schema?: JsonValue;
30
37
  /** 최상위에 반드시 있어야 하는 키 (schema 미지정 시의 최소 검증) */
31
38
  requiredTopLevelKeys?: string[];
32
39
  /** 허용되는 최상위 키 전체 — 래퍼 키 판정에 사용 */
33
40
  allowedTopLevelKeys?: string[];
34
41
  /** fixture 별 추가 계약 검증. 실패 사유 문자열을 반환하면 contract fail. */
35
- validate?: (parsed: unknown, rawText: string) => string | undefined;
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: unknown, schema: unknown): string[] {
47
- if (schema === null || typeof schema !== "object" || Array.isArray(schema)) return [];
48
- const validate = compileSchema(schema as Record<string, unknown>);
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
- return (validate.errors ?? []).map(
51
- (e) =>
52
- `${e.instancePath || "$"}: ${e.message ?? e.keyword}${e.keyword === "enum" ? ` (${JSON.stringify((e.params as { allowedValues?: unknown[] }).allowedValues?.slice(0, 8))})` : ""}`,
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: Record<string, unknown>): ValidateFunction {
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
- return {
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
- let parsed: unknown;
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) as BenchRecord;
223
- map.set(checkpointKey(record), record);
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<string, BenchRecord[]>();
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 bucket = groups.get(key);
258
- if (bucket) bucket.push(record);
259
- else groups.set(key, [record]);
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.entries()]
263
- .map(([key, bucket]) => {
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
  }