cartethyia 2.5.1 → 2.5.3

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 (235) hide show
  1. package/.env.example +5 -1
  2. package/dist/dashboard/assets/{AccountCooldown-ChpdzxRH.js → AccountCooldown-Bjo92Qat.js} +1 -1
  3. package/dist/dashboard/assets/AccountCooldown-Bjo92Qat.js.gz +0 -0
  4. package/dist/dashboard/assets/{BannedPage-ChqnTRpZ.js → BannedPage-hfCzi-Ux.js} +1 -1
  5. package/dist/dashboard/assets/BannedPage-hfCzi-Ux.js.gz +0 -0
  6. package/dist/dashboard/assets/CliToolDetailPage-DLGqYEwI.js +1 -0
  7. package/dist/dashboard/assets/CliToolDetailPage-DLGqYEwI.js.gz +0 -0
  8. package/dist/dashboard/assets/{CliToolsPage-BXUaRCQQ.js → CliToolsPage-DEDyKBlC.js} +1 -1
  9. package/dist/dashboard/assets/CliToolsPage-DEDyKBlC.js.gz +0 -0
  10. package/dist/dashboard/assets/CombosPage-CfuYiQ8Z.js +1 -0
  11. package/dist/dashboard/assets/CombosPage-CfuYiQ8Z.js.gz +0 -0
  12. package/dist/dashboard/assets/ConsoleLogPage-bsLuZmYH.js +1 -0
  13. package/dist/dashboard/assets/ConsoleLogPage-bsLuZmYH.js.gz +0 -0
  14. package/dist/dashboard/assets/CustomizationPage-_UWhwziJ.js +1 -0
  15. package/dist/dashboard/assets/CustomizationPage-_UWhwziJ.js.gz +0 -0
  16. package/dist/dashboard/assets/HealthEventsModal-Cjfc5WFS.js +1 -0
  17. package/dist/dashboard/assets/HealthEventsModal-Cjfc5WFS.js.gz +0 -0
  18. package/dist/dashboard/assets/LoginPage-DDVIfZGW.js +1 -0
  19. package/dist/dashboard/assets/LoginPage-DDVIfZGW.js.gz +0 -0
  20. package/dist/dashboard/assets/ModelPicker-CWkTUtpX.js +1 -0
  21. package/dist/dashboard/assets/ModelPicker-CWkTUtpX.js.gz +0 -0
  22. package/dist/dashboard/assets/OverviewPage-B53YYMng.js +1 -0
  23. package/dist/dashboard/assets/OverviewPage-B53YYMng.js.gz +0 -0
  24. package/dist/dashboard/assets/ProviderDetailPage-B0KaAQf0.js +3 -0
  25. package/dist/dashboard/assets/ProviderDetailPage-B0KaAQf0.js.gz +0 -0
  26. package/dist/dashboard/assets/ProvidersPage-Dx31DgIO.js +1 -0
  27. package/dist/dashboard/assets/ProvidersPage-Dx31DgIO.js.gz +0 -0
  28. package/dist/dashboard/assets/ProxyPage-BpnR4kXT.js +7 -0
  29. package/dist/dashboard/assets/ProxyPage-BpnR4kXT.js.gz +0 -0
  30. package/dist/dashboard/assets/QuotaPage-BpOQCnun.js +1 -0
  31. package/dist/dashboard/assets/QuotaPage-BpOQCnun.js.gz +0 -0
  32. package/dist/dashboard/assets/SettingsPage-DV1eo2J_.js +1 -0
  33. package/dist/dashboard/assets/SettingsPage-DV1eo2J_.js.gz +0 -0
  34. package/dist/dashboard/assets/SetupPage-D6pmQ86L.js +1 -0
  35. package/dist/dashboard/assets/SetupPage-D6pmQ86L.js.gz +0 -0
  36. package/dist/dashboard/assets/SortableList-DWvKiuSl.js +1 -0
  37. package/dist/dashboard/assets/SortableList-DWvKiuSl.js.gz +0 -0
  38. package/dist/dashboard/assets/{StudioPage-DqK5E26W.js → StudioPage-CmkUrgdj.js} +3 -3
  39. package/dist/dashboard/assets/StudioPage-CmkUrgdj.js.gz +0 -0
  40. package/dist/dashboard/assets/UsagePage-B47uOydr.js +56 -0
  41. package/dist/dashboard/assets/UsagePage-B47uOydr.js.gz +0 -0
  42. package/dist/dashboard/assets/{api-keys-BmDbsior.js → api-keys-uNywYlfv.js} +1 -1
  43. package/dist/dashboard/assets/api-keys-uNywYlfv.js.gz +0 -0
  44. package/dist/dashboard/assets/{app-C-Y4oZT0.js → app-CafWtVXA.js} +1 -1
  45. package/dist/dashboard/assets/{app-C-Y4oZT0.js.gz → app-CafWtVXA.js.gz} +0 -0
  46. package/dist/dashboard/assets/app-DxivpfYG.css +2 -0
  47. package/dist/dashboard/assets/app-DxivpfYG.css.gz +0 -0
  48. package/dist/dashboard/assets/{assert-BHfaei-X.js → assert-NHwYq95t.js} +1 -1
  49. package/dist/dashboard/assets/{assert-BHfaei-X.js.gz → assert-NHwYq95t.js.gz} +0 -0
  50. package/dist/dashboard/assets/badge-6uzDDvqs.js +1 -0
  51. package/dist/dashboard/assets/badge-6uzDDvqs.js.gz +0 -0
  52. package/dist/dashboard/assets/banner-4_WNg2WZ.js +1 -0
  53. package/dist/dashboard/assets/banner-4_WNg2WZ.js.gz +0 -0
  54. package/dist/dashboard/assets/card--AtFedAq.js +1 -0
  55. package/dist/dashboard/assets/card--AtFedAq.js.gz +0 -0
  56. package/dist/dashboard/assets/{cli-tools-B0wBq-RL.js → cli-tools-B3e7dx3N.js} +1 -1
  57. package/dist/dashboard/assets/cli-tools-B3e7dx3N.js.gz +0 -0
  58. package/dist/dashboard/assets/clipboard-button-NreUx6c8.js +1 -0
  59. package/dist/dashboard/assets/clipboard-button-NreUx6c8.js.gz +0 -0
  60. package/dist/dashboard/assets/dialog-BtpsJU98.js +1 -0
  61. package/dist/dashboard/assets/dialog-BtpsJU98.js.gz +0 -0
  62. package/dist/dashboard/assets/drawer-WwwtzihF.js +1 -0
  63. package/dist/dashboard/assets/drawer-WwwtzihF.js.gz +0 -0
  64. package/dist/dashboard/assets/entry-CkryrIea.js +4 -0
  65. package/dist/dashboard/assets/entry-CkryrIea.js.gz +0 -0
  66. package/dist/dashboard/assets/entry-TJIye_jT.js +1 -0
  67. package/dist/dashboard/assets/entry-TJIye_jT.js.gz +0 -0
  68. package/dist/dashboard/assets/{entry-D4O2229X.js → entry-jy9_4-Ke.js} +1 -1
  69. package/dist/dashboard/assets/entry-jy9_4-Ke.js.gz +0 -0
  70. package/dist/dashboard/assets/github-badge-D_eM3IgG.js +1 -0
  71. package/dist/dashboard/assets/github-badge-D_eM3IgG.js.gz +0 -0
  72. package/dist/dashboard/assets/{index-CcD1eSJ6.js → index-BydyKaDz.js} +2 -2
  73. package/dist/dashboard/assets/index-BydyKaDz.js.gz +0 -0
  74. package/dist/dashboard/assets/inline-BcNGF8Gr.js +1 -0
  75. package/dist/dashboard/assets/inline-BcNGF8Gr.js.gz +0 -0
  76. package/dist/dashboard/assets/input-BG41et9G.js +1 -0
  77. package/dist/dashboard/assets/input-BG41et9G.js.gz +0 -0
  78. package/dist/dashboard/assets/layout-C8mer_Ak.js +1 -0
  79. package/dist/dashboard/assets/layout-C8mer_Ak.js.gz +0 -0
  80. package/dist/dashboard/assets/list-row-Gh_W4OR_.js +1 -0
  81. package/dist/dashboard/assets/list-row-Gh_W4OR_.js.gz +0 -0
  82. package/dist/dashboard/assets/{live-yfEJFF9T.js → live-BJGYRBnR.js} +1 -1
  83. package/dist/dashboard/assets/live-BJGYRBnR.js.gz +0 -0
  84. package/dist/dashboard/assets/{model-bans-CT-TxSNX.js → model-bans-DeVLDA7I.js} +1 -1
  85. package/dist/dashboard/assets/model-bans-DeVLDA7I.js.gz +0 -0
  86. package/dist/dashboard/assets/numbers-Bs4LOOuf.js +2 -0
  87. package/dist/dashboard/assets/numbers-Bs4LOOuf.js.gz +0 -0
  88. package/dist/dashboard/assets/{routing-Cs6jl49L.js → routing-CcusqXA7.js} +1 -1
  89. package/dist/dashboard/assets/routing-CcusqXA7.js.gz +0 -0
  90. package/dist/dashboard/assets/select-BO6h8yNv.js +41 -0
  91. package/dist/dashboard/assets/select-BO6h8yNv.js.gz +0 -0
  92. package/dist/dashboard/assets/{settings-CfcTcOyU.js → settings-mpmXTgUa.js} +1 -1
  93. package/dist/dashboard/assets/settings-mpmXTgUa.js.gz +0 -0
  94. package/dist/dashboard/assets/stack-DwLmjUFK.js +1 -0
  95. package/dist/dashboard/assets/stack-DwLmjUFK.js.gz +0 -0
  96. package/dist/dashboard/assets/state-DYdQmdAl.js +1 -0
  97. package/dist/dashboard/assets/state-DYdQmdAl.js.gz +0 -0
  98. package/dist/dashboard/assets/switch-CidSfbG7.js +1 -0
  99. package/dist/dashboard/assets/switch-CidSfbG7.js.gz +0 -0
  100. package/dist/dashboard/assets/{useMutation-BQKMcJ1H.js → useMutation-w4u4CVue.js} +1 -1
  101. package/dist/dashboard/assets/useMutation-w4u4CVue.js.gz +0 -0
  102. package/dist/dashboard/index.html +1 -1
  103. package/dist/dashboard/index.html.gz +0 -0
  104. package/dist/dashboard/precompressed-assets.json +51 -54
  105. package/package.json +1 -1
  106. package/scripts/cli/command/console-menu.ts +248 -246
  107. package/scripts/cli/command/tray.ts +208 -204
  108. package/src/boot/app.ts +3 -2
  109. package/src/config/spec.ts +26 -2
  110. package/src/console/backup/routes.ts +52 -1
  111. package/src/console/backup/service.ts +60 -4
  112. package/src/console/observability/contracts.ts +11 -1
  113. package/src/console/observability/store.ts +9 -6
  114. package/src/console/share/share-router.ts +6 -9
  115. package/src/observability/payload-capture.ts +366 -344
  116. package/src/observability/payload-store.ts +12 -2
  117. package/src/observability/telemetry-status.ts +28 -16
  118. package/src/persistence/stores/api-key-store.ts +6 -0
  119. package/src/persistence/stores/share-store.ts +583 -556
  120. package/src/persistence/stores/telemetry-store.ts +1 -1
  121. package/src/protocol/response/gemini.ts +18 -4
  122. package/src/providers/discovery/openrouter-free-roster.ts +3 -11
  123. package/src/providers/discovery/probe-phases.ts +42 -5
  124. package/src/providers/discovery/probing-service.ts +25 -2
  125. package/src/providers/integrations/antigravity/antigravity-quota.ts +11 -3
  126. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +4 -4
  127. package/src/providers/integrations/buddy/workbuddy/shared.ts +201 -200
  128. package/src/providers/integrations/claude/claude.ts +3 -1
  129. package/src/providers/integrations/cline/cline-quota.ts +8 -11
  130. package/src/providers/integrations/cloudflareai/cloudflareai-models.ts +3 -1
  131. package/src/providers/integrations/inferhub.ts +3 -1
  132. package/src/providers/integrations/kilo/kilo.ts +0 -1
  133. package/src/providers/integrations/kiro/kiro-catalog.ts +8 -2
  134. package/src/providers/integrations/opencode/provider.ts +532 -532
  135. package/src/providers/integrations/openrouter/provider.ts +5 -15
  136. package/src/providers/integrations/tokenharbor.ts +2 -2
  137. package/src/providers/integrations/trae/trae-catalog.ts +5 -3
  138. package/src/providers/operations/account-health-service.ts +23 -0
  139. package/src/providers/operations/client-versions.ts +9 -2
  140. package/src/providers/provider-metadata.ts +7 -1
  141. package/src/security/api-key-auth.ts +23 -0
  142. package/src/transport/dispatch/attempt-finalize.ts +46 -5
  143. package/src/transport/dispatch/proxy-request.ts +9 -0
  144. package/src/transport/dispatch/streaming/stream.ts +16 -2
  145. package/src/transport/guards/auth-guard.ts +2 -3
  146. package/src/transport/routing/route-catalog.ts +806 -799
  147. package/dist/dashboard/assets/AccountCooldown-ChpdzxRH.js.gz +0 -0
  148. package/dist/dashboard/assets/BannedPage-ChqnTRpZ.js.gz +0 -0
  149. package/dist/dashboard/assets/CliToolDetailPage-Bb9CNuoh.js +0 -1
  150. package/dist/dashboard/assets/CliToolDetailPage-Bb9CNuoh.js.gz +0 -0
  151. package/dist/dashboard/assets/CliToolsPage-BXUaRCQQ.js.gz +0 -0
  152. package/dist/dashboard/assets/CombosPage-CisNZOUt.js +0 -1
  153. package/dist/dashboard/assets/CombosPage-CisNZOUt.js.gz +0 -0
  154. package/dist/dashboard/assets/ConsoleLogPage-BOpI_LOH.js +0 -1
  155. package/dist/dashboard/assets/ConsoleLogPage-BOpI_LOH.js.gz +0 -0
  156. package/dist/dashboard/assets/CustomizationPage-DAkdvJ4k.js +0 -1
  157. package/dist/dashboard/assets/CustomizationPage-DAkdvJ4k.js.gz +0 -0
  158. package/dist/dashboard/assets/HealthEventsModal-DeT-VXHd.js +0 -1
  159. package/dist/dashboard/assets/HealthEventsModal-DeT-VXHd.js.gz +0 -0
  160. package/dist/dashboard/assets/LoginPage-CytxSeGP.js +0 -1
  161. package/dist/dashboard/assets/LoginPage-CytxSeGP.js.gz +0 -0
  162. package/dist/dashboard/assets/ModelPicker-oQ-SDXp8.js +0 -1
  163. package/dist/dashboard/assets/ModelPicker-oQ-SDXp8.js.gz +0 -0
  164. package/dist/dashboard/assets/OverviewPage-CU7S1wJs.js +0 -1
  165. package/dist/dashboard/assets/OverviewPage-CU7S1wJs.js.gz +0 -0
  166. package/dist/dashboard/assets/ProviderDetailPage-DLm2baot.js +0 -3
  167. package/dist/dashboard/assets/ProviderDetailPage-DLm2baot.js.gz +0 -0
  168. package/dist/dashboard/assets/ProvidersPage-DOXfuzfL.js +0 -1
  169. package/dist/dashboard/assets/ProvidersPage-DOXfuzfL.js.gz +0 -0
  170. package/dist/dashboard/assets/ProxyPage-wvk0ciQN.js +0 -7
  171. package/dist/dashboard/assets/ProxyPage-wvk0ciQN.js.gz +0 -0
  172. package/dist/dashboard/assets/QuotaPage-Cn_fcUHE.js +0 -1
  173. package/dist/dashboard/assets/QuotaPage-Cn_fcUHE.js.gz +0 -0
  174. package/dist/dashboard/assets/SettingsPage-8-eVadhc.js +0 -1
  175. package/dist/dashboard/assets/SettingsPage-8-eVadhc.js.gz +0 -0
  176. package/dist/dashboard/assets/SetupPage-uEIXPyGO.js +0 -1
  177. package/dist/dashboard/assets/SetupPage-uEIXPyGO.js.gz +0 -0
  178. package/dist/dashboard/assets/SortableList-DCY0TPTZ.js +0 -1
  179. package/dist/dashboard/assets/SortableList-DCY0TPTZ.js.gz +0 -0
  180. package/dist/dashboard/assets/StudioPage-DqK5E26W.js.gz +0 -0
  181. package/dist/dashboard/assets/UsagePage-38JfiZ0H.js +0 -56
  182. package/dist/dashboard/assets/UsagePage-38JfiZ0H.js.gz +0 -0
  183. package/dist/dashboard/assets/api-keys-BmDbsior.js.gz +0 -0
  184. package/dist/dashboard/assets/app-BbpSarVV.css +0 -2
  185. package/dist/dashboard/assets/app-BbpSarVV.css.gz +0 -0
  186. package/dist/dashboard/assets/badge-DpXT4Ixt.js +0 -1
  187. package/dist/dashboard/assets/badge-DpXT4Ixt.js.gz +0 -0
  188. package/dist/dashboard/assets/banner-CSAAWLJv.js +0 -1
  189. package/dist/dashboard/assets/banner-CSAAWLJv.js.gz +0 -0
  190. package/dist/dashboard/assets/card-DEhRXXw4.js +0 -1
  191. package/dist/dashboard/assets/card-DEhRXXw4.js.gz +0 -0
  192. package/dist/dashboard/assets/check-D69FzdvC.js +0 -1
  193. package/dist/dashboard/assets/check-D69FzdvC.js.gz +0 -0
  194. package/dist/dashboard/assets/cli-tools-B0wBq-RL.js.gz +0 -0
  195. package/dist/dashboard/assets/clipboard-button-DOo6EU8O.js +0 -1
  196. package/dist/dashboard/assets/clipboard-button-DOo6EU8O.js.gz +0 -0
  197. package/dist/dashboard/assets/copy-ngmQ9zcZ.js +0 -1
  198. package/dist/dashboard/assets/copy-ngmQ9zcZ.js.gz +0 -0
  199. package/dist/dashboard/assets/dialog-BvQeBMfP.js +0 -1
  200. package/dist/dashboard/assets/dialog-BvQeBMfP.js.gz +0 -0
  201. package/dist/dashboard/assets/drawer-CMwPXwQB.js +0 -1
  202. package/dist/dashboard/assets/drawer-CMwPXwQB.js.gz +0 -0
  203. package/dist/dashboard/assets/entry--yj84zk3.js +0 -4
  204. package/dist/dashboard/assets/entry--yj84zk3.js.gz +0 -0
  205. package/dist/dashboard/assets/entry-D4O2229X.js.gz +0 -0
  206. package/dist/dashboard/assets/entry-DKvr5Q9K.js +0 -1
  207. package/dist/dashboard/assets/entry-DKvr5Q9K.js.gz +0 -0
  208. package/dist/dashboard/assets/github-badge-zliLvBwr.js +0 -1
  209. package/dist/dashboard/assets/github-badge-zliLvBwr.js.gz +0 -0
  210. package/dist/dashboard/assets/index-CcD1eSJ6.js.gz +0 -0
  211. package/dist/dashboard/assets/inline-D1IlZZ8H.js +0 -1
  212. package/dist/dashboard/assets/inline-D1IlZZ8H.js.gz +0 -0
  213. package/dist/dashboard/assets/input-BpVZdrbc.js +0 -1
  214. package/dist/dashboard/assets/input-BpVZdrbc.js.gz +0 -0
  215. package/dist/dashboard/assets/layout-BioLW7gN.js +0 -1
  216. package/dist/dashboard/assets/layout-BioLW7gN.js.gz +0 -0
  217. package/dist/dashboard/assets/list-row-CwVcGAIc.js +0 -1
  218. package/dist/dashboard/assets/list-row-CwVcGAIc.js.gz +0 -0
  219. package/dist/dashboard/assets/live-yfEJFF9T.js.gz +0 -0
  220. package/dist/dashboard/assets/model-bans-CT-TxSNX.js.gz +0 -0
  221. package/dist/dashboard/assets/numbers-CFL4_Dqc.js +0 -1
  222. package/dist/dashboard/assets/numbers-CFL4_Dqc.js.gz +0 -0
  223. package/dist/dashboard/assets/routing-Cs6jl49L.js.gz +0 -0
  224. package/dist/dashboard/assets/select-XPJ-dIw3.js +0 -41
  225. package/dist/dashboard/assets/select-XPJ-dIw3.js.gz +0 -0
  226. package/dist/dashboard/assets/settings-CfcTcOyU.js.gz +0 -0
  227. package/dist/dashboard/assets/stack-C9_uUjaA.js +0 -1
  228. package/dist/dashboard/assets/stack-C9_uUjaA.js.gz +0 -0
  229. package/dist/dashboard/assets/state-DA0B95UQ.js +0 -1
  230. package/dist/dashboard/assets/state-DA0B95UQ.js.gz +0 -0
  231. package/dist/dashboard/assets/switch-Cittxy-x.js +0 -1
  232. package/dist/dashboard/assets/switch-Cittxy-x.js.gz +0 -0
  233. package/dist/dashboard/assets/toast-CNt_jzO4.js +0 -2
  234. package/dist/dashboard/assets/toast-CNt_jzO4.js.gz +0 -0
  235. package/dist/dashboard/assets/useMutation-BQKMcJ1H.js.gz +0 -0
