@opengeni/core 3.0.1 → 3.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/access/index.d.ts +131 -7
- package/dist/access/service-initiator.d.ts +3 -0
- package/dist/application/archived-session-imports.d.ts +6 -0
- package/dist/application/composer-submit.d.ts +1 -1
- package/dist/application/connect-operation.d.ts +6 -0
- package/dist/application/external-workspace-members.d.ts +13 -1
- package/dist/application/modal-native-live-declaration-join.d.ts +37 -0
- package/dist/application/modal-native-original-configuration.d.ts +79 -0
- package/dist/application/modal-native-worker-host-transport.d.ts +297 -0
- package/dist/application/new-session-drafts.d.ts +29 -2
- package/dist/application/organization-privacy.d.ts +57 -0
- package/dist/application/prepared-mcp-connection.d.ts +31 -0
- package/dist/application/sandbox-recovery.d.ts +58 -0
- package/dist/application/session-commands.d.ts +3 -0
- package/dist/application/session-tenancy.d.ts +13 -4
- package/dist/billing/agent-run-admission.d.ts +21 -0
- package/dist/billing/limits.d.ts +27 -1
- package/dist/billing/model-call-admission.d.ts +34 -0
- package/dist/billing/model-usage-settlement.d.ts +112 -0
- package/dist/canonical-human-identities.js +1 -1
- package/dist/{chunk-VCZFFEPB.js → chunk-7T2SZ7BI.js} +16 -2
- package/dist/chunk-7T2SZ7BI.js.map +1 -0
- package/dist/{chunk-ZYCNLYGH.js → chunk-OMJFIIWK.js} +178 -12
- package/dist/chunk-OMJFIIWK.js.map +1 -0
- package/dist/{chunk-H7X52RI2.js → chunk-QI2M2B2K.js} +83 -8
- package/dist/chunk-QI2M2B2K.js.map +1 -0
- package/dist/{chunk-OF65T3PM.js → chunk-TTWTXQB2.js} +94 -6
- package/dist/chunk-TTWTXQB2.js.map +1 -0
- package/dist/codex-model-availability.d.ts +55 -0
- package/dist/default-session-model.d.ts +192 -0
- package/dist/dependencies.d.ts +41 -7
- package/dist/developer-setup-credential-ceiling.d.ts +10 -0
- package/dist/domain/agent-config-resolution.d.ts +49 -0
- package/dist/domain/automations.d.ts +8 -1
- package/dist/domain/capabilities.d.ts +112 -8
- package/dist/domain/connection-account-selection-error.d.ts +8 -0
- package/dist/domain/connector-tool-permissions.d.ts +62 -0
- package/dist/domain/direct-model-provider.d.ts +3 -0
- package/dist/domain/editable-artifacts/agent-application.d.ts +10 -1
- package/dist/domain/editable-artifacts/durable-export.d.ts +22 -1
- package/dist/domain/editable-artifacts/ports.d.ts +10 -0
- package/dist/domain/editable-artifacts/service.d.ts +3 -1
- package/dist/domain/editable-artifacts/types.d.ts +1 -1
- package/dist/domain/encryption-key.d.ts +12 -0
- package/dist/domain/insights-timing.d.ts +16 -0
- package/dist/domain/insights-usage.d.ts +27 -0
- package/dist/domain/insights.d.ts +21 -3
- package/dist/domain/knowledge-files.d.ts +1 -1
- package/dist/domain/knowledge-preparation.d.ts +10 -0
- package/dist/domain/knowledge-preparation.test.d.ts +1 -0
- package/dist/domain/knowledge-search.d.ts +15 -76
- package/dist/domain/mcp-account-bindings.d.ts +17 -0
- package/dist/domain/mcp-account-routes.d.ts +21 -0
- package/dist/domain/native-mcp-connection-admission.d.ts +3 -0
- package/dist/domain/organization-integration-catalog.d.ts +15 -0
- package/dist/domain/personal-connection-delegations.d.ts +73 -12
- package/dist/domain/pr-review.d.ts +121 -6
- package/dist/domain/realtime-voice-billing.d.ts +103 -0
- package/dist/domain/resources.d.ts +17 -1
- package/dist/domain/scheduled-task-access.d.ts +265 -0
- package/dist/domain/scheduled-task-account-audit.d.ts +27 -0
- package/dist/domain/scheduled-task-audience.d.ts +70 -0
- package/dist/domain/scheduled-tasks.d.ts +61 -18
- package/dist/domain/session-connection-accounts.d.ts +53 -0
- package/dist/domain/session-file-access.d.ts +4 -0
- package/dist/domain/session-native-connection-replacement.d.ts +14 -0
- package/dist/domain/session-start-timing.d.ts +7 -0
- package/dist/domain/session-tool-policy.d.ts +102 -4
- package/dist/domain/sessions.d.ts +179 -15
- package/dist/domain/site-audience.d.ts +52 -0
- package/dist/domain/skills.d.ts +29 -1
- package/dist/domain/slack-bot.d.ts +46 -1
- package/dist/domain/slack-publication-secret-safety.d.ts +1 -1
- package/dist/domain/vaults/index.d.ts +7 -0
- package/dist/domain/vaults/mapping.d.ts +25 -0
- package/dist/domain/vaults/provider-registry.d.ts +72 -0
- package/dist/domain/vaults/provider-sync.d.ts +176 -0
- package/dist/domain/vaults/provider-writes.d.ts +57 -0
- package/dist/domain/vaults/secret-resolver.d.ts +34 -0
- package/dist/domain/vaults/types.d.ts +24 -0
- package/dist/domain/vaults/vault-service.d.ts +35 -0
- package/dist/domain/voice-input-billing.d.ts +77 -0
- package/dist/domain/web-search-billing.d.ts +51 -0
- package/dist/domain/workspace-credits.d.ts +7 -0
- package/dist/editable-artifact-live/application.d.ts +2 -1
- package/dist/editable-artifact-live/server.d.ts +5 -1
- package/dist/editable-artifact-live/ticket.d.ts +2 -1
- package/dist/editable-artifact-live/types.d.ts +9 -0
- package/dist/editable-artifact-live.js +2 -2
- package/dist/editable-artifacts.js +10 -2
- package/dist/goal-admission.d.ts +627 -0
- package/dist/host-session-usage-limit.d.ts +15 -0
- package/dist/index.d.ts +43 -8
- package/dist/index.js +22682 -11743
- package/dist/index.js.map +1 -1
- package/dist/managed-session.d.ts +110 -7
- package/dist/model-call.d.ts +58 -0
- package/dist/model-catalog.d.ts +29 -3
- package/dist/model-provider-credentials.d.ts +43 -0
- package/dist/product-usage-metrics.d.ts +30 -0
- package/dist/rigs/index.d.ts +1 -13
- package/dist/sandbox/fleet.d.ts +58 -4
- package/dist/sandbox/routing.d.ts +42 -2
- package/dist/sandbox/runtime-settings.d.ts +27 -17
- package/dist/session-authorization.d.ts +10 -0
- package/dist/session-usage-limit-metrics.d.ts +60 -0
- package/dist/session-usage-limits.d.ts +89 -0
- package/dist/site-agent-clamp.d.ts +71 -0
- package/dist/site-session-origin.d.ts +39 -2
- package/dist/transcription.d.ts +120 -3
- package/dist/turn-surface.d.ts +20 -0
- package/dist/user-presence.d.ts +32 -0
- package/dist/workflow-wake-contract.d.ts +13 -1
- package/package.json +15 -22
- package/src/access/index.ts +961 -38
- package/src/access/service-initiator.ts +50 -0
- package/src/application/api-integration-servers.ts +9 -0
- package/src/application/archived-session-imports.ts +161 -0
- package/src/application/composer-submit.ts +3 -2
- package/src/application/connect-authority.ts +3 -3
- package/src/application/connect-operation.ts +37 -3
- package/src/application/external-continuation.ts +20 -3
- package/src/application/external-link-work-admission.ts +3 -0
- package/src/application/external-workspace-members.ts +51 -3
- package/src/application/modal-native-live-declaration-join.ts +196 -0
- package/src/application/modal-native-original-configuration.ts +261 -0
- package/src/application/modal-native-worker-host-transport.ts +263 -0
- package/src/application/new-session-drafts.ts +172 -55
- package/src/application/organization-privacy.ts +161 -0
- package/src/application/prepared-mcp-connection.ts +133 -0
- package/src/application/sandbox-recovery.ts +78 -0
- package/src/application/session-commands.ts +73 -1
- package/src/application/session-mcp-credential-rotation.ts +61 -18
- package/src/application/session-tenancy.ts +262 -44
- package/src/application/user-resource-grants.ts +1 -29
- package/src/billing/agent-run-admission.ts +117 -0
- package/src/billing/limits.ts +138 -14
- package/src/billing/model-call-admission.ts +118 -0
- package/src/billing/model-usage-settlement.ts +472 -0
- package/src/codex-model-availability.ts +264 -0
- package/src/default-session-model.ts +692 -0
- package/src/dependencies.ts +48 -6
- package/src/developer-setup-credential-ceiling.ts +60 -0
- package/src/domain/agent-config-resolution.ts +148 -0
- package/src/domain/automations.ts +76 -2
- package/src/domain/capabilities.ts +830 -181
- package/src/domain/connection-account-selection-error.ts +19 -0
- package/src/domain/connector-tool-permissions.ts +572 -0
- package/src/domain/direct-model-provider.ts +62 -0
- package/src/domain/editable-artifacts/agent-application.ts +44 -6
- package/src/domain/editable-artifacts/durable-export.ts +100 -5
- package/src/domain/editable-artifacts/in-memory.ts +6 -0
- package/src/domain/editable-artifacts/ports.ts +11 -0
- package/src/domain/editable-artifacts/service.ts +20 -0
- package/src/domain/editable-artifacts/types.ts +1 -1
- package/src/domain/encryption-key.ts +26 -0
- package/src/domain/file-owner.ts +16 -1
- package/src/domain/github-action-policies.ts +1 -1
- package/src/domain/github-skill-source.ts +3 -1
- package/src/domain/insights-timing.ts +95 -0
- package/src/domain/insights-usage.ts +67 -0
- package/src/domain/insights.ts +167 -43
- package/src/domain/knowledge-files.ts +10 -3
- package/src/domain/knowledge-messages.ts +1 -0
- package/src/domain/knowledge-preparation.test.ts +276 -0
- package/src/domain/knowledge-preparation.ts +147 -0
- package/src/domain/knowledge-search.test.ts +617 -5
- package/src/domain/knowledge-search.ts +240 -23
- package/src/domain/mcp-account-bindings.ts +169 -0
- package/src/domain/mcp-account-routes.ts +152 -0
- package/src/domain/native-mcp-connection-admission.ts +14 -0
- package/src/domain/organization-integration-catalog.ts +88 -0
- package/src/domain/personal-connection-delegations.ts +399 -257
- package/src/domain/pr-review.ts +24 -8
- package/src/domain/realtime-voice-billing.ts +425 -0
- package/src/domain/resources.ts +50 -4
- package/src/domain/scheduled-task-access.ts +1089 -0
- package/src/domain/scheduled-task-account-audit.ts +90 -0
- package/src/domain/scheduled-task-audience.ts +249 -0
- package/src/domain/scheduled-tasks.ts +1313 -374
- package/src/domain/session-connection-accounts.ts +239 -0
- package/src/domain/session-file-access.ts +47 -0
- package/src/domain/session-native-connection-replacement.ts +64 -0
- package/src/domain/session-start-timing.ts +60 -0
- package/src/domain/session-tool-policy.ts +489 -14
- package/src/domain/sessions.ts +2254 -717
- package/src/domain/site-audience.ts +130 -0
- package/src/domain/skills.ts +9 -0
- package/src/domain/slack-bot.ts +141 -4
- package/src/domain/slack-publication-secret-safety.ts +1 -1
- package/src/domain/vaults/index.ts +19 -0
- package/src/domain/vaults/mapping.ts +190 -0
- package/src/domain/vaults/provider-registry.ts +201 -0
- package/src/domain/vaults/provider-sync.ts +531 -0
- package/src/domain/vaults/provider-writes.ts +288 -0
- package/src/domain/vaults/secret-resolver.ts +464 -0
- package/src/domain/vaults/types.ts +26 -0
- package/src/domain/vaults/vault-service.ts +1316 -0
- package/src/domain/voice-input-billing.ts +330 -0
- package/src/domain/web-search-billing.ts +169 -0
- package/src/domain/workspace-credits.ts +13 -0
- package/src/editable-artifact-live/application.ts +8 -1
- package/src/editable-artifact-live/server.ts +154 -6
- package/src/editable-artifact-live/ticket.ts +86 -1
- package/src/editable-artifact-live/types.ts +10 -0
- package/src/goal-admission.ts +220 -0
- package/src/host-session-usage-limit.ts +25 -0
- package/src/index.ts +75 -11
- package/src/managed-session.ts +137 -6
- package/src/model-call.ts +79 -0
- package/src/model-catalog.ts +345 -91
- package/src/model-provider-credentials.ts +394 -0
- package/src/product-usage-metrics.ts +101 -0
- package/src/rigs/index.ts +23 -111
- package/src/sandbox/fleet.ts +253 -28
- package/src/sandbox/routing.ts +204 -43
- package/src/sandbox/runtime-settings.ts +159 -85
- package/src/session-authorization.ts +172 -23
- package/src/session-usage-limit-metrics.ts +95 -0
- package/src/session-usage-limits.ts +415 -0
- package/src/site-agent-clamp.ts +226 -0
- package/src/site-session-origin.ts +84 -5
- package/src/transcription.ts +123 -4
- package/src/turn-surface.ts +47 -0
- package/src/user-presence.ts +134 -0
- package/src/workflow-wake-contract.ts +14 -1
- package/dist/application/host-mcp-owner.d.ts +0 -6
- package/dist/application/host-mcp-resolvers.d.ts +0 -13
- package/dist/chunk-5WZYFL45.js +0 -266
- package/dist/chunk-5WZYFL45.js.map +0 -1
- package/dist/chunk-H7X52RI2.js.map +0 -1
- package/dist/chunk-OF65T3PM.js.map +0 -1
- package/dist/chunk-VCZFFEPB.js.map +0 -1
- package/dist/chunk-ZYCNLYGH.js.map +0 -1
- package/dist/domain/connection-authority.d.ts +0 -97
- package/dist/domain/connection-authority.js +0 -169
- package/dist/domain/connection-authority.js.map +0 -1
- package/dist/domain/environments.d.ts +0 -33
- package/dist/domain/host-mcp-authority-source-admission.d.ts +0 -10
- package/dist/domain/host-mcp-task-admission.d.ts +0 -18
- package/dist/domain/packs.d.ts +0 -59
- package/dist/domain/product-integration-pack.d.ts +0 -44
- package/dist/domain/product-integration-skill.gen.d.ts +0 -5
- package/dist/remote-mcp-credentials.d.ts +0 -23
- package/dist/remote-mcp-credentials.js +0 -11
- package/dist/remote-mcp-credentials.js.map +0 -1
- package/src/application/host-mcp-owner.ts +0 -41
- package/src/application/host-mcp-resolvers.ts +0 -84
- package/src/domain/connection-authority.ts +0 -329
- package/src/domain/environments.ts +0 -120
- package/src/domain/host-mcp-authority-source-admission.ts +0 -25
- package/src/domain/host-mcp-task-admission.ts +0 -121
- package/src/domain/packs.ts +0 -699
- package/src/domain/product-integration-pack.ts +0 -42
- package/src/domain/product-integration-skill.gen.ts +0 -52
- package/src/remote-mcp-credentials.ts +0 -361
package/src/billing/limits.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
configuredStaticUsageLimits,
|
|
3
|
+
canonicalizeConfiguredModelId,
|
|
3
4
|
resolveModelProviderForTurn,
|
|
4
5
|
type Settings,
|
|
5
6
|
} from "@opengeni/config";
|
|
@@ -11,13 +12,16 @@ import type {
|
|
|
11
12
|
TurnInitiatorContext,
|
|
12
13
|
} from "@opengeni/contracts";
|
|
13
14
|
import {
|
|
15
|
+
checkWorkspaceAllowance,
|
|
14
16
|
countActiveApiKeysForWorkspace,
|
|
15
17
|
countActiveOrganizationApiKeysForAccount,
|
|
16
18
|
countScheduledTasksForWorkspace,
|
|
17
19
|
countWorkspacesForAccount,
|
|
18
|
-
|
|
20
|
+
getSpendableCreditBalance,
|
|
19
21
|
isCodexBilledTurn,
|
|
22
|
+
readSubscriptionCoreRouteFunding,
|
|
20
23
|
recordUsageEvent,
|
|
24
|
+
SUBSCRIPTION_CORE_XAI_PROVIDER,
|
|
21
25
|
sumUsageQuantity,
|
|
22
26
|
} from "@opengeni/db";
|
|
23
27
|
import { HTTPException } from "hono/http-exception";
|
|
@@ -25,9 +29,32 @@ import type { ApiRouteDeps } from "../dependencies";
|
|
|
25
29
|
|
|
26
30
|
export type LimitDependencies = Pick<ApiRouteDeps, "db" | "settings">;
|
|
27
31
|
|
|
32
|
+
/** Deterministic/local embeddings never debit credits, even in paid mode. */
|
|
33
|
+
export function paidDocumentEmbedding(settings: Settings): boolean {
|
|
34
|
+
return (
|
|
35
|
+
settings.documentEmbeddingBillingMode === "credits" &&
|
|
36
|
+
settings.documentEmbeddingProvider === "openai"
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Round up each provider request so a positive input cannot become a free debit. */
|
|
41
|
+
export function documentEmbeddingCostMicros(settings: Settings, inputBytes: number): number {
|
|
42
|
+
if (!Number.isSafeInteger(inputBytes) || inputBytes < 0)
|
|
43
|
+
throw new Error("invalid embedding bytes");
|
|
44
|
+
const amount =
|
|
45
|
+
(BigInt(inputBytes) * BigInt(settings.documentEmbeddingRateMicrosPerMillionBytes) + 999_999n) /
|
|
46
|
+
1_000_000n;
|
|
47
|
+
if (amount > BigInt(Number.MAX_SAFE_INTEGER))
|
|
48
|
+
throw new Error("embedding cost exceeds safe integer");
|
|
49
|
+
return Number(amount);
|
|
50
|
+
}
|
|
51
|
+
|
|
28
52
|
export type LimitCheckInput = {
|
|
29
53
|
accountId: string;
|
|
30
54
|
workspaceId?: string;
|
|
55
|
+
/** Frozen causal human, never the API key or service admitting the work. */
|
|
56
|
+
initiatingHumanSubjectId?: string | null;
|
|
57
|
+
acceptedTurn?: { sessionId: string; turnId: string };
|
|
31
58
|
action: LimitAction;
|
|
32
59
|
quantity?: number;
|
|
33
60
|
// The turn's model id, when the action represents an agent turn. The model's
|
|
@@ -41,6 +68,12 @@ export function modelFundingForAdmission(
|
|
|
41
68
|
settings: Settings,
|
|
42
69
|
model: string | null | undefined,
|
|
43
70
|
codexBilled: boolean,
|
|
71
|
+
/**
|
|
72
|
+
* The shared core's live funding answer for a connected subscription that
|
|
73
|
+
* has cut over to the core (`coreRoutedSubscriptionFunding`); undefined
|
|
74
|
+
* keeps the legacy answer.
|
|
75
|
+
*/
|
|
76
|
+
coreRoutedFunding?: boolean,
|
|
44
77
|
): { fundedWithoutCredits: boolean; countsTowardTokenCap: boolean } {
|
|
45
78
|
const resolvedModel = model ? resolveModelProviderForTurn(settings, model)?.model : null;
|
|
46
79
|
const codexSubscriptionModel =
|
|
@@ -48,25 +81,81 @@ export function modelFundingForAdmission(
|
|
|
48
81
|
resolvedModel.credentialSource.provider === "codex";
|
|
49
82
|
return {
|
|
50
83
|
// A Codex namespace/definition never bypasses credits by itself: the live
|
|
51
|
-
// workspace credential predicate above remains authoritative.
|
|
52
|
-
// static overlay is likewise secret-free
|
|
53
|
-
// owns live account selection before any
|
|
84
|
+
// workspace credential predicate above remains authoritative. Claude's
|
|
85
|
+
// static overlay is likewise secret-free on its legacy route; its
|
|
86
|
+
// worker/provider admission owns live account selection before any
|
|
87
|
+
// upstream request can occur. On the core route (the only SuperGrok
|
|
88
|
+
// route) the core's funding answer is authoritative instead.
|
|
54
89
|
fundedWithoutCredits:
|
|
55
90
|
codexBilled ||
|
|
56
|
-
(
|
|
91
|
+
(coreRoutedFunding ??
|
|
92
|
+
(resolvedModel != null && !codexSubscriptionModel && resolvedModel.cost !== "credits")),
|
|
57
93
|
countsTowardTokenCap:
|
|
58
94
|
!codexBilled && resolvedModel != null && resolvedModel.billing.upstreamPayer === "deployment",
|
|
59
95
|
};
|
|
60
96
|
}
|
|
61
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Connected subscriptions whose legacy runtime was deleted after their
|
|
100
|
+
* cutover: only the shared core funds them, so any other route is unfunded.
|
|
101
|
+
*/
|
|
102
|
+
const CORE_ONLY_SUBSCRIPTION_PROVIDERS: ReadonlySet<string> = new Set([
|
|
103
|
+
SUBSCRIPTION_CORE_XAI_PROVIDER,
|
|
104
|
+
]);
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The shared core's funding answer for a non-Codex connected subscription
|
|
108
|
+
* whose provider has cut over (design 5.3, EP-N19): the provider-neutral form
|
|
109
|
+
* of `isCodexBilledTurn`, with explicit workspace, causal human, accepted
|
|
110
|
+
* work and model. Undefined on the provider's legacy route (one receipt
|
|
111
|
+
* read) and for every other model (no read). Codex keeps `isCodexBilledTurn`.
|
|
112
|
+
* A provider opts in at its own chat placement step and only while it is
|
|
113
|
+
* enabled on the deployment, as its catalog readiness does.
|
|
114
|
+
*/
|
|
115
|
+
const CORE_ROUTED_FUNDING: Partial<Record<string, (settings: Settings) => boolean>> = {
|
|
116
|
+
xai: (settings) => settings.supergrokSubscriptionEnabled,
|
|
117
|
+
claude: (settings) => settings.claudeSubscriptionEnabled,
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export async function coreRoutedSubscriptionFunding(
|
|
121
|
+
db: LimitDependencies["db"],
|
|
122
|
+
settings: Settings,
|
|
123
|
+
input: {
|
|
124
|
+
accountId: string;
|
|
125
|
+
workspaceId: string;
|
|
126
|
+
model: string | null | undefined;
|
|
127
|
+
subjectId: string | null;
|
|
128
|
+
acceptedTurn?: { sessionId: string; turnId: string };
|
|
129
|
+
},
|
|
130
|
+
): Promise<boolean | undefined> {
|
|
131
|
+
if (!input.model) return undefined;
|
|
132
|
+
const source = resolveModelProviderForTurn(settings, input.model)?.model.credentialSource;
|
|
133
|
+
if (source?.kind !== "connected_subscription") return undefined;
|
|
134
|
+
if (!CORE_ROUTED_FUNDING[source.provider]?.(settings)) return undefined;
|
|
135
|
+
const answer = await readSubscriptionCoreRouteFunding(db, {
|
|
136
|
+
provider: source.provider,
|
|
137
|
+
accountId: input.accountId,
|
|
138
|
+
workspaceId: input.workspaceId,
|
|
139
|
+
subjectId: input.subjectId,
|
|
140
|
+
productModelId: input.model,
|
|
141
|
+
...(input.acceptedTurn ? { acceptedTurn: input.acceptedTurn } : {}),
|
|
142
|
+
});
|
|
143
|
+
if (answer.route === "core") return answer.value;
|
|
144
|
+
return CORE_ONLY_SUBSCRIPTION_PROVIDERS.has(source.provider) ? false : undefined;
|
|
145
|
+
}
|
|
146
|
+
|
|
62
147
|
export async function requireLimit(deps: LimitDependencies, input: LimitCheckInput): Promise<void> {
|
|
63
148
|
const decision = await checkLimit(deps, input);
|
|
64
149
|
if (decision.allowed) {
|
|
65
150
|
return;
|
|
66
151
|
}
|
|
67
|
-
throw new HTTPException(
|
|
68
|
-
|
|
69
|
-
|
|
152
|
+
throw new HTTPException(
|
|
153
|
+
decision.code === "insufficient_credits" || decision.code === "allowance_exhausted" ? 402 : 429,
|
|
154
|
+
{
|
|
155
|
+
message: decision.message,
|
|
156
|
+
cause: decision,
|
|
157
|
+
},
|
|
158
|
+
);
|
|
70
159
|
}
|
|
71
160
|
|
|
72
161
|
export async function checkLimit(
|
|
@@ -82,17 +171,41 @@ export async function checkLimit(
|
|
|
82
171
|
settings: deps.settings,
|
|
83
172
|
workspaceId: input.workspaceId,
|
|
84
173
|
model: input.model,
|
|
174
|
+
// On the shared Codex core the causal human's own personal
|
|
175
|
+
// connection in their Personal workspace also funds the turn.
|
|
176
|
+
accountId: input.accountId,
|
|
177
|
+
subjectId: input.initiatingHumanSubjectId ?? null,
|
|
178
|
+
...(input.acceptedTurn ? { acceptedTurn: input.acceptedTurn } : {}),
|
|
85
179
|
})
|
|
86
180
|
: false;
|
|
181
|
+
const coreFunding = input.workspaceId
|
|
182
|
+
? await coreRoutedSubscriptionFunding(deps.db, deps.settings, {
|
|
183
|
+
accountId: input.accountId,
|
|
184
|
+
workspaceId: input.workspaceId,
|
|
185
|
+
model: input.model,
|
|
186
|
+
subjectId: input.initiatingHumanSubjectId ?? null,
|
|
187
|
+
...(input.acceptedTurn ? { acceptedTurn: input.acceptedTurn } : {}),
|
|
188
|
+
})
|
|
189
|
+
: undefined;
|
|
87
190
|
const { fundedWithoutCredits, countsTowardTokenCap } = modelFundingForAdmission(
|
|
88
191
|
deps.settings,
|
|
89
192
|
input.model,
|
|
90
193
|
codexBilled,
|
|
194
|
+
coreFunding,
|
|
91
195
|
);
|
|
92
196
|
const creditDecision = await checkCreditBalance(deps, input, fundedWithoutCredits);
|
|
93
197
|
if (!creditDecision.allowed) {
|
|
94
198
|
return creditDecision;
|
|
95
199
|
}
|
|
200
|
+
if (isCostlyAction(input.action) && input.workspaceId) {
|
|
201
|
+
const refusal = await checkWorkspaceAllowance(deps.db, {
|
|
202
|
+
accountId: input.accountId,
|
|
203
|
+
workspaceId: input.workspaceId,
|
|
204
|
+
subjectId: input.initiatingHumanSubjectId ?? null,
|
|
205
|
+
...(fundedWithoutCredits ? { fundedWithoutCredits: true } : {}),
|
|
206
|
+
});
|
|
207
|
+
if (refusal) return { allowed: false, ...refusal };
|
|
208
|
+
}
|
|
96
209
|
if (deps.settings.usageLimitsMode !== "static" && deps.settings.usageLimitsMode !== "managed") {
|
|
97
210
|
return { allowed: true };
|
|
98
211
|
}
|
|
@@ -108,16 +221,26 @@ async function checkCreditBalance(
|
|
|
108
221
|
externallyBilled: boolean,
|
|
109
222
|
): Promise<LimitDecision> {
|
|
110
223
|
if (externallyBilled) {
|
|
111
|
-
return { allowed: true }; // paid outside
|
|
224
|
+
return { allowed: true }; // paid outside Opengeni — zero Opengeni credits
|
|
112
225
|
}
|
|
113
226
|
if (!usesCreditLimits(deps) || !isCostlyAction(input.action)) {
|
|
114
227
|
return { allowed: true };
|
|
115
228
|
}
|
|
116
|
-
const balance = await
|
|
229
|
+
const balance = await getSpendableCreditBalance(
|
|
230
|
+
deps.db,
|
|
231
|
+
input.accountId,
|
|
232
|
+
input.model ? canonicalizeConfiguredModelId(deps.settings, input.model) : undefined,
|
|
233
|
+
);
|
|
117
234
|
if (balance.balanceMicros > 0) {
|
|
118
235
|
return { allowed: true };
|
|
119
236
|
}
|
|
120
|
-
return {
|
|
237
|
+
return {
|
|
238
|
+
allowed: false,
|
|
239
|
+
code: "insufficient_credits",
|
|
240
|
+
message: input.model
|
|
241
|
+
? "No credits available for this model. Choose a model covered by your promotional credits or add credits."
|
|
242
|
+
: "No general credits available. Promotional credits cover eligible models only.",
|
|
243
|
+
};
|
|
121
244
|
}
|
|
122
245
|
|
|
123
246
|
async function checkStaticCaps(
|
|
@@ -296,9 +419,10 @@ function usesCreditLimits(deps: LimitDependencies): boolean {
|
|
|
296
419
|
}
|
|
297
420
|
|
|
298
421
|
function isCostlyAction(action: LimitAction): boolean {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
422
|
+
// Document admission retains the monthly chunk quota above, but parsing and
|
|
423
|
+
// source retention do not consume embedding credits. The worker admits paid
|
|
424
|
+
// embedding separately and settles its actual provider input after use.
|
|
425
|
+
return action === "agent_run:create" || action === "tokens:consume";
|
|
302
426
|
}
|
|
303
427
|
|
|
304
428
|
function blocked(code: string, message: string): LimitDecision {
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { configuredStaticUsageLimits, type Settings } from "@opengeni/config";
|
|
2
|
+
import type { EntitlementsPort } from "@opengeni/contracts";
|
|
3
|
+
import {
|
|
4
|
+
checkWorkspaceAllowance,
|
|
5
|
+
getSpendableCreditBalance,
|
|
6
|
+
sumUsageQuantity,
|
|
7
|
+
type Database,
|
|
8
|
+
} from "@opengeni/db";
|
|
9
|
+
|
|
10
|
+
export type ModelCallAdmissionDenial =
|
|
11
|
+
| "insufficient_credits"
|
|
12
|
+
| "allowance_exhausted"
|
|
13
|
+
| "monthly_model_cost_limit"
|
|
14
|
+
| "monthly_token_limit";
|
|
15
|
+
|
|
16
|
+
export type ModelCallAdmission =
|
|
17
|
+
| { allowed: true; creditPolicyRevision: number | undefined }
|
|
18
|
+
| { allowed: false; denial: ModelCallAdmissionDenial; message: string };
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Admission for one stateless model call, with the same funding rules as an
|
|
22
|
+
* agent turn between model calls: an Opengeni-credit model needs a positive
|
|
23
|
+
* spendable balance, the workspace allowance must not be exhausted, and the
|
|
24
|
+
* static monthly caps apply. Admission is a read, never a reservation or an
|
|
25
|
+
* estimate of the call's cost; settlement charges actual usage afterwards.
|
|
26
|
+
* Externally funded calls (subscriptions, workspace or organization keys)
|
|
27
|
+
* skip the credit gate and the token cap.
|
|
28
|
+
*/
|
|
29
|
+
export async function admitModelCall(
|
|
30
|
+
services: { db: Database; settings: Settings; entitlements?: EntitlementsPort | null },
|
|
31
|
+
input: {
|
|
32
|
+
accountId: string;
|
|
33
|
+
workspaceId: string;
|
|
34
|
+
/** Canonical product model id; scopes model-specific promotional credits. */
|
|
35
|
+
model: string;
|
|
36
|
+
chargesOpenGeniCredits: boolean;
|
|
37
|
+
countsTowardTokenCap: boolean;
|
|
38
|
+
subjectId: string | null;
|
|
39
|
+
},
|
|
40
|
+
): Promise<ModelCallAdmission> {
|
|
41
|
+
const { settings, db } = services;
|
|
42
|
+
const metered = settings.billingMode === "stripe" || settings.usageLimitsMode === "managed";
|
|
43
|
+
let creditPolicyRevision: number | undefined;
|
|
44
|
+
if (input.chargesOpenGeniCredits && metered) {
|
|
45
|
+
if (services.entitlements) {
|
|
46
|
+
const decision = await services.entitlements.admitRun({
|
|
47
|
+
accountId: input.accountId,
|
|
48
|
+
workspaceId: input.workspaceId,
|
|
49
|
+
action: "model_call:create",
|
|
50
|
+
quantity: 1,
|
|
51
|
+
});
|
|
52
|
+
if (!decision.allowed) {
|
|
53
|
+
return {
|
|
54
|
+
allowed: false,
|
|
55
|
+
denial: "insufficient_credits",
|
|
56
|
+
message: decision.reason || "Insufficient Opengeni credits.",
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
const balance = await getSpendableCreditBalance(db, input.accountId, input.model);
|
|
61
|
+
creditPolicyRevision = balance.creditPolicyRevision;
|
|
62
|
+
if (balance.balanceMicros <= 0) {
|
|
63
|
+
return {
|
|
64
|
+
allowed: false,
|
|
65
|
+
denial: "insufficient_credits",
|
|
66
|
+
message: "Insufficient Opengeni credits.",
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const refusal = await checkWorkspaceAllowance(db, {
|
|
72
|
+
accountId: input.accountId,
|
|
73
|
+
workspaceId: input.workspaceId,
|
|
74
|
+
subjectId: input.subjectId,
|
|
75
|
+
...(input.chargesOpenGeniCredits ? {} : { fundedWithoutCredits: true }),
|
|
76
|
+
});
|
|
77
|
+
if (refusal) {
|
|
78
|
+
return { allowed: false, denial: "allowance_exhausted", message: refusal.message };
|
|
79
|
+
}
|
|
80
|
+
if (settings.usageLimitsMode === "static" || settings.usageLimitsMode === "managed") {
|
|
81
|
+
const limits = configuredStaticUsageLimits(settings);
|
|
82
|
+
const since = startOfUtcMonth();
|
|
83
|
+
if (input.chargesOpenGeniCredits && limits.maxMonthlyCostMicrosPerAccount) {
|
|
84
|
+
const used = await sumUsageQuantity(db, {
|
|
85
|
+
accountId: input.accountId,
|
|
86
|
+
eventType: "model.cost",
|
|
87
|
+
since,
|
|
88
|
+
});
|
|
89
|
+
if (used >= limits.maxMonthlyCostMicrosPerAccount) {
|
|
90
|
+
return {
|
|
91
|
+
allowed: false,
|
|
92
|
+
denial: "monthly_model_cost_limit",
|
|
93
|
+
message: "The monthly model cost limit has been reached.",
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (input.countsTowardTokenCap && limits.maxMonthlyTokensPerWorkspace) {
|
|
98
|
+
const used = await sumUsageQuantity(db, {
|
|
99
|
+
workspaceId: input.workspaceId,
|
|
100
|
+
eventType: "model.tokens",
|
|
101
|
+
since,
|
|
102
|
+
});
|
|
103
|
+
if (used >= limits.maxMonthlyTokensPerWorkspace) {
|
|
104
|
+
return {
|
|
105
|
+
allowed: false,
|
|
106
|
+
denial: "monthly_token_limit",
|
|
107
|
+
message: "The monthly token limit has been reached.",
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return { allowed: true, creditPolicyRevision };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function startOfUtcMonth(): Date {
|
|
116
|
+
const now = new Date();
|
|
117
|
+
return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
|
|
118
|
+
}
|