cartethyia 2.7.4 → 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 (553) hide show
  1. package/.env.example +2 -0
  2. package/README.md +17 -5
  3. package/dist/dashboard/assets/{BannedPage-ChG5svFb.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-BIcWQun7.js → CliToolDetailPage-DZKmVuJN.js} +1 -1
  8. package/dist/dashboard/assets/CliToolDetailPage-DZKmVuJN.js.gz +0 -0
  9. package/dist/dashboard/assets/{CliToolsPage-Bf2VelDe.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-ggeMSjte.js → CustomizationPage-BKj_r0rp.js} +1 -1
  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-U6AKaJcF.js → LoginPage-DkXb6dx-.js} +1 -1
  20. package/dist/dashboard/assets/LoginPage-DkXb6dx-.js.gz +0 -0
  21. package/dist/dashboard/assets/{ModelPicker-BMS2t8BA.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-lsrx7NYg.js → ProxyPage-5SnUSy3j.js} +1 -1
  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-f0DE1h0-.js → SetupPage-WTa0sktC.js} +1 -1
  38. package/dist/dashboard/assets/SetupPage-WTa0sktC.js.gz +0 -0
  39. package/dist/dashboard/assets/{StudioPage-Cq5tW7hW.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-C0K-c4eo.js +1 -0
  48. package/dist/dashboard/assets/assert-C0K-c4eo.js.gz +0 -0
  49. package/dist/dashboard/assets/{badge-D9CS6dZk.js → badge-DfbhQU4k.js} +1 -1
  50. package/dist/dashboard/assets/badge-DfbhQU4k.js.gz +0 -0
  51. package/dist/dashboard/assets/{banner-DympBNq4.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-ibRWU_uT.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-CQXVMn4P.js +1 -0
  62. package/dist/dashboard/assets/cli-tools-CQXVMn4P.js.gz +0 -0
  63. package/dist/dashboard/assets/{clipboard-button-DJm-9pz7.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-8fiE6tec.js → dialog-BEK7KP0H.js} +1 -1
  66. package/dist/dashboard/assets/dialog-BEK7KP0H.js.gz +0 -0
  67. package/dist/dashboard/assets/{drawer-CqGYeOrd.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-BSAsYsmV.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-D-MTJxMr.js → formatters-BAACA727.js} +1 -1
  76. package/dist/dashboard/assets/formatters-BAACA727.js.gz +0 -0
  77. package/dist/dashboard/assets/{github-badge-CPK47xQx.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-DBNPOfhV.js → index-FQlj0HUT.js} +2 -2
  80. package/dist/dashboard/assets/index-FQlj0HUT.js.gz +0 -0
  81. package/dist/dashboard/assets/{inline-BT0zntEr.js → inline-C4gU5iOL.js} +1 -1
  82. package/dist/dashboard/assets/inline-C4gU5iOL.js.gz +0 -0
  83. package/dist/dashboard/assets/{input-Da2ZViWg.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-BXNeLw6M.js → layout-CajrYUnD.js} +1 -1
  86. package/dist/dashboard/assets/layout-CajrYUnD.js.gz +0 -0
  87. package/dist/dashboard/assets/{list-row-BycOrrIV.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/network-C5UBACrJ.js +1 -0
  92. package/dist/dashboard/assets/network-C5UBACrJ.js.gz +0 -0
  93. package/dist/dashboard/assets/numbers-CUWAZK4J.js +2 -0
  94. package/dist/dashboard/assets/numbers-CUWAZK4J.js.gz +0 -0
  95. package/dist/dashboard/assets/pagination-Xsi9Qx04.js +1 -0
  96. package/dist/dashboard/assets/pagination-Xsi9Qx04.js.gz +0 -0
  97. package/dist/dashboard/assets/quota-DvQbD_sr.js +1 -0
  98. package/dist/dashboard/assets/quota-DvQbD_sr.js.gz +0 -0
  99. package/dist/dashboard/assets/routing-in0f_IfK.js +1 -0
  100. package/dist/dashboard/assets/routing-in0f_IfK.js.gz +0 -0
  101. package/dist/dashboard/assets/{select-BIYIBkMU.js → select-BLULNqTp.js} +1 -1
  102. package/dist/dashboard/assets/select-BLULNqTp.js.gz +0 -0
  103. package/dist/dashboard/assets/settings-CcGczj6I.js +1 -0
  104. package/dist/dashboard/assets/settings-CcGczj6I.js.gz +0 -0
  105. package/dist/dashboard/assets/shield-BMQNxORh.js +1 -0
  106. package/dist/dashboard/assets/shield-BMQNxORh.js.gz +0 -0
  107. package/dist/dashboard/assets/{stack-DfgnFDWY.js → stack-D2lIR0tg.js} +1 -1
  108. package/dist/dashboard/assets/stack-D2lIR0tg.js.gz +0 -0
  109. package/dist/dashboard/assets/{state-CkfdYWel.js → state-DTARjfiW.js} +1 -1
  110. package/dist/dashboard/assets/state-DTARjfiW.js.gz +0 -0
  111. package/dist/dashboard/assets/{switch-SYm237HG.js → switch-c0oPSnqW.js} +1 -1
  112. package/dist/dashboard/assets/switch-c0oPSnqW.js.gz +0 -0
  113. package/dist/dashboard/assets/{useMutation-BQqCRNIi.js → useMutation-m8WGNPrz.js} +1 -1
  114. package/dist/dashboard/assets/useMutation-m8WGNPrz.js.gz +0 -0
  115. package/dist/dashboard/console-backgrounds.jpg +0 -0
  116. package/dist/dashboard/console-backgrounds_initial.jpg +0 -0
  117. package/dist/dashboard/index.html +2 -2
  118. package/dist/dashboard/index.html.gz +0 -0
  119. package/dist/dashboard/precompressed-assets.json +56 -55
  120. package/migrations/full-postgres/0036_system_prompt_presets_refs.sql +10 -0
  121. package/migrations/full-postgres/0037_share_concurrency_scope.sql +3 -0
  122. package/migrations/full-postgres/0038_add_accounts_quota_snapshot.sql +7 -0
  123. package/migrations/full-postgres/0039_add_telemetry_provider_model_indexes.sql +13 -0
  124. package/migrations/full-postgres/0040_add_models_dialect.sql +3 -0
  125. package/migrations/full-postgres/0041_add_daily_checkin_ledger_account_fk.sql +7 -0
  126. package/migrations/lite-sqlite3/0036_system_prompt_presets_refs.sql +14 -0
  127. package/migrations/lite-sqlite3/0037_share_concurrency_scope.sql +3 -0
  128. package/migrations/lite-sqlite3/0038_add_accounts_quota_snapshot.sql +4 -0
  129. package/migrations/lite-sqlite3/0039_add_telemetry_provider_model_indexes.sql +6 -0
  130. package/migrations/lite-sqlite3/0040_add_models_dialect.sql +3 -0
  131. package/migrations/lite-sqlite3/0041_add_daily_checkin_ledger_account_fk.sql +26 -0
  132. package/package.json +4 -6
  133. package/scripts/cli/update.ts +15 -41
  134. package/src/boot/app.ts +58 -2
  135. package/src/boot/dependencies.ts +786 -732
  136. package/src/boot/lifecycle.ts +11 -12
  137. package/src/config/cooldowns.ts +34 -55
  138. package/src/config/network.ts +1 -1
  139. package/src/config/package-paths.ts +1 -1
  140. package/src/config/providers.ts +13 -14
  141. package/src/config/resources.ts +0 -5
  142. package/src/config/runtime.ts +2 -5
  143. package/src/config/spec.ts +26 -6
  144. package/src/console/api-keys/ban-constants.ts +38 -0
  145. package/src/console/api-keys/contracts.ts +71 -62
  146. package/src/console/api-keys/routes.ts +48 -12
  147. package/src/console/auth/access.ts +4 -4
  148. package/src/console/auth/account-reset.ts +1 -1
  149. package/src/console/auth/audit-recorder.ts +1 -1
  150. package/src/console/auth/lockout.ts +7 -3
  151. package/src/console/auth/row-coercion.ts +57 -12
  152. package/src/console/auth/session-resolver.ts +1 -1
  153. package/src/console/auth/session-service.ts +8 -2
  154. package/src/console/auth/session.ts +37 -31
  155. package/src/console/auth/setup-service.ts +4 -3
  156. package/src/console/auth/user-store.ts +1 -1
  157. package/src/console/backup/9router-map.ts +0 -3
  158. package/src/console/backup/contracts.ts +9 -9
  159. package/src/console/backup/export-endpoint.ts +5 -6
  160. package/src/console/backup/nine-router.ts +1 -7
  161. package/src/console/backup/routes.ts +29 -33
  162. package/src/console/backup/service.ts +468 -468
  163. package/src/console/backup/store.ts +47 -29
  164. package/src/console/backup/validate.ts +11 -1
  165. package/src/console/cli-tools/contracts.ts +7 -7
  166. package/src/console/cli-tools/injectors/cline.ts +110 -110
  167. package/src/console/cli-tools/injectors/codex.ts +181 -181
  168. package/src/console/cli-tools/injectors/copilot.ts +108 -108
  169. package/src/console/cli-tools/injectors/driver.ts +247 -247
  170. package/src/console/cli-tools/injectors/jcode.ts +115 -115
  171. package/src/console/cli-tools/injectors/openclaw.ts +161 -161
  172. package/src/console/cli-tools/injectors/opencode.ts +153 -153
  173. package/src/console/cli-tools/routes.ts +1 -1
  174. package/src/console/cli-tools/service.ts +1 -1
  175. package/src/console/cli-tools/store.ts +2 -2
  176. package/src/console/console-router.ts +32 -0
  177. package/src/console/dashboard-assets.ts +9 -9
  178. package/src/console/domain-registration.ts +16 -2
  179. package/src/console/inferhub/client.ts +0 -7
  180. package/src/console/inferhub/key-store.ts +1 -1
  181. package/src/console/inferhub/normalize.ts +2 -2
  182. package/src/console/inferhub/routes.ts +1 -1
  183. package/src/console/inferhub/types.ts +1 -1
  184. package/src/console/observability/contracts.ts +1 -1
  185. package/src/console/observability/live.ts +31 -18
  186. package/src/console/observability/logs.ts +1 -1
  187. package/src/console/observability/sse.ts +62 -8
  188. package/src/console/observability/store.ts +20 -4
  189. package/src/console/providers/catalog/catalog-projections.ts +1 -1
  190. package/src/console/providers/catalog/contracts.ts +39 -0
  191. package/src/console/providers/catalog/model-operations.ts +1 -1
  192. package/src/console/providers/catalog/provider-operations.ts +95 -17
  193. package/src/console/providers/catalog/public-model-store.ts +0 -0
  194. package/src/console/providers/catalog/routes.ts +27 -3
  195. package/src/console/providers/catalog/store.ts +147 -8
  196. package/src/console/providers/detail/routes.ts +1 -1
  197. package/src/console/providers/oauth/callback-listener.ts +100 -18
  198. package/src/console/providers/oauth/routes.ts +92 -13
  199. package/src/console/quota/account-quota-global.ts +41 -3
  200. package/src/console/quota/account-quota-shared.ts +10 -3
  201. package/src/console/quota/account-quota-tenant.ts +259 -66
  202. package/src/console/quota/daily-growth-job.ts +308 -0
  203. package/src/console/quota/native-identity.ts +2 -2
  204. package/src/console/quota/quota-view.ts +1 -1
  205. package/src/console/remote-support/routes.ts +1 -1
  206. package/src/console/remote-support/service.ts +2 -2
  207. package/src/console/routing/model/contracts.ts +1 -1
  208. package/src/console/routing/pools/contracts.ts +2 -2
  209. package/src/console/routing/pools/import-parse.ts +3 -3
  210. package/src/console/routing/pools/relay-deploy.ts +3 -3
  211. package/src/console/settings/contracts.ts +29 -2
  212. package/src/console/settings/store.ts +20 -0
  213. package/src/console/share/share-router.ts +100 -32
  214. package/src/console/share/share-stats.ts +4 -4
  215. package/src/console/share/share-usage.ts +1 -1
  216. package/src/console/shared/errors.ts +2 -2
  217. package/src/console/shared/object.ts +18 -0
  218. package/src/console/shared/read-through-cache.ts +4 -4
  219. package/src/console/shared/transaction.ts +14 -0
  220. package/src/console/system-prompt/contracts.ts +89 -12
  221. package/src/console/system-prompt/routes.ts +98 -45
  222. package/src/console/updates/apply-update.ts +244 -0
  223. package/src/console/updates/routes.ts +252 -97
  224. package/src/main.ts +11 -5
  225. package/src/network/http2-bidi.ts +1 -1
  226. package/src/network/ip-utils.ts +298 -0
  227. package/src/network/outbound-fetch.ts +1 -1
  228. package/src/network/pool/agent.ts +7 -6
  229. package/src/network/pool/byte-accounting.ts +1 -1
  230. package/src/network/pool/resolver.ts +16 -12
  231. package/src/network/pool/selector.ts +150 -136
  232. package/src/network/pool-health-machine.ts +1 -1
  233. package/src/network/rate-limit-verdict.ts +1 -1
  234. package/src/network/retry.ts +1 -1
  235. package/src/network/ssrf.ts +20 -71
  236. package/src/observability/event-log.ts +2 -2
  237. package/src/observability/event-query.ts +59 -291
  238. package/src/observability/log-sanitize.ts +1 -1
  239. package/src/observability/metrics.ts +4 -4
  240. package/src/observability/payload-capture.ts +6 -5
  241. package/src/observability/payload-store.ts +1 -1
  242. package/src/observability/runtime-metrics.ts +1 -1
  243. package/src/observability/telemetry-buffer.ts +12 -3
  244. package/src/observability/telemetry-reconciliation.ts +3 -3
  245. package/src/observability/token-speed.ts +1 -1
  246. package/src/persistence/connection-url.ts +1 -1
  247. package/src/persistence/db-handle.ts +1 -1
  248. package/src/persistence/db-pg.ts +2 -2
  249. package/src/persistence/db-sqlite.ts +3 -3
  250. package/src/persistence/dialect.ts +5 -1
  251. package/src/persistence/readiness.ts +1 -1
  252. package/src/persistence/redis.ts +1 -1
  253. package/src/persistence/schema.ts +113 -40
  254. package/src/persistence/shared-cache.ts +2 -2
  255. package/src/persistence/sort-order.ts +1 -1
  256. package/src/persistence/stores/api-key-store.ts +10 -1
  257. package/src/persistence/stores/share-store.ts +20 -8
  258. package/src/persistence/system-prompt-store.ts +63 -18
  259. package/src/persistence/tenant-preferences.ts +1 -1
  260. package/src/persistence/ttl-cache.ts +3 -3
  261. package/src/persistence/usage-flush.ts +7 -2
  262. package/src/protocol/canonical-model.ts +1 -1
  263. package/src/protocol/codecs.ts +6 -10
  264. package/src/protocol/dsml.ts +3 -49
  265. package/src/protocol/image-clamp.ts +2 -2
  266. package/src/protocol/reasoning-text.ts +1 -1
  267. package/src/protocol/reasoning.ts +7 -52
  268. package/src/protocol/registry.ts +50 -15
  269. package/src/protocol/request/chat.ts +46 -19
  270. package/src/protocol/request/codex.ts +1 -1
  271. package/src/protocol/request/messages.ts +46 -14
  272. package/src/protocol/request/responses.ts +19 -10
  273. package/src/protocol/response/chat.ts +25 -10
  274. package/src/protocol/response/codex.ts +1 -1
  275. package/src/protocol/response/messages.ts +1 -1
  276. package/src/protocol/response/omp-tool-call-markup.ts +1 -1
  277. package/src/protocol/response/responses.ts +1 -1
  278. package/src/protocol/sse-decoder.ts +50 -1
  279. package/src/protocol/stop-reason.ts +2 -2
  280. package/src/protocol/think-split.ts +347 -0
  281. package/src/protocol/translation/attachment-integrity.ts +3 -3
  282. package/src/protocol/translation/cache-controls.ts +8 -20
  283. package/src/protocol/translation/capabilities.ts +6 -6
  284. package/src/protocol/translation/dialect/deepseek.ts +10 -0
  285. package/src/protocol/translation/dialect/inband.ts +39 -0
  286. package/src/protocol/translation/dialect/model-thinking.ts +89 -0
  287. package/src/protocol/translation/dialect/qwen.ts +114 -0
  288. package/src/protocol/translation/dialect/resolver.ts +83 -0
  289. package/src/protocol/translation/dialect/rules.ts +93 -0
  290. package/src/protocol/translation/dialect/thinking-ladder.ts +245 -0
  291. package/src/protocol/translation/dialect/types.ts +58 -0
  292. package/src/protocol/translation/quirks.ts +18 -4
  293. package/src/protocol/translation/thinking.ts +12 -252
  294. package/src/protocol/translation/tool-id.ts +3 -3
  295. package/src/protocol/translation/tool-repair.ts +1 -1
  296. package/src/protocol/upstream/post-json.ts +6 -1
  297. package/src/providers/adapters/compatible-adapter.ts +42 -3
  298. package/src/providers/authentication/jwt-validator.ts +0 -5
  299. package/src/providers/authentication/oauth-client.ts +254 -253
  300. package/src/providers/authentication/oauth-flow-store.ts +42 -7
  301. package/src/providers/bundled-discovery-providers.ts +6 -30
  302. package/src/providers/configured-openai-providers.ts +82 -97
  303. package/src/providers/default-registry.ts +23 -35
  304. package/src/providers/discovery/catalog/catalog-format.ts +13 -2
  305. package/src/providers/discovery/discovery-types.ts +0 -1
  306. package/src/providers/discovery/openai-model-discovery.ts +2 -2
  307. package/src/providers/discovery/probe-phases.ts +48 -13
  308. package/src/providers/discovery/probing-service.ts +6 -8
  309. package/src/providers/integrations/agentrouter/agentrouter.ts +12 -2
  310. package/src/providers/integrations/antigravity/antigravity-oauth.ts +0 -1
  311. package/src/providers/integrations/antigravity/antigravity-protocol.ts +0 -14
  312. package/src/providers/integrations/antigravity/antigravity.ts +2 -2
  313. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +1 -1
  314. package/src/providers/integrations/buddy/buddy-chat-shared.ts +20 -4
  315. package/src/providers/integrations/buddy/buddy-cli-headers.ts +19 -52
  316. package/src/providers/integrations/buddy/buddy-identity.ts +86 -0
  317. package/src/providers/integrations/buddy/buddy-oauth-shared.ts +1 -1
  318. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +1 -2
  319. package/src/providers/integrations/buddy/codebuddy/provider.ts +4 -15
  320. package/src/providers/integrations/buddy/codebuddy/shared.ts +30 -9
  321. package/src/providers/integrations/buddy/workbuddy/provider.ts +5 -9
  322. package/src/providers/integrations/buddy/workbuddy/shared.ts +21 -3
  323. package/src/providers/integrations/claude/claude-cch.ts +148 -148
  324. package/src/providers/integrations/claude/fallback-credit.ts +3 -7
  325. package/src/providers/integrations/cline/cline.ts +1 -1
  326. package/src/providers/integrations/codex/codex-device-code.ts +101 -101
  327. package/src/providers/integrations/codex/codex-oauth.ts +0 -1
  328. package/src/providers/integrations/commandcode/commandcode.ts +75 -115
  329. package/src/providers/integrations/commandcode/quota.ts +131 -51
  330. package/src/providers/integrations/cursor/cursor-http2.ts +0 -3
  331. package/src/providers/integrations/cursor/cursor-quota.ts +2 -6
  332. package/src/providers/integrations/cursor/cursor-request.ts +0 -87
  333. package/src/providers/integrations/cursor/cursor-shared.ts +0 -4
  334. package/src/providers/integrations/cursor/cursor.ts +38 -5
  335. package/src/providers/integrations/cursor/protocol/framing.ts +0 -56
  336. package/src/providers/integrations/cursor/protocol/messages.ts +0 -96
  337. package/src/providers/integrations/factory/factory.ts +9 -9
  338. package/src/providers/integrations/github/github-discovery.ts +55 -10
  339. package/src/providers/integrations/github/github-oauth.ts +6 -5
  340. package/src/providers/integrations/github/github.ts +41 -14
  341. package/src/providers/integrations/grok/grok-oauth.ts +261 -261
  342. package/src/providers/integrations/grok/grok-quota-frame.ts +1 -1
  343. package/src/providers/integrations/grok/grok-quota.ts +16 -7
  344. package/src/providers/integrations/grok/grok-ratelimit-store.ts +0 -5
  345. package/src/providers/integrations/grok/grok-ratelimit.ts +3 -0
  346. package/src/providers/integrations/grok/grok.ts +86 -36
  347. package/src/providers/integrations/kilo/kilo-oauth.ts +1 -1
  348. package/src/providers/integrations/kiro/kiro-cache.ts +31 -0
  349. package/src/providers/integrations/kiro/kiro-catalog.ts +31 -5
  350. package/src/providers/integrations/kiro/kiro-discovery.ts +47 -43
  351. package/src/providers/integrations/kiro/kiro-machine-id.ts +6 -3
  352. package/src/providers/integrations/kiro/kiro-oauth.ts +523 -255
  353. package/src/providers/integrations/kiro/kiro-quota.ts +35 -47
  354. package/src/providers/integrations/kiro/kiro-request.ts +8 -11
  355. package/src/providers/integrations/kiro/kiro-stream.ts +13 -0
  356. package/src/providers/integrations/kiro/kiro.ts +81 -68
  357. package/src/providers/integrations/muse/muse-oauth.ts +0 -1
  358. package/src/providers/integrations/muse/muse-quota.ts +0 -4
  359. package/src/providers/integrations/opencode/fingerprint.ts +7 -5
  360. package/src/providers/integrations/opencode/provider.ts +17 -24
  361. package/src/providers/integrations/qoder/qoder-catalog.ts +85 -8
  362. package/src/providers/integrations/qoder/qoder-oauth.ts +165 -25
  363. package/src/providers/integrations/qoder/qoder-quota.ts +48 -6
  364. package/src/providers/integrations/qoder/qoder-realm.ts +10 -31
  365. package/src/providers/integrations/qoder/qoder-sse.ts +159 -0
  366. package/src/providers/integrations/qoder/qoder-umid.ts +21 -25
  367. package/src/providers/integrations/qoder/qoder-virtual.ts +12 -0
  368. package/src/providers/integrations/qoder/qoder.ts +354 -62
  369. package/src/providers/integrations/shared/connect.ts +0 -54
  370. package/src/providers/integrations/shared/hash.ts +15 -0
  371. package/src/providers/integrations/shared/session-cache.ts +136 -0
  372. package/src/providers/integrations/trae/trae-cache.ts +7 -87
  373. package/src/providers/integrations/trae/trae-catalog.ts +1 -6
  374. package/src/providers/integrations/trae/trae-protocol.ts +17 -8
  375. package/src/providers/integrations/trae/trae-realm.ts +0 -7
  376. package/src/providers/integrations/trae/trae.ts +53 -10
  377. package/src/providers/integrations/xai/xai-oauth.ts +5 -5
  378. package/src/providers/integrations/zcode-start-plan/gifts.ts +265 -0
  379. package/src/providers/integrations/zcode-start-plan/oauth.ts +154 -0
  380. package/src/providers/integrations/zcode-start-plan/provider.ts +131 -0
  381. package/src/providers/integrations/zcode-start-plan/quota.ts +152 -0
  382. package/src/providers/integrations/zcode-start-plan/zcode_prompt.json +10 -0
  383. package/src/providers/operations/account-error-classification.ts +131 -116
  384. package/src/providers/operations/account-health-service.ts +31 -39
  385. package/src/providers/operations/account-reset-service.ts +58 -1
  386. package/src/providers/operations/cli-platform.ts +31 -5
  387. package/src/providers/operations/client-version-resolver.ts +4 -6
  388. package/src/providers/operations/client-versions.ts +10 -7
  389. package/src/providers/operations/provider-catalog-seeder.ts +2 -0
  390. package/src/providers/operations/provider-catalog-service.ts +18 -2
  391. package/src/providers/operations/provider-credential-service.ts +19 -2
  392. package/src/providers/provider-contracts.ts +18 -7
  393. package/src/providers/provider-metadata.ts +17 -2
  394. package/src/providers/provider-model-definition.ts +22 -1
  395. package/src/providers/provider-probe.ts +4 -2
  396. package/src/providers/provider-usage.ts +81 -16
  397. package/src/providers/quota/cache.ts +29 -0
  398. package/src/providers/quota/quota-contracts.ts +4 -0
  399. package/src/providers/quota/refresh.ts +27 -4
  400. package/src/providers/quota/snapshot.ts +94 -0
  401. package/src/providers/search/search-provider.ts +1 -3
  402. package/src/providers/search/search-providers.ts +2 -1
  403. package/src/security/abuse.ts +4 -4
  404. package/src/security/admission/service.ts +2 -2
  405. package/src/security/admission/ttl.ts +61 -61
  406. package/src/security/api-key-auth.ts +16 -4
  407. package/src/security/client-router-fingerprint.ts +4 -4
  408. package/src/security/credential-codec.ts +3 -3
  409. package/src/security/csrf.ts +2 -2
  410. package/src/security/ip-boundary.ts +16 -189
  411. package/src/security/model-access-rule.ts +1 -1
  412. package/src/security/outbound-headers.ts +18 -0
  413. package/src/shared/invoke-guard.ts +47 -0
  414. package/src/transport/contracts.ts +13 -4
  415. package/src/transport/dispatch/attempt-finalize.ts +1 -1
  416. package/src/transport/dispatch/capture-policy.ts +34 -37
  417. package/src/transport/dispatch/empty-stream.ts +23 -20
  418. package/src/transport/dispatch/generation.ts +25 -45
  419. package/src/transport/dispatch/native-shared.ts +127 -0
  420. package/src/transport/dispatch/proxy-request.ts +3 -1
  421. package/src/transport/dispatch/responses-compact.ts +25 -46
  422. package/src/transport/dispatch/streaming/encoder.ts +18 -72
  423. package/src/transport/dispatch/streaming/finalize.ts +2 -2
  424. package/src/transport/dispatch/streaming/stream.ts +7 -3
  425. package/src/transport/dispatch/streaming/thinking-loop.ts +1 -1
  426. package/src/transport/dispatch/systemone.ts +20 -48
  427. package/src/transport/dispatch/token-count.ts +41 -38
  428. package/src/transport/dispatch/websearch.ts +19 -45
  429. package/src/transport/drain-endpoint.ts +1 -1
  430. package/src/transport/failure-policy.ts +6 -0
  431. package/src/transport/guards/auth-guard.ts +0 -6
  432. package/src/transport/guards/body-policy.ts +3 -3
  433. package/src/transport/guards/headroom-guard.ts +1 -1
  434. package/src/transport/guards/pipeline.ts +1 -1
  435. package/src/transport/guards/request-context.ts +12 -17
  436. package/src/transport/request/preparer.ts +286 -7
  437. package/src/transport/request/rtk/compress-request.ts +3 -3
  438. package/src/transport/request/rtk/rtk-constants.ts +2 -2
  439. package/src/transport/request/rtk/rtk-filters.ts +8 -8
  440. package/src/transport/request/state.ts +1 -13
  441. package/src/transport/request/system-prompt-injection.ts +74 -11
  442. package/src/transport/request/tenant-preferences.ts +22 -0
  443. package/src/transport/routing/candidate-index.ts +2 -2
  444. package/src/transport/routing/round-robin.ts +1 -1
  445. package/src/transport/routing/route-catalog.ts +17 -18
  446. package/src/transport/routing/route-model.ts +126 -33
  447. package/src/transport/routing/router.ts +152 -37
  448. package/src/transport/shutdown-notice.ts +2 -2
  449. package/src/transport/surface/adapters.ts +3 -3
  450. package/src/transport/surface/chat/encode.ts +7 -7
  451. package/src/transport/surface/chat/parse.ts +7 -7
  452. package/src/transport/surface/completion.ts +2 -2
  453. package/src/transport/surface/content-parts.ts +1 -1
  454. package/src/transport/surface/dialects.ts +2 -2
  455. package/src/transport/surface/messages/parse.ts +3 -3
  456. package/src/transport/surface/responses/adapter.ts +1 -1
  457. package/src/transport/surface/responses/errors.ts +0 -9
  458. package/src/transport/vision-handoff-cache.ts +58 -0
  459. package/src/transport/vision-handoff.ts +376 -0
  460. package/src/workers/account-identity-sweep.ts +2 -2
  461. package/src/workers/buddy-growth-tasks.ts +392 -346
  462. package/src/workers/daily-checkin.ts +102 -49
  463. package/src/workers/maintenance-lease.ts +2 -35
  464. package/src/workers/oauth-refresh-sweep.ts +8 -1
  465. package/src/workers/quota-refresh-sweep.ts +120 -82
  466. package/src/workers/scheduler.ts +67 -4
  467. package/src/workers/sweep-runner.ts +24 -5
  468. package/src/workers/zcode-gift-claim.ts +108 -0
  469. package/dist/dashboard/assets/AccountCooldown-Bjo92Qat.js +0 -1
  470. package/dist/dashboard/assets/AccountCooldown-Bjo92Qat.js.gz +0 -0
  471. package/dist/dashboard/assets/BannedPage-ChG5svFb.js.gz +0 -0
  472. package/dist/dashboard/assets/CliToolDetailPage-BIcWQun7.js.gz +0 -0
  473. package/dist/dashboard/assets/CliToolsPage-Bf2VelDe.js.gz +0 -0
  474. package/dist/dashboard/assets/CombosPage-DCgm5g6g.js +0 -1
  475. package/dist/dashboard/assets/CombosPage-DCgm5g6g.js.gz +0 -0
  476. package/dist/dashboard/assets/ConsoleLogPage-a8mD0QF1.js +0 -1
  477. package/dist/dashboard/assets/ConsoleLogPage-a8mD0QF1.js.gz +0 -0
  478. package/dist/dashboard/assets/CustomizationPage-ggeMSjte.js.gz +0 -0
  479. package/dist/dashboard/assets/HealthEventsModal-DzSWzE_j.js +0 -1
  480. package/dist/dashboard/assets/HealthEventsModal-DzSWzE_j.js.gz +0 -0
  481. package/dist/dashboard/assets/LoginPage-U6AKaJcF.js.gz +0 -0
  482. package/dist/dashboard/assets/ModelPicker-BMS2t8BA.js.gz +0 -0
  483. package/dist/dashboard/assets/OverviewPage-D2D4IiHl.js +0 -1
  484. package/dist/dashboard/assets/OverviewPage-D2D4IiHl.js.gz +0 -0
  485. package/dist/dashboard/assets/ProviderDetailPage-CrNf7j_X.js +0 -5
  486. package/dist/dashboard/assets/ProviderDetailPage-CrNf7j_X.js.gz +0 -0
  487. package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js +0 -1
  488. package/dist/dashboard/assets/ProviderIcon-FB6yS4v1.js.gz +0 -0
  489. package/dist/dashboard/assets/ProvidersPage-BSDbquc1.js +0 -1
  490. package/dist/dashboard/assets/ProvidersPage-BSDbquc1.js.gz +0 -0
  491. package/dist/dashboard/assets/ProxyPage-lsrx7NYg.js.gz +0 -0
  492. package/dist/dashboard/assets/QuotaPage-L0skzML8.js +0 -1
  493. package/dist/dashboard/assets/QuotaPage-L0skzML8.js.gz +0 -0
  494. package/dist/dashboard/assets/SettingsPage-CmAbjRHW.js +0 -1
  495. package/dist/dashboard/assets/SettingsPage-CmAbjRHW.js.gz +0 -0
  496. package/dist/dashboard/assets/SetupPage-f0DE1h0-.js.gz +0 -0
  497. package/dist/dashboard/assets/SortableList-ClzpwyET.js +0 -1
  498. package/dist/dashboard/assets/SortableList-ClzpwyET.js.gz +0 -0
  499. package/dist/dashboard/assets/StudioPage-Cq5tW7hW.js.gz +0 -0
  500. package/dist/dashboard/assets/UsagePage-Dfc9rWQn.js +0 -56
  501. package/dist/dashboard/assets/UsagePage-Dfc9rWQn.js.gz +0 -0
  502. package/dist/dashboard/assets/api-keys-C38iXoO4.js +0 -1
  503. package/dist/dashboard/assets/api-keys-C38iXoO4.js.gz +0 -0
  504. package/dist/dashboard/assets/app-a159k0ub.css +0 -2
  505. package/dist/dashboard/assets/app-a159k0ub.css.gz +0 -0
  506. package/dist/dashboard/assets/assert-DbRA8EG2.js +0 -15
  507. package/dist/dashboard/assets/assert-DbRA8EG2.js.gz +0 -0
  508. package/dist/dashboard/assets/badge-D9CS6dZk.js.gz +0 -0
  509. package/dist/dashboard/assets/banner-DympBNq4.js.gz +0 -0
  510. package/dist/dashboard/assets/card-ibRWU_uT.js.gz +0 -0
  511. package/dist/dashboard/assets/cli-tools-63qBGaRw.js +0 -1
  512. package/dist/dashboard/assets/cli-tools-63qBGaRw.js.gz +0 -0
  513. package/dist/dashboard/assets/clipboard-button-DJm-9pz7.js.gz +0 -0
  514. package/dist/dashboard/assets/dialog-8fiE6tec.js.gz +0 -0
  515. package/dist/dashboard/assets/drawer-CqGYeOrd.js.gz +0 -0
  516. package/dist/dashboard/assets/entry-BSAsYsmV.js.gz +0 -0
  517. package/dist/dashboard/assets/entry-CiIQULZD.js +0 -4
  518. package/dist/dashboard/assets/entry-CiIQULZD.js.gz +0 -0
  519. package/dist/dashboard/assets/entry-Daz0lJ-W.js +0 -1
  520. package/dist/dashboard/assets/entry-Daz0lJ-W.js.gz +0 -0
  521. package/dist/dashboard/assets/formatters-D-MTJxMr.js.gz +0 -0
  522. package/dist/dashboard/assets/github-badge-CPK47xQx.js.gz +0 -0
  523. package/dist/dashboard/assets/index-DBNPOfhV.js.gz +0 -0
  524. package/dist/dashboard/assets/inline-BT0zntEr.js.gz +0 -0
  525. package/dist/dashboard/assets/input-Da2ZViWg.js.gz +0 -0
  526. package/dist/dashboard/assets/layout-BXNeLw6M.js.gz +0 -0
  527. package/dist/dashboard/assets/list-row-BycOrrIV.js.gz +0 -0
  528. package/dist/dashboard/assets/live-DCQNZpF7.js +0 -1
  529. package/dist/dashboard/assets/live-DCQNZpF7.js.gz +0 -0
  530. package/dist/dashboard/assets/network-9Yj2ZyLo.js +0 -1
  531. package/dist/dashboard/assets/network-9Yj2ZyLo.js.gz +0 -0
  532. package/dist/dashboard/assets/numbers-CEagj6ZZ.js +0 -2
  533. package/dist/dashboard/assets/numbers-CEagj6ZZ.js.gz +0 -0
  534. package/dist/dashboard/assets/quota-Caw7wqci.js +0 -1
  535. package/dist/dashboard/assets/quota-Caw7wqci.js.gz +0 -0
  536. package/dist/dashboard/assets/routing-CO9zCUus.js +0 -1
  537. package/dist/dashboard/assets/routing-CO9zCUus.js.gz +0 -0
  538. package/dist/dashboard/assets/select-BIYIBkMU.js.gz +0 -0
  539. package/dist/dashboard/assets/settings-jS3-PNSA.js +0 -1
  540. package/dist/dashboard/assets/settings-jS3-PNSA.js.gz +0 -0
  541. package/dist/dashboard/assets/stack-DfgnFDWY.js.gz +0 -0
  542. package/dist/dashboard/assets/state-CkfdYWel.js.gz +0 -0
  543. package/dist/dashboard/assets/stream-retry-5QnVuSSo.js +0 -1
  544. package/dist/dashboard/assets/stream-retry-5QnVuSSo.js.gz +0 -0
  545. package/dist/dashboard/assets/switch-SYm237HG.js.gz +0 -0
  546. package/dist/dashboard/assets/useMutation-BQqCRNIi.js.gz +0 -0
  547. package/src/network/response-headers.ts +0 -39
  548. package/src/protocol/response/dsml.ts +0 -38
  549. package/src/providers/integrations/xiaomi-mimo/think-stream.ts +0 -229
  550. /package/dist/dashboard/assets/{app-BayTTy01.js → app-Bgd3zANQ.js} +0 -0
  551. /package/dist/dashboard/assets/{app-BayTTy01.js.gz → app-Bgd3zANQ.js.gz} +0 -0
  552. /package/dist/dashboard/assets/brand/{og_bansos.webp → og_bansos_initial.webp} +0 -0
  553. /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,10 +35,10 @@ 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 (request {requestId}). Check the shape of what you sent — then resend 🪞");
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
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
@@ -42,9 +46,7 @@ export function createSystemoneHandler(deps: SystemoneHandlerDeps) {
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.
@@ -60,7 +62,7 @@ export function createSystemoneHandler(deps: SystemoneHandlerDeps) {
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
  });
@@ -85,19 +87,7 @@ export function createSystemoneHandler(deps: SystemoneHandlerDeps) {
85
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(
@@ -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
@@ -154,20 +137,9 @@ export function createSystemoneHandler(deps: SystemoneHandlerDeps) {
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,31 +36,33 @@
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 (request {requestId}), the same shape POST /v1/messages accepts. Check the shape of what you sent — then resend 🪞",
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
@@ -71,7 +73,7 @@ export function createTokenCountHandler(deps: TokenCountHandlerDeps) {
71
73
  if (!Array.isArray(countBody.messages)) {
72
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
78
  "policy_rejected",
77
79
  403,
@@ -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>({
@@ -116,19 +118,7 @@ export function createTokenCountHandler(deps: TokenCountHandlerDeps) {
116
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
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 🧰");
@@ -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,51 +16,44 @@
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 (request {requestId}). Send one with at least a model and a query. Check the shape of what you sent — then resend 🪞");
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
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)) {
55
+ const { bareModel, body: searchBody } = stripThinkingSuffix(search.model, search);
56
+ if (!hasScope(authorization?.snapshot.scopes, "search:invoke")) {
64
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
@@ -91,19 +84,7 @@ export function createWebsearchHandler(deps: WebsearchHandlerDeps) {
91
84
  strictPoolSelection: false,
92
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(
@@ -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). */
@@ -656,6 +656,12 @@ const QUOTA_ERROR_IDENTIFIERS: ReadonlySet<string> = new Set([
656
656
  // it is a spend/limit verdict, never an auth one, so it must classify as
657
657
  // quota (a cooldown) instead of `auth_invalidated`.
658
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",
659
665
  // InferHub publishes its own vocabulary in `error.code`. The two 403s below
660
666
  // are deliberate absences: `account_blocked` means the account was blocked
661
667
  // (an auth/entitlement verdict, not a spend limit) and
@@ -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
@@ -4,7 +4,7 @@ import { GatewayError } from "../../protocol/gateway-error";
4
4
  import { assertBoundedJsonDepth } from "../../protocol/codecs";
5
5
  import type { ProxyRequestStateStore } from "../request/state";
6
6
  import { fastPathname } from "../pathname";
7
- import { NATIVE_SERVICE_PATHS, TOKEN_COUNT_PATH } from "../contracts";
7
+ import { NATIVE_SERVICE_PATHS, RESPONSES_COMPACT_PATH, TOKEN_COUNT_PATH } from "../contracts";
8
8
  import { bodyTooLargeError } from "./guard-errors";
9
9
 
10
10
  /** Whether this request is a gateway (`/v1/*`) request. */
@@ -33,7 +33,7 @@ const PROXY_JSON_ROUTES = [
33
33
  "/v1/chat/completions",
34
34
  "/v1/responses",
35
35
  "/v1/completions",
36
- "/v1/responses/compact",
36
+ RESPONSES_COMPACT_PATH,
37
37
  "/v1/messages",
38
38
  // Reads a JSON body through the ingress policy but skips the canonical chat
39
39
  // stage — see `isOpaqueBodyRoute`. Listed here because this table must mirror
@@ -62,7 +62,7 @@ export function isJsonProxyRoutePath(path: string): boolean {
62
62
  return isJsonProxyRoute(path);
63
63
  }
64
64
 
65
- export interface IngressPolicyOptions {
65
+ interface IngressPolicyOptions {
66
66
  readonly maxBodyBytes?: number;
67
67
  }
68
68
 
@@ -31,7 +31,7 @@ const SHED_MARKER_TTL_SECONDS = 5;
31
31
  const SHED_MARKER_KEY = "gateway:shed:v1";
32
32
  const SHED_RETRY_MS = 1_000;
33
33
 
34
- export interface HeadroomGuardDeps {
34
+ interface HeadroomGuardDeps {
35
35
  readonly store: ProxyRequestStateStore;
36
36
  /**
37
37
  * The last sampled memory state, published once per sample by
@@ -35,7 +35,7 @@ import {
35
35
  } from "../../observability/payload-capture";
36
36
 
37
37
  /** Shared state/context carried by every ordered transport stage. */
38
- export interface TransportPipelineContext {
38
+ interface TransportPipelineContext {
39
39
  readonly db: CartethyiaDatabase;
40
40
  readonly stateStore: ProxyRequestStateStore;
41
41
  readonly surfaceRegistry: SurfaceAdapterRegistry;
@@ -24,7 +24,7 @@ interface RequestApp {
24
24
  ): RequestApp;
25
25
  }
26
26
 
27
- export interface RequestContextDeps {
27
+ interface RequestContextDeps {
28
28
  readonly stateStore: ProxyRequestStateStore;
29
29
  readonly clock?: () => number;
30
30
  /** Explicit deadline override; defaults to `CARTETHYIA_UPSTREAM_TIMEOUT_MS` (120s). */
@@ -148,8 +148,7 @@ export function createCanonicalRequestMiddleware(deps: {
148
148
  // the JSON routes table, so the body-policy predicate covers it.
149
149
  // Native routes (compact, System One) read a body but are never parsed
150
150
  // into a canonical request — their bodies are opaque by contract.
151
- if (!isJsonProxyRoutePath(path) || path === "/v1/responses/compact" || isOpaqueBodyRoute(path))
152
- return;
151
+ if (!isJsonProxyRoutePath(path) || isOpaqueBodyRoute(path)) return;
153
152
  // Model discovery and other GET/multimodal routes must not go through canonical parsing.
154
153
  const state = deps.stateStore.require(request);
155
154
  // Single-read invariant: the ingress policy middleware already decoded the
@@ -221,25 +220,21 @@ export function createProxyRoutePreparationMiddleware(deps: {
221
220
  if (!path.startsWith("/v1/")) return;
222
221
  if (request.method === "GET" || request.method === "HEAD") return;
223
222
  const isJsonRoute = isJsonProxyRoutePath(path);
224
- if (!isJsonRoute || path === "/v1/responses/compact" || isOpaqueBodyRoute(path)) return;
223
+ if (!isJsonRoute || isOpaqueBodyRoute(path)) return;
225
224
  const state = deps.stateStore.require(request);
226
225
  if (!state.authorization || !state.canonicalRequest)
227
226
  throw new GatewayError("route_unavailable", 503, "The request was not authenticated and parsed, so this route cannot be served (request {requestId}). Resend the request. The bouncer stepped out — resend shortly 🚪");
228
227
  // The key rides along so a strike is charged to the caller that made it.
229
228
  // Without it, one recipient of a shared link could escalate a ban onto
230
- try {
231
- state.preparedRequest = await deps.preparer.prepare({
232
- canonicalRequest: state.canonicalRequest,
233
- authorization: state.authorization,
234
- deadlineMs: state.deadlineMs,
235
- signal: state.abortController.signal,
236
- ...(state.clientUserAgent === undefined
237
- ? {}
238
- : { clientUserAgent: state.clientUserAgent }),
239
- });
240
- } catch (error) {
241
- throw error;
242
- }
229
+ state.preparedRequest = await deps.preparer.prepare({
230
+ canonicalRequest: state.canonicalRequest,
231
+ authorization: state.authorization,
232
+ deadlineMs: state.deadlineMs,
233
+ signal: state.abortController.signal,
234
+ ...(state.clientUserAgent === undefined
235
+ ? {}
236
+ : { clientUserAgent: state.clientUserAgent }),
237
+ });
243
238
  })
244
239
  .as("plugin");
245
240
  }