@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.
Files changed (302) 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 +21 -19
  10. package/bundle/dist/application/qgrid/qgrid-run-lifecycle.js +40 -33
  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 -70
  24. package/bundle/dist/application/request-log/request-log.types.js +1 -1
  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 +32 -16
  33. package/bundle/dist/application/sonamu.generated.sso.js +1 -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 +1 -1
  39. package/bundle/dist/migrations/20260807133553_manual__backfill_request_log_cost.js +8 -6
  40. package/bundle/dist/server-bootstrap.js +4 -2
  41. package/bundle/dist/server-error-handler.js +1 -1
  42. package/bundle/dist/sonamu.config.js +37 -8
  43. package/bundle/dist/startup-migrations.js +17 -7
  44. package/bundle/dist/testing/global.js +1 -1
  45. package/bundle/dist/testing/schema-bench.js +0 -0
  46. package/bundle/dist/testing/test-database-name.js +1 -1
  47. package/bundle/dist/utils/providers/anthropic/anthropic-constants.js +1 -1
  48. package/bundle/dist/utils/providers/anthropic/anthropic-dispatcher.js +11 -5
  49. package/bundle/dist/utils/providers/anthropic/anthropic-quota.js +4 -4
  50. package/bundle/dist/utils/providers/anthropic/anthropic-thinking.js +1 -1
  51. package/bundle/dist/utils/providers/anthropic/claude-session.js +20 -19
  52. package/bundle/dist/utils/providers/anthropic/fence-strip.js +1 -1
  53. package/bundle/dist/utils/providers/anthropic/stream-json-adapter.js +49 -23
  54. package/bundle/dist/utils/providers/common/credentials.js +5 -1
  55. package/bundle/dist/utils/providers/common/effort.js +3 -3
  56. package/bundle/dist/utils/providers/common/model-cost.js +69 -14
  57. package/bundle/dist/utils/providers/common/provider-types.js +29 -1
  58. package/bundle/dist/utils/providers/common/schema-validation.js +78 -40
  59. package/bundle/dist/utils/providers/common/smooth-weighted-round-robin.js +1 -1
  60. package/bundle/dist/utils/providers/common/strictifier.js +62 -43
  61. package/bundle/dist/utils/providers/common/thinking.js +3 -2
  62. package/bundle/dist/utils/providers/common/ttft.js +1 -1
  63. package/bundle/dist/utils/providers/grok/grok-backend-protocol.js +427 -0
  64. package/bundle/dist/utils/providers/grok/grok-constants.js +55 -0
  65. package/bundle/dist/utils/providers/grok/grok-direct-client.js +196 -0
  66. package/bundle/dist/utils/providers/grok/grok-dispatcher.js +1313 -0
  67. package/bundle/dist/utils/providers/grok/grok-headers.js +118 -0
  68. package/bundle/dist/utils/providers/grok/grok-media-client.js +420 -0
  69. package/bundle/dist/utils/providers/grok/grok-models.js +114 -0
  70. package/bundle/dist/utils/providers/grok/grok-oauth.js +455 -0
  71. package/bundle/dist/utils/providers/grok/grok-quota.js +119 -0
  72. package/bundle/dist/utils/providers/grok/grok-refresh.js +192 -0
  73. package/bundle/dist/utils/providers/grok/grok-types.js +186 -0
  74. package/bundle/dist/utils/providers/grok/grok-voice-client.js +199 -0
  75. package/bundle/dist/utils/providers/grok/index.js +11 -0
  76. package/bundle/dist/utils/providers/openai/openai-backend-protocol.js +111 -83
  77. package/bundle/dist/utils/providers/openai/openai-callback-relay.js +3 -2
  78. package/bundle/dist/utils/providers/openai/openai-constants.js +1 -1
  79. package/bundle/dist/utils/providers/openai/openai-direct-client.js +23 -7
  80. package/bundle/dist/utils/providers/openai/openai-dispatcher.js +143 -106
  81. package/bundle/dist/utils/providers/openai/openai-image-generation.js +48 -33
  82. package/bundle/dist/utils/providers/openai/openai-oauth.js +28 -13
  83. package/bundle/dist/utils/providers/openai/openai-quota.js +38 -27
  84. package/bundle/dist/utils/providers/openai/openai-refresh.js +17 -9
  85. package/bundle/dist/utils/providers/openai/openai-sse.js +1 -1
  86. package/bundle/dist/utils/providers/openai/openai-transport-config.js +1 -1
  87. package/bundle/dist/utils/quiet-hours.js +1 -1
  88. package/bundle/dist/utils/slack-notify.js +14 -8
  89. package/bundle/dist/utils/subset-loaders.js +4 -5
  90. package/bundle/src/application/monit/log-buffer.ts +23 -9
  91. package/bundle/src/application/monit/monit.frame.ts +22 -5
  92. package/bundle/src/application/monit/monit.types.ts +6 -0
  93. package/bundle/src/application/qgrid/conv-routing.test.ts +76 -4
  94. package/bundle/src/application/qgrid/conv-routing.ts +35 -9
  95. package/bundle/src/application/qgrid/expired-token-reminder.ts +9 -1
  96. package/bundle/src/application/qgrid/oauth.ts +51 -15
  97. package/bundle/src/application/qgrid/qgrid-image-generation.ts +9 -0
  98. package/bundle/src/application/qgrid/qgrid-response-format.ts +50 -25
  99. package/bundle/src/application/qgrid/qgrid-run-lifecycle.test.ts +19 -0
  100. package/bundle/src/application/qgrid/qgrid-run-lifecycle.ts +84 -40
  101. package/bundle/src/application/qgrid/qgrid.dispatcher.test.ts +97 -0
  102. package/bundle/src/application/qgrid/qgrid.dispatcher.ts +274 -126
  103. package/bundle/src/application/qgrid/qgrid.frame.test.ts +1013 -3
  104. package/bundle/src/application/qgrid/qgrid.frame.ts +1415 -60
  105. package/bundle/src/application/qgrid/qgrid.types.ts +335 -0
  106. package/bundle/src/application/qgrid/schema-prompt.ts +105 -41
  107. package/bundle/src/application/qgrid/slack-user-map.ts +5 -0
  108. package/bundle/src/application/qgrid/token-death.ts +4 -0
  109. package/bundle/src/application/qgrid/token-subscriber.test.ts +21 -19
  110. package/bundle/src/application/qgrid/token-subscriber.ts +186 -53
  111. package/bundle/src/application/qgrid/token-trigger-setup.ts +2 -0
  112. package/bundle/src/application/qgrid/token-window-keepalive.ts +49 -6
  113. package/bundle/src/application/qgrid/tool-emulation-schema.ts +33 -29
  114. package/bundle/src/application/qgrid/tool-emulation.ts +52 -27
  115. package/bundle/src/application/request-log/json-schema-type-text.test.ts +5 -5
  116. package/bundle/src/application/request-log/json-schema-type-text.ts +81 -26
  117. package/bundle/src/application/request-log/request-log.model.test.ts +111 -4
  118. package/bundle/src/application/request-log/request-log.model.ts +277 -146
  119. package/bundle/src/application/request-log/request-log.types.ts +6 -0
  120. package/bundle/src/application/request-log/zod-code-format.ts +11 -0
  121. package/bundle/src/application/request-log-step/request-log-step.model.ts +1 -0
  122. package/bundle/src/application/request-log-step/request-log-step.types.ts +2 -0
  123. package/bundle/src/application/setting/setting.constant.ts +9 -0
  124. package/bundle/src/application/setting/setting.model.ts +12 -2
  125. package/bundle/src/application/setting/setting.store.ts +5 -1
  126. package/bundle/src/application/setting/setting.types.ts +8 -0
  127. package/bundle/src/application/sonamu.generated.http +133 -0
  128. package/bundle/src/application/sonamu.generated.sso.ts +2 -0
  129. package/bundle/src/application/sonamu.generated.ts +15 -0
  130. package/bundle/src/application/token/token.identity.test.ts +20 -0
  131. package/bundle/src/application/token/token.model.ts +66 -7
  132. package/bundle/src/application/token/token.types.ts +24 -1
  133. package/bundle/src/i18n/en.ts +19 -0
  134. package/bundle/src/i18n/ko.ts +21 -0
  135. package/bundle/src/i18n/sd.generated.ts +23 -2
  136. package/bundle/src/migrations/20260807133553_manual__backfill_request_log_cost.ts +16 -8
  137. package/bundle/src/server-bootstrap.ts +4 -2
  138. package/bundle/src/server-error-handler.ts +1 -0
  139. package/bundle/src/sonamu.config.ts +69 -7
  140. package/bundle/src/startup-migrations.ts +27 -9
  141. package/bundle/src/testing/global.ts +20 -0
  142. package/bundle/src/testing/schema-bench.ts +105 -30
  143. package/bundle/src/testing/test-database-name.ts +2 -0
  144. package/bundle/src/utils/providers/anthropic/anthropic-constants.ts +8 -0
  145. package/bundle/src/utils/providers/anthropic/anthropic-dispatcher.ts +41 -6
  146. package/bundle/src/utils/providers/anthropic/anthropic-quota.ts +7 -3
  147. package/bundle/src/utils/providers/anthropic/anthropic-thinking.ts +6 -0
  148. package/bundle/src/utils/providers/anthropic/claude-session.ts +58 -24
  149. package/bundle/src/utils/providers/anthropic/fence-strip.ts +11 -0
  150. package/bundle/src/utils/providers/anthropic/stream-json-adapter.ts +110 -45
  151. package/bundle/src/utils/providers/common/credentials.ts +8 -0
  152. package/bundle/src/utils/providers/common/effort.ts +9 -2
  153. package/bundle/src/utils/providers/common/model-cost.test.ts +48 -1
  154. package/bundle/src/utils/providers/common/model-cost.ts +259 -158
  155. package/bundle/src/utils/providers/common/provider-dispatcher.ts +98 -2
  156. package/bundle/src/utils/providers/common/provider-types.ts +46 -3
  157. package/bundle/src/utils/providers/common/schema-validation.test.ts +5 -1
  158. package/bundle/src/utils/providers/common/schema-validation.ts +124 -56
  159. package/bundle/src/utils/providers/common/smooth-weighted-round-robin.ts +4 -0
  160. package/bundle/src/utils/providers/common/strictifier.ts +185 -59
  161. package/bundle/src/utils/providers/common/thinking.ts +2 -2
  162. package/bundle/src/utils/providers/common/ttft.ts +4 -2
  163. package/bundle/src/utils/providers/grok/__fixtures__/golden-responses-1.0.60.json +164 -0
  164. package/bundle/src/utils/providers/grok/grok-backend-protocol.test.ts +325 -0
  165. package/bundle/src/utils/providers/grok/grok-backend-protocol.ts +721 -0
  166. package/bundle/src/utils/providers/grok/grok-constants.ts +105 -0
  167. package/bundle/src/utils/providers/grok/grok-direct-client.test.ts +179 -0
  168. package/bundle/src/utils/providers/grok/grok-direct-client.ts +313 -0
  169. package/bundle/src/utils/providers/grok/grok-dispatcher.test.ts +1395 -0
  170. package/bundle/src/utils/providers/grok/grok-dispatcher.ts +2105 -0
  171. package/bundle/src/utils/providers/grok/grok-headers.test.ts +149 -0
  172. package/bundle/src/utils/providers/grok/grok-headers.ts +199 -0
  173. package/bundle/src/utils/providers/grok/grok-media-client.test.ts +446 -0
  174. package/bundle/src/utils/providers/grok/grok-media-client.ts +703 -0
  175. package/bundle/src/utils/providers/grok/grok-models.test.ts +96 -0
  176. package/bundle/src/utils/providers/grok/grok-models.ts +161 -0
  177. package/bundle/src/utils/providers/grok/grok-oauth.test.ts +406 -0
  178. package/bundle/src/utils/providers/grok/grok-oauth.ts +771 -0
  179. package/bundle/src/utils/providers/grok/grok-quota.test.ts +142 -0
  180. package/bundle/src/utils/providers/grok/grok-quota.ts +198 -0
  181. package/bundle/src/utils/providers/grok/grok-refresh.test.ts +239 -0
  182. package/bundle/src/utils/providers/grok/grok-refresh.ts +325 -0
  183. package/bundle/src/utils/providers/grok/grok-types.test.ts +252 -0
  184. package/bundle/src/utils/providers/grok/grok-types.ts +374 -0
  185. package/bundle/src/utils/providers/grok/grok-voice-client.test.ts +321 -0
  186. package/bundle/src/utils/providers/grok/grok-voice-client.ts +284 -0
  187. package/bundle/src/utils/providers/grok/index.ts +20 -0
  188. package/bundle/src/utils/providers/openai/openai-backend-protocol.ts +221 -137
  189. package/bundle/src/utils/providers/openai/openai-callback-relay.ts +12 -1
  190. package/bundle/src/utils/providers/openai/openai-constants.ts +2 -0
  191. package/bundle/src/utils/providers/openai/openai-direct-client.ts +111 -21
  192. package/bundle/src/utils/providers/openai/openai-dispatcher.ts +322 -169
  193. package/bundle/src/utils/providers/openai/openai-image-generation.ts +116 -36
  194. package/bundle/src/utils/providers/openai/openai-oauth.ts +48 -20
  195. package/bundle/src/utils/providers/openai/openai-quota.ts +77 -46
  196. package/bundle/src/utils/providers/openai/openai-refresh.ts +42 -18
  197. package/bundle/src/utils/providers/openai/openai-sse.ts +27 -3
  198. package/bundle/src/utils/providers/openai/openai-transport-config.ts +2 -0
  199. package/bundle/src/utils/quiet-hours.ts +12 -1
  200. package/bundle/src/utils/slack-notify.ts +44 -13
  201. package/bundle/src/utils/subset-loaders.ts +7 -7
  202. package/bundle/web-dist/client/assets/clsx-5XM_pHvN.js +1 -0
  203. package/bundle/web-dist/client/assets/index-Be2qCZmI.css +1 -0
  204. package/bundle/web-dist/client/assets/index-C96h3feE.js +32 -0
  205. package/bundle/web-dist/client/assets/logs-DBM6U0QD.js +1 -0
  206. package/bundle/web-dist/client/assets/monit-BBjXa7vB.js +1 -0
  207. package/bundle/web-dist/client/assets/routes-Crz_PQzi.js +5 -0
  208. package/bundle/web-dist/client/assets/{services.generated-NP-ValHd.js → services.generated-B5lc3aIC.js} +5 -5
  209. package/bundle/web-dist/client/assets/settings-Cdwm3OBB.js +1 -0
  210. package/bundle/web-dist/client/assets/show-DBUhALT1.js +2 -0
  211. package/bundle/web-dist/client/assets/tokens-CVQAVJTo.js +1 -0
  212. package/bundle/web-dist/client/assets/total-cost-MnfKU8IN.js +1 -0
  213. package/bundle/web-dist/client/assets/use-oauth-login-flow-BfxYI4Oh.js +1 -0
  214. package/bundle/web-dist/client/assets/use-update-token-mutation-qKBC2sn7.js +1 -0
  215. package/bundle/web-dist/client/assets/{vendor-tanstack-DJRQAbrY.js → vendor-tanstack-DRPs09ai.js} +1 -1
  216. package/bundle/web-dist/client/index.html +5 -5
  217. package/bundle/web-dist/server/assets/clsx-D_N5PN_2.js +16 -0
  218. package/bundle/web-dist/server/assets/{lazyRouteComponent-CgBdabgd.js → lazyRouteComponent-AKiHfZWp.js} +129 -2
  219. package/bundle/web-dist/server/assets/{logs-BMyR98rU.js → logs-B7QmGJq3.js} +18 -14
  220. package/bundle/web-dist/server/assets/{monit-ZPt66W5M.js → monit-DFt7x3bh.js} +13 -12
  221. package/bundle/web-dist/server/assets/{plus-BZ9I0VgC.js → plus-DaYQRpBt.js} +1 -1
  222. package/bundle/web-dist/server/assets/{react-dom-Ds75e4Jl.js → react-dom-Cv23mm1K.js} +2 -129
  223. package/bundle/web-dist/server/assets/{refresh-cw-CztCFis-.js → refresh-cw-BpXNsAy-.js} +1 -1
  224. package/bundle/web-dist/server/assets/{request-model-DjGldb-h.js → request-model-eHE6YbaF.js} +1 -1
  225. package/bundle/web-dist/server/assets/{routes-Ct-k7nBA.js → routes-_Y-PJfgO.js} +110 -39
  226. package/bundle/web-dist/server/assets/{services.generated-BA4AcAf5.js → services.generated-CMJ8AHbO.js} +582 -172
  227. package/bundle/web-dist/server/assets/{settings-CBUZ47Xx.js → settings-JwJKxn2e.js} +14 -14
  228. package/bundle/web-dist/server/assets/{show-LSFIzvO2.js → show-BEv6z1v_.js} +90 -60
  229. package/bundle/web-dist/server/assets/{tokens-DxrBW512.js → tokens-CNT4mI-f.js} +162 -50
  230. package/bundle/web-dist/server/assets/total-cost-fTE9Xy82.js +18 -0
  231. package/bundle/web-dist/server/assets/use-oauth-login-flow-jkAkT3X6.js +2345 -0
  232. package/bundle/web-dist/server/assets/{use-update-token-mutation-pkArIzCP.js → use-update-token-mutation-Sm6kh02m.js} +1 -1
  233. package/bundle/web-dist/server/entry-server.generated.js +1644 -21655
  234. package/dist/cli.js +9 -9
  235. package/package.json +4 -11
  236. package/postinstall.mjs +12 -0
  237. package/skills/qgrid/SKILL.md +1 -0
  238. package/skills/qgrid/references/cli-env-and-server-boot.md +4 -1
  239. package/skills/qgrid/references/decision-rationale.md +23 -0
  240. package/skills/qgrid/references/grok-runtime.md +89 -0
  241. package/skills/qgrid/references/provider-runtime-differences.md +30 -29
  242. package/skills/qgrid/references/repo-map.md +10 -0
  243. package/skills/qgrid/references/token-auth-quota-lifecycle.md +56 -3
  244. package/bundle/dist/node_modules/.pnpm/@jridgewell_sourcemap-codec@1.5.5/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.js +0 -406
  245. package/bundle/dist/node_modules/.pnpm/@vitest_expect@4.1.10/node_modules/@vitest/expect/dist/index.js +0 -1507
  246. package/bundle/dist/node_modules/.pnpm/@vitest_pretty-format@4.1.10/node_modules/@vitest/pretty-format/dist/index.js +0 -887
  247. package/bundle/dist/node_modules/.pnpm/@vitest_runner@4.1.10/node_modules/@vitest/runner/dist/chunk-artifact.js +0 -2756
  248. package/bundle/dist/node_modules/.pnpm/@vitest_runner@4.1.10/node_modules/@vitest/runner/dist/index.js +0 -8
  249. package/bundle/dist/node_modules/.pnpm/@vitest_runner@4.1.10/node_modules/@vitest/runner/dist/utils.js +0 -8
  250. package/bundle/dist/node_modules/.pnpm/@vitest_snapshot@4.1.10/node_modules/@vitest/snapshot/dist/index.js +0 -922
  251. package/bundle/dist/node_modules/.pnpm/@vitest_spy@4.1.10/node_modules/@vitest/spy/dist/index.js +0 -386
  252. package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/chunk-pathe.M-eThtNZ.js +0 -121
  253. package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/constants.js +0 -51
  254. package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/diff.js +0 -1317
  255. package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/display.js +0 -561
  256. package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/error.js +0 -40
  257. package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/helpers.js +0 -272
  258. package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/offset.js +0 -27
  259. package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/serialize.js +0 -77
  260. package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/source-map.js +0 -381
  261. package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/timers.js +0 -51
  262. package/bundle/dist/node_modules/.pnpm/chai@6.2.2/node_modules/chai/index.js +0 -2986
  263. package/bundle/dist/node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/branding.js +0 -10
  264. package/bundle/dist/node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/index.js +0 -109
  265. package/bundle/dist/node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/messages.js +0 -10
  266. package/bundle/dist/node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/overloads.js +0 -10
  267. package/bundle/dist/node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/utils.js +0 -10
  268. package/bundle/dist/node_modules/.pnpm/magic-string@0.30.21/node_modules/magic-string/dist/magic-string.es.js +0 -1126
  269. package/bundle/dist/node_modules/.pnpm/pathe@2.0.3/node_modules/pathe/dist/index.js +0 -21
  270. package/bundle/dist/node_modules/.pnpm/pathe@2.0.3/node_modules/pathe/dist/shared/pathe.M-eThtNZ.js +0 -432
  271. package/bundle/dist/node_modules/.pnpm/tinybench@2.9.0/node_modules/tinybench/dist/index.js +0 -517
  272. package/bundle/dist/node_modules/.pnpm/tinyrainbow@3.1.0/node_modules/tinyrainbow/dist/index.js +0 -101
  273. 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
  274. 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
  275. 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
  276. 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
  277. 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
  278. 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
  279. 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
  280. 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
  281. 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
  282. 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
  283. package/bundle/dist/testing/setup-mocks.js +0 -16
  284. package/bundle/src/testing/setup-mocks.ts +0 -44
  285. package/bundle/web-dist/client/assets/index-Bw2s88Ot.css +0 -1
  286. package/bundle/web-dist/client/assets/index-DZwzUfI-.js +0 -106
  287. package/bundle/web-dist/client/assets/logs-Cog79DU5.js +0 -1
  288. package/bundle/web-dist/client/assets/monit-2auznBys.js +0 -1
  289. package/bundle/web-dist/client/assets/routes-B0lTeLK-.js +0 -5
  290. package/bundle/web-dist/client/assets/settings-D8ZxZGSJ.js +0 -1
  291. package/bundle/web-dist/client/assets/show-CFSERlnM.js +0 -2
  292. package/bundle/web-dist/client/assets/tokens-CZXUyI7O.js +0 -1
  293. package/bundle/web-dist/client/assets/use-oauth-login-flow-hboOs3wG.js +0 -1
  294. package/bundle/web-dist/client/assets/use-update-token-mutation-BQHS0byG.js +0 -1
  295. package/bundle/web-dist/server/assets/use-oauth-login-flow-Gn8flMK5.js +0 -178
  296. package/dist/cli.d.ts +0 -10
  297. /package/bundle/web-dist/client/assets/{cost-DH2jZbrw.js → cost-DB3HbU7T.js} +0 -0
  298. /package/bundle/web-dist/client/assets/{plus-B62CuqT8.js → plus-BsQdY7PS.js} +0 -0
  299. /package/bundle/web-dist/client/assets/{preload-helper-ca-nBW7U.js → preload-helper-DWTEM3RW.js} +0 -0
  300. /package/bundle/web-dist/client/assets/{refresh-cw-NNcU0Cmq.js → refresh-cw-D9IUmT23.js} +0 -0
  301. /package/bundle/web-dist/client/assets/{request-model-CojtinqK.js → request-model-Bpp_rkK8.js} +0 -0
  302. /package/bundle/web-dist/server/assets/{cost-eEHeTRT5.js → cost-BC9yzyA9.js} +0 -0
