cartethyia 2.7.1 → 2.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +2 -0
- package/dist/dashboard/assets/{BannedPage-D-J75hBM.js → BannedPage-2TJD37Hb.js} +1 -1
- package/dist/dashboard/assets/BannedPage-2TJD37Hb.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-xefJNRmJ.js +1 -0
- package/dist/dashboard/assets/CliToolDetailPage-xefJNRmJ.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-Rj9iHQ3F.js +1 -0
- package/dist/dashboard/assets/CliToolsPage-Rj9iHQ3F.js.gz +0 -0
- package/dist/dashboard/assets/{CombosPage-QoJ6foxo.js → CombosPage-JEbYJFxw.js} +1 -1
- package/dist/dashboard/assets/CombosPage-JEbYJFxw.js.gz +0 -0
- package/dist/dashboard/assets/{ConsoleLogPage-DpNRay4C.js → ConsoleLogPage-C3xx_inM.js} +1 -1
- package/dist/dashboard/assets/ConsoleLogPage-C3xx_inM.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-Dw306gJU.js +1 -0
- package/dist/dashboard/assets/CustomizationPage-Dw306gJU.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-jaBo1TWV.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-jaBo1TWV.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-DubdQcvX.js → LoginPage-C4w7vea-.js} +1 -1
- package/dist/dashboard/assets/LoginPage-C4w7vea-.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-BH8Tehdi.js → ModelPicker-BtPcAfot.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-BtPcAfot.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-CGC5gNs1.js +1 -0
- package/dist/dashboard/assets/OverviewPage-CGC5gNs1.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-DQcthNGt.js +4 -0
- package/dist/dashboard/assets/ProviderDetailPage-DQcthNGt.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-0OsXRGXe.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-0OsXRGXe.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-DdPc8slb.js +9 -0
- package/dist/dashboard/assets/ProxyPage-DdPc8slb.js.gz +0 -0
- package/dist/dashboard/assets/{QuotaPage-ATpaxrbl.js → QuotaPage-Ci5O4BnH.js} +1 -1
- package/dist/dashboard/assets/QuotaPage-Ci5O4BnH.js.gz +0 -0
- package/dist/dashboard/assets/{SettingsPage-CETophpx.js → SettingsPage-CDZjTt87.js} +1 -1
- package/dist/dashboard/assets/SettingsPage-CDZjTt87.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-BQS0Rx_u.js +1 -0
- package/dist/dashboard/assets/SetupPage-BQS0Rx_u.js.gz +0 -0
- package/dist/dashboard/assets/{SortableList-CAK-EN9z.js → SortableList-BAL5npBC.js} +1 -1
- package/dist/dashboard/assets/SortableList-BAL5npBC.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-B7W2_RhT.js → StudioPage-TP7voAQ0.js} +3 -3
- package/dist/dashboard/assets/StudioPage-TP7voAQ0.js.gz +0 -0
- package/dist/dashboard/assets/{UsagePage-6T4EFDjW.js → UsagePage-Cj_fEzhg.js} +13 -13
- package/dist/dashboard/assets/UsagePage-Cj_fEzhg.js.gz +0 -0
- package/dist/dashboard/assets/{api-keys-CUi6UXsR.js → api-keys-MbI7e7rt.js} +1 -1
- package/dist/dashboard/assets/api-keys-MbI7e7rt.js.gz +0 -0
- package/dist/dashboard/assets/app-B2vyPoII.css +2 -0
- package/dist/dashboard/assets/app-B2vyPoII.css.gz +0 -0
- package/dist/dashboard/assets/arrow-up-59ms1iiT.js +1 -0
- package/dist/dashboard/assets/arrow-up-59ms1iiT.js.gz +0 -0
- package/dist/dashboard/assets/{assert-67xXH5rf.js → assert-C0YYuZJ4.js} +1 -1
- package/dist/dashboard/assets/assert-C0YYuZJ4.js.gz +0 -0
- package/dist/dashboard/assets/{badge-DC2L43Mj.js → badge-B_DMpMJN.js} +1 -1
- package/dist/dashboard/assets/badge-B_DMpMJN.js.gz +0 -0
- package/dist/dashboard/assets/{banner-BBVcb0J3.js → banner-DMrMPmth.js} +1 -1
- package/dist/dashboard/assets/banner-DMrMPmth.js.gz +0 -0
- package/dist/dashboard/assets/{card-Cca1XC6G.js → card-Be5Uzamx.js} +1 -1
- package/dist/dashboard/assets/card-Be5Uzamx.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-KvsWa1Jl.js → cli-tools-muFRphDu.js} +1 -1
- package/dist/dashboard/assets/cli-tools-muFRphDu.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-DUzEbynq.js → clipboard-button-DOfrVmsi.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-DOfrVmsi.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-DFvWhwTa.js → dialog-C9omdjQt.js} +1 -1
- package/dist/dashboard/assets/dialog-C9omdjQt.js.gz +0 -0
- package/dist/dashboard/assets/drawer-Bm8WKaGY.js +1 -0
- package/dist/dashboard/assets/drawer-Bm8WKaGY.js.gz +0 -0
- package/dist/dashboard/assets/{entry-_xHo5x6r.js → entry-DS90gUks.js} +1 -1
- package/dist/dashboard/assets/entry-DS90gUks.js.gz +0 -0
- package/dist/dashboard/assets/{entry-BLBLxX0h.js → entry-j40LJotO.js} +3 -3
- package/dist/dashboard/assets/entry-j40LJotO.js.gz +0 -0
- package/dist/dashboard/assets/{entry-Bqy8RFvM.js → entry-q5HwZ8nN.js} +1 -1
- package/dist/dashboard/assets/entry-q5HwZ8nN.js.gz +0 -0
- package/dist/dashboard/assets/{concurrency-BDF0ok7y.js → fingerprint-pattern-BWuHtbW4.js} +1 -1
- package/dist/dashboard/assets/fingerprint-pattern-BWuHtbW4.js.gz +0 -0
- package/dist/dashboard/assets/{formatters-mpBPqAQ8.js → formatters-BTScrQpe.js} +1 -1
- package/dist/dashboard/assets/formatters-BTScrQpe.js.gz +0 -0
- package/dist/dashboard/assets/{github-badge-C9e8Ts7e.js → github-badge-Orjuzemr.js} +1 -1
- package/dist/dashboard/assets/github-badge-Orjuzemr.js.gz +0 -0
- package/dist/dashboard/assets/{index-D6QzEC_n.js → index-cPrX5vn6.js} +2 -2
- package/dist/dashboard/assets/index-cPrX5vn6.js.gz +0 -0
- package/dist/dashboard/assets/{inline-BFUcvllj.js → inline-Dvux8w-0.js} +1 -1
- package/dist/dashboard/assets/inline-Dvux8w-0.js.gz +0 -0
- package/dist/dashboard/assets/{input-C_ILAvBb.js → input-CU1Rgf3p.js} +1 -1
- package/dist/dashboard/assets/input-CU1Rgf3p.js.gz +0 -0
- package/dist/dashboard/assets/{layout-CBIyT6Rt.js → layout-C_7A6H55.js} +1 -1
- package/dist/dashboard/assets/layout-C_7A6H55.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-ebJ_-Nsb.js → list-row-CzFvogbj.js} +1 -1
- package/dist/dashboard/assets/list-row-CzFvogbj.js.gz +0 -0
- package/dist/dashboard/assets/{live-D0mm--ME.js → live-KaG15HLQ.js} +1 -1
- package/dist/dashboard/assets/live-KaG15HLQ.js.gz +0 -0
- package/dist/dashboard/assets/{mutation-sMD965oO.js → mutation-JJGQm8TD.js} +1 -1
- package/dist/dashboard/assets/mutation-JJGQm8TD.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-BO9dKaFr.js → numbers-BNDZnsUE.js} +1 -1
- package/dist/dashboard/assets/numbers-BNDZnsUE.js.gz +0 -0
- package/dist/dashboard/assets/{query-keys-CfL_Zdoh.js → query-keys-CLaNz8Xz.js} +1 -1
- package/dist/dashboard/assets/query-keys-CLaNz8Xz.js.gz +0 -0
- package/dist/dashboard/assets/{routing-BnIpi1IJ.js → routing-BEH32QOC.js} +1 -1
- package/dist/dashboard/assets/routing-BEH32QOC.js.gz +0 -0
- package/dist/dashboard/assets/{select-Cj5Nw43g.js → select-B3iTetHN.js} +1 -1
- package/dist/dashboard/assets/select-B3iTetHN.js.gz +0 -0
- package/dist/dashboard/assets/settings-2-Ds9DQp0t.js +1 -0
- package/dist/dashboard/assets/settings-2-Ds9DQp0t.js.gz +0 -0
- package/dist/dashboard/assets/{settings-BAFhDgX8.js → settings-Dt3Eqbnq.js} +1 -1
- package/dist/dashboard/assets/settings-Dt3Eqbnq.js.gz +0 -0
- package/dist/dashboard/assets/{stack-EMrOMt4I.js → stack-KrOrm7JZ.js} +1 -1
- package/dist/dashboard/assets/stack-KrOrm7JZ.js.gz +0 -0
- package/dist/dashboard/assets/{state-CWoo7ty5.js → state-DLS7IDMy.js} +1 -1
- package/dist/dashboard/assets/state-DLS7IDMy.js.gz +0 -0
- package/dist/dashboard/assets/{switch-CCjT6IkF.js → switch-42ItvbAs.js} +1 -1
- package/dist/dashboard/assets/switch-42ItvbAs.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-DjkLHUcE.js → useMutation-Dmnfh1Ue.js} +1 -1
- package/dist/dashboard/assets/useMutation-Dmnfh1Ue.js.gz +0 -0
- package/dist/dashboard/index.html +3 -2
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +117 -115
- package/migrations/full-postgres/0026_add_qoder_native_identity.sql +20 -0
- package/migrations/full-postgres/0027_qoder_identity_genuine_seed.sql +22 -0
- package/migrations/full-postgres/0028_add_family_template_key_telemetry.sql +40 -0
- package/migrations/full-postgres/0029_null_share_child_policy_columns.sql +38 -0
- package/migrations/full-postgres/0030_add_api_key_max_children.sql +10 -0
- package/migrations/full-postgres/0031_tenant_system_prompts.sql +16 -0
- package/migrations/lite-sqlite3/0026_add_qoder_native_identity.sql +15 -0
- package/migrations/lite-sqlite3/0027_qoder_identity_genuine_seed.sql +22 -0
- package/migrations/lite-sqlite3/0028_add_family_template_key_telemetry.sql +40 -0
- package/migrations/lite-sqlite3/0029_null_share_child_policy_columns.sql +38 -0
- package/migrations/lite-sqlite3/0030_add_api_key_max_children.sql +10 -0
- package/migrations/lite-sqlite3/0031_tenant_system_prompts.sql +10 -0
- package/package.json +1 -1
- package/src/config/network.ts +1 -1
- package/src/console/api-keys/bucket-spend.ts +7 -7
- package/src/console/api-keys/contracts.ts +423 -410
- package/src/console/api-keys/routes.ts +118 -19
- package/src/console/backup/export-endpoint.ts +8 -11
- package/src/console/backup/routes.ts +40 -8
- package/src/console/backup/service.ts +11 -6
- package/src/console/backup/store.ts +384 -88
- package/src/console/cli-tools/contracts.ts +10 -4
- package/src/console/dashboard-assets.ts +16 -1
- package/src/console/domain-registration.ts +2 -0
- package/src/console/quota/account-quota-tenant.ts +8 -1
- package/src/console/quota/account-quota.ts +4 -1
- package/src/console/quota/native-identity.ts +189 -0
- package/src/console/remote-support/contracts.ts +12 -6
- package/src/console/remote-support/service.ts +2 -2
- package/src/console/share/share-router.ts +39 -6
- package/src/console/share/share-stats.ts +64 -25
- package/src/console/share/share-usage.ts +24 -7
- package/src/console/system-prompt/contracts.ts +102 -0
- package/src/console/system-prompt/routes.ts +165 -0
- package/src/main.ts +11 -0
- package/src/network/outbound-fetch.ts +281 -226
- package/src/network/pool/agent.ts +7 -0
- package/src/observability/event-query.ts +118 -7
- package/src/persistence/db-pg.ts +42 -0
- package/src/persistence/schema.ts +102 -0
- package/src/persistence/stores/api-key-store.ts +753 -581
- package/src/persistence/stores/share-store.ts +65 -13
- package/src/persistence/stores/telemetry-store.ts +57 -1
- package/src/persistence/system-prompt-store.ts +104 -0
- package/src/protocol/reasoning.ts +65 -1
- package/src/protocol/request/chat.ts +1 -0
- package/src/providers/default-registry.ts +1 -0
- package/src/providers/integrations/antigravity/antigravity.ts +20 -23
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +30 -2
- package/src/providers/integrations/buddy/buddy-media.ts +5 -1
- package/src/providers/integrations/buddy/codebuddy/provider.ts +5 -1
- package/src/providers/integrations/buddy/workbuddy/provider.ts +5 -1
- package/src/providers/integrations/cline/cline.ts +7 -0
- package/src/providers/integrations/github/github.ts +20 -1
- package/src/providers/integrations/hermes.ts +5 -1
- package/src/providers/integrations/kilo/kilo.ts +4 -0
- package/src/providers/integrations/ollamacloud-models.ts +50 -0
- package/src/providers/integrations/openrouter/provider.ts +4 -0
- package/src/providers/integrations/qoder/qoder-checkin.ts +224 -51
- package/src/providers/integrations/qoder/qoder-native-identity-store.ts +156 -0
- package/src/providers/integrations/qoder/qoder-oauth.ts +24 -4
- package/src/providers/integrations/qoder/qoder-realm.ts +20 -0
- package/src/providers/integrations/qoder/qoder-text-tools.ts +277 -0
- package/src/providers/integrations/qoder/qoder-umid.ts +288 -0
- package/src/providers/integrations/qoder/qoder.ts +1375 -1641
- package/src/providers/operations/account-error-classification.ts +1682 -1660
- package/src/providers/operations/account-health-service.ts +619 -621
- package/src/providers/operations/client-versions.ts +37 -22
- package/src/providers/operations/provider-catalog-service.ts +11 -1
- package/src/providers/provider-contracts.ts +15 -2
- package/src/security/access-control.ts +19 -0
- package/src/security/api-key-auth.ts +14 -1
- package/src/security/cli-client-fingerprint.ts +9 -2
- package/src/transport/dispatch/empty-stream.ts +224 -224
- package/src/transport/dispatch/proxy-request.ts +31 -2
- package/src/transport/dispatch/streaming/stream.ts +16 -1
- package/src/transport/dispatch/upstream.ts +10 -0
- package/src/transport/failure-policy.ts +33 -0
- package/src/transport/request/preparer.ts +17 -1
- package/src/transport/request/system-prompt-injection.ts +89 -0
- package/src/transport/request/tenant-preferences.ts +43 -0
- package/src/transport/routing/admission.ts +13 -3
- package/src/transport/routing/route-catalog.ts +41 -12
- package/src/transport/routing/route-model.ts +53 -0
- package/src/transport/routing/router.ts +306 -228
- package/src/transport/surface/responses/encode.ts +2 -3
- package/src/transport/surface/responses/parse.ts +49 -7
- package/src/types/undici.d.ts +33 -0
- package/src/workers/daily-checkin.ts +2 -0
- package/dist/dashboard/assets/BannedPage-D-J75hBM.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-CVe6RaBH.js +0 -1
- package/dist/dashboard/assets/CliToolDetailPage-CVe6RaBH.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-Dd8ZSamp.js +0 -1
- package/dist/dashboard/assets/CliToolsPage-Dd8ZSamp.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-QoJ6foxo.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-DpNRay4C.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-CKUFTcW4.js +0 -1
- package/dist/dashboard/assets/CustomizationPage-CKUFTcW4.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-CzSLiDzr.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-CzSLiDzr.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-DubdQcvX.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-BH8Tehdi.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-DuWzAQFL.js +0 -1
- package/dist/dashboard/assets/OverviewPage-DuWzAQFL.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-BKEKe2Fm.js +0 -4
- package/dist/dashboard/assets/ProviderDetailPage-BKEKe2Fm.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-Bwotu8UJ.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-Bwotu8UJ.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-D7hpWbg9.js +0 -9
- package/dist/dashboard/assets/ProxyPage-D7hpWbg9.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-ATpaxrbl.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-CETophpx.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-BdgECjqq.js +0 -1
- package/dist/dashboard/assets/SetupPage-BdgECjqq.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-CAK-EN9z.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-B7W2_RhT.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-6T4EFDjW.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-CUi6UXsR.js.gz +0 -0
- package/dist/dashboard/assets/app-KvsUJUMF.css +0 -2
- package/dist/dashboard/assets/app-KvsUJUMF.css.gz +0 -0
- package/dist/dashboard/assets/assert-67xXH5rf.js.gz +0 -0
- package/dist/dashboard/assets/badge-DC2L43Mj.js.gz +0 -0
- package/dist/dashboard/assets/banner-BBVcb0J3.js.gz +0 -0
- package/dist/dashboard/assets/card-Cca1XC6G.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-KvsWa1Jl.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-DUzEbynq.js.gz +0 -0
- package/dist/dashboard/assets/concurrency-BDF0ok7y.js.gz +0 -0
- package/dist/dashboard/assets/dialog-DFvWhwTa.js.gz +0 -0
- package/dist/dashboard/assets/drawer-CFM2sMX1.js +0 -1
- package/dist/dashboard/assets/drawer-CFM2sMX1.js.gz +0 -0
- package/dist/dashboard/assets/entry-BLBLxX0h.js.gz +0 -0
- package/dist/dashboard/assets/entry-Bqy8RFvM.js.gz +0 -0
- package/dist/dashboard/assets/entry-_xHo5x6r.js.gz +0 -0
- package/dist/dashboard/assets/formatters-mpBPqAQ8.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-C9e8Ts7e.js.gz +0 -0
- package/dist/dashboard/assets/index-D6QzEC_n.js.gz +0 -0
- package/dist/dashboard/assets/inline-BFUcvllj.js.gz +0 -0
- package/dist/dashboard/assets/input-C_ILAvBb.js.gz +0 -0
- package/dist/dashboard/assets/layout-CBIyT6Rt.js.gz +0 -0
- package/dist/dashboard/assets/list-row-ebJ_-Nsb.js.gz +0 -0
- package/dist/dashboard/assets/live-D0mm--ME.js.gz +0 -0
- package/dist/dashboard/assets/mutation-sMD965oO.js.gz +0 -0
- package/dist/dashboard/assets/numbers-BO9dKaFr.js.gz +0 -0
- package/dist/dashboard/assets/query-keys-CfL_Zdoh.js.gz +0 -0
- package/dist/dashboard/assets/routing-BnIpi1IJ.js.gz +0 -0
- package/dist/dashboard/assets/select-Cj5Nw43g.js.gz +0 -0
- package/dist/dashboard/assets/settings-BAFhDgX8.js.gz +0 -0
- package/dist/dashboard/assets/stack-EMrOMt4I.js.gz +0 -0
- package/dist/dashboard/assets/state-CWoo7ty5.js.gz +0 -0
- package/dist/dashboard/assets/switch-CCjT6IkF.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-DjkLHUcE.js.gz +0 -0
- /package/dist/dashboard/assets/{app-BaWd0puM.js → app-wvwCi4Eh.js} +0 -0
- /package/dist/dashboard/assets/{app-BaWd0puM.js.gz → app-wvwCi4Eh.js.gz} +0 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// Tenant system-prompt injection contracts.
|
|
2
|
+
//
|
|
3
|
+
// Shared by the console API, the persistence store, and the dispatch-path
|
|
4
|
+
// transform — all Elysia-free so the dashboard bundle can import the types.
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* One scope's prompt configuration. `enabled` is the master switch for the
|
|
8
|
+
* scope; `mode` decides how the text meets existing system content.
|
|
9
|
+
*/
|
|
10
|
+
export interface PromptSetting {
|
|
11
|
+
readonly enabled: boolean;
|
|
12
|
+
readonly mode: PromptMode;
|
|
13
|
+
readonly text: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const PROMPT_MODES = ["append", "override"] as const;
|
|
17
|
+
export type PromptMode = (typeof PROMPT_MODES)[number];
|
|
18
|
+
|
|
19
|
+
/** Per-provider entry: follow the tenant's global prompt, or carry a custom one. */
|
|
20
|
+
export interface ProviderPromptEntry {
|
|
21
|
+
readonly followGlobal: boolean;
|
|
22
|
+
/** Present and used only when `followGlobal` is false. */
|
|
23
|
+
readonly setting?: PromptSetting;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** The full per-tenant configuration, one row in `tenant_system_prompts`. */
|
|
27
|
+
export interface TenantSystemPrompts {
|
|
28
|
+
/** `null` = never configured — distinct from an explicit disabled setting. */
|
|
29
|
+
readonly global: PromptSetting | null;
|
|
30
|
+
/** providerId → entry */
|
|
31
|
+
readonly providers: Readonly<Record<string, ProviderPromptEntry>>;
|
|
32
|
+
/** `${providerId}::${modelId}` → setting */
|
|
33
|
+
readonly models: Readonly<Record<string, PromptSetting>>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Empty configuration: nothing configured anywhere. */
|
|
37
|
+
export const EMPTY_TENANT_SYSTEM_PROMPTS: TenantSystemPrompts = {
|
|
38
|
+
global: null,
|
|
39
|
+
providers: {},
|
|
40
|
+
models: {},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/** Maximum stored text per scope (5 MB). Enforced at the console API. */
|
|
44
|
+
export const SYSTEM_PROMPT_TEXT_CAP_BYTES = 5_000_000;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Marker wrapping tenant text injected into a provider wire whose upstream
|
|
48
|
+
* validates the system prompt as a channel identity (the buddy family). The
|
|
49
|
+
* fixed identity prompt stays the opener; the tenant text rides after it
|
|
50
|
+
* inside the same turn, framed so the provider's own parser is not confused.
|
|
51
|
+
*/
|
|
52
|
+
export const SYSTEM_PROMPT_WRAPPER_PREFIX = "<cartethyia:system-prompt>";
|
|
53
|
+
export const SYSTEM_PROMPT_WRAPPER_SUFFIX = "</cartethyia:system-prompt>";
|
|
54
|
+
|
|
55
|
+
/** Maximum number of per-provider entries stored for one tenant. */
|
|
56
|
+
export const SYSTEM_PROMPT_PROVIDER_LIMIT = 256;
|
|
57
|
+
|
|
58
|
+
/** Maximum number of per-model entries stored for one tenant. */
|
|
59
|
+
export const SYSTEM_PROMPT_MODEL_LIMIT = 512;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Providers whose upstream validates the fixed system prompt as a channel
|
|
63
|
+
* identity. Their system turn must keep the fixed prompt as the opener, so
|
|
64
|
+
* tenant text is appended after it inside the same turn, wrapped in the
|
|
65
|
+
* gateway's prefix/suffix markers (see `SYSTEM_PROMPT_WRAPPER_PREFIX`).
|
|
66
|
+
* `override` mode is not honoured for these providers — it would strip the
|
|
67
|
+
* identity and the upstream would refuse the request.
|
|
68
|
+
*/
|
|
69
|
+
export const WRAPPED_INJECTION_PROVIDERS: ReadonlySet<string> = new Set([
|
|
70
|
+
"cb", // CodeBuddy International
|
|
71
|
+
"cbcn", // CodeBuddy CN
|
|
72
|
+
"workbuddy",
|
|
73
|
+
]);
|
|
74
|
+
|
|
75
|
+
/** Whether a provider's tenant text rides wrapped after the fixed identity prompt. */
|
|
76
|
+
export function isWrappedInjectionProvider(providerId: string): boolean {
|
|
77
|
+
return WRAPPED_INJECTION_PROVIDERS.has(providerId);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Model-scope key for the `models` map. */
|
|
81
|
+
export function systemPromptModelKey(providerId: string, modelId: string): string {
|
|
82
|
+
return `${providerId}::${modelId}`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Parses a model-scope key; null when the key is malformed. */
|
|
86
|
+
export function parseSystemPromptModelKey(key: string): { providerId: string; modelId: string } | null {
|
|
87
|
+
const index = key.indexOf("::");
|
|
88
|
+
if (index <= 0 || index === key.length - 2) return null;
|
|
89
|
+
return { providerId: key.slice(0, index), modelId: key.slice(index + 2) };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** True when the value is a well-formed prompt setting (shape only, not size). */
|
|
93
|
+
export function isPromptSetting(value: unknown): value is PromptSetting {
|
|
94
|
+
if (typeof value !== "object" || value === null) return false;
|
|
95
|
+
const candidate = value as Partial<PromptSetting>;
|
|
96
|
+
return (
|
|
97
|
+
typeof candidate.enabled === "boolean" &&
|
|
98
|
+
typeof candidate.mode === "string" &&
|
|
99
|
+
(PROMPT_MODES as readonly string[]).includes(candidate.mode) &&
|
|
100
|
+
typeof candidate.text === "string"
|
|
101
|
+
);
|
|
102
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// Console API for tenant system-prompt injection.
|
|
2
|
+
//
|
|
3
|
+
// GET /system-prompt → the stored configuration
|
|
4
|
+
// PUT /system-prompt → replace the configuration wholesale
|
|
5
|
+
// DELETE /system-prompt → clear it entirely
|
|
6
|
+
//
|
|
7
|
+
// The PUT replaces the row: the modal edits the whole three-scope shape at
|
|
8
|
+
// once, so a PATCH surface would add states the UI never produces. Text is
|
|
9
|
+
// capped at 5 MB per scope and the provider/model maps are bounded, matching
|
|
10
|
+
// the store's own defensive limits.
|
|
11
|
+
import { Elysia, t } from "elysia";
|
|
12
|
+
import type { ConsoleAccessResolver } from "../auth/access";
|
|
13
|
+
import { ConsoleDomainError, consoleErrorHandler, requireTenantScope } from "../shared/errors";
|
|
14
|
+
import type { AuditSink } from "../audit/contracts";
|
|
15
|
+
import type { CartethyiaDatabase } from "../../persistence/db-handle";
|
|
16
|
+
import {
|
|
17
|
+
clearTenantSystemPrompts,
|
|
18
|
+
readTenantSystemPrompts,
|
|
19
|
+
writeTenantSystemPrompts,
|
|
20
|
+
} from "../../persistence/system-prompt-store";
|
|
21
|
+
import {
|
|
22
|
+
EMPTY_TENANT_SYSTEM_PROMPTS,
|
|
23
|
+
PROMPT_MODES,
|
|
24
|
+
SYSTEM_PROMPT_MODEL_LIMIT,
|
|
25
|
+
SYSTEM_PROMPT_PROVIDER_LIMIT,
|
|
26
|
+
SYSTEM_PROMPT_TEXT_CAP_BYTES,
|
|
27
|
+
isPromptSetting,
|
|
28
|
+
parseSystemPromptModelKey,
|
|
29
|
+
type PromptSetting,
|
|
30
|
+
type ProviderPromptEntry,
|
|
31
|
+
type TenantSystemPrompts,
|
|
32
|
+
} from "./contracts";
|
|
33
|
+
|
|
34
|
+
/** Route-group context injected by the composition root. */
|
|
35
|
+
export interface SystemPromptRoutesConfig {
|
|
36
|
+
readonly db: CartethyiaDatabase;
|
|
37
|
+
readonly accessResolver: ConsoleAccessResolver;
|
|
38
|
+
readonly auditSink?: AuditSink;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Validates one scope setting, including the 5 MB text cap. */
|
|
42
|
+
function validateSetting(value: unknown, scopeLabel: string): PromptSetting {
|
|
43
|
+
if (!isPromptSetting(value)) {
|
|
44
|
+
throw new ConsoleDomainError("invalid_request", 400, `${scopeLabel} is not a valid prompt setting`);
|
|
45
|
+
}
|
|
46
|
+
const bytes = Buffer.byteLength(value.text, "utf8");
|
|
47
|
+
if (bytes > SYSTEM_PROMPT_TEXT_CAP_BYTES) {
|
|
48
|
+
throw new ConsoleDomainError("payload_too_large", 413, `${scopeLabel} exceeds the 5 MB limit`);
|
|
49
|
+
}
|
|
50
|
+
if (!(PROMPT_MODES as readonly string[]).includes(value.mode)) {
|
|
51
|
+
throw new ConsoleDomainError("invalid_request", 400, `${scopeLabel} has an invalid mode`);
|
|
52
|
+
}
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Validates the full PUT body into the stored shape. */
|
|
57
|
+
function validateBody(value: unknown): TenantSystemPrompts {
|
|
58
|
+
if (typeof value !== "object" || value === null) {
|
|
59
|
+
throw new ConsoleDomainError("invalid_request", 400, "Body must be an object");
|
|
60
|
+
}
|
|
61
|
+
const body = value as {
|
|
62
|
+
global?: unknown;
|
|
63
|
+
providers?: unknown;
|
|
64
|
+
models?: unknown;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const global =
|
|
68
|
+
body.global === null || body.global === undefined
|
|
69
|
+
? null
|
|
70
|
+
: validateSetting(body.global, "global");
|
|
71
|
+
|
|
72
|
+
if (body.providers !== undefined && typeof body.providers !== "object") {
|
|
73
|
+
throw new ConsoleDomainError("invalid_request", 400, "providers must be an object");
|
|
74
|
+
}
|
|
75
|
+
const rawProviders = (body.providers ?? {}) as Record<string, unknown>;
|
|
76
|
+
const providerIds = Object.keys(rawProviders);
|
|
77
|
+
if (providerIds.length > SYSTEM_PROMPT_PROVIDER_LIMIT) {
|
|
78
|
+
throw new ConsoleDomainError(
|
|
79
|
+
"invalid_request",
|
|
80
|
+
400,
|
|
81
|
+
`providers exceeds the ${SYSTEM_PROMPT_PROVIDER_LIMIT} entry limit`,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
const providers: Record<string, ProviderPromptEntry> = {};
|
|
85
|
+
for (const providerId of providerIds) {
|
|
86
|
+
const entry = rawProviders[providerId];
|
|
87
|
+
if (typeof entry !== "object" || entry === null) {
|
|
88
|
+
throw new ConsoleDomainError("invalid_request", 400, `provider ${providerId} entry must be an object`);
|
|
89
|
+
}
|
|
90
|
+
const candidate = entry as Partial<ProviderPromptEntry>;
|
|
91
|
+
if (typeof candidate.followGlobal !== "boolean") {
|
|
92
|
+
throw new ConsoleDomainError("invalid_request", 400, `provider ${providerId} needs followGlobal`);
|
|
93
|
+
}
|
|
94
|
+
if (candidate.followGlobal) {
|
|
95
|
+
providers[providerId] = { followGlobal: true };
|
|
96
|
+
} else {
|
|
97
|
+
providers[providerId] = {
|
|
98
|
+
followGlobal: false,
|
|
99
|
+
setting: validateSetting(candidate.setting, `provider ${providerId}`),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (body.models !== undefined && typeof body.models !== "object") {
|
|
105
|
+
throw new ConsoleDomainError("invalid_request", 400, "models must be an object");
|
|
106
|
+
}
|
|
107
|
+
const rawModels = (body.models ?? {}) as Record<string, unknown>;
|
|
108
|
+
const modelKeys = Object.keys(rawModels);
|
|
109
|
+
if (modelKeys.length > SYSTEM_PROMPT_MODEL_LIMIT) {
|
|
110
|
+
throw new ConsoleDomainError(
|
|
111
|
+
"invalid_request",
|
|
112
|
+
400,
|
|
113
|
+
`models exceeds the ${SYSTEM_PROMPT_MODEL_LIMIT} entry limit`,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
const models: Record<string, PromptSetting> = {};
|
|
117
|
+
for (const key of modelKeys) {
|
|
118
|
+
if (parseSystemPromptModelKey(key) === null) {
|
|
119
|
+
throw new ConsoleDomainError("invalid_request", 400, `model key ${key} is malformed`);
|
|
120
|
+
}
|
|
121
|
+
models[key] = validateSetting(rawModels[key], `model ${key}`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return { global, providers, models };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* The return type is inferred rather than annotated as `Elysia`: `.error()`
|
|
129
|
+
* widens the instance type with its handler's response envelope, so the
|
|
130
|
+
* annotated base type no longer matches the chained result (same reason the
|
|
131
|
+
* remote-support routes leave it inferred).
|
|
132
|
+
*/
|
|
133
|
+
export function createSystemPromptRoutes(config: SystemPromptRoutesConfig) {
|
|
134
|
+
return new Elysia()
|
|
135
|
+
.error(consoleErrorHandler("System prompt settings failed"))
|
|
136
|
+
.get("/system-prompt", async ({ request }) => {
|
|
137
|
+
const access = requireTenantScope(config.accessResolver(request), "dashboard:read");
|
|
138
|
+
return await readTenantSystemPrompts(config.db, access.tenantId);
|
|
139
|
+
})
|
|
140
|
+
.put(
|
|
141
|
+
"/system-prompt",
|
|
142
|
+
{ body: t.Any() },
|
|
143
|
+
async ({ request, body }) => {
|
|
144
|
+
const access = requireTenantScope(config.accessResolver(request), "dashboard:write");
|
|
145
|
+
const next = validateBody(body);
|
|
146
|
+
await writeTenantSystemPrompts(config.db, access.tenantId, next);
|
|
147
|
+
await config.auditSink?.record({
|
|
148
|
+
access,
|
|
149
|
+
action: "system_prompt.update",
|
|
150
|
+
target: "tenant_system_prompts",
|
|
151
|
+
detail: {
|
|
152
|
+
global: next.global !== null,
|
|
153
|
+
providers: Object.keys(next.providers).length,
|
|
154
|
+
models: Object.keys(next.models).length,
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
return next;
|
|
158
|
+
},
|
|
159
|
+
)
|
|
160
|
+
.delete("/system-prompt", async ({ request }) => {
|
|
161
|
+
const access = requireTenantScope(config.accessResolver(request), "dashboard:write");
|
|
162
|
+
await clearTenantSystemPrompts(config.db, access.tenantId);
|
|
163
|
+
return EMPTY_TENANT_SYSTEM_PROMPTS;
|
|
164
|
+
});
|
|
165
|
+
}
|
package/src/main.ts
CHANGED
|
@@ -169,6 +169,17 @@ if (boot) {
|
|
|
169
169
|
globalThis.__cartethyiaSignalsRegistered = true;
|
|
170
170
|
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
171
171
|
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
172
|
+
// A rejected promise nothing awaited (a sweep, a cache warm, a read whose
|
|
173
|
+
// parameters a bug elsewhere ballooned) must not kill the listener: the
|
|
174
|
+
// default is fatal exit, which turned one query fault into a systemd
|
|
175
|
+
// restart loop. Logged and absorbed — the failure stays visible, the
|
|
176
|
+
// process stays up.
|
|
177
|
+
process.on("unhandledRejection", (reason) => {
|
|
178
|
+
log.error(
|
|
179
|
+
"[process] unhandled rejection (kept alive)",
|
|
180
|
+
reason instanceof Error ? reason : new Error(String(reason)),
|
|
181
|
+
);
|
|
182
|
+
});
|
|
172
183
|
// An in-place update signals SIGUSR2 before swapping the image so the old
|
|
173
184
|
// process drains with the `update` reason and its callers are told the
|
|
174
185
|
// replacement is seconds away instead of seeing a generic shutdown. Not
|