@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.
Files changed (257) hide show
  1. package/README.md +4 -4
  2. package/dist/access/index.d.ts +131 -7
  3. package/dist/access/service-initiator.d.ts +3 -0
  4. package/dist/application/archived-session-imports.d.ts +6 -0
  5. package/dist/application/composer-submit.d.ts +1 -1
  6. package/dist/application/connect-operation.d.ts +6 -0
  7. package/dist/application/external-workspace-members.d.ts +13 -1
  8. package/dist/application/modal-native-live-declaration-join.d.ts +37 -0
  9. package/dist/application/modal-native-original-configuration.d.ts +79 -0
  10. package/dist/application/modal-native-worker-host-transport.d.ts +297 -0
  11. package/dist/application/new-session-drafts.d.ts +29 -2
  12. package/dist/application/organization-privacy.d.ts +57 -0
  13. package/dist/application/prepared-mcp-connection.d.ts +31 -0
  14. package/dist/application/sandbox-recovery.d.ts +58 -0
  15. package/dist/application/session-commands.d.ts +3 -0
  16. package/dist/application/session-tenancy.d.ts +13 -4
  17. package/dist/billing/agent-run-admission.d.ts +21 -0
  18. package/dist/billing/limits.d.ts +27 -1
  19. package/dist/billing/model-call-admission.d.ts +34 -0
  20. package/dist/billing/model-usage-settlement.d.ts +112 -0
  21. package/dist/canonical-human-identities.js +1 -1
  22. package/dist/{chunk-VCZFFEPB.js → chunk-7T2SZ7BI.js} +16 -2
  23. package/dist/chunk-7T2SZ7BI.js.map +1 -0
  24. package/dist/{chunk-ZYCNLYGH.js → chunk-OMJFIIWK.js} +178 -12
  25. package/dist/chunk-OMJFIIWK.js.map +1 -0
  26. package/dist/{chunk-H7X52RI2.js → chunk-QI2M2B2K.js} +83 -8
  27. package/dist/chunk-QI2M2B2K.js.map +1 -0
  28. package/dist/{chunk-OF65T3PM.js → chunk-TTWTXQB2.js} +94 -6
  29. package/dist/chunk-TTWTXQB2.js.map +1 -0
  30. package/dist/codex-model-availability.d.ts +55 -0
  31. package/dist/default-session-model.d.ts +192 -0
  32. package/dist/dependencies.d.ts +41 -7
  33. package/dist/developer-setup-credential-ceiling.d.ts +10 -0
  34. package/dist/domain/agent-config-resolution.d.ts +49 -0
  35. package/dist/domain/automations.d.ts +8 -1
  36. package/dist/domain/capabilities.d.ts +112 -8
  37. package/dist/domain/connection-account-selection-error.d.ts +8 -0
  38. package/dist/domain/connector-tool-permissions.d.ts +62 -0
  39. package/dist/domain/direct-model-provider.d.ts +3 -0
  40. package/dist/domain/editable-artifacts/agent-application.d.ts +10 -1
  41. package/dist/domain/editable-artifacts/durable-export.d.ts +22 -1
  42. package/dist/domain/editable-artifacts/ports.d.ts +10 -0
  43. package/dist/domain/editable-artifacts/service.d.ts +3 -1
  44. package/dist/domain/editable-artifacts/types.d.ts +1 -1
  45. package/dist/domain/encryption-key.d.ts +12 -0
  46. package/dist/domain/insights-timing.d.ts +16 -0
  47. package/dist/domain/insights-usage.d.ts +27 -0
  48. package/dist/domain/insights.d.ts +21 -3
  49. package/dist/domain/knowledge-files.d.ts +1 -1
  50. package/dist/domain/knowledge-preparation.d.ts +10 -0
  51. package/dist/domain/knowledge-preparation.test.d.ts +1 -0
  52. package/dist/domain/knowledge-search.d.ts +15 -76
  53. package/dist/domain/mcp-account-bindings.d.ts +17 -0
  54. package/dist/domain/mcp-account-routes.d.ts +21 -0
  55. package/dist/domain/native-mcp-connection-admission.d.ts +3 -0
  56. package/dist/domain/organization-integration-catalog.d.ts +15 -0
  57. package/dist/domain/personal-connection-delegations.d.ts +73 -12
  58. package/dist/domain/pr-review.d.ts +121 -6
  59. package/dist/domain/realtime-voice-billing.d.ts +103 -0
  60. package/dist/domain/resources.d.ts +17 -1
  61. package/dist/domain/scheduled-task-access.d.ts +265 -0
  62. package/dist/domain/scheduled-task-account-audit.d.ts +27 -0
  63. package/dist/domain/scheduled-task-audience.d.ts +70 -0
  64. package/dist/domain/scheduled-tasks.d.ts +61 -18
  65. package/dist/domain/session-connection-accounts.d.ts +53 -0
  66. package/dist/domain/session-file-access.d.ts +4 -0
  67. package/dist/domain/session-native-connection-replacement.d.ts +14 -0
  68. package/dist/domain/session-start-timing.d.ts +7 -0
  69. package/dist/domain/session-tool-policy.d.ts +102 -4
  70. package/dist/domain/sessions.d.ts +179 -15
  71. package/dist/domain/site-audience.d.ts +52 -0
  72. package/dist/domain/skills.d.ts +29 -1
  73. package/dist/domain/slack-bot.d.ts +46 -1
  74. package/dist/domain/slack-publication-secret-safety.d.ts +1 -1
  75. package/dist/domain/vaults/index.d.ts +7 -0
  76. package/dist/domain/vaults/mapping.d.ts +25 -0
  77. package/dist/domain/vaults/provider-registry.d.ts +72 -0
  78. package/dist/domain/vaults/provider-sync.d.ts +176 -0
  79. package/dist/domain/vaults/provider-writes.d.ts +57 -0
  80. package/dist/domain/vaults/secret-resolver.d.ts +34 -0
  81. package/dist/domain/vaults/types.d.ts +24 -0
  82. package/dist/domain/vaults/vault-service.d.ts +35 -0
  83. package/dist/domain/voice-input-billing.d.ts +77 -0
  84. package/dist/domain/web-search-billing.d.ts +51 -0
  85. package/dist/domain/workspace-credits.d.ts +7 -0
  86. package/dist/editable-artifact-live/application.d.ts +2 -1
  87. package/dist/editable-artifact-live/server.d.ts +5 -1
  88. package/dist/editable-artifact-live/ticket.d.ts +2 -1
  89. package/dist/editable-artifact-live/types.d.ts +9 -0
  90. package/dist/editable-artifact-live.js +2 -2
  91. package/dist/editable-artifacts.js +10 -2
  92. package/dist/goal-admission.d.ts +627 -0
  93. package/dist/host-session-usage-limit.d.ts +15 -0
  94. package/dist/index.d.ts +43 -8
  95. package/dist/index.js +22682 -11743
  96. package/dist/index.js.map +1 -1
  97. package/dist/managed-session.d.ts +110 -7
  98. package/dist/model-call.d.ts +58 -0
  99. package/dist/model-catalog.d.ts +29 -3
  100. package/dist/model-provider-credentials.d.ts +43 -0
  101. package/dist/product-usage-metrics.d.ts +30 -0
  102. package/dist/rigs/index.d.ts +1 -13
  103. package/dist/sandbox/fleet.d.ts +58 -4
  104. package/dist/sandbox/routing.d.ts +42 -2
  105. package/dist/sandbox/runtime-settings.d.ts +27 -17
  106. package/dist/session-authorization.d.ts +10 -0
  107. package/dist/session-usage-limit-metrics.d.ts +60 -0
  108. package/dist/session-usage-limits.d.ts +89 -0
  109. package/dist/site-agent-clamp.d.ts +71 -0
  110. package/dist/site-session-origin.d.ts +39 -2
  111. package/dist/transcription.d.ts +120 -3
  112. package/dist/turn-surface.d.ts +20 -0
  113. package/dist/user-presence.d.ts +32 -0
  114. package/dist/workflow-wake-contract.d.ts +13 -1
  115. package/package.json +15 -22
  116. package/src/access/index.ts +961 -38
  117. package/src/access/service-initiator.ts +50 -0
  118. package/src/application/api-integration-servers.ts +9 -0
  119. package/src/application/archived-session-imports.ts +161 -0
  120. package/src/application/composer-submit.ts +3 -2
  121. package/src/application/connect-authority.ts +3 -3
  122. package/src/application/connect-operation.ts +37 -3
  123. package/src/application/external-continuation.ts +20 -3
  124. package/src/application/external-link-work-admission.ts +3 -0
  125. package/src/application/external-workspace-members.ts +51 -3
  126. package/src/application/modal-native-live-declaration-join.ts +196 -0
  127. package/src/application/modal-native-original-configuration.ts +261 -0
  128. package/src/application/modal-native-worker-host-transport.ts +263 -0
  129. package/src/application/new-session-drafts.ts +172 -55
  130. package/src/application/organization-privacy.ts +161 -0
  131. package/src/application/prepared-mcp-connection.ts +133 -0
  132. package/src/application/sandbox-recovery.ts +78 -0
  133. package/src/application/session-commands.ts +73 -1
  134. package/src/application/session-mcp-credential-rotation.ts +61 -18
  135. package/src/application/session-tenancy.ts +262 -44
  136. package/src/application/user-resource-grants.ts +1 -29
  137. package/src/billing/agent-run-admission.ts +117 -0
  138. package/src/billing/limits.ts +138 -14
  139. package/src/billing/model-call-admission.ts +118 -0
  140. package/src/billing/model-usage-settlement.ts +472 -0
  141. package/src/codex-model-availability.ts +264 -0
  142. package/src/default-session-model.ts +692 -0
  143. package/src/dependencies.ts +48 -6
  144. package/src/developer-setup-credential-ceiling.ts +60 -0
  145. package/src/domain/agent-config-resolution.ts +148 -0
  146. package/src/domain/automations.ts +76 -2
  147. package/src/domain/capabilities.ts +830 -181
  148. package/src/domain/connection-account-selection-error.ts +19 -0
  149. package/src/domain/connector-tool-permissions.ts +572 -0
  150. package/src/domain/direct-model-provider.ts +62 -0
  151. package/src/domain/editable-artifacts/agent-application.ts +44 -6
  152. package/src/domain/editable-artifacts/durable-export.ts +100 -5
  153. package/src/domain/editable-artifacts/in-memory.ts +6 -0
  154. package/src/domain/editable-artifacts/ports.ts +11 -0
  155. package/src/domain/editable-artifacts/service.ts +20 -0
  156. package/src/domain/editable-artifacts/types.ts +1 -1
  157. package/src/domain/encryption-key.ts +26 -0
  158. package/src/domain/file-owner.ts +16 -1
  159. package/src/domain/github-action-policies.ts +1 -1
  160. package/src/domain/github-skill-source.ts +3 -1
  161. package/src/domain/insights-timing.ts +95 -0
  162. package/src/domain/insights-usage.ts +67 -0
  163. package/src/domain/insights.ts +167 -43
  164. package/src/domain/knowledge-files.ts +10 -3
  165. package/src/domain/knowledge-messages.ts +1 -0
  166. package/src/domain/knowledge-preparation.test.ts +276 -0
  167. package/src/domain/knowledge-preparation.ts +147 -0
  168. package/src/domain/knowledge-search.test.ts +617 -5
  169. package/src/domain/knowledge-search.ts +240 -23
  170. package/src/domain/mcp-account-bindings.ts +169 -0
  171. package/src/domain/mcp-account-routes.ts +152 -0
  172. package/src/domain/native-mcp-connection-admission.ts +14 -0
  173. package/src/domain/organization-integration-catalog.ts +88 -0
  174. package/src/domain/personal-connection-delegations.ts +399 -257
  175. package/src/domain/pr-review.ts +24 -8
  176. package/src/domain/realtime-voice-billing.ts +425 -0
  177. package/src/domain/resources.ts +50 -4
  178. package/src/domain/scheduled-task-access.ts +1089 -0
  179. package/src/domain/scheduled-task-account-audit.ts +90 -0
  180. package/src/domain/scheduled-task-audience.ts +249 -0
  181. package/src/domain/scheduled-tasks.ts +1313 -374
  182. package/src/domain/session-connection-accounts.ts +239 -0
  183. package/src/domain/session-file-access.ts +47 -0
  184. package/src/domain/session-native-connection-replacement.ts +64 -0
  185. package/src/domain/session-start-timing.ts +60 -0
  186. package/src/domain/session-tool-policy.ts +489 -14
  187. package/src/domain/sessions.ts +2254 -717
  188. package/src/domain/site-audience.ts +130 -0
  189. package/src/domain/skills.ts +9 -0
  190. package/src/domain/slack-bot.ts +141 -4
  191. package/src/domain/slack-publication-secret-safety.ts +1 -1
  192. package/src/domain/vaults/index.ts +19 -0
  193. package/src/domain/vaults/mapping.ts +190 -0
  194. package/src/domain/vaults/provider-registry.ts +201 -0
  195. package/src/domain/vaults/provider-sync.ts +531 -0
  196. package/src/domain/vaults/provider-writes.ts +288 -0
  197. package/src/domain/vaults/secret-resolver.ts +464 -0
  198. package/src/domain/vaults/types.ts +26 -0
  199. package/src/domain/vaults/vault-service.ts +1316 -0
  200. package/src/domain/voice-input-billing.ts +330 -0
  201. package/src/domain/web-search-billing.ts +169 -0
  202. package/src/domain/workspace-credits.ts +13 -0
  203. package/src/editable-artifact-live/application.ts +8 -1
  204. package/src/editable-artifact-live/server.ts +154 -6
  205. package/src/editable-artifact-live/ticket.ts +86 -1
  206. package/src/editable-artifact-live/types.ts +10 -0
  207. package/src/goal-admission.ts +220 -0
  208. package/src/host-session-usage-limit.ts +25 -0
  209. package/src/index.ts +75 -11
  210. package/src/managed-session.ts +137 -6
  211. package/src/model-call.ts +79 -0
  212. package/src/model-catalog.ts +345 -91
  213. package/src/model-provider-credentials.ts +394 -0
  214. package/src/product-usage-metrics.ts +101 -0
  215. package/src/rigs/index.ts +23 -111
  216. package/src/sandbox/fleet.ts +253 -28
  217. package/src/sandbox/routing.ts +204 -43
  218. package/src/sandbox/runtime-settings.ts +159 -85
  219. package/src/session-authorization.ts +172 -23
  220. package/src/session-usage-limit-metrics.ts +95 -0
  221. package/src/session-usage-limits.ts +415 -0
  222. package/src/site-agent-clamp.ts +226 -0
  223. package/src/site-session-origin.ts +84 -5
  224. package/src/transcription.ts +123 -4
  225. package/src/turn-surface.ts +47 -0
  226. package/src/user-presence.ts +134 -0
  227. package/src/workflow-wake-contract.ts +14 -1
  228. package/dist/application/host-mcp-owner.d.ts +0 -6
  229. package/dist/application/host-mcp-resolvers.d.ts +0 -13
  230. package/dist/chunk-5WZYFL45.js +0 -266
  231. package/dist/chunk-5WZYFL45.js.map +0 -1
  232. package/dist/chunk-H7X52RI2.js.map +0 -1
  233. package/dist/chunk-OF65T3PM.js.map +0 -1
  234. package/dist/chunk-VCZFFEPB.js.map +0 -1
  235. package/dist/chunk-ZYCNLYGH.js.map +0 -1
  236. package/dist/domain/connection-authority.d.ts +0 -97
  237. package/dist/domain/connection-authority.js +0 -169
  238. package/dist/domain/connection-authority.js.map +0 -1
  239. package/dist/domain/environments.d.ts +0 -33
  240. package/dist/domain/host-mcp-authority-source-admission.d.ts +0 -10
  241. package/dist/domain/host-mcp-task-admission.d.ts +0 -18
  242. package/dist/domain/packs.d.ts +0 -59
  243. package/dist/domain/product-integration-pack.d.ts +0 -44
  244. package/dist/domain/product-integration-skill.gen.d.ts +0 -5
  245. package/dist/remote-mcp-credentials.d.ts +0 -23
  246. package/dist/remote-mcp-credentials.js +0 -11
  247. package/dist/remote-mcp-credentials.js.map +0 -1
  248. package/src/application/host-mcp-owner.ts +0 -41
  249. package/src/application/host-mcp-resolvers.ts +0 -84
  250. package/src/domain/connection-authority.ts +0 -329
  251. package/src/domain/environments.ts +0 -120
  252. package/src/domain/host-mcp-authority-source-admission.ts +0 -25
  253. package/src/domain/host-mcp-task-admission.ts +0 -121
  254. package/src/domain/packs.ts +0 -699
  255. package/src/domain/product-integration-pack.ts +0 -42
  256. package/src/domain/product-integration-skill.gen.ts +0 -52
  257. package/src/remote-mcp-credentials.ts +0 -361
@@ -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
- getBillingBalance,
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. SuperGrok's
52
- // static overlay is likewise secret-free; its worker/provider admission
53
- // owns live account selection before any upstream request can occur.
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
- (resolvedModel != null && !codexSubscriptionModel && resolvedModel.cost !== "credits"),
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(decision.code === "insufficient_credits" ? 402 : 429, {
68
- message: decision.message,
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 OpenGeni — zero OpenGeni credits
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 getBillingBalance(deps.db, input.accountId);
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 { allowed: false, code: "insufficient_credits", message: "insufficient OpenGeni credits" };
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
- return (
300
- action === "agent_run:create" || action === "tokens:consume" || action === "document:index"
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
+ }