cartethyia 2.6.3 → 2.6.5
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/dist/dashboard/assets/{BannedPage-C8pfspAj.js → BannedPage-B7OjIxCp.js} +1 -1
- package/dist/dashboard/assets/BannedPage-B7OjIxCp.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-D-OwfzDV.js → CliToolDetailPage-Bt-6y24w.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-Bt-6y24w.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-DqRcDO5j.js → CliToolsPage-C6LunsuE.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-C6LunsuE.js.gz +0 -0
- package/dist/dashboard/assets/{CombosPage-B-a0l8yE.js → CombosPage-CgyfN2Yy.js} +1 -1
- package/dist/dashboard/assets/CombosPage-CgyfN2Yy.js.gz +0 -0
- package/dist/dashboard/assets/{ConsoleLogPage-BVVGUdqt.js → ConsoleLogPage-BHWU7y6j.js} +1 -1
- package/dist/dashboard/assets/ConsoleLogPage-BHWU7y6j.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-BzmbE05k.js → CustomizationPage-xIjU736m.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-xIjU736m.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-DeT1zkYG.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-DeT1zkYG.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-BdSGc5J0.js → LoginPage-ChVqIAam.js} +1 -1
- package/dist/dashboard/assets/LoginPage-ChVqIAam.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-DVny4S5_.js → ModelPicker-CA1kLs45.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-CA1kLs45.js.gz +0 -0
- package/dist/dashboard/assets/{OverviewPage-D3fMRkpP.js → OverviewPage-BD4GkRtS.js} +1 -1
- package/dist/dashboard/assets/OverviewPage-BD4GkRtS.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-BJXwqpKj.js +3 -0
- package/dist/dashboard/assets/ProviderDetailPage-BJXwqpKj.js.gz +0 -0
- package/dist/dashboard/assets/{ProvidersPage-T7WamMuq.js → ProvidersPage-Ck64riM5.js} +1 -1
- package/dist/dashboard/assets/ProvidersPage-Ck64riM5.js.gz +0 -0
- package/dist/dashboard/assets/{ProxyPage-CKJb_Kj4.js → ProxyPage-Br_Leqq0.js} +1 -1
- package/dist/dashboard/assets/ProxyPage-Br_Leqq0.js.gz +0 -0
- package/dist/dashboard/assets/{QuotaPage-hxJ1hGES.js → QuotaPage-D2gyhfa8.js} +1 -1
- package/dist/dashboard/assets/QuotaPage-D2gyhfa8.js.gz +0 -0
- package/dist/dashboard/assets/{SettingsPage-CpEErAq5.js → SettingsPage-1dbYb8cM.js} +1 -1
- package/dist/dashboard/assets/SettingsPage-1dbYb8cM.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-DRsoneXm.js → SetupPage-DUTFy9wH.js} +1 -1
- package/dist/dashboard/assets/SetupPage-DUTFy9wH.js.gz +0 -0
- package/dist/dashboard/assets/{SortableList-QvDz0eQo.js → SortableList-BZEhfDpZ.js} +1 -1
- package/dist/dashboard/assets/SortableList-BZEhfDpZ.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-6yLBksbv.js → StudioPage-GBDtsJjh.js} +1 -1
- package/dist/dashboard/assets/StudioPage-GBDtsJjh.js.gz +0 -0
- package/dist/dashboard/assets/{UsagePage-CpB88NH4.js → UsagePage-CxidMvxq.js} +1 -1
- package/dist/dashboard/assets/UsagePage-CxidMvxq.js.gz +0 -0
- package/dist/dashboard/assets/app-BUSiYL6F.css +2 -0
- package/dist/dashboard/assets/app-BUSiYL6F.css.gz +0 -0
- package/dist/dashboard/assets/{badge-CKsF123d.js → badge-Bkz_-RrM.js} +1 -1
- package/dist/dashboard/assets/badge-Bkz_-RrM.js.gz +0 -0
- package/dist/dashboard/assets/{banner-8j7uChA9.js → banner-C-7y3VKV.js} +1 -1
- package/dist/dashboard/assets/banner-C-7y3VKV.js.gz +0 -0
- package/dist/dashboard/assets/{card-CdsSlWA3.js → card-BOPBuBzD.js} +1 -1
- package/dist/dashboard/assets/card-BOPBuBzD.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-uL5aYLfW.js → clipboard-button-e-Mlk0FH.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-e-Mlk0FH.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-C07hr5_G.js → dialog-Iec_ywbD.js} +1 -1
- package/dist/dashboard/assets/dialog-Iec_ywbD.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-BPO1TcKH.js → drawer-Bs8MIIHc.js} +1 -1
- package/dist/dashboard/assets/drawer-Bs8MIIHc.js.gz +0 -0
- package/dist/dashboard/assets/{entry-Bz_CtDfC.js → entry-BRszm4_Z.js} +1 -1
- package/dist/dashboard/assets/entry-BRszm4_Z.js.gz +0 -0
- package/dist/dashboard/assets/{entry-8U_8IAW2.js → entry-BrksMru8.js} +3 -3
- package/dist/dashboard/assets/entry-BrksMru8.js.gz +0 -0
- package/dist/dashboard/assets/{entry-GJRbmrpA.js → entry-DcobaKFu.js} +1 -1
- package/dist/dashboard/assets/entry-DcobaKFu.js.gz +0 -0
- package/dist/dashboard/assets/{github-badge-DmQreSDd.js → github-badge-BuDsrsPn.js} +1 -1
- package/dist/dashboard/assets/github-badge-BuDsrsPn.js.gz +0 -0
- package/dist/dashboard/assets/{index-BG48fsZ2.js → index-DU61iviS.js} +2 -2
- package/dist/dashboard/assets/index-DU61iviS.js.gz +0 -0
- package/dist/dashboard/assets/{inline-C9dCYaBZ.js → inline-DToMICwl.js} +1 -1
- package/dist/dashboard/assets/inline-DToMICwl.js.gz +0 -0
- package/dist/dashboard/assets/{input-CjCnXweJ.js → input-WlyHWeG2.js} +1 -1
- package/dist/dashboard/assets/input-WlyHWeG2.js.gz +0 -0
- package/dist/dashboard/assets/{layout-V6y8DAkl.js → layout-DaJ5baJr.js} +1 -1
- package/dist/dashboard/assets/layout-DaJ5baJr.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-CM1AaG_y.js → list-row-B3oSK13z.js} +1 -1
- package/dist/dashboard/assets/list-row-B3oSK13z.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-BOevJzdz.js → numbers-Dulqgys5.js} +1 -1
- package/dist/dashboard/assets/numbers-Dulqgys5.js.gz +0 -0
- package/dist/dashboard/assets/{select-AnGKpdl1.js → select-Ciq_KFQ1.js} +1 -1
- package/dist/dashboard/assets/{select-AnGKpdl1.js.gz → select-Ciq_KFQ1.js.gz} +0 -0
- package/dist/dashboard/assets/{stack-_gfTdLVB.js → stack-B1w5BM0u.js} +1 -1
- package/dist/dashboard/assets/stack-B1w5BM0u.js.gz +0 -0
- package/dist/dashboard/assets/{state-2c5V4zo7.js → state-DhZBfWgx.js} +1 -1
- package/dist/dashboard/assets/state-DhZBfWgx.js.gz +0 -0
- package/dist/dashboard/assets/{switch-Dkke7Naf.js → switch-yD7D99Lg.js} +1 -1
- package/dist/dashboard/assets/switch-yD7D99Lg.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 +41 -41
- package/package.json +96 -96
- package/src/boot/app.ts +11 -1
- package/src/console/providers/catalog/contracts.ts +7 -0
- package/src/console/providers/catalog/store.ts +25 -2
- package/src/main.ts +6 -0
- package/src/observability/event-query.ts +10 -5
- package/src/providers/operations/account-health-service.ts +211 -17
- package/dist/dashboard/assets/BannedPage-C8pfspAj.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-D-OwfzDV.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-DqRcDO5j.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-B-a0l8yE.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-BVVGUdqt.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-BzmbE05k.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-BRDc_wx8.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-BRDc_wx8.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-BdSGc5J0.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-DVny4S5_.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-D3fMRkpP.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-DlBwPsHM.js +0 -3
- package/dist/dashboard/assets/ProviderDetailPage-DlBwPsHM.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-T7WamMuq.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-CKJb_Kj4.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-hxJ1hGES.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-CpEErAq5.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-DRsoneXm.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-QvDz0eQo.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-6yLBksbv.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-CpB88NH4.js.gz +0 -0
- package/dist/dashboard/assets/app-CEeaMHOy.css +0 -2
- package/dist/dashboard/assets/app-CEeaMHOy.css.gz +0 -0
- package/dist/dashboard/assets/badge-CKsF123d.js.gz +0 -0
- package/dist/dashboard/assets/banner-8j7uChA9.js.gz +0 -0
- package/dist/dashboard/assets/card-CdsSlWA3.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-uL5aYLfW.js.gz +0 -0
- package/dist/dashboard/assets/dialog-C07hr5_G.js.gz +0 -0
- package/dist/dashboard/assets/drawer-BPO1TcKH.js.gz +0 -0
- package/dist/dashboard/assets/entry-8U_8IAW2.js.gz +0 -0
- package/dist/dashboard/assets/entry-Bz_CtDfC.js.gz +0 -0
- package/dist/dashboard/assets/entry-GJRbmrpA.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-DmQreSDd.js.gz +0 -0
- package/dist/dashboard/assets/index-BG48fsZ2.js.gz +0 -0
- package/dist/dashboard/assets/inline-C9dCYaBZ.js.gz +0 -0
- package/dist/dashboard/assets/input-CjCnXweJ.js.gz +0 -0
- package/dist/dashboard/assets/layout-V6y8DAkl.js.gz +0 -0
- package/dist/dashboard/assets/list-row-CM1AaG_y.js.gz +0 -0
- package/dist/dashboard/assets/numbers-BOevJzdz.js.gz +0 -0
- package/dist/dashboard/assets/stack-_gfTdLVB.js.gz +0 -0
- package/dist/dashboard/assets/state-2c5V4zo7.js.gz +0 -0
- package/dist/dashboard/assets/switch-Dkke7Naf.js.gz +0 -0
- /package/dist/dashboard/assets/{app-2KhtHCv9.js → app-MdVfwraB.js} +0 -0
- /package/dist/dashboard/assets/{app-2KhtHCv9.js.gz → app-MdVfwraB.js.gz} +0 -0
package/package.json
CHANGED
|
@@ -1,96 +1,96 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "cartethyia",
|
|
3
|
-
"version": "2.6.
|
|
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
|
-
"license": "GPL-3.0-only",
|
|
6
|
-
"type": "module",
|
|
7
|
-
"packageManager": "bun@1.4.0",
|
|
8
|
-
"repository": {
|
|
9
|
-
"type": "git",
|
|
10
|
-
"url": "git+https://github.com/risunCodex/Cartethyia.git"
|
|
11
|
-
},
|
|
12
|
-
"homepage": "https://github.com/risunCodex/Cartethyia#readme",
|
|
13
|
-
"bugs": {
|
|
14
|
-
"url": "https://github.com/risunCodex/Cartethyia/issues"
|
|
15
|
-
},
|
|
16
|
-
"keywords": [
|
|
17
|
-
"ai",
|
|
18
|
-
"gateway",
|
|
19
|
-
"llm",
|
|
20
|
-
"bun",
|
|
21
|
-
"self-hosted"
|
|
22
|
-
],
|
|
23
|
-
"bin": {
|
|
24
|
-
"cartethyia": "scripts/cli/bin/cartethyia.cjs",
|
|
25
|
-
"carte": "scripts/cli/bin/carte.cjs"
|
|
26
|
-
},
|
|
27
|
-
"files": [
|
|
28
|
-
"scripts/cli/",
|
|
29
|
-
"src/",
|
|
30
|
-
"migrations/",
|
|
31
|
-
"dist/dashboard/",
|
|
32
|
-
".env.example",
|
|
33
|
-
"README.md"
|
|
34
|
-
],
|
|
35
|
-
"workspaces": [
|
|
36
|
-
"dashboard"
|
|
37
|
-
],
|
|
38
|
-
"engines": {
|
|
39
|
-
"bun": ">=1.4.0"
|
|
40
|
-
},
|
|
41
|
-
"scripts": {
|
|
42
|
-
"dev": "bun run dev:stack",
|
|
43
|
-
"dev:watch": "bun run scripts/dev/supervisor.ts",
|
|
44
|
-
"test": "bun run scripts/test/run.ts",
|
|
45
|
-
"build": "bun run scripts/build/build.ts",
|
|
46
|
-
"prepack": "bun run dashboard:build",
|
|
47
|
-
"setup": "bun run scripts/cli/setup.ts",
|
|
48
|
-
"start": "bun run scripts/cli/main.ts start",
|
|
49
|
-
"doctor": "bun run scripts/cli/doctor.ts",
|
|
50
|
-
"probe:egress": "bun run scripts/tools/probe-egress.ts",
|
|
51
|
-
"restart": "bun run scripts/build/restart.ts",
|
|
52
|
-
"build:aot": "bun run scripts/build/aot.ts",
|
|
53
|
-
"dev:stack": "concurrently -n BACKEND,DASHBOARD -c cyan,magenta --kill-others-on-fail \"bun run dev:backend\" \"bun run dashboard:dev\"",
|
|
54
|
-
"typecheck": "tsc --noEmit",
|
|
55
|
-
"test:watch": "bun run scripts/test/run.ts --watch",
|
|
56
|
-
"test-db:up": "docker compose -f docker-compose.test.yml up -d --wait postgres-test",
|
|
57
|
-
"dev:backend": "bun run --hot src/main.ts",
|
|
58
|
-
"dev:console-auth": "bun run scripts/test/console-session.ts",
|
|
59
|
-
"dev:visual-baseline": "bun run scripts/test/visual-baseline.ts",
|
|
60
|
-
"build:binary": "bun run scripts/build/compile.ts",
|
|
61
|
-
"test:backend": "bun run scripts/test/run.ts --scope backend",
|
|
62
|
-
"test:integration": "bun run scripts/test/run.ts --scope integration",
|
|
63
|
-
"test-db:down": "docker compose -f docker-compose.test.yml down",
|
|
64
|
-
"dashboard:dev": "bun run --cwd dashboard dev",
|
|
65
|
-
"test-db:check": "bun run scripts/test/check-db.ts",
|
|
66
|
-
"dashboard:test": "bun run scripts/test/run.ts --scope dashboard",
|
|
67
|
-
"test-db:reset": "docker compose -f docker-compose.test.yml down -v && docker compose -f docker-compose.test.yml up -d --wait postgres-test",
|
|
68
|
-
"dashboard:build": "bun run --cwd dashboard build",
|
|
69
|
-
"dashboard:typecheck": "bun run --cwd dashboard typecheck",
|
|
70
|
-
"check:migrations": "bun run scripts/test/check-migrations.ts"
|
|
71
|
-
},
|
|
72
|
-
"dependencies": {
|
|
73
|
-
"drizzle-orm": "0.45.2",
|
|
74
|
-
"elysia": "2.0.0-beta.16",
|
|
75
|
-
"enquirer": "^2.4.1",
|
|
76
|
-
"ioredis": "6.0.0",
|
|
77
|
-
"jose": "^6.2.12",
|
|
78
|
-
"jsonrepair": "^3.15.0",
|
|
79
|
-
"pg": "8.23.0",
|
|
80
|
-
"pino": "^10.3.1",
|
|
81
|
-
"pino-pretty": "^13.1.3",
|
|
82
|
-
"protobufjs": "^7.6.5",
|
|
83
|
-
"socks-proxy-agent": "^10.1.0",
|
|
84
|
-
"systray2": "2.1.4",
|
|
85
|
-
"typebox": "1.3.27",
|
|
86
|
-
"ws": "8.22.0"
|
|
87
|
-
},
|
|
88
|
-
"devDependencies": {
|
|
89
|
-
"@types/pg": "8.11.10",
|
|
90
|
-
"@types/ws": "8.18.1",
|
|
91
|
-
"bun-types": "1.4.0",
|
|
92
|
-
"concurrently": "^10.0.5",
|
|
93
|
-
"drizzle-kit": "^0.31.11",
|
|
94
|
-
"typescript": "5.9.2"
|
|
95
|
-
}
|
|
96
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "cartethyia",
|
|
3
|
+
"version": "2.6.5",
|
|
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
|
+
"license": "GPL-3.0-only",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"packageManager": "bun@1.4.0",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/risunCodex/Cartethyia.git"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/risunCodex/Cartethyia#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/risunCodex/Cartethyia/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"ai",
|
|
18
|
+
"gateway",
|
|
19
|
+
"llm",
|
|
20
|
+
"bun",
|
|
21
|
+
"self-hosted"
|
|
22
|
+
],
|
|
23
|
+
"bin": {
|
|
24
|
+
"cartethyia": "scripts/cli/bin/cartethyia.cjs",
|
|
25
|
+
"carte": "scripts/cli/bin/carte.cjs"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"scripts/cli/",
|
|
29
|
+
"src/",
|
|
30
|
+
"migrations/",
|
|
31
|
+
"dist/dashboard/",
|
|
32
|
+
".env.example",
|
|
33
|
+
"README.md"
|
|
34
|
+
],
|
|
35
|
+
"workspaces": [
|
|
36
|
+
"dashboard"
|
|
37
|
+
],
|
|
38
|
+
"engines": {
|
|
39
|
+
"bun": ">=1.4.0"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"dev": "bun run dev:stack",
|
|
43
|
+
"dev:watch": "bun run scripts/dev/supervisor.ts",
|
|
44
|
+
"test": "bun run scripts/test/run.ts",
|
|
45
|
+
"build": "bun run scripts/build/build.ts",
|
|
46
|
+
"prepack": "bun run dashboard:build",
|
|
47
|
+
"setup": "bun run scripts/cli/setup.ts",
|
|
48
|
+
"start": "bun run scripts/cli/main.ts start",
|
|
49
|
+
"doctor": "bun run scripts/cli/doctor.ts",
|
|
50
|
+
"probe:egress": "bun run scripts/tools/probe-egress.ts",
|
|
51
|
+
"restart": "bun run scripts/build/restart.ts",
|
|
52
|
+
"build:aot": "bun run scripts/build/aot.ts",
|
|
53
|
+
"dev:stack": "concurrently -n BACKEND,DASHBOARD -c cyan,magenta --kill-others-on-fail \"bun run dev:backend\" \"bun run dashboard:dev\"",
|
|
54
|
+
"typecheck": "tsc --noEmit",
|
|
55
|
+
"test:watch": "bun run scripts/test/run.ts --watch",
|
|
56
|
+
"test-db:up": "docker compose -f docker-compose.test.yml up -d --wait postgres-test",
|
|
57
|
+
"dev:backend": "bun run --hot src/main.ts",
|
|
58
|
+
"dev:console-auth": "bun run scripts/test/console-session.ts",
|
|
59
|
+
"dev:visual-baseline": "bun run scripts/test/visual-baseline.ts",
|
|
60
|
+
"build:binary": "bun run scripts/build/compile.ts",
|
|
61
|
+
"test:backend": "bun run scripts/test/run.ts --scope backend",
|
|
62
|
+
"test:integration": "bun run scripts/test/run.ts --scope integration",
|
|
63
|
+
"test-db:down": "docker compose -f docker-compose.test.yml down",
|
|
64
|
+
"dashboard:dev": "bun run --cwd dashboard dev",
|
|
65
|
+
"test-db:check": "bun run scripts/test/check-db.ts",
|
|
66
|
+
"dashboard:test": "bun run scripts/test/run.ts --scope dashboard",
|
|
67
|
+
"test-db:reset": "docker compose -f docker-compose.test.yml down -v && docker compose -f docker-compose.test.yml up -d --wait postgres-test",
|
|
68
|
+
"dashboard:build": "bun run --cwd dashboard build",
|
|
69
|
+
"dashboard:typecheck": "bun run --cwd dashboard typecheck",
|
|
70
|
+
"check:migrations": "bun run scripts/test/check-migrations.ts"
|
|
71
|
+
},
|
|
72
|
+
"dependencies": {
|
|
73
|
+
"drizzle-orm": "0.45.2",
|
|
74
|
+
"elysia": "2.0.0-beta.16",
|
|
75
|
+
"enquirer": "^2.4.1",
|
|
76
|
+
"ioredis": "6.0.0",
|
|
77
|
+
"jose": "^6.2.12",
|
|
78
|
+
"jsonrepair": "^3.15.0",
|
|
79
|
+
"pg": "8.23.0",
|
|
80
|
+
"pino": "^10.3.1",
|
|
81
|
+
"pino-pretty": "^13.1.3",
|
|
82
|
+
"protobufjs": "^7.6.5",
|
|
83
|
+
"socks-proxy-agent": "^10.1.0",
|
|
84
|
+
"systray2": "2.1.4",
|
|
85
|
+
"typebox": "1.3.27",
|
|
86
|
+
"ws": "8.22.0"
|
|
87
|
+
},
|
|
88
|
+
"devDependencies": {
|
|
89
|
+
"@types/pg": "8.11.10",
|
|
90
|
+
"@types/ws": "8.18.1",
|
|
91
|
+
"bun-types": "1.4.0",
|
|
92
|
+
"concurrently": "^10.0.5",
|
|
93
|
+
"drizzle-kit": "^0.31.11",
|
|
94
|
+
"typescript": "5.9.2"
|
|
95
|
+
}
|
|
96
|
+
}
|
package/src/boot/app.ts
CHANGED
|
@@ -269,7 +269,17 @@ export function createGatewayApp(deps: GatewayAppDeps) {
|
|
|
269
269
|
const INFLIGHT_BACKSTOP_INTERVAL_MS = 30_000;
|
|
270
270
|
const INFLIGHT_BACKSTOP_GRACE_MS = 30_000;
|
|
271
271
|
const INFLIGHT_BACKSTOP_HARD_GRACE_MS = 120_000;
|
|
272
|
-
|
|
272
|
+
// Registered in every mode, not just production. The gauge is the operator's
|
|
273
|
+
// concurrency signal, and a cancelled request that never ran a finalizer
|
|
274
|
+
// leaves its flight behind — gating the only thing that reaps those on
|
|
275
|
+
// production made a permanently wrong count depend on how the gateway was
|
|
276
|
+
// started. The registry exists in every mode, so there is no mode where
|
|
277
|
+
// running this is wrong.
|
|
278
|
+
// `scheduledTasks` is declared on the production shape only, so it is
|
|
279
|
+
// narrowed rather than reached through the union — a shell composition
|
|
280
|
+
// without the registry simply skips the backstop, exactly as before.
|
|
281
|
+
const backstopTasks = "scheduledTasks" in deps ? deps.scheduledTasks : undefined;
|
|
282
|
+
backstopTasks?.register({
|
|
273
283
|
name: "inflight-backstop",
|
|
274
284
|
intervalMs: INFLIGHT_BACKSTOP_INTERVAL_MS,
|
|
275
285
|
run: () => {
|
|
@@ -509,6 +509,13 @@ export interface ProviderAccountResponse {
|
|
|
509
509
|
credentialKind: CredentialKind;
|
|
510
510
|
credentialMode?: CredentialMode;
|
|
511
511
|
tokenExpiresAt?: string;
|
|
512
|
+
/**
|
|
513
|
+
* Who issued this credential, read from the JWT `iss` claim at registration
|
|
514
|
+
* time. Present only for static JWTs, and only when the payload carried the
|
|
515
|
+
* claim: the secret is never readable after registration, so this is the one
|
|
516
|
+
* record of where the token came from.
|
|
517
|
+
*/
|
|
518
|
+
jwtIssuer?: string;
|
|
512
519
|
status: string;
|
|
513
520
|
/** Live routing admission count for this request path; absent when unavailable. */
|
|
514
521
|
inflight?: number;
|
|
@@ -59,11 +59,26 @@ function parseExpiry(value: unknown): Date | undefined {
|
|
|
59
59
|
}
|
|
60
60
|
return undefined;
|
|
61
61
|
}
|
|
62
|
-
|
|
62
|
+
/**
|
|
63
|
+
* Reads the two claims a static token's own payload can answer: when it stops
|
|
64
|
+
* working, and who issued it.
|
|
65
|
+
*
|
|
66
|
+
* The issuer is kept because a static JWT carries no other hint of its origin:
|
|
67
|
+
* an operator who pasted one from the wrong surface has nothing in the console
|
|
68
|
+
* to check it against, and several issuers accept the same token shape.
|
|
69
|
+
*/
|
|
70
|
+
function parseJwtCredential(
|
|
71
|
+
raw: string,
|
|
72
|
+
): { readonly expiresAt?: Date; readonly issuer?: string } | undefined {
|
|
63
73
|
const payload = decodeJwtPayload(raw.trim());
|
|
64
74
|
if (payload === undefined) return undefined;
|
|
65
75
|
const expiresAt = parseExpiry(payload["exp"]);
|
|
66
|
-
|
|
76
|
+
const iss = payload["iss"];
|
|
77
|
+
const issuer = typeof iss === "string" && iss.trim().length > 0 ? iss.trim() : undefined;
|
|
78
|
+
return {
|
|
79
|
+
...(expiresAt === undefined ? {} : { expiresAt }),
|
|
80
|
+
...(issuer === undefined ? {} : { issuer }),
|
|
81
|
+
};
|
|
67
82
|
}
|
|
68
83
|
|
|
69
84
|
/**
|
|
@@ -1124,6 +1139,7 @@ export class DrizzleProviderCatalogStore implements ProviderCatalogStore {
|
|
|
1124
1139
|
const requestedCredentialMode: CredentialMode = request.credentialMode ?? "auto";
|
|
1125
1140
|
let effectiveCredentialMode: CredentialMode = requestedCredentialMode;
|
|
1126
1141
|
let tokenExpiresAt: Date | undefined;
|
|
1142
|
+
let jwtIssuer: string | undefined;
|
|
1127
1143
|
let oauthRefreshCiphertext: Buffer | undefined;
|
|
1128
1144
|
let oauthExpiresAt: Date | undefined;
|
|
1129
1145
|
let oauthHasRefreshToken = false;
|
|
@@ -1160,6 +1176,9 @@ export class DrizzleProviderCatalogStore implements ProviderCatalogStore {
|
|
|
1160
1176
|
effectiveCredentialKind = "api_key";
|
|
1161
1177
|
effectiveCredentialMode = "jwt";
|
|
1162
1178
|
tokenExpiresAt = parsedJwt.expiresAt;
|
|
1179
|
+
// Kept alongside the expiry: the console shows who issued a static
|
|
1180
|
+
// token, and the secret itself is never readable after this point.
|
|
1181
|
+
jwtIssuer = parsedJwt.issuer;
|
|
1163
1182
|
} else if (request.credentialKind === "oauth" || hasRefreshToken) {
|
|
1164
1183
|
effectiveSecret = parts.accessToken;
|
|
1165
1184
|
effectiveCredentialKind = hasRefreshToken ? "oauth" : "api_key";
|
|
@@ -1185,6 +1204,7 @@ export class DrizzleProviderCatalogStore implements ProviderCatalogStore {
|
|
|
1185
1204
|
...(request.authState ?? {}),
|
|
1186
1205
|
credentialMode: effectiveCredentialMode,
|
|
1187
1206
|
...(tokenExpiresAt === undefined ? {} : { tokenExpiresAt: tokenExpiresAt.toISOString() }),
|
|
1207
|
+
...(jwtIssuer === undefined ? {} : { jwtIssuer }),
|
|
1188
1208
|
};
|
|
1189
1209
|
const nextSortIndex =
|
|
1190
1210
|
sortIndexOverride ?? (await this.nextAccountSortIndex(tx, providerId, tenantId));
|
|
@@ -1409,6 +1429,8 @@ export class DrizzleProviderCatalogStore implements ProviderCatalogStore {
|
|
|
1409
1429
|
: undefined;
|
|
1410
1430
|
const tokenExpiresAt =
|
|
1411
1431
|
typeof authState?.["tokenExpiresAt"] === "string" ? authState["tokenExpiresAt"] : undefined;
|
|
1432
|
+
const jwtIssuer =
|
|
1433
|
+
typeof authState?.["jwtIssuer"] === "string" ? authState["jwtIssuer"] : undefined;
|
|
1412
1434
|
return {
|
|
1413
1435
|
id: row.id,
|
|
1414
1436
|
providerId: row.providerId,
|
|
@@ -1417,6 +1439,7 @@ export class DrizzleProviderCatalogStore implements ProviderCatalogStore {
|
|
|
1417
1439
|
credentialKind: row.credentialKind,
|
|
1418
1440
|
...(credentialMode === undefined ? {} : { credentialMode }),
|
|
1419
1441
|
...(tokenExpiresAt === undefined ? {} : { tokenExpiresAt }),
|
|
1442
|
+
...(jwtIssuer === undefined ? {} : { jwtIssuer }),
|
|
1420
1443
|
status: row.status,
|
|
1421
1444
|
...(usage.inflight === undefined ? {} : { inflight: usage.inflight }),
|
|
1422
1445
|
usageToday: usage.today,
|
package/src/main.ts
CHANGED
|
@@ -52,6 +52,12 @@ const app = boot
|
|
|
52
52
|
modelStrikes: boot.deps.modelStrikes,
|
|
53
53
|
maxBodyBytes: resolveMaxBodyBytes(),
|
|
54
54
|
scheduledTasks: boot.deps.scheduledTasks,
|
|
55
|
+
// The sampler is constructed in the dependency graph and already
|
|
56
|
+
// sampling on its own interval; without passing it here the app cannot
|
|
57
|
+
// bind its in-flight source, so `cartethyia_process_in_flight` stays
|
|
58
|
+
// pinned at the constructor's `() => 0` placeholder forever while the
|
|
59
|
+
// registry it was meant to read reports real flights.
|
|
60
|
+
runtimeMetricsSampler: boot.deps.runtimeMetricsSampler,
|
|
55
61
|
shutdownCoordinator: boot.shutdownCoordinator,
|
|
56
62
|
// Signal-free graceful stop for platforms where a catchable signal cannot
|
|
57
63
|
// be delivered (Windows). Off unless `CARTETHYIA_DRAIN_TOKEN` is set.
|
|
@@ -215,7 +215,8 @@ export function eventRecordFromRow(row: {
|
|
|
215
215
|
*/
|
|
216
216
|
function isPostgres(db: CartethyiaDatabase): boolean {
|
|
217
217
|
const dialect = (db as unknown as { dialect?: { constructor?: { name?: string } } }).dialect;
|
|
218
|
-
|
|
218
|
+
const name = dialect?.constructor?.name;
|
|
219
|
+
return name === "PostgresDialect" || name === "PgDialect";
|
|
219
220
|
}
|
|
220
221
|
|
|
221
222
|
/**
|
|
@@ -508,9 +509,14 @@ export async function chartTelemetry(
|
|
|
508
509
|
bucketSeconds: number,
|
|
509
510
|
): Promise<ChartBucket[]> {
|
|
510
511
|
const width = Math.max(1, Math.trunc(bucketSeconds));
|
|
512
|
+
// Inlined (not parameterised) so Postgres sees the SELECT and GROUP BY
|
|
513
|
+
// expressions as identical: parameter numbers differing between the two
|
|
514
|
+
// clauses made Postgres treat them as unrelated expressions referencing
|
|
515
|
+
// `created_at`, which 42803-rejected the query.
|
|
516
|
+
const w = sql.raw(String(width));
|
|
511
517
|
const bucketExpression = isPostgres(db)
|
|
512
|
-
? sql<number>`(cast(extract(epoch from ${telemetryEvents.createdAt}) as bigint) / ${
|
|
513
|
-
: sql<number>`(cast(strftime('%s', ${telemetryEvents.createdAt}) as integer) / ${
|
|
518
|
+
? sql<number>`(cast(extract(epoch from ${telemetryEvents.createdAt}) as bigint) / ${w}) * ${w}`
|
|
519
|
+
: sql<number>`(cast(strftime('%s', ${telemetryEvents.createdAt}) as integer) / ${w}) * ${w}`;
|
|
514
520
|
const rows = await db
|
|
515
521
|
.select({
|
|
516
522
|
at: bucketExpression,
|
|
@@ -521,8 +527,7 @@ export async function chartTelemetry(
|
|
|
521
527
|
})
|
|
522
528
|
.from(telemetryEvents)
|
|
523
529
|
.where(scoped(tenantId, since))
|
|
524
|
-
.groupBy(bucketExpression)
|
|
525
|
-
.orderBy(bucketExpression);
|
|
530
|
+
.groupBy(bucketExpression);
|
|
526
531
|
return rows.map((row) => ({
|
|
527
532
|
at: numeric(row.at) * 1000,
|
|
528
533
|
requests: numeric(row.requests),
|