cartethyia 2.7.3 → 2.7.5

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 (625) hide show
  1. package/.env.example +2 -0
  2. package/README.md +17 -5
  3. package/dist/dashboard/assets/{BannedPage-2TJD37Hb.js → BannedPage-Cj15GsMp.js} +1 -1
  4. package/dist/dashboard/assets/BannedPage-Cj15GsMp.js.gz +0 -0
  5. package/dist/dashboard/assets/ClaimGiftsButton-D83pBJ6J.js +1 -0
  6. package/dist/dashboard/assets/ClaimGiftsButton-D83pBJ6J.js.gz +0 -0
  7. package/dist/dashboard/assets/{CliToolDetailPage-xefJNRmJ.js → CliToolDetailPage-DZKmVuJN.js} +1 -1
  8. package/dist/dashboard/assets/CliToolDetailPage-DZKmVuJN.js.gz +0 -0
  9. package/dist/dashboard/assets/{CliToolsPage-Rj9iHQ3F.js → CliToolsPage-BIccZmeL.js} +1 -1
  10. package/dist/dashboard/assets/CliToolsPage-BIccZmeL.js.gz +0 -0
  11. package/dist/dashboard/assets/CombosPage-DEEKkYD-.js +1 -0
  12. package/dist/dashboard/assets/CombosPage-DEEKkYD-.js.gz +0 -0
  13. package/dist/dashboard/assets/ConsoleLogPage-D5rol2j2.js +1 -0
  14. package/dist/dashboard/assets/ConsoleLogPage-D5rol2j2.js.gz +0 -0
  15. package/dist/dashboard/assets/CustomizationPage-BKj_r0rp.js +1 -0
  16. package/dist/dashboard/assets/CustomizationPage-BKj_r0rp.js.gz +0 -0
  17. package/dist/dashboard/assets/InjectSystemPromptPanel-B5nZvmf0.js +656 -0
  18. package/dist/dashboard/assets/InjectSystemPromptPanel-B5nZvmf0.js.gz +0 -0
  19. package/dist/dashboard/assets/{LoginPage-C4w7vea-.js → LoginPage-DkXb6dx-.js} +1 -1
  20. package/dist/dashboard/assets/LoginPage-DkXb6dx-.js.gz +0 -0
  21. package/dist/dashboard/assets/{ModelPicker-BtPcAfot.js → ModelPicker-DnQPSIA8.js} +1 -1
  22. package/dist/dashboard/assets/ModelPicker-DnQPSIA8.js.gz +0 -0
  23. package/dist/dashboard/assets/OverviewPage-Ci2wQKUz.js +1 -0
  24. package/dist/dashboard/assets/OverviewPage-Ci2wQKUz.js.gz +0 -0
  25. package/dist/dashboard/assets/ProviderDetailPage-odlgYrOg.js +5 -0
  26. package/dist/dashboard/assets/ProviderDetailPage-odlgYrOg.js.gz +0 -0
  27. package/dist/dashboard/assets/ProviderIcon-EfeloBn4.js +1 -0
  28. package/dist/dashboard/assets/ProviderIcon-EfeloBn4.js.gz +0 -0
  29. package/dist/dashboard/assets/ProvidersPage-CqFBQEB8.js +1 -0
  30. package/dist/dashboard/assets/ProvidersPage-CqFBQEB8.js.gz +0 -0
  31. package/dist/dashboard/assets/ProxyPage-5SnUSy3j.js +9 -0
  32. package/dist/dashboard/assets/ProxyPage-5SnUSy3j.js.gz +0 -0
  33. package/dist/dashboard/assets/QuotaPage-CmnMDzhP.js +1 -0
  34. package/dist/dashboard/assets/QuotaPage-CmnMDzhP.js.gz +0 -0
  35. package/dist/dashboard/assets/SettingsPage-BJtRZ4qD.js +1 -0
  36. package/dist/dashboard/assets/SettingsPage-BJtRZ4qD.js.gz +0 -0
  37. package/dist/dashboard/assets/{SetupPage-BQS0Rx_u.js → SetupPage-WTa0sktC.js} +1 -1
  38. package/dist/dashboard/assets/SetupPage-WTa0sktC.js.gz +0 -0
  39. package/dist/dashboard/assets/{StudioPage-TP7voAQ0.js → StudioPage-BfAb5-o_.js} +3 -3
  40. package/dist/dashboard/assets/StudioPage-BfAb5-o_.js.gz +0 -0
  41. package/dist/dashboard/assets/UsagePage-BNzeZ2Vk.js +56 -0
  42. package/dist/dashboard/assets/UsagePage-BNzeZ2Vk.js.gz +0 -0
  43. package/dist/dashboard/assets/api-keys-l29JROPv.js +1 -0
  44. package/dist/dashboard/assets/api-keys-l29JROPv.js.gz +0 -0
  45. package/dist/dashboard/assets/app--PJWq8_C.css +2 -0
  46. package/dist/dashboard/assets/app--PJWq8_C.css.gz +0 -0
  47. package/dist/dashboard/assets/{assert-C0YYuZJ4.js → assert-C0K-c4eo.js} +1 -1
  48. package/dist/dashboard/assets/assert-C0K-c4eo.js.gz +0 -0
  49. package/dist/dashboard/assets/{badge-B_DMpMJN.js → badge-DfbhQU4k.js} +1 -1
  50. package/dist/dashboard/assets/badge-DfbhQU4k.js.gz +0 -0
  51. package/dist/dashboard/assets/{banner-DMrMPmth.js → banner-BllT4LcF.js} +1 -1
  52. package/dist/dashboard/assets/banner-BllT4LcF.js.gz +0 -0
  53. package/dist/dashboard/assets/brand/login_register.jpg +0 -0
  54. package/dist/dashboard/assets/brand/login_register_initial.jpg +0 -0
  55. package/dist/dashboard/assets/brand/og_bansos.jpg +0 -0
  56. package/dist/dashboard/assets/brand/og_image.jpg +0 -0
  57. package/dist/dashboard/assets/{card-Be5Uzamx.js → card-CtkX66Wd.js} +1 -1
  58. package/dist/dashboard/assets/card-CtkX66Wd.js.gz +0 -0
  59. package/dist/dashboard/assets/chevron-left-Bf6CaTSJ.js +1 -0
  60. package/dist/dashboard/assets/chevron-left-Bf6CaTSJ.js.gz +0 -0
  61. package/dist/dashboard/assets/{cli-tools-muFRphDu.js → cli-tools-CQXVMn4P.js} +1 -1
  62. package/dist/dashboard/assets/cli-tools-CQXVMn4P.js.gz +0 -0
  63. package/dist/dashboard/assets/{clipboard-button-DOfrVmsi.js → clipboard-button-BQqBQd2n.js} +1 -1
  64. package/dist/dashboard/assets/clipboard-button-BQqBQd2n.js.gz +0 -0
  65. package/dist/dashboard/assets/{dialog-C9omdjQt.js → dialog-BEK7KP0H.js} +1 -1
  66. package/dist/dashboard/assets/dialog-BEK7KP0H.js.gz +0 -0
  67. package/dist/dashboard/assets/{drawer-Bm8WKaGY.js → drawer-BrTEZ8zu.js} +1 -1
  68. package/dist/dashboard/assets/drawer-BrTEZ8zu.js.gz +0 -0
  69. package/dist/dashboard/assets/entry-CHWxbS56.js +1 -0
  70. package/dist/dashboard/assets/entry-CHWxbS56.js.gz +0 -0
  71. package/dist/dashboard/assets/{entry-q5HwZ8nN.js → entry-EBHJ3DrE.js} +1 -1
  72. package/dist/dashboard/assets/entry-EBHJ3DrE.js.gz +0 -0
  73. package/dist/dashboard/assets/entry-LCS85hiq.js +4 -0
  74. package/dist/dashboard/assets/entry-LCS85hiq.js.gz +0 -0
  75. package/dist/dashboard/assets/{formatters-BTScrQpe.js → formatters-BAACA727.js} +1 -1
  76. package/dist/dashboard/assets/formatters-BAACA727.js.gz +0 -0
  77. package/dist/dashboard/assets/{github-badge-Orjuzemr.js → github-badge-YSCE4UC2.js} +1 -1
  78. package/dist/dashboard/assets/github-badge-YSCE4UC2.js.gz +0 -0
  79. package/dist/dashboard/assets/{index-cPrX5vn6.js → index-FQlj0HUT.js} +2 -2
  80. package/dist/dashboard/assets/index-FQlj0HUT.js.gz +0 -0
  81. package/dist/dashboard/assets/{inline-Dvux8w-0.js → inline-C4gU5iOL.js} +1 -1
  82. package/dist/dashboard/assets/inline-C4gU5iOL.js.gz +0 -0
  83. package/dist/dashboard/assets/{input-CU1Rgf3p.js → input-BHHX_Tmm.js} +1 -1
  84. package/dist/dashboard/assets/input-BHHX_Tmm.js.gz +0 -0
  85. package/dist/dashboard/assets/{layout-C_7A6H55.js → layout-CajrYUnD.js} +1 -1
  86. package/dist/dashboard/assets/{layout-C_7A6H55.js.gz → layout-CajrYUnD.js.gz} +0 -0
  87. package/dist/dashboard/assets/{list-row-CzFvogbj.js → list-row-Bx2I7XUe.js} +1 -1
  88. package/dist/dashboard/assets/list-row-Bx2I7XUe.js.gz +0 -0
  89. package/dist/dashboard/assets/live-DNNqRbj2.js +1 -0
  90. package/dist/dashboard/assets/live-DNNqRbj2.js.gz +0 -0
  91. package/dist/dashboard/assets/minimize-2-BTmMTK4o.js +1 -0
  92. package/dist/dashboard/assets/minimize-2-BTmMTK4o.js.gz +0 -0
  93. package/dist/dashboard/assets/{mutation-JJGQm8TD.js → mutation-Rr0TIvcj.js} +1 -1
  94. package/dist/dashboard/assets/mutation-Rr0TIvcj.js.gz +0 -0
  95. package/dist/dashboard/assets/network-C5UBACrJ.js +1 -0
  96. package/dist/dashboard/assets/network-C5UBACrJ.js.gz +0 -0
  97. package/dist/dashboard/assets/numbers-CUWAZK4J.js +2 -0
  98. package/dist/dashboard/assets/numbers-CUWAZK4J.js.gz +0 -0
  99. package/dist/dashboard/assets/pagination-Xsi9Qx04.js +1 -0
  100. package/dist/dashboard/assets/pagination-Xsi9Qx04.js.gz +0 -0
  101. package/dist/dashboard/assets/{query-keys-CLaNz8Xz.js → query-keys-BloKtaXJ.js} +1 -1
  102. package/dist/dashboard/assets/query-keys-BloKtaXJ.js.gz +0 -0
  103. package/dist/dashboard/assets/quota-DvQbD_sr.js +1 -0
  104. package/dist/dashboard/assets/quota-DvQbD_sr.js.gz +0 -0
  105. package/dist/dashboard/assets/{routing-BEH32QOC.js → routing-in0f_IfK.js} +1 -1
  106. package/dist/dashboard/assets/routing-in0f_IfK.js.gz +0 -0
  107. package/dist/dashboard/assets/{select-B3iTetHN.js → select-BLULNqTp.js} +2 -2
  108. package/dist/dashboard/assets/select-BLULNqTp.js.gz +0 -0
  109. package/dist/dashboard/assets/settings-CcGczj6I.js +1 -0
  110. package/dist/dashboard/assets/settings-CcGczj6I.js.gz +0 -0
  111. package/dist/dashboard/assets/shield-BMQNxORh.js +1 -0
  112. package/dist/dashboard/assets/shield-BMQNxORh.js.gz +0 -0
  113. package/dist/dashboard/assets/{stack-KrOrm7JZ.js → stack-D2lIR0tg.js} +1 -1
  114. package/dist/dashboard/assets/stack-D2lIR0tg.js.gz +0 -0
  115. package/dist/dashboard/assets/{state-DLS7IDMy.js → state-DTARjfiW.js} +1 -1
  116. package/dist/dashboard/assets/state-DTARjfiW.js.gz +0 -0
  117. package/dist/dashboard/assets/{switch-42ItvbAs.js → switch-c0oPSnqW.js} +1 -1
  118. package/dist/dashboard/assets/switch-c0oPSnqW.js.gz +0 -0
  119. package/dist/dashboard/assets/{useMutation-Dmnfh1Ue.js → useMutation-m8WGNPrz.js} +1 -1
  120. package/dist/dashboard/assets/useMutation-m8WGNPrz.js.gz +0 -0
  121. package/dist/dashboard/console-backgrounds.jpg +0 -0
  122. package/dist/dashboard/console-backgrounds_initial.jpg +0 -0
  123. package/dist/dashboard/index.html +2 -2
  124. package/dist/dashboard/index.html.gz +0 -0
  125. package/dist/dashboard/precompressed-assets.json +59 -57
  126. package/migrations/full-postgres/0027_qoder_identity_genuine_seed.sql +9 -10
  127. package/migrations/full-postgres/0032_add_provider_pool_accounts.sql +53 -0
  128. package/migrations/full-postgres/0033_add_api_key_ban.sql +9 -0
  129. package/migrations/full-postgres/0034_add_api_key_ban_until.sql +7 -0
  130. package/migrations/full-postgres/0035_align_pool_account_status_enum.sql +21 -0
  131. package/migrations/full-postgres/0036_system_prompt_presets_refs.sql +10 -0
  132. package/migrations/full-postgres/0037_share_concurrency_scope.sql +3 -0
  133. package/migrations/full-postgres/0038_add_accounts_quota_snapshot.sql +7 -0
  134. package/migrations/full-postgres/0039_add_telemetry_provider_model_indexes.sql +13 -0
  135. package/migrations/full-postgres/0040_add_models_dialect.sql +3 -0
  136. package/migrations/full-postgres/0041_add_daily_checkin_ledger_account_fk.sql +7 -0
  137. package/migrations/lite-sqlite3/0027_qoder_identity_genuine_seed.sql +9 -10
  138. package/migrations/lite-sqlite3/0032_add_provider_pool_accounts.sql +44 -0
  139. package/migrations/lite-sqlite3/0033_add_api_key_ban.sql +8 -0
  140. package/migrations/lite-sqlite3/0034_add_api_key_ban_until.sql +5 -0
  141. package/migrations/lite-sqlite3/0035_align_pool_account_status_enum.sql +6 -0
  142. package/migrations/lite-sqlite3/0036_system_prompt_presets_refs.sql +14 -0
  143. package/migrations/lite-sqlite3/0037_share_concurrency_scope.sql +3 -0
  144. package/migrations/lite-sqlite3/0038_add_accounts_quota_snapshot.sql +4 -0
  145. package/migrations/lite-sqlite3/0039_add_telemetry_provider_model_indexes.sql +6 -0
  146. package/migrations/lite-sqlite3/0040_add_models_dialect.sql +3 -0
  147. package/migrations/lite-sqlite3/0041_add_daily_checkin_ledger_account_fk.sql +26 -0
  148. package/package.json +4 -5
  149. package/scripts/cli/update.ts +15 -41
  150. package/src/boot/app.ts +67 -10
  151. package/src/boot/dependencies.ts +786 -732
  152. package/src/boot/lifecycle.ts +11 -12
  153. package/src/config/cooldowns.ts +34 -55
  154. package/src/config/network.ts +8 -4
  155. package/src/config/package-paths.ts +1 -1
  156. package/src/config/providers.ts +13 -14
  157. package/src/config/resources.ts +0 -5
  158. package/src/config/runtime.ts +2 -5
  159. package/src/config/spec.ts +26 -6
  160. package/src/console/api-keys/ban-constants.ts +38 -0
  161. package/src/console/api-keys/contracts.ts +92 -31
  162. package/src/console/api-keys/routes.ts +131 -6
  163. package/src/console/audit/routes.ts +3 -3
  164. package/src/console/auth/access.ts +4 -4
  165. package/src/console/auth/account-reset.ts +5 -5
  166. package/src/console/auth/audit-recorder.ts +1 -1
  167. package/src/console/auth/lockout.ts +7 -3
  168. package/src/console/auth/row-coercion.ts +57 -12
  169. package/src/console/auth/session-resolver.ts +1 -1
  170. package/src/console/auth/session-service.ts +8 -2
  171. package/src/console/auth/session.ts +40 -34
  172. package/src/console/auth/setup-service.ts +4 -3
  173. package/src/console/auth/user-store.ts +1 -1
  174. package/src/console/backup/9router-map.ts +0 -3
  175. package/src/console/backup/contracts.ts +11 -9
  176. package/src/console/backup/export-endpoint.ts +11 -12
  177. package/src/console/backup/nine-router.ts +1 -7
  178. package/src/console/backup/routes.ts +32 -36
  179. package/src/console/backup/service.ts +468 -468
  180. package/src/console/backup/store.ts +47 -29
  181. package/src/console/backup/validate.ts +11 -1
  182. package/src/console/cli-tools/contracts.ts +7 -7
  183. package/src/console/cli-tools/injectors/cline.ts +110 -110
  184. package/src/console/cli-tools/injectors/codex.ts +181 -181
  185. package/src/console/cli-tools/injectors/copilot.ts +108 -108
  186. package/src/console/cli-tools/injectors/driver.ts +247 -247
  187. package/src/console/cli-tools/injectors/jcode.ts +115 -115
  188. package/src/console/cli-tools/injectors/openclaw.ts +161 -161
  189. package/src/console/cli-tools/injectors/opencode.ts +153 -153
  190. package/src/console/cli-tools/routes.ts +4 -4
  191. package/src/console/cli-tools/service.ts +1 -1
  192. package/src/console/cli-tools/store.ts +2 -2
  193. package/src/console/console-router-guards.ts +2 -2
  194. package/src/console/console-router.ts +36 -4
  195. package/src/console/dashboard-assets.ts +9 -9
  196. package/src/console/data-management/routes.ts +3 -3
  197. package/src/console/domain-registration.ts +18 -4
  198. package/src/console/inferhub/client.ts +0 -7
  199. package/src/console/inferhub/key-store.ts +1 -1
  200. package/src/console/inferhub/normalize.ts +2 -2
  201. package/src/console/inferhub/routes.ts +1 -1
  202. package/src/console/inferhub/types.ts +1 -1
  203. package/src/console/observability/contracts.ts +5 -1
  204. package/src/console/observability/live.ts +34 -21
  205. package/src/console/observability/logs.ts +4 -4
  206. package/src/console/observability/performance.ts +3 -3
  207. package/src/console/observability/routes.ts +3 -3
  208. package/src/console/observability/sse.ts +62 -8
  209. package/src/console/observability/store.ts +36 -20
  210. package/src/console/providers/catalog/catalog-projections.ts +1 -1
  211. package/src/console/providers/catalog/contracts.ts +82 -22
  212. package/src/console/providers/catalog/model-operations.ts +1 -1
  213. package/src/console/providers/catalog/provider-operations.ts +110 -17
  214. package/src/console/providers/catalog/public-model-store.ts +0 -0
  215. package/src/console/providers/catalog/routes.ts +30 -6
  216. package/src/console/providers/catalog/store-helpers.ts +349 -326
  217. package/src/console/providers/catalog/store.ts +1662 -1292
  218. package/src/console/providers/detail/routes.ts +4 -4
  219. package/src/console/providers/oauth/callback-listener.ts +100 -18
  220. package/src/console/providers/oauth/routes.ts +95 -16
  221. package/src/console/quota/account-quota-global.ts +41 -3
  222. package/src/console/quota/account-quota-shared.ts +10 -3
  223. package/src/console/quota/account-quota-tenant.ts +285 -67
  224. package/src/console/quota/account-test-egress.ts +4 -4
  225. package/src/console/quota/daily-growth-job.ts +308 -0
  226. package/src/console/quota/native-identity.ts +2 -2
  227. package/src/console/quota/quota-view.ts +1 -1
  228. package/src/console/remote-support/routes.ts +1 -1
  229. package/src/console/remote-support/service.ts +2 -2
  230. package/src/console/routing/model/contracts.ts +1 -1
  231. package/src/console/routing/model/routes.ts +3 -3
  232. package/src/console/routing/pools/contracts.ts +13 -10
  233. package/src/console/routing/pools/import-parse.ts +3 -3
  234. package/src/console/routing/pools/operations.ts +14 -7
  235. package/src/console/routing/pools/probe-verdict.ts +8 -3
  236. package/src/console/routing/pools/relay-deploy.ts +12 -12
  237. package/src/console/routing/pools/routes.ts +3 -3
  238. package/src/console/routing/pools/store.ts +21 -1
  239. package/src/console/settings/contracts.ts +29 -2
  240. package/src/console/settings/routes.ts +3 -3
  241. package/src/console/settings/store.ts +20 -0
  242. package/src/console/share/share-router.ts +112 -44
  243. package/src/console/share/share-stats.ts +109 -39
  244. package/src/console/share/share-usage.ts +1 -1
  245. package/src/console/shared/errors.ts +2 -2
  246. package/src/console/shared/object.ts +18 -0
  247. package/src/console/shared/read-through-cache.ts +42 -6
  248. package/src/console/shared/transaction.ts +14 -0
  249. package/src/console/system-prompt/contracts.ts +89 -12
  250. package/src/console/system-prompt/routes.ts +98 -45
  251. package/src/console/updates/apply-update.ts +244 -0
  252. package/src/console/updates/routes.ts +256 -98
  253. package/src/main.ts +11 -5
  254. package/src/network/http2-bidi.ts +1 -1
  255. package/src/network/ip-utils.ts +298 -0
  256. package/src/network/outbound-fetch.ts +76 -5
  257. package/src/network/pool/agent.ts +7 -6
  258. package/src/network/pool/byte-accounting.ts +1 -1
  259. package/src/network/pool/resolver.ts +55 -51
  260. package/src/network/pool/selector.ts +150 -136
  261. package/src/network/pool-cooldown-store.ts +71 -16
  262. package/src/network/pool-health-machine.ts +22 -2
  263. package/src/network/rate-limit-verdict.ts +68 -0
  264. package/src/network/retry.ts +1 -1
  265. package/src/network/ssrf.ts +24 -75
  266. package/src/observability/event-log.ts +2 -2
  267. package/src/observability/event-query.ts +286 -214
  268. package/src/observability/log-sanitize.ts +1 -1
  269. package/src/observability/metrics.ts +4 -4
  270. package/src/observability/payload-capture.ts +6 -5
  271. package/src/observability/payload-store.ts +1 -1
  272. package/src/observability/runtime-metrics.ts +1 -1
  273. package/src/observability/telemetry-buffer.ts +12 -3
  274. package/src/observability/telemetry-reconciliation.ts +3 -3
  275. package/src/observability/telemetry-status.ts +1 -1
  276. package/src/observability/token-speed.ts +1 -1
  277. package/src/persistence/connection-url.ts +1 -1
  278. package/src/persistence/db-handle.ts +1 -1
  279. package/src/persistence/db-pg.ts +2 -2
  280. package/src/persistence/db-sqlite.ts +10 -5
  281. package/src/persistence/dialect.ts +5 -1
  282. package/src/persistence/readiness.ts +19 -7
  283. package/src/persistence/redis.ts +1 -1
  284. package/src/persistence/schema.ts +177 -45
  285. package/src/persistence/shared-cache.ts +2 -2
  286. package/src/persistence/sort-order.ts +1 -1
  287. package/src/persistence/stores/api-key-store.ts +109 -4
  288. package/src/persistence/stores/share-store.ts +20 -8
  289. package/src/persistence/stores/telemetry-store.ts +1 -1
  290. package/src/persistence/system-prompt-store.ts +63 -18
  291. package/src/persistence/tenant-preferences.ts +1 -1
  292. package/src/persistence/ttl-cache.ts +3 -3
  293. package/src/persistence/usage-flush.ts +7 -2
  294. package/src/protocol/canonical-model.ts +1 -1
  295. package/src/protocol/codecs.ts +12 -16
  296. package/src/protocol/dsml.ts +3 -49
  297. package/src/protocol/gateway-error.ts +6 -3
  298. package/src/protocol/image-clamp.ts +2 -2
  299. package/src/protocol/image-generation.ts +2 -2
  300. package/src/protocol/reasoning-text.ts +1 -1
  301. package/src/protocol/reasoning.ts +7 -52
  302. package/src/protocol/registry.ts +50 -15
  303. package/src/protocol/request/chat.ts +46 -19
  304. package/src/protocol/request/codex.ts +1 -1
  305. package/src/protocol/request/messages.ts +46 -14
  306. package/src/protocol/request/responses.ts +19 -10
  307. package/src/protocol/response/chat.ts +27 -12
  308. package/src/protocol/response/codex.ts +2 -2
  309. package/src/protocol/response/gemini.ts +1 -1
  310. package/src/protocol/response/messages.ts +6 -6
  311. package/src/protocol/response/omp-tool-call-markup.ts +1 -1
  312. package/src/protocol/response/responses.ts +3 -3
  313. package/src/protocol/sse-decoder.ts +64 -4
  314. package/src/protocol/stop-reason.ts +2 -2
  315. package/src/protocol/think-split.ts +347 -0
  316. package/src/protocol/translation/attachment-integrity.ts +3 -3
  317. package/src/protocol/translation/cache-controls.ts +8 -20
  318. package/src/protocol/translation/capabilities.ts +8 -8
  319. package/src/protocol/translation/dialect/deepseek.ts +10 -0
  320. package/src/protocol/translation/dialect/inband.ts +39 -0
  321. package/src/protocol/translation/dialect/model-thinking.ts +89 -0
  322. package/src/protocol/translation/dialect/qwen.ts +114 -0
  323. package/src/protocol/translation/dialect/resolver.ts +83 -0
  324. package/src/protocol/translation/dialect/rules.ts +93 -0
  325. package/src/protocol/translation/dialect/thinking-ladder.ts +245 -0
  326. package/src/protocol/translation/dialect/types.ts +58 -0
  327. package/src/protocol/translation/quirks.ts +18 -4
  328. package/src/protocol/translation/thinking.ts +12 -252
  329. package/src/protocol/translation/tool-id.ts +3 -3
  330. package/src/protocol/translation/tool-repair.ts +1 -1
  331. package/src/protocol/upstream/post-json.ts +6 -1
  332. package/src/protocol/upstream/send-claude-messages.ts +3 -3
  333. package/src/providers/adapters/base-adapter.ts +1 -1
  334. package/src/providers/adapters/compatible-adapter.ts +44 -5
  335. package/src/providers/authentication/jwt-validator.ts +0 -5
  336. package/src/providers/authentication/oauth-client.ts +254 -253
  337. package/src/providers/authentication/oauth-flow-store.ts +42 -7
  338. package/src/providers/authentication/oauth-refresh-service.ts +2 -2
  339. package/src/providers/bundled-discovery-providers.ts +6 -30
  340. package/src/providers/configured-openai-providers.ts +82 -97
  341. package/src/providers/configured-provider.ts +1 -1
  342. package/src/providers/default-registry.ts +23 -35
  343. package/src/providers/discovery/catalog/catalog-format.ts +13 -2
  344. package/src/providers/discovery/discovery-types.ts +0 -1
  345. package/src/providers/discovery/openai-model-discovery.ts +2 -2
  346. package/src/providers/discovery/probe-phases.ts +50 -15
  347. package/src/providers/discovery/probing-service.ts +18 -20
  348. package/src/providers/integrations/agentrouter/agentrouter.ts +14 -5
  349. package/src/providers/integrations/alibabacp/alibabacp.ts +2 -2
  350. package/src/providers/integrations/anthropic.ts +4 -11
  351. package/src/providers/integrations/antigravity/antigravity-oauth.ts +0 -1
  352. package/src/providers/integrations/antigravity/antigravity-protocol.ts +0 -14
  353. package/src/providers/integrations/antigravity/antigravity.ts +15 -26
  354. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +2 -2
  355. package/src/providers/integrations/buddy/buddy-chat-shared.ts +20 -4
  356. package/src/providers/integrations/buddy/buddy-cli-headers.ts +19 -52
  357. package/src/providers/integrations/buddy/buddy-discovery-shared.ts +37 -6
  358. package/src/providers/integrations/buddy/buddy-identity.ts +108 -1
  359. package/src/providers/integrations/buddy/buddy-media.ts +16 -16
  360. package/src/providers/integrations/buddy/buddy-oauth-shared.ts +1 -1
  361. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +1 -2
  362. package/src/providers/integrations/buddy/codebuddy/provider.ts +5 -16
  363. package/src/providers/integrations/buddy/codebuddy/shared.ts +30 -9
  364. package/src/providers/integrations/buddy/workbuddy/provider.ts +6 -10
  365. package/src/providers/integrations/buddy/workbuddy/shared.ts +21 -3
  366. package/src/providers/integrations/claude/claude-betas.ts +1 -1
  367. package/src/providers/integrations/claude/claude-cch.ts +148 -148
  368. package/src/providers/integrations/claude/claude-credentials.ts +1 -1
  369. package/src/providers/integrations/claude/claude-slow-lane-retry.ts +1 -1
  370. package/src/providers/integrations/claude/claude.ts +3 -10
  371. package/src/providers/integrations/claude/fallback-credit.ts +3 -7
  372. package/src/providers/integrations/cline/cline.ts +2 -2
  373. package/src/providers/integrations/cloudflareai/cloudflareai.ts +4 -7
  374. package/src/providers/integrations/codex/codex-device-code.ts +101 -101
  375. package/src/providers/integrations/codex/codex-errors.ts +1 -1
  376. package/src/providers/integrations/codex/codex-oauth.ts +0 -1
  377. package/src/providers/integrations/codex/codex.ts +14 -15
  378. package/src/providers/integrations/commandcode/commandcode.ts +76 -116
  379. package/src/providers/integrations/commandcode/quota.ts +132 -48
  380. package/src/providers/integrations/cursor/cursor-http2.ts +11 -14
  381. package/src/providers/integrations/cursor/cursor-quota.ts +2 -6
  382. package/src/providers/integrations/cursor/cursor-request.ts +0 -87
  383. package/src/providers/integrations/cursor/cursor-shared.ts +0 -4
  384. package/src/providers/integrations/cursor/cursor.ts +53 -20
  385. package/src/providers/integrations/cursor/protocol/framing.ts +0 -56
  386. package/src/providers/integrations/cursor/protocol/messages.ts +0 -96
  387. package/src/providers/integrations/factory/factory.ts +13 -13
  388. package/src/providers/integrations/gemini.ts +11 -11
  389. package/src/providers/integrations/github/github-discovery.ts +55 -10
  390. package/src/providers/integrations/github/github-oauth.ts +6 -5
  391. package/src/providers/integrations/github/github.ts +47 -20
  392. package/src/providers/integrations/grok/grok-oauth.ts +261 -261
  393. package/src/providers/integrations/grok/grok-quota-frame.ts +1 -1
  394. package/src/providers/integrations/grok/grok-quota.ts +16 -7
  395. package/src/providers/integrations/grok/grok-ratelimit-store.ts +0 -5
  396. package/src/providers/integrations/grok/grok-ratelimit.ts +3 -0
  397. package/src/providers/integrations/grok/grok.ts +88 -42
  398. package/src/providers/integrations/inferhub.ts +4 -11
  399. package/src/providers/integrations/kilo/kilo-oauth.ts +1 -1
  400. package/src/providers/integrations/kimi/kimi.ts +4 -11
  401. package/src/providers/integrations/kiro/kiro-cache.ts +31 -0
  402. package/src/providers/integrations/kiro/kiro-catalog.ts +31 -5
  403. package/src/providers/integrations/kiro/kiro-discovery.ts +47 -43
  404. package/src/providers/integrations/kiro/kiro-machine-id.ts +6 -3
  405. package/src/providers/integrations/kiro/kiro-oauth.ts +523 -255
  406. package/src/providers/integrations/kiro/kiro-quota.ts +35 -47
  407. package/src/providers/integrations/kiro/kiro-request.ts +8 -11
  408. package/src/providers/integrations/kiro/kiro-stream.ts +13 -0
  409. package/src/providers/integrations/kiro/kiro.ts +91 -78
  410. package/src/providers/integrations/muse/muse-oauth.ts +0 -1
  411. package/src/providers/integrations/muse/muse-quota.ts +0 -4
  412. package/src/providers/integrations/muse/muse.ts +6 -9
  413. package/src/providers/integrations/opencode/fingerprint.ts +7 -5
  414. package/src/providers/integrations/opencode/provider.ts +17 -24
  415. package/src/providers/integrations/perplexity.ts +2 -2
  416. package/src/providers/integrations/qoder/qoder-catalog.ts +132 -17
  417. package/src/providers/integrations/qoder/qoder-checkin.ts +26 -8
  418. package/src/providers/integrations/qoder/qoder-native-identity-store.ts +9 -8
  419. package/src/providers/integrations/qoder/qoder-oauth.ts +165 -25
  420. package/src/providers/integrations/qoder/qoder-quota.ts +48 -6
  421. package/src/providers/integrations/qoder/qoder-realm.ts +22 -28
  422. package/src/providers/integrations/qoder/qoder-sse.ts +159 -0
  423. package/src/providers/integrations/qoder/qoder-umid.ts +33 -38
  424. package/src/providers/integrations/qoder/qoder-virtual.ts +14 -2
  425. package/src/providers/integrations/qoder/qoder.ts +521 -63
  426. package/src/providers/integrations/shared/connect.ts +0 -54
  427. package/src/providers/integrations/shared/hash.ts +15 -0
  428. package/src/providers/integrations/shared/session-cache.ts +136 -0
  429. package/src/providers/integrations/tokenharbor.ts +2 -2
  430. package/src/providers/integrations/trae/trae-cache.ts +7 -87
  431. package/src/providers/integrations/trae/trae-catalog.ts +1 -6
  432. package/src/providers/integrations/trae/trae-protocol.ts +17 -8
  433. package/src/providers/integrations/trae/trae-realm.ts +0 -7
  434. package/src/providers/integrations/trae/trae.ts +59 -19
  435. package/src/providers/integrations/xai/xai-oauth.ts +5 -5
  436. package/src/providers/integrations/zcode/oauth.ts +1 -1
  437. package/src/providers/integrations/zcode-start-plan/gifts.ts +265 -0
  438. package/src/providers/integrations/zcode-start-plan/oauth.ts +154 -0
  439. package/src/providers/integrations/zcode-start-plan/provider.ts +131 -0
  440. package/src/providers/integrations/zcode-start-plan/quota.ts +152 -0
  441. package/src/providers/integrations/zcode-start-plan/zcode_prompt.json +10 -0
  442. package/src/providers/operations/account-error-classification.ts +131 -116
  443. package/src/providers/operations/account-health-service.ts +31 -39
  444. package/src/providers/operations/account-reset-service.ts +58 -1
  445. package/src/providers/operations/cli-platform.ts +31 -5
  446. package/src/providers/operations/client-version-resolver.ts +4 -6
  447. package/src/providers/operations/client-versions.ts +10 -7
  448. package/src/providers/operations/provider-catalog-seeder.ts +2 -0
  449. package/src/providers/operations/provider-catalog-service.ts +22 -6
  450. package/src/providers/operations/provider-credential-service.ts +35 -6
  451. package/src/providers/operations/upstream-deadline.ts +3 -3
  452. package/src/providers/provider-contracts.ts +65 -10
  453. package/src/providers/provider-credential-resolver.ts +1 -1
  454. package/src/providers/provider-metadata.ts +17 -2
  455. package/src/providers/provider-model-definition.ts +22 -1
  456. package/src/providers/provider-probe.ts +4 -2
  457. package/src/providers/provider-registry.ts +4 -4
  458. package/src/providers/provider-usage.ts +81 -16
  459. package/src/providers/quota/cache.ts +29 -0
  460. package/src/providers/quota/quota-contracts.ts +28 -10
  461. package/src/providers/quota/refresh.ts +29 -6
  462. package/src/providers/quota/snapshot.ts +94 -0
  463. package/src/providers/search/search-provider.ts +6 -8
  464. package/src/providers/search/search-providers.ts +2 -1
  465. package/src/security/abuse.ts +8 -8
  466. package/src/security/admission/reasons.ts +17 -17
  467. package/src/security/admission/service.ts +2 -2
  468. package/src/security/admission/ttl.ts +61 -61
  469. package/src/security/api-key-auth.ts +20 -8
  470. package/src/security/client-router-fingerprint.ts +4 -4
  471. package/src/security/credential-codec.ts +3 -3
  472. package/src/security/csrf.ts +2 -2
  473. package/src/security/ip-boundary.ts +16 -189
  474. package/src/security/model-access-rule.ts +1 -1
  475. package/src/security/outbound-headers.ts +18 -0
  476. package/src/shared/coerce.ts +19 -0
  477. package/src/shared/invoke-guard.ts +47 -0
  478. package/src/transport/contracts.ts +13 -4
  479. package/src/transport/dispatch/attempt-finalize.ts +5 -4
  480. package/src/transport/dispatch/attempt-loop.ts +10 -7
  481. package/src/transport/dispatch/capture-policy.ts +34 -37
  482. package/src/transport/dispatch/empty-stream.ts +28 -25
  483. package/src/transport/dispatch/generation.ts +33 -53
  484. package/src/transport/dispatch/leases.ts +8 -8
  485. package/src/transport/dispatch/native-shared.ts +127 -0
  486. package/src/transport/dispatch/proxy-request.ts +10 -8
  487. package/src/transport/dispatch/responses-compact.ts +35 -56
  488. package/src/transport/dispatch/retry-policy.ts +4 -67
  489. package/src/transport/dispatch/stream-bridge.ts +1 -1
  490. package/src/transport/dispatch/streaming/encoder.ts +18 -72
  491. package/src/transport/dispatch/streaming/finalize.ts +11 -10
  492. package/src/transport/dispatch/streaming/stream.ts +7 -3
  493. package/src/transport/dispatch/streaming/thinking-loop.ts +1 -1
  494. package/src/transport/dispatch/systemone.ts +27 -55
  495. package/src/transport/dispatch/token-count.ts +49 -46
  496. package/src/transport/dispatch/websearch.ts +24 -50
  497. package/src/transport/drain-endpoint.ts +3 -3
  498. package/src/transport/failure-policy.ts +19 -6
  499. package/src/transport/guards/auth-guard.ts +6 -11
  500. package/src/transport/guards/body-policy.ts +7 -7
  501. package/src/transport/guards/error-lifecycle.ts +18 -3
  502. package/src/transport/guards/guard-errors.ts +7 -3
  503. package/src/transport/guards/headroom-guard.ts +2 -2
  504. package/src/transport/guards/pipeline.ts +7 -7
  505. package/src/transport/guards/readiness-guard.ts +16 -2
  506. package/src/transport/guards/request-context.ts +15 -20
  507. package/src/transport/request/preparer.ts +306 -27
  508. package/src/transport/request/rtk/compress-request.ts +3 -3
  509. package/src/transport/request/rtk/rtk-constants.ts +2 -2
  510. package/src/transport/request/rtk/rtk-filters.ts +8 -8
  511. package/src/transport/request/state.ts +5 -20
  512. package/src/transport/request/system-prompt-injection.ts +74 -11
  513. package/src/transport/request/tenant-preferences.ts +22 -0
  514. package/src/transport/routing/candidate-index.ts +2 -2
  515. package/src/transport/routing/round-robin.ts +1 -1
  516. package/src/transport/routing/route-catalog.ts +113 -19
  517. package/src/transport/routing/route-model.ts +131 -38
  518. package/src/transport/routing/router.ts +154 -39
  519. package/src/transport/shutdown-notice.ts +21 -9
  520. package/src/transport/surface/adapters.ts +3 -3
  521. package/src/transport/surface/chat/encode.ts +7 -7
  522. package/src/transport/surface/chat/parse.ts +7 -7
  523. package/src/transport/surface/completion.ts +3 -3
  524. package/src/transport/surface/content-parts.ts +1 -1
  525. package/src/transport/surface/dialects.ts +2 -2
  526. package/src/transport/surface/messages/encode.ts +1 -1
  527. package/src/transport/surface/messages/parse.ts +3 -3
  528. package/src/transport/surface/responses/adapter.ts +1 -1
  529. package/src/transport/surface/responses/errors.ts +0 -9
  530. package/src/transport/vision-handoff-cache.ts +58 -0
  531. package/src/transport/vision-handoff.ts +376 -0
  532. package/src/workers/account-identity-sweep.ts +2 -2
  533. package/src/workers/buddy-growth-tasks.ts +392 -346
  534. package/src/workers/daily-checkin.ts +102 -49
  535. package/src/workers/maintenance-lease.ts +2 -35
  536. package/src/workers/oauth-refresh-sweep.ts +8 -1
  537. package/src/workers/quota-refresh-sweep.ts +120 -82
  538. package/src/workers/scheduler.ts +67 -4
  539. package/src/workers/sweep-runner.ts +24 -5
  540. package/src/workers/zcode-gift-claim.ts +108 -0
  541. package/dist/dashboard/assets/AccountCooldown-Bjo92Qat.js +0 -1
  542. package/dist/dashboard/assets/AccountCooldown-Bjo92Qat.js.gz +0 -0
  543. package/dist/dashboard/assets/BannedPage-2TJD37Hb.js.gz +0 -0
  544. package/dist/dashboard/assets/CliToolDetailPage-xefJNRmJ.js.gz +0 -0
  545. package/dist/dashboard/assets/CliToolsPage-Rj9iHQ3F.js.gz +0 -0
  546. package/dist/dashboard/assets/CombosPage-JEbYJFxw.js +0 -1
  547. package/dist/dashboard/assets/CombosPage-JEbYJFxw.js.gz +0 -0
  548. package/dist/dashboard/assets/ConsoleLogPage-C3xx_inM.js +0 -1
  549. package/dist/dashboard/assets/ConsoleLogPage-C3xx_inM.js.gz +0 -0
  550. package/dist/dashboard/assets/CustomizationPage-Dw306gJU.js +0 -1
  551. package/dist/dashboard/assets/CustomizationPage-Dw306gJU.js.gz +0 -0
  552. package/dist/dashboard/assets/HealthEventsModal-jaBo1TWV.js +0 -1
  553. package/dist/dashboard/assets/HealthEventsModal-jaBo1TWV.js.gz +0 -0
  554. package/dist/dashboard/assets/LoginPage-C4w7vea-.js.gz +0 -0
  555. package/dist/dashboard/assets/ModelPicker-BtPcAfot.js.gz +0 -0
  556. package/dist/dashboard/assets/OverviewPage-CGC5gNs1.js +0 -1
  557. package/dist/dashboard/assets/OverviewPage-CGC5gNs1.js.gz +0 -0
  558. package/dist/dashboard/assets/ProviderDetailPage-DQcthNGt.js +0 -4
  559. package/dist/dashboard/assets/ProviderDetailPage-DQcthNGt.js.gz +0 -0
  560. package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js +0 -1
  561. package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js.gz +0 -0
  562. package/dist/dashboard/assets/ProvidersPage-0OsXRGXe.js +0 -1
  563. package/dist/dashboard/assets/ProvidersPage-0OsXRGXe.js.gz +0 -0
  564. package/dist/dashboard/assets/ProxyPage-DdPc8slb.js +0 -9
  565. package/dist/dashboard/assets/ProxyPage-DdPc8slb.js.gz +0 -0
  566. package/dist/dashboard/assets/QuotaPage-Ci5O4BnH.js +0 -1
  567. package/dist/dashboard/assets/QuotaPage-Ci5O4BnH.js.gz +0 -0
  568. package/dist/dashboard/assets/SettingsPage-CDZjTt87.js +0 -1
  569. package/dist/dashboard/assets/SettingsPage-CDZjTt87.js.gz +0 -0
  570. package/dist/dashboard/assets/SetupPage-BQS0Rx_u.js.gz +0 -0
  571. package/dist/dashboard/assets/SortableList-BAL5npBC.js +0 -1
  572. package/dist/dashboard/assets/SortableList-BAL5npBC.js.gz +0 -0
  573. package/dist/dashboard/assets/StudioPage-TP7voAQ0.js.gz +0 -0
  574. package/dist/dashboard/assets/UsagePage-Cj_fEzhg.js +0 -56
  575. package/dist/dashboard/assets/UsagePage-Cj_fEzhg.js.gz +0 -0
  576. package/dist/dashboard/assets/api-keys-MbI7e7rt.js +0 -1
  577. package/dist/dashboard/assets/api-keys-MbI7e7rt.js.gz +0 -0
  578. package/dist/dashboard/assets/app-B2vyPoII.css +0 -2
  579. package/dist/dashboard/assets/app-B2vyPoII.css.gz +0 -0
  580. package/dist/dashboard/assets/assert-C0YYuZJ4.js.gz +0 -0
  581. package/dist/dashboard/assets/badge-B_DMpMJN.js.gz +0 -0
  582. package/dist/dashboard/assets/banner-DMrMPmth.js.gz +0 -0
  583. package/dist/dashboard/assets/card-Be5Uzamx.js.gz +0 -0
  584. package/dist/dashboard/assets/cli-tools-muFRphDu.js.gz +0 -0
  585. package/dist/dashboard/assets/clipboard-button-DOfrVmsi.js.gz +0 -0
  586. package/dist/dashboard/assets/dialog-C9omdjQt.js.gz +0 -0
  587. package/dist/dashboard/assets/drawer-Bm8WKaGY.js.gz +0 -0
  588. package/dist/dashboard/assets/entry-DS90gUks.js +0 -1
  589. package/dist/dashboard/assets/entry-DS90gUks.js.gz +0 -0
  590. package/dist/dashboard/assets/entry-j40LJotO.js +0 -4
  591. package/dist/dashboard/assets/entry-j40LJotO.js.gz +0 -0
  592. package/dist/dashboard/assets/entry-q5HwZ8nN.js.gz +0 -0
  593. package/dist/dashboard/assets/formatters-BTScrQpe.js.gz +0 -0
  594. package/dist/dashboard/assets/github-badge-Orjuzemr.js.gz +0 -0
  595. package/dist/dashboard/assets/index-cPrX5vn6.js.gz +0 -0
  596. package/dist/dashboard/assets/inline-Dvux8w-0.js.gz +0 -0
  597. package/dist/dashboard/assets/input-CU1Rgf3p.js.gz +0 -0
  598. package/dist/dashboard/assets/list-row-CzFvogbj.js.gz +0 -0
  599. package/dist/dashboard/assets/live-KaG15HLQ.js +0 -1
  600. package/dist/dashboard/assets/live-KaG15HLQ.js.gz +0 -0
  601. package/dist/dashboard/assets/minimize-2-D-MN8W8a.js +0 -1
  602. package/dist/dashboard/assets/minimize-2-D-MN8W8a.js.gz +0 -0
  603. package/dist/dashboard/assets/mutation-JJGQm8TD.js.gz +0 -0
  604. package/dist/dashboard/assets/numbers-BNDZnsUE.js +0 -2
  605. package/dist/dashboard/assets/numbers-BNDZnsUE.js.gz +0 -0
  606. package/dist/dashboard/assets/query-keys-CLaNz8Xz.js.gz +0 -0
  607. package/dist/dashboard/assets/quota-DZGyp0N5.js +0 -1
  608. package/dist/dashboard/assets/quota-DZGyp0N5.js.gz +0 -0
  609. package/dist/dashboard/assets/routing-BEH32QOC.js.gz +0 -0
  610. package/dist/dashboard/assets/select-B3iTetHN.js.gz +0 -0
  611. package/dist/dashboard/assets/settings-Dt3Eqbnq.js +0 -1
  612. package/dist/dashboard/assets/settings-Dt3Eqbnq.js.gz +0 -0
  613. package/dist/dashboard/assets/stack-KrOrm7JZ.js.gz +0 -0
  614. package/dist/dashboard/assets/state-DLS7IDMy.js.gz +0 -0
  615. package/dist/dashboard/assets/stream-retry-5QnVuSSo.js +0 -1
  616. package/dist/dashboard/assets/stream-retry-5QnVuSSo.js.gz +0 -0
  617. package/dist/dashboard/assets/switch-42ItvbAs.js.gz +0 -0
  618. package/dist/dashboard/assets/useMutation-Dmnfh1Ue.js.gz +0 -0
  619. package/src/network/response-headers.ts +0 -39
  620. package/src/protocol/response/dsml.ts +0 -38
  621. package/src/providers/integrations/xiaomi-mimo/think-stream.ts +0 -229
  622. /package/dist/dashboard/assets/{app-wvwCi4Eh.js → app-Bgd3zANQ.js} +0 -0
  623. /package/dist/dashboard/assets/{app-wvwCi4Eh.js.gz → app-Bgd3zANQ.js.gz} +0 -0
  624. /package/dist/dashboard/assets/brand/{og_bansos.webp → og_bansos_initial.webp} +0 -0
  625. /package/dist/dashboard/assets/brand/{og_image.webp → og_image_initial.webp} +0 -0
