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
|
@@ -2,6 +2,7 @@ import { GatewayError } from "../../protocol/gateway-error";
|
|
|
2
2
|
import type { CanonicalRequest, ServiceKind } from "../../protocol/canonical-model";
|
|
3
3
|
import { toolResultParts } from "../../protocol/canonical-model";
|
|
4
4
|
import type { ApiKeyAdmissionService } from "../../security/admission/service";
|
|
5
|
+
import type { CallerIdentity } from "../../security/access-control";
|
|
5
6
|
import type { RouteCandidate as RouteCandidate, InMemoryRouteSnapshotService, RoutePlan } from "../routing/route-model";
|
|
6
7
|
import type { RouteContinuationAffinity } from "../routing/route-model";
|
|
7
8
|
import { resolveAliasTarget, type RoutingEngine } from "../routing/router";
|
|
@@ -80,6 +81,8 @@ export function resolveContinuationAffinity(
|
|
|
80
81
|
apiKeyId: authorization.id,
|
|
81
82
|
};
|
|
82
83
|
let affinity: RouteContinuationAffinity | undefined;
|
|
84
|
+
let anyResolved = false;
|
|
85
|
+
let allResolved = true;
|
|
83
86
|
for (const callId of ids) {
|
|
84
87
|
// The registry resolves by tenant + API key + provider + account, so a call
|
|
85
88
|
// id replayed from another key or tenant resolves nothing.
|
|
@@ -91,6 +94,7 @@ export function resolveContinuationAffinity(
|
|
|
91
94
|
// through the ids that still resolve; when none do, the ordinary routing
|
|
92
95
|
// path decides (a Cursor adapter may rebase the full history into a
|
|
93
96
|
// fresh Run).
|
|
97
|
+
allResolved = false;
|
|
94
98
|
continue;
|
|
95
99
|
}
|
|
96
100
|
if (affinity !== undefined && affinity.providerAccountId !== record.providerAccountId) {
|
|
@@ -101,12 +105,21 @@ export function resolveContinuationAffinity(
|
|
|
101
105
|
{ callIds: [...ids] },
|
|
102
106
|
);
|
|
103
107
|
}
|
|
108
|
+
anyResolved = true;
|
|
104
109
|
affinity = {
|
|
105
110
|
providerId: record.providerId,
|
|
106
111
|
providerAccountId: record.providerAccountId,
|
|
112
|
+
rebaseable: false,
|
|
107
113
|
};
|
|
108
114
|
}
|
|
109
|
-
|
|
115
|
+
if (affinity !== undefined) {
|
|
116
|
+
// Rebaseable when every supplied call id resolved to a live pending call
|
|
117
|
+
// (so none is owed to another account) — or when none resolved and the
|
|
118
|
+
// whole history is present. In both cases the conversation is complete and
|
|
119
|
+
// may travel as history to a fresh Run.
|
|
120
|
+
return { ...affinity, rebaseable: anyResolved ? allResolved : true };
|
|
121
|
+
}
|
|
122
|
+
return undefined;
|
|
110
123
|
}
|
|
111
124
|
|
|
112
125
|
/** Looks up one pending call across every provider that records continuations. */
|
|
@@ -137,6 +150,8 @@ export interface PreparedProxyRequest {
|
|
|
137
150
|
* second account and pool slot.
|
|
138
151
|
*/
|
|
139
152
|
readonly continuationAffinity?: RouteContinuationAffinity;
|
|
153
|
+
/** Single "who is speaking" identity, derived at auth. */
|
|
154
|
+
readonly caller: CallerIdentity;
|
|
140
155
|
readonly estimatedInputTokens: number;
|
|
141
156
|
readonly estimatedOutputTokens: number;
|
|
142
157
|
readonly deadlineMs: number;
|
|
@@ -407,6 +422,7 @@ export class ProxyRequestPreparer {
|
|
|
407
422
|
degradedCapabilities: degraded,
|
|
408
423
|
plan,
|
|
409
424
|
...(continuationAffinity === undefined ? {} : { continuationAffinity }),
|
|
425
|
+
caller: authorization.caller,
|
|
410
426
|
estimatedInputTokens,
|
|
411
427
|
estimatedOutputTokens,
|
|
412
428
|
deadlineMs: input.deadlineMs,
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tenant system-prompt injection at the canonical level.
|
|
3
|
+
*
|
|
4
|
+
* Runs inside `applyTenantPreferences`, after reasoning/thinking shaping and
|
|
5
|
+
* before RTK/PonyTail, so the injected text composes with (and is then
|
|
6
|
+
* appended-to by) the compression directive. Wire-agnostic: one transform
|
|
7
|
+
* covers messages/chat/responses/gemini/codex/kiro; provider-native
|
|
8
|
+
* injection (Claude identity/billing prepend, buddy fixed system turn,
|
|
9
|
+
* antigravity identity) happens later, in the adapters, and is untouched.
|
|
10
|
+
*
|
|
11
|
+
* Resolution is most-specific-wins: model > provider > global, where a
|
|
12
|
+
* provider either follows the tenant's global prompt or carries a custom one.
|
|
13
|
+
* Failure is non-fatal — an unusable configuration must not fail a
|
|
14
|
+
* dispatchable request.
|
|
15
|
+
*/
|
|
16
|
+
import type { CanonicalRequest } from "../../protocol/canonical-model";
|
|
17
|
+
import type { ServiceKind } from "../../protocol/canonical-model";
|
|
18
|
+
import type {
|
|
19
|
+
PromptSetting,
|
|
20
|
+
TenantSystemPrompts,
|
|
21
|
+
} from "../../console/system-prompt/contracts";
|
|
22
|
+
import {
|
|
23
|
+
systemPromptModelKey,
|
|
24
|
+
} from "../../console/system-prompt/contracts";
|
|
25
|
+
|
|
26
|
+
/** The route facts the resolution needs. */
|
|
27
|
+
export interface SystemPromptRoute {
|
|
28
|
+
readonly providerId: string;
|
|
29
|
+
readonly modelId: string;
|
|
30
|
+
readonly serviceKind: ServiceKind;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Resolves the effective prompt for a route: the most-specific enabled scope
|
|
35
|
+
* wins. A provider scope honours its `followGlobal` toggle; model scope is
|
|
36
|
+
* always explicit. Returns `undefined` when nothing applies.
|
|
37
|
+
*/
|
|
38
|
+
export function resolveTenantSystemPrompt(
|
|
39
|
+
prompts: TenantSystemPrompts | undefined,
|
|
40
|
+
route: Pick<SystemPromptRoute, "providerId" | "modelId">,
|
|
41
|
+
): PromptSetting | undefined {
|
|
42
|
+
if (prompts === undefined) return undefined;
|
|
43
|
+
const model = prompts.models[systemPromptModelKey(route.providerId, route.modelId)];
|
|
44
|
+
if (model !== undefined && model.enabled) return model;
|
|
45
|
+
const provider = prompts.providers[route.providerId];
|
|
46
|
+
if (provider !== undefined) {
|
|
47
|
+
if (provider.followGlobal) {
|
|
48
|
+
return prompts.global !== null && prompts.global.enabled ? prompts.global : undefined;
|
|
49
|
+
}
|
|
50
|
+
if (provider.setting !== undefined && provider.setting.enabled) return provider.setting;
|
|
51
|
+
}
|
|
52
|
+
return prompts.global !== null && prompts.global.enabled ? prompts.global : undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Marker text guard so retries never double-inject. */
|
|
56
|
+
function carriesText(system: readonly { kind: string }[] | undefined, text: string): boolean {
|
|
57
|
+
return (system ?? []).some((part) => part.kind === "text" && (part as { text?: string }).text === text);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Applies the resolved tenant prompt to the canonical request. Appends after
|
|
62
|
+
* existing system content (or replaces the caller's system content for
|
|
63
|
+
* `override` — provider-native identity injection later re-adds the fixed
|
|
64
|
+
* provider prompt, so override is never a bare wire).
|
|
65
|
+
*/
|
|
66
|
+
export function applyInjectSystemPrompt(
|
|
67
|
+
request: CanonicalRequest,
|
|
68
|
+
prompts: TenantSystemPrompts | undefined,
|
|
69
|
+
route: SystemPromptRoute,
|
|
70
|
+
): CanonicalRequest {
|
|
71
|
+
// Non-chat native protocols have no system surface to inject into.
|
|
72
|
+
if (route.serviceKind !== "llm") return request;
|
|
73
|
+
const setting = resolveTenantSystemPrompt(prompts, route);
|
|
74
|
+
if (setting === undefined || setting.text.length === 0) return request;
|
|
75
|
+
|
|
76
|
+
const text = setting.text;
|
|
77
|
+
// `override` replaces only the CALLER's system content: the adapters later
|
|
78
|
+
// prepend their mandatory identity/billing blocks (claude, antigravity) or
|
|
79
|
+
// install their fixed system turn (buddy), so the wire always keeps the
|
|
80
|
+
// provider-native prompt. For wrapped-injection providers the override
|
|
81
|
+
// cannot survive the replace at all — the identity must stay the opener —
|
|
82
|
+
// so it degrades to append there.
|
|
83
|
+
const existing = request.system ?? [];
|
|
84
|
+
if (setting.mode === "override" && existing.length > 0) {
|
|
85
|
+
return { ...request, system: [{ kind: "text", text }] };
|
|
86
|
+
}
|
|
87
|
+
if (carriesText(existing, text)) return request;
|
|
88
|
+
return { ...request, system: [...existing, { kind: "text", text }] };
|
|
89
|
+
}
|
|
@@ -12,9 +12,42 @@ import type { CanonicalRequest } from "../../protocol/canonical-model";
|
|
|
12
12
|
import type { PreparedProxyRequest } from "./preparer";
|
|
13
13
|
import { normalizeThinkingConfig } from "../../protocol/translation/thinking";
|
|
14
14
|
import { compressRequest } from "./rtk/compress-request";
|
|
15
|
+
import { applyInjectSystemPrompt } from "./system-prompt-injection";
|
|
15
16
|
import { preferencesReaderFor } from "../../persistence/tenant-preferences";
|
|
17
|
+
import {
|
|
18
|
+
readTenantSystemPrompts,
|
|
19
|
+
type StoredTenantSystemPrompts,
|
|
20
|
+
} from "../../persistence/system-prompt-store";
|
|
21
|
+
import { currentSettingsRevision } from "../../persistence/settings-revision";
|
|
22
|
+
import { TtlCache } from "../../persistence/ttl-cache";
|
|
16
23
|
import { resolveUpstreamTimeoutMs } from "../../config/timeouts";
|
|
17
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Revision-keyed cached read of the tenant's system-prompt configuration, the
|
|
27
|
+
* same convergence mechanism as the runtime-preferences reader: a mutation
|
|
28
|
+
* bumps the settings revision, which changes every cache key, so writes are
|
|
29
|
+
* visible to the dispatch path immediately without a per-request round trip.
|
|
30
|
+
*/
|
|
31
|
+
const systemPromptCache = new TtlCache<StoredTenantSystemPrompts>({
|
|
32
|
+
ttlMs: 5_000,
|
|
33
|
+
maxEntries: 128,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
async function readTenantSystemPromptsCached(
|
|
37
|
+
db: CartethyiaDatabase,
|
|
38
|
+
tenantId: string,
|
|
39
|
+
): Promise<StoredTenantSystemPrompts> {
|
|
40
|
+
const cacheKey = `${tenantId}:${currentSettingsRevision()}`;
|
|
41
|
+
const cached = systemPromptCache.get(cacheKey);
|
|
42
|
+
if (cached !== undefined) return cached;
|
|
43
|
+
const prompts = await readTenantSystemPrompts(db, tenantId);
|
|
44
|
+
systemPromptCache.set(cacheKey, prompts);
|
|
45
|
+
return prompts;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Cached read for the dispatch path (same revision-keyed cache). */
|
|
49
|
+
export { readTenantSystemPromptsCached as readTenantSystemPromptsForDispatch };
|
|
50
|
+
|
|
18
51
|
/**
|
|
19
52
|
* Re-arms the request deadline from the tenant's timeout override.
|
|
20
53
|
*
|
|
@@ -91,6 +124,16 @@ export async function applyTenantPreferences(
|
|
|
91
124
|
const next = normalizeThinkingConfig(canonicalRequest);
|
|
92
125
|
if (next !== canonicalRequest) canonicalRequest = next;
|
|
93
126
|
}
|
|
127
|
+
// Tenant system-prompt injection runs before compression: the PonyTail
|
|
128
|
+
// directive must stay the tail of the system content, and the injected
|
|
129
|
+
// prompt is itself stable per scope, so the prefix-cache invariant holds.
|
|
130
|
+
const prompts = await readTenantSystemPromptsCached(db, tenantId);
|
|
131
|
+
const candidate = prepared.candidate;
|
|
132
|
+
canonicalRequest = applyInjectSystemPrompt(canonicalRequest, prompts, {
|
|
133
|
+
providerId: candidate.provider_id,
|
|
134
|
+
modelId: candidate.model_id,
|
|
135
|
+
serviceKind: candidate.service_kind ?? "llm",
|
|
136
|
+
});
|
|
94
137
|
if (prefs) {
|
|
95
138
|
// When the routed model is Responses (codex/openai/muse-spark) and the
|
|
96
139
|
// client hit a Chat surface, force the configured summary mode so the
|
|
@@ -17,15 +17,25 @@ import type {
|
|
|
17
17
|
/**
|
|
18
18
|
* The admission bucket one candidate draws from.
|
|
19
19
|
*
|
|
20
|
+
* Bucket identity is `provider:model[:account][:ownerTenant]`:
|
|
21
|
+
* - A candidate with no account (credential-less provider) buckets per
|
|
22
|
+
* provider:model.
|
|
23
|
+
* - A **shared** account (`owner_tenant_id` null) is one bucket across all
|
|
24
|
+
* tenants — the account is pool-wide, so its concurrency ceiling is global.
|
|
25
|
+
* - A **tenant-owned** account is scoped to its owner tenant, so two tenants
|
|
26
|
+
* cannot drain each other's ceiling on an account only one of them owns.
|
|
27
|
+
*
|
|
20
28
|
* Exported so the router can order candidates by the same counters admission
|
|
21
29
|
* enforces against: a second copy of this format would drift and quietly
|
|
22
30
|
* deprioritize the wrong accounts.
|
|
23
31
|
*/
|
|
24
32
|
export function admissionKey(candidate: RouteCandidate): string {
|
|
25
33
|
const base = `${candidate.provider_id}:${candidate.model_id}`;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
34
|
+
if (candidate.provider_account_id === undefined) return base;
|
|
35
|
+
const account = `${base}:${candidate.provider_account_id}`;
|
|
36
|
+
return candidate.owner_tenant_id === undefined || candidate.owner_tenant_id === null
|
|
37
|
+
? account
|
|
38
|
+
: `${account}:${candidate.owner_tenant_id}`;
|
|
29
39
|
}
|
|
30
40
|
|
|
31
41
|
/**
|
|
@@ -35,7 +35,19 @@ import { providerHasAdapterUserAgent, providerUsesBespokeWire } from "../../prov
|
|
|
35
35
|
|
|
36
36
|
const CLAUDE_MODEL_FAMILIES = new Set(["opus", "sonnet", "haiku", "fable", "mythos"]);
|
|
37
37
|
|
|
38
|
+
/** Codex's built-in (non-routeable) model spellings worth matching. */
|
|
39
|
+
const CODEX_REVIEW_MODELS = new Set(["codex-auto-review"]);
|
|
40
|
+
|
|
38
41
|
export function cliMappingSourceKeys(toolId: string, sourceModel: string): readonly string[] {
|
|
42
|
+
if (toolId === "codex") {
|
|
43
|
+
const normalized = sourceModel.trim().toLowerCase();
|
|
44
|
+
if (CODEX_REVIEW_MODELS.has(normalized)) {
|
|
45
|
+
// Guardian's auto-review model is one fixed spelling; surface the full
|
|
46
|
+
// spelling plus the bare family so a slot mapped to either one hits.
|
|
47
|
+
return [...new Set([sourceModel, "codex-auto-review"])];
|
|
48
|
+
}
|
|
49
|
+
return [sourceModel];
|
|
50
|
+
}
|
|
39
51
|
if (toolId !== "claude") return [sourceModel];
|
|
40
52
|
const normalized = sourceModel.trim().toLowerCase();
|
|
41
53
|
const family =
|
|
@@ -199,23 +211,33 @@ export function capabilityProfileCacheSize(): number {
|
|
|
199
211
|
return capabilityProfileCache.size;
|
|
200
212
|
}
|
|
201
213
|
|
|
202
|
-
/** Resolves route identity
|
|
214
|
+
/** Resolves route identity: bundled adapters without a native User-Agent builder, plus custom providers. */
|
|
203
215
|
export function resolveRouteUserAgent(
|
|
204
216
|
providerId: string,
|
|
205
217
|
tenantUserAgent: string | undefined,
|
|
206
218
|
globalUserAgent: string | undefined,
|
|
207
219
|
): string | undefined {
|
|
208
|
-
if (
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
220
|
+
if (isBundledProviderId(providerId)) {
|
|
221
|
+
// A bundled adapter that frames its own identity headers never consumes a
|
|
222
|
+
// route User-Agent (Command Code, Qoder, buddy, ...), so it never gets one
|
|
223
|
+
// — the control is hidden for it too.
|
|
224
|
+
if (providerHasAdapterUserAgent(providerId)) return undefined;
|
|
225
|
+
// Bundled API-key providers: an operator who set no identity of their own
|
|
226
|
+
// gets the Claude Code CLI identity, resolved live so the stamped version
|
|
227
|
+
// tracks discovery. This used to be a frozen `codex_cli_rs` literal, which
|
|
228
|
+
// named a different vendor's client on requests that mostly reached neither.
|
|
229
|
+
const selected = resolveTenantOverride(
|
|
230
|
+
tenantUserAgent,
|
|
231
|
+
globalUserAgent,
|
|
232
|
+
buildClaudeCliUserAgent(),
|
|
233
|
+
);
|
|
234
|
+
return selected.trim() === "" ? undefined : selected;
|
|
235
|
+
}
|
|
236
|
+
// Custom (BYOK) providers: only an explicit operator choice rides on the
|
|
237
|
+
// route; the adapter owns the default otherwise (see `registerByokProviders`).
|
|
238
|
+
// An explicit empty choice ("None") is preserved so the adapter can honor it.
|
|
239
|
+
const selected = resolveTenantOverride(tenantUserAgent, globalUserAgent, undefined);
|
|
240
|
+
return selected === undefined ? undefined : selected.trim();
|
|
219
241
|
}
|
|
220
242
|
|
|
221
243
|
/** One persisted model row ready for candidate construction. */
|
|
@@ -648,6 +670,8 @@ class RouteCatalogRepository {
|
|
|
648
670
|
...(model.outputLimit == null ? {} : { output_limit: model.outputLimit }),
|
|
649
671
|
capability_profile: capabilityProfile,
|
|
650
672
|
tenant_id: tId,
|
|
673
|
+
// Public credential-less candidates have no account to own them.
|
|
674
|
+
owner_tenant_id: null,
|
|
651
675
|
requires_account: false as const,
|
|
652
676
|
network_pool_ids: tenantPools.ids,
|
|
653
677
|
network_pool_required: true,
|
|
@@ -674,6 +698,8 @@ class RouteCatalogRepository {
|
|
|
674
698
|
...(model.outputLimit == null ? {} : { output_limit: model.outputLimit }),
|
|
675
699
|
capability_profile: capabilityProfile,
|
|
676
700
|
tenant_id: providerTenantId,
|
|
701
|
+
// Public credential-less candidates have no account to own them.
|
|
702
|
+
owner_tenant_id: null,
|
|
677
703
|
...(providerRequiresAccount
|
|
678
704
|
? { health_status: "disabled" as const }
|
|
679
705
|
: { requires_account: false as const }),
|
|
@@ -716,6 +742,9 @@ class RouteCatalogRepository {
|
|
|
716
742
|
...(routeUserAgent === undefined ? {} : { user_agent: routeUserAgent }),
|
|
717
743
|
tenant_id: rowTenantId,
|
|
718
744
|
provider_account_id: account.id,
|
|
745
|
+
// The account's owning tenant (null = shared pool-wide) — the bucket
|
|
746
|
+
// key for admission, distinct from the requesting tenant.
|
|
747
|
+
owner_tenant_id: account.tenantId ?? null,
|
|
719
748
|
// The plan gate is per (account, model): the model may be refused on
|
|
720
749
|
// a free plan and the account may be on one. Both ride here because
|
|
721
750
|
// `plan()` sees the candidate and nothing else.
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
// Routing contracts and snapshots.
|
|
2
2
|
import { GatewayError } from "../../protocol/gateway-error";
|
|
3
3
|
import { type ServiceKind, type WireFamily } from "../../protocol/canonical-model";
|
|
4
|
+
import type { CallerIdentity } from "../../security/access-control";
|
|
5
|
+
|
|
6
|
+
export type { CallerIdentity };
|
|
4
7
|
|
|
5
8
|
|
|
6
9
|
export type RoutingRevision = number;
|
|
@@ -57,6 +60,14 @@ export interface RouteCandidate {
|
|
|
57
60
|
readonly network_pool_provider_limit?: number;
|
|
58
61
|
/** `null`/`undefined` = globally routable; a tenant id = routable only for that tenant's requests (BYOK). */
|
|
59
62
|
readonly tenant_id?: string | null;
|
|
63
|
+
/**
|
|
64
|
+
* The tenant that owns this candidate's account (`provider_accounts.tenant_id`),
|
|
65
|
+
* or null for a shared pool-wide account. Distinct from `tenant_id` (the
|
|
66
|
+
* requesting tenant). Used to scope the admission bucket: a shared account
|
|
67
|
+
* is one bucket across tenants; a tenant-owned account is scoped to its
|
|
68
|
+
* owner so two tenants cannot drain each other's ceiling.
|
|
69
|
+
*/
|
|
70
|
+
readonly owner_tenant_id?: string | null;
|
|
60
71
|
/** `false` only for a genuinely credential-less provider (e.g. OpenCode
|
|
61
72
|
* Free) dispatched with zero `provider_accounts` rows — the handler skips
|
|
62
73
|
* `resolveCredentialForAccount` and dispatches with a `"none"` credential.
|
|
@@ -80,6 +91,11 @@ export interface RouteCandidate {
|
|
|
80
91
|
* "free": guessing free here would defeat the gate it exists to serve.
|
|
81
92
|
*/
|
|
82
93
|
readonly account_plan?: string | null;
|
|
94
|
+
/** Usage-floor ceiling from Routing Strategy; read live at plan time. */
|
|
95
|
+
readonly usage_floor?: number;
|
|
96
|
+
readonly usage_floor_unit?: "credit" | "percent";
|
|
97
|
+
/** Live quota windows (cache) the floor check evaluates. */
|
|
98
|
+
readonly quota_windows?: readonly unknown[];
|
|
83
99
|
}
|
|
84
100
|
|
|
85
101
|
/**
|
|
@@ -163,6 +179,16 @@ export interface PoolRoutingSetting {
|
|
|
163
179
|
export interface RouteContinuationAffinity {
|
|
164
180
|
readonly providerId: string;
|
|
165
181
|
readonly providerAccountId: string;
|
|
182
|
+
/**
|
|
183
|
+
* True when the request's tool-result ids are all already answered by the
|
|
184
|
+
* conversation history (a replay), so no call is still owed to the pinned
|
|
185
|
+
* account. In that case, if the pinned account is no longer routable for
|
|
186
|
+
* the requested model, the request may safely rebase to a fresh Run on an
|
|
187
|
+
* eligible account instead of being refused — the recorded exchange travels
|
|
188
|
+
* as history. When false, the pin is a hard constraint: a live pending call
|
|
189
|
+
* must reach the exact account that owns it.
|
|
190
|
+
*/
|
|
191
|
+
readonly rebaseable: boolean;
|
|
166
192
|
}
|
|
167
193
|
|
|
168
194
|
export interface RoutePlan {
|
|
@@ -171,6 +197,33 @@ export interface RoutePlan {
|
|
|
171
197
|
readonly requested_model: string;
|
|
172
198
|
readonly resolved_model: string;
|
|
173
199
|
readonly provider_id: string;
|
|
200
|
+
/**
|
|
201
|
+
* The system decision behind each candidate, keyed by the candidate's
|
|
202
|
+
* admission bucket. Written once at plan time; every later layer (dispatch,
|
|
203
|
+
* telemetry) reads this instead of re-deriving its own ordering.
|
|
204
|
+
*/
|
|
205
|
+
readonly decisions: ReadonlyMap<string, RoutingDecision>;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** How eligible a candidate is and why — written once, read by every layer. */
|
|
209
|
+
export const ROUTING_RANK = {
|
|
210
|
+
FRESH: 0,
|
|
211
|
+
SLOW: 1,
|
|
212
|
+
COOLING: 2,
|
|
213
|
+
SATURATED: 3,
|
|
214
|
+
} as const;
|
|
215
|
+
export type RoutingRank = (typeof ROUTING_RANK)[keyof typeof ROUTING_RANK];
|
|
216
|
+
|
|
217
|
+
export interface RoutingDecision {
|
|
218
|
+
readonly eligible: boolean;
|
|
219
|
+
readonly rank: RoutingRank;
|
|
220
|
+
readonly reason: string;
|
|
221
|
+
readonly health_status?: string;
|
|
222
|
+
readonly cooldown_until?: number;
|
|
223
|
+
readonly floor_blocked?: boolean;
|
|
224
|
+
readonly plan_gate_blocked?: boolean;
|
|
225
|
+
readonly capability_blocked?: boolean;
|
|
226
|
+
readonly saturated?: boolean;
|
|
174
227
|
}
|
|
175
228
|
|
|
176
229
|
import type { ComboStrategy, PoolRoutingStrategy, ProviderRoutingStrategy } from "../../persistence/schema";
|