cartethyia 2.7.3 → 2.7.4

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 (352) hide show
  1. package/dist/dashboard/assets/{BannedPage-2TJD37Hb.js → BannedPage-ChG5svFb.js} +1 -1
  2. package/dist/dashboard/assets/BannedPage-ChG5svFb.js.gz +0 -0
  3. package/dist/dashboard/assets/{CliToolDetailPage-xefJNRmJ.js → CliToolDetailPage-BIcWQun7.js} +1 -1
  4. package/dist/dashboard/assets/CliToolDetailPage-BIcWQun7.js.gz +0 -0
  5. package/dist/dashboard/assets/{CliToolsPage-Rj9iHQ3F.js → CliToolsPage-Bf2VelDe.js} +1 -1
  6. package/dist/dashboard/assets/CliToolsPage-Bf2VelDe.js.gz +0 -0
  7. package/dist/dashboard/assets/{CombosPage-JEbYJFxw.js → CombosPage-DCgm5g6g.js} +1 -1
  8. package/dist/dashboard/assets/CombosPage-DCgm5g6g.js.gz +0 -0
  9. package/dist/dashboard/assets/ConsoleLogPage-a8mD0QF1.js +1 -0
  10. package/dist/dashboard/assets/ConsoleLogPage-a8mD0QF1.js.gz +0 -0
  11. package/dist/dashboard/assets/CustomizationPage-ggeMSjte.js +1 -0
  12. package/dist/dashboard/assets/CustomizationPage-ggeMSjte.js.gz +0 -0
  13. package/dist/dashboard/assets/HealthEventsModal-DzSWzE_j.js +1 -0
  14. package/dist/dashboard/assets/HealthEventsModal-DzSWzE_j.js.gz +0 -0
  15. package/dist/dashboard/assets/{LoginPage-C4w7vea-.js → LoginPage-U6AKaJcF.js} +1 -1
  16. package/dist/dashboard/assets/LoginPage-U6AKaJcF.js.gz +0 -0
  17. package/dist/dashboard/assets/{ModelPicker-BtPcAfot.js → ModelPicker-BMS2t8BA.js} +1 -1
  18. package/dist/dashboard/assets/ModelPicker-BMS2t8BA.js.gz +0 -0
  19. package/dist/dashboard/assets/OverviewPage-D2D4IiHl.js +1 -0
  20. package/dist/dashboard/assets/OverviewPage-D2D4IiHl.js.gz +0 -0
  21. package/dist/dashboard/assets/ProviderDetailPage-CrNf7j_X.js +5 -0
  22. package/dist/dashboard/assets/ProviderDetailPage-CrNf7j_X.js.gz +0 -0
  23. package/dist/dashboard/assets/ProvidersPage-BSDbquc1.js +1 -0
  24. package/dist/dashboard/assets/ProvidersPage-BSDbquc1.js.gz +0 -0
  25. package/dist/dashboard/assets/ProxyPage-lsrx7NYg.js +9 -0
  26. package/dist/dashboard/assets/ProxyPage-lsrx7NYg.js.gz +0 -0
  27. package/dist/dashboard/assets/QuotaPage-L0skzML8.js +1 -0
  28. package/dist/dashboard/assets/QuotaPage-L0skzML8.js.gz +0 -0
  29. package/dist/dashboard/assets/SettingsPage-CmAbjRHW.js +1 -0
  30. package/dist/dashboard/assets/SettingsPage-CmAbjRHW.js.gz +0 -0
  31. package/dist/dashboard/assets/{SetupPage-BQS0Rx_u.js → SetupPage-f0DE1h0-.js} +1 -1
  32. package/dist/dashboard/assets/SetupPage-f0DE1h0-.js.gz +0 -0
  33. package/dist/dashboard/assets/{SortableList-BAL5npBC.js → SortableList-ClzpwyET.js} +1 -1
  34. package/dist/dashboard/assets/SortableList-ClzpwyET.js.gz +0 -0
  35. package/dist/dashboard/assets/{StudioPage-TP7voAQ0.js → StudioPage-Cq5tW7hW.js} +3 -3
  36. package/dist/dashboard/assets/StudioPage-Cq5tW7hW.js.gz +0 -0
  37. package/dist/dashboard/assets/UsagePage-Dfc9rWQn.js +56 -0
  38. package/dist/dashboard/assets/UsagePage-Dfc9rWQn.js.gz +0 -0
  39. package/dist/dashboard/assets/api-keys-C38iXoO4.js +1 -0
  40. package/dist/dashboard/assets/api-keys-C38iXoO4.js.gz +0 -0
  41. package/dist/dashboard/assets/app-a159k0ub.css +2 -0
  42. package/dist/dashboard/assets/app-a159k0ub.css.gz +0 -0
  43. package/dist/dashboard/assets/assert-DbRA8EG2.js +15 -0
  44. package/dist/dashboard/assets/assert-DbRA8EG2.js.gz +0 -0
  45. package/dist/dashboard/assets/{badge-B_DMpMJN.js → badge-D9CS6dZk.js} +1 -1
  46. package/dist/dashboard/assets/badge-D9CS6dZk.js.gz +0 -0
  47. package/dist/dashboard/assets/{banner-DMrMPmth.js → banner-DympBNq4.js} +1 -1
  48. package/dist/dashboard/assets/banner-DympBNq4.js.gz +0 -0
  49. package/dist/dashboard/assets/{card-Be5Uzamx.js → card-ibRWU_uT.js} +1 -1
  50. package/dist/dashboard/assets/card-ibRWU_uT.js.gz +0 -0
  51. package/dist/dashboard/assets/cli-tools-63qBGaRw.js +1 -0
  52. package/dist/dashboard/assets/cli-tools-63qBGaRw.js.gz +0 -0
  53. package/dist/dashboard/assets/{clipboard-button-DOfrVmsi.js → clipboard-button-DJm-9pz7.js} +1 -1
  54. package/dist/dashboard/assets/clipboard-button-DJm-9pz7.js.gz +0 -0
  55. package/dist/dashboard/assets/{dialog-C9omdjQt.js → dialog-8fiE6tec.js} +1 -1
  56. package/dist/dashboard/assets/dialog-8fiE6tec.js.gz +0 -0
  57. package/dist/dashboard/assets/{drawer-Bm8WKaGY.js → drawer-CqGYeOrd.js} +1 -1
  58. package/dist/dashboard/assets/drawer-CqGYeOrd.js.gz +0 -0
  59. package/dist/dashboard/assets/{entry-q5HwZ8nN.js → entry-BSAsYsmV.js} +1 -1
  60. package/dist/dashboard/assets/entry-BSAsYsmV.js.gz +0 -0
  61. package/dist/dashboard/assets/entry-CiIQULZD.js +4 -0
  62. package/dist/dashboard/assets/entry-CiIQULZD.js.gz +0 -0
  63. package/dist/dashboard/assets/entry-Daz0lJ-W.js +1 -0
  64. package/dist/dashboard/assets/entry-Daz0lJ-W.js.gz +0 -0
  65. package/dist/dashboard/assets/{formatters-BTScrQpe.js → formatters-D-MTJxMr.js} +1 -1
  66. package/dist/dashboard/assets/formatters-D-MTJxMr.js.gz +0 -0
  67. package/dist/dashboard/assets/{github-badge-Orjuzemr.js → github-badge-CPK47xQx.js} +1 -1
  68. package/dist/dashboard/assets/github-badge-CPK47xQx.js.gz +0 -0
  69. package/dist/dashboard/assets/{index-cPrX5vn6.js → index-DBNPOfhV.js} +2 -2
  70. package/dist/dashboard/assets/index-DBNPOfhV.js.gz +0 -0
  71. package/dist/dashboard/assets/{inline-Dvux8w-0.js → inline-BT0zntEr.js} +1 -1
  72. package/dist/dashboard/assets/inline-BT0zntEr.js.gz +0 -0
  73. package/dist/dashboard/assets/{input-CU1Rgf3p.js → input-Da2ZViWg.js} +1 -1
  74. package/dist/dashboard/assets/input-Da2ZViWg.js.gz +0 -0
  75. package/dist/dashboard/assets/{layout-C_7A6H55.js → layout-BXNeLw6M.js} +1 -1
  76. package/dist/dashboard/assets/layout-BXNeLw6M.js.gz +0 -0
  77. package/dist/dashboard/assets/{list-row-CzFvogbj.js → list-row-BycOrrIV.js} +1 -1
  78. package/dist/dashboard/assets/list-row-BycOrrIV.js.gz +0 -0
  79. package/dist/dashboard/assets/live-DCQNZpF7.js +1 -0
  80. package/dist/dashboard/assets/live-DCQNZpF7.js.gz +0 -0
  81. package/dist/dashboard/assets/minimize-2-BTmMTK4o.js +1 -0
  82. package/dist/dashboard/assets/minimize-2-BTmMTK4o.js.gz +0 -0
  83. package/dist/dashboard/assets/{mutation-JJGQm8TD.js → mutation-Rr0TIvcj.js} +1 -1
  84. package/dist/dashboard/assets/mutation-Rr0TIvcj.js.gz +0 -0
  85. package/dist/dashboard/assets/network-9Yj2ZyLo.js +1 -0
  86. package/dist/dashboard/assets/network-9Yj2ZyLo.js.gz +0 -0
  87. package/dist/dashboard/assets/{numbers-BNDZnsUE.js → numbers-CEagj6ZZ.js} +2 -2
  88. package/dist/dashboard/assets/numbers-CEagj6ZZ.js.gz +0 -0
  89. package/dist/dashboard/assets/{query-keys-CLaNz8Xz.js → query-keys-BloKtaXJ.js} +1 -1
  90. package/dist/dashboard/assets/query-keys-BloKtaXJ.js.gz +0 -0
  91. package/dist/dashboard/assets/quota-Caw7wqci.js +1 -0
  92. package/dist/dashboard/assets/quota-Caw7wqci.js.gz +0 -0
  93. package/dist/dashboard/assets/routing-CO9zCUus.js +1 -0
  94. package/dist/dashboard/assets/routing-CO9zCUus.js.gz +0 -0
  95. package/dist/dashboard/assets/{select-B3iTetHN.js → select-BIYIBkMU.js} +2 -2
  96. package/dist/dashboard/assets/select-BIYIBkMU.js.gz +0 -0
  97. package/dist/dashboard/assets/settings-jS3-PNSA.js +1 -0
  98. package/dist/dashboard/assets/settings-jS3-PNSA.js.gz +0 -0
  99. package/dist/dashboard/assets/{stack-KrOrm7JZ.js → stack-DfgnFDWY.js} +1 -1
  100. package/dist/dashboard/assets/stack-DfgnFDWY.js.gz +0 -0
  101. package/dist/dashboard/assets/{state-DLS7IDMy.js → state-CkfdYWel.js} +1 -1
  102. package/dist/dashboard/assets/state-CkfdYWel.js.gz +0 -0
  103. package/dist/dashboard/assets/{switch-42ItvbAs.js → switch-SYm237HG.js} +1 -1
  104. package/dist/dashboard/assets/switch-SYm237HG.js.gz +0 -0
  105. package/dist/dashboard/assets/{useMutation-Dmnfh1Ue.js → useMutation-BQqCRNIi.js} +1 -1
  106. package/dist/dashboard/assets/useMutation-BQqCRNIi.js.gz +0 -0
  107. package/dist/dashboard/index.html +2 -2
  108. package/dist/dashboard/index.html.gz +0 -0
  109. package/dist/dashboard/precompressed-assets.json +55 -54
  110. package/migrations/full-postgres/0027_qoder_identity_genuine_seed.sql +9 -10
  111. package/migrations/full-postgres/0032_add_provider_pool_accounts.sql +53 -0
  112. package/migrations/full-postgres/0033_add_api_key_ban.sql +9 -0
  113. package/migrations/full-postgres/0034_add_api_key_ban_until.sql +7 -0
  114. package/migrations/full-postgres/0035_align_pool_account_status_enum.sql +21 -0
  115. package/migrations/lite-sqlite3/0027_qoder_identity_genuine_seed.sql +9 -10
  116. package/migrations/lite-sqlite3/0032_add_provider_pool_accounts.sql +44 -0
  117. package/migrations/lite-sqlite3/0033_add_api_key_ban.sql +8 -0
  118. package/migrations/lite-sqlite3/0034_add_api_key_ban_until.sql +5 -0
  119. package/migrations/lite-sqlite3/0035_align_pool_account_status_enum.sql +6 -0
  120. package/package.json +2 -1
  121. package/src/boot/app.ts +9 -8
  122. package/src/config/network.ts +7 -3
  123. package/src/console/api-keys/contracts.ts +52 -0
  124. package/src/console/api-keys/routes.ts +93 -4
  125. package/src/console/audit/routes.ts +3 -3
  126. package/src/console/auth/account-reset.ts +4 -4
  127. package/src/console/auth/session.ts +3 -3
  128. package/src/console/backup/contracts.ts +2 -0
  129. package/src/console/backup/export-endpoint.ts +6 -6
  130. package/src/console/backup/routes.ts +3 -3
  131. package/src/console/cli-tools/routes.ts +3 -3
  132. package/src/console/console-router-guards.ts +2 -2
  133. package/src/console/console-router.ts +4 -4
  134. package/src/console/data-management/routes.ts +3 -3
  135. package/src/console/domain-registration.ts +2 -2
  136. package/src/console/observability/contracts.ts +4 -0
  137. package/src/console/observability/live.ts +3 -3
  138. package/src/console/observability/logs.ts +3 -3
  139. package/src/console/observability/performance.ts +3 -3
  140. package/src/console/observability/routes.ts +3 -3
  141. package/src/console/observability/store.ts +16 -16
  142. package/src/console/providers/catalog/contracts.ts +43 -22
  143. package/src/console/providers/catalog/provider-operations.ts +17 -2
  144. package/src/console/providers/catalog/routes.ts +3 -3
  145. package/src/console/providers/catalog/store-helpers.ts +349 -326
  146. package/src/console/providers/catalog/store.ts +1523 -1292
  147. package/src/console/providers/detail/routes.ts +3 -3
  148. package/src/console/providers/oauth/routes.ts +3 -3
  149. package/src/console/quota/account-quota-tenant.ts +26 -1
  150. package/src/console/quota/account-test-egress.ts +4 -4
  151. package/src/console/routing/model/routes.ts +3 -3
  152. package/src/console/routing/pools/contracts.ts +11 -8
  153. package/src/console/routing/pools/operations.ts +14 -7
  154. package/src/console/routing/pools/probe-verdict.ts +8 -3
  155. package/src/console/routing/pools/relay-deploy.ts +9 -9
  156. package/src/console/routing/pools/routes.ts +3 -3
  157. package/src/console/routing/pools/store.ts +21 -1
  158. package/src/console/settings/routes.ts +3 -3
  159. package/src/console/share/share-router.ts +12 -12
  160. package/src/console/share/share-stats.ts +105 -35
  161. package/src/console/shared/read-through-cache.ts +38 -2
  162. package/src/console/updates/routes.ts +7 -4
  163. package/src/network/outbound-fetch.ts +75 -4
  164. package/src/network/pool/resolver.ts +39 -39
  165. package/src/network/pool-cooldown-store.ts +71 -16
  166. package/src/network/pool-health-machine.ts +21 -1
  167. package/src/network/rate-limit-verdict.ts +68 -0
  168. package/src/network/ssrf.ts +4 -4
  169. package/src/observability/event-query.ts +320 -16
  170. package/src/observability/telemetry-status.ts +1 -1
  171. package/src/persistence/db-sqlite.ts +7 -2
  172. package/src/persistence/readiness.ts +18 -6
  173. package/src/persistence/schema.ts +66 -7
  174. package/src/persistence/stores/api-key-store.ts +99 -3
  175. package/src/persistence/stores/telemetry-store.ts +1 -1
  176. package/src/protocol/codecs.ts +6 -6
  177. package/src/protocol/gateway-error.ts +6 -3
  178. package/src/protocol/image-generation.ts +2 -2
  179. package/src/protocol/response/chat.ts +2 -2
  180. package/src/protocol/response/codex.ts +1 -1
  181. package/src/protocol/response/gemini.ts +1 -1
  182. package/src/protocol/response/messages.ts +5 -5
  183. package/src/protocol/response/responses.ts +2 -2
  184. package/src/protocol/sse-decoder.ts +14 -3
  185. package/src/protocol/translation/capabilities.ts +2 -2
  186. package/src/protocol/upstream/send-claude-messages.ts +3 -3
  187. package/src/providers/adapters/base-adapter.ts +1 -1
  188. package/src/providers/adapters/compatible-adapter.ts +2 -2
  189. package/src/providers/authentication/oauth-refresh-service.ts +2 -2
  190. package/src/providers/configured-provider.ts +1 -1
  191. package/src/providers/discovery/probe-phases.ts +2 -2
  192. package/src/providers/discovery/probing-service.ts +12 -12
  193. package/src/providers/integrations/agentrouter/agentrouter.ts +2 -3
  194. package/src/providers/integrations/alibabacp/alibabacp.ts +2 -2
  195. package/src/providers/integrations/anthropic.ts +4 -11
  196. package/src/providers/integrations/antigravity/antigravity.ts +13 -24
  197. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +1 -1
  198. package/src/providers/integrations/buddy/buddy-discovery-shared.ts +37 -6
  199. package/src/providers/integrations/buddy/buddy-identity.ts +22 -1
  200. package/src/providers/integrations/buddy/buddy-media.ts +16 -16
  201. package/src/providers/integrations/buddy/codebuddy/provider.ts +1 -1
  202. package/src/providers/integrations/buddy/workbuddy/provider.ts +1 -1
  203. package/src/providers/integrations/claude/claude-betas.ts +1 -1
  204. package/src/providers/integrations/claude/claude-credentials.ts +1 -1
  205. package/src/providers/integrations/claude/claude-slow-lane-retry.ts +1 -1
  206. package/src/providers/integrations/claude/claude.ts +3 -10
  207. package/src/providers/integrations/cline/cline.ts +1 -1
  208. package/src/providers/integrations/cloudflareai/cloudflareai.ts +4 -7
  209. package/src/providers/integrations/codex/codex-errors.ts +1 -1
  210. package/src/providers/integrations/codex/codex.ts +14 -15
  211. package/src/providers/integrations/commandcode/commandcode.ts +4 -4
  212. package/src/providers/integrations/commandcode/quota.ts +5 -1
  213. package/src/providers/integrations/cursor/cursor-http2.ts +11 -11
  214. package/src/providers/integrations/cursor/cursor.ts +15 -15
  215. package/src/providers/integrations/factory/factory.ts +4 -4
  216. package/src/providers/integrations/gemini.ts +11 -11
  217. package/src/providers/integrations/github/github.ts +6 -6
  218. package/src/providers/integrations/grok/grok.ts +2 -6
  219. package/src/providers/integrations/inferhub.ts +4 -11
  220. package/src/providers/integrations/kimi/kimi.ts +4 -11
  221. package/src/providers/integrations/kiro/kiro.ts +10 -10
  222. package/src/providers/integrations/muse/muse.ts +6 -9
  223. package/src/providers/integrations/perplexity.ts +2 -2
  224. package/src/providers/integrations/qoder/qoder-catalog.ts +47 -9
  225. package/src/providers/integrations/qoder/qoder-checkin.ts +26 -8
  226. package/src/providers/integrations/qoder/qoder-native-identity-store.ts +9 -8
  227. package/src/providers/integrations/qoder/qoder-realm.ts +15 -0
  228. package/src/providers/integrations/qoder/qoder-umid.ts +12 -13
  229. package/src/providers/integrations/qoder/qoder-virtual.ts +2 -2
  230. package/src/providers/integrations/qoder/qoder.ts +188 -22
  231. package/src/providers/integrations/tokenharbor.ts +2 -2
  232. package/src/providers/integrations/trae/trae.ts +6 -9
  233. package/src/providers/integrations/zcode/oauth.ts +1 -1
  234. package/src/providers/operations/provider-catalog-service.ts +4 -4
  235. package/src/providers/operations/provider-credential-service.ts +16 -4
  236. package/src/providers/operations/upstream-deadline.ts +3 -3
  237. package/src/providers/provider-contracts.ts +47 -3
  238. package/src/providers/provider-credential-resolver.ts +1 -1
  239. package/src/providers/provider-registry.ts +4 -4
  240. package/src/providers/quota/quota-contracts.ts +24 -10
  241. package/src/providers/quota/refresh.ts +2 -2
  242. package/src/providers/search/search-provider.ts +5 -5
  243. package/src/security/abuse.ts +4 -4
  244. package/src/security/admission/reasons.ts +17 -17
  245. package/src/security/api-key-auth.ts +4 -4
  246. package/src/shared/coerce.ts +19 -0
  247. package/src/transport/dispatch/attempt-finalize.ts +4 -3
  248. package/src/transport/dispatch/attempt-loop.ts +10 -7
  249. package/src/transport/dispatch/empty-stream.ts +6 -6
  250. package/src/transport/dispatch/generation.ts +9 -9
  251. package/src/transport/dispatch/leases.ts +8 -8
  252. package/src/transport/dispatch/proxy-request.ts +7 -7
  253. package/src/transport/dispatch/responses-compact.ts +11 -11
  254. package/src/transport/dispatch/retry-policy.ts +4 -67
  255. package/src/transport/dispatch/stream-bridge.ts +1 -1
  256. package/src/transport/dispatch/streaming/finalize.ts +9 -8
  257. package/src/transport/dispatch/systemone.ts +8 -8
  258. package/src/transport/dispatch/token-count.ts +9 -9
  259. package/src/transport/dispatch/websearch.ts +6 -6
  260. package/src/transport/drain-endpoint.ts +2 -2
  261. package/src/transport/failure-policy.ts +13 -6
  262. package/src/transport/guards/auth-guard.ts +6 -5
  263. package/src/transport/guards/body-policy.ts +4 -4
  264. package/src/transport/guards/error-lifecycle.ts +18 -3
  265. package/src/transport/guards/guard-errors.ts +7 -3
  266. package/src/transport/guards/headroom-guard.ts +1 -1
  267. package/src/transport/guards/pipeline.ts +6 -6
  268. package/src/transport/guards/readiness-guard.ts +16 -2
  269. package/src/transport/guards/request-context.ts +3 -3
  270. package/src/transport/request/preparer.ts +20 -20
  271. package/src/transport/request/state.ts +4 -7
  272. package/src/transport/routing/route-catalog.ts +97 -2
  273. package/src/transport/routing/route-model.ts +7 -7
  274. package/src/transport/routing/router.ts +2 -2
  275. package/src/transport/shutdown-notice.ts +19 -7
  276. package/src/transport/surface/completion.ts +1 -1
  277. package/src/transport/surface/messages/encode.ts +1 -1
  278. package/dist/dashboard/assets/BannedPage-2TJD37Hb.js.gz +0 -0
  279. package/dist/dashboard/assets/CliToolDetailPage-xefJNRmJ.js.gz +0 -0
  280. package/dist/dashboard/assets/CliToolsPage-Rj9iHQ3F.js.gz +0 -0
  281. package/dist/dashboard/assets/CombosPage-JEbYJFxw.js.gz +0 -0
  282. package/dist/dashboard/assets/ConsoleLogPage-C3xx_inM.js +0 -1
  283. package/dist/dashboard/assets/ConsoleLogPage-C3xx_inM.js.gz +0 -0
  284. package/dist/dashboard/assets/CustomizationPage-Dw306gJU.js +0 -1
  285. package/dist/dashboard/assets/CustomizationPage-Dw306gJU.js.gz +0 -0
  286. package/dist/dashboard/assets/HealthEventsModal-jaBo1TWV.js +0 -1
  287. package/dist/dashboard/assets/HealthEventsModal-jaBo1TWV.js.gz +0 -0
  288. package/dist/dashboard/assets/LoginPage-C4w7vea-.js.gz +0 -0
  289. package/dist/dashboard/assets/ModelPicker-BtPcAfot.js.gz +0 -0
  290. package/dist/dashboard/assets/OverviewPage-CGC5gNs1.js +0 -1
  291. package/dist/dashboard/assets/OverviewPage-CGC5gNs1.js.gz +0 -0
  292. package/dist/dashboard/assets/ProviderDetailPage-DQcthNGt.js +0 -4
  293. package/dist/dashboard/assets/ProviderDetailPage-DQcthNGt.js.gz +0 -0
  294. package/dist/dashboard/assets/ProvidersPage-0OsXRGXe.js +0 -1
  295. package/dist/dashboard/assets/ProvidersPage-0OsXRGXe.js.gz +0 -0
  296. package/dist/dashboard/assets/ProxyPage-DdPc8slb.js +0 -9
  297. package/dist/dashboard/assets/ProxyPage-DdPc8slb.js.gz +0 -0
  298. package/dist/dashboard/assets/QuotaPage-Ci5O4BnH.js +0 -1
  299. package/dist/dashboard/assets/QuotaPage-Ci5O4BnH.js.gz +0 -0
  300. package/dist/dashboard/assets/SettingsPage-CDZjTt87.js +0 -1
  301. package/dist/dashboard/assets/SettingsPage-CDZjTt87.js.gz +0 -0
  302. package/dist/dashboard/assets/SetupPage-BQS0Rx_u.js.gz +0 -0
  303. package/dist/dashboard/assets/SortableList-BAL5npBC.js.gz +0 -0
  304. package/dist/dashboard/assets/StudioPage-TP7voAQ0.js.gz +0 -0
  305. package/dist/dashboard/assets/UsagePage-Cj_fEzhg.js +0 -56
  306. package/dist/dashboard/assets/UsagePage-Cj_fEzhg.js.gz +0 -0
  307. package/dist/dashboard/assets/api-keys-MbI7e7rt.js +0 -1
  308. package/dist/dashboard/assets/api-keys-MbI7e7rt.js.gz +0 -0
  309. package/dist/dashboard/assets/app-B2vyPoII.css +0 -2
  310. package/dist/dashboard/assets/app-B2vyPoII.css.gz +0 -0
  311. package/dist/dashboard/assets/assert-C0YYuZJ4.js +0 -1
  312. package/dist/dashboard/assets/assert-C0YYuZJ4.js.gz +0 -0
  313. package/dist/dashboard/assets/badge-B_DMpMJN.js.gz +0 -0
  314. package/dist/dashboard/assets/banner-DMrMPmth.js.gz +0 -0
  315. package/dist/dashboard/assets/card-Be5Uzamx.js.gz +0 -0
  316. package/dist/dashboard/assets/cli-tools-muFRphDu.js +0 -1
  317. package/dist/dashboard/assets/cli-tools-muFRphDu.js.gz +0 -0
  318. package/dist/dashboard/assets/clipboard-button-DOfrVmsi.js.gz +0 -0
  319. package/dist/dashboard/assets/dialog-C9omdjQt.js.gz +0 -0
  320. package/dist/dashboard/assets/drawer-Bm8WKaGY.js.gz +0 -0
  321. package/dist/dashboard/assets/entry-DS90gUks.js +0 -1
  322. package/dist/dashboard/assets/entry-DS90gUks.js.gz +0 -0
  323. package/dist/dashboard/assets/entry-j40LJotO.js +0 -4
  324. package/dist/dashboard/assets/entry-j40LJotO.js.gz +0 -0
  325. package/dist/dashboard/assets/entry-q5HwZ8nN.js.gz +0 -0
  326. package/dist/dashboard/assets/formatters-BTScrQpe.js.gz +0 -0
  327. package/dist/dashboard/assets/github-badge-Orjuzemr.js.gz +0 -0
  328. package/dist/dashboard/assets/index-cPrX5vn6.js.gz +0 -0
  329. package/dist/dashboard/assets/inline-Dvux8w-0.js.gz +0 -0
  330. package/dist/dashboard/assets/input-CU1Rgf3p.js.gz +0 -0
  331. package/dist/dashboard/assets/layout-C_7A6H55.js.gz +0 -0
  332. package/dist/dashboard/assets/list-row-CzFvogbj.js.gz +0 -0
  333. package/dist/dashboard/assets/live-KaG15HLQ.js +0 -1
  334. package/dist/dashboard/assets/live-KaG15HLQ.js.gz +0 -0
  335. package/dist/dashboard/assets/minimize-2-D-MN8W8a.js +0 -1
  336. package/dist/dashboard/assets/minimize-2-D-MN8W8a.js.gz +0 -0
  337. package/dist/dashboard/assets/mutation-JJGQm8TD.js.gz +0 -0
  338. package/dist/dashboard/assets/numbers-BNDZnsUE.js.gz +0 -0
  339. package/dist/dashboard/assets/query-keys-CLaNz8Xz.js.gz +0 -0
  340. package/dist/dashboard/assets/quota-DZGyp0N5.js +0 -1
  341. package/dist/dashboard/assets/quota-DZGyp0N5.js.gz +0 -0
  342. package/dist/dashboard/assets/routing-BEH32QOC.js +0 -1
  343. package/dist/dashboard/assets/routing-BEH32QOC.js.gz +0 -0
  344. package/dist/dashboard/assets/select-B3iTetHN.js.gz +0 -0
  345. package/dist/dashboard/assets/settings-Dt3Eqbnq.js +0 -1
  346. package/dist/dashboard/assets/settings-Dt3Eqbnq.js.gz +0 -0
  347. package/dist/dashboard/assets/stack-KrOrm7JZ.js.gz +0 -0
  348. package/dist/dashboard/assets/state-DLS7IDMy.js.gz +0 -0
  349. package/dist/dashboard/assets/switch-42ItvbAs.js.gz +0 -0
  350. package/dist/dashboard/assets/useMutation-Dmnfh1Ue.js.gz +0 -0
  351. /package/dist/dashboard/assets/{app-wvwCi4Eh.js → app-BayTTy01.js} +0 -0
  352. /package/dist/dashboard/assets/{app-wvwCi4Eh.js.gz → app-BayTTy01.js.gz} +0 -0