@@ -1,11 +1,15 @@
1
1
  import type { ProviderAdapter, ProviderDispatchTarget } from "../../providers/provider-contracts";
2
2
  import { GatewayError } from "../../protocol/gateway-error";
3
- import { resolveCredentialForAccount } from "../../providers/operations/provider-credential-service";
4
3
  import { metrics } from "../../observability/metrics";
5
- import { parseThinkingSuffix } from "../../protocol/translation/thinking";
6
- import { completeAttempt, estimatedUsage, nativeCompletionBase } from "./attempt-finalize";
7
- import { repriceUsage, usageFromProvider } from "../../providers/provider-usage";
4
+ import { estimatedUsage, nativeCompletionBase, completeAttempt } from "./attempt-finalize";
8
5
  import { runAttemptLoop } from "./attempt-loop";
6
+ import {
7
+ networkFetchContext,
8
+ requireRecordBody,
9
+ resolveAttemptCredential,
10
+ stripThinkingSuffix,
11
+ usageFromUpstreamClone,
12
+ } from "./native-shared";
9
13
 
10
14
  /**
11
15
  * The native System One route (`POST /v1/systemone`) and the dependency surface
@@ -20,7 +24,7 @@ import { runAttemptLoop } from "./attempt-loop";
20
24
  */
