cartethyia 2.5.1 → 2.5.2

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 (208) 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-MsQaeQTs.js} +1 -1
  5. package/dist/dashboard/assets/BannedPage-MsQaeQTs.js.gz +0 -0
  6. package/dist/dashboard/assets/CliToolDetailPage-BO9nKg9w.js +1 -0
  7. package/dist/dashboard/assets/CliToolDetailPage-BO9nKg9w.js.gz +0 -0
  8. package/dist/dashboard/assets/{CliToolsPage-BXUaRCQQ.js → CliToolsPage-DfWHTJ7v.js} +1 -1
  9. package/dist/dashboard/assets/CliToolsPage-DfWHTJ7v.js.gz +0 -0
  10. package/dist/dashboard/assets/CombosPage-BrCBQDAc.js +1 -0
  11. package/dist/dashboard/assets/CombosPage-BrCBQDAc.js.gz +0 -0
  12. package/dist/dashboard/assets/ConsoleLogPage-C6Uh33er.js +1 -0
  13. package/dist/dashboard/assets/ConsoleLogPage-C6Uh33er.js.gz +0 -0
  14. package/dist/dashboard/assets/CustomizationPage-8aS9hyJu.js +1 -0
  15. package/dist/dashboard/assets/CustomizationPage-8aS9hyJu.js.gz +0 -0
  16. package/dist/dashboard/assets/HealthEventsModal-qomXThxX.js +1 -0
  17. package/dist/dashboard/assets/HealthEventsModal-qomXThxX.js.gz +0 -0
  18. package/dist/dashboard/assets/LoginPage-C8hKyXBe.js +1 -0
  19. package/dist/dashboard/assets/LoginPage-C8hKyXBe.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-C-ExCy1V.js +1 -0
  23. package/dist/dashboard/assets/OverviewPage-C-ExCy1V.js.gz +0 -0
  24. package/dist/dashboard/assets/ProviderDetailPage-tr811uaF.js +3 -0
  25. package/dist/dashboard/assets/ProviderDetailPage-tr811uaF.js.gz +0 -0
  26. package/dist/dashboard/assets/ProvidersPage-BS9IEpvv.js +1 -0
  27. package/dist/dashboard/assets/ProvidersPage-BS9IEpvv.js.gz +0 -0
  28. package/dist/dashboard/assets/ProxyPage-Rn_qXScb.js +7 -0
  29. package/dist/dashboard/assets/ProxyPage-Rn_qXScb.js.gz +0 -0
  30. package/dist/dashboard/assets/QuotaPage-BihkgQSq.js +1 -0
  31. package/dist/dashboard/assets/QuotaPage-BihkgQSq.js.gz +0 -0
  32. package/dist/dashboard/assets/SettingsPage-DhiEjm6T.js +1 -0
  33. package/dist/dashboard/assets/SettingsPage-DhiEjm6T.js.gz +0 -0
  34. package/dist/dashboard/assets/SetupPage-CXjUlfqr.js +1 -0
  35. package/dist/dashboard/assets/SetupPage-CXjUlfqr.js.gz +0 -0
  36. package/dist/dashboard/assets/SortableList-C-_oD3F2.js +1 -0
  37. package/dist/dashboard/assets/SortableList-C-_oD3F2.js.gz +0 -0
  38. package/dist/dashboard/assets/{StudioPage-DqK5E26W.js → StudioPage-u9IxmYOI.js} +3 -3
  39. package/dist/dashboard/assets/StudioPage-u9IxmYOI.js.gz +0 -0
  40. package/dist/dashboard/assets/UsagePage-BEVLVrwP.js +56 -0
  41. package/dist/dashboard/assets/UsagePage-BEVLVrwP.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-Dj9AR0gY.js +4 -0
  65. package/dist/dashboard/assets/entry-Dj9AR0gY.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-DCBtROoK.js} +2 -2
  73. package/dist/dashboard/assets/index-DCBtROoK.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-DsX5QT8K.js +41 -0
  91. package/dist/dashboard/assets/select-DsX5QT8K.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/src/boot/app.ts +3 -2
  107. package/src/console/observability/contracts.ts +11 -1
  108. package/src/console/observability/store.ts +9 -6
  109. package/src/providers/discovery/openrouter-free-roster.ts +3 -11
  110. package/src/providers/discovery/probe-phases.ts +42 -5
  111. package/src/providers/discovery/probing-service.ts +25 -2
  112. package/src/providers/integrations/claude/claude.ts +3 -1
  113. package/src/providers/integrations/cloudflareai/cloudflareai-models.ts +3 -1
  114. package/src/providers/integrations/inferhub.ts +3 -1
  115. package/src/providers/integrations/kilo/kilo.ts +0 -1
  116. package/src/providers/integrations/kiro/kiro-catalog.ts +8 -2
  117. package/src/providers/integrations/openrouter/provider.ts +5 -15
  118. package/src/security/api-key-auth.ts +23 -0
  119. package/src/transport/guards/auth-guard.ts +2 -3
  120. package/dist/dashboard/assets/AccountCooldown-ChpdzxRH.js.gz +0 -0
  121. package/dist/dashboard/assets/BannedPage-ChqnTRpZ.js.gz +0 -0
  122. package/dist/dashboard/assets/CliToolDetailPage-Bb9CNuoh.js +0 -1
  123. package/dist/dashboard/assets/CliToolDetailPage-Bb9CNuoh.js.gz +0 -0
  124. package/dist/dashboard/assets/CliToolsPage-BXUaRCQQ.js.gz +0 -0
  125. package/dist/dashboard/assets/CombosPage-CisNZOUt.js +0 -1
  126. package/dist/dashboard/assets/CombosPage-CisNZOUt.js.gz +0 -0
  127. package/dist/dashboard/assets/ConsoleLogPage-BOpI_LOH.js +0 -1
  128. package/dist/dashboard/assets/ConsoleLogPage-BOpI_LOH.js.gz +0 -0
  129. package/dist/dashboard/assets/CustomizationPage-DAkdvJ4k.js +0 -1
  130. package/dist/dashboard/assets/CustomizationPage-DAkdvJ4k.js.gz +0 -0
  131. package/dist/dashboard/assets/HealthEventsModal-DeT-VXHd.js +0 -1
  132. package/dist/dashboard/assets/HealthEventsModal-DeT-VXHd.js.gz +0 -0
  133. package/dist/dashboard/assets/LoginPage-CytxSeGP.js +0 -1
  134. package/dist/dashboard/assets/LoginPage-CytxSeGP.js.gz +0 -0
  135. package/dist/dashboard/assets/ModelPicker-oQ-SDXp8.js +0 -1
  136. package/dist/dashboard/assets/ModelPicker-oQ-SDXp8.js.gz +0 -0
  137. package/dist/dashboard/assets/OverviewPage-CU7S1wJs.js +0 -1
  138. package/dist/dashboard/assets/OverviewPage-CU7S1wJs.js.gz +0 -0
  139. package/dist/dashboard/assets/ProviderDetailPage-DLm2baot.js +0 -3
  140. package/dist/dashboard/assets/ProviderDetailPage-DLm2baot.js.gz +0 -0
  141. package/dist/dashboard/assets/ProvidersPage-DOXfuzfL.js +0 -1
  142. package/dist/dashboard/assets/ProvidersPage-DOXfuzfL.js.gz +0 -0
  143. package/dist/dashboard/assets/ProxyPage-wvk0ciQN.js +0 -7
  144. package/dist/dashboard/assets/ProxyPage-wvk0ciQN.js.gz +0 -0
  145. package/dist/dashboard/assets/QuotaPage-Cn_fcUHE.js +0 -1
  146. package/dist/dashboard/assets/QuotaPage-Cn_fcUHE.js.gz +0 -0
  147. package/dist/dashboard/assets/SettingsPage-8-eVadhc.js +0 -1
  148. package/dist/dashboard/assets/SettingsPage-8-eVadhc.js.gz +0 -0
  149. package/dist/dashboard/assets/SetupPage-uEIXPyGO.js +0 -1
  150. package/dist/dashboard/assets/SetupPage-uEIXPyGO.js.gz +0 -0
  151. package/dist/dashboard/assets/SortableList-DCY0TPTZ.js +0 -1
  152. package/dist/dashboard/assets/SortableList-DCY0TPTZ.js.gz +0 -0
  153. package/dist/dashboard/assets/StudioPage-DqK5E26W.js.gz +0 -0
  154. package/dist/dashboard/assets/UsagePage-38JfiZ0H.js +0 -56
  155. package/dist/dashboard/assets/UsagePage-38JfiZ0H.js.gz +0 -0
  156. package/dist/dashboard/assets/api-keys-BmDbsior.js.gz +0 -0
  157. package/dist/dashboard/assets/app-BbpSarVV.css +0 -2
  158. package/dist/dashboard/assets/app-BbpSarVV.css.gz +0 -0
  159. package/dist/dashboard/assets/badge-DpXT4Ixt.js +0 -1
  160. package/dist/dashboard/assets/badge-DpXT4Ixt.js.gz +0 -0
  161. package/dist/dashboard/assets/banner-CSAAWLJv.js +0 -1
  162. package/dist/dashboard/assets/banner-CSAAWLJv.js.gz +0 -0
  163. package/dist/dashboard/assets/card-DEhRXXw4.js +0 -1
  164. package/dist/dashboard/assets/card-DEhRXXw4.js.gz +0 -0
  165. package/dist/dashboard/assets/check-D69FzdvC.js +0 -1
  166. package/dist/dashboard/assets/check-D69FzdvC.js.gz +0 -0
  167. package/dist/dashboard/assets/cli-tools-B0wBq-RL.js.gz +0 -0
  168. package/dist/dashboard/assets/clipboard-button-DOo6EU8O.js +0 -1
  169. package/dist/dashboard/assets/clipboard-button-DOo6EU8O.js.gz +0 -0
  170. package/dist/dashboard/assets/copy-ngmQ9zcZ.js +0 -1
  171. package/dist/dashboard/assets/copy-ngmQ9zcZ.js.gz +0 -0
  172. package/dist/dashboard/assets/dialog-BvQeBMfP.js +0 -1
  173. package/dist/dashboard/assets/dialog-BvQeBMfP.js.gz +0 -0
  174. package/dist/dashboard/assets/drawer-CMwPXwQB.js +0 -1
  175. package/dist/dashboard/assets/drawer-CMwPXwQB.js.gz +0 -0
  176. package/dist/dashboard/assets/entry--yj84zk3.js +0 -4
  177. package/dist/dashboard/assets/entry--yj84zk3.js.gz +0 -0
  178. package/dist/dashboard/assets/entry-D4O2229X.js.gz +0 -0
  179. package/dist/dashboard/assets/entry-DKvr5Q9K.js +0 -1
  180. package/dist/dashboard/assets/entry-DKvr5Q9K.js.gz +0 -0
  181. package/dist/dashboard/assets/github-badge-zliLvBwr.js +0 -1
  182. package/dist/dashboard/assets/github-badge-zliLvBwr.js.gz +0 -0
  183. package/dist/dashboard/assets/index-CcD1eSJ6.js.gz +0 -0
  184. package/dist/dashboard/assets/inline-D1IlZZ8H.js +0 -1
  185. package/dist/dashboard/assets/inline-D1IlZZ8H.js.gz +0 -0
  186. package/dist/dashboard/assets/input-BpVZdrbc.js +0 -1
  187. package/dist/dashboard/assets/input-BpVZdrbc.js.gz +0 -0
  188. package/dist/dashboard/assets/layout-BioLW7gN.js +0 -1
  189. package/dist/dashboard/assets/layout-BioLW7gN.js.gz +0 -0
  190. package/dist/dashboard/assets/list-row-CwVcGAIc.js +0 -1
  191. package/dist/dashboard/assets/list-row-CwVcGAIc.js.gz +0 -0
  192. package/dist/dashboard/assets/live-yfEJFF9T.js.gz +0 -0
  193. package/dist/dashboard/assets/model-bans-CT-TxSNX.js.gz +0 -0
  194. package/dist/dashboard/assets/numbers-CFL4_Dqc.js +0 -1
  195. package/dist/dashboard/assets/numbers-CFL4_Dqc.js.gz +0 -0
  196. package/dist/dashboard/assets/routing-Cs6jl49L.js.gz +0 -0
  197. package/dist/dashboard/assets/select-XPJ-dIw3.js +0 -41
  198. package/dist/dashboard/assets/select-XPJ-dIw3.js.gz +0 -0
  199. package/dist/dashboard/assets/settings-CfcTcOyU.js.gz +0 -0
  200. package/dist/dashboard/assets/stack-C9_uUjaA.js +0 -1
  201. package/dist/dashboard/assets/stack-C9_uUjaA.js.gz +0 -0
  202. package/dist/dashboard/assets/state-DA0B95UQ.js +0 -1
  203. package/dist/dashboard/assets/state-DA0B95UQ.js.gz +0 -0
  204. package/dist/dashboard/assets/switch-Cittxy-x.js +0 -1
  205. package/dist/dashboard/assets/switch-Cittxy-x.js.gz +0 -0
  206. package/dist/dashboard/assets/toast-CNt_jzO4.js +0 -2
  207. package/dist/dashboard/assets/toast-CNt_jzO4.js.gz +0 -0
  208. package/dist/dashboard/assets/useMutation-BQKMcJ1H.js.gz +0 -0