@@ -24,21 +24,11 @@ const OPENROUTER_REQUIRED_HEADERS: Readonly<Record<string, string>> = {
24
24
  * served by the native System One route, never by the chat surface.
25
25
  */
26
26
  export const OPENROUTER_MODELS: readonly ModelDefinition[] = [
27
- // The router, and only the router. A hand-kept catalog of specific ids was a
28
- // snapshot of one afternoon: OpenRouter adds and pulls models within days, so
29
- // every committed row outside this one drifts out of truth. Discovery is the
30
- // authority, and the free roster below is the offline floor.
31
- defineModel({
32
- id: "openrouter/auto",
33
- providerId: OPENROUTER_PROVIDER_ID,
34
- wireFamily: "chat",
35
- endpoint: "/chat/completions",
36
- // The catalog publishes no limits for a router (pricing is -1, meaning
37
- // "decided at dispatch"), so this is the largest window it may select into.
38
- ctx: 2_000_000,
39
- out: 128_000,
40
- reasoning: true,
41
- }),
27
+ // No committed chat rows. OpenRouter adds and pulls models within days, so a
28
+ // hand-kept list of ids drifts out of truth and discovery is the authority;
29
+ // the free roster below stays as the offline floor. `openrouter/auto` was the
30
+ // last committed chat row and went with the rest of the static catalog — it
31
+ // publishes no limits of its own anyway, since a router's pricing is -1.
42
32
  // Not a chat row: it answers `{answers}` to a `{state, questions}` body, and
43
33
  // is served by the native System One route. Kept because deleting it removes
44
34
  // that route, which is a capability, not a stale catalog entry.
@@ -35,7 +35,7 @@ export const TOKENHARBOR_FALLBACK_MODELS: readonly ModelDefinition[] = [
35
35
  defineModel({ id: "muse-spark-1.2", providerId: "tokenharbor", ctx: 1_048_576, reasoning: true, vision: true }),
36
36
  defineModel({ id: "muse-spark-1-3", providerId: "tokenharbor", ctx: 1_048_576, reasoning: true, vision: true }),
37
37
  defineModel({ id: "gpt-6-astra", providerId: "tokenharbor", ctx: 1_050_000, reasoning: true, vision: true }),
38
- defineModel({ id: "deepseek-v4.1-flash", providerId: "tokenharbor", ctx: 1_048_576, reasoning: true }),
38
+ defineModel({ id: "deepseek-v4.1-flash", providerId: "tokenharbor", ctx: 1_048_576, reasoning: true, vision: true }),
39
39
  defineModel({ id: "gemini-3.6-flash", providerId: "tokenharbor", ctx: 1_000_000, reasoning: true, vision: true }),
40
40
  defineModel({ id: "glm-5.2", providerId: "tokenharbor", ctx: 1_000_000, reasoning: true }),
41
41
  defineModel({ id: "gpt-5.6-luna", providerId: "tokenharbor", reasoning: true, vision: true }),
@@ -62,7 +62,7 @@ export const TOKENHARBOR_FALLBACK_MODELS: readonly ModelDefinition[] = [
62
62
  defineModel({ id: "mimo-v2.6-pro", providerId: "tokenharbor", ctx: 1_048_576, reasoning: true, vision: true }),
63
63
  defineModel({ id: "mimo-v2.5:free", providerId: "tokenharbor", ctx: 1_048_576, reasoning: true, vision: true, free: true }),
64
64
  defineModel({ id: "deepseek-v4-flash:free", providerId: "tokenharbor", reasoning: true, free: true }),
65
- defineModel({ id: "deepseek-v4.1-flash:free", providerId: "tokenharbor", ctx: 1_048_576, reasoning: true, free: true }),
65
+ defineModel({ id: "deepseek-v4.1-flash:free", providerId: "tokenharbor", ctx: 1_048_576, reasoning: true, vision: true, free: true }),
66
66
  defineModel({ id: "qwen3.8-flash:free", providerId: "tokenharbor", reasoning: true, free: true }),
67
67
  defineModel({ id: "mimo-v2.6-flash:free", providerId: "tokenharbor", ctx: 1_048_576, reasoning: true, vision: true, free: true }),
68
68
  ];
@@ -79,7 +79,9 @@ const TRAE_CN_ROWS: readonly TraeModelRow[] = [
79
79
  * (DeepSeek-V4 rows: 200k active, 1M selectable), so reading `dev` makes a
80
80
  * client under-request the window it is entitled to. The maximum is per model
81
81
  * and is **not** uniform: the GPT rows cap at 272k and Kimi-K2.5 / MiniMax-M2.7
82
- * offer no selectable upgrade at all, staying at 200k.
82
+ * offer no selectable upgrade at all, staying at 200k. `gpt-5.4` is the
83
+ * exception: upstream raised its selectable maximum to 1M, so it is filed at
84
+ * 1M while the rest of the GPT rows stay at 272k.
83
85
  *
84
86
  * `out` is what the same model is filed at in the CodeBuddy roster where that
85
87
  * roster carries it (DeepSeek-V4 384k, MiniMax-M3 512k, Kimi-K2.5 262k,
@@ -98,13 +100,13 @@ const TRAE_CN_ROWS: readonly TraeModelRow[] = [
98
100
  * one.
99
101
  */
100
102
  const TRAE_INTL_ROWS: readonly TraeModelRow[] = [
101
- { id: "gpt-5.4", displayName: "GPT-5.4", ctx: 272000, out: 128_000, vision: true, reasoning: true, surface: "agent" },
103
+ { id: "gpt-5.4", displayName: "GPT-5.4", ctx: 1_000_000, out: 128_000, vision: true, reasoning: true, surface: "agent" },
102
104
  { id: "gpt-5.2", displayName: "GPT-5.2", ctx: 272000, out: 128_000, vision: true, reasoning: true, surface: "agent" },
103
105
  { id: "gpt-5.2-codex-paygo", displayName: "GPT-5.2-Codex", ctx: 272000, out: 128_000, vision: true, reasoning: true, surface: "agent" },
104
106
  { id: "gpt-5.1-paygo", displayName: "GPT-5.1", ctx: 272000, out: 128_000, vision: true, reasoning: true, surface: "agent" },
105
107
  { id: "gpt-5-paygo", displayName: "GPT-5", ctx: 272000, out: 128_000, vision: true, reasoning: true, surface: "agent" },
106
108
  { id: "Dola-Seed-2.0-Code", displayName: "Seed-2.1-Turbo", ctx: 1000000, out: 131_072, vision: true, reasoning: true, surface: "agent" },
107
- { id: "deepseek-v4.1-flash", displayName: "DeepSeek-V4.1-Flash", ctx: 1000000, out: 384_000, vision: false, reasoning: true, surface: "agent" },
109
+ { id: "deepseek-v4.1-flash", displayName: "DeepSeek-V4.1-Flash", ctx: 1000000, out: 384_000, vision: true, reasoning: true, surface: "agent" },
108
110
  { id: "deepseek-v4-flash-0731", displayName: "DeepSeek-V4-Flash", ctx: 1000000, out: 384_000, vision: false, reasoning: true, surface: "agent" },
109
111
  { id: "deepseek-v4-pro-0813", displayName: "DeepSeek-V4-Pro", ctx: 1000000, out: 384_000, vision: false, reasoning: true, surface: "agent" },
110
112
  { id: "kimi-k3", displayName: "Kimi-K3", ctx: 1000000, out: 131_072, vision: true, reasoning: true, surface: "agent" },
@@ -637,11 +637,33 @@ export function classifyAccountError(
637
637
  lower.includes("no access to model") ||
638
638
  lower.includes("model_not_supported") ||
639
639
  lower.includes("not available in your region"));
640
+ /**
641
+ * The upstream accepted the request and put it in a queue behind other
642
+ * callers — it is busy, not rejecting the caller's credential.
643
+ *
644
+ * Qoder answers `403 {"code":"10605","message":"{\"isQueued\":true,
645
+ * \"modelKey\":\"dfmodel\",\"queueCount\":1395,...}"}`. The 403 is what made
646
+ * this misclassify: the generic `(401 || 403) && accountEvidence` arm claimed
647
+ * it as `auth_invalidated`, so a healthy account was parked as "Re-login
648
+ * required" purely because the provider was busy, and an operator was sent
649
+ * to re-authenticate a credential that was never rejected.
650
+ *
651
+ * Matched on the queue evidence rather than on the bare `10605`, so an
652
+ * unrelated 403 that happens to share the code is not swept in. The bodies
653
+ * are nested JSON, so the tell is the key, not a parse of the envelope.
654
+ */
655
+ const queuedUpstream =
656
+ providerCode === "10605" ||
657
+ lower.includes("isqueued") ||
658
+ lower.includes("queuecount") ||
659
+ lower.includes("queuetype") ||
660
+ (lower.includes("queue") && lower.includes("modelkey"));
640
661
  const authSignal =
641
662
  !quotaSignal &&
642
663
  !policyRejection &&
643
664
  !hostedToolFailure &&
644
665
  !modelEntitlementFailure &&
666
+ !queuedUpstream &&
645
667
  (providerCode === "authentication_failed" ||
646
668
  folded.includes("invalid api key") ||
647
669
  folded.includes("incorrect api key") ||
@@ -805,6 +827,7 @@ export function classifyAccountError(
805
827
  statusCode === 503 ||
806
828
  statusCode === 529 ||
807
829
  providerCode === "model_capacity" ||
830
+ queuedUpstream ||
808
831
  lower.includes("model overloaded") ||
809
832
  lower.includes("capacity exceeded") ||
810
833
  lower.includes("temporarily unavailable");
@@ -632,12 +632,19 @@ export function buildWorkBuddyUserAgent(
632
632
  cliVersion = getWorkBuddyCliVersion(),
633
633
  ): string {
634
634
  // 1:1 with codebuddy2api. The CLI profile is `CLI/<v> CodeBuddy/<v>` and the
635
- // WorkBuddy profile is `WorkBuddy/<wb> WorkBuddy AI/<wb> CLI/<cli>`, which
635
+ // WorkBuddy profile is `WorkBuddy/<wb> workbuddy-ai/<wb> CLI/<cli>`, which
636
636
  // sends NO SDK headers — two separate profiles, not one string with a switch.
637
637
  // Whether SDK headers ride along is decided by `workbuddyHeaders`, not here, so
638
638
  // this builder only decides the UA text.
639
+ //
640
+ // The second token is the wire spelling `workbuddy-ai`, NOT the display
641
+ // brand `WorkBuddy AI`. This is what an official client actually sends; the
642
+ // product is branded with a space in the UI and in docs, and copying that
643
+ // branding into the header produced a UA no real client emits — which the
644
+ // intl gateway answers with 403 `11140`, the same refusal it gives a
645
+ // wrong-brand header. Product name ≠ wire token.
639
646
  if (identity === "CLI") return `CLI/${cliVersion} CodeBuddy/${cliVersion}`;
640
- return `WorkBuddy/${clientVersion} WorkBuddy AI/${clientVersion} CLI/${cliVersion}`;
647
+ return `WorkBuddy/${clientVersion} workbuddy-ai/${clientVersion} CLI/${cliVersion}`;
641
648
  }
642
649
 
643
650
  export const _resetWorkBuddyCnClientVersionCache = accessor("workbuddyCnClient").reset;
@@ -86,6 +86,9 @@ const RAW_BUNDLED_PROVIDER_METADATA = [
86
86
  serviceKinds: ["llm", "websearch", "imageGeneration"],
87
87
  credentialUrl: "https://antigravity.google",
88
88
  credentialHint: "Signed in with a Google account through the Antigravity login flow.",
89
+ // Per-account credential, so rotating the egress address buys nothing —
90
+ // the same reason Hermes, InferHub and Cline skip.
91
+ defaultSkipProxy: true,
89
92
  },
90
93
  { id: "muse", displayName: "Muse Code", baseUrl: "https://api.meta.ai" },
91
94
  {
@@ -109,6 +112,7 @@ const RAW_BUNDLED_PROVIDER_METADATA = [
109
112
  baseUrl: "https://q.us-east-1.amazonaws.com",
110
113
  bespokeWire: true,
111
114
  hasAdapterUserAgent: true,
115
+ defaultSkipProxy: true,
112
116
  credentialUrl: "https://kiro.dev",
113
117
  credentialHint: "Sign in with Google or GitHub in the browser, or use the device-code login with an Identity Center organization, then paste a Kiro API key if you prefer. Prompt caching is not reliably working for Kiro: the upstream recomputes the prompt on every turn, so cached-token savings should not be expected.",
114
118
  },
@@ -331,6 +335,7 @@ const RAW_BUNDLED_PROVIDER_METADATA = [
331
335
  displayName: "Kilo Code",
332
336
  baseUrl: "https://api.kilo.ai/api/openrouter",
333
337
  credentialUrl: "https://app.kilo.ai/device-auth",
338
+ defaultSkipProxy: true,
334
339
  },
335
340
  {
336
341
  // Command Code's adapter stamps its own client identity headers
@@ -366,7 +371,7 @@ const RAW_BUNDLED_PROVIDER_METADATA = [
366
371
  credentialUrl: `${QODER_REALMS.cn.website}/account/integrations`,
367
372
  credentialHint: "Paste a Personal Access Token (pt-...) from the Qoder CN integrations page, or sign in with your Qoder China account.",
368
373
  },
369
- { id: "trae", displayName: "Trae", baseUrl: `${TRAE_REALMS.intl.core}${TRAE_CHAT_PATH}`, hasAdapterUserAgent: true, credentialUrl: "https://www.trae.ai/", credentialHint: "Paste a Trae account export (accessToken + refreshToken) or the raw access token from an international Trae account." },
374
+ { id: "trae", displayName: "Trae", baseUrl: `${TRAE_REALMS.intl.core}${TRAE_CHAT_PATH}`, hasAdapterUserAgent: true, defaultSkipProxy: true, credentialUrl: "https://www.trae.ai/", credentialHint: "Paste a Trae account export (accessToken + refreshToken) or the raw access token from an international Trae account." },
370
375
  // A separate provider, not a region flag on `trae`: the China deployment has
371
376
  // its own core host, authorization scheme, and model roster, and a CN account
372
377
  // dispatched to the intl host is refused rather than degraded.
@@ -392,6 +397,7 @@ const RAW_BUNDLED_PROVIDER_METADATA = [
392
397
  baseUrl: "https://api.individual.githubcopilot.com",
393
398
  hasAdapterUserAgent: true,
394
399
  credentialUrl: "https://github.com/settings/copilot",
400
+ defaultSkipProxy: true,
395
401
  },
396
402
  { id: "perplexity", displayName: "Perplexity", baseUrl: "https://api.perplexity.ai", credentialUrl: "https://www.perplexity.ai/settings/api" },
397
403
  // Native web-search providers expose only the search service. Their catalog
@@ -305,6 +305,29 @@ export interface ResolvedApiKey {
305
305
  /** Share-template id whose remote CLI mappings are inherited by this child. */
306
306
  readonly cliMappingOwnerId?: string;
307
307
  }
308
+
309
+ /**
310
+ * The 401 a caller gets when a key resolves to nothing.
311
+ *
312
+ * One function, not a message repeated at each boundary: three surfaces refuse
313
+ * an unknown key, and any wording drift between them is a caller guessing which
314
+ * surface it hit. Unknown, revoked and expired stay deliberately
315
+ * indistinguishable — distinguishing them would let a caller confirm that a
316
+ * guessed token once existed — so there is no variant carrying a reason.
317
+ *
318
+ * The rotate instruction is unconditional for the same reason. The key that
319
+ * fails is usually the one seeded at first boot, which cannot be deleted and
320
+ * only rotates, and an operator hitting this on a personal key loses nothing by
321
+ * being told the same thing.
322
+ */
323
+ export function unknownApiKeyError(): GatewayError {
324
+ return new GatewayError(
325
+ "authentication_failed",
326
+ 401,
327
+ "Invalid API key. Rotate the default Cartethyia API key in API Keys and send the new key as: Authorization: Bearer <key>.",
328
+ );
329
+ }
330
+
308
331
  /** Extracts exactly one supported credential source, preserving public auth statuses. */
309
332
  export function requestToken(headers: Headers | Record<string, string>): string {
310
333
  const get = (name: string): string | null =>
@@ -39,6 +39,27 @@ export async function shouldCaptureProviderBody(
39
39
  return payloadCaptureKeepsBodies(db, tenantId);
40
40
  }
41
41
 
42
+ /**
43
+ * Per-stage cap for the provider request/response tee at one capture depth.
44
+ *
45
+ * The tee is what the drawer's "Provider Request" and "Provider Response"
46
+ * panels read, so its cap is the most a provider panel can ever show. It used
47
+ * to be a flat 256 KiB at every depth: on a large request the translated
48
+ * provider body was clipped to 256 KiB before the shared record budget ever
49
+ * ran, so a tenant who raised the depth to Maximum — 16 MiB — still saw a
50
+ * quarter-megabyte provider request and an empty provider response. Derived
51
+ * from the same depth tiers as the record budget, so raising the depth now
52
+ * raises both.
53
+ *
54
+ * Capped at a quarter of the depth's total budget: four panels share it, and
55
+ * one of them must never be able to spend the whole thing.
56
+ */
57
+ export function providerCaptureMaxBytes(depth?: TelemetryPayloadDepth): number {
58
+ if (depth === "moderate") return 2 * 1024 * 1024;
59
+ if (depth === "maximum") return 4 * 1024 * 1024;
60
+ return 256 * 1024;
61
+ }
62
+
42
63
  /**
43
64
  * Same answer as {@link shouldCaptureProviderBody}, memoized on the request.
44
65
  *
@@ -62,6 +83,28 @@ export async function shouldCaptureProviderBodyForRequest(
62
83
  return resolved.ok === true && resolved.mode === "full";
63
84
  }
64
85
 
86
+ /**
87
+ * The tenant's capture depth, memoized on the request like the mode above.
88
+ *
89
+ * Read *after* {@link shouldCaptureProviderBodyForRequest} has populated the
90
+ * memo, so the two answers come from one preference read. It sizes the
91
+ * provider tee: a tenant who raised the depth is asking for more of every
92
+ * panel, and a flat cap would answer with the same clipped body regardless.
93
+ */
94
+ export async function resolveCaptureDepthForRequest(
95
+ db: CartethyiaDatabase,
96
+ tenantId: string | null,
97
+ state: { payloadCapturePreference?: unknown },
98
+ ): Promise<TelemetryPayloadDepth> {
99
+ if (state.payloadCapturePreference === undefined) {
100
+ state.payloadCapturePreference = await resolvePayloadCapture(db, tenantId);
101
+ }
102
+ const resolved = state.payloadCapturePreference as
103
+ | { readonly ok: true; readonly depth: TelemetryPayloadDepth }
104
+ | { readonly ok: false };
105
+ return resolved.ok === true ? resolved.depth : "minimum";
106
+ }
107
+
65
108
  /**
66
109
  * Combined body cap for one capture depth. Fixed size tiers: every depth
67
110
  * stores all four drawer panels (client request, translated provider
@@ -103,7 +146,6 @@ export function parseCapturedBody(value: unknown): unknown {
103
146
  }
104
147
  }
105
148
 
106
- const PROVIDER_CAPTURE_MAX_BYTES = 256 * 1024;
107
149
  const PROVIDER_CAPTURE_AWAIT_MS = 2_000;
108
150
  const CAPTURABLE_RESPONSE_TYPES = /^(text\/|application\/(json|x-ndjson|sse))/i;
109
151
 
@@ -258,6 +300,7 @@ export function captureProviderExchange(
258
300
  inner: ValidatedOutboundFetch,
259
301
  sink: ProviderExchangeCapture,
260
302
  captureBody = true,
303
+ maxBytes = providerCaptureMaxBytes(),
261
304
  ): ValidatedOutboundFetch {
262
305
  return async (input, init) => {
263
306
  const url =
@@ -276,14 +319,12 @@ export function captureProviderExchange(
276
319
  // unbounded translated request body per attempt — up to `maxAttempts` of
277
320
  // them, all alive until the loop returns — costs more than it can tell
278
321
  // an operator.
279
- ...(rawBody === null
280
- ? {}
281
- : { body: parseCapturedBody(rawBody.slice(0, PROVIDER_CAPTURE_MAX_BYTES)) }),
322
+ ...(rawBody === null ? {} : { body: parseCapturedBody(rawBody.slice(0, maxBytes)) }),
282
323
  };
283
324
  const response = await inner(input, init);
284
325
  sink.response =
285
326
  captureBody && shouldCaptureProviderResponse(response)
286
- ? readBoundedBody(response.clone(), PROVIDER_CAPTURE_MAX_BYTES).then(parseCapturedBody)
327
+ ? readBoundedBody(response.clone(), maxBytes).then(parseCapturedBody)
287
328
  : Promise.resolve(null);
288
329
  // Read regardless of `captureBody`: this is metadata about the dispatch, not
289
330
  // payload retention, so a tenant in `metadata` mode still records which
@@ -28,6 +28,8 @@ import {
28
28
  completionContext,
29
29
  estimatedUsage,
30
30
  parseCapturedBody,
31
+ providerCaptureMaxBytes,
32
+ resolveCaptureDepthForRequest,
31
33
  shouldCaptureProviderBodyForRequest,
32
34
  terminalFailure,
33
35
  } from "./attempt-finalize";
@@ -239,6 +241,13 @@ export async function handleProviderProxyRequest(
239
241
  prepared.authorization.snapshot.tenant_id,
240
242
  state,
241
243
  ),
244
+ providerCaptureMaxBytes(
245
+ await resolveCaptureDepthForRequest(
246
+ deps.db,
247
+ prepared.authorization.snapshot.tenant_id,
248
+ state,
249
+ ),
250
+ ),
242
251
  ),
243
252
  }
244
253
  : {}),
@@ -43,7 +43,12 @@ import type { AdmissionLease } from "../../../security/admission/contracts";
43
43
  import type { NetworkPoolSelector } from "../../../network/pool/selector";
44
44
  import type { TelemetryBatchBuffer } from "../../../observability/telemetry-buffer";
45
45
  import type { ProviderExchangeCapture } from "../attempt-finalize";
46
- import { captureProviderExchange, shouldCaptureProviderBodyForRequest } from "../attempt-finalize";
46
+ import {
47
+ captureProviderExchange,
48
+ providerCaptureMaxBytes,
49
+ resolveCaptureDepthForRequest,
50
+ shouldCaptureProviderBodyForRequest,
51
+ } from "../attempt-finalize";
47
52
  import { proxySuccessHeaders, buildUpstreamDispatchContext } from "../upstream";
48
53
  import type { AttemptLeaseOwnership } from "../leases";
49
54
  import type { ProviderContinuationScope } from "../../../providers/provider-contracts";
@@ -165,11 +170,20 @@ export async function dispatchStreamingAttempt(input: StreamingDispatchInput): P
165
170
  networkPoolId,
166
171
  prepared.authorization.snapshot.tenant_id,
167
172
  );
173
+ const captureBody = outboundFetch
174
+ ? await shouldCaptureProviderBodyForRequest(deps.db, prepared.authorization.snapshot.tenant_id, state)
175
+ : false;
176
+ const captureDepth = await resolveCaptureDepthForRequest(
177
+ deps.db,
178
+ prepared.authorization.snapshot.tenant_id,
179
+ state,
180
+ );
168
181
  const wrappedOutboundFetch = outboundFetch
169
182
  ? captureProviderExchange(
170
183
  outboundFetch,
171
184
  providerCapture,
172
- await shouldCaptureProviderBodyForRequest(deps.db, prepared.authorization.snapshot.tenant_id, state),
185
+ captureBody,
186
+ providerCaptureMaxBytes(captureDepth),
173
187
  )
174
188
  : undefined;
175
189
  const dispatchContext = buildUpstreamDispatchContext({
@@ -6,7 +6,7 @@ import { GatewayError } from "../../protocol/gateway-error";
6
6
  import type { ProxyRequestStateStore } from "../request/state";
7
7
  import { fastPathname } from "../pathname";
8
8
  import type { CartethyiaDatabase } from "../../persistence/db-handle";
9
- import { requestToken, resolveApiKeyAuthorization } from "../../security/api-key-auth";
9
+ import { requestToken, resolveApiKeyAuthorization, unknownApiKeyError } from "../../security/api-key-auth";
10
10
  import {
11
11
  deniedClientRouter,
12
12
  detectClientRouter,
@@ -65,8 +65,7 @@ export function createApiKeyAuthenticationMiddleware(deps: {
65
65
  "cartethyia",
66
66
  );
67
67
  });
68
- if (!authorization)
69
- throw new GatewayError("invalid_request", 401, "This API key is unknown, revoked, or expired. Issue a new key and send it as: Authorization: Bearer <key>.");
68
+ if (!authorization) throw unknownApiKeyError();
70
69
  const decision = createAccessDecision({
71
70
  id: authorization.id,
72
71
  tenantId: authorization.tenantId,