cartethyia 2.7.9 → 2.8.0

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 (375) hide show
  1. package/.env.example +3 -2
  2. package/dist/dashboard/assets/{BannedPage-BxoG1MrL.js → BannedPage-Cc0vKpMG.js} +1 -1
  3. package/dist/dashboard/assets/BannedPage-Cc0vKpMG.js.gz +0 -0
  4. package/dist/dashboard/assets/{CliToolDetailPage-ixwgKk53.js → CliToolDetailPage-Bqj2aa1T.js} +1 -1
  5. package/dist/dashboard/assets/CliToolDetailPage-Bqj2aa1T.js.gz +0 -0
  6. package/dist/dashboard/assets/{CliToolsPage-BmttNV3U.js → CliToolsPage-CXsaOAO2.js} +1 -1
  7. package/dist/dashboard/assets/CliToolsPage-CXsaOAO2.js.gz +0 -0
  8. package/dist/dashboard/assets/{CombosPage-BE3qwwfC.js → CombosPage-BeLpJ4ma.js} +1 -1
  9. package/dist/dashboard/assets/CombosPage-BeLpJ4ma.js.gz +0 -0
  10. package/dist/dashboard/assets/{ConsoleLogPage-CRZdmMIX.js → ConsoleLogPage-DnV0uZ3m.js} +1 -1
  11. package/dist/dashboard/assets/ConsoleLogPage-DnV0uZ3m.js.gz +0 -0
  12. package/dist/dashboard/assets/{CustomizationPage-Dv_FJ6Er.js → CustomizationPage-CBxAjo63.js} +1 -1
  13. package/dist/dashboard/assets/CustomizationPage-CBxAjo63.js.gz +0 -0
  14. package/dist/dashboard/assets/HealthEventsModal-B5yEE0k6.js +1 -0
  15. package/dist/dashboard/assets/HealthEventsModal-B5yEE0k6.js.gz +0 -0
  16. package/dist/dashboard/assets/{InjectSystemPromptPanel-BOoO8rpz.js → InjectSystemPromptPanel-BWFLECmz.js} +2 -2
  17. package/dist/dashboard/assets/InjectSystemPromptPanel-BWFLECmz.js.gz +0 -0
  18. package/dist/dashboard/assets/{LoginPage-ChJuNNVN.js → LoginPage-CgoPc5B9.js} +1 -1
  19. package/dist/dashboard/assets/LoginPage-CgoPc5B9.js.gz +0 -0
  20. package/dist/dashboard/assets/{ModelPicker-BZAJtPg1.js → ModelPicker-D9bFQern.js} +1 -1
  21. package/dist/dashboard/assets/ModelPicker-D9bFQern.js.gz +0 -0
  22. package/dist/dashboard/assets/{OverviewPage-BE40s3u5.js → OverviewPage-B_p4tL8k.js} +1 -1
  23. package/dist/dashboard/assets/OverviewPage-B_p4tL8k.js.gz +0 -0
  24. package/dist/dashboard/assets/ProviderDetailPage-rs5E2rxv.js +8 -0
  25. package/dist/dashboard/assets/ProviderDetailPage-rs5E2rxv.js.gz +0 -0
  26. package/dist/dashboard/assets/{ProviderIcon-CQ6bPUf_.js → ProviderIcon-D_0PiZtg.js} +1 -1
  27. package/dist/dashboard/assets/ProviderIcon-D_0PiZtg.js.gz +0 -0
  28. package/dist/dashboard/assets/ProvidersPage-CLI2bJTK.js +1 -0
  29. package/dist/dashboard/assets/ProvidersPage-CLI2bJTK.js.gz +0 -0
  30. package/dist/dashboard/assets/{ProxyPage-BJ87Pp0U.js → ProxyPage-BwNC_-Cx.js} +1 -1
  31. package/dist/dashboard/assets/ProxyPage-BwNC_-Cx.js.gz +0 -0
  32. package/dist/dashboard/assets/QuotaPage-DA-a1iSN.js +1 -0
  33. package/dist/dashboard/assets/QuotaPage-DA-a1iSN.js.gz +0 -0
  34. package/dist/dashboard/assets/{SettingsPage-C5HPWqEv.js → SettingsPage-CctTga2C.js} +1 -1
  35. package/dist/dashboard/assets/SettingsPage-CctTga2C.js.gz +0 -0
  36. package/dist/dashboard/assets/{SetupPage-CFNcATJJ.js → SetupPage-febLU4r1.js} +1 -1
  37. package/dist/dashboard/assets/SetupPage-febLU4r1.js.gz +0 -0
  38. package/dist/dashboard/assets/{StudioPage-YoD4tY5l.js → StudioPage-M6fvR5Wz.js} +1 -1
  39. package/dist/dashboard/assets/StudioPage-M6fvR5Wz.js.gz +0 -0
  40. package/dist/dashboard/assets/{UsagePage-DxjaAhb_.js → UsagePage-nyeuRqz9.js} +1 -1
  41. package/dist/dashboard/assets/UsagePage-nyeuRqz9.js.gz +0 -0
  42. package/dist/dashboard/assets/{api-keys-BTlkq_Uk.js → api-keys-BFOlvrJC.js} +1 -1
  43. package/dist/dashboard/assets/api-keys-BFOlvrJC.js.gz +0 -0
  44. package/dist/dashboard/assets/app-Dx2QgsNj.css +2 -0
  45. package/dist/dashboard/assets/app-Dx2QgsNj.css.gz +0 -0
  46. package/dist/dashboard/assets/assert-CqTLw5e7.js +1 -0
  47. package/dist/dashboard/assets/assert-CqTLw5e7.js.gz +0 -0
  48. package/dist/dashboard/assets/{badge-DdbT_7Am.js → badge-C83EqLB0.js} +1 -1
  49. package/dist/dashboard/assets/badge-C83EqLB0.js.gz +0 -0
  50. package/dist/dashboard/assets/{banner-CsGN-nbm.js → banner-D1xn7pIe.js} +1 -1
  51. package/dist/dashboard/assets/banner-D1xn7pIe.js.gz +0 -0
  52. package/dist/dashboard/assets/{card-mgZ_TLLZ.js → card-DU3Lq5Lm.js} +1 -1
  53. package/dist/dashboard/assets/card-DU3Lq5Lm.js.gz +0 -0
  54. package/dist/dashboard/assets/{cli-tools-A1-v8-03.js → cli-tools-Dw1U_drK.js} +1 -1
  55. package/dist/dashboard/assets/cli-tools-Dw1U_drK.js.gz +0 -0
  56. package/dist/dashboard/assets/{clipboard-button-CJ4Zty89.js → clipboard-button-CEL5Sg_J.js} +1 -1
  57. package/dist/dashboard/assets/clipboard-button-CEL5Sg_J.js.gz +0 -0
  58. package/dist/dashboard/assets/{dialog-B5L2jPQ_.js → dialog-BjFR5UHy.js} +1 -1
  59. package/dist/dashboard/assets/dialog-BjFR5UHy.js.gz +0 -0
  60. package/dist/dashboard/assets/{drawer-DkjUouAj.js → drawer-HvBW0AjI.js} +1 -1
  61. package/dist/dashboard/assets/drawer-HvBW0AjI.js.gz +0 -0
  62. package/dist/dashboard/assets/entry-BSu3gFlt.js +4 -0
  63. package/dist/dashboard/assets/entry-BSu3gFlt.js.gz +0 -0
  64. package/dist/dashboard/assets/{entry-B-LVLV6o.js → entry-CECVELyy.js} +1 -1
  65. package/dist/dashboard/assets/entry-CECVELyy.js.gz +0 -0
  66. package/dist/dashboard/assets/{entry-CAhdYl3B.js → entry-DYYofQ7W.js} +1 -1
  67. package/dist/dashboard/assets/entry-DYYofQ7W.js.gz +0 -0
  68. package/dist/dashboard/assets/{formatters-CrsdLVsn.js → formatters-DLW4i8zK.js} +1 -1
  69. package/dist/dashboard/assets/formatters-DLW4i8zK.js.gz +0 -0
  70. package/dist/dashboard/assets/{index-BU64Lqvp.js → index-1-fCqe64.js} +2 -2
  71. package/dist/dashboard/assets/index-1-fCqe64.js.gz +0 -0
  72. package/dist/dashboard/assets/{inline-B2aDLyyg.js → inline-DGvU2oLK.js} +1 -1
  73. package/dist/dashboard/assets/inline-DGvU2oLK.js.gz +0 -0
  74. package/dist/dashboard/assets/{input-BCVkdR9d.js → input-iNnO1X2K.js} +1 -1
  75. package/dist/dashboard/assets/input-iNnO1X2K.js.gz +0 -0
  76. package/dist/dashboard/assets/{layout-DNcAdMK-.js → layout-0q8UGypb.js} +1 -1
  77. package/dist/dashboard/assets/layout-0q8UGypb.js.gz +0 -0
  78. package/dist/dashboard/assets/{list-row-DgrIH0hw.js → list-row-C0w2bQjJ.js} +1 -1
  79. package/dist/dashboard/assets/list-row-C0w2bQjJ.js.gz +0 -0
  80. package/dist/dashboard/assets/{live-Cae01iu8.js → live-M5aJ_xCa.js} +1 -1
  81. package/dist/dashboard/assets/live-M5aJ_xCa.js.gz +0 -0
  82. package/dist/dashboard/assets/{network-CRib7H_2.js → network-BKeQGT3K.js} +1 -1
  83. package/dist/dashboard/assets/network-BKeQGT3K.js.gz +0 -0
  84. package/dist/dashboard/assets/{numbers-BKQE8Xpz.js → numbers-CivAk3da.js} +1 -1
  85. package/dist/dashboard/assets/numbers-CivAk3da.js.gz +0 -0
  86. package/dist/dashboard/assets/{pagination-ecPvG_8g.js → pagination-CpzvI5ep.js} +1 -1
  87. package/dist/dashboard/assets/pagination-CpzvI5ep.js.gz +0 -0
  88. package/dist/dashboard/assets/providers/zro.svg +3 -0
  89. package/dist/dashboard/assets/providers/zro.svg.gz +0 -0
  90. package/dist/dashboard/assets/quota-DiPD6UKH.js +1 -0
  91. package/dist/dashboard/assets/quota-DiPD6UKH.js.gz +0 -0
  92. package/dist/dashboard/assets/{routing-Ckm1UXuC.js → routing-BFQX2VBc.js} +1 -1
  93. package/dist/dashboard/assets/routing-BFQX2VBc.js.gz +0 -0
  94. package/dist/dashboard/assets/{select-ChoFWyKz.js → select-CXbck0jG.js} +2 -2
  95. package/dist/dashboard/assets/select-CXbck0jG.js.gz +0 -0
  96. package/dist/dashboard/assets/{settings-CKjFPlKW.js → settings-C_XvMHh9.js} +1 -1
  97. package/dist/dashboard/assets/settings-C_XvMHh9.js.gz +0 -0
  98. package/dist/dashboard/assets/{stack-DWCwU3fh.js → stack-DggRiCxE.js} +1 -1
  99. package/dist/dashboard/assets/stack-DggRiCxE.js.gz +0 -0
  100. package/dist/dashboard/assets/{state-Cg3VL5J_.js → state-ClFzfbYp.js} +1 -1
  101. package/dist/dashboard/assets/state-ClFzfbYp.js.gz +0 -0
  102. package/dist/dashboard/assets/{switch-Db70BMix.js → switch-CTIFxKqS.js} +1 -1
  103. package/dist/dashboard/assets/switch-CTIFxKqS.js.gz +0 -0
  104. package/dist/dashboard/assets/{useMutation-BqdE0LuM.js → useMutation-BClG3yWj.js} +1 -1
  105. package/dist/dashboard/assets/useMutation-BClG3yWj.js.gz +0 -0
  106. package/dist/dashboard/index.html +2 -2
  107. package/dist/dashboard/index.html.gz +0 -0
  108. package/dist/dashboard/precompressed-assets.json +54 -53
  109. package/migrations/full-postgres/0046_add_accounts_quota_plan_fetched_at.sql +7 -0
  110. package/migrations/lite-sqlite3/0046_add_accounts_quota_plan_fetched_at.sql +4 -0
  111. package/package.json +1 -1
  112. package/src/boot/app.ts +10 -13
  113. package/src/boot/dependencies.ts +1 -1
  114. package/src/boot/flush.ts +20 -0
  115. package/src/boot/lifecycle.ts +7 -5
  116. package/src/config/routing.ts +26 -0
  117. package/src/config/spec-metadata.ts +72 -0
  118. package/src/config/spec.ts +20 -0
  119. package/src/console/api-keys/contracts.ts +12 -1
  120. package/src/console/api-keys/routes.ts +40 -28
  121. package/src/console/auth/account-reset.ts +7 -9
  122. package/src/console/backup/contracts.ts +4 -0
  123. package/src/console/backup/export-endpoint.ts +7 -5
  124. package/src/console/cli-tools/service.ts +6 -12
  125. package/src/console/console-router-guards.ts +23 -16
  126. package/src/console/domain-registration.ts +0 -2
  127. package/src/console/inferhub/normalize.ts +24 -28
  128. package/src/console/inferhub/types.ts +3 -0
  129. package/src/console/ip-bans/routes.ts +7 -13
  130. package/src/console/observability/store.ts +0 -53
  131. package/src/console/providers/catalog/contracts.ts +2 -2
  132. package/src/console/providers/catalog/discover-constants.ts +6 -0
  133. package/src/console/providers/catalog/routes.ts +1 -0
  134. package/src/console/providers/catalog/store.ts +21 -16
  135. package/src/console/quota/account-quota-global.ts +14 -24
  136. package/src/console/quota/account-quota-shared.ts +49 -0
  137. package/src/console/quota/account-quota-tenant.ts +6 -18
  138. package/src/console/quota/quota-view.ts +19 -3
  139. package/src/console/routing/pools/operations.ts +31 -46
  140. package/src/console/shared/errors.ts +33 -6
  141. package/src/main.ts +2 -3
  142. package/src/network/pool/byte-accounting.ts +3 -2
  143. package/src/network/pool/selector.ts +182 -39
  144. package/src/network/pool-cooldown-store.ts +89 -14
  145. package/src/network/pool-health.ts +49 -61
  146. package/src/network/rate-limit-verdict.ts +1 -1
  147. package/src/network/ssrf.ts +6 -6
  148. package/src/observability/event-log.ts +7 -361
  149. package/src/observability/performance-metrics.ts +9 -0
  150. package/src/observability/runtime-metrics.ts +14 -9
  151. package/src/observability/telemetry-buffer.ts +15 -25
  152. package/src/observability/telemetry-reconciliation.ts +9 -37
  153. package/src/observability/telemetry-row.ts +125 -0
  154. package/src/persistence/db-mode.ts +4 -1
  155. package/src/persistence/dialect.ts +18 -0
  156. package/src/persistence/migrate.ts +6 -3
  157. package/src/persistence/schema.ts +8 -0
  158. package/src/persistence/ttl-cache.ts +20 -2
  159. package/src/protocol/codecs.ts +1 -1
  160. package/src/protocol/dsml.ts +34 -86
  161. package/src/protocol/markup-stream-parser.ts +195 -0
  162. package/src/protocol/reasoning-text.ts +19 -0
  163. package/src/protocol/request/chat.ts +10 -10
  164. package/src/protocol/request/gemini.ts +8 -2
  165. package/src/protocol/request/messages.ts +3 -4
  166. package/src/protocol/response/chat.ts +12 -20
  167. package/src/protocol/response/omp-tool-call-markup.ts +43 -86
  168. package/src/protocol/response/responses.ts +18 -2
  169. package/src/protocol/stop-reason.ts +23 -1
  170. package/src/protocol/think-split.ts +112 -0
  171. package/src/protocol/translation/dialect/qwen.ts +26 -58
  172. package/src/providers/adapters/compatible-adapter.ts +71 -12
  173. package/src/providers/authentication/oauth-client.ts +1 -1
  174. package/src/providers/authentication/oauth-device-flow.ts +98 -8
  175. package/src/providers/authentication/oauth-refresh-service.ts +30 -49
  176. package/src/providers/configured-provider.ts +148 -0
  177. package/src/providers/default-registry.ts +61 -36
  178. package/src/providers/discovery/probe-phases.ts +14 -4
  179. package/src/providers/discovery/probing-service.ts +15 -2
  180. package/src/providers/integrations/alibabacp/alibabacp-models.ts +1 -2
  181. package/src/providers/integrations/{dashscope-models.ts → alibabacp/dashscope-models.ts} +2 -2
  182. package/src/providers/integrations/antigravity/antigravity-protocol.ts +5 -1
  183. package/src/providers/integrations/buddy/buddy-chat-shared.ts +173 -17
  184. package/src/providers/integrations/buddy/buddy-identity.ts +19 -6
  185. package/src/providers/integrations/buddy/capabilities.ts +2 -2
  186. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +3 -0
  187. package/src/providers/integrations/buddy/codebuddy/provider.ts +4 -0
  188. package/src/providers/integrations/buddy/codebuddy/shared.ts +37 -22
  189. package/src/providers/integrations/claude/claude-quota.ts +14 -17
  190. package/src/providers/integrations/cline/cline.ts +2 -2
  191. package/src/providers/integrations/commandcode/quota.ts +22 -28
  192. package/src/providers/integrations/cursor/cursor-auth.ts +0 -8
  193. package/src/providers/integrations/cursor/cursor-exec.ts +9 -2
  194. package/src/providers/integrations/cursor/cursor-request.ts +26 -12
  195. package/src/providers/integrations/cursor/cursor-shared.ts +7 -4
  196. package/src/providers/integrations/cursor/cursor.ts +89 -40
  197. package/src/providers/integrations/cursor/protocol/messages.ts +22 -29
  198. package/src/providers/integrations/cursor/protocol/protobuf-schema.ts +37 -0
  199. package/src/providers/integrations/devin/protocol/devin-proto.ts +1 -27
  200. package/src/providers/integrations/factory/factory-oauth.ts +17 -44
  201. package/src/providers/integrations/freebuff/assets/official-system-manager.txt +88 -0
  202. package/src/providers/integrations/freebuff/assets/official-system-worker.txt +57 -0
  203. package/src/providers/integrations/freebuff/assets/official-tools.json +1070 -0
  204. package/src/providers/integrations/freebuff/freebuff-catalog.ts +120 -0
  205. package/src/providers/integrations/freebuff/freebuff-constants.ts +1233 -0
  206. package/src/providers/integrations/freebuff/freebuff-discovery.ts +108 -0
  207. package/src/providers/integrations/freebuff/freebuff-oauth.ts +240 -0
  208. package/src/providers/integrations/freebuff/freebuff-quota.ts +260 -0
  209. package/src/providers/integrations/freebuff/freebuff-virtual.ts +89 -0
  210. package/src/providers/integrations/freebuff/freebuff.ts +682 -0
  211. package/src/providers/integrations/grok/grok-oauth.ts +48 -45
  212. package/src/providers/integrations/grok/grok-ratelimit-store.ts +10 -17
  213. package/src/providers/integrations/grok/grok-turn-index.ts +11 -16
  214. package/src/providers/integrations/{hermes.ts → hermes/hermes.ts} +9 -9
  215. package/src/providers/integrations/hermes/index.ts +2 -0
  216. package/src/providers/integrations/kiro/kiro-catalog.ts +17 -1
  217. package/src/providers/integrations/kiro/kiro-discovery.ts +10 -12
  218. package/src/providers/integrations/kiro/kiro-quota.ts +8 -4
  219. package/src/providers/integrations/kiro/kiro.ts +15 -19
  220. package/src/providers/integrations/ollamacloud/index.ts +2 -0
  221. package/src/providers/integrations/{ollamacloud-models.ts → ollamacloud/models.ts} +2 -2
  222. package/src/providers/integrations/{ollamacloud-quota.ts → ollamacloud/quota.ts} +3 -3
  223. package/src/providers/integrations/opencode/opencode-go-quota.ts +2 -8
  224. package/src/providers/integrations/perch/perch-credential.ts +25 -0
  225. package/src/providers/integrations/perch/perch-protocol.ts +0 -3
  226. package/src/providers/integrations/perch/perch-quota.ts +2 -16
  227. package/src/providers/integrations/perch/perch.ts +3 -13
  228. package/src/providers/integrations/qoder/claim-qoder-credits.ts +18 -59
  229. package/src/providers/integrations/qoder/qoder-adapter.ts +1 -12
  230. package/src/providers/integrations/qoder/qoder-auth.ts +101 -29
  231. package/src/providers/integrations/qoder/qoder-catalog.ts +20 -5
  232. package/src/providers/integrations/qoder/qoder-chat.ts +7 -21
  233. package/src/providers/integrations/qoder/qoder-checkin.ts +4 -4
  234. package/src/providers/integrations/qoder/qoder-grpc.ts +0 -1
  235. package/src/providers/integrations/qoder/qoder-ide.ts +0 -107
  236. package/src/providers/integrations/qoder/qoder.ts +0 -1
  237. package/src/providers/integrations/trae/trae-protocol.ts +9 -5
  238. package/src/providers/integrations/trae/trae-quota.ts +6 -28
  239. package/src/providers/integrations/trae/trae-realm.ts +38 -0
  240. package/src/providers/integrations/trae/trae.ts +7 -24
  241. package/src/providers/integrations/zcode-start-plan/provider.ts +226 -23
  242. package/src/providers/integrations/zcode-start-plan/quota.ts +19 -2
  243. package/src/providers/integrations/zro/zro.ts +204 -0
  244. package/src/providers/operations/account-error-classification.ts +199 -307
  245. package/src/providers/operations/account-health-service.ts +82 -1
  246. package/src/providers/operations/account-refresh-status.ts +22 -37
  247. package/src/providers/operations/account-tier.ts +15 -16
  248. package/src/providers/operations/client-version-resolver.ts +1 -1
  249. package/src/providers/operations/client-versions.ts +5 -51
  250. package/src/providers/operations/cooldown-rules.ts +11 -16
  251. package/src/providers/operations/{model-catalog-cache.ts → provider-caches.ts} +68 -16
  252. package/src/providers/operations/provider-catalog-service.ts +1 -1
  253. package/src/providers/operations/provider-credential-service.ts +102 -32
  254. package/src/providers/operations/provider-rules/github.ts +48 -0
  255. package/src/providers/operations/provider-rules/grok.ts +33 -0
  256. package/src/providers/operations/provider-rules/inception.ts +49 -0
  257. package/src/providers/operations/provider-rules/index.ts +32 -0
  258. package/src/providers/operations/provider-rules/perch.ts +37 -0
  259. package/src/providers/operations/provider-rules/qoder.ts +70 -0
  260. package/src/providers/operations/provider-rules/trae.ts +81 -0
  261. package/src/providers/provider-contracts.ts +4 -2
  262. package/src/providers/{provider-credential-resolver.ts → provider-credentials.ts} +40 -3
  263. package/src/providers/provider-metadata.ts +40 -10
  264. package/src/providers/provider-probe.ts +15 -1
  265. package/src/providers/provider-registry.ts +42 -33
  266. package/src/providers/quota/quota-limit-windows.ts +16 -5
  267. package/src/providers/quota/quota-support.ts +1 -1
  268. package/src/providers/quota/quota-window-parser.ts +90 -18
  269. package/src/providers/quota/refresh.ts +75 -18
  270. package/src/security/api-key-auth-cache.ts +1 -1
  271. package/src/security/api-key-auth.ts +6 -2
  272. package/src/transport/dispatch/attempt-finalize.ts +14 -13
  273. package/src/transport/dispatch/attempt-loop.ts +71 -17
  274. package/src/transport/dispatch/generation.ts +2 -8
  275. package/src/transport/dispatch/native-shared.ts +25 -0
  276. package/src/transport/dispatch/proxy-request.ts +8 -46
  277. package/src/transport/dispatch/responses-compact.ts +2 -8
  278. package/src/transport/dispatch/streaming/finalize.ts +9 -6
  279. package/src/transport/dispatch/streaming/stream.ts +8 -36
  280. package/src/transport/dispatch/systemone.ts +2 -8
  281. package/src/transport/dispatch/token-count.ts +2 -6
  282. package/src/transport/dispatch/upstream.ts +63 -0
  283. package/src/transport/drain-endpoint.ts +31 -4
  284. package/src/transport/failure-policy.ts +16 -30
  285. package/src/transport/guards/body-policy.ts +7 -1
  286. package/src/transport/guards/error-lifecycle.ts +13 -1
  287. package/src/transport/guards/request-context.ts +22 -14
  288. package/src/transport/request/preparer.ts +81 -71
  289. package/src/transport/routing/bounded-lru.ts +14 -0
  290. package/src/transport/routing/drain-reserve.ts +129 -0
  291. package/src/transport/routing/plan-gate.ts +7 -1
  292. package/src/transport/routing/round-robin.ts +60 -0
  293. package/src/transport/routing/route-catalog.ts +123 -111
  294. package/src/transport/routing/route-model.ts +51 -9
  295. package/src/transport/routing/router.ts +393 -32
  296. package/src/transport/surface/adapters.ts +11 -2
  297. package/src/transport/surface/chat/adapter.ts +2 -2
  298. package/src/transport/surface/chat/encode.ts +47 -4
  299. package/src/transport/surface/chat/parse.ts +2 -12
  300. package/src/transport/surface/completion.ts +44 -33
  301. package/src/transport/surface/content-parts.ts +21 -0
  302. package/src/transport/surface/messages/adapter.ts +7 -1
  303. package/src/transport/surface/messages/encode.ts +11 -3
  304. package/src/transport/surface/messages/parse.ts +2 -1
  305. package/src/transport/surface/messages/stream.ts +33 -17
  306. package/src/transport/surface/responses/encode.ts +22 -5
  307. package/src/transport/surface/responses/parse.ts +3 -11
  308. package/dist/dashboard/assets/BannedPage-BxoG1MrL.js.gz +0 -0
  309. package/dist/dashboard/assets/CliToolDetailPage-ixwgKk53.js.gz +0 -0
  310. package/dist/dashboard/assets/CliToolsPage-BmttNV3U.js.gz +0 -0
  311. package/dist/dashboard/assets/CombosPage-BE3qwwfC.js.gz +0 -0
  312. package/dist/dashboard/assets/ConsoleLogPage-CRZdmMIX.js.gz +0 -0
  313. package/dist/dashboard/assets/CustomizationPage-Dv_FJ6Er.js.gz +0 -0
  314. package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js +0 -1
  315. package/dist/dashboard/assets/HealthEventsModal-CoVrw2sM.js.gz +0 -0
  316. package/dist/dashboard/assets/InjectSystemPromptPanel-BOoO8rpz.js.gz +0 -0
  317. package/dist/dashboard/assets/LoginPage-ChJuNNVN.js.gz +0 -0
  318. package/dist/dashboard/assets/ModelPicker-BZAJtPg1.js.gz +0 -0
  319. package/dist/dashboard/assets/OverviewPage-BE40s3u5.js.gz +0 -0
  320. package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js +0 -8
  321. package/dist/dashboard/assets/ProviderDetailPage-sfLoQtPV.js.gz +0 -0
  322. package/dist/dashboard/assets/ProviderIcon-CQ6bPUf_.js.gz +0 -0
  323. package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js +0 -1
  324. package/dist/dashboard/assets/ProvidersPage-DcepmPWo.js.gz +0 -0
  325. package/dist/dashboard/assets/ProxyPage-BJ87Pp0U.js.gz +0 -0
  326. package/dist/dashboard/assets/QuotaPage-gkSyRAKa.js +0 -1
  327. package/dist/dashboard/assets/QuotaPage-gkSyRAKa.js.gz +0 -0
  328. package/dist/dashboard/assets/SettingsPage-C5HPWqEv.js.gz +0 -0
  329. package/dist/dashboard/assets/SetupPage-CFNcATJJ.js.gz +0 -0
  330. package/dist/dashboard/assets/StudioPage-YoD4tY5l.js.gz +0 -0
  331. package/dist/dashboard/assets/UsagePage-DxjaAhb_.js.gz +0 -0
  332. package/dist/dashboard/assets/api-keys-BTlkq_Uk.js.gz +0 -0
  333. package/dist/dashboard/assets/app-BX2nnKqs.css +0 -2
  334. package/dist/dashboard/assets/app-BX2nnKqs.css.gz +0 -0
  335. package/dist/dashboard/assets/assert-CndthXFQ.js +0 -1
  336. package/dist/dashboard/assets/assert-CndthXFQ.js.gz +0 -0
  337. package/dist/dashboard/assets/badge-DdbT_7Am.js.gz +0 -0
  338. package/dist/dashboard/assets/banner-CsGN-nbm.js.gz +0 -0
  339. package/dist/dashboard/assets/card-mgZ_TLLZ.js.gz +0 -0
  340. package/dist/dashboard/assets/cli-tools-A1-v8-03.js.gz +0 -0
  341. package/dist/dashboard/assets/clipboard-button-CJ4Zty89.js.gz +0 -0
  342. package/dist/dashboard/assets/dialog-B5L2jPQ_.js.gz +0 -0
  343. package/dist/dashboard/assets/drawer-DkjUouAj.js.gz +0 -0
  344. package/dist/dashboard/assets/entry-B-LVLV6o.js.gz +0 -0
  345. package/dist/dashboard/assets/entry-CAhdYl3B.js.gz +0 -0
  346. package/dist/dashboard/assets/entry-D2xD0Tmd.js +0 -4
  347. package/dist/dashboard/assets/entry-D2xD0Tmd.js.gz +0 -0
  348. package/dist/dashboard/assets/formatters-CrsdLVsn.js.gz +0 -0
  349. package/dist/dashboard/assets/index-BU64Lqvp.js.gz +0 -0
  350. package/dist/dashboard/assets/inline-B2aDLyyg.js.gz +0 -0
  351. package/dist/dashboard/assets/input-BCVkdR9d.js.gz +0 -0
  352. package/dist/dashboard/assets/layout-DNcAdMK-.js.gz +0 -0
  353. package/dist/dashboard/assets/list-row-DgrIH0hw.js.gz +0 -0
  354. package/dist/dashboard/assets/live-Cae01iu8.js.gz +0 -0
  355. package/dist/dashboard/assets/network-CRib7H_2.js.gz +0 -0
  356. package/dist/dashboard/assets/numbers-BKQE8Xpz.js.gz +0 -0
  357. package/dist/dashboard/assets/pagination-ecPvG_8g.js.gz +0 -0
  358. package/dist/dashboard/assets/quota-DJ1QlAOq.js +0 -1
  359. package/dist/dashboard/assets/quota-DJ1QlAOq.js.gz +0 -0
  360. package/dist/dashboard/assets/routing-Ckm1UXuC.js.gz +0 -0
  361. package/dist/dashboard/assets/select-ChoFWyKz.js.gz +0 -0
  362. package/dist/dashboard/assets/settings-CKjFPlKW.js.gz +0 -0
  363. package/dist/dashboard/assets/stack-DWCwU3fh.js.gz +0 -0
  364. package/dist/dashboard/assets/state-Cg3VL5J_.js.gz +0 -0
  365. package/dist/dashboard/assets/switch-Db70BMix.js.gz +0 -0
  366. package/dist/dashboard/assets/useMutation-BqdE0LuM.js.gz +0 -0
  367. package/src/providers/bundled-discovery-providers.ts +0 -98
  368. package/src/providers/configured-openai-providers.ts +0 -120
  369. package/src/providers/operations/model-discovery-cache.ts +0 -34
  370. package/src/providers/operations/probe-fetch.ts +0 -15
  371. package/src/providers/operations/provider-version-cache.ts +0 -30
  372. package/src/providers/provider-capabilities.ts +0 -33
  373. package/src/providers/provider-credential-envelope.ts +0 -43
  374. /package/dist/dashboard/assets/{app-B7kIWFm5.js → app-DIXLjojg.js} +0 -0
  375. /package/dist/dashboard/assets/{app-B7kIWFm5.js.gz → app-DIXLjojg.js.gz} +0 -0