21
25
  import type { GatewayHandlerDeps } from "./handler-deps";
22
26
 
23
- export type SystemoneHandlerDeps = GatewayHandlerDeps;
27
+ type SystemoneHandlerDeps = GatewayHandlerDeps;
24
28
 
25
29
  /** Adapter that can serve a System One decision request. */
26
30
  type SystemoneAdapter = ProviderAdapter & {
@@ -31,36 +35,34 @@ export function createSystemoneHandler(deps: SystemoneHandlerDeps) {
31
35
  return async ({ request }: { request: Request }): Promise<Response> => {
32
36
  const state = deps.stateStore.require(request);
33
37
  const authorization = state.authorization;
34
- const body = state.ingressBody;
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");
37
- const decision = body as Record<string, unknown>;
38
+ const decision = requireRecordBody(
39
+ state,
40
+ "systemone request body must be a JSON object (request {requestId}). Check the shape of what you sent — then resend 🪞",
41
+ );
38
42
  if (typeof decision.model !== "string" || decision.model.trim().length === 0)
39
- throw new GatewayError("invalid_request", 400, "model is required");
43
+ throw new GatewayError("invalid_request", 400, "model is required (request {requestId}). Check the shape of what you sent — then resend 🪞");
40
44
  // A thinking suffix (`model(high)`) is stripped for the same reason the
41
45
  // canonical preparer strips it: the route matches the model name against the
42
46
  // allowlist and the route plan, and `(high)` is not part of any registered
43
47
  // id. The level itself is dropped — a decision request carries no reasoning
44
48
  // effort to shape, and the body is passed upstream verbatim.
45
- const { model: bareModel } = parseThinkingSuffix(decision.model);
46
- const decisionBody: Record<string, unknown> =
47
- bareModel === decision.model ? decision : { ...decision, model: bareModel };
49
+ const { bareModel, body: decisionBody } = stripThinkingSuffix(decision.model, decision);
48
50
  // Trust boundary only: the decision model's own question shapes are
49
51
  // upstream's to validate. `state` and a `questions` object are what make a
50
52
  // System One request a System One request at all.
51
53
  if (decision.state === undefined || decision.state === null)
52
- throw new GatewayError("invalid_request", 400, "state is required");
54
+ throw new GatewayError("invalid_request", 400, "state is required (request {requestId}). Check the shape of what you sent — then resend 🪞");
53
55
  if (
54
56
  decision.questions === undefined ||
55
57
  decision.questions === null ||
56
58
  typeof decision.questions !== "object" ||
57
59
  Array.isArray(decision.questions)
58
60
  )
59
- throw new GatewayError("invalid_request", 400, "questions must be an object");
61
+ throw new GatewayError("invalid_request", 400, "questions must be an object (request {requestId}). Check the shape of what you sent — then resend 🪞");
60
62
  const prepared = await deps.proxyPreparer.prepareNativeService({
61
63
  model: bareModel,
62
64
  serviceKind: "systemone",
63
- authorization,
65
+ authorization: authorization!,
64
66
  ...(state.abortController.signal ? { signal: state.abortController.signal } : {}),
65
67
  ...(state.clientUserAgent === undefined ? {} : { clientUserAgent: state.clientUserAgent }),
66
68
  });
@@ -80,30 +82,18 @@ export function createSystemoneHandler(deps: SystemoneHandlerDeps) {
80
82
  // Lenient pool semantics: a missing slot tolerates direct egress.
81
83
  strictPoolSelection: false,
82
84
  exhaustedError: new GatewayError(
83
- "admission_unavailable",
85
+ "route_unavailable",
84
86
  503,
85
- "no eligible System One route",
87
+ "no eligible System One route (request {requestId}). The bouncer stepped out — come back shortly 🚪",
86
88
  ),
87
89
  prepare: async (candidate) => {
88
- const credential = candidate.provider_account_id
89
- ? await resolveCredentialForAccount(
90
- deps.db,
91
- candidate.provider_id,
92
- candidate.provider_account_id,
93
- deps.oauthRefreshService && deps.resolveOAuthRefresher
94
- ? { refreshService: deps.oauthRefreshService, resolveRefresher: deps.resolveOAuthRefresher }
95
- : undefined,
96
- )
97
- : {
98
- provider_id: candidate.provider_id as ProviderAdapter["provider_id"],
99
- credential_kind: "none" as const,
100
- };
90
+ const credential = await resolveAttemptCredential(deps, candidate);
101
91
  const adapter = (await deps.resolveProviderAdapter?.(candidate.provider_id)) as SystemoneAdapter | undefined;
102
92
  if (!adapter || typeof adapter.systemone !== "function")
103
93
  throw new GatewayError(
104
94
  "capability_unsupported",
105
95
  400,
106
- "System One transport unavailable for this provider",
96
+ "System One transport unavailable for this provider (request {requestId}). Know your tools — this one just isn't in the box 🧰",
107
97
  );
108
98
  return { credential, adapter };
109
99
  },
@@ -131,14 +121,7 @@ export function createSystemoneHandler(deps: SystemoneHandlerDeps) {
131
121
  deadline: state.deadlineMs,
132
122
  abort_signal: state.abortController.signal,
133
123
  ...(candidate.user_agent === undefined ? {} : { user_agent: candidate.user_agent }),
134
- ...(deps.networkBindingFactory
135
- ? {
136
- outbound_fetch: deps.networkBindingFactory.fetch(
137
- leases.proxySlot?.poolId,
138
- authorization.tenantId,
139
- ),
140
- }
141
- : {}),
124
+ ...networkFetchContext(deps, leases.proxySlot?.poolId, authorization!.tenantId),
142
125
  });
143
126
  if (!response.ok) {
144
127
  // Surface the upstream decision error verbatim: it is the operator's
@@ -148,26 +131,15 @@ export function createSystemoneHandler(deps: SystemoneHandlerDeps) {
148
131
  throw new GatewayError(
149
132
  response.status >= 500 ? "platform_unavailable" : "invalid_request",
150
133
  response.status,
151
- `System One upstream error: ${detail.slice(0, 240)}`,
134
+ `System One upstream error (request {requestId}): ${detail.slice(0, 240)}`,
152
135
  );
153
136
  }
154
137
  // Prefer the usage the decision endpoint reported; fall back to the
155
138
  // fixed native estimate. `response.clone()` leaves the body readable
156
139
  // for the client.
157
- let reportedUsage: ReturnType<typeof usageFromProvider>;
158
- try {
159
- const parsed: unknown = await response.clone().json();
160
- reportedUsage = usageFromProvider(
161
- typeof parsed === "object" && parsed !== null
162
- ? (parsed as Record<string, unknown>)["usage"]
163
- : undefined,
164
- );
165
- } catch {
166
- reportedUsage = undefined;
167
- }
168
- const usage = repriceUsage(
169
- reportedUsage ??
170
- estimatedUsage(prepared.estimatedInputTokens, prepared.estimatedOutputTokens),
140
+ const usage = await usageFromUpstreamClone(
141
+ response,
142
+ estimatedUsage(prepared.estimatedInputTokens, prepared.estimatedOutputTokens),
171
143
  candidate.provider_id,
172
144
  candidate.model_id,
173
145
  );
@@ -36,46 +36,48 @@
36
36
  import type { ProviderAdapter, ProviderDispatchTarget } from "../../providers/provider-contracts";
37
37
  import { GatewayError } from "../../protocol/gateway-error";
38
38
  import { isRecord } from "../../protocol/codecs";
39
- import { resolveCredentialForAccount } from "../../providers/operations/provider-credential-service";
40
39
  import { metrics } from "../../observability/metrics";
40
+ import type { TelemetryBatchBuffer } from "../../observability/telemetry-buffer";
41
41
  import type { ProxyRequestState } from "../request/state";
42
42
  import type { PreparedNativeRequest } from "../request/preparer";
43
43
  import { completeAttempt, estimatedUsage, nativeCompletionBase } from "./attempt-finalize";
44
+ import { finalizeRequestTelemetry } from "../guards/error-lifecycle";
44
45
  import { runAttemptLoop } from "./attempt-loop";
46
+ import { hasScope, networkFetchContext, requireRecordBody, resolveAttemptCredential } from "./native-shared";
45
47
 
46
48
  import type { GatewayHandlerDeps } from "./handler-deps";
47
49
 
48
- export type TokenCountHandlerDeps = GatewayHandlerDeps;
50
+ type TokenCountHandlerDeps = GatewayHandlerDeps;
49
51
 
50
52
  /** Builds the `POST /v1/messages/count_tokens` handler. */
51
53
  export function createTokenCountHandler(deps: TokenCountHandlerDeps) {
52
54
  return async ({ request }: { request: Request }): Promise<Response> => {
53
55
  const state = deps.stateStore.require(request);
54
56
  const authorization = state.authorization;
55
- const body = state.ingressBody;
56
- if (!authorization || body === null || typeof body !== "object" || Array.isArray(body)) {
57
- throw new GatewayError(
58
- "invalid_request",
59
- 400,
60
- "The count_tokens body must be a JSON object, the same shape POST /v1/messages accepts.",
61
- );
57
+ const countBody = requireRecordBody(
58
+ state,
59
+ "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 🪞",
60
+ );
61
+ // `requireRecordBody` already refused a missing authorization; this guard
62
+ // exists only to narrow the type for the code below.
63
+ if (!authorization) {
64
+ throw new GatewayError("invalid_request", 400, "authentication required (request {requestId})");
62
65
  }
63
- const countBody = body as Record<string, unknown>;
64
66
 
65
67
  // `model` names the route and `messages` is what gets counted; both are the
66
68
  // upstream's own required fields, so the gateway does not restate its own
67
69
  // narrower rule and risk rejecting a body the provider would accept.
68
70
  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.");
71
+ 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
72
  }
71
73
  if (!Array.isArray(countBody.messages)) {
72
- throw new GatewayError("invalid_request", 400, "messages must be an array — there is nothing to count without it.");
74
+ 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
75
  }
74
- if (!authorization.snapshot.scopes?.includes("routing:invoke" as never)) {
76
+ if (!hasScope(authorization?.snapshot.scopes, "routing:invoke")) {
75
77
  throw new GatewayError(
76
- "invalid_request",
78
+ "policy_rejected",
77
79
  403,
78
- "token counting not allowed for this key — enable routing:invoke on the API key",
80
+ "token counting not allowed for this key (request {requestId}) — enable routing:invoke on the API key. Not allowed by policy — no hard feelings 🫂",
79
81
  );
80
82
  }
81
83
 
@@ -93,7 +95,7 @@ export function createTokenCountHandler(deps: TokenCountHandlerDeps) {
93
95
  // real 429/503 — a fabricated count would tell the client its context
94
96
  // fits when nothing is there to serve the turn that follows.
95
97
  if (!(error instanceof GatewayError) || error.code !== "capability_unsupported") throw error;
96
- return estimatedCountResponse(state, countBody);
98
+ return estimatedCountResponse(state, countBody, deps.telemetryBuffer, deps.db);
97
99
  }
98
100
 
99
101
  return runAttemptLoop<Response, ProviderAdapter>({
@@ -111,27 +113,15 @@ export function createTokenCountHandler(deps: TokenCountHandlerDeps) {
111
113
  tenantId: prepared.authorization.tenantId,
112
114
  strictPoolSelection: false,
113
115
  exhaustedError: new GatewayError(
114
- "admission_unavailable",
116
+ "route_unavailable",
115
117
  503,
116
- "no eligible route can count tokens for this model",
118
+ "no eligible route can count tokens for this model (request {requestId}). The bouncer stepped out — come back shortly 🚪",
117
119
  ),
118
120
  prepare: async (candidate) => {
119
- const credential = candidate.provider_account_id
120
- ? await resolveCredentialForAccount(
121
- deps.db,
122
- candidate.provider_id,
123
- candidate.provider_account_id,
124
- deps.oauthRefreshService && deps.resolveOAuthRefresher
125
- ? { refreshService: deps.oauthRefreshService, resolveRefresher: deps.resolveOAuthRefresher }
126
- : undefined,
127
- )
128
- : {
129
- provider_id: candidate.provider_id as ProviderAdapter["provider_id"],
130
- credential_kind: "none" as const,
131
- };
121
+ const credential = await resolveAttemptCredential(deps, candidate);
132
122
  const adapter = await deps.resolveProviderAdapter?.(candidate.provider_id);
133
123
  if (!adapter) {
134
- throw new GatewayError("capability_unsupported", 400, "token counting is unavailable for this provider");
124
+ 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
125
  }
136
126
  return { credential, adapter };
137
127
  },
@@ -146,7 +136,7 @@ export function createTokenCountHandler(deps: TokenCountHandlerDeps) {
146
136
 
147
137
  const adapter = context.adapter;
148
138
  if (typeof adapter.dispatchNativeService !== "function") {
149
- throw new GatewayError("capability_unsupported", 400, "token counting is unavailable for this provider");
139
+ 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
140
  }
151
141
 
152
142
  const target: ProviderDispatchTarget = {
@@ -167,14 +157,7 @@ export function createTokenCountHandler(deps: TokenCountHandlerDeps) {
167
157
  deadline: state.deadlineMs,
168
158
  abort_signal: state.abortController.signal,
169
159
  ...(candidate.user_agent === undefined ? {} : { user_agent: candidate.user_agent }),
170
- ...(deps.networkBindingFactory
171
- ? {
172
- outbound_fetch: deps.networkBindingFactory.fetch(
173
- leases.proxySlot?.poolId,
174
- authorization.tenantId,
175
- ),
176
- }
177
- : {}),
160
+ ...networkFetchContext(deps, leases.proxySlot?.poolId, authorization.tenantId),
178
161
  });
179
162
 
180
163
  // A count is free and generates nothing, so nothing is reserved or
@@ -221,20 +204,40 @@ export function createTokenCountHandler(deps: TokenCountHandlerDeps) {
221
204
  *
222
205
  * No attempt is finalized: nothing was leased, nothing was sent upstream, and
223
206
  * no provider produced this number, so recording it against an account would
224
- * invent usage the account never served. The request still gets an outcome —
225
- * without one the `afterResponse` hook finalizes it as a failure, and a count
226
- * that answered 200 would show up in Console Log as an error.
207
+ * invent usage the account never served. The request is still finalized through
208
+ * `finalizeRequestTelemetry` (the early-rejection pattern) — without it the
209
+ * latency histogram, the `request_complete` console event, and the durable
210
+ * telemetry row all miss this request — and `state.completed` prevents the
211
+ * `afterResponse` fallback from finalizing it a second time.
227
212
  */
228
213
  function estimatedCountResponse(
229
214
  state: ProxyRequestState,
230
215
  countBody: Record<string, unknown>,
216
+ telemetryBuffer: TelemetryBatchBuffer | undefined,
217
+ db: GatewayHandlerDeps["db"],
231
218
  ): Response {
232
219
  const inputTokens = estimateMessagesInputTokens(countBody);
233
220
  const payload = JSON.stringify({ input_tokens: inputTokens });
234
- state.outcome = { status: "completed", httpStatus: 200 };
221
+ // The request still gets an outcome — without one the `afterResponse` hook
222
+ // would finalize it as a failure, and a count that answered 200 would show up
223
+ // in Console Log as an error. `usage` is a zero-priced estimate: nothing was
224
+ // leased and no provider produced this number, so recording a cost against an
225
+ // account would invent usage the account never served (`estimated_cost: null`).
226
+ state.outcome = {
227
+ status: "completed",
228
+ httpStatus: 200,
229
+ usage: estimatedUsage(inputTokens, 0),
230
+ };
235
231
  state.completed = true;
236
232
  state.clientResponseText = payload;
237
233
  metrics.proxy_requests_total.inc(1, { status: "completed" });
234
+ // Finalize telemetry inline (the early-rejection pattern) instead of routing
235
+ // through `completeAttempt`: there was no lease and no provider — but the
236
+ // latency histogram, the `request_complete` console event, and the durable
237
+ // row must still happen, exactly once. `completed = true` also disables the
238
+ // `afterResponse` fallback, which is what used to make this request vanish
239
+ // from telemetry entirely.
240
+ if (telemetryBuffer) finalizeRequestTelemetry(state, telemetryBuffer, db);
238
241
  return new Response(payload, {
239
242
  headers: {
240
243
  "content-type": "application/json",
@@ -255,7 +258,7 @@ function estimatedCountResponse(
255
258
  * `/v1/messages?beta=true/count_tokens` — the counting segment lands inside the
256
259
  * query, so the upstream sees the chat path with junk after it and never counts.
257
260
  */
258
- export function joinCountTokensPath(endpoint: string): string {
261
+ function joinCountTokensPath(endpoint: string): string {
259
262
  const queryAt = endpoint.indexOf("?");
260
263
  const path = queryAt === -1 ? endpoint : endpoint.slice(0, queryAt);
261
264
  const query = queryAt === -1 ? "" : endpoint.slice(queryAt);
@@ -16,57 +16,50 @@
16
16
  */
17
17
  import type { ProviderAdapter, ProviderDispatchTarget } from "../../providers/provider-contracts";
18
18
  import { GatewayError } from "../../protocol/gateway-error";
19
- import { resolveCredentialForAccount } from "../../providers/operations/provider-credential-service";
20
19
  import { metrics } from "../../observability/metrics";
21
- import { parseThinkingSuffix } from "../../protocol/translation/thinking";
22
20
  import { completeAttempt, estimatedUsage, nativeCompletionBase } from "./attempt-finalize";
23
21
  import { runAttemptLoop } from "./attempt-loop";
22
+ import {
23
+ hasScope,
24
+ jsonResponse,
25
+ networkFetchContext,
26
+ requireRecordBody,
27
+ resolveAttemptCredential,
28
+ stripThinkingSuffix,
29
+ } from "./native-shared";
24
30
 
25
31
  import type { GatewayHandlerDeps } from "./handler-deps";
26
32
 
27
- export type WebsearchHandlerDeps = GatewayHandlerDeps;
28
-
29
-
33
+ type WebsearchHandlerDeps = GatewayHandlerDeps;
30
34
 
31
35
  /** Adapter that can serve a web-search request. */
32
36
  type WebsearchAdapter = ProviderAdapter & {
33
37
  websearch: NonNullable<ProviderAdapter["websearch"]>;
34
38
  };
35
39
 
36
- function jsonResponse(payload: unknown, status: number, requestId: string): Response {
37
- return new Response(JSON.stringify(payload), {
38
- status,
39
- headers: {
40
- "content-type": "application/json",
41
- "cache-control": "no-store",
42
- "x-request-id": requestId,
43
- },
44
- });
45
- }
46
-
47
40
  export function createWebsearchHandler(deps: WebsearchHandlerDeps) {
48
41
  return async ({ request }: { request: Request }): Promise<Response> => {
49
42
  const state = deps.stateStore.require(request);
50
43
  const authorization = state.authorization;
51
- const body = state.ingressBody;
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.");
54
- const search = body as Record<string, unknown>;
44
+ const search = requireRecordBody(state, "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 🪞");
45
+ // `requireRecordBody` already refused a missing authorization; this guard
46
+ // exists only to narrow the type for the code below.
47
+ if (!authorization) {
48
+ throw new GatewayError("invalid_request", 400, "authentication required (request {requestId})");
49
+ }
55
50
  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.");
51
+ 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
52
  // The search "model" names the provider (or an alias/combo of one). A
58
53
  // thinking suffix is stripped for the same reason the canonical preparer
59
54
  // strips it: it is not part of any registered id.
60
- const { model: bareModel } = parseThinkingSuffix(search.model);
61
- const searchBody: Record<string, unknown> =
62
- bareModel === search.model ? search : { ...search, model: bareModel };
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");
55
+ const { bareModel, body: searchBody } = stripThinkingSuffix(search.model, search);
56
+ if (!hasScope(authorization?.snapshot.scopes, "search:invoke")) {
57
+ 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
58
  }
66
59
  // Trust boundary only: the query must be a non-empty string. The adapter
67
60
  // validates the remaining shape when it maps the body onto its provider.
68
61
  if (typeof search.query !== "string" || search.query.trim().length === 0)
69
- throw new GatewayError("invalid_request", 400, "query is required");
62
+ throw new GatewayError("invalid_request", 400, "query is required (request {requestId}). Check the shape of what you sent — then resend 🪞");
70
63
 
71
64
  const prepared = await deps.proxyPreparer.prepareNativeService({
72
65
  model: bareModel,
@@ -89,27 +82,15 @@ export function createWebsearchHandler(deps: WebsearchHandlerDeps) {
89
82
  candidates: prepared.candidates,
90
83
  tenantId: prepared.authorization.tenantId,
91
84
  strictPoolSelection: false,
92
- exhaustedError: new GatewayError("admission_unavailable", 503, "no eligible search route"),
85
+ exhaustedError: new GatewayError("route_unavailable", 503, "no eligible search route (request {requestId}). The bouncer stepped out — come back shortly 🚪"),
93
86
  prepare: async (candidate) => {
94
- const credential = candidate.provider_account_id
95
- ? await resolveCredentialForAccount(
96
- deps.db,
97
- candidate.provider_id,
98
- candidate.provider_account_id,
99
- deps.oauthRefreshService && deps.resolveOAuthRefresher
100
- ? { refreshService: deps.oauthRefreshService, resolveRefresher: deps.resolveOAuthRefresher }
101
- : undefined,
102
- )
103
- : {
104
- provider_id: candidate.provider_id as ProviderAdapter["provider_id"],
105
- credential_kind: "none" as const,
106
- };
87
+ const credential = await resolveAttemptCredential(deps, candidate);
107
88
  const adapter = (await deps.resolveProviderAdapter?.(candidate.provider_id)) as WebsearchAdapter | undefined;
108
89
  if (!adapter || typeof adapter.websearch !== "function")
109
90
  throw new GatewayError(
110
91
  "capability_unsupported",
111
92
  400,
112
- "web search is unavailable for this provider",
93
+ "web search is unavailable for this provider (request {requestId}). Know your tools — this one just isn't in the box 🧰",
113
94
  );
114
95
  return { credential, adapter };
115
96
  },
@@ -132,14 +113,7 @@ export function createWebsearchHandler(deps: WebsearchHandlerDeps) {
132
113
  deadline: state.deadlineMs,
133
114
  abort_signal: state.abortController.signal,
134
115
  ...(candidate.user_agent === undefined ? {} : { user_agent: candidate.user_agent }),
135
- ...(deps.networkBindingFactory
136
- ? {
137
- outbound_fetch: deps.networkBindingFactory.fetch(
138
- leases.proxySlot?.poolId,
139
- authorization.tenantId,
140
- ),
141
- }
142
- : {}),
116
+ ...networkFetchContext(deps, leases.proxySlot?.poolId, authorization.tenantId),
143
117
  });
144
118
  const payload = {
145
119
  provider: candidate.provider_id,
@@ -20,7 +20,7 @@
20
20
  import { timingSafeEqual } from "node:crypto";
21
21
  import { GatewayError } from "../protocol/gateway-error";
22
22
 
23
- export interface DrainEndpointDeps {
23
+ interface DrainEndpointDeps {
24
24
  /** Shared secret; the route is registered only when this is set. */
25
25
  readonly token: string;
26
26
  /** Begins the graceful drain (the process's own shutdown path). */
@@ -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",
@@ -649,6 +656,12 @@ const QUOTA_ERROR_IDENTIFIERS: ReadonlySet<string> = new Set([
649
656
  // it is a spend/limit verdict, never an auth one, so it must classify as
650
657
  // quota (a cooldown) instead of `auth_invalidated`.
651
658
  "110",
659
+ // Qoder also reports quota exhaustion as `{"code":116,"error":"quota
660
+ // exceeded"}` (seen on the IDE model-server surface with a 499/stream
661
+ // close). The 499 special-case below already treats qoder 499 as quota;
662
+ // naming the code here makes any status that carries it classify as quota
663
+ // instead of `invalid_request`/`authentication_failed`.
664
+ "116",
652
665
  // InferHub publishes its own vocabulary in `error.code`. The two 403s below
653
666
  // are deliberate absences: `account_blocked` means the account was blocked
654
667
  // (an auth/entitlement verdict, not a spend limit) and
@@ -816,7 +829,7 @@ export function classifyUpstreamError(
816
829
  }
817
830
  if (normalized !== undefined && CAPACITY_ERROR_IDENTIFIERS.has(normalized))
818
831
  return {
819
- code: "capacity_exhausted",
832
+ code: "upstream_overloaded",
820
833
  status: status === 503 || status === 529 ? status : 529,
821
834
  origin: "upstream",
822
835
  };
@@ -969,7 +982,7 @@ export async function throwIfHtmlResponse(response: Response): Promise<void> {
969
982
  throw new GatewayError(
970
983
  "transport_unavailable",
971
984
  502,
972
- `Upstream returned an HTML error page: ${summary}`.slice(0, 300),
985
+ `Upstream returned an HTML error page: ${summary} (request {requestId}). The provider's edge is having a moment — hang tight ☕️`.slice(0, 320),
973
986
  { upstreamStatus: response.status, contentType, accountScope: true },
974
987
  "upstream",
975
988
  );
@@ -1011,11 +1024,11 @@ export function fallbackRetryDelayMs(attemptIndex: number): number {
1011
1024
 
1012
1025
  /** Sleeps until the delay expires or the request is aborted. */
1013
1026
  export function sleep(ms: number, signal: AbortSignal): Promise<void> {
1014
- if (signal.aborted) return Promise.reject(new GatewayError("transport_closed", 499, "request was cancelled"));
1027
+ if (signal.aborted) return Promise.reject(new GatewayError("transport_closed", 499, "The request was cancelled (request {requestId}). Send it again whenever ready 🙃"));
1015
1028
  if (ms <= 0) return Promise.resolve();
1016
1029
  const { promise, resolve, reject } = Promise.withResolvers<void>();
1017
1030
  const timer = setTimeout(() => { signal.removeEventListener("abort", onAbort); resolve(); }, ms);
1018
- const onAbort = () => { clearTimeout(timer); reject(new GatewayError("transport_closed", 499, "request was cancelled")); };
1031
+ const onAbort = () => { clearTimeout(timer); reject(new GatewayError("transport_closed", 499, "The request was cancelled (request {requestId}). Send it again whenever ready 🙃")); };
1019
1032
  signal.addEventListener("abort", onAbort, { once: true });
1020
1033
  return promise;
1021
1034
  }
@@ -20,12 +20,6 @@ import { createAccessDecision } from "../../security/access-control";
20
20
  export function createApiKeyAuthenticationMiddleware(deps: {
21
21
  readonly db: CartethyiaDatabase;
22
22
  readonly stateStore: ProxyRequestStateStore;
23
- /**
24
- * Model-abuse ban gate. A banned IP or key is refused here, before the
25
- * canonical parse and route preparation, and before `state.authorization` is
26
- * assigned — so a banned caller's attempts produce no telemetry row and no
27
- * console error, which is exactly the noise this layer exists to stop.
28
- */
29
23
  /**
30
24
  * Tenant invoke kill switch. Checked after the key resolves, because the
31
25
  * tenant is a property of the key: the same gateway serves several tenants
@@ -62,9 +56,9 @@ export function createApiKeyAuthenticationMiddleware(deps: {
62
56
  safe.name = error instanceof Error ? error.name : "Error";
63
57
  log.error("[auth] key lookup failed; treating as unavailable", safe);
64
58
  throw new GatewayError(
65
- "accounts_unavailable",
59
+ "admission_unavailable",
66
60
  503,
67
- "The gateway could not verify this API key because its credential store is unavailable. Retry shortly.",
61
+ "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
62
  {},
69
63
  "cartethyia",
70
64
  );
@@ -76,7 +70,7 @@ export function createApiKeyAuthenticationMiddleware(deps: {
76
70
  scopes: authorization.scopes,
77
71
  });
78
72
  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.");
73
+ 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
74
  // Kill switch: refuse before any upstream is touched. Distinct from the
81
75
  // scope check above, which is a property of the key — this is the tenant
82
76
  // having stopped all traffic, and it applies to every key the tenant owns.
@@ -86,7 +80,8 @@ export function createApiKeyAuthenticationMiddleware(deps: {
86
80
  throw new GatewayError(
87
81
  "platform_unavailable",
88
82
  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.",
83
+ "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 🔌",
84
+ { reason: "invoke_switched_off" },
90
85
  );
91
86
  // A key may refuse a downstream router it has been resold to. This is a
92
87
  // policy check on the resolved key, so it belongs with the other
@@ -100,7 +95,7 @@ export function createApiKeyAuthenticationMiddleware(deps: {
100
95
  throw new GatewayError(
101
96
  "client_router_denied",
102
97
  403,
103
- "No API invocation access for this client.",
98
+ "No API invocation access for this client (request {requestId}). This router isn't on the guest list — denied, but politely 🤗",
104
99
  { reason: "client_router_denied", clientRouter: denied },
105
100
  );
106
101
  deps.stateStore.require(request).authorization = authorization;