cartethyia 2.7.3 → 2.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (352) hide show
  1. package/dist/dashboard/assets/{BannedPage-2TJD37Hb.js → BannedPage-ChG5svFb.js} +1 -1
  2. package/dist/dashboard/assets/BannedPage-ChG5svFb.js.gz +0 -0
  3. package/dist/dashboard/assets/{CliToolDetailPage-xefJNRmJ.js → CliToolDetailPage-BIcWQun7.js} +1 -1
  4. package/dist/dashboard/assets/CliToolDetailPage-BIcWQun7.js.gz +0 -0
  5. package/dist/dashboard/assets/{CliToolsPage-Rj9iHQ3F.js → CliToolsPage-Bf2VelDe.js} +1 -1
  6. package/dist/dashboard/assets/CliToolsPage-Bf2VelDe.js.gz +0 -0
  7. package/dist/dashboard/assets/{CombosPage-JEbYJFxw.js → CombosPage-DCgm5g6g.js} +1 -1
  8. package/dist/dashboard/assets/CombosPage-DCgm5g6g.js.gz +0 -0
  9. package/dist/dashboard/assets/ConsoleLogPage-a8mD0QF1.js +1 -0
  10. package/dist/dashboard/assets/ConsoleLogPage-a8mD0QF1.js.gz +0 -0
  11. package/dist/dashboard/assets/CustomizationPage-ggeMSjte.js +1 -0
  12. package/dist/dashboard/assets/CustomizationPage-ggeMSjte.js.gz +0 -0
  13. package/dist/dashboard/assets/HealthEventsModal-DzSWzE_j.js +1 -0
  14. package/dist/dashboard/assets/HealthEventsModal-DzSWzE_j.js.gz +0 -0
  15. package/dist/dashboard/assets/{LoginPage-C4w7vea-.js → LoginPage-U6AKaJcF.js} +1 -1
  16. package/dist/dashboard/assets/LoginPage-U6AKaJcF.js.gz +0 -0
  17. package/dist/dashboard/assets/{ModelPicker-BtPcAfot.js → ModelPicker-BMS2t8BA.js} +1 -1
  18. package/dist/dashboard/assets/ModelPicker-BMS2t8BA.js.gz +0 -0
  19. package/dist/dashboard/assets/OverviewPage-D2D4IiHl.js +1 -0
  20. package/dist/dashboard/assets/OverviewPage-D2D4IiHl.js.gz +0 -0
  21. package/dist/dashboard/assets/ProviderDetailPage-CrNf7j_X.js +5 -0
  22. package/dist/dashboard/assets/ProviderDetailPage-CrNf7j_X.js.gz +0 -0
  23. package/dist/dashboard/assets/ProvidersPage-BSDbquc1.js +1 -0
  24. package/dist/dashboard/assets/ProvidersPage-BSDbquc1.js.gz +0 -0
  25. package/dist/dashboard/assets/ProxyPage-lsrx7NYg.js +9 -0
  26. package/dist/dashboard/assets/ProxyPage-lsrx7NYg.js.gz +0 -0
  27. package/dist/dashboard/assets/QuotaPage-L0skzML8.js +1 -0
  28. package/dist/dashboard/assets/QuotaPage-L0skzML8.js.gz +0 -0
  29. package/dist/dashboard/assets/SettingsPage-CmAbjRHW.js +1 -0
  30. package/dist/dashboard/assets/SettingsPage-CmAbjRHW.js.gz +0 -0
  31. package/dist/dashboard/assets/{SetupPage-BQS0Rx_u.js → SetupPage-f0DE1h0-.js} +1 -1
  32. package/dist/dashboard/assets/SetupPage-f0DE1h0-.js.gz +0 -0
  33. package/dist/dashboard/assets/{SortableList-BAL5npBC.js → SortableList-ClzpwyET.js} +1 -1
  34. package/dist/dashboard/assets/SortableList-ClzpwyET.js.gz +0 -0
  35. package/dist/dashboard/assets/{StudioPage-TP7voAQ0.js → StudioPage-Cq5tW7hW.js} +3 -3
  36. package/dist/dashboard/assets/StudioPage-Cq5tW7hW.js.gz +0 -0
  37. package/dist/dashboard/assets/UsagePage-Dfc9rWQn.js +56 -0
  38. package/dist/dashboard/assets/UsagePage-Dfc9rWQn.js.gz +0 -0
  39. package/dist/dashboard/assets/api-keys-C38iXoO4.js +1 -0
  40. package/dist/dashboard/assets/api-keys-C38iXoO4.js.gz +0 -0
  41. package/dist/dashboard/assets/app-a159k0ub.css +2 -0
  42. package/dist/dashboard/assets/app-a159k0ub.css.gz +0 -0
  43. package/dist/dashboard/assets/assert-DbRA8EG2.js +15 -0
  44. package/dist/dashboard/assets/assert-DbRA8EG2.js.gz +0 -0
  45. package/dist/dashboard/assets/{badge-B_DMpMJN.js → badge-D9CS6dZk.js} +1 -1
  46. package/dist/dashboard/assets/badge-D9CS6dZk.js.gz +0 -0
  47. package/dist/dashboard/assets/{banner-DMrMPmth.js → banner-DympBNq4.js} +1 -1
  48. package/dist/dashboard/assets/banner-DympBNq4.js.gz +0 -0
  49. package/dist/dashboard/assets/{card-Be5Uzamx.js → card-ibRWU_uT.js} +1 -1
  50. package/dist/dashboard/assets/card-ibRWU_uT.js.gz +0 -0
  51. package/dist/dashboard/assets/cli-tools-63qBGaRw.js +1 -0
  52. package/dist/dashboard/assets/cli-tools-63qBGaRw.js.gz +0 -0
  53. package/dist/dashboard/assets/{clipboard-button-DOfrVmsi.js → clipboard-button-DJm-9pz7.js} +1 -1
  54. package/dist/dashboard/assets/clipboard-button-DJm-9pz7.js.gz +0 -0
  55. package/dist/dashboard/assets/{dialog-C9omdjQt.js → dialog-8fiE6tec.js} +1 -1
  56. package/dist/dashboard/assets/dialog-8fiE6tec.js.gz +0 -0
  57. package/dist/dashboard/assets/{drawer-Bm8WKaGY.js → drawer-CqGYeOrd.js} +1 -1
  58. package/dist/dashboard/assets/drawer-CqGYeOrd.js.gz +0 -0
  59. package/dist/dashboard/assets/{entry-q5HwZ8nN.js → entry-BSAsYsmV.js} +1 -1
  60. package/dist/dashboard/assets/entry-BSAsYsmV.js.gz +0 -0
  61. package/dist/dashboard/assets/entry-CiIQULZD.js +4 -0
  62. package/dist/dashboard/assets/entry-CiIQULZD.js.gz +0 -0
  63. package/dist/dashboard/assets/entry-Daz0lJ-W.js +1 -0
  64. package/dist/dashboard/assets/entry-Daz0lJ-W.js.gz +0 -0
  65. package/dist/dashboard/assets/{formatters-BTScrQpe.js → formatters-D-MTJxMr.js} +1 -1
  66. package/dist/dashboard/assets/formatters-D-MTJxMr.js.gz +0 -0
  67. package/dist/dashboard/assets/{github-badge-Orjuzemr.js → github-badge-CPK47xQx.js} +1 -1
  68. package/dist/dashboard/assets/github-badge-CPK47xQx.js.gz +0 -0
  69. package/dist/dashboard/assets/{index-cPrX5vn6.js → index-DBNPOfhV.js} +2 -2
  70. package/dist/dashboard/assets/index-DBNPOfhV.js.gz +0 -0
  71. package/dist/dashboard/assets/{inline-Dvux8w-0.js → inline-BT0zntEr.js} +1 -1
  72. package/dist/dashboard/assets/inline-BT0zntEr.js.gz +0 -0
  73. package/dist/dashboard/assets/{input-CU1Rgf3p.js → input-Da2ZViWg.js} +1 -1
  74. package/dist/dashboard/assets/input-Da2ZViWg.js.gz +0 -0
  75. package/dist/dashboard/assets/{layout-C_7A6H55.js → layout-BXNeLw6M.js} +1 -1
  76. package/dist/dashboard/assets/layout-BXNeLw6M.js.gz +0 -0
  77. package/dist/dashboard/assets/{list-row-CzFvogbj.js → list-row-BycOrrIV.js} +1 -1
  78. package/dist/dashboard/assets/list-row-BycOrrIV.js.gz +0 -0
  79. package/dist/dashboard/assets/live-DCQNZpF7.js +1 -0
  80. package/dist/dashboard/assets/live-DCQNZpF7.js.gz +0 -0
  81. package/dist/dashboard/assets/minimize-2-BTmMTK4o.js +1 -0
  82. package/dist/dashboard/assets/minimize-2-BTmMTK4o.js.gz +0 -0
  83. package/dist/dashboard/assets/{mutation-JJGQm8TD.js → mutation-Rr0TIvcj.js} +1 -1
  84. package/dist/dashboard/assets/mutation-Rr0TIvcj.js.gz +0 -0
  85. package/dist/dashboard/assets/network-9Yj2ZyLo.js +1 -0
  86. package/dist/dashboard/assets/network-9Yj2ZyLo.js.gz +0 -0
  87. package/dist/dashboard/assets/{numbers-BNDZnsUE.js → numbers-CEagj6ZZ.js} +2 -2
  88. package/dist/dashboard/assets/numbers-CEagj6ZZ.js.gz +0 -0
  89. package/dist/dashboard/assets/{query-keys-CLaNz8Xz.js → query-keys-BloKtaXJ.js} +1 -1
  90. package/dist/dashboard/assets/query-keys-BloKtaXJ.js.gz +0 -0
  91. package/dist/dashboard/assets/quota-Caw7wqci.js +1 -0
  92. package/dist/dashboard/assets/quota-Caw7wqci.js.gz +0 -0
  93. package/dist/dashboard/assets/routing-CO9zCUus.js +1 -0
  94. package/dist/dashboard/assets/routing-CO9zCUus.js.gz +0 -0
  95. package/dist/dashboard/assets/{select-B3iTetHN.js → select-BIYIBkMU.js} +2 -2
  96. package/dist/dashboard/assets/select-BIYIBkMU.js.gz +0 -0
  97. package/dist/dashboard/assets/settings-jS3-PNSA.js +1 -0
  98. package/dist/dashboard/assets/settings-jS3-PNSA.js.gz +0 -0
  99. package/dist/dashboard/assets/{stack-KrOrm7JZ.js → stack-DfgnFDWY.js} +1 -1
  100. package/dist/dashboard/assets/stack-DfgnFDWY.js.gz +0 -0
  101. package/dist/dashboard/assets/{state-DLS7IDMy.js → state-CkfdYWel.js} +1 -1
  102. package/dist/dashboard/assets/state-CkfdYWel.js.gz +0 -0
  103. package/dist/dashboard/assets/{switch-42ItvbAs.js → switch-SYm237HG.js} +1 -1
  104. package/dist/dashboard/assets/switch-SYm237HG.js.gz +0 -0
  105. package/dist/dashboard/assets/{useMutation-Dmnfh1Ue.js → useMutation-BQqCRNIi.js} +1 -1
  106. package/dist/dashboard/assets/useMutation-BQqCRNIi.js.gz +0 -0
  107. package/dist/dashboard/index.html +2 -2
  108. package/dist/dashboard/index.html.gz +0 -0
  109. package/dist/dashboard/precompressed-assets.json +55 -54
  110. package/migrations/full-postgres/0027_qoder_identity_genuine_seed.sql +9 -10
  111. package/migrations/full-postgres/0032_add_provider_pool_accounts.sql +53 -0
  112. package/migrations/full-postgres/0033_add_api_key_ban.sql +9 -0
  113. package/migrations/full-postgres/0034_add_api_key_ban_until.sql +7 -0
  114. package/migrations/full-postgres/0035_align_pool_account_status_enum.sql +21 -0
  115. package/migrations/lite-sqlite3/0027_qoder_identity_genuine_seed.sql +9 -10
  116. package/migrations/lite-sqlite3/0032_add_provider_pool_accounts.sql +44 -0
  117. package/migrations/lite-sqlite3/0033_add_api_key_ban.sql +8 -0
  118. package/migrations/lite-sqlite3/0034_add_api_key_ban_until.sql +5 -0
  119. package/migrations/lite-sqlite3/0035_align_pool_account_status_enum.sql +6 -0
  120. package/package.json +2 -1
  121. package/src/boot/app.ts +9 -8
  122. package/src/config/network.ts +7 -3
  123. package/src/console/api-keys/contracts.ts +52 -0
  124. package/src/console/api-keys/routes.ts +93 -4
  125. package/src/console/audit/routes.ts +3 -3
  126. package/src/console/auth/account-reset.ts +4 -4
  127. package/src/console/auth/session.ts +3 -3
  128. package/src/console/backup/contracts.ts +2 -0
  129. package/src/console/backup/export-endpoint.ts +6 -6
  130. package/src/console/backup/routes.ts +3 -3
  131. package/src/console/cli-tools/routes.ts +3 -3
  132. package/src/console/console-router-guards.ts +2 -2
  133. package/src/console/console-router.ts +4 -4
  134. package/src/console/data-management/routes.ts +3 -3
  135. package/src/console/domain-registration.ts +2 -2
  136. package/src/console/observability/contracts.ts +4 -0
  137. package/src/console/observability/live.ts +3 -3
  138. package/src/console/observability/logs.ts +3 -3
  139. package/src/console/observability/performance.ts +3 -3
  140. package/src/console/observability/routes.ts +3 -3
  141. package/src/console/observability/store.ts +16 -16
  142. package/src/console/providers/catalog/contracts.ts +43 -22
  143. package/src/console/providers/catalog/provider-operations.ts +17 -2
  144. package/src/console/providers/catalog/routes.ts +3 -3
  145. package/src/console/providers/catalog/store-helpers.ts +349 -326
  146. package/src/console/providers/catalog/store.ts +1523 -1292
  147. package/src/console/providers/detail/routes.ts +3 -3
  148. package/src/console/providers/oauth/routes.ts +3 -3
  149. package/src/console/quota/account-quota-tenant.ts +26 -1
  150. package/src/console/quota/account-test-egress.ts +4 -4
  151. package/src/console/routing/model/routes.ts +3 -3
  152. package/src/console/routing/pools/contracts.ts +11 -8
  153. package/src/console/routing/pools/operations.ts +14 -7
  154. package/src/console/routing/pools/probe-verdict.ts +8 -3
  155. package/src/console/routing/pools/relay-deploy.ts +9 -9
  156. package/src/console/routing/pools/routes.ts +3 -3
  157. package/src/console/routing/pools/store.ts +21 -1
  158. package/src/console/settings/routes.ts +3 -3
  159. package/src/console/share/share-router.ts +12 -12
  160. package/src/console/share/share-stats.ts +105 -35
  161. package/src/console/shared/read-through-cache.ts +38 -2
  162. package/src/console/updates/routes.ts +7 -4
  163. package/src/network/outbound-fetch.ts +75 -4
  164. package/src/network/pool/resolver.ts +39 -39
  165. package/src/network/pool-cooldown-store.ts +71 -16
  166. package/src/network/pool-health-machine.ts +21 -1
  167. package/src/network/rate-limit-verdict.ts +68 -0
  168. package/src/network/ssrf.ts +4 -4
  169. package/src/observability/event-query.ts +320 -16
  170. package/src/observability/telemetry-status.ts +1 -1
  171. package/src/persistence/db-sqlite.ts +7 -2
  172. package/src/persistence/readiness.ts +18 -6
  173. package/src/persistence/schema.ts +66 -7
  174. package/src/persistence/stores/api-key-store.ts +99 -3
  175. package/src/persistence/stores/telemetry-store.ts +1 -1
  176. package/src/protocol/codecs.ts +6 -6
  177. package/src/protocol/gateway-error.ts +6 -3
  178. package/src/protocol/image-generation.ts +2 -2
  179. package/src/protocol/response/chat.ts +2 -2
  180. package/src/protocol/response/codex.ts +1 -1
  181. package/src/protocol/response/gemini.ts +1 -1
  182. package/src/protocol/response/messages.ts +5 -5
  183. package/src/protocol/response/responses.ts +2 -2
  184. package/src/protocol/sse-decoder.ts +14 -3
  185. package/src/protocol/translation/capabilities.ts +2 -2
  186. package/src/protocol/upstream/send-claude-messages.ts +3 -3
  187. package/src/providers/adapters/base-adapter.ts +1 -1
  188. package/src/providers/adapters/compatible-adapter.ts +2 -2
  189. package/src/providers/authentication/oauth-refresh-service.ts +2 -2
  190. package/src/providers/configured-provider.ts +1 -1
  191. package/src/providers/discovery/probe-phases.ts +2 -2
  192. package/src/providers/discovery/probing-service.ts +12 -12
  193. package/src/providers/integrations/agentrouter/agentrouter.ts +2 -3
  194. package/src/providers/integrations/alibabacp/alibabacp.ts +2 -2
  195. package/src/providers/integrations/anthropic.ts +4 -11
  196. package/src/providers/integrations/antigravity/antigravity.ts +13 -24
  197. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +1 -1
  198. package/src/providers/integrations/buddy/buddy-discovery-shared.ts +37 -6
  199. package/src/providers/integrations/buddy/buddy-identity.ts +22 -1
  200. package/src/providers/integrations/buddy/buddy-media.ts +16 -16
  201. package/src/providers/integrations/buddy/codebuddy/provider.ts +1 -1
  202. package/src/providers/integrations/buddy/workbuddy/provider.ts +1 -1
  203. package/src/providers/integrations/claude/claude-betas.ts +1 -1
  204. package/src/providers/integrations/claude/claude-credentials.ts +1 -1
  205. package/src/providers/integrations/claude/claude-slow-lane-retry.ts +1 -1
  206. package/src/providers/integrations/claude/claude.ts +3 -10
  207. package/src/providers/integrations/cline/cline.ts +1 -1
  208. package/src/providers/integrations/cloudflareai/cloudflareai.ts +4 -7
  209. package/src/providers/integrations/codex/codex-errors.ts +1 -1
  210. package/src/providers/integrations/codex/codex.ts +14 -15
  211. package/src/providers/integrations/commandcode/commandcode.ts +4 -4
  212. package/src/providers/integrations/commandcode/quota.ts +5 -1
  213. package/src/providers/integrations/cursor/cursor-http2.ts +11 -11
  214. package/src/providers/integrations/cursor/cursor.ts +15 -15
  215. package/src/providers/integrations/factory/factory.ts +4 -4
  216. package/src/providers/integrations/gemini.ts +11 -11
  217. package/src/providers/integrations/github/github.ts +6 -6
  218. package/src/providers/integrations/grok/grok.ts +2 -6
  219. package/src/providers/integrations/inferhub.ts +4 -11
  220. package/src/providers/integrations/kimi/kimi.ts +4 -11
  221. package/src/providers/integrations/kiro/kiro.ts +10 -10
  222. package/src/providers/integrations/muse/muse.ts +6 -9
  223. package/src/providers/integrations/perplexity.ts +2 -2
  224. package/src/providers/integrations/qoder/qoder-catalog.ts +47 -9
  225. package/src/providers/integrations/qoder/qoder-checkin.ts +26 -8
  226. package/src/providers/integrations/qoder/qoder-native-identity-store.ts +9 -8
  227. package/src/providers/integrations/qoder/qoder-realm.ts +15 -0
  228. package/src/providers/integrations/qoder/qoder-umid.ts +12 -13
  229. package/src/providers/integrations/qoder/qoder-virtual.ts +2 -2
  230. package/src/providers/integrations/qoder/qoder.ts +188 -22
  231. package/src/providers/integrations/tokenharbor.ts +2 -2
  232. package/src/providers/integrations/trae/trae.ts +6 -9
  233. package/src/providers/integrations/zcode/oauth.ts +1 -1
  234. package/src/providers/operations/provider-catalog-service.ts +4 -4
  235. package/src/providers/operations/provider-credential-service.ts +16 -4
  236. package/src/providers/operations/upstream-deadline.ts +3 -3
  237. package/src/providers/provider-contracts.ts +47 -3
  238. package/src/providers/provider-credential-resolver.ts +1 -1
  239. package/src/providers/provider-registry.ts +4 -4
  240. package/src/providers/quota/quota-contracts.ts +24 -10
  241. package/src/providers/quota/refresh.ts +2 -2
  242. package/src/providers/search/search-provider.ts +5 -5
  243. package/src/security/abuse.ts +4 -4
  244. package/src/security/admission/reasons.ts +17 -17
  245. package/src/security/api-key-auth.ts +4 -4
  246. package/src/shared/coerce.ts +19 -0
  247. package/src/transport/dispatch/attempt-finalize.ts +4 -3
  248. package/src/transport/dispatch/attempt-loop.ts +10 -7
  249. package/src/transport/dispatch/empty-stream.ts +6 -6
  250. package/src/transport/dispatch/generation.ts +9 -9
  251. package/src/transport/dispatch/leases.ts +8 -8
  252. package/src/transport/dispatch/proxy-request.ts +7 -7
  253. package/src/transport/dispatch/responses-compact.ts +11 -11
  254. package/src/transport/dispatch/retry-policy.ts +4 -67
  255. package/src/transport/dispatch/stream-bridge.ts +1 -1
  256. package/src/transport/dispatch/streaming/finalize.ts +9 -8
  257. package/src/transport/dispatch/systemone.ts +8 -8
  258. package/src/transport/dispatch/token-count.ts +9 -9
  259. package/src/transport/dispatch/websearch.ts +6 -6
  260. package/src/transport/drain-endpoint.ts +2 -2
  261. package/src/transport/failure-policy.ts +13 -6
  262. package/src/transport/guards/auth-guard.ts +6 -5
  263. package/src/transport/guards/body-policy.ts +4 -4
  264. package/src/transport/guards/error-lifecycle.ts +18 -3
  265. package/src/transport/guards/guard-errors.ts +7 -3
  266. package/src/transport/guards/headroom-guard.ts +1 -1
  267. package/src/transport/guards/pipeline.ts +6 -6
  268. package/src/transport/guards/readiness-guard.ts +16 -2
  269. package/src/transport/guards/request-context.ts +3 -3
  270. package/src/transport/request/preparer.ts +20 -20
  271. package/src/transport/request/state.ts +4 -7
  272. package/src/transport/routing/route-catalog.ts +97 -2
  273. package/src/transport/routing/route-model.ts +7 -7
  274. package/src/transport/routing/router.ts +2 -2
  275. package/src/transport/shutdown-notice.ts +19 -7
  276. package/src/transport/surface/completion.ts +1 -1
  277. package/src/transport/surface/messages/encode.ts +1 -1
  278. package/dist/dashboard/assets/BannedPage-2TJD37Hb.js.gz +0 -0
  279. package/dist/dashboard/assets/CliToolDetailPage-xefJNRmJ.js.gz +0 -0
  280. package/dist/dashboard/assets/CliToolsPage-Rj9iHQ3F.js.gz +0 -0
  281. package/dist/dashboard/assets/CombosPage-JEbYJFxw.js.gz +0 -0
  282. package/dist/dashboard/assets/ConsoleLogPage-C3xx_inM.js +0 -1
  283. package/dist/dashboard/assets/ConsoleLogPage-C3xx_inM.js.gz +0 -0
  284. package/dist/dashboard/assets/CustomizationPage-Dw306gJU.js +0 -1
  285. package/dist/dashboard/assets/CustomizationPage-Dw306gJU.js.gz +0 -0
  286. package/dist/dashboard/assets/HealthEventsModal-jaBo1TWV.js +0 -1
  287. package/dist/dashboard/assets/HealthEventsModal-jaBo1TWV.js.gz +0 -0
  288. package/dist/dashboard/assets/LoginPage-C4w7vea-.js.gz +0 -0
  289. package/dist/dashboard/assets/ModelPicker-BtPcAfot.js.gz +0 -0
  290. package/dist/dashboard/assets/OverviewPage-CGC5gNs1.js +0 -1
  291. package/dist/dashboard/assets/OverviewPage-CGC5gNs1.js.gz +0 -0
  292. package/dist/dashboard/assets/ProviderDetailPage-DQcthNGt.js +0 -4
  293. package/dist/dashboard/assets/ProviderDetailPage-DQcthNGt.js.gz +0 -0
  294. package/dist/dashboard/assets/ProvidersPage-0OsXRGXe.js +0 -1
  295. package/dist/dashboard/assets/ProvidersPage-0OsXRGXe.js.gz +0 -0
  296. package/dist/dashboard/assets/ProxyPage-DdPc8slb.js +0 -9
  297. package/dist/dashboard/assets/ProxyPage-DdPc8slb.js.gz +0 -0
  298. package/dist/dashboard/assets/QuotaPage-Ci5O4BnH.js +0 -1
  299. package/dist/dashboard/assets/QuotaPage-Ci5O4BnH.js.gz +0 -0
  300. package/dist/dashboard/assets/SettingsPage-CDZjTt87.js +0 -1
  301. package/dist/dashboard/assets/SettingsPage-CDZjTt87.js.gz +0 -0
  302. package/dist/dashboard/assets/SetupPage-BQS0Rx_u.js.gz +0 -0
  303. package/dist/dashboard/assets/SortableList-BAL5npBC.js.gz +0 -0
  304. package/dist/dashboard/assets/StudioPage-TP7voAQ0.js.gz +0 -0
  305. package/dist/dashboard/assets/UsagePage-Cj_fEzhg.js +0 -56
  306. package/dist/dashboard/assets/UsagePage-Cj_fEzhg.js.gz +0 -0
  307. package/dist/dashboard/assets/api-keys-MbI7e7rt.js +0 -1
  308. package/dist/dashboard/assets/api-keys-MbI7e7rt.js.gz +0 -0
  309. package/dist/dashboard/assets/app-B2vyPoII.css +0 -2
  310. package/dist/dashboard/assets/app-B2vyPoII.css.gz +0 -0
  311. package/dist/dashboard/assets/assert-C0YYuZJ4.js +0 -1
  312. package/dist/dashboard/assets/assert-C0YYuZJ4.js.gz +0 -0
  313. package/dist/dashboard/assets/badge-B_DMpMJN.js.gz +0 -0
  314. package/dist/dashboard/assets/banner-DMrMPmth.js.gz +0 -0
  315. package/dist/dashboard/assets/card-Be5Uzamx.js.gz +0 -0
  316. package/dist/dashboard/assets/cli-tools-muFRphDu.js +0 -1
  317. package/dist/dashboard/assets/cli-tools-muFRphDu.js.gz +0 -0
  318. package/dist/dashboard/assets/clipboard-button-DOfrVmsi.js.gz +0 -0
  319. package/dist/dashboard/assets/dialog-C9omdjQt.js.gz +0 -0
  320. package/dist/dashboard/assets/drawer-Bm8WKaGY.js.gz +0 -0
  321. package/dist/dashboard/assets/entry-DS90gUks.js +0 -1
  322. package/dist/dashboard/assets/entry-DS90gUks.js.gz +0 -0
  323. package/dist/dashboard/assets/entry-j40LJotO.js +0 -4
  324. package/dist/dashboard/assets/entry-j40LJotO.js.gz +0 -0
  325. package/dist/dashboard/assets/entry-q5HwZ8nN.js.gz +0 -0
  326. package/dist/dashboard/assets/formatters-BTScrQpe.js.gz +0 -0
  327. package/dist/dashboard/assets/github-badge-Orjuzemr.js.gz +0 -0
  328. package/dist/dashboard/assets/index-cPrX5vn6.js.gz +0 -0
  329. package/dist/dashboard/assets/inline-Dvux8w-0.js.gz +0 -0
  330. package/dist/dashboard/assets/input-CU1Rgf3p.js.gz +0 -0
  331. package/dist/dashboard/assets/layout-C_7A6H55.js.gz +0 -0
  332. package/dist/dashboard/assets/list-row-CzFvogbj.js.gz +0 -0
  333. package/dist/dashboard/assets/live-KaG15HLQ.js +0 -1
  334. package/dist/dashboard/assets/live-KaG15HLQ.js.gz +0 -0
  335. package/dist/dashboard/assets/minimize-2-D-MN8W8a.js +0 -1
  336. package/dist/dashboard/assets/minimize-2-D-MN8W8a.js.gz +0 -0
  337. package/dist/dashboard/assets/mutation-JJGQm8TD.js.gz +0 -0
  338. package/dist/dashboard/assets/numbers-BNDZnsUE.js.gz +0 -0
  339. package/dist/dashboard/assets/query-keys-CLaNz8Xz.js.gz +0 -0
  340. package/dist/dashboard/assets/quota-DZGyp0N5.js +0 -1
  341. package/dist/dashboard/assets/quota-DZGyp0N5.js.gz +0 -0
  342. package/dist/dashboard/assets/routing-BEH32QOC.js +0 -1
  343. package/dist/dashboard/assets/routing-BEH32QOC.js.gz +0 -0
  344. package/dist/dashboard/assets/select-B3iTetHN.js.gz +0 -0
  345. package/dist/dashboard/assets/settings-Dt3Eqbnq.js +0 -1
  346. package/dist/dashboard/assets/settings-Dt3Eqbnq.js.gz +0 -0
  347. package/dist/dashboard/assets/stack-KrOrm7JZ.js.gz +0 -0
  348. package/dist/dashboard/assets/state-DLS7IDMy.js.gz +0 -0
  349. package/dist/dashboard/assets/switch-42ItvbAs.js.gz +0 -0
  350. package/dist/dashboard/assets/useMutation-Dmnfh1Ue.js.gz +0 -0
  351. /package/dist/dashboard/assets/{app-wvwCi4Eh.js → app-BayTTy01.js} +0 -0
  352. /package/dist/dashboard/assets/{app-wvwCi4Eh.js.gz → app-BayTTy01.js.gz} +0 -0