@@ -1,13 +1,14 @@
1
1
  /** xAI Grok Build device-code OAuth client. */
2
- import { devicePollBackoff, nonEmptyTrimmedString, parseDeviceAuthStart, postFormTokenRequest, readJsonResponse } from "../../authentication/oauth-flow-store";
2
+ import { devicePollBackoff, nonEmptyTrimmedString, parseDeviceAuthStart, postFormTokenRequest, readJsonResponse, record } from "../../authentication/oauth-flow-store";
3
3
  import type {
4
4
  OAuthDeviceFlowContext,
5
5
  OAuthDevicePollResult,
6
6
  OAuthDeviceStartResult,
7
+ OAuthExchangeResult,
7
8
  } from "../../authentication/oauth-flow-store";
8
9
  import type { OAuthTokenRefreshResult } from "../../authentication/oauth-refresh-service";
9
10
  import { OAuthDeviceFlow } from "../../authentication/oauth-device-flow";
10
- import type { FetchLike } from "../../authentication/oauth-client";
11
+ import type { FetchLike, NormalizedTokenResponse } from "../../authentication/oauth-client";
11
12
  import { XAI_AUTH_SERVER } from "../xai/xai-oauth";
12
13
 
13
14
  /**
@@ -178,56 +179,58 @@ export class GrokOAuthClient extends OAuthDeviceFlow {
178
179
  }
179
180
 
180
181
  override async pollDeviceAuth(deviceAuthId: string, _context?: OAuthDeviceFlowContext): Promise<OAuthDevicePollResult> {
181
- const body = new URLSearchParams({
182
- grant_type: "urn:ietf:params:oauth:grant-type:device_code",
183
- device_code: deviceAuthId,
184
- client_id: GROK_CLIENT_ID,
185
- });
186
- const response = await this.fetchFn(GROK_TOKEN_URL, {
187
- method: "POST",
188
- headers: authHeaders(),
189
- body,
182
+ return this.pollGenericDeviceAuth(deviceAuthId, {
183
+ tokenUrl: GROK_TOKEN_URL,
184
+ deviceAuthUrl: GROK_DEVICE_URL,
185
+ clientId: GROK_CLIENT_ID,
186
+ formHeaders: authHeaders(),
190
187
  });
191
- const raw = await response.text();
192
- let payload: TokenPayload;
193
- try {
194
- payload = JSON.parse(raw) as TokenPayload;
195
- } catch {
196
- return { status: "failed", reason: "grok token endpoint returned invalid JSON" };
197
- }
198
- const error = nonEmptyTrimmedString(payload.error);
199
- const verdict = devicePollBackoff(error, payload.interval);
200
- if (verdict !== undefined) return verdict;
201
- if (!response.ok) {
202
- return { status: "failed", reason: nonEmptyTrimmedString(payload.error_description) ?? error ?? `token polling failed (${response.status})` };
203
- }
188
+ }
189
+
190
+ protected override unparsablePollReason(_status: number): string {
191
+ return "grok token endpoint returned invalid JSON";
192
+ }
193
+
194
+ protected override preVerdict(payload: unknown): OAuthDevicePollResult | undefined {
195
+ const root = record(payload) ?? {};
196
+ // Grok votes on the `error` field before the status: a 200 body can still
197
+ // state the poll state, and a pending/slow_down answer is honored wherever
198
+ // it appears on the wire.
199
+ return devicePollBackoff(nonEmptyTrimmedString(root.error), root.interval);
200
+ }
201
+
202
+ protected override pollFailureReason(
203
+ status: number,
204
+ errorKey: string | undefined,
205
+ errorDescription: string | undefined,
206
+ ): string {
207
+ // Grok's wire prefers the human-readable description over the error code,
208
+ // and falls back to the generic phrasing only when both are absent.
209
+ return nonEmptyTrimmedString(errorDescription) ?? nonEmptyTrimmedString(errorKey) ?? `token polling failed (${status})`;
210
+ }
211
+
212
+ protected override missingTokenReason(errorKey: string | undefined, errorDescription: string | undefined): string {
204
213
  // A 2xx is not by itself a completed authorization: the endpoint can answer
205
214
  // 200 with an `error` field and no token. Returning `failed` rather than
206
215
  // letting the shared parser throw keeps the verdict inside the poll
207
216
  // contract — the dashboard's poll error path stops polling but shows no
208
217
  // reason, so a throw would look like a spinner that never ends.
209
- if (nonEmptyTrimmedString(payload.access_token) === undefined) {
210
- return {
211
- status: "failed",
212
- reason:
213
- nonEmptyTrimmedString(payload.error_description) ??
214
- error ??
215
- "grok token response omitted access_token",
216
- };
217
- }
218
- const result = this.parseTokenResponse(payload);
219
- const identity = await fetchGrokIdentity(result.access, this.fetchFn);
218
+ return nonEmptyTrimmedString(errorDescription) ?? nonEmptyTrimmedString(errorKey) ?? "grok token response omitted access_token";
219
+ }
220
+
221
+ protected override async afterDeviceTokenExchange(
222
+ _payload: unknown,
223
+ normalized: NormalizedTokenResponse,
224
+ ): Promise<OAuthExchangeResult> {
225
+ const identity = await fetchGrokIdentity(normalized.access, this.fetchFn);
220
226
  const authState = grokAuthState(identity);
221
- return {
222
- status: "complete",
223
- result: this.toExchangeResult({
224
- ...result,
225
- ...(identity.label === undefined ? {} : { accountLabel: identity.label }),
226
- // Omitted when the proxy reported nothing, which leaves any stored
227
- // state alone rather than blanking it.
228
- ...(authState === undefined ? {} : { auth_state: authState }),
229
- }),
230
- };
227
+ return this.toExchangeResult({
228
+ ...normalized,
229
+ ...(identity.label === undefined ? {} : { accountLabel: identity.label }),
230
+ // Omitted when the proxy reported nothing, which leaves any stored
231
+ // state alone rather than blanking it.
232
+ ...(authState === undefined ? {} : { auth_state: authState }),
233
+ });
231
234
  }
232
235
 
233
236
  override async refresh(refreshToken: string, signal?: AbortSignal): Promise<OAuthTokenRefreshResult> {
@@ -12,6 +12,7 @@
12
12
  // Keyed by account id: two Grok accounts carry different buckets, and a reading
13
13
  // filed against the wrong one is worse than no reading at all.
14
14
 
15
+ import { TtlCache } from "../../../persistence/ttl-cache";
15
16
  import type { ProviderQuotaWindow } from "../../quota/quota-contracts";
16
17
  import { grokHeaderRequestsWindow, grokHeaderWindow, type GrokRateLimitReading } from "./grok-ratelimit";
17
18
 
@@ -27,39 +28,31 @@ const SNAPSHOT_TTL_MS = 5 * 60 * 1000;
27
28
 
28
29
  interface Snapshot {
29
30
  readonly windows: readonly ProviderQuotaWindow[];
30
- readonly capturedAt: number;
31
31
  }
32
32
 
33
- const snapshots = new Map<string, Snapshot>();
33
+ // Bounded FIFO cache keyed by account id; the TTL and `maxEntries` cap are
34
+ // enforced by the cache itself (oldest-first eviction past 4096 entries).
35
+ const snapshots = new TtlCache<Snapshot>({
36
+ ttlMs: SNAPSHOT_TTL_MS,
37
+ maxEntries: 4096,
38
+ });
34
39
 
35
40
  /** Files a reading captured from a real dispatch against one account. */