@@ -2,6 +2,7 @@ import { PNG } from "pngjs";
2
2
  import { z } from "zod";
3
3
 
4
4
  import { ImageGenerationMetadata } from "../../../application/qgrid/qgrid.types";
5
+ import { isJsonObject, type JsonValue, jsonString } from "../common/provider-types";
5
6
  import {
6
7
  CHATGPT_CODEX_RESPONSES_URL,
7
8
  imageMimeType,
@@ -14,6 +15,21 @@ const MAX_PNG_BYTES = 32 * 1024 * 1024;
14
15
  const MAX_IMAGE_PIXELS = 4096 * 4096;
15
16
  const MAX_RESPONSE_BYTES = 64 * 1024 * 1024;
16
17
  const PNG_SIGNATURE = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
18
+ const TEXT_PART_TYPES = new Set(["input_text", "output_text", "text"]);
19
+
20
+ /** Request body for the standalone Codex images endpoint; controls are forwarded only when set. */
21
+ type StandaloneImageBody = {
22
+ model: "gpt-image-2";
23
+ prompt: string;
24
+ background?: JsonValue;
25
+ quality?: JsonValue;
26
+ size?: JsonValue;
27
+ images?: Array<{ image_url: string }>;
28
+ };
29
+
30
+ function imageUrlOf(value: JsonValue | undefined): string | undefined {
31
+ return jsonString(value) ?? (isJsonObject(value) ? jsonString(value.url) : undefined);
32
+ }
17
33
 
18
34
  export function buildStandaloneImageRequest(options: OpenAIResponsesOptions) {
19
35
  if (options.outputSchema || options.tools?.length) {
@@ -21,88 +37,126 @@ export function buildStandaloneImageRequest(options: OpenAIResponsesOptions) {
21
37
  "Standalone image generation does not support tools or structured output",
22
38
  );
23
39
  }
40
+
24
41
  if (!options.imageGeneration) {
25
42
  throw new OpenAIProtocolError("Standalone images require image generation to be enabled");
26
43
  }
27
- const controls = typeof options.imageGeneration === "object" ? options.imageGeneration : {};
44
+
45
+ const controls = options.imageGeneration === true ? {} : options.imageGeneration;
46
+
28
47
  if (options.model !== "gpt-image-2" && controls.background !== "transparent") {
29
48
  throw new OpenAIProtocolError(
30
49
  "Standalone images require an explicit transparent background request",
31
50
  );
32
51
  }
52
+
33
53
  if (controls.output_format && controls.output_format !== "png") {
34
54
  throw new OpenAIProtocolError("Standalone image generation only supports PNG output");
35
55
  }
56
+
36
57
  const text: string[] = [];
37
58
  const images: Array<{ image_url: string }> = [];
59
+
38
60
  if (options.instructions) text.push(`System instructions:\n${options.instructions}`);
61
+
39
62
  for (const item of options.history) {
63
+ if (!isJsonObject(item)) {
64
+ throw new OpenAIProtocolError(
65
+ "Unsupported conversation item for standalone image generation",
66
+ );
67
+ }
68
+
40
69
  if (item.type === "reasoning") continue;
41
- if (item.type === "image_generation_call" && typeof item.result === "string") {
70
+
71
+ const result = jsonString(item.result);
72
+
73
+ if (item.type === "image_generation_call" && result !== undefined) {
42
74
  images.push({
43
- image_url: `data:${imageMimeType(item.result, item.output_format)};base64,${item.result}`,
75
+ image_url: `data:${imageMimeType(result, item.output_format)};base64,${result}`,
44
76
  });
45
77
  continue;
46
78
  }
47
- if (item.type !== "message" && typeof item.role !== "string") {
79
+
80
+ const role = jsonString(item.role);
81
+
82
+ if (item.type !== "message" && role === undefined) {
48
83
  throw new OpenAIProtocolError(
49
84
  "Unsupported conversation item for standalone image generation",
50
85
  );
51
86
  }
52
- if (typeof item.content === "string") {
53
- text.push(`${item.role ?? "user"}:\n${item.content}`);
87
+
88
+ const speaker = role ?? "user";
89
+ const content = jsonString(item.content);
90
+
91
+ if (content !== undefined) {
92
+ text.push(`${speaker}:\n${content}`);
54
93
  continue;
55
94
  }
95
+
56
96
  if (!Array.isArray(item.content)) {
57
97
  throw new OpenAIProtocolError("Unsupported message content for standalone image generation");
58
98
  }
99
+
59
100
  for (const part of item.content) {
60
- if (!part || typeof part !== "object") {
101
+ if (!isJsonObject(part)) {
61
102
  throw new OpenAIProtocolError("Unsupported message part for standalone image generation");
62
103
  }
63
- if (
64
- ["input_text", "output_text", "text"].includes(part.type) &&
65
- typeof part.text === "string"
66
- ) {
67
- text.push(`${item.role ?? "user"}:\n${part.text}`);
104
+
105
+ const partType = jsonString(part.type);
106
+ const partText = jsonString(part.text);
107
+
108
+ if (partType !== undefined && TEXT_PART_TYPES.has(partType) && partText !== undefined) {
109
+ text.push(`${speaker}:\n${partText}`);
68
110
  } else if (part.type === "input_image" || part.type === "image_url") {
69
- const url = typeof part.image_url === "string" ? part.image_url : part.image_url?.url;
70
- if (typeof url !== "string" || !/^(https:\/\/|data:image\/)/i.test(url)) {
111
+ const url = imageUrlOf(part.image_url);
112
+
113
+ if (url === undefined || !/^(https:\/\/|data:image\/)/i.test(url)) {
71
114
  throw new OpenAIProtocolError("Image references must be HTTPS URLs or image data URLs");
72
115
  }
116
+
73
117
  images.push({ image_url: url });
74
118
  } else {
75
119
  throw new OpenAIProtocolError("Unsupported message part for standalone image generation");
76
120
  }
77
121
  }
78
122
  }
123
+
79
124
  if (images.length > 5)
80
125
  throw new OpenAIProtocolError("Standalone image editing supports at most 5 reference images");
126
+
81
127
  if (!text.some((part) => part.trim())) throw new OpenAIProtocolError("Image prompt is empty");
128
+
129
+ const body: StandaloneImageBody = { model: "gpt-image-2", prompt: text.join("\n\n") };
130
+
131
+ if (controls.background) body.background = controls.background;
132
+
133
+ if (controls.quality) body.quality = controls.quality;
134
+
135
+ if (controls.size) body.size = controls.size;
136
+
137
+ if (images.length) body.images = images;
138
+
82
139
  return {
83
140
  url: CHATGPT_CODEX_RESPONSES_URL.replace(
84
141
  /\/responses$/,
85
142
  `/images/${images.length ? "edits" : "generations"}`,
86
143
  ),
87
- body: {
88
- model: "gpt-image-2",
89
- prompt: text.join("\n\n"),
90
- ...(controls.background ? { background: controls.background } : {}),
91
- ...(controls.quality ? { quality: controls.quality } : {}),
92
- ...(controls.size ? { size: controls.size } : {}),
93
- ...(images.length ? { images } : {}),
94
- },
144
+ body,
95
145
  };
96
146
  }
97
147
 
98
- function inspectGeneratedPng(
99
- base64: string,
100
- requireTransparency: boolean,
101
- ): { width: number; height: number } {
148
+ interface PngDimensions {
149
+ width: number;
150
+ height: number;
151
+ }
152
+
153
+ function inspectGeneratedPng(base64: string, requireTransparency: boolean): PngDimensions {
102
154
  if (base64.length > Math.ceil(MAX_PNG_BYTES / 3) * 4 || !/^[A-Za-z0-9+/]+={0,2}$/.test(base64)) {
103
155
  throw new OpenAIProtocolError("Invalid or oversized generated PNG");
104
156
  }
157
+
105
158
  const bytes = Buffer.from(base64, "base64");
159
+
106
160
  if (
107
161
  bytes.length < 33 ||
108
162
  !bytes.subarray(0, 8).equals(PNG_SIGNATURE) ||
@@ -110,26 +164,34 @@ function inspectGeneratedPng(
110
164
  ) {
111
165
  throw new OpenAIProtocolError("Image generation did not return a PNG");
112
166
  }
167
+
113
168
  const width = bytes.readUInt32BE(16);
114
169
  const height = bytes.readUInt32BE(20);
170
+
115
171
  if (!width || !height || width * height > MAX_IMAGE_PIXELS) {
116
172
  throw new OpenAIProtocolError("Generated PNG dimensions exceed the decoding limit");
117
173
  }
174
+
118
175
  let image: PNG;
176
+
119
177
  try {
120
178
  image = PNG.sync.read(bytes);
121
179
  } catch {
122
180
  throw new OpenAIProtocolError("Image generation returned an invalid PNG");
123
181
  }
182
+
124
183
  if (!requireTransparency) return { width: image.width, height: image.height };
125
184
  // An alpha channel alone can still be fully opaque or have no visible content.
126
185
  let transparent = false;
127
186
  let visible = false;
187
+
128
188
  for (let i = 3; i < image.data.length; i += 4) {
129
189
  transparent ||= image.data[i] === 0;
130
190
  visible ||= image.data[i]! > 0;
191
+
131
192
  if (transparent && visible) return { width: image.width, height: image.height };
132
193
  }
194
+
133
195
  throw new OpenAIProtocolError(
134
196
  "Image generation did not return visible content on a transparent background",
135
197
  );
@@ -143,20 +205,25 @@ export async function readStandaloneImageResponse(
143
205
  const reader = response.body.getReader();
144
206
  const chunks: Uint8Array[] = [];
145
207
  let size = 0;
208
+
146
209
  try {
147
210
  while (true) {
148
211
  const { done, value } = await reader.read();
212
+
149
213
  if (done) break;
150
214
  size += value.byteLength;
215
+
151
216
  if (size > MAX_RESPONSE_BYTES) {
152
217
  await reader.cancel();
153
218
  throw new OpenAIProtocolError("Image response exceeded the size limit");
154
219
  }
220
+
155
221
  chunks.push(value);
156
222
  }
157
223
  } finally {
158
224
  reader.releaseLock();
159
225
  }
226
+
160
227
  const schema = z.object({
161
228
  data: z
162
229
  .array(z.object({ b64_json: z.string().min(1), revised_prompt: z.string().nullish() }))
@@ -167,28 +234,41 @@ export async function readStandaloneImageResponse(
167
234
  background: z.string().optional(),
168
235
  usage: ImageGenerationMetadata.shape.usage,
169
236
  });
237
+
170
238
  let parsed: z.infer<typeof schema>;
239
+
171
240
  try {
172
241
  parsed = schema.parse(JSON.parse(Buffer.concat(chunks).toString("utf8")));
173
242
  } catch {
174
243
  throw new OpenAIProtocolError("Invalid standalone image response");
175
244
  }
245
+
176
246
  const background = parsed.background ?? (requireTransparency ? "transparent" : undefined);
247
+
177
248
  return parsed.data.map((item, index) => {
178
249
  const dimensions = inspectGeneratedPng(item.b64_json, requireTransparency);
179
- return {
250
+
251
+ const generation: ImageGenerationMetadata = {
252
+ route: "codex-images",
253
+ model: parsed.model ?? "gpt-image-2",
254
+ size: `${dimensions.width}x${dimensions.height}`,
255
+ };
256
+
257
+ if (background) generation.background = background;
258
+
259
+ if (parsed.quality) generation.quality = parsed.quality;
260
+
261
+ if (index === 0 && parsed.usage) generation.usage = parsed.usage;
262
+
263
+ const event: OpenAINormalizedEvent = {
180
264
  type: "image",
181
265
  base64: item.b64_json,
182
266
  mimeType: "image/png",
183
- ...(item.revised_prompt ? { revisedPrompt: item.revised_prompt } : {}),
184
- generation: {
185
- route: "codex-images",
186
- model: parsed.model ?? "gpt-image-2",
187
- ...(background ? { background } : {}),
188
- size: `${dimensions.width}x${dimensions.height}`,
189
- ...(parsed.quality ? { quality: parsed.quality } : {}),
190
- ...(index === 0 && parsed.usage ? { usage: parsed.usage } : {}),
191
- },
267
+ generation,
192
268
  };
269
+
270
+ if (item.revised_prompt) event.revisedPrompt = item.revised_prompt;
271
+
272
+ return event;
193
273
  });
194
274
  }
@@ -1,5 +1,8 @@
1
1
  import crypto from "node:crypto";
2
2
 
3
+ import { z } from "zod";
4
+
5
+ import { type JsonValue } from "../common/provider-types";
3
6
  import {
4
7
  OPENAI_AUTHORIZE_URL,
5
8
  OPENAI_CLIENT_ID,
@@ -10,6 +13,25 @@ import {
10
13
 
11
14
  const OPENAI_AUTH_CLAIM = "https://api.openai.com/auth";
12
15
 
16
+ // Mistyped claims read as absent, matching how the callers treat missing ones.
17
+ const openAITokenClaimsSchema = z.looseObject({
18
+ exp: z.number().optional().catch(undefined),
19
+ [OPENAI_AUTH_CLAIM]: z
20
+ .looseObject({
21
+ chatgpt_account_id: z.string().optional().catch(undefined),
22
+ chatgpt_plan_type: z.string().optional().catch(undefined),
23
+ })
24
+ .optional()
25
+ .catch(undefined),
26
+ });
27
+
28
+ const openAITokenExchangeResponseSchema = z.looseObject({
29
+ id_token: z.string().optional(),
30
+ access_token: z.string().optional(),
31
+ refresh_token: z.string().optional(),
32
+ expires_in: z.number().optional(),
33
+ });
34
+
13
35
  export interface OpenAIPKCE {
14
36
  codeVerifier: string;
15
37
  codeChallenge: string;
@@ -34,6 +56,7 @@ export interface OpenAIExchangedCredentials {
34
56
 
35
57
  export function generateOpenAIPKCE(): OpenAIPKCE {
36
58
  const codeVerifier = crypto.randomBytes(64).toString("base64url");
59
+
37
60
  return {
38
61
  codeVerifier,
39
62
  codeChallenge: crypto.createHash("sha256").update(codeVerifier).digest("base64url"),
@@ -59,32 +82,36 @@ export function buildOpenAIAuthUrl(
59
82
  state,
60
83
  originator: OPENAI_ORIGINATOR,
61
84
  }).toString();
85
+
62
86
  return url.toString();
63
87
  }
64
88
 
65
89
  export function parseOpenAITokenClaims(jwt: string): OpenAITokenClaims {
66
90
  const parts = jwt.split(".");
67
91
  const payloadPart = parts[1];
92
+
68
93
  if (parts.length !== 3 || parts.some((part) => !part) || !payloadPart) {
69
94
  throw new Error("invalid OpenAI JWT format");
70
95
  }
71
96
 
72
- let payload: unknown;
97
+ let payload: JsonValue;
98
+
73
99
  try {
100
+ // JSON.parse without a reviver yields only JSON values.
74
101
  payload = JSON.parse(Buffer.from(payloadPart, "base64url").toString("utf8"));
75
102
  } catch {
76
103
  throw new Error("invalid OpenAI JWT payload");
77
104
  }
78
- if (!payload || typeof payload !== "object") throw new Error("invalid OpenAI JWT payload");
79
105
 
80
- const claims = payload as Record<string, unknown>;
81
- const auth = claims[OPENAI_AUTH_CLAIM];
82
- const nested = auth && typeof auth === "object" ? (auth as Record<string, unknown>) : {};
106
+ const claims = openAITokenClaimsSchema.safeParse(payload);
107
+
108
+ if (!claims.success) throw new Error("invalid OpenAI JWT payload");
109
+ const auth = claims.data[OPENAI_AUTH_CLAIM];
110
+
83
111
  return {
84
- expiresAt: typeof claims.exp === "number" ? claims.exp * 1000 : undefined,
85
- accountId:
86
- typeof nested.chatgpt_account_id === "string" ? nested.chatgpt_account_id : undefined,
87
- planType: typeof nested.chatgpt_plan_type === "string" ? nested.chatgpt_plan_type : undefined,
112
+ expiresAt: claims.data.exp === undefined ? undefined : claims.data.exp * 1000,
113
+ accountId: auth?.chatgpt_account_id,
114
+ planType: auth?.chatgpt_plan_type,
88
115
  };
89
116
  }
90
117
 
@@ -104,18 +131,15 @@ export async function exchangeOpenAICode(
104
131
  code_verifier: codeVerifier,
105
132
  }),
106
133
  });
134
+
107
135
  if (!response.ok) {
108
136
  // Token endpoint bodies can echo credentials or provider diagnostics; never expose them.
109
137
  await response.body?.cancel().catch(() => undefined);
110
138
  throw new Error(`OPENAI_TOKEN_EXCHANGE_HTTP_${response.status}`);
111
139
  }
112
140
 
113
- const tokens = (await response.json()) as {
114
- id_token?: string;
115
- access_token?: string;
116
- refresh_token?: string;
117
- expires_in?: number;
118
- };
141
+ const tokens = openAITokenExchangeResponseSchema.parse(await response.json());
142
+
119
143
  if (!tokens.id_token || !tokens.access_token || !tokens.refresh_token) {
120
144
  throw new Error("OpenAI token exchange returned incomplete credentials");
121
145
  }
@@ -123,19 +147,23 @@ export async function exchangeOpenAICode(
123
147
  const idClaims = parseOpenAITokenClaims(tokens.id_token);
124
148
  const accessClaims = parseOpenAITokenClaims(tokens.access_token);
125
149
  const accountId = idClaims.accountId ?? accessClaims.accountId;
150
+
126
151
  if (!accountId) throw new Error("OpenAI token did not include a ChatGPT account id");
127
152
 
128
- return {
153
+ const credentials: OpenAIExchangedCredentials = {
129
154
  accessToken: tokens.access_token,
130
155
  refreshToken: tokens.refresh_token,
131
156
  idToken: tokens.id_token,
132
157
  accessTokenExpiresAt:
133
158
  accessClaims.expiresAt ??
134
159
  (tokens.expires_in ? Date.now() + tokens.expires_in * 1000 : Date.now()),
135
- ...(idClaims.expiresAt ? { idTokenExpiresAt: idClaims.expiresAt } : {}),
136
160
  accountId,
137
- ...((idClaims.planType ?? accessClaims.planType)
138
- ? { planType: idClaims.planType ?? accessClaims.planType }
139
- : {}),
140
161
  };
162
+
163
+ if (idClaims.expiresAt) credentials.idTokenExpiresAt = idClaims.expiresAt;
164
+ const planType = idClaims.planType ?? accessClaims.planType;
165
+
166
+ if (planType) credentials.planType = planType;
167
+
168
+ return credentials;
141
169
  }
@@ -1,5 +1,13 @@
1
1
  import { type OpenAICredentials } from "../../../application/token/token.types";
2
- import { type QuotaRateLimits, type QuotaRateLimitSnapshot } from "../common/provider-types";
2
+ import {
3
+ isJsonObject,
4
+ type JsonObject,
5
+ type JsonValue,
6
+ jsonString,
7
+ type QuotaRateLimits,
8
+ type QuotaRateLimitSnapshot,
9
+ type QuotaRateLimitWindow,
10
+ } from "../common/provider-types";
3
11
  import { buildCodexIdentityHeaders } from "./openai-backend-protocol";
4
12
 
5
13
  export const CHATGPT_WHAM_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage" as const;
@@ -33,9 +41,11 @@ async function readDirect(options: OpenAIQuotaHttpOptions): Promise<OpenAIRateLi
33
41
  (options.fetch ?? fetch)(CHATGPT_WHAM_USAGE_URL, {
34
42
  method: "GET",
35
43
  headers: buildCodexIdentityHeaders(credentials.accessToken, credentials.accountId),
36
- ...(options.signal ? { signal: options.signal } : {}),
44
+ signal: options.signal,
37
45
  });
46
+
38
47
  let response = await read(options.credentials);
48
+
39
49
  if (response.status === 401 && options.refreshCredentials) {
40
50
  await response.body?.cancel();
41
51
  options.signal?.throwIfAborted();
@@ -43,20 +53,29 @@ async function readDirect(options: OpenAIQuotaHttpOptions): Promise<OpenAIRateLi
43
53
  options.signal?.throwIfAborted();
44
54
  response = await read(credentials);
45
55
  }
56
+
46
57
  if (!response.ok) throw new Error(`OpenAI quota lookup failed: HTTP ${response.status}`);
47
- const body = (await response.json()) as Record<string, unknown>;
58
+ // Response.json() parses JSON text, which yields only JSON values.
59
+ const body: JsonValue = await response.json();
48
60
  const data = normalizeWhamUsage(body);
61
+
49
62
  return { data, cachedAt: Date.now() };
50
63
  }
51
64
 
52
- function windowFrom(value: unknown) {
53
- if (!value || typeof value !== "object") return null;
54
- const source = value as Record<string, unknown>;
65
+ function jsonObjectOrEmpty(value: JsonValue | undefined): JsonObject {
66
+ return isJsonObject(value) ? value : {};
67
+ }
68
+
69
+ function windowFrom(value: JsonValue | undefined): QuotaRateLimitWindow | null {
70
+ if (!isJsonObject(value)) return null;
71
+ const source = value;
55
72
  const usedPercent = Number(source.usedPercent ?? source.used_percent);
73
+
56
74
  if (!Number.isFinite(usedPercent)) return null;
57
75
  const durationMins = source.windowDurationMins ?? source.window_minutes;
58
76
  const durationSeconds = source.limitWindowSeconds ?? source.limit_window_seconds;
59
77
  const resets = source.resetsAt ?? source.reset_at;
78
+
60
79
  return {
61
80
  usedPercent,
62
81
  windowDurationMins:
@@ -69,62 +88,75 @@ function windowFrom(value: unknown) {
69
88
  };
70
89
  }
71
90
 
72
- function snapshotFrom(value: unknown): QuotaRateLimitSnapshot {
73
- const source = (value && typeof value === "object" ? value : {}) as Record<string, unknown>;
74
- const limitId = source.limitId ?? source.limit_id;
75
- const limitName = source.limitName ?? source.limit_name;
91
+ function snapshotFrom(value: JsonValue | undefined): QuotaRateLimitSnapshot {
92
+ const source = jsonObjectOrEmpty(value);
93
+
76
94
  return {
77
- limitId: typeof limitId === "string" ? limitId : null,
78
- limitName: typeof limitName === "string" ? limitName : null,
95
+ limitId: jsonString(source.limitId ?? source.limit_id) ?? null,
96
+ limitName: jsonString(source.limitName ?? source.limit_name) ?? null,
79
97
  primary: windowFrom(source.primary ?? source.primary_window),
80
98
  secondary: windowFrom(source.secondary ?? source.secondary_window),
81
- credits: (source.credits ?? null) as QuotaRateLimitSnapshot["credits"],
82
- planType:
83
- typeof (source.planType ?? source.plan_type) === "string"
84
- ? String(source.planType ?? source.plan_type)
85
- : null,
99
+ credits: source.credits ?? null,
100
+ planType: jsonString(source.planType ?? source.plan_type) ?? null,
86
101
  rateLimitReachedType:
87
- typeof (source.rateLimitReachedType ?? source.rate_limit_reached) === "string"
88
- ? String(source.rateLimitReachedType ?? source.rate_limit_reached)
89
- : null,
102
+ jsonString(source.rateLimitReachedType ?? source.rate_limit_reached) ?? null,
90
103
  };
91
104
  }
92
105
 
93
- function normalizeWhamUsage(body: Record<string, unknown>): QuotaRateLimits {
94
- if (body.rateLimits && typeof body.rateLimits === "object") {
95
- return body as QuotaRateLimits;
96
- }
97
- const container =
98
- body.rate_limits && typeof body.rate_limits === "object"
99
- ? (body.rate_limits as Record<string, unknown>)
100
- : body;
101
- const shared = {
106
+ /** Normalizes snapshots that already use the app-server (camelCase) shape. */
107
+ function normalizeAppServerUsage(body: JsonObject): QuotaRateLimits {
108
+ const byId = isJsonObject(body.rateLimitsByLimitId)
109
+ ? Object.fromEntries(
110
+ Object.entries(body.rateLimitsByLimitId).map(([id, snapshot]) => [
111
+ id,
112
+ snapshotFrom(snapshot),
113
+ ]),
114
+ )
115
+ : null;
116
+
117
+ return { rateLimits: snapshotFrom(body.rateLimits), rateLimitsByLimitId: byId };
118
+ }
119
+
120
+ function normalizeWhamUsage(value: JsonValue): QuotaRateLimits {
121
+ const body = jsonObjectOrEmpty(value);
122
+
123
+ if (isJsonObject(body.rateLimits)) return normalizeAppServerUsage(body);
124
+
125
+ const container = isJsonObject(body.rate_limits) ? body.rate_limits : body;
126
+
127
+ const shared: JsonObject = {
102
128
  plan_type: body.plan_type ?? container.plan_type,
103
129
  credits: body.credits ?? container.credits,
104
130
  rate_limit_reached: body.rate_limit_reached ?? container.rate_limit_reached,
105
131
  };
132
+
106
133
  const primary = snapshotFrom({
107
134
  ...shared,
108
- ...((container.rate_limit ?? container) as Record<string, unknown>),
135
+ ...jsonObjectOrEmpty(container.rate_limit ?? container),
109
136
  });
137
+
110
138
  const additional = Array.isArray(container.additional_rate_limits)
111
139
  ? container.additional_rate_limits
112
140
  : Array.isArray(body.additional_rate_limits)
113
141
  ? body.additional_rate_limits
114
142
  : [];
115
- const byId: Record<string, QuotaRateLimitSnapshot> = {};
143
+
144
+ const byId = new Map<string, QuotaRateLimitSnapshot>();
145
+
116
146
  for (const entry of additional) {
117
- if (!entry || typeof entry !== "object") continue;
118
- const wrapper = entry as Record<string, unknown>;
147
+ if (!isJsonObject(entry)) continue;
148
+
119
149
  const snapshot = snapshotFrom({
120
150
  ...shared,
121
- ...((wrapper.rate_limit ?? wrapper) as Record<string, unknown>),
151
+ ...jsonObjectOrEmpty(entry.rate_limit ?? entry),
122
152
  });
123
- if (snapshot.limitId) byId[snapshot.limitId] = snapshot;
153
+
154
+ if (snapshot.limitId) byId.set(snapshot.limitId, snapshot);
124
155
  }
156
+
125
157
  return {
126
158
  rateLimits: primary,
127
- rateLimitsByLimitId: Object.keys(byId).length ? byId : null,
159
+ rateLimitsByLimitId: byId.size ? Object.fromEntries(byId) : null,
128
160
  };
129
161
  }
130
162
 
@@ -132,29 +164,28 @@ export async function readOpenAIQuotaUsage(
132
164
  source: (() => Promise<OpenAIRateLimitsWithMeta>) | OpenAIQuotaHttpOptions,
133
165
  ): Promise<OpenAIQuotaUsageResult> {
134
166
  try {
135
- const direct = typeof source !== "function";
136
- const { data, cachedAt } = await (typeof source === "function" ? source() : readDirect(source));
167
+ const direct = "credentials" in source;
168
+ const { data, cachedAt } = await ("credentials" in source ? readDirect(source) : source());
137
169
  const rateLimits = data.rateLimits;
138
170
  const primary = rateLimits?.primary;
139
171
 
140
- if (
141
- !primary ||
142
- typeof primary.usedPercent !== "number" ||
143
- !Number.isFinite(primary.usedPercent)
144
- ) {
172
+ if (!primary || !Number.isFinite(primary.usedPercent)) {
145
173
  return { kind: "lookup_failed", reason: "primary usedPercent unavailable" };
146
174
  }
147
175
 
148
- return {
176
+ const result: OpenAIQuotaUsageResult = {
149
177
  kind: "ok",
150
178
  utilizationPct: primary.usedPercent,
151
179
  cacheAgeMs: Math.max(Date.now() - cachedAt, 0),
152
180
  windowDurationMins: primary.windowDurationMins,
153
181
  resetsAt: primary.resetsAt,
154
182
  limitId: rateLimits.limitId,
155
- ...(direct ? { raw: data } : {}),
156
183
  };
184
+
185
+ if (direct) result.raw = data;
186
+
187
+ return result;
157
188
  } catch (e) {
158
- return { kind: "lookup_failed", reason: (e as Error).message };
189
+ return { kind: "lookup_failed", reason: e instanceof Error ? e.message : String(e) };
159
190
  }
160
191
  }