@@ -1,75 +1,73 @@
1
1
  {
2
- "count": 117,
2
+ "count": 114,
3
3
  "assets": [
4
- "assets\\AccountCooldown-ChpdzxRH.js",
5
- "assets\\BannedPage-ChqnTRpZ.js",
6
- "assets\\CliToolDetailPage-Bb9CNuoh.js",
7
- "assets\\CliToolsPage-BXUaRCQQ.js",
8
- "assets\\CombosPage-CisNZOUt.js",
9
- "assets\\ConsoleLogPage-BOpI_LOH.js",
10
- "assets\\CustomizationPage-DAkdvJ4k.js",
11
- "assets\\HealthEventsModal-DeT-VXHd.js",
12
- "assets\\LoginPage-CytxSeGP.js",
13
- "assets\\ModelPicker-oQ-SDXp8.js",
14
- "assets\\OverviewPage-CU7S1wJs.js",
15
- "assets\\ProviderDetailPage-DLm2baot.js",
4
+ "assets\\AccountCooldown-Bjo92Qat.js",
5
+ "assets\\BannedPage-MsQaeQTs.js",
6
+ "assets\\CliToolDetailPage-BO9nKg9w.js",
7
+ "assets\\CliToolsPage-DfWHTJ7v.js",
8
+ "assets\\CombosPage-BrCBQDAc.js",
9
+ "assets\\ConsoleLogPage-C6Uh33er.js",
10
+ "assets\\CustomizationPage-8aS9hyJu.js",
11
+ "assets\\HealthEventsModal-qomXThxX.js",
12
+ "assets\\LoginPage-C8hKyXBe.js",
13
+ "assets\\ModelPicker-CWkTUtpX.js",
14
+ "assets\\OverviewPage-C-ExCy1V.js",
15
+ "assets\\ProviderDetailPage-tr811uaF.js",
16
16
  "assets\\ProviderIcon-CmfzxpVM.js",
17
- "assets\\ProvidersPage-DOXfuzfL.js",
18
- "assets\\ProxyPage-wvk0ciQN.js",
19
- "assets\\QuotaPage-Cn_fcUHE.js",
20
- "assets\\SettingsPage-8-eVadhc.js",
21
- "assets\\SetupPage-uEIXPyGO.js",
22
- "assets\\SortableList-DCY0TPTZ.js",
23
- "assets\\StudioPage-DqK5E26W.js",
17
+ "assets\\ProvidersPage-BS9IEpvv.js",
18
+ "assets\\ProxyPage-Rn_qXScb.js",
19
+ "assets\\QuotaPage-BihkgQSq.js",
20
+ "assets\\SettingsPage-DhiEjm6T.js",
21
+ "assets\\SetupPage-CXjUlfqr.js",
22
+ "assets\\SortableList-C-_oD3F2.js",
23
+ "assets\\StudioPage-u9IxmYOI.js",
24
24
  "assets\\ToolIcon-K1ckcDKg.js",
25
- "assets\\UsagePage-38JfiZ0H.js",
26
- "assets\\api-keys-BmDbsior.js",
27
- "assets\\app-BbpSarVV.css",
28
- "assets\\app-C-Y4oZT0.js",
25
+ "assets\\UsagePage-BEVLVrwP.js",
26
+ "assets\\api-keys-uNywYlfv.js",
27
+ "assets\\app-CafWtVXA.js",
28
+ "assets\\app-DxivpfYG.css",
29
29
  "assets\\arrow-left-CpBxkoxI.js",
30
30
  "assets\\arrow-right-Dl5yKnp8.js",
31
- "assets\\assert-BHfaei-X.js",
31
+ "assets\\assert-NHwYq95t.js",
32
32
  "assets\\backup-file-gdKIlg8U.js",
33
- "assets\\badge-DpXT4Ixt.js",
34
- "assets\\banner-CSAAWLJv.js",
33
+ "assets\\badge-6uzDDvqs.js",
34
+ "assets\\banner-4_WNg2WZ.js",
35
35
  "assets\\brain-Cwed_72z.js",
36
- "assets\\card-DEhRXXw4.js",
37
- "assets\\check-D69FzdvC.js",
36
+ "assets\\card--AtFedAq.js",
38
37
  "assets\\chevron-right-BXIy5l3h.js",
39
38
  "assets\\circle-alert-CntYHV5v.js",
40
39
  "assets\\circle-check-BU3v_mFW.js",
41
- "assets\\cli-tools-B0wBq-RL.js",
42
- "assets\\clipboard-button-DOo6EU8O.js",
40
+ "assets\\cli-tools-B3e7dx3N.js",
41
+ "assets\\clipboard-button-NreUx6c8.js",
43
42
  "assets\\cog-CdWhsajs.js",
44
- "assets\\copy-ngmQ9zcZ.js",
45
- "assets\\dialog-BvQeBMfP.js",
43
+ "assets\\dialog-BtpsJU98.js",
46
44
  "assets\\dialog-actions-B1axpEg7.js",
47
45
  "assets\\download-A0SW9OCW.js",
48
46
  "assets\\download-dRFo5_Wu.js",
49
- "assets\\drawer-CMwPXwQB.js",
50
- "assets\\entry--yj84zk3.js",
51
- "assets\\entry-D4O2229X.js",
52
- "assets\\entry-DKvr5Q9K.js",
47
+ "assets\\drawer-WwwtzihF.js",
48
+ "assets\\entry-Dj9AR0gY.js",
49
+ "assets\\entry-TJIye_jT.js",
50
+ "assets\\entry-jy9_4-Ke.js",
53
51
  "assets\\eye-C_hawfH4.js",
54
52
  "assets\\eye-off-Bk5g2luF.js",
55
53
  "assets\\fingerprint-pattern-BWuHtbW4.js",
56
54
  "assets\\gauge-BbIMy7MB.js",
57
- "assets\\github-badge-zliLvBwr.js",
55
+ "assets\\github-badge-D_eM3IgG.js",
58
56
  "assets\\image-BPaouViG.js",
59
- "assets\\index-CcD1eSJ6.js",
60
- "assets\\inline-D1IlZZ8H.js",
61
- "assets\\input-BpVZdrbc.js",
57
+ "assets\\index-DCBtROoK.js",
58
+ "assets\\inline-BcNGF8Gr.js",
59
+ "assets\\input-BG41et9G.js",
62
60
  "assets\\jsx-runtime-CXUMngI_.js",
63
61
  "assets\\key-round-UkavsGxp.js",
64
62
  "assets\\layers-2-glBbjl.js",
65
- "assets\\layout-BioLW7gN.js",
66
- "assets\\list-row-CwVcGAIc.js",
67
- "assets\\live-yfEJFF9T.js",
63
+ "assets\\layout-C8mer_Ak.js",
64
+ "assets\\list-row-Gh_W4OR_.js",
65
+ "assets\\live-BJGYRBnR.js",
68
66
  "assets\\log-in-BmRjy60c.js",
69
67
  "assets\\message-circle-CCxGwRvv.js",
70
- "assets\\model-bans-CT-TxSNX.js",
68
+ "assets\\model-bans-DeVLDA7I.js",
71
69
  "assets\\mutation-BkoLM5Ba.js",
72
- "assets\\numbers-CFL4_Dqc.js",
70
+ "assets\\numbers-Bs4LOOuf.js",
73
71
  "assets\\pencil-768ysRAr.js",
74
72
  "assets\\plus-DIdX_g-x.js",
75
73
  "assets\\power-off-Yjw9GMJh.js",
@@ -96,26 +94,25 @@
96
94
  "assets\\refresh-cw-BqJFk4sU.js",
97
95
  "assets\\rotate-ccw-CufcqITi.js",
98
96
  "assets\\route-CNSNj9Zm.js",
99
- "assets\\routing-Cs6jl49L.js",
97
+ "assets\\routing-CcusqXA7.js",
100
98
  "assets\\scaling-nWE4Cx8f.js",
101
99
  "assets\\search-JOKus9rb.js",
102
- "assets\\select-XPJ-dIw3.js",
103
- "assets\\settings-CfcTcOyU.js",
100
+ "assets\\select-DsX5QT8K.js",
101
+ "assets\\settings-mpmXTgUa.js",
104
102
  "assets\\shield-check-DVS0Foqh.js",
105
103
  "assets\\sparkles-BRaRUxAU.js",
106
- "assets\\stack-C9_uUjaA.js",
107
- "assets\\state-DA0B95UQ.js",
104
+ "assets\\stack-DwLmjUFK.js",
105
+ "assets\\state-DYdQmdAl.js",
108
106
  "assets\\stream-retry-5QnVuSSo.js",
109
- "assets\\switch-Cittxy-x.js",
107
+ "assets\\switch-CidSfbG7.js",
110
108
  "assets\\terminal-Bd4ee8TA.js",
111
- "assets\\toast-CNt_jzO4.js",
112
109
  "assets\\tokens-DDxMbSRL.js",
113
110
  "assets\\trash-Cppn5c3R.js",
114
111
  "assets\\use-clipboard-B0Sy03fE.js",
115
112
  "assets\\use-presence-BSZEBx7U.js",
116
113
  "assets\\use-reduced-motion-C4B0R6Sz.js",
117
114
  "assets\\use-timeout-BwcBBRrT.js",
118
- "assets\\useMutation-BQKMcJ1H.js",
115
+ "assets\\useMutation-w4u4CVue.js",
119
116
  "assets\\wrench-B6E1oEuY.js",
120
117
  "index.html"
121
118
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cartethyia",
3
- "version": "2.5.1",
3
+ "version": "2.5.2",
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",
package/src/boot/app.ts CHANGED
@@ -15,6 +15,7 @@ import { GatewayError, publicGatewayErrorBody } from "../protocol/gateway-error"
15
15
  import { shutdownError, shutdownNotice } from "../transport/shutdown-notice";
16
16
  import type { CanonicalAdapter } from "../transport/contracts";
17
17
  import type { ApiKeyAuthorizationSnapshot } from "../security/api-key-auth";
18
+ import { unknownApiKeyError } from "../security/api-key-auth";
18
19
  import { getDbHandle } from "../persistence/boot";
19
20
  import { isPgHandle } from "../persistence/db-pg";
20
21
  import { resolveRedisClient } from "../persistence/redis";
@@ -408,7 +409,7 @@ export function createGatewayApp(deps: GatewayAppDeps) {
408
409
  const handleModelsList = async ({ request }: { request: Request }): Promise<Response> => {
409
410
  const state = requestStateStore.require(request);
410
411
  const auth = state.authorization;
411
- if (!auth) 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>.");
412
+ if (!auth) throw unknownApiKeyError();
412
413
  const data = await queryAllowedModels(auth);
413
414
  return new Response(JSON.stringify({ object: "list", data }), {
414
415
  headers: {
@@ -431,7 +432,7 @@ export function createGatewayApp(deps: GatewayAppDeps) {
431
432
  }): Promise<Response> => {
432
433
  const state = requestStateStore.require(request);
433
434
  const auth = state.authorization;
434
- if (!auth) 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>.");
435
+ if (!auth) throw unknownApiKeyError();
435
436
  const raw = params?.["*"] ?? params?.id ?? "";
436
437
  const targetId = new URL(request.url).searchParams.get("id") ?? raw;
437
438
  const found = await publicModelStore.getPublicModelDetail(
@@ -45,7 +45,17 @@ export interface SystemHealthResponse {
45
45
  * gateway itself grew.
46
46
  */
47
47
  database_in_process: boolean;
48
- cpu_percent: number;
48
+ /**
49
+ * `null` when the platform does not report it — there is no `loadavg` on
50
+ * Windows and Node hands back a zeroed tuple rather than failing, so on
51
+ * Windows this is always `null` rather than a confident-looking `0`.
52
+ *
53
+ * Nullable rather than `NaN`: `NaN` is not a JSON value and serializes to
54
+ * `null` anyway, which means a client validating the field as a number
55
+ * rejects the *whole* health payload. One unavailable metric then reads as
56
+ * every metric being unavailable.
57
+ */
58
+ cpu_percent: number | null;
49
59
  cpu_cores: number;
50
60
  pid: number;
51
61
  platform: string;
@@ -330,12 +330,15 @@ export class DrizzleObservabilityStore implements ObservabilityStore {
330
330
  // syscall and Node reports a zeroed tuple rather than failing. Divided by
331
331
  // cores that is a confident-looking 0.0% on a busy gateway, which is worse
332
332
  // than saying nothing: an operator reads it as "idle" and stops looking.
333
- // Reported as unavailable instead of as a measurement.
334
- const load = loadavg()[0] ?? 0;
335
- const cpuPercent =
336
- process.platform === "win32" || load === 0
337
- ? Number.NaN
338
- : Math.round((load / Math.max(cpus().length, 1)) * 10000) / 100;
333
+ // Reported as null instead of as a measurement.
334
+ //
335
+ // A real zero load is *not* the same thing and is reported as zero: an
336
+ // idle machine genuinely has nothing queued, and collapsing that into
337
+ // "unavailable" would hide a number the platform did supply.
338
+ const cpuPercent: number | null =
339
+ process.platform === "win32"
340
+ ? null
341
+ : Math.round(((loadavg()[0] ?? 0) / Math.max(cpus().length, 1)) * 10000) / 100;
339
342
 
340
343
  let databaseHealthy = true;
341
344
  let total = 0;
@@ -2,7 +2,7 @@
2
2
  * The free tier shared by the OpenRouter-backed providers.
3
3
  *
4
4
  * Cline, Kilo Code and OpenRouter serve one model catalog: the same 469 ids,
5
- * and the same 17 of them carrying a `:free` suffix. Verified against all three
5
+ * and the same 9 of them carrying a `:free` suffix. Verified against all three
6
6
  * live listings — every id below is present under `openrouter`, `kilo` and
7
7
  * `cline` — so this roster is a catalog fact, not a per-provider guess, and it
8
8
  * lives in one place rather than being restated three times.
@@ -37,10 +37,10 @@ interface FreeRow {
37
37
  }
38
38
 
39
39
  /**
40
- * The 17 `:free` ids, with the limits the catalog states for each.
40
+ * The 9 `:free` ids, with the limits the catalog states for each.
41
41
  *
42
42
  * Every row declares `reasoning`: `supported_parameters` lists it on all
43
- * seventeen. `toolCall` is `true` unless stated otherwise, because a row that
43
+ * nine. `toolCall` is `true` unless stated otherwise, because a row that
44
44
  * advertises tools must keep them — a false `toolCall` makes the preflight
45
45
  * strip `tools` from the request, which is a wrong capability rather than a
46
46
  * conservative one.
@@ -48,8 +48,6 @@ interface FreeRow {
48
48
  const FREE_ROWS: readonly FreeRow[] = [
49
49
  { id: "cohere/north-mini-code:free", ctx: 256_000, out: 64_000 },
50
50
  { id: "dots-studio/dots-3-note-preview:free", ctx: 512_000, out: 460_800, vision: true },
51
- { id: "google/gemma-4-26b-a4b-it:free", ctx: 262_144, out: 32_768, vision: true },
52
- { id: "google/gemma-4-31b-it:free", ctx: 262_144, out: 32_768, vision: true },
53
51
  { id: "liquid/lfm-2.5-2.6b:free", ctx: 65_536, out: 8_192 },
54
52
  { id: "minimax/minimax-m3.1-flash:free", ctx: 1_000_000, out: 524_288, vision: true },
55
53
  {
@@ -58,7 +56,6 @@ const FREE_ROWS: readonly FreeRow[] = [
58
56
  out: 65_536,
59
57
  vision: true,
60
58
  },
61
- { id: "nvidia/nemotron-3-super-120b-a12b:free", ctx: 262_144, out: 235_929 },
62
59
  { id: "nvidia/nemotron-3-ultra-550b-a55b:free", ctx: 1_000_000, out: 65_536 },
63
60
  {
64
61
  id: "nvidia/nemotron-3.5-content-safety:free",
@@ -69,11 +66,6 @@ const FREE_ROWS: readonly FreeRow[] = [
69
66
  },
70
67
  { id: "nvidia/nemotron-3.5-lightning:free", ctx: 1_000_000, out: 65_536 },
71
68
  { id: "nvidia/nemotron-3.5-super-vl:free", ctx: 1_000_000, out: 131_072, vision: true },
72
- { id: "poolside/laguna-s-2.1:free", ctx: 262_144, out: 32_768 },
73
- { id: "poolside/laguna-xs-2.1:free", ctx: 262_144, out: 32_768 },
74
- { id: "thinkingmachines/inkling-small:free", ctx: 1_048_576, out: 262_144, vision: true },
75
- { id: "thinkingmachines/inkling:free", ctx: 1_048_576, out: 262_144, vision: true },
76
- { id: "xingchen-agi/xing4.0-29b-a4b:free", ctx: 262_144, out: 235_929 },
77
69
  ];
78
70
 
79
71
  /** Builds this roster for one provider, since each spells its own endpoint. */
@@ -37,6 +37,7 @@ import { resolveByokWireProfile } from "../operations/byok-wire-profile";
37
37
  import { recordAccountFailure, recordAccountSuccess } from "../operations/account-health-service";
38
38
  import { buildCapabilityProfile } from "../../transport/routing/route-catalog";
39
39
  import { classifyUpstreamFailure } from "../../transport/failure-policy";
40
+ import { terminalFailure } from "../../transport/dispatch/attempt-finalize";
40
41
  import { GatewayError } from "../../protocol/gateway-error";
41
42
  import type { ModelDefinition } from "../provider-model-definition";
42
43
  import type { ProviderAdapter, ProviderId } from "../provider-contracts";
@@ -579,13 +580,33 @@ export async function recordProbeHealth(args: {
579
580
  readonly invalidator: { invalidate(): unknown } | undefined;
580
581
  readonly accountId: string | undefined;
581
582
  readonly dispatchError: unknown;
583
+ /**
584
+ * The probe's canonical events, when there are any.
585
+ *
586
+ * Needed because some adapters deliver a refusal as a terminal event rather
587
+ * than as a thrown error — Cursor's `resource_exhausted` is the case — so
588
+ * `dispatchError` stays `undefined` for a Run the upstream refused. Without
589
+ * the events, that account looked like a success and was recovered.
590
+ */
591
+ readonly events?: readonly CanonicalEvent[] | undefined;
582
592
  readonly modelId: string;
583
593
  readonly upstreamStatusCode: number | undefined;
584
594
  }): Promise<void> {
585
595
  const { db, invalidator, accountId, dispatchError, modelId, upstreamStatusCode } = args;
586
- if (dispatchError !== undefined && accountId) {
596
+ // A terminal `failed` state is written only by the upstream decoders, so it
597
+ // is the upstream refusing — the same thing a thrown error would have meant.
598
+ const terminal = args.events?.find((event) => event.type === "terminal");
599
+ // Reconstructed through the very function the live dispatch path uses, so a
600
+ // refusal carries the same code, status, and health classification in both.
601
+ // Hand-rolling a category here is how the two paths drifted apart.
602
+ const refusedUpstream =
603
+ terminal?.type === "terminal" && terminal.state === "failed"
604
+ ? terminalFailure(terminal)
605
+ : undefined;
606
+ const failure = dispatchError ?? refusedUpstream;
607
+ if (failure !== undefined && accountId) {
587
608
  const failureEvidence = {
588
- ...classifyUpstreamFailure(dispatchError),
609
+ ...classifyUpstreamFailure(failure),
589
610
  modelId,
590
611
  ...(upstreamStatusCode === undefined ? {} : { statusCode: upstreamStatusCode }),
591
612
  };
@@ -593,7 +614,7 @@ export async function recordProbeHealth(args: {
593
614
  // already classified as `rate_limit_transient` with a real cooldown by
594
615
  // `classifyAccountError`, so writing a status here only clobbered that
595
616
  // cooldown with a state nothing recovers from.
596
- await recordAccountFailure(db, accountId, dispatchError, failureEvidence)
617
+ await recordAccountFailure(db, accountId, failure, failureEvidence)
597
618
  .then((classification) => {
598
619
  if (classification?.mutatesAccount) void invalidator?.invalidate();
599
620
  })
@@ -603,7 +624,7 @@ export async function recordProbeHealth(args: {
603
624
  // (a cooling account flips back to active instead of staying stale).
604
625
  // recordAccountSuccess is a no-op write for already-healthy accounts and
605
626
  // never throws.
606
- if (dispatchError === undefined && accountId) {
627
+ if (failure === undefined && accountId) {
607
628
  await recordAccountSuccess(db, accountId)
608
629
  .then((recovered) => {
609
630
  if (recovered) void invalidator?.invalidate();
@@ -703,7 +724,23 @@ export function computeProbeVerdict(args: {
703
724
  errorMessage = `${wireFamily} — ${tagged}${upstreamReq ? ` [req ${upstreamReq}]` : ""}`;
704
725
 
705
726
  } else if (terminal?.type === "terminal" && terminal.state !== "complete") {
706
- errorMessage = terminal.stop_reason ?? "Model probe failed";
727
+ // Read the terminal's own detail before falling back to the stop reason.
728
+ //
729
+ // `stop_reason` is a canonical one-word label — "error" — so reporting it
730
+ // alone turned a Cursor quota refusal into a toast whose entire body was
731
+ // the word "error". The upstream sentence an operator needs is in
732
+ // `stop_details`, which is where the adapter parks it and where the live
733
+ // dispatch path already reads it (see `terminalFailure` in
734
+ // `attempt-finalize.ts`). The probe path disagreed with the dispatch path
735
+ // about the same provider response.
736
+ const detail = terminal.stop_details;
737
+ const message =
738
+ typeof detail?.["message"] === "string" && detail["message"].trim().length > 0
739
+ ? detail["message"].trim()
740
+ : undefined;
741
+ const code = typeof detail?.["code"] === "string" ? detail["code"] : undefined;
742
+ const tag = code === undefined ? "" : ` (${code})`;
743
+ errorMessage = `${message ?? terminal.stop_reason ?? "Model probe failed"}${tag}`;
707
744
  } else {
708
745
  errorMessage = undefined;
709
746
  }
@@ -274,7 +274,13 @@ export class ProviderProbingService {
274
274
  // The same wrapper a real dispatch uses, so the probe reads the model the
275
275
  // upstream named from the raw bytes rather than from a synthesized event.
276
276
  const probeFetch = createProbeFetch(
277
- captureProviderExchange(outbound.fetch, probeCapture, false),
277
+ // Body capture on, unlike a live dispatch: a probe is one-shot and
278
+ // operator-initiated, so it exists to be inspected. With it off, the
279
+ // provider-response tee resolved to null and every probe row reported
280
+ // the middle two stages as absent — a constant, not a measurement,
281
+ // which made the stage markers useless for the diagnosis they exist
282
+ // for. The 256 KiB cap in the tee bounds the cost.
283
+ captureProviderExchange(outbound.fetch, probeCapture, true),
278
284
  );
279
285
  try {
280
286
  if (serviceKind === "websearch") {
@@ -435,6 +441,11 @@ export class ProviderProbingService {
435
441
  invalidator: this.snapshotInvalidator,
436
442
  accountId,
437
443
  dispatchError,
444
+ // Passed so a refusal delivered as a terminal event — not as a thrown
445
+ // error — still reaches the health machine. Without it, Cursor's
446
+ // `resource_exhausted` left `dispatchError` undefined and the probe
447
+ // recovered an exhausted account.
448
+ ...(events === undefined ? {} : { events }),
438
449
  modelId,
439
450
  upstreamStatusCode,
440
451
  });
@@ -496,12 +507,24 @@ export class ProviderProbingService {
496
507
  ...(tokensPerSec !== undefined ? { tokensPerSec } : {}),
497
508
  });
498
509
  if (this.telemetryBuffer && payloadCaptureEnabled) {
510
+ // All four surfaces, so the row's stage markers mean the same thing here
511
+ // as they do on a live request: which hop actually produced bytes.
512
+ const providerRequest = probeCapture?.request ?? null;
513
+ const providerResponse = await probeCapture?.response.catch(() => null) ?? null;
499
514
  await this.payloadCapture
500
515
  .capture({
501
516
  tenantId,
502
517
  requestId,
503
518
  requestBody: capturedRequest,
504
- responseBody: events,
519
+ // `events` is what the *client* receives, so it belongs in the client
520
+ // surface. Passing it as `responseBody` lit the client bit on probes
521
+ // whose event list was empty, over a body that was never captured.
522
+ // The legacy alias is explicitly absent here; `null` leaves bit 8 to
523
+ // be decided by the real client body alone.
524
+ responseBody: null,
525
+ ...(events === undefined ? {} : { clientResponseBody: events }),
526
+ ...(providerRequest === null ? {} : { providerRequestBody: providerRequest }),
527
+ ...(providerResponse === null ? {} : { providerResponseBody: providerResponse }),
505
528
  scope: "tenant",
506
529
  tenantOptIn: true,
507
530
  })
@@ -96,7 +96,9 @@ export const CLAUDE_MODELS: readonly ModelDefinition[] = [
96
96
  claudeMessagesModel("claude-sonnet-5", 1_000_000, 128_000, true),
97
97
  claudeMessagesModel("claude-sonnet-4-6", 1_000_000, 128_000, true),
98
98
  claudeMessagesModel("claude-opus-4-5", 200_000, 64_000, true),
99
- claudeMessagesModel("claude-sonnet-4-5", 1_000_000, 64_000, true),
99
+ // 200K: the 4.5 trio predates the million-token window, which arrived with
100
+ // Opus 4.6. Only this row claimed 1M while its own siblings said 200K.
101
+ claudeMessagesModel("claude-sonnet-4-5", 200_000, 64_000, true),
100
102
  claudeMessagesModel("claude-haiku-4-5", 200_000, 64_000, true),
101
103
  ];
102
104
 
@@ -59,7 +59,9 @@ export const CLOUDFLAREAI_MODELS: readonly ModelDefinition[] = [
59
59
  ["claude-opus-4.7", 1_000_000, 128_000] as const,
60
60
  ["claude-opus-4.8", 1_000_000, 128_000] as const,
61
61
  ["claude-opus-5", 1_000_000, 128_000] as const,
62
- ["claude-sonnet-4.5", 1_000_000, 64_000] as const,
62
+ // 200K, matching the other two 4.5 rows: the million-token window starts at
63
+ // Opus 4.6, so the 4.5 trio is 200K across the board.
64
+ ["claude-sonnet-4.5", 200_000, 64_000] as const,
63
65
  ["claude-sonnet-4.6", 1_000_000, 128_000] as const,
64
66
  ["claude-sonnet-5", 1_000_000, 128_000] as const,
65
67
  ].map(cloudflareaiModel);
@@ -124,7 +124,9 @@ const INFERHUB_MODELS_RAW: readonly {
124
124
  { id: "cc/claude-opus-4-8", contextLimit: 1000000, outputLimit: 128000 },
125
125
  { id: "cc/claude-opus-5", contextLimit: 1000000, outputLimit: 128000 },
126
126
  { id: "cc/claude-sonnet-4-5", contextLimit: 200000, outputLimit: 64000 },
127
- { id: "cc/claude-sonnet-4-6", contextLimit: 1000000, outputLimit: 64000 },
127
+ // 128K: Sonnet 4.6 is in the 1M/128K generation, the same as the Opus 4.6
128
+ // row directly above it and every other 4.6+ row here.
129
+ { id: "cc/claude-sonnet-4-6", contextLimit: 1000000, outputLimit: 128000 },
128
130
  { id: "cc/claude-sonnet-5", contextLimit: 1000000, outputLimit: 128000 },
129
131
  // cb/ — CodeBuddy (12 models)
130
132
  { id: "cb/claude-opus-4.6", contextLimit: 1000000, outputLimit: 128000 },
@@ -87,7 +87,6 @@ export const KILO_MODELS: readonly ModelDefinition[] = [
87
87
  // window the router may select into rather than one model's.
88
88
  kiloModel("kilo-auto/free", 256_000, 32_768, { reasoning: true }),
89
89
  kiloModel("kilo-auto/small", 262_144, 32_768, { vision: true, reasoning: true }),
90
- kiloModel("openrouter/auto", 2_000_000, 128_000, { reasoning: true }),
91
90
  ];
92
91
 
93
92
  /**
@@ -71,9 +71,15 @@ export const KIRO_MODELS: readonly ModelDefinition[] = [
71
71
  kiroModel({ id: "claude-opus-4.5", ctx: 200_000, out: 64_000, vision: true, reasoning: true, toolCall: true }),
72
72
  kiroModel({ id: "claude-sonnet-5", ctx: 1_000_000, out: 128_000, vision: true, reasoning: true, toolCall: true }),
73
73
  kiroModel({ id: "claude-sonnet-5.5", ctx: 1_000_000, out: 128_000, vision: true, reasoning: true, toolCall: true }),
74
- kiroModel({ id: "claude-sonnet-4.5", ctx: 1_000_000, out: 64_000, vision: true, reasoning: true, toolCall: true }),
74
+ // 200K, not the 1M the 1M-window models use: Sonnet 4.5 predates it. The
75
+ // million-token window arrived with Opus 4.6 and the 5.x generation; the 4.5
76
+ // trio is 200K, and Opus and Haiku here already say so.
77
+ kiroModel({ id: "claude-sonnet-4.5", ctx: 200_000, out: 64_000, vision: true, reasoning: true, toolCall: true }),
75
78
  kiroModel({ id: "claude-haiku-4.5", ctx: 200_000, out: 64_000, vision: true, reasoning: true, toolCall: true }),
76
- kiroModel({ id: "claude-haiku-5.5", ctx: 200_000, out: 64_000, vision: true, reasoning: true, toolCall: true }),
79
+ // 1M/128K, not the 200K/64K of the 4.5 row above it: Haiku moved to the
80
+ // million-token window and the doubled output ceiling with the rest of the
81
+ // 5.x generation, and this line had been copied from its predecessor.
82
+ kiroModel({ id: "claude-haiku-5.5", ctx: 1_000_000, out: 128_000, vision: true, reasoning: true, toolCall: true }),
77
83
  kiroModel({ id: "gpt-5.5", ctx: 272_000, out: 128_000, reasoning: true, toolCall: true }),
78
84
  kiroModel({ id: "gpt-5.6-sol", ctx: 272_000, out: 128_000, reasoning: true, toolCall: true }),
79
85
  kiroModel({ id: "gpt-5.6-terra", ctx: 272_000, out: 128_000, reasoning: true, toolCall: true }),
@@ -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.
@@ -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 =>
@@ -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,
@@ -1 +0,0 @@
1
- import{n as e,o as t,r as n,t as r}from"./jsx-runtime-CXUMngI_.js";import{t as i}from"./arrow-left-CpBxkoxI.js";import{t as a}from"./circle-check-BU3v_mFW.js";import{r as o,t as s}from"./toast-CNt_jzO4.js";import{t as ee}from"./download-A0SW9OCW.js";import{n as c,r as l}from"./state-DA0B95UQ.js";import{t as te}from"./route-CNSNj9Zm.js";import{t as u}from"./search-JOKus9rb.js";import{l as d}from"./query-keys-D7wX2vM5.js";import{Q as f,X as p}from"./entry--yj84zk3.js";import{n as m,r as h,t as g}from"./card-DEhRXXw4.js";import{n as _}from"./download-dRFo5_Wu.js";import{t as v}from"./badge-DpXT4Ixt.js";import{n as y}from"./input-BpVZdrbc.js";import{t as b}from"./select-XPJ-dIw3.js";import{t as ne}from"./ModelPicker-oQ-SDXp8.js";import{t as re}from"./switch-Cittxy-x.js";import{t as x}from"./stack-C9_uUjaA.js";import{n as S}from"./inline-D1IlZZ8H.js";import{f as ie,t as C}from"./api-keys-BmDbsior.js";import{a as w,i as ae,n as oe,o as T,r as se,t as E}from"./cli-tools-B0wBq-RL.js";import{t as D}from"./ToolIcon-K1ckcDKg.js";var ce=e(`book-open`,[[`path`,{d:`M12 5v16`,key:`1f6ucr`}],[`path`,{d:`M20.001 19A2 2 0 0022 17V5a2 2 0 00-1.999-2L16 3.002A5 5 0 0012 5a5 5 0 00-4-2H4a2 2 0 00-2 2v12a2 2 0 001.999 2H8a5 5 0 014 2 5 5 0 014-2z`,key:`1fyvmf`}]]),le=e(`circle-x`,[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}],[`path`,{d:`m15 9-6 6`,key:`1uzhvr`}],[`path`,{d:`m9 9 6 6`,key:`z0biqf`}]]),ue=e(`file-code-corner`,[[`path`,{d:`M4 12.15V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.706.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2h-3.35`,key:`1wthlu`}],[`path`,{d:`M14 2v5a1 1 0 0 0 1 1h5`,key:`wfsgrz`}],[`path`,{d:`m5 16-3 3 3 3`,key:`331omg`}],[`path`,{d:`m9 22 3-3-3-3`,key:`lsp7cz`}]]),de=e(`settings-2`,[[`path`,{d:`M14 17H5`,key:`gfn3mx`}],[`path`,{d:`M19 7h-9`,key:`6i9tg`}],[`circle`,{cx:`17`,cy:`17`,r:`3`,key:`18b49y`}],[`circle`,{cx:`7`,cy:`7`,r:`3`,key:`dfmy0x`}]]),O=t(n(),1);function k(e,t){return e==null?!1:e instanceof Set?e.has(t):e.includes(t)}function A(e){return e==null?0:e instanceof Set?e.size:e.length}function j(e){let t=e.lastIndexOf(`/`);return t<0?e:e.slice(t+1)}function M(e,t,n,r){let i=n?`${n}/${j(t)}`:void 0,a=[t,j(t),...i?[i]:[]];r&&r!==t&&a.push(r);let o=e.model_list;return e.model_access_mode===`blacklist`?a.some(e=>k(o,e))?`model-denied`:null:o==null||A(o)===0||a.some(e=>k(o,e))?null:`model-not-allowed`}function N(e,t,n,r){return M(e,t,n,r)===null}var P=r();function F(){let{toolId:e}=f(),t=w(),n=T(),r=C(),i=(0,O.useMemo)(()=>t.data?.find(t=>t.id===e)??null,[t.data,e]),a=(0,O.useMemo)(()=>{let t=n.data;if(t)return Array.isArray(t)?t.find(t=>t.toolId===e):t[e]},[n.data,e]);return t.isLoading?(0,P.jsx)(l,{label:`Loading tool details…`}):t.isError?(0,P.jsx)(c,{title:`Unable to load tool`,message:t.error?.message??`Could not load the CLI tool registry.`,onRetry:()=>void t.refetch()}):i?(0,P.jsx)(I,{tool:i,status:a,apiKeys:r.data??[]}):(0,P.jsxs)(x,{gap:`12px`,children:[(0,P.jsx)(p,{to:`/cli-tools`,className:`text-caption text-accent`,children:`← Back to CLI Tools`}),(0,P.jsx)(c,{title:`Tool not found`,message:`Unknown CLI tool id.`})]})}function I({tool:e,status:t,apiKeys:n}){let[r,c]=(0,O.useState)(``),[l,f]=(0,O.useState)(()=>typeof window>`u`?`http://localhost:12800`:window.location.origin),[C,w]=(0,O.useState)({}),[T,k]=(0,O.useState)(!1),[A,j]=(0,O.useState)({}),[M,F]=(0,O.useState)(null),[I,L]=(0,O.useState)(`slot`),R=(0,O.useRef)(null),z=(0,O.useMemo)(()=>n.filter(e=>e.keyMode===`personal`||e.keyMode===`share`&&e.parentKeyId===void 0),[n]),B=ae(e.id,r),V=se(),H=oe(),U=ie(),W=E(),G=z.find(e=>e.id===r),K=G?.scopes?.includes(`routing:cli_mapping`)===!0,q=G?.keyMode===`personal`,J=G?.modelAccessMode??`whitelist`,Y=G?.modelList,X=e=>N({model_access_mode:J,model_list:Y},e),fe=(0,O.useMemo)(()=>{let t=new Set;if(!e.mappingSupported)return t;for(let n of e.defaultModels){let e=A[n.alias]??``;if(e.length===0)continue;let r=C[n.alias]??n.defaultValue??n.id;!X(e)&&!X(r)&&t.add(n.alias)}return t},[e.defaultModels,e.mappingSupported,A,C,G]),pe=e=>{if(!G)return;let t=Y??[],n=J===`blacklist`?t.filter(t=>t!==e):[...new Set([...t,e])];U.mutate({keyId:G.id,request:{modelAccessMode:J,modelList:n}},{onSuccess:()=>s.success(J===`blacklist`?`Model unblocked for this key`:`Model allowed for this key`),onError:e=>s.error(d(e,`Could not update the API key.`))})};(0,O.useEffect)(()=>{let t=Object.fromEntries(e.defaultModels.map(e=>[e.alias,e.defaultValue??e.id]));w(t),j({}),R.current=null},[e,r]),(0,O.useEffect)(()=>{let t=B.data;if(t&&t.apiKeyId===r){if(k(t.enabled),t.mappings.length>0)w(e=>({...e,...Object.fromEntries(t.mappings.map(e=>[e.slotKey,e.sourceModel]))})),j(e=>({...e,...Object.fromEntries(t.mappings.map(e=>[e.slotKey,e.targetModel]))}));else{let t=Object.fromEntries(e.defaultModels.map(e=>[e.alias,e.defaultValue??e.id]));w(t),j({})}R.current=JSON.stringify({enabled:t.enabled,mappings:t.mappings})}},[r,B.data,e.defaultModels]);let me=t?.installed??!1,Z=t?.configured??!1,he=e.configType===`guide`,Q=(0,O.useMemo)(()=>e.defaultModels.map(e=>C[e.alias]??e.defaultValue??e.id),[C,e.defaultModels]),$=(0,O.useCallback)(()=>{if(e.mappingSupported)return{enabled:T,mappings:e.defaultModels.filter(e=>(A[e.alias]??``).length>0).map(e=>({slotKey:e.alias,sourceModel:C[e.alias]??e.defaultValue??e.id,targetModel:A[e.alias]??``,enabled:!0})).filter(e=>e.targetModel.length>0)}},[T,A,C,e.defaultModels,e.mappingSupported]);(0,O.useEffect)(()=>{if(!e.mappingSupported||r.length===0)return;let t=B.data;if(!t||t.apiKeyId!==r)return;let n=$();if(!n)return;let i=JSON.stringify({enabled:n.enabled,mappings:n.mappings});if(i===R.current)return;let a=setTimeout(()=>{V.mutate({toolId:e.id,keyId:r,input:n},{onSuccess:()=>{R.current=i},onError:e=>s.error(d(e,`Could not save mappings.`))})},600);return()=>clearTimeout(a)},[$,r,B.data,V,e.id,e.mappingSupported]);let ge=(0,O.useCallback)(()=>{let t=Object.fromEntries(e.defaultModels.map(e=>[e.alias,C[e.alias]??e.defaultValue??e.id])),n=e.defaultModels.find(e=>e.roleKind===`subagent`);return{endpoint:l,apiKey:``,modelIds:e.id===`claude`?[]:Q,modelSlots:t,...Q[0]?{activeModel:Q[0]}:{},...n?{subagentModel:C[n.alias]??n.defaultValue??n.id}:{},mappingOwnerId:r,...e.mappingSupported?{mapping:$()}:{}}},[Q,$,r,l,C,e]);return(0,P.jsxs)(x,{gap:`16px`,children:[(0,P.jsxs)(p,{to:`/cli-tools`,className:`inline-flex items-center gap-3 text-caption text-accent`,children:[(0,P.jsx)(i,{size:14}),`Back to CLI Tools`]}),(0,P.jsx)(g,{children:(0,P.jsxs)(m,{className:`flex flex-col gap-5`,children:[(0,P.jsxs)(S,{gap:`14px`,align:`flex-start`,className:`flex-wrap`,children:[(0,P.jsx)(D,{toolId:e.id,name:e.name,color:e.color,size:46}),(0,P.jsxs)(S,{gap:`8px`,className:`flex-wrap`,children:[(0,P.jsx)(`h1`,{className:`text-title font-bold m-0`,children:e.name}),me?(0,P.jsx)(v,{tone:`ok`,children:`Installed`}):(0,P.jsx)(v,{tone:`disabled`,children:`Not installed`}),Z?(0,P.jsx)(v,{tone:`accent`,children:`Configured`}):null,e.configType===`guide`?(0,P.jsx)(v,{tone:`info`,children:`Guide`}):null]})]}),(0,P.jsx)(`p`,{className:`m-0 text-caption text-text-secondary`,children:e.description}),e.docsUrl?(0,P.jsx)(`a`,{href:e.docsUrl,target:`_blank`,rel:`noreferrer`,className:`inline-block text-caption text-accent`,children:`Official documentation →`}):null,t?.settingsPath?(0,P.jsxs)(`div`,{className:`font-mono tabular-nums px-6 py-4 rounded-inset bg-surface-2 border border-inner-border text-badge text-text-secondary break-all`,children:[`Config file: `,t.settingsPath]}):null,t?.message?(0,P.jsxs)(`div`,{className:`flex items-center gap-3 text-badge ${Z?`text-green`:`text-orange`}`,children:[Z?(0,P.jsx)(a,{size:13}):(0,P.jsx)(le,{size:13}),t.message]}):null]})}),(0,P.jsxs)(g,{children:[(0,P.jsx)(h,{title:`Setup`,subtitle:`Pick the Apikey Name and endpoint, set the models, then download. The key's secret is read server-side — you never paste it.`,icon:(0,P.jsx)(de,{size:16})}),(0,P.jsx)(m,{children:(0,P.jsx)(x,{gap:`12px`,children:(0,P.jsxs)(`div`,{className:`grid grid-cols-2 gap-4 items-start min-w-0 max-medium:grid-cols-1`,children:[(0,P.jsxs)(`div`,{className:`flex flex-col gap-3 min-w-0`,children:[(0,P.jsx)(b,{label:`Apikey Name`,id:`api-key-select`,value:r,onValueChange:c,options:[{value:``,label:`Select…`},...z.map(e=>({value:e.id,label:`${e.label} — ${e.keyMode===`personal`?`Personal`:`Share template`}`}))]}),(0,P.jsx)(`div`,{className:`flex items-center gap-3 flex-wrap`,children:G===void 0?(0,P.jsx)(`span`,{className:`mt-1 text-badge text-text-tertiary`,children:`Choose the Apikey Name whose routes this CLI session should use.`}):K?(0,P.jsxs)(P.Fragment,{children:[(0,P.jsx)(v,{tone:`ok`,children:`routing:cli_mapping`}),(0,P.jsx)(`span`,{className:`mt-1 text-badge text-text-tertiary`,children:`Remote routes work with this key.`})]}):(0,P.jsxs)(P.Fragment,{children:[(0,P.jsx)(v,{tone:`warn`,title:`This key cannot resolve persisted CLI mappings on /v1/*. Turn on Remote Routing below to enable it.`,children:`no CLI scope`}),(0,P.jsx)(`span`,{className:`mt-1 text-badge text-text-tertiary`,children:`Remote routes stay inert on this key — turning on Remote Routing below will activate it.`})]})}),G?.keyMode===`share`?(0,P.jsx)(`span`,{className:`mt-1 text-badge text-text-tertiary`,children:`Share template: its child keys inherit these routes automatically.`}):null]}),(0,P.jsx)(`div`,{className:`min-w-0`,children:(0,P.jsx)(y,{label:`Endpoint`,value:l,onChange:e=>f(e.target.value)})})]})})})]}),(0,P.jsxs)(g,{children:[(0,P.jsx)(h,{title:`Model routing`,icon:(0,P.jsx)(te,{size:16}),subtitle:e.mappingSupported?`Left is the model name the CLI asks for. Right is the route Cartethyia sends it to. Leave a target empty to send that slot straight to the provider.`:`The model names written into this tool's config.`,action:e.mappingSupported?(0,P.jsxs)(`div`,{className:`flex items-center gap-5`,children:[(0,P.jsx)(re,{checked:T,disabled:!r,onChange:t=>{if(k(t),!r){s.error(`Select an API key first to configure its Remote Routing.`);return}if(t&&G&&!K){let e=[...new Set([...G.scopes??[],`routing:cli_mapping`])];U.mutate({keyId:G.id,request:{scopes:e}},{onSuccess:()=>{s.success(`Activated routing:cli_mapping on "${G.label}".`)},onError:e=>{s.error(d(e,`Could not update key scope.`))}})}let n=$();n&&V.mutate({toolId:e.id,keyId:r,input:{...n,enabled:t}},{onError:e=>s.error(d(e,`Could not save mappings.`))})},label:T?`Remote Routing on`:`Remote Routing off`}),(0,P.jsx)(o,{variant:`secondary`,size:`sm`,disabled:!r||H.isPending,onClick:()=>{if(!r){s.error(`Select an API key first.`);return}H.mutate({toolId:e.id,keyId:r},{onSuccess:()=>{k(!1),j({}),s.success(`Remote routes cleared and Remote Routing turned off.`)},onError:e=>s.error(d(e,`Could not reset remote routes.`))})},children:H.isPending?`Resetting…`:`Reset`})]}):void 0}),(0,P.jsxs)(m,{children:[r?null:(0,P.jsx)(`p`,{className:`text-badge text-text-tertiary mb-5`,children:`Select an API key above to load and save remote routes for that key. You can still set models below to download a config file.`}),e.mappingSupported?(0,P.jsxs)(`div`,{className:`mb-5 flex items-center gap-4 rounded-inset border border-inner-border bg-surface-2 px-5 py-3 text-micro text-text-tertiary`,children:[(0,P.jsx)(`span`,{className:`flex-1`,children:`CLI asks for`}),(0,P.jsx)(`span`,{className:`w-9 text-center`,children:`→`}),(0,P.jsx)(`span`,{className:`flex-1`,children:`Cartethyia routes to`})]}):null,(0,P.jsx)(x,{gap:`6px`,children:e.defaultModels.map(t=>{let n=C[t.alias]??t.defaultValue??t.id,r=A[t.alias]??``,i=r===n;return(0,P.jsxs)(`div`,{className:`grid items-center gap-4 ${e.mappingSupported?`grid-cols-2`:`grid-cols-1`}`,children:[(0,P.jsxs)(`div`,{className:`flex flex-col gap-1 min-w-0`,children:[(0,P.jsxs)(`button`,{type:`button`,onClick:()=>{F(t.alias),L(`slot`)},className:`font-mono tabular-nums flex w-full min-w-0 cursor-pointer items-center justify-between gap-4 rounded-inset border border-inner-border bg-surface-2 px-5 py-3.5 text-left text-badge text-text-primary`,children:[(0,P.jsx)(`span`,{className:`overflow-hidden truncate whitespace-nowrap`,children:n}),(0,P.jsx)(u,{size:12,className:`text-text-tertiary shrink-0`})]}),(0,P.jsx)(`span`,{className:`text-micro text-text-tertiary pl-1`,children:t.roleLabel??t.name})]}),e.mappingSupported?(0,P.jsxs)(`div`,{className:`flex flex-col gap-1 min-w-0`,children:[(0,P.jsxs)(`button`,{type:`button`,onClick:()=>{F(t.alias),L(`target`)},className:`font-mono tabular-nums flex w-full min-w-0 cursor-pointer items-center justify-between gap-4 rounded-inset border px-5 py-3.5 text-left text-badge ${i?`border-inner-border bg-surface-2 text-text-tertiary`:`border-accent bg-accent-soft text-text-primary`}`,children:[(0,P.jsx)(`span`,{className:`overflow-hidden truncate whitespace-nowrap`,children:r.length>0?r:`Not routed`}),(0,P.jsx)(u,{size:12,className:`text-text-tertiary shrink-0`})]}),(0,P.jsxs)(`span`,{className:`pl-1 text-micro ${r.length===0?`text-text-tertiary`:`text-accent`}`,children:[r.length===0?`not routed`:`rerouted`,r.length>0?(0,P.jsx)(`button`,{type:`button`,onClick:()=>j(e=>({...e,[t.alias]:``})),className:`ml-3 border-none bg-none p-0 text-micro text-text-tertiary cursor-pointer underline`,children:`clear`}):null]}),fe.has(t.alias)?(0,P.jsxs)(`span`,{className:`mt-1 flex items-center gap-3 text-micro text-orange`,children:[(0,P.jsx)(`span`,{children:J===`blacklist`?`This key blocks the target model.`:`This key does not allow the target model.`}),(0,P.jsx)(`button`,{type:`button`,disabled:U.isPending,onClick:()=>pe(r),className:`border-none bg-none p-0 text-micro font-semibold text-accent underline ${U.isPending?`cursor-not-allowed`:`cursor-pointer`}`,children:J===`blacklist`?`Unblock`:`Allow this model`})]}):null]}):null]},t.alias)})})]})]}),he&&e.guideSteps?(0,P.jsxs)(g,{children:[(0,P.jsx)(h,{title:`Guide`,subtitle:`Manual setup steps for tools that do not support file injection.`,icon:(0,P.jsx)(ce,{size:16})}),(0,P.jsx)(m,{children:(0,P.jsx)(`ol`,{className:`flex flex-col gap-6 pl-9`,children:e.guideSteps.map(e=>(0,P.jsxs)(`li`,{children:[(0,P.jsx)(`strong`,{className:`text-body`,children:e.title}),e.desc?(0,P.jsx)(`p`,{className:`text-caption text-text-secondary mt-2`,children:e.desc}):null,e.value?(0,P.jsx)(`pre`,{className:`mt-3 overflow-x-auto rounded-control border border-inner-border bg-surface-2 p-5 font-mono text-badge`,children:e.value.replace(/\{\{baseUrl\}\}/g,l)}):null]},e.step))})})]}):null,(0,P.jsxs)(g,{children:[(0,P.jsx)(h,{title:`Config file`,subtitle:`Download the config file for this CLI tool. The selected API key secret is decrypted automatically into the download.`,icon:(0,P.jsx)(ue,{size:16})}),(0,P.jsxs)(m,{className:`flex flex-col gap-6`,children:[(0,P.jsx)(`div`,{className:`flex gap-4 flex-wrap justify-end`,children:(0,P.jsx)(o,{variant:`primary`,icon:(0,P.jsx)(ee,{size:14}),onClick:()=>{if(!r){s.error(`Select an API key first.`);return}W.mutate({toolId:e.id,keyId:r,input:ge()},{onSuccess:e=>{_(e.filename,e.content,e.mimeType),s.success(`Downloaded ${e.filename}.`)},onError:e=>s.error(d(e,`Could not download config.`))})},disabled:W.isPending||!q,children:W.isPending?`Downloading…`:`Download config`})}),q?null:(0,P.jsx)(`p`,{className:`text-badge text-text-tertiary text-right`,children:G?.keyMode===`share`?`A share template has no secret to write into a config. Select a personal key to download the config file.`:`Select an Apikey Name above to enable downloading the config file.`})]})]}),(0,P.jsx)(ne,{open:M!==null,onClose:()=>F(null),selected:M?[I===`target`?A[M]??C[M]??``:C[M]??``]:[],onToggle:()=>{},onSelectOne:e=>{M&&(I===`target`?j(t=>({...t,[M]:e})):w(t=>({...t,[M]:e}))),F(null)},title:M?`Select ${M} model`:`Select model`,multi:!1})]})}export{F as default};