36
41
  export function rememberGrokRateLimit(
37
42
  accountId: string,
38
43
  reading: GrokRateLimitReading,
39
- now = Date.now(),
40
44
  ): void {
41
45
  const windows = [grokHeaderWindow(reading), grokHeaderRequestsWindow(reading)].filter(
42
46
  (value): value is ProviderQuotaWindow => value !== null,
43
47
  );
44
48
  if (windows.length === 0) return;
45
- snapshots.set(accountId, { windows, capturedAt: now });
46
- if (snapshots.size > 4096) {
47
- // Bounded: an unbounded map keyed by account is a leak dressed as a cache.
48
- for (const [key, value] of snapshots) {
49
- if (now - value.capturedAt > SNAPSHOT_TTL_MS) snapshots.delete(key);
50
- }
51
- }
49
+ snapshots.set(accountId, { windows });
52
50
  }
53
51
 
54
52
  /** The stored window for one account, or `null` when none is fresh enough. */
55
- export function storedGrokRateLimit(accountId: string, now = Date.now()): readonly ProviderQuotaWindow[] | null {
53
+ export function storedGrokRateLimit(accountId: string): readonly ProviderQuotaWindow[] | null {
56
54
  const snapshot = snapshots.get(accountId);
57
- if (!snapshot) return null;
58
- if (now - snapshot.capturedAt > SNAPSHOT_TTL_MS) {
59
- snapshots.delete(accountId);
60
- return null;
61
- }
62
- return snapshot.windows;
55
+ return snapshot?.windows ?? null;
63
56
  }
64
57
 
65
58
  /** Test seam. */
@@ -13,19 +13,25 @@
13
13
  */
14
14
 
15
15
  import type { CanonicalRequest } from "../../../protocol/canonical-model";
16
+ import { TtlCache } from "../../../persistence/ttl-cache";
16
17
 
17
18
  /** Idle window after which a session's remembered index is discarded. */
18
19
  const SESSION_TTL_MS = 30 * 60_000;
19
20
 
20
- /** Bound on remembered sessions; oldest insertion is evicted past this. */
21
+ /** Bound on remembered sessions; least-recently-used is evicted past this. */
21
22
  const MAX_SESSIONS = 5_000;
22
23
 
23
24
  interface TurnEntry {
24
25
  readonly turn: number;
25
- lastUsedAt: number;
26
26
  }
27
27
 
28
- const turns = new Map<string, TurnEntry>();
28
+ // Bounded, LRU-ordered: a session re-inserts on every use so the newest
29
+ // session is the last evicted, exactly as the previous delete-then-set Map.
30
+ const turns = new TtlCache<TurnEntry>({
31
+ ttlMs: SESSION_TTL_MS,
32
+ maxEntries: MAX_SESSIONS,
33
+ lru: true,
34
+ });
29
35
  const requestTurns = new WeakMap<object, number>();
30
36
 
31
37
  /** User turns in the request, floored at 1 (a turn is never index 0). */
@@ -58,25 +64,14 @@ export function resolveGrokTurnIndex(
58
64
  }
59
65
  if (sessionId === undefined || sessionId.length === 0) return fromInput;
60
66
 
61
- const now = Date.now();
62
- const previous = turns.get(sessionId);
63
- const remembered =
64
- previous !== undefined && now - previous.lastUsedAt <= SESSION_TTL_MS
65
- ? previous.turn
66
- : undefined;
67
+ const remembered = turns.get(sessionId)?.turn;
67
68
 
68
69
  // A full-history client already reports the true index; a delta-style client
69
70
  // reports 1 every time, so the stored index is advanced by one for it.
70
71
  const turn = remembered === undefined ? fromInput : Math.max(fromInput, remembered + 1);
71
72
 
72
73
  // Re-insert so the entry is newest for eviction order.
73
- turns.delete(sessionId);
74
- if (turns.size >= MAX_SESSIONS) {
75
- const oldest = turns.keys().next();
76
- if (!oldest.done) turns.delete(oldest.value);
77
- }
78
- turns.set(sessionId, { turn, lastUsedAt: now });
74
+ turns.set(sessionId, { turn });
79
75
  if (requestKey !== undefined) requestTurns.set(requestKey, turn);
80
76
  return turn;
81
77
  }