@@ -89,7 +89,7 @@ function assertAdapterIdentity(expected: ProviderId, adapter: ProviderAdapter):
89
89
  // error: `invalid_request` would blame the caller and invite a byte-identical
90
90
  // retry. `internal_error` (500) says what is true — the gateway is
91
91
  // misconfigured and the request never had a chance.
92
- throw new GatewayError("internal_error", 500, "Loaded adapter ID does not match registration", {
92
+ throw new GatewayError("internal_error", 500, "Loaded adapter ID does not match registration (request {requestId}). Even we have bad days — check the registry wiring 🐻", {
93
93
  provider_id: expected,
94
94
  adapter_id: adapter.provider_id,
95
95
  });
@@ -122,12 +122,12 @@ export class ProviderRegistry {
122
122
  register(registration: ProviderRegistration): void {
123
123
  const providerId = parseProviderId(registration.provider_id);
124
124
  if (this.#registrations.has(providerId)) {
125
- throw new GatewayError("invalid_request", 409, "Provider is already registered", {
125
+ throw new GatewayError("invalid_request", 409, "Provider is already registered (request {requestId}). This name's taken — pick another alias ✨", {
126
126
  provider_id: providerId,
127
127
  });
128
128
  }
129
129
  if (providerId !== registration.provider_id) {
130
- throw new GatewayError("internal_error", 500, "Provider registration ID is not normalized", {
130
+ throw new GatewayError("internal_error", 500, "Provider registration ID is not normalized (request {requestId}). Even we have bad days — check the registry wiring 🐻", {
131
131
  provider_id: registration.provider_id,
132
132
  });
133
133
  }
@@ -158,7 +158,7 @@ export class ProviderRegistry {
158
158
  upsert(registration: ProviderRegistration): boolean {
159
159
  const providerId = parseProviderId(registration.provider_id);
160
160
  if (providerId !== registration.provider_id) {
161
- throw new GatewayError("internal_error", 500, "Provider registration ID is not normalized", {
161
+ throw new GatewayError("internal_error", 500, "Provider registration ID is not normalized (request {requestId}). Even we have bad days — check the registry wiring 🐻", {
162
162
  provider_id: registration.provider_id,
163
163
  });
164
164
  }
@@ -58,20 +58,27 @@ export function totalRemainingCredit(
58
58
  * independent quota windows, or `null` when neither is reported.
59
59
  *
60
60
  * Pooled credits are summed before converting to percent, so one exhausted
61
- * bonus pack cannot hide balance in another. Independent windows keep their
62
- * own remaining percent and the lowest one gates the account.
61
+ * bonus pack cannot hide balance in another. A pooled window without a ceiling
62
+ * (Command Code credits) contributes its absolute remaining to the credit sum
63
+ * but cannot produce a percent, so it is not counted in the percent ratio.
64
+ * Independent windows keep their own remaining percent and the lowest one
65
+ * gates the account.
63
66
  */
64
67
  export function totalRemainingPercent(
65
68
  windows: readonly ProviderQuotaWindow[],
66
69
  ): number | null {
67
70
  let creditLimit = 0;
68
71
  let creditRemaining = 0;
72
+ let hasPooledLimit = false;
69
73
  let independentPercent: number | null = null;
70
74
  for (const window of windows) {
71
75
  if (window.accounting === "pooled") {
72
76
  const balance = creditWindowBalance(window);
73
77
  if (balance !== null) {
74
- creditLimit += balance.limit;
78
+ if (balance.limit !== null) {
79
+ creditLimit += balance.limit;
80
+ hasPooledLimit = true;
81
+ }
75
82
  creditRemaining += balance.remaining;
76
83
  }
77
84
  continue;
@@ -85,8 +92,13 @@ export function totalRemainingPercent(
85
92
  independentPercent =
86
93
  independentPercent === null ? clamped : Math.min(independentPercent, clamped);
87
94
  }
88
- const creditPercent =
89
- creditLimit > 0 ? Math.min(100, Math.max(0, (creditRemaining / creditLimit) * 100)) : null;
95
+ // A pooled window with a reported ceiling (including a 0 ceiling — a spent
96
+ // balance) always defines a percent: 0 ceiling means 0% remaining.
97
+ const creditPercent = hasPooledLimit
98
+ ? creditLimit > 0
99
+ ? Math.min(100, Math.max(0, (creditRemaining / creditLimit) * 100))
100
+ : 0
101
+ : null;
90
102
  if (creditPercent === null) return independentPercent;
91
103
  if (independentPercent === null) return creditPercent;
92
104
  return Math.min(creditPercent, independentPercent);
@@ -94,20 +106,22 @@ export function totalRemainingPercent(
94
106
 
95
107
  function creditWindowBalance(
96
108
  window: ProviderQuotaWindow,
97
- ): { readonly limit: number; readonly remaining: number } | null {
109
+ ): { readonly limit: number | null; readonly remaining: number } | null {
110
+ // A ceiling of 0 is a spent balance, not an absence of data; only a window
111
+ // with no remaining figure at all is skipped. `limit` may be null (Command
112
+ // Code reports credits without a ceiling) — the remaining total still counts.
98
113
  const limit =
99
- typeof window.limit === "number" && Number.isFinite(window.limit) && window.limit > 0
114
+ typeof window.limit === "number" && Number.isFinite(window.limit) && window.limit >= 0
100
115
  ? window.limit
101
116
  : null;
102
- if (limit === null) return null;
103
117
  let remaining: number | null = null;
104
118
  if (typeof window.remaining === "number" && Number.isFinite(window.remaining)) {
105
119
  remaining = window.remaining;
106
- } else if (typeof window.used === "number" && Number.isFinite(window.used)) {
120
+ } else if (typeof window.used === "number" && Number.isFinite(window.used) && limit !== null) {
107
121
  remaining = limit - window.used;
108
122
  }
109
123
  if (remaining === null) return null;
110
- return { limit, remaining: Math.min(limit, Math.max(0, remaining)) };
124
+ return { limit, remaining: Math.max(0, remaining) };
111
125
  }
112
126
 
113
127
  const TIMEOUT_MS = 15_000;
@@ -16,7 +16,7 @@ import { type FetchLike, type ProviderQuotaResult } from "./quota-contracts";
16
16
  import type { ProviderId } from "../provider-contracts";
17
17
  import type { ProviderRegistry } from "../provider-registry";
18
18
  import { GLOBAL_QUOTA_LENS, setCachedQuota } from "./cache";
19
- import { record } from "../authentication/oauth-flow-store";
19
+ import { asJsonbRecord } from "../../shared/coerce";
20
20
 
21
21
  /** Why an account quota refresh failed; `null` means the fetch succeeded. */
22
22
  export type QuotaRefreshFailureCode = "missing_credential" | "quota_fetch_failed";
@@ -355,7 +355,7 @@ async function runQuotaRefresh(
355
355
  ? (deps.markApiKeyCredential?.(target.providerId, credential) ?? credential)
356
356
  : credential;
357
357
  try {
358
- const authState = record(target.authState);
358
+ const authState = asJsonbRecord(target.authState);
359
359
  quota = await fetchProviderQuota(deps.providerRegistry, target.providerId, marked, fetcher, {
360
360
  // Carried so a collector can reuse a reading captured from real
361
361
  // traffic rather than spending a request to rediscover it.
@@ -53,7 +53,7 @@ function stringField(record: Record<string, unknown> | undefined, key: string):
53
53
  export function searchParamsFromBody(body: Record<string, unknown>): SearchParams {
54
54
  const query = typeof body.query === "string" ? body.query.trim() : "";
55
55
  if (query.length === 0)
56
- throw new GatewayError("invalid_request", 400, "search query is required");
56
+ throw new GatewayError("invalid_request", 400, "search query is required (request {requestId}). Check the shape of what you sent — then resend 🪞");
57
57
  const rawMax = body.max_results;
58
58
  const maxResults =
59
59
  typeof rawMax === "number" && Number.isFinite(rawMax)
@@ -90,7 +90,7 @@ export function createSearchAdapter(spec: SearchProviderSpec): ProviderAdapter {
90
90
  throw new GatewayError(
91
91
  "capability_unsupported",
92
92
  400,
93
- `${spec.provider_id} serves web search, not a chat wire`,
93
+ `${spec.provider_id} serves web search, not a chat wire (request {requestId}). Know your tools — this one just isn't in the box 🧰`,
94
94
  );
95
95
  },
96
96
  async websearch(
@@ -113,7 +113,7 @@ export function createSearchAdapter(spec: SearchProviderSpec): ProviderAdapter {
113
113
  throw new GatewayError(
114
114
  "platform_unavailable",
115
115
  502,
116
- `${spec.provider_id} search unreachable: ${error instanceof Error ? error.message : String(error)}`,
116
+ `${spec.provider_id} search unreachable (request {requestId}): ${error instanceof Error ? error.message : String(error)}. The connection ghosted us — come back in a moment 👻`,
117
117
  );
118
118
  }
119
119
  if (!res.ok) {
@@ -121,7 +121,7 @@ export function createSearchAdapter(spec: SearchProviderSpec): ProviderAdapter {
121
121
  throw new GatewayError(
122
122
  res.status >= 500 ? "platform_unavailable" : "invalid_request",
123
123
  res.status,
124
- `${spec.provider_id} search error: ${detail.slice(0, 240)}`,
124
+ `${spec.provider_id} search error (request {requestId}): ${detail.slice(0, 240)}. The upstream came back empty-handed — retry 🌙`,
125
125
  );
126
126
  }
127
127
  const raw = await res.text();
@@ -135,7 +135,7 @@ export function createSearchAdapter(spec: SearchProviderSpec): ProviderAdapter {
135
135
  throw new GatewayError(
136
136
  "platform_unavailable",
137
137
  502,
138
- `${spec.provider_id} search returned an unparseable body`,
138
+ `${spec.provider_id} search returned an unparseable body (request {requestId}). The upstream came back empty-handed — retry 🌙`,
139
139
  );
140
140
  }
141
141
  const results = spec.normalize(data);
@@ -399,7 +399,7 @@ export class IpAbuseProtectionService {
399
399
  readonly signal?: AbortSignal;
400
400
  }): Promise<void> {
401
401
  if (input.signal?.aborted) {
402
- throw new GatewayError("admission_unavailable", 503, "The abuse check never ran because the request was already cancelled.", {
402
+ throw new GatewayError("admission_unavailable", 503, "The abuse check never ran because the request was already cancelled (request {requestId}). Send it again whenever ready 🙃", {
403
403
  reason: "admission-unavailable",
404
404
  });
405
405
  }
@@ -436,7 +436,7 @@ export class IpAbuseProtectionService {
436
436
 
437
437
  if (outcome.banned) {
438
438
  metrics.cartethyia_ip_abuse_refusals_total.inc(1, { reason: "ip-banned" });
439
- throw new GatewayError("quota_exceeded", 429, "This address is temporarily banned for too many failed requests. Wait for the ban to lift, then slow down.", {
439
+ throw new GatewayError("rate_limited", 429, "This address is temporarily banned for too many failed requests (request {requestId}). Wait for the ban to lift, then slow down. You've maxed this window — pace yourself ⏳", {
440
440
  // Distinguishes this from the rate-limited 429 below and from an API
441
441
  // key's own budget. It used to be "admission-unavailable", which is
442
442
  // the label for an *unreachable* store — so every refusal from this
@@ -454,7 +454,7 @@ export class IpAbuseProtectionService {
454
454
 
455
455
  if (outcome.count > this.maxRequestsPerWindow) {
456
456
  metrics.cartethyia_ip_abuse_refusals_total.inc(1, { reason: "ip-rate-limited" });
457
- throw new GatewayError("quota_exceeded", 429, "This address is sending requests faster than the abuse limit allows. Back off and retry after the window rolls over.", {
457
+ throw new GatewayError("rate_limited", 429, "This address is sending requests faster than the abuse limit allows (request {requestId}). Back off and retry after the window rolls over. You've maxed this window — pace yourself ⏳", {
458
458
  reason: "ip-rate-limited",
459
459
  ip,
460
460
  route: input.route,
@@ -464,7 +464,7 @@ export class IpAbuseProtectionService {
464
464
  }
465
465
  } catch (err) {
466
466
  if (err instanceof GatewayError) throw err;
467
- throw new GatewayError("admission_unavailable", 503, "The abuse-tracking store is unreachable, so requests from this address cannot be checked. Retry shortly.", {
467
+ throw new GatewayError("admission_unavailable", 503, "The abuse-tracking store is unreachable, so requests from this address cannot be checked (request {requestId}). Retry shortly. The bouncer stepped out — come back shortly 🚪", {
468
468
  reason: "admission-unavailable",
469
469
  cause: (err as Error).message,
470
470
  });
@@ -19,26 +19,26 @@ const REASON_META: Record<
19
19
  }
20
20
  > = {
21
21
  "rpm-exhausted": {
22
- message: "This key has used its requests-per-minute allowance. Wait for the window to roll over, or raise the key's rpm limit.",
23
- code: "quota_exceeded",
22
+ message: "This key has used its requests-per-minute allowance (request {requestId}). Wait for the window to roll over, or raise the key's rpm limit. You've maxed this window — pace yourself ⏳",
23
+ code: "rate_limited",
24
24
  status: 429,
25
25
  metricLabel: "rpm_exhausted",
26
26
  },
27
27
  "daily-token-limit": {
28
- message: "This key has used today's token allowance. It resets at the start of the next day, or raise the key's daily limit.",
29
- code: "quota_exceeded",
28
+ message: "This key has used today's token allowance (request {requestId}). It resets at the start of the next day, or raise the key's daily limit. Pace yourself — it resets tomorrow ⏳",
29
+ code: "rate_limited",
30
30
  status: 429,
31
31
  metricLabel: "daily",
32
32
  },
33
33
  "monthly-token-limit": {
34
- message: "This key has used this month's token allowance. It resets at the start of the next month, or raise the key's monthly limit.",
35
- code: "quota_exceeded",
34
+ message: "This key has used this month's token allowance (request {requestId}). It resets at the start of the next month, or raise the key's monthly limit. Saving up for next month? It resets then ⏳",
35
+ code: "rate_limited",
36
36
  status: 429,
37
37
  metricLabel: "monthly",
38
38
  },
39
39
  "lifetime-token-budget": {
40
- message: "This key has spent its lifetime token budget. Raise the budget, or issue a new key.",
41
- code: "quota_exceeded",
40
+ message: "This key has spent its lifetime token budget (request {requestId}). Raise the budget, or issue a new key. The piggy bank is empty — top it up or mint a new key 🐷",
41
+ code: "rate_limited",
42
42
  status: 429,
43
43
  metricLabel: "lifetime",
44
44
  },
@@ -47,27 +47,27 @@ const REASON_META: Record<
47
47
  // max-inflight. The two used to be indistinguishable and both named the
48
48
  // provider, sending operators to the wrong setting after a rejection. The
49
49
  // `details` carry the limit so a client can tell which one fired.
50
- message: "This API key is already serving its maximum number of concurrent requests. Retry shortly, or raise the key's concurrency limit.",
50
+ message: "This API key is already serving its maximum number of concurrent requests (request {requestId}). Retry shortly, or raise the key's concurrency limit. It'll free up in a moment 🌊",
51
51
  code: "capacity_exhausted",
52
52
  status: 429,
53
53
  metricLabel: "concurrency",
54
54
  },
55
55
  "tenant-capacity-exhausted": {
56
- message: "This tenant is already serving its maximum number of concurrent requests. Retry shortly, or raise the tenant's concurrency limit.",
56
+ message: "This tenant is already serving its maximum number of concurrent requests (request {requestId}). Retry shortly, or raise the tenant's concurrency limit. The tenant is maxed out right now — chill 🏠",
57
57
  code: "tenant_capacity_exhausted",
58
58
  status: 429,
59
59
  metricLabel: "tenant_capacity",
60
60
  },
61
61
  "model-not-allowed": {
62
- message: "This key is not allowed to use this model under its allowlist. Add the model to the key, or use one it already allows.",
63
- code: "model_not_found",
64
- status: 404,
62
+ message: "This key is not allowed to use this model under its allowlist (request {requestId}). Add the model to the key, or use one it already allows. The door's locked for this one — check the allowlist 🔒",
63
+ code: "model_not_allowed",
64
+ status: 403,
65
65
  metricLabel: "model_not_allowed",
66
66
  },
67
67
  "model-denied": {
68
- message: "This key's denylist blocks this model. Remove the model from the denylist, or use a different model.",
69
- code: "model_not_found",
70
- status: 404,
68
+ message: "This key's denylist blocks this model (request {requestId}). Remove the model from the denylist, or use a different model. This one's on the banned list — pick another 🚫",
69
+ code: "model_not_allowed",
70
+ status: 403,
71
71
  metricLabel: "model_not_allowed",
72
72
  },
73
73
  "admission-unavailable": {
@@ -76,7 +76,7 @@ const REASON_META: Record<
76
76
  // `invalid_counter`, `unknown_reservation`, `invalid_actual_usage`). The
77
77
  // second is not an outage, so the text must not claim one — it names the
78
78
  // effect the client can act on, and the diagnostic stays in `details.reason`.
79
- message: "The gateway could not admit this request. Retry shortly; if it persists, check the instance's Redis or coordination backend.",
79
+ message: "The gateway could not admit this request (request {requestId}). Retry shortly; if it persists, check the instance's Redis or coordination backend. The bouncer stepped out — come back shortly 🚪",
80
80
  code: "admission_unavailable",
81
81
  status: 503,
82
82
  metricLabel: "admission_unavailable",
@@ -239,7 +239,7 @@ export interface ResolvedApiKey {
239
239
  * indistinguishable — distinguishing them would let a caller confirm that a
240
240
  * guessed token once existed — so there is no variant carrying a reason.
241
241
  */
242
- const INVALID_KEY_MESSAGE = "Invalid API key. Check the key you are sending, or create a new one in API Keys and send it as: Authorization: Bearer <key>.";
242
+ const INVALID_KEY_MESSAGE = "Invalid API key (request {requestId}). Check the key you are sending, or create a new one in API Keys and send it as: Authorization: Bearer <key>. Wrong key, right door — double-check and come back 🔑";
243
243
 
244
244
  export function unknownApiKeyError(): GatewayError {
245
245
  return new GatewayError("authentication_failed", 401, INVALID_KEY_MESSAGE);
@@ -258,7 +258,7 @@ export function modelsListApiKeyError(): GatewayError {
258
258
  return new GatewayError(
259
259
  "authentication_failed",
260
260
  401,
261
- "Invalid API key. Rotate the default Cartethyia API key in API Keys and send the new key as: Authorization: Bearer <key>.",
261
+ "Invalid API key (request {requestId}). Rotate the default Cartethyia API key in API Keys and send the new key as: Authorization: Bearer <key>. Wrong key, right door — rotate and try again 🔑",
262
262
  );
263
263
  }
264
264
 
@@ -279,12 +279,12 @@ export function requestToken(headers: Headers | Record<string, string>): string
279
279
  // *different* credentials stay a conflict — accepting whichever came first
280
280
  // would silently decide the request's identity.
281
281
  if (bearerMatch?.[1] === apiKey) return apiKey;
282
- throw new GatewayError("invalid_request", 400, "The request sends two different credentials. Send exactly one key, either as Authorization: Bearer <key> or as the api-key header.");
282
+ throw new GatewayError("invalid_request", 400, "The request sends two different credentials (request {requestId}). Send exactly one key, either as Authorization: Bearer <key> or as the api-key header. Pick one key and stick with it 🤝");
283
283
  }
284
284
  if (hasApiKey) return apiKey;
285
285
  const token = bearerMatch?.[1];
286
286
  if (token === undefined)
287
- throw new GatewayError("invalid_request", 401, "The request carries no usable credential. Send the key as: Authorization: Bearer <key>.");
287
+ throw new GatewayError("authentication_failed", 401, "The request carries no usable credential (request {requestId}). Send the key as: Authorization: Bearer <key>. We need a key to let you in 🔑");
288
288
  return token;
289
289
  }
290
290
 
@@ -13,6 +13,25 @@ export function asRecord(value: unknown): Record<string, unknown> | undefined {
13
13
  : undefined;
14
14
  }
15
15
 
16
+ /**
17
+ * Narrow a stored `auth_state`/JSONB value to a plain object. Postgres'
18
+ * drizzle `jsonb` arrives as a POJO; SQLite's text-backed column arrives as
19
+ * a JSON STRING. Parse the string form so both backends hand downstream
20
+ * readers the same shape — otherwise auth-state-dependent providers (Qoder's
21
+ * userId/machineId, Trae's region, …) silently lose their identity on the
22
+ * SQLite backend.
23
+ */
24
+ export function asJsonbRecord(value: unknown): Record<string, unknown> | undefined {
25
+ if (typeof value === "string") {
26
+ try {
27
+ return asRecord(JSON.parse(value));
28
+ } catch {
29
+ return undefined;
30
+ }
31
+ }
32
+ return asRecord(value);
33
+ }
34
+
16
35
  /** Narrow to a finite number, else `undefined`. `NaN`/`Infinity` are not numbers here. */
17
36
  export function asNumber(value: unknown): number | undefined {
18
37
  return typeof value === "number" && Number.isFinite(value) ? value : undefined;
@@ -425,7 +425,7 @@ export function terminalFailure(
425
425
  return new GatewayError(
426
426
  "transport_unavailable",
427
427
  502,
428
- "The upstream closed the response without reporting a result. Retry; if it keeps happening, the account or pool is unstable.",
428
+ "The upstream closed the response without reporting a result (request {requestId}). Retry; if it keeps happening, the account or pool is unstable. The connection ghosted us — come back in a moment 👻",
429
429
  {},
430
430
  "upstream",
431
431
  );
@@ -437,7 +437,7 @@ export function terminalFailure(
437
437
  const details: Record<string, unknown> = {};
438
438
  if (terminal.provider_stop_reason) details["provider_code"] = terminal.provider_stop_reason;
439
439
  if (terminal.stop_details) Object.assign(details, terminal.stop_details);
440
- const message = typeof details["message"] === "string" ? details["message"] : "upstream request failed";
440
+ const message = typeof details["message"] === "string" ? details["message"] : "upstream request failed (request {requestId}). The upstream came back empty-handed — retry 🌙";
441
441
  // Preserve the upstream's error code when the provider embedded it in
442
442
  // `stop_details` (Cursor writes `quota_exceeded` and `authentication_failed`
443
443
  // there). Wrapping every terminal failure as `transport_unavailable` discarded
@@ -460,7 +460,7 @@ export function terminalFailure(
460
460
  // reports it as truncation instead.
461
461
  return options.truncated === true
462
462
  ? undefined
463
- : new GatewayError("transport_closed", 499, "request was cancelled");
463
+ : new GatewayError("transport_closed", 499, "The request was cancelled (request {requestId}). Send it again whenever ready 🙃");
464
464
  }
465
465
  return undefined;
466
466
  }
@@ -823,6 +823,7 @@ export async function completeAttempt(
823
823
  (completion.error instanceof ProxyConnectRefusalError ? "network" : undefined);
824
824
  await recordPoolDispatchOutcome(completion.db, completion.networkPoolId, {
825
825
  succeeded: completion.status === "completed",
826
+ ...(completion.providerId === undefined ? {} : { providerId: completion.providerId }),
826
827
  ...(completion.error === undefined ? {} : { error: completion.error }),
827
828
  // A CONNECT refusal is the proxy's own answer, given before any
828
829
  // upstream was dialed, so it is a network fault even though the
@@ -41,7 +41,8 @@ import {
41
41
  type ProviderExchangeCapture,
42
42
  } from "./attempt-finalize";
43
43
  import { repriceUsage } from "../../providers/provider-usage";
44
- import { isFreeTierIpBlock, isOAuthCredentialInvalidated, shouldCooldownPool } from "./retry-policy";
44
+ import { classifyEgressFailure } from "../../network/rate-limit-verdict";
45
+ import { isOAuthCredentialInvalidated } from "./retry-policy";
45
46
  import { drainAbortReason } from "../shutdown-notice";
46
47
 
47
48
  /**
@@ -316,7 +317,7 @@ export async function runAttemptLoop<TResult, TAdapter>(
316
317
  };
317
318
  try {
318
319
  if (state.abortController.signal.aborted)
319
- throw new GatewayError("transport_closed", 499, "request was cancelled");
320
+ throw new GatewayError("transport_closed", 499, "The request was cancelled (request {requestId}). Send it again whenever ready 🙃");
320
321
  const { credential, adapter } = await input.prepare(candidate);
321
322
  const host = input.resolveHost?.(candidate);
322
323
  // Single attempt-lease manager owns acquisition order (admission →
@@ -353,9 +354,10 @@ export async function runAttemptLoop<TResult, TAdapter>(
353
354
  });
354
355
  } catch (error) {
355
356
  // A drain aborts the controller, so a mid-flight attempt unwinds here as
356
- // an abort. Surface the typed shutdown error (503 shutting_down /
357
- // restart_for_update) instead of an opaque `transport_closed`, so the
358
- // client's JSON error — not just the SSE path — names the real cause.
357
+ // an abort. Surface the typed shutdown error (503 shutting_down, reason
358
+ // restart_for_update for an update drain) instead of an opaque
359
+ // `transport_closed`, so the client's JSON error — not just the SSE path —
360
+ // names the real cause.
359
361
  const drain = drainAbortReason(state.abortController.signal.reason);
360
362
  if (drain !== undefined) error = drain;
361
363
  lastError = error;
@@ -374,11 +376,12 @@ export async function runAttemptLoop<TResult, TAdapter>(
374
376
  // single candidate carrying every pool). Judging it there is what made a
375
377
  // refused egress end the request instead of failing over to a healthy
376
378
  // one.
377
- const rateLimitedPool = !cancelled && shouldCooldownPool(error, candidate.provider_id);
379
+ const egressVerdict = !cancelled ? classifyEgressFailure(error, candidate.provider_id) : { kind: "none" } as const;
378
380
  // A free-tier refusal is only ever this provider's: the guard is gated on
379
381
  // `ipScopedRateLimit` in the provider metadata, so a 403 from any other
380
382
  // upstream still means what it always did and never retires an egress.
381
- const blockedPool = !cancelled && isFreeTierIpBlock(error, candidate.provider_id);
383
+ const rateLimitedPool = egressVerdict.kind === "cooldown";
384
+ const blockedPool = egressVerdict.kind === "blocked";
382
385
  const poolScopedFailure =
383
386
  (rateLimitedPool || blockedPool) &&
384
387
  networkPoolId !== undefined &&
@@ -107,7 +107,7 @@ export async function fetchWithEmptyStreamRecovery(
107
107
  const sleep = deps.policy?.sleep ?? abortableSleep;
108
108
 
109
109
  if (endpoints.length === 0) {
110
- throw new GatewayError("platform_unavailable", 502, "No endpoints available", {}, "upstream");
110
+ throw new GatewayError("platform_unavailable", 502, "No endpoints available (request {requestId}). The upstream came back empty-handed — retry 🌙", {}, "upstream");
111
111
  }
112
112
 
113
113
  for (let endpointIndex = 0; endpointIndex < endpoints.length; endpointIndex++) {
@@ -118,7 +118,7 @@ export async function fetchWithEmptyStreamRecovery(
118
118
  for (let replay = 0; replay <= replaysAllowed; replay++) {
119
119
  // Check abort before each fetch
120
120
  if (deps.signal.aborted) {
121
- throw new GatewayError("transport_closed", 499, "Aborted");
121
+ throw new GatewayError("transport_closed", 499, "The request was aborted (request {requestId}). Send it again whenever ready 🙃");
122
122
  }
123
123
 
124
124
  // Check deadline floor
@@ -128,7 +128,7 @@ export async function fetchWithEmptyStreamRecovery(
128
128
  throw new GatewayError(
129
129
  "platform_unavailable",
130
130
  502,
131
- "Deadline exhausted before empty-stream replay",
131
+ "Deadline exhausted before empty-stream replay (request {requestId}). Some things just take longer 🐢",
132
132
  {},
133
133
  "upstream",
134
134
  );
@@ -181,7 +181,7 @@ export async function fetchWithEmptyStreamRecovery(
181
181
  throw new GatewayError(
182
182
  "platform_unavailable",
183
183
  502,
184
- "All endpoints returned empty streams",
184
+ "All endpoints returned empty streams (request {requestId}). The upstream came back empty-handed — retry 🌙",
185
185
  {},
186
186
  "upstream",
187
187
  );
@@ -204,11 +204,11 @@ async function fetchWithTimeout(
204
204
  // It must surface as a deadline failure, never as a `transport_closed`
205
205
  // 499 — that code is reserved for genuine client cancellations and would
206
206
  // otherwise hide a real upstream wait behind "cancelled by client".
207
- reject(new GatewayError("deadline_exceeded", 504, "First event timeout", {}, "upstream"));
207
+ reject(new GatewayError("deadline_exceeded", 504, "First event timeout (request {requestId}). Some things just take longer 🐢", {}, "upstream"));
208
208
  }, timeoutMs);
209
209
  signal.addEventListener("abort", () => {
210
210
  clearTimeout(timer);
211
- reject(new GatewayError("transport_closed", 499, "Aborted"));
211
+ reject(new GatewayError("transport_closed", 499, "The request was aborted (request {requestId}). Send it again whenever ready 🙃"));
212
212
  }, { once: true });
213
213
  });
214
214
 
@@ -163,18 +163,18 @@ export function createNativeSurfaceHandler(
163
163
  throw new GatewayError(
164
164
  "invalid_request",
165
165
  400,
166
- `The ${noun} body must be a JSON object. Send one with at least a model and a prompt.`,
166
+ `The ${noun} body must be a JSON object (request {requestId}). Send one with at least a model and a prompt. Check the shape of what you sent — then resend 🪞`,
167
167
  );
168
168
  }
169
169
  const generation = body as Record<string, unknown>;
170
170
  if (typeof generation.model !== "string" || generation.model.trim().length === 0) {
171
- throw new GatewayError("invalid_request", 400, `A model is required: it names the ${noun} provider to use.`);
171
+ throw new GatewayError("invalid_request", 400, `A model is required (request {requestId}): it names the ${noun} provider to use. Check the shape of what you sent — then resend 🪞`);
172
172
  }
173
173
  if (!authorization.snapshot.scopes?.includes("routing:invoke" as never)) {
174
174
  throw new GatewayError(
175
- "invalid_request",
175
+ "policy_rejected",
176
176
  403,
177
- `${noun} generation not allowed for this key — enable routing:invoke on the API key`,
177
+ `${noun} generation not allowed for this key (request {requestId}) — enable routing:invoke on the API key. Not allowed by policy — no hard feelings 🫂`,
178
178
  );
179
179
  }
180
180
  // Trust boundary only. Keep accepting `messages` for chat-backed providers
@@ -183,7 +183,7 @@ export function createNativeSurfaceHandler(
183
183
  const hasPrompt = typeof generation.prompt === "string" && generation.prompt.trim().length > 0;
184
184
  const hasMessages = Array.isArray(generation.messages) && generation.messages.length > 0;
185
185
  if (!hasPrompt && !hasMessages) {
186
- throw new GatewayError("invalid_request", 400, "prompt is required");
186
+ throw new GatewayError("invalid_request", 400, "prompt is required (request {requestId}). Check the shape of what you sent — then resend 🪞");
187
187
  }
188
188
  if (generation.n !== undefined) {
189
189
  const n = generation.n;
@@ -191,7 +191,7 @@ export function createNativeSurfaceHandler(
191
191
  throw new GatewayError(
192
192
  "invalid_request",
193
193
  400,
194
- `n must be an integer between 1 and ${MAX_ITEMS_PER_REQUEST}`,
194
+ `n must be an integer between 1 and ${MAX_ITEMS_PER_REQUEST} (request {requestId}). Check the shape of what you sent — then resend 🪞`,
195
195
  );
196
196
  }
197
197
  }
@@ -219,7 +219,7 @@ export function createNativeSurfaceHandler(
219
219
  candidates: prepared.candidates,
220
220
  tenantId: prepared.authorization.tenantId,
221
221
  strictPoolSelection: false,
222
- exhaustedError: new GatewayError("admission_unavailable", 503, `no eligible ${noun} generation route`),
222
+ exhaustedError: new GatewayError("route_unavailable", 503, `no eligible ${noun} generation route (request {requestId}). The bouncer stepped out — come back shortly 🚪`),
223
223
  prepare: async (candidate) => {
224
224
  const credential = candidate.provider_account_id
225
225
  ? await resolveCredentialForAccount(
@@ -239,7 +239,7 @@ export function createNativeSurfaceHandler(
239
239
  throw new GatewayError(
240
240
  "capability_unsupported",
241
241
  400,
242
- `${noun} generation is unavailable for this provider`,
242
+ `${noun} generation is unavailable for this provider (request {requestId}). Know your tools — this one just isn't in the box 🧰`,
243
243
  );
244
244
  }
245
245
  return { credential, adapter };
@@ -344,7 +344,7 @@ export function createNativeSurfaceHandler(
344
344
  new GatewayError(
345
345
  "capability_unsupported",
346
346
  400,
347
- `${noun} generation is unavailable for this provider`,
347
+ `${noun} generation is unavailable for this provider (request {requestId}). Know your tools — this one just isn't in the box 🧰`,
348
348
  ),
349
349
  )
350
350
  : adapter
@@ -141,7 +141,7 @@ async function acquirePoolSlot(
141
141
  throw new GatewayError(
142
142
  "proxy_pool_unhealthy",
143
143
  503,
144
- "Every proxy pool this route requires is unhealthy or disabled. Repair a pool, or relax the route's pool requirement.",
144
+ "Every proxy pool this route requires is unhealthy or disabled (request {requestId}). Repair a pool, or relax the route's pool requirement. This bridge looks shaky — repair or reroute 🔧",
145
145
  { reason: "no_healthy_pool" },
146
146
  "network",
147
147
  );
@@ -172,7 +172,7 @@ async function acquirePoolSlot(
172
172
  throw new GatewayError(
173
173
  "proxy_pool_unavailable",
174
174
  503,
175
- "No active proxy pool is available because coordination is unavailable.",
175
+ "No active proxy pool is available because coordination is unavailable (request {requestId}). The bouncer stepped out — come back shortly 🚪",
176
176
  { reason: "coordination_unavailable" },
177
177
  "network",
178
178
  );
@@ -219,14 +219,14 @@ async function acquirePoolSlot(
219
219
  failure.reason === "at_capacity" || failure.reason === "provider_at_capacity" ? 429 : 503;
220
220
  const message =
221
221
  failure.reason === "at_capacity"
222
- ? "Every proxy pool for this provider is at its concurrency limit. Retry shortly, or add a pool or raise its capacity."
222
+ ? "Every proxy pool for this provider is at its concurrency limit (request {requestId}). Retry shortly, or add a pool or raise its capacity. The pools are full right now — chill 🌊"
223
223
  : failure.reason === "provider_at_capacity"
224
- ? "Every proxy pool has reached this provider's per-proxy concurrency limit. Retry shortly, raise the limit in the provider's routing settings, or add a pool."
224
+ ? "Every proxy pool has reached this provider's per-proxy concurrency limit (request {requestId}). Retry shortly, raise the limit in the provider's routing settings, or add a pool. The proxies are working overtime — hang tight ⚡️"
225
225
  : failure.reason === "blocked"
226
- ? "All proxy pools are saturated for this provider: the upstream refuses every egress configured for it. Replace a blocked proxy, or add one the upstream has not flagged."
226
+ ? "All proxy pools are saturated for this provider (request {requestId}): the upstream refuses every egress configured for it. Replace a blocked proxy, or add one the upstream has not flagged. All roads blocked — time to find a new route 🗺️"
227
227
  : failure.reason === "cooldown"
228
- ? "Every proxy pool for this provider is cooling down after upstream failures. Retry once the cooldown resets."
229
- : "No active proxy pool is configured for this tenant and provider. Add one, or route this provider through direct egress.";
228
+ ? "Every proxy pool for this provider is cooling down after upstream failures (request {requestId}). Retry once the cooldown resets. The proxies are catching their breath — zzz 💤"
229
+ : "No active proxy pool is configured for this tenant and provider (request {requestId}). Add one, or route this provider through direct egress. No bridge to cross — configure a pool first 🏗️";
230
230
  throw new GatewayError(code, status, message, {
231
231
  pools: failure.pools,
232
232
  // Only measured evidence: a cooldown's real reset instant. The
@@ -279,7 +279,7 @@ export async function acquireAttemptLeases(
279
279
  : await acquirePoolSlot(candidate, source, input.poolSelector, input.strictPoolSelection);
280
280
  if (slot) partial.proxySlot = slot;
281
281
  if (input.strictPoolSelection && input.signal.aborted)
282
- throw new GatewayError("transport_closed", 499, "request was cancelled");
282
+ throw new GatewayError("transport_closed", 499, "The request was cancelled (request {requestId}). Send it again whenever ready 🙃");
283
283
  partial.reservation = await source.routingEngine.reserve({
284
284
  ...source.plan,
285
285
  candidates: [candidate],