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
@@ -116,7 +116,7 @@ export async function handleProviderProxyRequest(
116
116
  const state = deps.stateStore.require(request);
117
117
  const prepared = state.preparedRequest;
118
118
  if (!prepared)
119
- throw new GatewayError("admission_unavailable", 503, "The request was not prepared, so no route can be dialed. Resend the request.");
119
+ throw new GatewayError("route_unavailable", 503, "The request was not prepared, so no route can be dialed (request {requestId}). Resend the request. The bouncer stepped out — resend shortly 🚪");
120
120
  const canonicalRequest = await applyTenantPreferences(prepared, deps.db);
121
121
  // Tenant system-prompt injection, resolved once per request. For providers
122
122
  // whose upstream validates the fixed system prompt as a channel identity
@@ -188,13 +188,13 @@ export async function handleProviderProxyRequest(
188
188
  tenantId: prepared.authorization.tenantId,
189
189
  strictPoolSelection: true,
190
190
  resolveHost: (candidate) => deps.byokUpstreamHosts?.get(candidate.provider_id),
191
- exhaustedError: new GatewayError("admission_unavailable", 503, "No account could be leased for this request. Retry shortly; if it persists, add an account or check that one is healthy."),
191
+ exhaustedError: new GatewayError("route_unavailable", 503, "No account could be leased for this request (request {requestId}). Retry shortly; if it persists, add an account or check that one is healthy. The bouncer stepped out — come back shortly 🚪"),
192
192
  prepare: async (candidate) => {
193
193
  if (!candidate.provider_account_id && candidate.requires_account !== false)
194
194
  throw new GatewayError(
195
- "admission_unavailable",
195
+ "route_unavailable",
196
196
  503,
197
- "This route has no usable credential. Save one on the account, or route to a provider whose account is configured.",
197
+ "This route has no usable credential (request {requestId}). Save one on the account, or route to a provider whose account is configured. Wrong key, right door — check the credential 🔑",
198
198
  );
199
199
  const credential = candidate.provider_account_id
200
200
  ? await resolveCredentialForAccount(
@@ -211,13 +211,13 @@ export async function handleProviderProxyRequest(
211
211
  };
212
212
  const adapter = await deps.resolveProviderAdapter?.(candidate.provider_id);
213
213
  if (!adapter)
214
- throw new GatewayError("admission_unavailable", 503, "provider adapter not registered");
214
+ throw new GatewayError("route_unavailable", 503, "provider adapter not registered (request {requestId}). Even we have bad days — check the registry wiring 🐻");
215
215
  if (!isBundledProviderId(candidate.provider_id)) {
216
216
  if (!deps.byokUpstreamHosts?.get(candidate.provider_id) || !deps.networkBindingFactory)
217
217
  throw new GatewayError(
218
- "admission_unavailable",
218
+ "route_unavailable",
219
219
  503,
220
- "validated network binding is required for configurable upstreams",
220
+ "validated network binding is required for configurable upstreams (request {requestId}). No bridge to cross — configure a pool first 🏗️",
221
221
  );
222
222
  }
223
223
  return { credential, adapter };
@@ -27,10 +27,10 @@ export function createResponsesCompactHandler(deps: ResponsesCompactHandlerDeps)
27
27
  const authorization = state.authorization;
28
28
  const body = state.ingressBody;
29
29
  if (!authorization || body === null || typeof body !== "object" || Array.isArray(body))
30
- throw new GatewayError("invalid_request", 400, "compact request body must be a JSON object");
30
+ throw new GatewayError("invalid_request", 400, "compact request body must be a JSON object (request {requestId}). Check the shape of what you sent — then resend 🪞");
31
31
  const compact = body as Record<string, unknown>;
32
32
  if (typeof compact.model !== "string" || compact.model.trim().length === 0)
33
- throw new GatewayError("invalid_request", 400, "model is required");
33
+ throw new GatewayError("invalid_request", 400, "model is required (request {requestId}). Check the shape of what you sent — then resend 🪞");
34
34
  // A thinking suffix (`model(high)`) is stripped here for the same reason the
35
35
  // canonical preparer strips it: this route matches the model name against
36
36
  // the allowlist and the route plan, and `(high)` is not part of any
@@ -43,18 +43,18 @@ export function createResponsesCompactHandler(deps: ResponsesCompactHandlerDeps)
43
43
  const compactBody: Record<string, unknown> =
44
44
  compactModel === compact.model ? compact : { ...compact, model: compactModel };
45
45
  if (!(typeof compact.input === "string" || Array.isArray(compact.input)))
46
- throw new GatewayError("invalid_request", 400, "input must be a string or array");
46
+ throw new GatewayError("invalid_request", 400, "input must be a string or array (request {requestId}). Check the shape of what you sent — then resend 🪞");
47
47
  if ("instructions" in compact && typeof compact.instructions !== "string")
48
- throw new GatewayError("invalid_request", 400, "instructions must be a string");
48
+ throw new GatewayError("invalid_request", 400, "instructions must be a string (request {requestId}). Check the shape of what you sent — then resend 🪞");
49
49
  if (compact.stream === true)
50
50
  throw new GatewayError(
51
51
  "capability_unsupported",
52
52
  400,
53
- "Responses compact does not support streaming",
53
+ "Responses compact does not support streaming (request {requestId}). Know your tools — this one just isn't in the box 🧰",
54
54
  );
55
55
  const adapter = (await deps.resolveProviderAdapter?.("codex")) as CodexCompactAdapter | undefined;
56
56
  if (!adapter || typeof adapter.compact !== "function")
57
- throw new GatewayError("admission_unavailable", 503, "Codex compact transport unavailable");
57
+ throw new GatewayError("admission_unavailable", 503, "Codex compact transport unavailable (request {requestId}). The bouncer stepped out — come back shortly 🚪");
58
58
  const prepared = await deps.proxyPreparer.prepareNativeCompact({
59
59
  model: compactModel,
60
60
  authorization,
@@ -77,16 +77,16 @@ export function createResponsesCompactHandler(deps: ResponsesCompactHandlerDeps)
77
77
  // Lenient pool semantics: a missing slot tolerates direct egress.
78
78
  strictPoolSelection: false,
79
79
  exhaustedError: new GatewayError(
80
- "admission_unavailable",
80
+ "route_unavailable",
81
81
  503,
82
- "no eligible Codex route",
82
+ "no eligible Codex route (request {requestId}). The bouncer stepped out — come back shortly 🚪",
83
83
  ),
84
84
  prepare: async (candidate) => {
85
85
  if (!candidate.provider_account_id)
86
86
  throw new GatewayError(
87
- "admission_unavailable",
87
+ "route_unavailable",
88
88
  503,
89
- "Codex compact requires an OAuth account",
89
+ "Codex compact requires an OAuth account (request {requestId}). Wrong key, right door — check the credential 🔑",
90
90
  );
91
91
  const credential = await resolveCredentialForAccount(
92
92
  deps.db,
@@ -100,7 +100,7 @@ export function createResponsesCompactHandler(deps: ResponsesCompactHandlerDeps)
100
100
  throw new GatewayError(
101
101
  "capability_unsupported",
102
102
  400,
103
- "Responses compact requires a Codex ChatGPT OAuth account",
103
+ "Responses compact requires a Codex ChatGPT OAuth account (request {requestId}). Wrong key, right door — check the credential 🔑",
104
104
  );
105
105
  return { credential, adapter };
106
106
  },
@@ -8,9 +8,12 @@
8
8
  * than inside it so the loop stays a policy *consumer* — one place decides
9
9
  * retryability (`../failure-policy.ts`), one place decides refresh and
10
10
  * cooldown eligibility (here).
11
+ *
12
+ * The egress-scoped half of the rate-limit decision lives in
13
+ * `../../network/rate-limit-verdict`; this module keeps only the credential-
14
+ * scoped predicates.
11
15
  */
12
16
  import { GatewayError } from "../../protocol/gateway-error";
13
- import { providerRateLimitIsIpScoped } from "../../providers/provider-metadata";
14
17
 
15
18
  /**
16
19
  * Returns true only when an upstream failure is evidence that the OAuth
@@ -32,69 +35,3 @@ export function isOAuthCredentialInvalidated(error: unknown): error is GatewayEr
32
35
  if (/web[ _-]?search|web[ _-]?fetch|x[ _-]?search|tool invocation|search backend/i.test(message)) return false;
33
36
  return error.details.credentialEvidence === true;
34
37
  }
35
-
36
- /**
37
- * A free-tier IP refusal: the upstream has flagged this egress address, not
38
- * this request.
39
- *
40
- * OpenCode answers a blocked address with `403` and its own
41
- * `FreeTierError`/`ModelDeprecated`-style body. The request was never read, so
42
- * retrying it — with the same tools, the same headers, or the same credential —
43
- * cannot succeed from the same address. Only a different egress can.
44
- *
45
- * This is deliberately narrower than "any 403": a content-policy rejection or a
46
- * licence refusal is also a 403 and must not retire a healthy proxy. The
47
- * provider must also declare `ipScopedRateLimit`, which is the same fact the
48
- * 429 path already relies on.
49
- */
50
- export function isFreeTierIpBlock(error: unknown, providerId: string): error is GatewayError {
51
- if (!(error instanceof GatewayError) || error.origin !== "upstream" || error.status !== 403) {
52
- return false;
53
- }
54
- if (!providerRateLimitIsIpScoped(providerId)) return false;
55
- const providerCode = typeof error.details.providerCode === "string" ? error.details.providerCode : "";
56
- const raw = String(error.details.raw ?? "");
57
- return (
58
- /freetier|free.?tier/i.test(providerCode) ||
59
- /FreeTierError|free tier can only be used/i.test(`${error.message} ${raw}`)
60
- );
61
- }
62
-
63
- /**
64
- * A provider-scoped 429, on a provider whose limits follow the egress address.
65
- *
66
- * Only then is the pool the thing that ran out: the upstream counted requests
67
- * from this address, so the next attempt must leave by a different one and
68
- * cooling this pool is the fix. `providerRateLimitIsIpScoped` holds that fact
69
- * per provider (see the metadata field) rather than inferring it from the
70
- * response — a 429 states which bucket was exhausted only in the provider's own
71
- * wording, and every account-keyed provider words it the same way.
72
- *
73
- * For an account-keyed provider the 429 belongs to the credential that was
74
- * dialed. The account health machine already records it and routes the next
75
- * request to a sibling, so cooling the pool as well only removed healthy
76
- * accounts from service: every account sharing that egress lost its route for
77
- * the cooldown window because one of them hit its own limit. A bare 429 with no
78
- * provider scope stays a request-level limit and never cools the pool.
79
- *
80
- * `providerId` is the caller's own candidate, not a field read off the error:
81
- * the in-stream error frames the protocol modules build carry no `providerId`
82
- * detail, so reading it from `details` would silently disable the IP-scoped
83
- * rule on exactly the streaming path it exists for.
84
- *
85
- * A type guard on `GatewayError`, so a caller that passes this check can hand
86
- * the same value to `flagPoolCooldown` without a second narrowing step.
87
- */
88
- export function shouldCooldownPool(error: unknown, providerId: string): error is GatewayError {
89
- if (
90
- !(
91
- error instanceof GatewayError &&
92
- error.origin === "upstream" &&
93
- error.status === 429 &&
94
- error.details.rateLimitScope === "provider"
95
- )
96
- ) {
97
- return false;
98
- }
99
- return providerRateLimitIsIpScoped(providerId);
100
- }
@@ -60,7 +60,7 @@ export function createDispatchStreamEncoder(
60
60
  throw new GatewayError(
61
61
  "capability_unsupported",
62
62
  400,
63
- `Unknown surface: ${surface}`,
63
+ `Unknown surface: ${surface} (request {requestId}). Know your tools — this one just isn't in the box 🧰`,
64
64
  { surface },
65
65
  );
66
66
  }
@@ -24,7 +24,7 @@ import type { Reservation } from "../../routing/route-model";
24
24
  import { repriceUsage } from "../../../providers/provider-usage";
25
25
  import { metrics } from "../../../observability/metrics";
26
26
  import { flagPoolBlocked, flagPoolCooldown } from "../../../network/pool-health";
27
- import { isFreeTierIpBlock, shouldCooldownPool } from "../retry-policy";
27
+ import { classifyEgressFailure } from "../../../network/rate-limit-verdict";
28
28
  import { releaseAttemptLeases } from "../leases";
29
29
  import type { AttemptLeaseOwnership } from "../leases";
30
30
  import { finalizeRequestTelemetry } from "../../guards/error-lifecycle";
@@ -186,7 +186,7 @@ export class StreamFinalizer {
186
186
  // origin is the upstream's: labelling it `cartethyia` reported a
187
187
  // provider that stopped sending as a gateway defect.
188
188
  this.input.state.abortController.abort(
189
- new GatewayError("deadline_exceeded", 504, "The upstream stopped sending data mid-stream. Retry; if it keeps happening, the account or pool is unstable.", {}, "upstream"),
189
+ new GatewayError("deadline_exceeded", 504, "The upstream stopped sending data mid-stream (request {requestId}). Retry; if it keeps happening, the account or pool is unstable. The connection ghosted us — come back in a moment 👻", {}, "upstream"),
190
190
  );
191
191
  }, timeoutMs);
192
192
  }
@@ -363,7 +363,7 @@ export class StreamFinalizer {
363
363
  if (drain) return drain;
364
364
  if (reason instanceof GatewayError && reason.code === "deadline_exceeded") return reason;
365
365
  if (reason instanceof DOMException && reason.name === "TimeoutError")
366
- return new GatewayError("deadline_exceeded", 504, "request deadline exceeded", {}, "cartethyia");
366
+ return new GatewayError("deadline_exceeded", 504, "request deadline exceeded (request {requestId}). Some things just take longer 🐢", {}, "cartethyia");
367
367
  return undefined;
368
368
  }
369
369
 
@@ -453,7 +453,7 @@ export class StreamFinalizer {
453
453
  ? new GatewayError(
454
454
  "transport_unavailable",
455
455
  502,
456
- "The upstream closed the stream before it reported a final result. Retry; if it keeps happening, the account or pool is unstable.",
456
+ "The upstream closed the stream before it reported a final result (request {requestId}). Retry; if it keeps happening, the account or pool is unstable. The connection ghosted us — come back in a moment 👻",
457
457
  {},
458
458
  "upstream",
459
459
  )
@@ -531,7 +531,7 @@ export class StreamFinalizer {
531
531
  ? new GatewayError(
532
532
  "deadline_exceeded",
533
533
  504,
534
- "request deadline exceeded",
534
+ "request deadline exceeded (request {requestId}). Some things just take longer 🐢",
535
535
  {},
536
536
  "cartethyia",
537
537
  )
@@ -579,10 +579,11 @@ export class StreamFinalizer {
579
579
  ...(pipeline.clientResponseText.length > 0 ? { clientResponseText: pipeline.clientResponseText } : {}),
580
580
  });
581
581
  this.streamSettled = true;
582
- if (candidate.networkPoolId && !cancelled && deps.poolSelector) {
583
- if (shouldCooldownPool(err, candidate.providerId)) {
582
+ if (candidate.networkPoolId && !cancelled && deps.poolSelector && err instanceof GatewayError) {
583
+ const egressVerdict = classifyEgressFailure(err, candidate.providerId);
584
+ if (egressVerdict.kind === "cooldown") {
584
585
  flagPoolCooldown(deps.poolSelector, deps.db, candidate.networkPoolId, candidate.providerId, err);
585
- } else if (isFreeTierIpBlock(err, candidate.providerId)) {
586
+ } else if (egressVerdict.kind === "blocked") {
586
587
  flagPoolBlocked(deps.poolSelector, deps.db, candidate.networkPoolId, candidate.providerId, err);
587
588
  }
588
589
  }
@@ -33,10 +33,10 @@ export function createSystemoneHandler(deps: SystemoneHandlerDeps) {
33
33
  const authorization = state.authorization;
34
34
  const body = state.ingressBody;
35
35
  if (!authorization || body === null || typeof body !== "object" || Array.isArray(body))
36
- throw new GatewayError("invalid_request", 400, "systemone request body must be a JSON object");
36
+ throw new GatewayError("invalid_request", 400, "systemone request body must be a JSON object (request {requestId}). Check the shape of what you sent — then resend 🪞");
37
37
  const decision = body as Record<string, unknown>;
38
38
  if (typeof decision.model !== "string" || decision.model.trim().length === 0)
39
- throw new GatewayError("invalid_request", 400, "model is required");
39
+ throw new GatewayError("invalid_request", 400, "model is required (request {requestId}). Check the shape of what you sent — then resend 🪞");
40
40
  // A thinking suffix (`model(high)`) is stripped for the same reason the
41
41
  // canonical preparer strips it: the route matches the model name against the
42
42
  // allowlist and the route plan, and `(high)` is not part of any registered
@@ -49,14 +49,14 @@ export function createSystemoneHandler(deps: SystemoneHandlerDeps) {
49
49
  // upstream's to validate. `state` and a `questions` object are what make a
50
50
  // System One request a System One request at all.
51
51
  if (decision.state === undefined || decision.state === null)
52
- throw new GatewayError("invalid_request", 400, "state is required");
52
+ throw new GatewayError("invalid_request", 400, "state is required (request {requestId}). Check the shape of what you sent — then resend 🪞");
53
53
  if (
54
54
  decision.questions === undefined ||
55
55
  decision.questions === null ||
56
56
  typeof decision.questions !== "object" ||
57
57
  Array.isArray(decision.questions)
58
58
  )
59
- throw new GatewayError("invalid_request", 400, "questions must be an object");
59
+ throw new GatewayError("invalid_request", 400, "questions must be an object (request {requestId}). Check the shape of what you sent — then resend 🪞");
60
60
  const prepared = await deps.proxyPreparer.prepareNativeService({
61
61
  model: bareModel,
62
62
  serviceKind: "systemone",
@@ -80,9 +80,9 @@ export function createSystemoneHandler(deps: SystemoneHandlerDeps) {
80
80
  // Lenient pool semantics: a missing slot tolerates direct egress.
81
81
  strictPoolSelection: false,
82
82
  exhaustedError: new GatewayError(
83
- "admission_unavailable",
83
+ "route_unavailable",
84
84
  503,
85
- "no eligible System One route",
85
+ "no eligible System One route (request {requestId}). The bouncer stepped out — come back shortly 🚪",
86
86
  ),
87
87
  prepare: async (candidate) => {
88
88
  const credential = candidate.provider_account_id
@@ -103,7 +103,7 @@ export function createSystemoneHandler(deps: SystemoneHandlerDeps) {
103
103
  throw new GatewayError(
104
104
  "capability_unsupported",
105
105
  400,
106
- "System One transport unavailable for this provider",
106
+ "System One transport unavailable for this provider (request {requestId}). Know your tools — this one just isn't in the box 🧰",
107
107
  );
108
108
  return { credential, adapter };
109
109
  },
@@ -148,7 +148,7 @@ export function createSystemoneHandler(deps: SystemoneHandlerDeps) {
148
148
  throw new GatewayError(
149
149
  response.status >= 500 ? "platform_unavailable" : "invalid_request",
150
150
  response.status,
151
- `System One upstream error: ${detail.slice(0, 240)}`,
151
+ `System One upstream error (request {requestId}): ${detail.slice(0, 240)}`,
152
152
  );
153
153
  }
154
154
  // Prefer the usage the decision endpoint reported; fall back to the
@@ -57,7 +57,7 @@ export function createTokenCountHandler(deps: TokenCountHandlerDeps) {
57
57
  throw new GatewayError(
58
58
  "invalid_request",
59
59
  400,
60
- "The count_tokens body must be a JSON object, the same shape POST /v1/messages accepts.",
60
+ "The count_tokens body must be a JSON object (request {requestId}), the same shape POST /v1/messages accepts. Check the shape of what you sent — then resend 🪞",
61
61
  );
62
62
  }
63
63
  const countBody = body as Record<string, unknown>;
@@ -66,16 +66,16 @@ export function createTokenCountHandler(deps: TokenCountHandlerDeps) {
66
66
  // upstream's own required fields, so the gateway does not restate its own
67
67
  // narrower rule and risk rejecting a body the provider would accept.
68
68
  if (typeof countBody.model !== "string" || countBody.model.trim().length === 0) {
69
- throw new GatewayError("invalid_request", 400, "A model is required: it names the provider to count with.");
69
+ throw new GatewayError("invalid_request", 400, "A model is required (request {requestId}): it names the provider to count with. Check the shape of what you sent — then resend 🪞");
70
70
  }
71
71
  if (!Array.isArray(countBody.messages)) {
72
- throw new GatewayError("invalid_request", 400, "messages must be an array — there is nothing to count without it.");
72
+ throw new GatewayError("invalid_request", 400, "messages must be an array (request {requestId}) — there is nothing to count without it. Check the shape of what you sent — then resend 🪞");
73
73
  }
74
74
  if (!authorization.snapshot.scopes?.includes("routing:invoke" as never)) {
75
75
  throw new GatewayError(
76
- "invalid_request",
76
+ "policy_rejected",
77
77
  403,
78
- "token counting not allowed for this key — enable routing:invoke on the API key",
78
+ "token counting not allowed for this key (request {requestId}) — enable routing:invoke on the API key. Not allowed by policy — no hard feelings 🫂",
79
79
  );
80
80
  }
81
81
 
@@ -111,9 +111,9 @@ export function createTokenCountHandler(deps: TokenCountHandlerDeps) {
111
111
  tenantId: prepared.authorization.tenantId,
112
112
  strictPoolSelection: false,
113
113
  exhaustedError: new GatewayError(
114
- "admission_unavailable",
114
+ "route_unavailable",
115
115
  503,
116
- "no eligible route can count tokens for this model",
116
+ "no eligible route can count tokens for this model (request {requestId}). The bouncer stepped out — come back shortly 🚪",
117
117
  ),
118
118
  prepare: async (candidate) => {
119
119
  const credential = candidate.provider_account_id
@@ -131,7 +131,7 @@ export function createTokenCountHandler(deps: TokenCountHandlerDeps) {
131
131
  };
132
132
  const adapter = await deps.resolveProviderAdapter?.(candidate.provider_id);
133
133
  if (!adapter) {
134
- throw new GatewayError("capability_unsupported", 400, "token counting is unavailable for this provider");
134
+ throw new GatewayError("capability_unsupported", 400, "token counting is unavailable for this provider (request {requestId}). Know your tools — this one just isn't in the box 🧰");
135
135
  }
136
136
  return { credential, adapter };
137
137
  },
@@ -146,7 +146,7 @@ export function createTokenCountHandler(deps: TokenCountHandlerDeps) {
146
146
 
147
147
  const adapter = context.adapter;
148
148
  if (typeof adapter.dispatchNativeService !== "function") {
149
- throw new GatewayError("capability_unsupported", 400, "token counting is unavailable for this provider");
149
+ throw new GatewayError("capability_unsupported", 400, "token counting is unavailable for this provider (request {requestId}). Know your tools — this one just isn't in the box 🧰");
150
150
  }
151
151
 
152
152
  const target: ProviderDispatchTarget = {
@@ -50,10 +50,10 @@ export function createWebsearchHandler(deps: WebsearchHandlerDeps) {
50
50
  const authorization = state.authorization;
51
51
  const body = state.ingressBody;
52
52
  if (!authorization || body === null || typeof body !== "object" || Array.isArray(body))
53
- throw new GatewayError("invalid_request", 400, "The search body must be a JSON object. Send one with at least a model and a query.");
53
+ throw new GatewayError("invalid_request", 400, "The search body must be a JSON object (request {requestId}). Send one with at least a model and a query. Check the shape of what you sent — then resend 🪞");
54
54
  const search = body as Record<string, unknown>;
55
55
  if (typeof search.model !== "string" || search.model.trim().length === 0)
56
- throw new GatewayError("invalid_request", 400, "A model is required: it names the search provider to use.");
56
+ throw new GatewayError("invalid_request", 400, "A model is required (request {requestId}): it names the search provider to use. Check the shape of what you sent — then resend 🪞");
57
57
  // The search "model" names the provider (or an alias/combo of one). A
58
58
  // thinking suffix is stripped for the same reason the canonical preparer
59
59
  // strips it: it is not part of any registered id.
@@ -61,12 +61,12 @@ export function createWebsearchHandler(deps: WebsearchHandlerDeps) {
61
61
  const searchBody: Record<string, unknown> =
62
62
  bareModel === search.model ? search : { ...search, model: bareModel };
63
63
  if (!authorization.snapshot.scopes?.includes("search:invoke" as never)) {
64
- throw new GatewayError("invalid_request", 403, "search not allowed for this key — enable search:invoke on the API key");
64
+ throw new GatewayError("policy_rejected", 403, "search not allowed for this key (request {requestId}) — enable search:invoke on the API key. Not allowed by policy — no hard feelings 🫂");
65
65
  }
66
66
  // Trust boundary only: the query must be a non-empty string. The adapter
67
67
  // validates the remaining shape when it maps the body onto its provider.
68
68
  if (typeof search.query !== "string" || search.query.trim().length === 0)
69
- throw new GatewayError("invalid_request", 400, "query is required");
69
+ throw new GatewayError("invalid_request", 400, "query is required (request {requestId}). Check the shape of what you sent — then resend 🪞");
70
70
 
71
71
  const prepared = await deps.proxyPreparer.prepareNativeService({
72
72
  model: bareModel,
@@ -89,7 +89,7 @@ export function createWebsearchHandler(deps: WebsearchHandlerDeps) {
89
89
  candidates: prepared.candidates,
90
90
  tenantId: prepared.authorization.tenantId,
91
91
  strictPoolSelection: false,
92
- exhaustedError: new GatewayError("admission_unavailable", 503, "no eligible search route"),
92
+ exhaustedError: new GatewayError("route_unavailable", 503, "no eligible search route (request {requestId}). The bouncer stepped out — come back shortly 🚪"),
93
93
  prepare: async (candidate) => {
94
94
  const credential = candidate.provider_account_id
95
95
  ? await resolveCredentialForAccount(
@@ -109,7 +109,7 @@ export function createWebsearchHandler(deps: WebsearchHandlerDeps) {
109
109
  throw new GatewayError(
110
110
  "capability_unsupported",
111
111
  400,
112
- "web search is unavailable for this provider",
112
+ "web search is unavailable for this provider (request {requestId}). Know your tools — this one just isn't in the box 🧰",
113
113
  );
114
114
  return { credential, adapter };
115
115
  },
@@ -55,9 +55,9 @@ export function createDrainHandler(
55
55
  ): (context: { request: Request }) => Promise<Response> {
56
56
  return async ({ request }) => {
57
57
  if (!isLoopbackPeer(deps.resolvePeerAddress(request)))
58
- throw new GatewayError("invalid_request", 403, "drain is only available from loopback");
58
+ throw new GatewayError("policy_rejected", 403, "drain is only available from loopback (request {requestId}). This operation is only allowed from a trusted loopback connection — no hard feelings 🫂");
59
59
  if (!tokenMatches(request.headers.get("x-drain-token"), deps.token))
60
- throw new GatewayError("invalid_request", 401, "invalid drain token");
60
+ throw new GatewayError("authentication_failed", 401, "invalid drain token (request {requestId}). Wrong key, right door — check the token 🔑");
61
61
  // Acknowledge first, then drain: the response must be on the wire before the
62
62
  // shutdown path starts closing sockets, or the caller sees a dropped
63
63
  // connection instead of a 202.
@@ -167,7 +167,7 @@ export function statusToGatewayErrorCode(status: number): GatewayErrorCode {
167
167
  if (status === 413) return "request_too_large";
168
168
  if (status === 415) return "unsupported_media_type";
169
169
  if (status === 422) return "upstream_unprocessable";
170
- if (status === 529) return "capacity_exhausted";
170
+ if (status === 529) return "upstream_overloaded";
171
171
  if (status >= 500 && status <= 599) return "platform_unavailable";
172
172
  return "invalid_request";
173
173
  }
@@ -274,7 +274,9 @@ export function classifyUpstreamFailure(error: unknown): UpstreamFailurePolicy {
274
274
  error.code === "capability_unsupported" ||
275
275
  error.code === "model_not_found" ||
276
276
  error.code === "admission_unavailable" ||
277
+ error.code === "route_unavailable" ||
277
278
  error.code === "capacity_exhausted" ||
279
+ error.code === "upstream_overloaded" ||
278
280
  // A provider max-inflight rejection: the plan's next sibling account may
279
281
  // have a free slot, exactly as with an API-key concurrency rejection.
280
282
  error.code === "provider_capacity_exhausted" ||
@@ -286,6 +288,7 @@ export function classifyUpstreamFailure(error: unknown): UpstreamFailurePolicy {
286
288
  error.code === "accounts_unavailable" ||
287
289
  error.code === "accounts_rate_limited" ||
288
290
  error.code === "quota_exceeded" ||
291
+ error.code === "rate_limited" ||
289
292
  error.code === "authentication_failed" ||
290
293
  error.code === "proxy_auth_required" ||
291
294
  error.code === "deadline_exceeded" ||
@@ -427,10 +430,14 @@ const CATEGORY_HTTP_STATUS: Partial<Record<GatewayErrorCode | "unknown_error", n
427
430
  // in this process.
428
431
  transport_unavailable: 502,
429
432
  platform_unavailable: 502,
433
+ upstream_overloaded: 529,
430
434
  deadline_exceeded: 504,
431
435
  transport_closed: 499,
432
436
  shutting_down: 503,
433
437
  overloaded: 529,
438
+ route_unavailable: 503,
439
+ model_not_allowed: 403,
440
+ rate_limited: 429,
434
441
  unknown_error: 500,
435
442
  };
436
443
 
@@ -624,7 +631,7 @@ export function upstreamQueuedError(
624
631
  503,
625
632
  detail.length > 0
626
633
  ? detail
627
- : "The upstream has queued this request. Retry after the stated interval.",
634
+ : "The upstream has queued this request (request {requestId}); retry after the stated interval. You're in line — no cuts 💛",
628
635
  {
629
636
  providerId,
630
637
  reason: "upstream-queued",
@@ -816,7 +823,7 @@ export function classifyUpstreamError(
816
823
  }
817
824
  if (normalized !== undefined && CAPACITY_ERROR_IDENTIFIERS.has(normalized))
818
825
  return {
819
- code: "capacity_exhausted",
826
+ code: "upstream_overloaded",
820
827
  status: status === 503 || status === 529 ? status : 529,
821
828
  origin: "upstream",
822
829
  };
@@ -969,7 +976,7 @@ export async function throwIfHtmlResponse(response: Response): Promise<void> {
969
976
  throw new GatewayError(
970
977
  "transport_unavailable",
971
978
  502,
972
- `Upstream returned an HTML error page: ${summary}`.slice(0, 300),
979
+ `Upstream returned an HTML error page: ${summary} (request {requestId}). The provider's edge is having a moment — hang tight ☕️`.slice(0, 320),
973
980
  { upstreamStatus: response.status, contentType, accountScope: true },
974
981
  "upstream",
975
982
  );
@@ -1011,11 +1018,11 @@ export function fallbackRetryDelayMs(attemptIndex: number): number {
1011
1018
 
1012
1019
  /** Sleeps until the delay expires or the request is aborted. */
1013
1020
  export function sleep(ms: number, signal: AbortSignal): Promise<void> {
1014
- if (signal.aborted) return Promise.reject(new GatewayError("transport_closed", 499, "request was cancelled"));
1021
+ if (signal.aborted) return Promise.reject(new GatewayError("transport_closed", 499, "The request was cancelled (request {requestId}). Send it again whenever ready 🙃"));
1015
1022
  if (ms <= 0) return Promise.resolve();
1016
1023
  const { promise, resolve, reject } = Promise.withResolvers<void>();
1017
1024
  const timer = setTimeout(() => { signal.removeEventListener("abort", onAbort); resolve(); }, ms);
1018
- const onAbort = () => { clearTimeout(timer); reject(new GatewayError("transport_closed", 499, "request was cancelled")); };
1025
+ const onAbort = () => { clearTimeout(timer); reject(new GatewayError("transport_closed", 499, "The request was cancelled (request {requestId}). Send it again whenever ready 🙃")); };
1019
1026
  signal.addEventListener("abort", onAbort, { once: true });
1020
1027
  return promise;
1021
1028
  }
@@ -62,9 +62,9 @@ export function createApiKeyAuthenticationMiddleware(deps: {
62
62
  safe.name = error instanceof Error ? error.name : "Error";
63
63
  log.error("[auth] key lookup failed; treating as unavailable", safe);
64
64
  throw new GatewayError(
65
- "accounts_unavailable",
65
+ "admission_unavailable",
66
66
  503,
67
- "The gateway could not verify this API key because its credential store is unavailable. Retry shortly.",
67
+ "The gateway could not verify this API key because its credential store is unavailable (request {requestId}). Retry shortly. The bouncer stepped out — come back shortly 🚪",
68
68
  {},
69
69
  "cartethyia",
70
70
  );
@@ -76,7 +76,7 @@ export function createApiKeyAuthenticationMiddleware(deps: {
76
76
  scopes: authorization.scopes,
77
77
  });
78
78
  if (!decision.scopes.includes("routing:invoke"))
79
- throw new GatewayError("invalid_request", 403, "This API key is not allowed to invoke routing. Grant it the routing:invoke scope, or use a key that has it.");
79
+ throw new GatewayError("policy_rejected", 403, "This API key is not allowed to invoke routing (request {requestId}). Grant it the routing:invoke scope, or use a key that has it. Not allowed by policy — no hard feelings 🫂");
80
80
  // Kill switch: refuse before any upstream is touched. Distinct from the
81
81
  // scope check above, which is a property of the key — this is the tenant
82
82
  // having stopped all traffic, and it applies to every key the tenant owns.
@@ -86,7 +86,8 @@ export function createApiKeyAuthenticationMiddleware(deps: {
86
86
  throw new GatewayError(
87
87
  "platform_unavailable",
88
88
  503,
89
- "This gateway has invoke switched off. Nothing upstream is being dialed while the switch is off; turn it back on from the dashboard overview to resume routing.",
89
+ "This gateway has invoke switched off (request {requestId}). Nothing upstream is being dialed while the switch is off; turn it back on from the dashboard overview to resume routing. Flip the switch back on when ready 🔌",
90
+ { reason: "invoke_switched_off" },
90
91
  );
91
92
  // A key may refuse a downstream router it has been resold to. This is a
92
93
  // policy check on the resolved key, so it belongs with the other
@@ -100,7 +101,7 @@ export function createApiKeyAuthenticationMiddleware(deps: {
100
101
  throw new GatewayError(
101
102
  "client_router_denied",
102
103
  403,
103
- "No API invocation access for this client.",
104
+ "No API invocation access for this client (request {requestId}). This router isn't on the guest list — denied, but politely 🤗",
104
105
  { reason: "client_router_denied", clientRouter: denied },
105
106
  );
106
107
  deps.stateStore.require(request).authorization = authorization;
@@ -77,11 +77,11 @@ export async function readIngressBody(
77
77
  const isJsonRoute = isJsonProxyRoute(p);
78
78
  const contentType = request.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase();
79
79
  if (!contentType) {
80
- if (isJsonRoute) throw new GatewayError("unsupported_media_type", 415, "This endpoint reads JSON, so the request needs a content-type: application/json header.");
80
+ if (isJsonRoute) throw new GatewayError("unsupported_media_type", 415, "This endpoint reads JSON, so the request needs a content-type: application/json header (request {requestId}). We speak JSON here — send JSON 💬");
81
81
  return undefined;
82
82
  }
83
83
  if (contentType !== "application/json") {
84
- if (isJsonRoute) throw new GatewayError("unsupported_media_type", 415, `This endpoint reads JSON and cannot parse '${contentType}'. Send content-type: application/json.`);
84
+ if (isJsonRoute) throw new GatewayError("unsupported_media_type", 415, `This endpoint reads JSON and cannot parse '${contentType}' (request {requestId}). Send content-type: application/json. We speak JSON here — send JSON 💬`);
85
85
  return undefined;
86
86
  }
87
87
  const maxBytes = options.maxBodyBytes ?? 1_048_576;
@@ -91,7 +91,7 @@ export async function readIngressBody(
91
91
  throw bodyTooLargeError(maxBytes);
92
92
 
93
93
  const reader = request.body?.getReader();
94
- if (!reader) throw new GatewayError("invalid_request", 400, "The request body could not be read. Resend it with a JSON body.");
94
+ if (!reader) throw new GatewayError("invalid_request", 400, "The request body could not be read (request {requestId}). Resend it with a JSON body. Check the shape of what you sent — then resend 🪞");
95
95
  // Collect raw chunks then decode once. String `+=` on each chunk reallocates
96
96
  // the accumulating UTF-16 string (O(n²) for large Claude Code prompts);
97
97
  // Buffer.concat + one TextDecoder pass is linear and allocation-stable.
@@ -126,7 +126,7 @@ export async function readIngressBody(
126
126
  return body;
127
127
  } catch (error) {
128
128
  if (error instanceof GatewayError) throw error;
129
- throw new GatewayError("invalid_request", 400, "The request body is not valid JSON. Check for a trailing comma or an unclosed brace.");
129
+ throw new GatewayError("invalid_request", 400, "The request body is not valid JSON (request {requestId}). Check for a trailing comma or an unclosed brace. Check the shape of what you sent — then resend 🪞");
130
130
  }
131
131
  }
132
132