82
-
@@ -1,10 +1,10 @@
1
- import type { CanonicalRequest } from "../../protocol/canonical-model";
2
- import type { ProviderDispatchTarget } from "../provider-contracts";
3
- import type { ApiKeyProviderSpec } from "../configured-provider";
4
- import { isRecord } from "../../protocol/codecs";
5
- import { boundedUpstreamArray, boundedUpstreamNumber, sanitizeUpstreamLabel } from "../provider-sanitize";
6
- import { getHermesVersion } from "../operations/client-versions";
7
- import { clineReasoningReplayPrepares } from "./buddy/buddy-chat-shared";
1
+ import type { CanonicalRequest } from "../../../protocol/canonical-model";
2
+ import type { ProviderDispatchTarget } from "../../provider-contracts";
3
+ import type { ApiKeyProviderSpec } from "../../configured-provider";
4
+ import { isRecord } from "../../../protocol/codecs";
5
+ import { boundedUpstreamArray, boundedUpstreamNumber, sanitizeUpstreamLabel } from "../../provider-sanitize";
6
+ import { getHermesVersion } from "../../operations/client-versions";
7
+ import { clineReasoningReplayPrepares } from "../buddy/buddy-chat-shared";
8
8
 
9
9
  const HERMES_PROVIDER_ID = "hermes" as const;
