cartethyia 2.5.0 → 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.
- package/.env.example +5 -1
- package/dist/dashboard/assets/{AccountCooldown-ChpdzxRH.js → AccountCooldown-Bjo92Qat.js} +1 -1
- package/dist/dashboard/assets/AccountCooldown-Bjo92Qat.js.gz +0 -0
- package/dist/dashboard/assets/{BannedPage-9igfctfZ.js → BannedPage-MsQaeQTs.js} +1 -1
- package/dist/dashboard/assets/BannedPage-MsQaeQTs.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-BO9nKg9w.js +1 -0
- package/dist/dashboard/assets/CliToolDetailPage-BO9nKg9w.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-D3xqZbN-.js → CliToolsPage-DfWHTJ7v.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-DfWHTJ7v.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-BrCBQDAc.js +1 -0
- package/dist/dashboard/assets/CombosPage-BrCBQDAc.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-C6Uh33er.js +1 -0
- package/dist/dashboard/assets/ConsoleLogPage-C6Uh33er.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-8aS9hyJu.js +1 -0
- package/dist/dashboard/assets/CustomizationPage-8aS9hyJu.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-qomXThxX.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-qomXThxX.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-C8hKyXBe.js +1 -0
- package/dist/dashboard/assets/LoginPage-C8hKyXBe.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-CWkTUtpX.js +1 -0
- package/dist/dashboard/assets/ModelPicker-CWkTUtpX.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-C-ExCy1V.js +1 -0
- package/dist/dashboard/assets/OverviewPage-C-ExCy1V.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-tr811uaF.js +3 -0
- package/dist/dashboard/assets/ProviderDetailPage-tr811uaF.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-BS9IEpvv.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-BS9IEpvv.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-Rn_qXScb.js +7 -0
- package/dist/dashboard/assets/ProxyPage-Rn_qXScb.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-BihkgQSq.js +1 -0
- package/dist/dashboard/assets/QuotaPage-BihkgQSq.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-DhiEjm6T.js +1 -0
- package/dist/dashboard/assets/SettingsPage-DhiEjm6T.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-CXjUlfqr.js +1 -0
- package/dist/dashboard/assets/SetupPage-CXjUlfqr.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-C-_oD3F2.js +1 -0
- package/dist/dashboard/assets/SortableList-C-_oD3F2.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-CbK3hXvU.js → StudioPage-u9IxmYOI.js} +3 -3
- package/dist/dashboard/assets/StudioPage-u9IxmYOI.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-BEVLVrwP.js +56 -0
- package/dist/dashboard/assets/UsagePage-BEVLVrwP.js.gz +0 -0
- package/dist/dashboard/assets/{api-keys-BmDbsior.js → api-keys-uNywYlfv.js} +1 -1
- package/dist/dashboard/assets/api-keys-uNywYlfv.js.gz +0 -0
- package/dist/dashboard/assets/{app-C-Y4oZT0.js → app-CafWtVXA.js} +1 -1
- package/dist/dashboard/assets/{app-C-Y4oZT0.js.gz → app-CafWtVXA.js.gz} +0 -0
- package/dist/dashboard/assets/app-DxivpfYG.css +2 -0
- package/dist/dashboard/assets/app-DxivpfYG.css.gz +0 -0
- package/dist/dashboard/assets/{assert-BHfaei-X.js → assert-NHwYq95t.js} +1 -1
- package/dist/dashboard/assets/{assert-BHfaei-X.js.gz → assert-NHwYq95t.js.gz} +0 -0
- package/dist/dashboard/assets/badge-6uzDDvqs.js +1 -0
- package/dist/dashboard/assets/badge-6uzDDvqs.js.gz +0 -0
- package/dist/dashboard/assets/banner-4_WNg2WZ.js +1 -0
- package/dist/dashboard/assets/banner-4_WNg2WZ.js.gz +0 -0
- package/dist/dashboard/assets/card--AtFedAq.js +1 -0
- package/dist/dashboard/assets/card--AtFedAq.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-B0wBq-RL.js → cli-tools-B3e7dx3N.js} +1 -1
- package/dist/dashboard/assets/cli-tools-B3e7dx3N.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-NreUx6c8.js +1 -0
- package/dist/dashboard/assets/clipboard-button-NreUx6c8.js.gz +0 -0
- package/dist/dashboard/assets/dialog-BtpsJU98.js +1 -0
- package/dist/dashboard/assets/dialog-BtpsJU98.js.gz +0 -0
- package/dist/dashboard/assets/drawer-WwwtzihF.js +1 -0
- package/dist/dashboard/assets/drawer-WwwtzihF.js.gz +0 -0
- package/dist/dashboard/assets/entry-Dj9AR0gY.js +4 -0
- package/dist/dashboard/assets/entry-Dj9AR0gY.js.gz +0 -0
- package/dist/dashboard/assets/entry-TJIye_jT.js +1 -0
- package/dist/dashboard/assets/entry-TJIye_jT.js.gz +0 -0
- package/dist/dashboard/assets/{entry-D4O2229X.js → entry-jy9_4-Ke.js} +1 -1
- package/dist/dashboard/assets/entry-jy9_4-Ke.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-D_eM3IgG.js +1 -0
- package/dist/dashboard/assets/github-badge-D_eM3IgG.js.gz +0 -0
- package/dist/dashboard/assets/{index-CXm3FIyt.js → index-DCBtROoK.js} +2 -2
- package/dist/dashboard/assets/index-DCBtROoK.js.gz +0 -0
- package/dist/dashboard/assets/inline-BcNGF8Gr.js +1 -0
- package/dist/dashboard/assets/inline-BcNGF8Gr.js.gz +0 -0
- package/dist/dashboard/assets/input-BG41et9G.js +1 -0
- package/dist/dashboard/assets/input-BG41et9G.js.gz +0 -0
- package/dist/dashboard/assets/layout-C8mer_Ak.js +1 -0
- package/dist/dashboard/assets/layout-C8mer_Ak.js.gz +0 -0
- package/dist/dashboard/assets/list-row-Gh_W4OR_.js +1 -0
- package/dist/dashboard/assets/list-row-Gh_W4OR_.js.gz +0 -0
- package/dist/dashboard/assets/{live-yfEJFF9T.js → live-BJGYRBnR.js} +1 -1
- package/dist/dashboard/assets/live-BJGYRBnR.js.gz +0 -0
- package/dist/dashboard/assets/{model-bans-CT-TxSNX.js → model-bans-DeVLDA7I.js} +1 -1
- package/dist/dashboard/assets/model-bans-DeVLDA7I.js.gz +0 -0
- package/dist/dashboard/assets/numbers-Bs4LOOuf.js +2 -0
- package/dist/dashboard/assets/numbers-Bs4LOOuf.js.gz +0 -0
- package/dist/dashboard/assets/{routing-Cs6jl49L.js → routing-CcusqXA7.js} +1 -1
- package/dist/dashboard/assets/routing-CcusqXA7.js.gz +0 -0
- package/dist/dashboard/assets/select-DsX5QT8K.js +41 -0
- package/dist/dashboard/assets/select-DsX5QT8K.js.gz +0 -0
- package/dist/dashboard/assets/{settings-CfcTcOyU.js → settings-mpmXTgUa.js} +1 -1
- package/dist/dashboard/assets/settings-mpmXTgUa.js.gz +0 -0
- package/dist/dashboard/assets/stack-DwLmjUFK.js +1 -0
- package/dist/dashboard/assets/stack-DwLmjUFK.js.gz +0 -0
- package/dist/dashboard/assets/state-DYdQmdAl.js +1 -0
- package/dist/dashboard/assets/state-DYdQmdAl.js.gz +0 -0
- package/dist/dashboard/assets/switch-CidSfbG7.js +1 -0
- package/dist/dashboard/assets/switch-CidSfbG7.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-BQKMcJ1H.js → useMutation-w4u4CVue.js} +1 -1
- package/dist/dashboard/assets/useMutation-w4u4CVue.js.gz +0 -0
- package/dist/dashboard/index.html +1 -1
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +51 -54
- package/package.json +1 -1
- package/scripts/cli/command/tray.ts +47 -8
- package/src/boot/app.ts +3 -2
- package/src/console/observability/contracts.ts +11 -1
- package/src/console/observability/store.ts +9 -6
- package/src/providers/discovery/openrouter-free-roster.ts +3 -11
- package/src/providers/discovery/probe-phases.ts +42 -5
- package/src/providers/discovery/probing-service.ts +25 -2
- package/src/providers/integrations/claude/claude.ts +3 -1
- package/src/providers/integrations/cloudflareai/cloudflareai-models.ts +3 -1
- package/src/providers/integrations/inferhub.ts +3 -1
- package/src/providers/integrations/kilo/kilo.ts +0 -1
- package/src/providers/integrations/kiro/kiro-catalog.ts +8 -2
- package/src/providers/integrations/openrouter/provider.ts +5 -15
- package/src/security/api-key-auth.ts +23 -0
- package/src/transport/guards/auth-guard.ts +2 -3
- package/dist/dashboard/assets/AccountCooldown-ChpdzxRH.js.gz +0 -0
- package/dist/dashboard/assets/BannedPage-9igfctfZ.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-ZRr2K2sF.js +0 -1
- package/dist/dashboard/assets/CliToolDetailPage-ZRr2K2sF.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-D3xqZbN-.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-CVnQPBBz.js +0 -1
- package/dist/dashboard/assets/CombosPage-CVnQPBBz.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-u6W1UXSc.js +0 -1
- package/dist/dashboard/assets/ConsoleLogPage-u6W1UXSc.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-CTTpCb25.js +0 -1
- package/dist/dashboard/assets/CustomizationPage-CTTpCb25.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-C5XpWlvy.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-C5XpWlvy.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-DHoB3Agn.js +0 -1
- package/dist/dashboard/assets/LoginPage-DHoB3Agn.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-oQ-SDXp8.js +0 -1
- package/dist/dashboard/assets/ModelPicker-oQ-SDXp8.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-BrY4SgIt.js +0 -1
- package/dist/dashboard/assets/OverviewPage-BrY4SgIt.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-D9VSt7T_.js +0 -3
- package/dist/dashboard/assets/ProviderDetailPage-D9VSt7T_.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-Df77V5I5.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-Df77V5I5.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-CDfxP7AC.js +0 -7
- package/dist/dashboard/assets/ProxyPage-CDfxP7AC.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-CY4o2t4p.js +0 -1
- package/dist/dashboard/assets/QuotaPage-CY4o2t4p.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-CvjZxHE4.js +0 -1
- package/dist/dashboard/assets/SettingsPage-CvjZxHE4.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-D3nza7LH.js +0 -1
- package/dist/dashboard/assets/SetupPage-D3nza7LH.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-Dtt12WT1.js +0 -1
- package/dist/dashboard/assets/SortableList-Dtt12WT1.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-CbK3hXvU.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-BQlWbWg2.js +0 -56
- package/dist/dashboard/assets/UsagePage-BQlWbWg2.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-BmDbsior.js.gz +0 -0
- package/dist/dashboard/assets/app-BbpSarVV.css +0 -2
- package/dist/dashboard/assets/app-BbpSarVV.css.gz +0 -0
- package/dist/dashboard/assets/badge-DpXT4Ixt.js +0 -1
- package/dist/dashboard/assets/badge-DpXT4Ixt.js.gz +0 -0
- package/dist/dashboard/assets/banner-CSAAWLJv.js +0 -1
- package/dist/dashboard/assets/banner-CSAAWLJv.js.gz +0 -0
- package/dist/dashboard/assets/card-DEhRXXw4.js +0 -1
- package/dist/dashboard/assets/card-DEhRXXw4.js.gz +0 -0
- package/dist/dashboard/assets/check-D69FzdvC.js +0 -1
- package/dist/dashboard/assets/check-D69FzdvC.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-B0wBq-RL.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-DOo6EU8O.js +0 -1
- package/dist/dashboard/assets/clipboard-button-DOo6EU8O.js.gz +0 -0
- package/dist/dashboard/assets/copy-ngmQ9zcZ.js +0 -1
- package/dist/dashboard/assets/copy-ngmQ9zcZ.js.gz +0 -0
- package/dist/dashboard/assets/dialog-BvQeBMfP.js +0 -1
- package/dist/dashboard/assets/dialog-BvQeBMfP.js.gz +0 -0
- package/dist/dashboard/assets/drawer-CMwPXwQB.js +0 -1
- package/dist/dashboard/assets/drawer-CMwPXwQB.js.gz +0 -0
- package/dist/dashboard/assets/entry-D4O2229X.js.gz +0 -0
- package/dist/dashboard/assets/entry-DKvr5Q9K.js +0 -1
- package/dist/dashboard/assets/entry-DKvr5Q9K.js.gz +0 -0
- package/dist/dashboard/assets/entry-FQCejoDY.js +0 -4
- package/dist/dashboard/assets/entry-FQCejoDY.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-zliLvBwr.js +0 -1
- package/dist/dashboard/assets/github-badge-zliLvBwr.js.gz +0 -0
- package/dist/dashboard/assets/index-CXm3FIyt.js.gz +0 -0
- package/dist/dashboard/assets/inline-D1IlZZ8H.js +0 -1
- package/dist/dashboard/assets/inline-D1IlZZ8H.js.gz +0 -0
- package/dist/dashboard/assets/input-BpVZdrbc.js +0 -1
- package/dist/dashboard/assets/input-BpVZdrbc.js.gz +0 -0
- package/dist/dashboard/assets/layout-BioLW7gN.js +0 -1
- package/dist/dashboard/assets/layout-BioLW7gN.js.gz +0 -0
- package/dist/dashboard/assets/list-row-CwVcGAIc.js +0 -1
- package/dist/dashboard/assets/list-row-CwVcGAIc.js.gz +0 -0
- package/dist/dashboard/assets/live-yfEJFF9T.js.gz +0 -0
- package/dist/dashboard/assets/model-bans-CT-TxSNX.js.gz +0 -0
- package/dist/dashboard/assets/numbers-CFL4_Dqc.js +0 -1
- package/dist/dashboard/assets/numbers-CFL4_Dqc.js.gz +0 -0
- package/dist/dashboard/assets/routing-Cs6jl49L.js.gz +0 -0
- package/dist/dashboard/assets/select-C0Rer3gF.js +0 -41
- package/dist/dashboard/assets/select-C0Rer3gF.js.gz +0 -0
- package/dist/dashboard/assets/settings-CfcTcOyU.js.gz +0 -0
- package/dist/dashboard/assets/stack-C9_uUjaA.js +0 -1
- package/dist/dashboard/assets/stack-C9_uUjaA.js.gz +0 -0
- package/dist/dashboard/assets/state-DA0B95UQ.js +0 -1
- package/dist/dashboard/assets/state-DA0B95UQ.js.gz +0 -0
- package/dist/dashboard/assets/switch-Cittxy-x.js +0 -1
- package/dist/dashboard/assets/switch-Cittxy-x.js.gz +0 -0
- package/dist/dashboard/assets/toast-CNt_jzO4.js +0 -2
- package/dist/dashboard/assets/toast-CNt_jzO4.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-BQKMcJ1H.js.gz +0 -0
|
@@ -1,75 +1,73 @@
|
|
|
1
1
|
{
|
|
2
|
-
"count":
|
|
2
|
+
"count": 114,
|
|
3
3
|
"assets": [
|
|
4
|
-
"assets\\AccountCooldown-
|
|
5
|
-
"assets\\BannedPage-
|
|
6
|
-
"assets\\CliToolDetailPage-
|
|
7
|
-
"assets\\CliToolsPage-
|
|
8
|
-
"assets\\CombosPage-
|
|
9
|
-
"assets\\ConsoleLogPage-
|
|
10
|
-
"assets\\CustomizationPage-
|
|
11
|
-
"assets\\HealthEventsModal-
|
|
12
|
-
"assets\\LoginPage-
|
|
13
|
-
"assets\\ModelPicker-
|
|
14
|
-
"assets\\OverviewPage-
|
|
15
|
-
"assets\\ProviderDetailPage-
|
|
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-
|
|
18
|
-
"assets\\ProxyPage-
|
|
19
|
-
"assets\\QuotaPage-
|
|
20
|
-
"assets\\SettingsPage-
|
|
21
|
-
"assets\\SetupPage-
|
|
22
|
-
"assets\\SortableList-
|
|
23
|
-
"assets\\StudioPage-
|
|
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-
|
|
26
|
-
"assets\\api-keys-
|
|
27
|
-
"assets\\app-
|
|
28
|
-
"assets\\app-
|
|
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-
|
|
31
|
+
"assets\\assert-NHwYq95t.js",
|
|
32
32
|
"assets\\backup-file-gdKIlg8U.js",
|
|
33
|
-
"assets\\badge-
|
|
34
|
-
"assets\\banner-
|
|
33
|
+
"assets\\badge-6uzDDvqs.js",
|
|
34
|
+
"assets\\banner-4_WNg2WZ.js",
|
|
35
35
|
"assets\\brain-Cwed_72z.js",
|
|
36
|
-
"assets\\card
|
|
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-
|
|
42
|
-
"assets\\clipboard-button-
|
|
40
|
+
"assets\\cli-tools-B3e7dx3N.js",
|
|
41
|
+
"assets\\clipboard-button-NreUx6c8.js",
|
|
43
42
|
"assets\\cog-CdWhsajs.js",
|
|
44
|
-
"assets\\
|
|
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-
|
|
50
|
-
"assets\\entry-
|
|
51
|
-
"assets\\entry-
|
|
52
|
-
"assets\\entry-
|
|
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-
|
|
55
|
+
"assets\\github-badge-D_eM3IgG.js",
|
|
58
56
|
"assets\\image-BPaouViG.js",
|
|
59
|
-
"assets\\index-
|
|
60
|
-
"assets\\inline-
|
|
61
|
-
"assets\\input-
|
|
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-
|
|
66
|
-
"assets\\list-row-
|
|
67
|
-
"assets\\live-
|
|
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-
|
|
68
|
+
"assets\\model-bans-DeVLDA7I.js",
|
|
71
69
|
"assets\\mutation-BkoLM5Ba.js",
|
|
72
|
-
"assets\\numbers-
|
|
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-
|
|
97
|
+
"assets\\routing-CcusqXA7.js",
|
|
100
98
|
"assets\\scaling-nWE4Cx8f.js",
|
|
101
99
|
"assets\\search-JOKus9rb.js",
|
|
102
|
-
"assets\\select-
|
|
103
|
-
"assets\\settings-
|
|
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-
|
|
107
|
-
"assets\\state-
|
|
104
|
+
"assets\\stack-DwLmjUFK.js",
|
|
105
|
+
"assets\\state-DYdQmdAl.js",
|
|
108
106
|
"assets\\stream-retry-5QnVuSSo.js",
|
|
109
|
-
"assets\\switch-
|
|
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-
|
|
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.
|
|
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",
|
|
@@ -17,7 +17,29 @@ import { printUpdateInstructions } from "./update";
|
|
|
17
17
|
* an empty console tab open for the tray's whole lifetime, and that window is
|
|
18
18
|
* owned by Windows Terminal — not by this process, so it cannot be found and
|
|
19
19
|
* hidden from inside.
|
|
20
|
+
*
|
|
21
|
+
* The gateway it starts is detached, which is what makes the health poll below
|
|
22
|
+
* necessary — see {@link TRAY_HEALTH_POLL_MS}.
|
|
20
23
|
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* How often the tray asks whether the gateway is still answering.
|
|
27
|
+
*
|
|
28
|
+
* 10 s: often enough that an operator who stops the gateway by hand sees the
|
|
29
|
+
* icon go away, and far below the cost of a request. A detached gateway can no
|
|
30
|
+
* longer be waited on, so this poll is the only signal left.
|
|
31
|
+
*/
|
|
32
|
+
const TRAY_HEALTH_POLL_MS = 10_000;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Consecutive misses to forgive before a dead gateway closes the tray.
|
|
36
|
+
*
|
|
37
|
+
* One refusal is not a stopped gateway — a probe can lose a race with startup
|
|
38
|
+
* or a slow request — and closing the icon on one would make startup feel
|
|
39
|
+
* random.
|
|
40
|
+
*/
|
|
41
|
+
const TRAY_HEALTH_MISSES_BEFORE_CLOSE = 3;
|
|
42
|
+
|
|
21
43
|
export async function startTray(): Promise<void> {
|
|
22
44
|
const port = resolvePort();
|
|
23
45
|
const drainToken = ensureDrainToken();
|
|
@@ -137,7 +159,13 @@ export async function startTray(): Promise<void> {
|
|
|
137
159
|
|
|
138
160
|
try {
|
|
139
161
|
await tray.ready();
|
|
140
|
-
gateway
|
|
162
|
+
// Detached, unlike every other caller: the gateway must outlive this
|
|
163
|
+
// process. Attached, it dies with the tray — and the tray dies with the
|
|
164
|
+
// terminal that launched it, so closing that terminal took the gateway
|
|
165
|
+
// down too and "Hide to tray" left nothing serving. `stopGateway` drains
|
|
166
|
+
// over HTTP before it ever reaches for `kill()`, so detaching costs the
|
|
167
|
+
// exit path nothing: the tray can still stop the gateway on Exit.
|
|
168
|
+
gateway = await startGateway(port, drainToken, { detached: true });
|
|
141
169
|
// The launcher that spawned this tray is blocked on a signal file: it
|
|
142
170
|
// cannot read our stdio, because the tray is detached. Without this write
|
|
143
171
|
// it waits out its 60 s deadline and reports a failure for a tray that is
|
|
@@ -146,17 +174,28 @@ export async function startTray(): Promise<void> {
|
|
|
146
174
|
console.log(`Cartethyia is running at ${dashboardUrl}`);
|
|
147
175
|
void refreshVersion();
|
|
148
176
|
|
|
177
|
+
let misses = 0;
|
|
178
|
+
// A detached child's `exited` never settles on Windows, so the old
|
|
179
|
+
// `await current.exited` would have parked here forever and the tray would
|
|
180
|
+
// never notice the gateway going away. Poll its health endpoint instead:
|
|
181
|
+
// that answers for a process we no longer own, which is the whole point of
|
|
182
|
+
// detaching it. The probe is the same one startup used, so a gateway that
|
|
183
|
+
// is up reads as up regardless of who spawned it.
|
|
149
184
|
while (!closing) {
|
|
150
|
-
|
|
151
|
-
if (
|
|
152
|
-
|
|
185
|
+
await Bun.sleep(TRAY_HEALTH_POLL_MS);
|
|
186
|
+
if (closing || gateway === null) continue;
|
|
187
|
+
const alive = await fetch(`http://127.0.0.1:${port}/health`, {
|
|
188
|
+
signal: AbortSignal.timeout(2_000),
|
|
189
|
+
}).then((response) => response.ok).catch(() => false);
|
|
190
|
+
if (alive) {
|
|
191
|
+
misses = 0;
|
|
153
192
|
continue;
|
|
154
193
|
}
|
|
155
|
-
|
|
156
|
-
if (
|
|
194
|
+
misses += 1;
|
|
195
|
+
if (misses < TRAY_HEALTH_MISSES_BEFORE_CLOSE) continue;
|
|
157
196
|
gateway = null;
|
|
158
|
-
console.error(
|
|
159
|
-
await close(
|
|
197
|
+
console.error("Cartethyia stopped answering; the tray is closing.");
|
|
198
|
+
await close(1);
|
|
160
199
|
}
|
|
161
200
|
} catch (error) {
|
|
162
201
|
await close(1);
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
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
|
|
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
|
|
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
|
-
*
|
|
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
|
-
|
|
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(
|
|
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,
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
|
|
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 =>
|