@@ -1,74 +1,75 @@
1
1
  {
2
- "count": 117,
2
+ "count": 118,
3
3
  "assets": [
4
4
  "assets/AccountCooldown-Bjo92Qat.js",
5
- "assets/BannedPage-2TJD37Hb.js",
6
- "assets/CliToolDetailPage-xefJNRmJ.js",
7
- "assets/CliToolsPage-Rj9iHQ3F.js",
8
- "assets/CombosPage-JEbYJFxw.js",
9
- "assets/ConsoleLogPage-C3xx_inM.js",
10
- "assets/CustomizationPage-Dw306gJU.js",
11
- "assets/HealthEventsModal-jaBo1TWV.js",
12
- "assets/LoginPage-C4w7vea-.js",
13
- "assets/ModelPicker-BtPcAfot.js",
14
- "assets/OverviewPage-CGC5gNs1.js",
15
- "assets/ProviderDetailPage-DQcthNGt.js",
5
+ "assets/BannedPage-ChG5svFb.js",
6
+ "assets/CliToolDetailPage-BIcWQun7.js",
7
+ "assets/CliToolsPage-Bf2VelDe.js",
8
+ "assets/CombosPage-DCgm5g6g.js",
9
+ "assets/ConsoleLogPage-a8mD0QF1.js",
10
+ "assets/CustomizationPage-ggeMSjte.js",
11
+ "assets/HealthEventsModal-DzSWzE_j.js",
12
+ "assets/LoginPage-U6AKaJcF.js",
13
+ "assets/ModelPicker-BMS2t8BA.js",
14
+ "assets/OverviewPage-D2D4IiHl.js",
15
+ "assets/ProviderDetailPage-CrNf7j_X.js",
16
16
  "assets/ProviderIcon-FB6yS4v1.js",
17
- "assets/ProvidersPage-0OsXRGXe.js",
18
- "assets/ProxyPage-DdPc8slb.js",
19
- "assets/QuotaPage-Ci5O4BnH.js",
20
- "assets/SettingsPage-CDZjTt87.js",
21
- "assets/SetupPage-BQS0Rx_u.js",
22
- "assets/SortableList-BAL5npBC.js",
23
- "assets/StudioPage-TP7voAQ0.js",
17
+ "assets/ProvidersPage-BSDbquc1.js",
18
+ "assets/ProxyPage-lsrx7NYg.js",
19
+ "assets/QuotaPage-L0skzML8.js",
20
+ "assets/SettingsPage-CmAbjRHW.js",
21
+ "assets/SetupPage-f0DE1h0-.js",
22
+ "assets/SortableList-ClzpwyET.js",
23
+ "assets/StudioPage-Cq5tW7hW.js",
24
24
  "assets/ToolIcon-K1ckcDKg.js",
25
- "assets/UsagePage-Cj_fEzhg.js",
26
- "assets/api-keys-MbI7e7rt.js",
27
- "assets/app-B2vyPoII.css",
28
- "assets/app-wvwCi4Eh.js",
25
+ "assets/UsagePage-Dfc9rWQn.js",
26
+ "assets/api-keys-C38iXoO4.js",
27
+ "assets/app-BayTTy01.js",
28
+ "assets/app-a159k0ub.css",
29
29
  "assets/arrow-left-CpBxkoxI.js",
30
30
  "assets/arrow-right-Dl5yKnp8.js",
31
31
  "assets/arrow-up-59ms1iiT.js",
32
- "assets/assert-C0YYuZJ4.js",
32
+ "assets/assert-DbRA8EG2.js",
33
33
  "assets/backup-file-gdKIlg8U.js",
34
- "assets/badge-B_DMpMJN.js",
35
- "assets/banner-DMrMPmth.js",
34
+ "assets/badge-D9CS6dZk.js",
35
+ "assets/banner-DympBNq4.js",
36
36
  "assets/brain-Cwed_72z.js",
37
- "assets/card-Be5Uzamx.js",
37
+ "assets/card-ibRWU_uT.js",
38
38
  "assets/chevron-right-BXIy5l3h.js",
39
39
  "assets/circle-alert-CntYHV5v.js",
40
40
  "assets/circle-check-BU3v_mFW.js",
41
- "assets/cli-tools-muFRphDu.js",
42
- "assets/clipboard-button-DOfrVmsi.js",
41
+ "assets/cli-tools-63qBGaRw.js",
42
+ "assets/clipboard-button-DJm-9pz7.js",
43
43
  "assets/database-Dz9N3zGd.js",
44
- "assets/dialog-C9omdjQt.js",
44
+ "assets/dialog-8fiE6tec.js",
45
45
  "assets/dialog-actions-B1axpEg7.js",
46
46
  "assets/download-dRFo5_Wu.js",
47
- "assets/drawer-Bm8WKaGY.js",
48
- "assets/entry-DS90gUks.js",
49
- "assets/entry-j40LJotO.js",
50
- "assets/entry-q5HwZ8nN.js",
47
+ "assets/drawer-CqGYeOrd.js",
48
+ "assets/entry-BSAsYsmV.js",
49
+ "assets/entry-CiIQULZD.js",
50
+ "assets/entry-Daz0lJ-W.js",
51
51
  "assets/eye-C_hawfH4.js",
52
52
  "assets/eye-off-Bk5g2luF.js",
53
53
  "assets/fingerprint-pattern-BWuHtbW4.js",
54
- "assets/formatters-BTScrQpe.js",
54
+ "assets/formatters-D-MTJxMr.js",
55
55
  "assets/gauge-BbIMy7MB.js",
56
- "assets/github-badge-Orjuzemr.js",
56
+ "assets/github-badge-CPK47xQx.js",
57
57
  "assets/image-BPaouViG.js",
58
- "assets/index-cPrX5vn6.js",
59
- "assets/inline-Dvux8w-0.js",
60
- "assets/input-CU1Rgf3p.js",
58
+ "assets/index-DBNPOfhV.js",
59
+ "assets/inline-BT0zntEr.js",
60
+ "assets/input-Da2ZViWg.js",
61
61
  "assets/jsx-runtime-CXUMngI_.js",
62
62
  "assets/key-round-UkavsGxp.js",
63
63
  "assets/layers-2-glBbjl.js",
64
- "assets/layout-C_7A6H55.js",
65
- "assets/list-row-CzFvogbj.js",
66
- "assets/live-KaG15HLQ.js",
64
+ "assets/layout-BXNeLw6M.js",
65
+ "assets/list-row-BycOrrIV.js",
66
+ "assets/live-DCQNZpF7.js",
67
67
  "assets/log-in-BmRjy60c.js",
68
68
  "assets/message-circle-CCxGwRvv.js",
69
- "assets/minimize-2-D-MN8W8a.js",
70
- "assets/mutation-JJGQm8TD.js",
71
- "assets/numbers-BNDZnsUE.js",
69
+ "assets/minimize-2-BTmMTK4o.js",
70
+ "assets/mutation-Rr0TIvcj.js",
71
+ "assets/network-9Yj2ZyLo.js",
72
+ "assets/numbers-CEagj6ZZ.js",
72
73
  "assets/pencil-768ysRAr.js",
73
74
  "assets/plus-DIdX_g-x.js",
74
75
  "assets/power-off-Yjw9GMJh.js",
@@ -91,22 +92,22 @@
91
92
  "assets/providers/parallel.svg",
92
93
  "assets/providers/tokenharbor.svg",
93
94
  "assets/providers/zai-coding-plan.svg",
94
- "assets/query-keys-CLaNz8Xz.js",
95
- "assets/quota-DZGyp0N5.js",
95
+ "assets/query-keys-BloKtaXJ.js",
96
+ "assets/quota-Caw7wqci.js",
96
97
  "assets/refresh-cw-BqJFk4sU.js",
97
98
  "assets/rotate-ccw-CufcqITi.js",
98
99
  "assets/route-CNSNj9Zm.js",
99
- "assets/routing-BEH32QOC.js",
100
+ "assets/routing-CO9zCUus.js",
100
101
  "assets/scaling-nWE4Cx8f.js",
101
102
  "assets/search-JOKus9rb.js",
102
- "assets/select-B3iTetHN.js",
103
+ "assets/select-BIYIBkMU.js",
103
104
  "assets/settings-2-Ds9DQp0t.js",
104
- "assets/settings-Dt3Eqbnq.js",
105
+ "assets/settings-jS3-PNSA.js",
105
106
  "assets/shield-check-DVS0Foqh.js",
106
- "assets/stack-KrOrm7JZ.js",
107
- "assets/state-DLS7IDMy.js",
107
+ "assets/stack-DfgnFDWY.js",
108
+ "assets/state-CkfdYWel.js",
108
109
  "assets/stream-retry-5QnVuSSo.js",
109
- "assets/switch-42ItvbAs.js",
110
+ "assets/switch-SYm237HG.js",
110
111
  "assets/terminal-0_IjPWTk.js",
111
112
  "assets/tokens-COzario9.js",
112
113
  "assets/trash-Cppn5c3R.js",
@@ -114,7 +115,7 @@
114
115
  "assets/use-modal-focus-By7oJoGd.js",
115
116
  "assets/use-reduced-motion-C4B0R6Sz.js",
116
117
  "assets/use-timeout-BwcBBRrT.js",
117
- "assets/useMutation-Dmnfh1Ue.js",
118
+ "assets/useMutation-BQqCRNIi.js",
118
119
  "assets/wrench-B6E1oEuY.js",
119
120
  "assets/zap-C4EeVW4q.js",
120
121
  "index.html"
@@ -1,21 +1,20 @@
1
1
  -- Qoder native identity: genuine SG identity + per-account seed replay.
2
2
  --
3
- -- Research (2026-10-30, see documentation/research/qoder-identity-gate-
4
- -- postmortem.md and qoder-umid-reverse.md) established that the daily-credit
5
- -- campaign gates its CREDITS grant on a *genuine* Alibaba SG-produced
6
- -- identity: generated/deterministic tokens do not reveal the grant on a
7
- -- fresh account, while the `runtime-info` bridge (SG-signed) does.
3
+ -- Research (2026-10-30, see
4
+ -- documentation/archive/research/Qoder-03-umid-and-identity-findings.md)
5
+ -- established that machine identity does not gate the daily-credit campaign.
6
+ -- The seed is retained for deterministic replay of a native identity on
7
+ -- surfaces that may require one; it is not required for campaign eligibility.
8
8
  --
9
9
  -- Migration 0026 already added `native_identity` (the token triple + optional
10
10
  -- seed) with a partial unique index on the extracted machineToken — the
11
11
  -- "one device per account" invariant. This migration does not change that
12
12
  -- schema; it only documents the contract change on the same column:
13
13
  --
14
- -- `seed` is now REQUIRED for a claim-capable identity (the seed is the
15
- -- device material; the token can be re-derived from it, never the reverse).
16
- -- Rows minted by the retired generated path carry `seed = ""` — they stay
17
- -- valid records but must be re-minted through the bridge before the next
18
- -- claim; the store treats a missing/empty seed as "not replay-capable".
14
+ -- `seed` makes a native identity replay-capable (the token can be
15
+ -- re-derived from the seed, never the reverse). Rows minted by the
16
+ -- generated path may have `seed = ""`; that affects native replay only,
17
+ -- not the daily campaign claim.
19
18
  --
20
19
  -- No DDL is needed: the JSON shape already carries everything. Kept as an
21
20
  -- empty migration so the migration numbering records the policy change.
@@ -0,0 +1,53 @@
1
+ -- Pool-as-account routing entities for ip-scoped, credential-less providers.
2
+ --
3
+ -- For providers whose rate limit follows the egress address (OpenCode Free),
4
+ -- the pool is the resource that runs out — but it was invisible to account-level
5
+ -- health and routing because the provider owns no `provider_accounts` rows.
6
+ -- This table materializes one row per (pool × ip-scoped provider), carrying the
7
+ -- same health state machine (`status`, `cooldown_until`, …) so a spent egress
8
+ -- is excluded from routing plans exactly like a cooling account, and shows in
9
+ -- the provider's Accounts card with a "Pool" badge.
10
+ --
11
+ -- The row is an account-equivalent, not an account: it resolves no credential,
12
+ -- is skipped by quota-refresh and identity sweeps (they filter on real
13
+ -- credential kinds), and is owned by its pool — deleting the pool cascades the
14
+ -- row. Rows are backfilled here for every existing pool × ip-scoped provider so
15
+ -- no operator action is needed to adopt the feature.
16
+ CREATE TABLE IF NOT EXISTS "provider_pool_accounts" (
17
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
18
+ "tenant_id" uuid NOT NULL REFERENCES "tenants" ("id") ON DELETE CASCADE,
19
+ "network_pool_id" uuid NOT NULL REFERENCES "network_pools" ("id") ON DELETE CASCADE,
20
+ "provider_id" text NOT NULL,
21
+ "label" text NOT NULL,
22
+ "status" text NOT NULL DEFAULT 'active'
23
+ CHECK ("status" IN ('active', 'cooldown', 'disabled')),
24
+ "consecutive_failures" integer NOT NULL DEFAULT 0,
25
+ "last_success_at" timestamptz,
26
+ "last_error" text,
27
+ "last_error_category" text,
28
+ "last_error_at" timestamptz,
29
+ "cooldown_until" timestamptz,
30
+ "last_recovered_at" timestamptz,
31
+ "created_at" timestamptz NOT NULL DEFAULT now(),
32
+ "updated_at" timestamptz NOT NULL DEFAULT now(),
33
+ CONSTRAINT provider_pool_accounts_pool_provider_uidx
34
+ UNIQUE ("network_pool_id", "provider_id", "tenant_id")
35
+ );
36
+
37
+ CREATE INDEX IF NOT EXISTS provider_pool_accounts_tenant_status_idx
38
+ ON "provider_pool_accounts" ("tenant_id", "status");
39
+ CREATE INDEX IF NOT EXISTS provider_pool_accounts_provider_status_idx
40
+ ON "provider_pool_accounts" ("provider_id", "status");
41
+
42
+ -- Backfill: every existing pool × ip-scoped provider gets its row. The
43
+ -- ip-scoped set is OpenCode Free today; the provider id is spelled here so the
44
+ -- rule stays a data fact, not a code constant.
45
+ INSERT INTO "provider_pool_accounts"
46
+ ("tenant_id", "network_pool_id", "provider_id", "label")
47
+ SELECT
48
+ p."tenant_id",
49
+ p."id",
50
+ 'opencode',
51
+ 'Pool ' || left(p."id"::text, 8)
52
+ FROM "network_pools" p
53
+ ON CONFLICT ("network_pool_id", "provider_id", "tenant_id") DO NOTHING;
@@ -0,0 +1,9 @@
1
+ -- API-key ban state.
2
+ --
3
+ -- A ban is distinct from revoke (terminal, tombstone-like) and disable
4
+ -- (operator toggle, reversible): it records *why* a key was banned
5
+ -- (`ban_reason`) for the console's "See reason" modal. Unban clears both
6
+ -- columns entirely — no tombstone, no leftover metadata.
7
+ ALTER TABLE "api_keys"
8
+ ADD COLUMN "banned_at" timestamptz,
9
+ ADD COLUMN "ban_reason" text;
@@ -0,0 +1,7 @@
1
+ -- API-key ban expiry.
2
+ --
3
+ -- A ban may now carry an optional `banned_until`: a timed ban lifts on its
4
+ -- own (auth checks `banned_at` against `banned_until`), while `null` keeps the
5
+ -- earlier permanent-ban behavior. Unban clears all three ban columns.
6
+ ALTER TABLE "api_keys"
7
+ ADD COLUMN "banned_until" timestamptz;
@@ -0,0 +1,21 @@
1
+ -- Align `provider_pool_accounts.status` with the shared `health_status` enum.
2
+ --
3
+ -- The table was created with a `text` column plus an inline CHECK (migration
4
+ -- 0032), while every other health-state column uses the `health_status` enum.
5
+ -- The Drizzle schema declares the enum, so the drift checker reported the text
6
+ -- column and its CHECK as "produced by migrations but not in schema". This
7
+ -- migration rewrites the column to the enum: same allowed values, one
8
+ -- canonical type.
9
+ --
10
+ -- The inline CHECK from 0032 must be dropped first: PostgreSQL rewrites
11
+ -- CHECK expressions during an ALTER TYPE, and the old `status IN ('active',
12
+ -- ...)` expression compares the new enum column against text literals, which
13
+ -- has no operator once the column becomes the enum.
14
+
15
+ ALTER TABLE "provider_pool_accounts" DROP CONSTRAINT "provider_pool_accounts_status_check";
16
+
17
+ ALTER TABLE "provider_pool_accounts"
18
+ ALTER COLUMN "status" DROP DEFAULT,
19
+ ALTER COLUMN "status" SET DATA TYPE "public"."health_status"
20
+ USING "status"::"public"."health_status",
21
+ ALTER COLUMN "status" SET DEFAULT 'active';
@@ -1,21 +1,20 @@
1
1
  -- Qoder native identity: genuine SG identity + per-account seed replay.
2
2
  --
3
- -- Research (2026-10-30, see documentation/research/qoder-identity-gate-
4
- -- postmortem.md and qoder-umid-reverse.md) established that the daily-credit
5
- -- campaign gates its CREDITS grant on a *genuine* Alibaba SG-produced
6
- -- identity: generated/deterministic tokens do not reveal the grant on a
7
- -- fresh account, while the `runtime-info` bridge (SG-signed) does.
3
+ -- Research (2026-10-30, see
4
+ -- documentation/archive/research/Qoder-03-umid-and-identity-findings.md)
5
+ -- established that machine identity does not gate the daily-credit campaign.
6
+ -- The seed is retained for deterministic replay of a native identity on
7
+ -- surfaces that may require one; it is not required for campaign eligibility.
8
8
  --
9
9
  -- Migration 0026 already added `native_identity` (the token triple + optional
10
10
  -- seed) with a partial unique index on the extracted machineToken — the
11
11
  -- "one device per account" invariant. This migration does not change that
12
12
  -- schema; it only documents the contract change on the same column:
13
13
  --
14
- -- `seed` is now REQUIRED for a claim-capable identity (the seed is the
15
- -- device material; the token can be re-derived from it, never the reverse).
16
- -- Rows minted by the retired generated path carry `seed = ""` — they stay
17
- -- valid records but must be re-minted through the bridge before the next
18
- -- claim; the store treats a missing/empty seed as "not replay-capable".
14
+ -- `seed` makes a native identity replay-capable (the token can be
15
+ -- re-derived from the seed, never the reverse). Rows minted by the
16
+ -- generated path may have `seed = ""`; that affects native replay only,
17
+ -- not the daily campaign claim.
19
18
  --
20
19
  -- No DDL is needed: the JSON shape already carries everything. Kept as an
21
20
  -- empty migration so the migration numbering records the policy change.
@@ -0,0 +1,44 @@
1
+ -- Pool-as-account routing entities for ip-scoped, credential-less providers.
2
+ --
3
+ -- Mirror of `full-postgres/0032_add_provider_pool_accounts.sql`; see that file
4
+ -- for the rationale. SQLite representation: `id`/`tenant_id`/`network_pool_id`
5
+ -- are text (uuid), `status` is text with a CHECK, timestamps are text.
6
+ CREATE TABLE IF NOT EXISTS "provider_pool_accounts" (
7
+ "id" text PRIMARY KEY,
8
+ "tenant_id" text NOT NULL REFERENCES "tenants" ("id") ON DELETE CASCADE,
9
+ "network_pool_id" text NOT NULL REFERENCES "network_pools" ("id") ON DELETE CASCADE,
10
+ "provider_id" text NOT NULL,
11
+ "label" text NOT NULL,
12
+ "status" text NOT NULL DEFAULT 'active'
13
+ CHECK ("status" IN ('active', 'cooldown', 'disabled')),
14
+ "consecutive_failures" integer NOT NULL DEFAULT 0,
15
+ "last_success_at" text,
16
+ "last_error" text,
17
+ "last_error_category" text,
18
+ "last_error_at" text,
19
+ "cooldown_until" text,
20
+ "last_recovered_at" text,
21
+ "created_at" text NOT NULL DEFAULT (datetime('now')),
22
+ "updated_at" text NOT NULL DEFAULT (datetime('now')),
23
+ CONSTRAINT provider_pool_accounts_pool_provider_uidx
24
+ UNIQUE ("network_pool_id", "provider_id", "tenant_id")
25
+ );
26
+
27
+ CREATE INDEX IF NOT EXISTS provider_pool_accounts_tenant_status_idx
28
+ ON "provider_pool_accounts" ("tenant_id", "status");
29
+ CREATE INDEX IF NOT EXISTS provider_pool_accounts_provider_status_idx
30
+ ON "provider_pool_accounts" ("provider_id", "status");
31
+
32
+ -- Backfill: every existing pool × ip-scoped provider gets its row. The
33
+ -- ip-scoped set is OpenCode Free today; the provider id is spelled here so the
34
+ -- rule stays a data fact, not a code constant. SQLite's bun driver rejects the
35
+ -- newer `ON CONFLICT DO NOTHING` on an INSERT…SELECT, so the equivalent
36
+ -- `INSERT OR IGNORE` form is used here (Postgres version keeps the standard one).
37
+ INSERT OR IGNORE INTO "provider_pool_accounts"
38
+ ("tenant_id", "network_pool_id", "provider_id", "label")
39
+ SELECT
40
+ p."tenant_id",
41
+ p."id",
42
+ 'opencode',
43
+ 'Pool ' || substr(p."id", 1, 8)
44
+ FROM "network_pools" p;
@@ -0,0 +1,8 @@
1
+ -- API-key ban state. Mirror of `full-postgres/0033_add_api_key_ban.sql`;
2
+ -- SQLite stores timestamps as text (ISO), and each `ALTER TABLE ... ADD COLUMN`
3
+ -- may carry exactly one column.
4
+ ALTER TABLE "api_keys"
5
+ ADD COLUMN "banned_at" text;
6
+
7
+ ALTER TABLE "api_keys"
8
+ ADD COLUMN "ban_reason" text;
@@ -0,0 +1,5 @@
1
+ -- API-key ban expiry. Mirror of `full-postgres/0034_add_api_key_ban_until.sql`;
2
+ -- SQLite stores timestamps as text (ISO), and each `ALTER TABLE ... ADD COLUMN`
3
+ -- may carry exactly one column.
4
+ ALTER TABLE "api_keys"
5
+ ADD COLUMN "banned_until" text;
@@ -0,0 +1,6 @@
1
+ -- SQLite mirror of `full-postgres/0035_align_pool_account_status_enum.sql`.
2
+ --
3
+ -- SQLite has no enum type: `provider_pool_accounts.status` is a `text`
4
+ -- column with a CHECK constraint already (migration 0032), which matches the
5
+ -- shared schema's semantics. Nothing to change here; the file exists so both
6
+ -- backends carry the same numbered migration set.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cartethyia",
3
- "version": "2.7.3",
3
+ "version": "2.7.4",
4
4
  "description": "A powerful, self-hosted reverse proxy for OpenAI- and Anthropic-compatible AI clients, powered by native Bun and Elysia. It serves both protocols from a single server, routes every model across any configured provider, and translates requests and responses in real time — streaming and prompt caching included. Underneath it runs a full delivery engine: per-account health tracking, quota and cooldown enforcement, automatic failover across pools and providers, usage accounting, and telemetry. Point OpenCode, Cline, Claude Code, Codex, or any compatible client at Cartethyia and it keeps its native protocol while the gateway absorbs every provider-specific difference behind it. Runs on embedded SQLite or external PostgreSQL with optional Redis coordination, ships as a global CLI with a system-tray console, and needs only Bun 1.4+ alongside Node.js/npm.",
5
5
  "license": "GPL-3.0-only",
6
6
  "type": "module",
@@ -48,6 +48,7 @@
48
48
  "start": "bun run scripts/cli/main.ts start",
49
49
  "doctor": "bun run scripts/cli/doctor.ts",
50
50
  "probe:egress": "bun run scripts/tools/probe-egress.ts",
51
+ "probe:qoder": "bun run scripts/provider-probing/qoder/probe-qoder.ts",
51
52
  "restart": "bun run scripts/build/restart.ts",
52
53
  "build:aot": "bun run scripts/build/aot.ts",
53
54
  "dev:stack": "concurrently -n BACKEND,DASHBOARD -c cyan,magenta --kill-others-on-fail \"bun run dev:backend\" \"bun run dashboard:dev\"",
package/src/boot/app.ts CHANGED
@@ -221,7 +221,7 @@ export function createGatewayApp(deps: GatewayAppDeps) {
221
221
  (prefix) => pathname === prefix || pathname.startsWith(`${prefix}/`),
222
222
  );
223
223
  if (reserved) {
224
- const notFound = new GatewayError("not_found", 404, "No endpoint exists at this path. Check the route against the API reference.");
224
+ const notFound = new GatewayError("not_found", 404, "No endpoint exists at this path (request {requestId}). Check the route against the API reference. Nothing here but dust — check the path 🌙");
225
225
  return new Response(JSON.stringify(publicGatewayErrorBody(notFound)), {
226
226
  status: notFound.status,
227
227
  headers: {
@@ -244,8 +244,9 @@ export function createGatewayApp(deps: GatewayAppDeps) {
244
244
  // Shutdown ordering: abort in-flight proxy controllers first so the
245
245
  // bounded drain observes cancellation and finalizers run before
246
246
  // telemetry flush and pool close. The reason is threaded through so the
247
- // streaming path emits a drain-aware terminal frame (`restart_for_update`
248
- // vs `shutting_down`) instead of a bare abort that reads as a client drop.
247
+ // streaming path emits a drain-aware terminal frame (`shutting_down` with
248
+ // reason `restart_for_update` vs plain `shutting_down`) instead of a bare
249
+ // abort that reads as a client drop.
249
250
  // Only production has a coordinator to wire; the shell never drains.
250
251
  if (deps.mode === "production") {
251
252
  const coordinator = deps.shutdownCoordinator;
@@ -469,7 +470,7 @@ export function createGatewayApp(deps: GatewayAppDeps) {
469
470
  throw new GatewayError(
470
471
  "model_not_found",
471
472
  404,
472
- `The model '${targetId}' does not exist or you do not have access to it.`,
473
+ `The model '${targetId}' does not exist or you do not have access to it (request {requestId}). Try another model — this one's off the menu 📚`,
473
474
  );
474
475
  return new Response(JSON.stringify(found), {
475
476
  headers: {
@@ -600,14 +601,14 @@ export function createGatewayApp(deps: GatewayAppDeps) {
600
601
  // for something only the local host may ask. `invalid_request` told an
601
602
  // operator to fix the body, when the body was never the problem.
602
603
  if (!isLoopbackPeer(peerAddresses.get(request) ?? null))
603
- throw new GatewayError("policy_rejected", 403, "task trigger is only available from loopback");
604
+ throw new GatewayError("policy_rejected", 403, "task trigger is only available from loopback (request {requestId}). This operation is only allowed from a trusted loopback connection — no hard feelings 🫂");
604
605
  if (!tokenMatches(request.headers.get("x-drain-token"), token))
605
- throw new GatewayError("authentication_failed", 401, "invalid drain token");
606
+ throw new GatewayError("authentication_failed", 401, "invalid drain token (request {requestId}). Wrong key, right door — check the token 🔑");
606
607
  const name = params.name;
607
608
  if (!tasks.names().includes(name))
608
- throw new GatewayError("invalid_request", 404, `no such task: ${name}`);
609
+ throw new GatewayError("not_found", 404, `no such task: ${name} (request {requestId}). Nothing here but dust — check the task name 🌙`);
609
610
  if (tasks.isRunning(name))
610
- throw new GatewayError("invalid_request", 409, `${name} is already running`);
611
+ throw new GatewayError("invalid_request", 409, `${name} is already running (request {requestId}). One at a time — wait for it to finish ⏳`);
611
612
  void tasks.runNow(name);
612
613
  return new Response(JSON.stringify({ task: name, status: "started" }), {
613
614
  status: 202,
@@ -111,10 +111,14 @@ export const DEFAULT_BOUNDS = {
111
111
  * the worker sweeps (`runGrowingWaves`, at most 5 concurrent), auth, and
112
112
  * readiness.
113
113
  *
114
- * 20 leaves headroom over that without letting one process hold a large
114
+ * 16 leaves headroom over that without letting one process hold a large
115
115
  * share of the server's `max_connections` (Postgres defaults to 100), which
116
116
  * matters because the gateway is designed to run several processes behind
117
- * `reusePort` — see `assertPoolFitsServerCapacity`.
117
+ * `reusePort` — see `assertPoolFitsServerCapacity`. With well-optimized
118
+ * queries a 16-connection pool sustains a hundred concurrent requests: the
119
+ * connections are held for microseconds per query, not for the whole
120
+ * request. If a burst still queues on the pool, fix the query/consumer
121
+ * that holds connections, rather than raising the ceiling.
118
122
  */
119
- maxPostgresPoolSize: 8,
123
+ maxPostgresPoolSize: 16,
120
124
  } as const;
@@ -101,6 +101,11 @@ export interface ApiKeyResponse {
101
101
  readonly sharePopupBody?: string;
102
102
  readonly createdAt: string;
103
103
  readonly revokedAt?: string;
104
+ /** Ban state; see the schema columns for the contract. */
105
+ readonly bannedAt?: string;
106
+ readonly banReason?: string;
107
+ /** When the ban lifts on its own; absent means permanent. */
108
+ readonly bannedUntil?: string;
104
109
  readonly tokensConsumed: number;
105
110
  }
106
111
  /** Creation result; plaintext is returned only when a personal key is minted. */
@@ -113,6 +118,50 @@ export interface UpdateApiKeyResponse extends ApiKeyResponse {
113
118
  readonly secret?: string;
114
119
  }
115
120
 
121
+ /**
122
+ * Ban reasons offered by the console dropdown. The first two are the operator's
123
+ * own: "Instant Ban — Suka suka Gw" for the arbitrary ban, and a template for
124
+ * suspicious traffic. The rest are generic, and an operator can still type a
125
+ * custom reason — the dropdown is a convenience, not a closed set.
126
+ */
127
+ export const API_KEY_BAN_REASONS: readonly { readonly value: string; readonly label: string }[] = [
128
+ { value: "instant_arbitrary", label: "Instant Ban — Suka suka Gw" },
129
+ { value: "traffic_suspicious", label: "Your traffic looks suspicious" },
130
+ { value: "abuse", label: "Abuse of the gateway" },
131
+ { value: "policy_violation", label: "Policy violation" },
132
+ { value: "compromised", label: "Key may be compromised" },
133
+ { value: "quota_exceeded", label: "Quota abused" },
134
+ { value: "other", label: "Other / custom" },
135
+ ] as const;
136
+
137
+ /**
138
+ * Ban durations the console offers, in whole minutes. `null` is permanent.
139
+ * Mirrored by the dashboard (`dashboard/src/lib/ban-durations.ts`) so the UI
140
+ * and the API agree on one list.
141
+ */
142
+ export const BAN_DURATION_CHOICES: readonly { readonly minutes: number | null; readonly label: string }[] = [
143
+ { minutes: 60, label: "1 hour" },
144
+ { minutes: 6 * 60, label: "6 hours" },
145
+ { minutes: 12 * 60, label: "12 hours" },
146
+ { minutes: 24 * 60, label: "1 day" },
147
+ { minutes: 7 * 24 * 60, label: "7 days" },
148
+ { minutes: null, label: "Permanent" },
149
+ ];
150
+
151
+ /** What a bare "ban this key" click uses: the shortest, most reversible choice. */
152
+ export const DEFAULT_BAN_MINUTES: number = 60;
153
+
154
+ /** Body accepted by the ban endpoint. */
155
+ export interface BanApiKeyRequest {
156
+ readonly reason: string;
157
+ readonly reasonLabel?: string;
158
+ /**
159
+ * Ban duration in whole minutes from {@link BAN_DURATION_CHOICES}; `null`
160
+ * means permanent. Absent = {@link DEFAULT_BAN_MINUTES}.
161
+ */
162
+ readonly minutes?: number | null;
163
+ }
164
+
116
165
  /** API-key route dependency boundary. */
117
166
  export interface ApiKeyConfig {
118
167
  readonly store: ApiKeyStore;
@@ -400,6 +449,9 @@ export function sanitizeApiKeyResponse(record: ApiKeyRecord): ApiKeyResponse {
400
449
  : { clientRouterDenylist: record.clientRouterDenylist }),
401
450
  createdAt: record.createdAt.toISOString(),
402
451
  ...(record.revokedAt === undefined ? {} : { revokedAt: record.revokedAt.toISOString() }),
452
+ ...(record.bannedAt === undefined ? {} : { bannedAt: record.bannedAt.toISOString() }),
453
+ ...(record.banReason === undefined ? {} : { banReason: record.banReason }),
454
+ ...(record.bannedUntil === undefined ? {} : { bannedUntil: record.bannedUntil.toISOString() }),
403
455
  tokensConsumed: record.tokensConsumed,
404
456
  };
405
457
  }