10
10
 
@@ -57,8 +57,8 @@ function injectHermesTags(
57
57
 
58
58
 
59
59
 
60
- import { defineModel } from "../provider-model-definition";
61
- import type { ModelDefinition } from "../provider-model-definition";
60
+ import { defineModel } from "../../provider-model-definition";
61
+ import type { ModelDefinition } from "../../provider-model-definition";
62
62
 
63
63
  /**
64
64
  * Offline fallback: the free rows as the live catalog last reported them.
@@ -0,0 +1,2 @@
1
+ export * from "./hermes";
2
+ export * from "./hermes-oauth";
@@ -15,6 +15,22 @@ export const KIRO_GENERATE_PATH = "/generateAssistantResponse" as const;
15
15
  /** AWS region shape, checked before a region is interpolated into a URL. */
16
16
  export const AWS_REGION_PATTERN = /^[a-z]{2}-[a-z]+-\d{1,2}$/;
17
17
 
18
+ /**
19
+ * Maps an auth method onto the `TokenType` header the 1.4.62 client sends.
20
+ *
21
+ * Observed map (`qje`): `api_key → API_KEY`, `external_idp → EXTERNAL_IDP`,
22
+ * `idc → SSO_OIDC`, `machine_token → KIRO_MACHINE_TOKEN`. Google/GitHub/
23
+ * Builder-ID sessions send no `TokenType` header. Every surface (dispatch,
24
+ * usage, discovery) must classify the same account identically or the upstream
25
+ * sees one credential as different token types across surfaces.
26
+ */
27
+ export function kiroTokenTypeHeader(authMethod: string | undefined): string | undefined {
28
+ if (authMethod === "api_key") return "API_KEY";
29
+ if (authMethod === "external_idp") return "EXTERNAL_IDP";
30
+ if (authMethod === "idc") return "SSO_OIDC";
31
+ return undefined;
32
+ }
33
+
18
34
  /** Fallback region when no (or an invalid) region is configured. */
19
35
  export const KIRO_DEFAULT_REGION = "us-east-1" as const;
20
36
 
@@ -37,7 +53,7 @@ export const KIRO_MANAGEMENT_HOST_TEMPLATE = "https://management.{region}.kiro.d
37
53
  * The upstream has no `minimal` or `max` tier on either schema, so the ladder
38
54
  * stops at `xhigh` and the builder maps the canonical extremes onto it.
39
55
  */
40
- const KIRO_EFFORTS = ["low", "medium", "high", "xhigh"] as const;
56
+ export const KIRO_EFFORTS = ["low", "medium", "high", "xhigh"] as const;
41
57
 
42
58
  /**
43
59
  * One Kiro model row.
@@ -22,14 +22,19 @@ import {
22
22
  } from "../../operations/client-versions";
23
23
  import { machineIdForAuthState } from "./kiro-machine-id";
24
24
  import { resolveKiroProfileArn } from "./kiro-profile";
25
- import { AWS_REGION_PATTERN, KIRO_DEFAULT_REGION, KIRO_GENERATE_PATH, KIRO_MODELS, KIRO_MANAGEMENT_HOST_TEMPLATE } from "./kiro-catalog";
25
+ import {
26
+ AWS_REGION_PATTERN,
27
+ KIRO_DEFAULT_REGION,
28
+ KIRO_EFFORTS,
29
+ KIRO_GENERATE_PATH,
30
+ KIRO_MODELS,
31
+ KIRO_MANAGEMENT_HOST_TEMPLATE,
32
+ kiroTokenTypeHeader,
33
+ } from "./kiro-catalog";
26
34
 
27
35
  /** Longest model list this will read; the upstream sends a few dozen at most. */
28
36
  const MAX_DISCOVERED_MODELS = 500;
29
37
 
30
- /** Effort tiers the models that accept an effort field advertise. */
31
- const KIRO_EFFORTS = ["low", "medium", "high", "xhigh"] as const;
32
-
33
38
  /** The management service target used in `x-amz-target`. */
34
39
  const KIRO_MANAGEMENT_TARGET = "KiroControlPlaneBearerService.ListAvailableModels" as const;
35
40
 
@@ -105,14 +110,7 @@ export async function fetchKiroModels(
105
110
  // `TokenType`; social/Builder-ID sessions send none. Mirror the client's
106
111
  // `qje` map and the probe verified against the live service.
107
112
  const authMethod = options.authState?.["authMethod"];
108
- const tokenType =
109
- authMethod === "api_key"
110
- ? "API_KEY"
111
- : authMethod === "external_idp"
112
- ? "EXTERNAL_IDP"
113
- : authMethod === "idc"
114
- ? "SSO_OIDC"
115
- : undefined;
113
+ const tokenType = kiroTokenTypeHeader(typeof authMethod === "string" ? authMethod : undefined);
116
114
  if (tokenType !== undefined) headers["TokenType"] = tokenType;
117
115
  response = await fetcher(`${host}/List-Available-Models?${params.toString()}`, {
118
116
  headers,
@@ -34,7 +34,12 @@ import {
34
34
  } from "../../operations/client-versions";
35
35
  import { machineIdForAuthState } from "./kiro-machine-id";
36
36
  import { resolveKiroProfileArn } from "./kiro-profile";
37
- import { AWS_REGION_PATTERN, KIRO_DEFAULT_REGION, KIRO_MANAGEMENT_HOST_TEMPLATE } from "./kiro-catalog";
37
+ import {
38
+ AWS_REGION_PATTERN,
39
+ KIRO_DEFAULT_REGION,
40
+ KIRO_MANAGEMENT_HOST_TEMPLATE,
41
+ kiroTokenTypeHeader,
42
+ } from "./kiro-catalog";
38
43
 
39
44
  /**
40
45
  * The fetcher shape this module needs: a plain callable. `FetchLike` from the
@@ -82,9 +87,8 @@ function usageHeaders(credential: string, auth: KiroQuotaAuth): Record<string, s
82
87
  "user-agent": buildKiroRuntimeUserAgent(version, auth.machineId),
83
88
  "x-kiro-machineid": auth.machineId,
84
89
  };
85
- if (auth.authMethod === "api_key") headers["TokenType"] = "API_KEY";
86
- else if (auth.authMethod === "external_idp") headers["TokenType"] = "EXTERNAL_IDP";
87
- else if (auth.authMethod === "idc") headers["TokenType"] = "SSO_OIDC";
90
+ const tokenType = kiroTokenTypeHeader(auth.authMethod);
91
+ if (tokenType !== undefined) headers["TokenType"] = tokenType;
88
92
  return headers;
89
93
  }
90
94
 
@@ -59,6 +59,7 @@ import {
59
59
  kiroContextWindow,
60
60
  KIRO_GENERATE_PATH,
61
61
  KIRO_RUNTIME_HOST_TEMPLATE,
62
+ kiroTokenTypeHeader,
62
63
  } from "./kiro-catalog";
63
64
  import { resolveInboundSessionId } from "../../operations/session-resolution";
64
65
  import { KiroStreamDecoder, isKiroTruncationReason, type KiroUsage } from "./kiro-stream";
@@ -180,19 +181,6 @@ export function kiroEndpoint(authState: KiroAuthState | undefined): string {
180
181
  */
181
182
  export const KIRO_RUNTIME_AWS_JSON_TARGET = "KiroRuntimeService.GenerateAssistantResponse" as const;
182
183
 
183
- /**
184
- * Maps an auth method onto the `TokenType` header the 1.4.62 client sends.
185
- *
186
- * Observed map (`qje`): `api_key → API_KEY`, `external_idp → EXTERNAL_IDP`,
187
- * `idc → SSO_OIDC`, `machine_token → KIRO_MACHINE_TOKEN`. Google/GitHub/
188
- * Builder-ID sessions send no `TokenType` header.
189
- */
190
- const KIRO_TOKEN_TYPE: Readonly<Record<string, string>> = {
191
- api_key: "API_KEY",
192
- external_idp: "EXTERNAL_IDP",
193
- idc: "SSO_OIDC",
194
- };
195
-
196
184
  /**
197
185
  * Builds the request headers.
198
186
  *
@@ -230,7 +218,7 @@ export function kiroHeaders(
230
218
  "x-amz-user-agent": buildKiroAmzUserAgent(version, machineId),
231
219
  authorization: `Bearer ${secret}`,
232
220
  };
233
- const tokenType = authMethod === undefined ? undefined : KIRO_TOKEN_TYPE[authMethod];
221
+ const tokenType = kiroTokenTypeHeader(authMethod);
234
222
  if (tokenType !== undefined) {
235
223
  headers["TokenType"] = tokenType;
236
224
  }
@@ -377,9 +365,17 @@ class KiroAdapter implements ProviderAdapter {
377
365
  effortPath !== undefined
378
366
  ? undefined
379
367
  : (request.reasoning?.budget_tokens ?? kiroThinkingBudgetForEffort(requestedEffort));
368
+ // One resolution per dispatch: the resolver mints a fresh UUID only when
369
+ // the request carries no session, conversation id, or derivable opening
370
+ // turn, so resolving twice here would give `conversationId` and
371
+ // `rootConversationId` two different UUIDs — contradicting the client rule
372
+ // (`rootConversationId ?? conversationId`). The same id feeds the session
373
+ // cache estimator at stream end, so its key matches what the request
374
+ // presented upstream.
375
+ const conversationId = resolveKiroConversationId(request, context);
380
376
  const built = buildKiroWireRequest(request, {
381
- conversationId: resolveKiroConversationId(request, context),
382
- rootConversationId: resolveKiroConversationId(request, context),
377
+ conversationId,
378
+ rootConversationId: conversationId,
383
379
  modelId,
384
380
  profileArn: resolveProfileArn(context),
385
381
  ...(effortPath === undefined || effort === undefined ? {} : { effortPath, effort }),
@@ -414,7 +410,7 @@ class KiroAdapter implements ProviderAdapter {
414
410
  }, "upstream");
415
411
  }
416
412
 
417
- yield* this.#stream(response.body, built.value, request, context);
413
+ yield* this.#stream(response.body, built.value, request, conversationId);
418
414
  }
419
415
 
420
416
  /**
@@ -471,7 +467,7 @@ class KiroAdapter implements ProviderAdapter {
471
467
  stream: ReadableStream<Uint8Array>,
472
468
  built: KiroWireRequest,
473
469
  request: CanonicalRequest,
474
- context: ProviderDispatchContext,
470
+ conversationId: string,
475
471
  ): AsyncIterable<CanonicalEvent> {
476
472
  const decoder = new KiroStreamDecoder();
477
473
  const reader = stream.getReader();
@@ -605,7 +601,7 @@ class KiroAdapter implements ProviderAdapter {
605
601
  reader.releaseLock();
606
602
  }
607
603
 
608
- const finalUsage = kiroUsage(usage, request.model, outputText, request.messages, resolveKiroConversationId(request, context));
604
+ const finalUsage = kiroUsage(usage, request.model, outputText, request.messages, conversationId);
609
605
  yield {
610
606
  type: "usage",
611
607
  sequence_number: sequence++,
@@ -0,0 +1,2 @@
1
+ export * from "./models";
2
+ export * from "./quota";
@@ -16,8 +16,8 @@
16
16
  * All six serve the chat wire (none is a Responses-native id), and all report
17
17
  * `supported_in_api: true` on the live roster.
18
18
  */
19
- import { defineModel } from "../provider-model-definition";
20
- import type { ModelDefinition } from "../provider-model-definition";
19
+ import { defineModel } from "../../provider-model-definition";
20
+ import type { ModelDefinition } from "../../provider-model-definition";
21
21
 
22
22
  export const OLLAMACLOUD_PROVIDER_ID = "ollamacloud" as const;
23
23
 
@@ -1,6 +1,6 @@
1
- import type { FetchLike, ProviderQuotaResult } from "../quota/quota-contracts";
2
- import { getJson, record, text } from "../quota/quota-contracts";
3
- import { parseQuotaWindows } from "../quota/quota-window-parser";
1
+ import type { FetchLike, ProviderQuotaResult } from "../../quota/quota-contracts";
2
+ import { getJson, record, text } from "../../quota/quota-contracts";
3
+ import { parseQuotaWindows } from "../../quota/quota-window-parser";
4
4
 
5
5
  async function fetchPlan(fetcher: FetchLike, headers: Record<string, string>): Promise<string> {
6
6
  try {
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  getJson,
3
+ isoDate,
3
4
  type FetchLike,
4
5
  type ProviderQuotaResult,
5
6
  type ProviderQuotaWindow,
@@ -42,13 +43,6 @@ function labelFor(kind: string): string {
42
43
  return kind.charAt(0).toUpperCase() + kind.slice(1);
43
44
  }
44
45
 
45
- function resetIso(value: string | null | undefined): string | null {
46
- if (typeof value !== "string" || value.trim() === "") return null;
47
- const parsed = Date.parse(value);
48
- if (!Number.isFinite(parsed)) return null;
49
- return new Date(parsed).toISOString();
50
- }
51
-
52
46
  /**
53
47
  * One window, or `undefined` when the upstream omitted what makes it usable.
54
48
  *
@@ -75,7 +69,7 @@ function windowFor(
75
69
  accounting: "independent",
76
70
  usedPercent: percent,
77
71
  remainingPercent: 100 - percent,
78
- resetsAt: resetIso(raw.resetsAt),
72
+ resetsAt: isoDate(raw.resetsAt),
79
73
  };
80
74
  }
81
75
 
@@ -0,0 +1,25 @@
1
+ // Perch credential-envelope parsing.
2
+ //
3
+ // Perch accounts authenticate via Supabase OAuth (PKCE), and the stored secret
4
+ // is the OAuth envelope JSON with `accessToken` (or `access_token`). Both the
5
+ // dispatch adapter and the quota collector need the access token out of that
6
+ // envelope; the parse is shared here and each surface keeps its own fallback
7
+ // for a secret that is not an envelope.
8
+
9
+ /**
10
+ * Reads the access token out of a stored Perch OAuth envelope.
11
+ *
12
+ * Returns the token when the secret parses as JSON and carries `accessToken`
13
+ * (or `access_token`) as a string; `null` when it is not such an envelope
14
+ * (not JSON, or JSON without a token field). The caller decides what a
15
+ * `null` means for its surface.
16
+ */
17
+ export function parsePerchCredentialToken(secret: string): string | null {
18
+ try {
19
+ const parsed = JSON.parse(secret) as Record<string, unknown>;
20
+ const found = parsed["accessToken"] ?? parsed["access_token"];
21
+ return typeof found === "string" ? found : null;
22
+ } catch {
23
+ return null;
24
+ }
25
+ }
@@ -27,9 +27,6 @@ import { PERCH_STARTER_PINS } from "./perch-realm";
27
27
  /** Default hosted model id when the account endpoint is unreachable. */
28
28
  export const PERCH_DEFAULT_CHAT_MODEL = "wandb-kimi-k2-6";
29
29
 
30
- /** App origin (model-call + ticket + account + plan endpoints). */
31
- export const PERCH_APP_URL = "https://app.perchai.app";
32
-
33
30
  /** The CLI user-agent the upstream recognizes as a CLI surface. */
34
31
  export const PERCH_CLI_USER_AGENT = "perchai-cli/unknown";
35
32
 
@@ -11,21 +11,7 @@ import type { FetchLike, ProviderQuotaResult, ProviderQuotaWindow } from "../../
11
11
  import { cleanError, percentWindow, text } from "../../quota/quota-contracts";
12
12
  import type { QuotaCollectionContext } from "../../quota/quota-support";
13
13
  import { PERCH_APP_URL } from "./perch-realm";
14
-
15
- interface PerchSessionCredential {
16
- readonly accessToken?: string;
17
- readonly access_token?: string;
18
- }
19
-
20
- function credentialToken(credential: string): string {
21
- try {
22
- const parsed = JSON.parse(credential) as PerchSessionCredential;
23
- const found = parsed.accessToken ?? parsed.access_token;
24
- return typeof found === "string" ? found : "";
25
- } catch {
26
- return credential.trim();
27
- }
28
- }
14
+ import { parsePerchCredentialToken } from "./perch-credential";
29
15
 
30
16
  async function readAccount(
31
17
  baseUrl: string,
@@ -61,7 +47,7 @@ export async function fetchPerchQuota(
61
47
  _context?: QuotaCollectionContext,
62
48
  ): Promise<ProviderQuotaResult> {
63
49
  try {
64
- const token = credentialToken(credential);
50
+ const token = parsePerchCredentialToken(credential) ?? "";
65
51
  const account = await readAccount(PERCH_APP_URL, token, fetcher);
66
52
  if (!account) {
67
53
  return { source: "perch", plan: null, windows: [], error: "Perch account endpoint unavailable" };
@@ -28,7 +28,6 @@ import {
28
28
  } from "../../provider-contracts";
29
29
  import { usageFromProvider } from "../../provider-usage";
30
30
  import {
31
- PERCH_APP_URL,
32
31
  PERCH_CLI_USER_AGENT,
33
32
  PERCH_SAFFRON_SYSTEM_PROMPT,
34
33
  buildPerchModelCallBody,
@@ -39,7 +38,8 @@ import {
39
38
  PERCH_MODEL_PINS,
40
39
  PERCH_MODEL_ROOST_TIERS,
41
40
  } from "./perch-catalog";
42
- import { PERCH_PROVIDER_ID } from "./perch-realm";
41
+ import { PERCH_APP_URL, PERCH_PROVIDER_ID } from "./perch-realm";
42
+ import { parsePerchCredentialToken } from "./perch-credential";
43
43
 
44
44
  interface PerchAdapterOptions {
45
45
  readonly providerId?: string;
@@ -126,7 +126,7 @@ export class PerchAdapter implements ProviderAdapter {
126
126
  }
127
127
  const secret = readCredentialSecret(context.credential, "Perch credential is required");
128
128
  const modelId = candidate.model_id || request.model || "standard";
129
- const access = secret.startsWith("{") ? parseAccessToken(secret) : secret.trim();
129
+ const access = parsePerchCredentialToken(secret) ?? secret.trim();
130
130
  // Probes pass a validated outbound fetch (with capture wrapped around it);
131
131
  // live dispatch passes one too. `globalThis.fetch` here silently split the
132
132
  // probe from the route's egress policy and left Request Detail empty.
@@ -281,16 +281,6 @@ function normalizePerchUsage(usage: Record<string, unknown>): Record<string, unk
281
281
  return out;
282
282
  }
283
283
 
284
- function parseAccessToken(secret: string): string {
285
- try {
286
- const parsed = JSON.parse(secret) as Record<string, unknown>;
287
- const found = parsed["accessToken"] ?? parsed["access_token"];
288
- return typeof found === "string" ? found : secret.trim();
289
- } catch {
290
- return secret.trim();
291
- }
292
- }
293
-
294
284
  /**
295
285
  * Builds an upstream verdict for the Perch surface. `origin: "upstream"` is
296
286
  * what lets the account health machine see the failure: `attempt-finalize`