@opengeni/core 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/default-session-model.d.ts +5 -3
- package/dist/goal-admission.d.ts +8 -0
- package/dist/index.js +45 -21
- package/dist/index.js.map +1 -1
- package/package.json +12 -12
- package/src/billing/agent-run-admission.ts +11 -3
- package/src/billing/limits.ts +14 -3
- package/src/default-session-model.ts +23 -12
- package/src/domain/knowledge-search.test.ts +1 -1
- package/src/domain/knowledge-search.ts +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "OpenGeni framework-agnostic core: the domain, access, and billing layers (neutral access, off-HTTP V2 surface). Behavior-preserving extraction from apps/api — keeps Hono's HTTPException for error throwing (typed-errors cleanup deferred).",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -50,17 +50,17 @@
|
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
53
|
-
"@opengeni/capabilities": "1.
|
|
54
|
-
"@opengeni/codex": "1.
|
|
55
|
-
"@opengeni/config": "1.
|
|
56
|
-
"@opengeni/contracts": "1.
|
|
57
|
-
"@opengeni/db": "1.
|
|
58
|
-
"@opengeni/documents": "1.
|
|
59
|
-
"@opengeni/events": "1.
|
|
60
|
-
"@opengeni/network": "1.
|
|
61
|
-
"@opengeni/observability": "1.
|
|
62
|
-
"@opengeni/runtime": "1.
|
|
63
|
-
"@opengeni/storage": "1.
|
|
53
|
+
"@opengeni/capabilities": "1.2.0",
|
|
54
|
+
"@opengeni/codex": "1.2.0",
|
|
55
|
+
"@opengeni/config": "1.2.0",
|
|
56
|
+
"@opengeni/contracts": "1.2.0",
|
|
57
|
+
"@opengeni/db": "1.2.0",
|
|
58
|
+
"@opengeni/documents": "1.2.0",
|
|
59
|
+
"@opengeni/events": "1.2.0",
|
|
60
|
+
"@opengeni/network": "1.2.0",
|
|
61
|
+
"@opengeni/observability": "1.2.0",
|
|
62
|
+
"@opengeni/runtime": "1.2.0",
|
|
63
|
+
"@opengeni/storage": "1.2.0",
|
|
64
64
|
"hono": "^4.12.18",
|
|
65
65
|
"zod": "^4.2.1"
|
|
66
66
|
},
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
canonicalizeConfiguredModelId,
|
|
3
|
+
configuredStaticUsageLimits,
|
|
4
|
+
type Settings,
|
|
5
|
+
} from "@opengeni/config";
|
|
2
6
|
import type { EntitlementsPort } from "@opengeni/contracts";
|
|
3
7
|
import { modelFundingForAdmission } from "./limits";
|
|
4
8
|
import {
|
|
5
9
|
checkWorkspaceAllowance,
|
|
6
|
-
|
|
10
|
+
getSpendableCreditBalance,
|
|
7
11
|
isCodexBilledTurn,
|
|
8
12
|
sumUsageQuantity,
|
|
9
13
|
type Database,
|
|
@@ -51,7 +55,11 @@ export async function agentRunAdmissionDenial(
|
|
|
51
55
|
});
|
|
52
56
|
if (!decision.allowed) return "insufficient_credits";
|
|
53
57
|
} else {
|
|
54
|
-
const balance = await
|
|
58
|
+
const balance = await getSpendableCreditBalance(
|
|
59
|
+
services.db,
|
|
60
|
+
input.accountId,
|
|
61
|
+
canonicalizeConfiguredModelId(services.settings, input.model),
|
|
62
|
+
);
|
|
55
63
|
if (balance.balanceMicros <= 0) return "insufficient_credits";
|
|
56
64
|
}
|
|
57
65
|
}
|
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";
|
|
@@ -16,7 +17,7 @@ import {
|
|
|
16
17
|
countActiveOrganizationApiKeysForAccount,
|
|
17
18
|
countScheduledTasksForWorkspace,
|
|
18
19
|
countWorkspacesForAccount,
|
|
19
|
-
|
|
20
|
+
getSpendableCreditBalance,
|
|
20
21
|
isCodexBilledTurn,
|
|
21
22
|
recordUsageEvent,
|
|
22
23
|
sumUsageQuantity,
|
|
@@ -148,11 +149,21 @@ async function checkCreditBalance(
|
|
|
148
149
|
if (!usesCreditLimits(deps) || !isCostlyAction(input.action)) {
|
|
149
150
|
return { allowed: true };
|
|
150
151
|
}
|
|
151
|
-
const balance = await
|
|
152
|
+
const balance = await getSpendableCreditBalance(
|
|
153
|
+
deps.db,
|
|
154
|
+
input.accountId,
|
|
155
|
+
input.model ? canonicalizeConfiguredModelId(deps.settings, input.model) : undefined,
|
|
156
|
+
);
|
|
152
157
|
if (balance.balanceMicros > 0) {
|
|
153
158
|
return { allowed: true };
|
|
154
159
|
}
|
|
155
|
-
return {
|
|
160
|
+
return {
|
|
161
|
+
allowed: false,
|
|
162
|
+
code: "insufficient_credits",
|
|
163
|
+
message: input.model
|
|
164
|
+
? "No credits available for this model. Choose a model covered by your promotional credits or add credits."
|
|
165
|
+
: "No general credits available. Promotional credits cover eligible models only.",
|
|
166
|
+
};
|
|
156
167
|
}
|
|
157
168
|
|
|
158
169
|
async function checkStaticCaps(
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
type WorkspaceSessionDefaults,
|
|
14
14
|
type XaiProviderAccountAuthoritySnapshotV1,
|
|
15
15
|
type ClaudeProviderAccountAuthoritySnapshotV1,
|
|
16
|
+
type BillingBalance,
|
|
16
17
|
} from "@opengeni/contracts";
|
|
17
18
|
import {
|
|
18
19
|
getScheduledTaskXaiProviderAccountAuthoritySnapshot,
|
|
@@ -30,7 +31,8 @@ import {
|
|
|
30
31
|
workspaceCodexSubscriptionActive,
|
|
31
32
|
workspaceOpenRouterConnectionActive,
|
|
32
33
|
workspaceVercelAiGatewayConnectionActive,
|
|
33
|
-
|
|
34
|
+
getBillingBalance,
|
|
35
|
+
spendableCreditMicros,
|
|
34
36
|
workspaceXaiSubscriptionActive,
|
|
35
37
|
workspaceXaiSubscriptionActiveForAuthority,
|
|
36
38
|
XaiAuthorityPoolInactiveError,
|
|
@@ -112,16 +114,22 @@ export type DefaultSessionModelInput = {
|
|
|
112
114
|
* `organizationHoldsCredits`).
|
|
113
115
|
*/
|
|
114
116
|
creditsAvailable: boolean;
|
|
117
|
+
creditBalance?: BillingBalance;
|
|
115
118
|
};
|
|
116
119
|
|
|
117
120
|
function creditsCandidate(
|
|
118
|
-
input: Pick<DefaultSessionModelInput, "settings" | "selections">,
|
|
121
|
+
input: Pick<DefaultSessionModelInput, "settings" | "selections" | "creditBalance">,
|
|
119
122
|
): DefaultModelSelection | null {
|
|
123
|
+
const selections = input.creditBalance
|
|
124
|
+
? input.selections.filter(
|
|
125
|
+
(selection) => spendableCreditMicros(input.creditBalance!, selection.model.id) > 0,
|
|
126
|
+
)
|
|
127
|
+
: input.selections;
|
|
120
128
|
const fallbackEffort = input.settings.openaiReasoningEffort;
|
|
121
|
-
const deployment = findSelection(
|
|
122
|
-
const configured = findSelection(
|
|
129
|
+
const deployment = findSelection(selections, input.settings.openaiModel);
|
|
130
|
+
const configured = findSelection(selections, input.settings.creditsDefaultModel);
|
|
123
131
|
if (deployment?.availability.selectable && deployment.model.cost === "credits") {
|
|
124
|
-
//
|
|
132
|
+
// A funded paid deployment default is preserved. When it is the
|
|
125
133
|
// credits default model itself, credit holders get the credits default
|
|
126
134
|
// effort rather than the deployment-wide fallback effort.
|
|
127
135
|
if (configured?.model.id !== deployment.model.id) return null;
|
|
@@ -146,7 +154,7 @@ function creditsCandidate(
|
|
|
146
154
|
source: "credits",
|
|
147
155
|
};
|
|
148
156
|
}
|
|
149
|
-
const first =
|
|
157
|
+
const first = selections.find(
|
|
150
158
|
(selection) => selection.availability.selectable && selection.model.cost === "credits",
|
|
151
159
|
);
|
|
152
160
|
return first
|
|
@@ -175,8 +183,8 @@ function creditsCandidate(
|
|
|
175
183
|
* configured credits default (`OPENGENI_CREDITS_DEFAULT_MODEL`, effort
|
|
176
184
|
* clamped to what the model supports), or the first selectable
|
|
177
185
|
* credits-billed model when that one is not selectable. When the
|
|
178
|
-
* deployment default is already a selectable credits-billed model it
|
|
179
|
-
*
|
|
186
|
+
* deployment default is already a funded, selectable credits-billed model it
|
|
187
|
+
* keeps the deployment effort, except that it takes the
|
|
180
188
|
* credits default effort when it is the credits default model itself.
|
|
181
189
|
* 4. `deployment`: the deployment default with the deployment reasoning effort
|
|
182
190
|
* when stably admissible; otherwise the first stably admissible catalog
|
|
@@ -247,6 +255,7 @@ export function creditsDefaultSessionModel(input: {
|
|
|
247
255
|
settings: Settings;
|
|
248
256
|
selections: readonly WorkspaceModelSelection[];
|
|
249
257
|
workspaceSettings: unknown;
|
|
258
|
+
creditBalance?: BillingBalance;
|
|
250
259
|
}): DefaultModelSelection | null {
|
|
251
260
|
if (input.settings.billingMode !== "stripe") return null;
|
|
252
261
|
return selectDefaultSessionModel({
|
|
@@ -254,6 +263,7 @@ export function creditsDefaultSessionModel(input: {
|
|
|
254
263
|
selections: input.selections,
|
|
255
264
|
workspaceDefaults: resolveWorkspaceSessionDefaults(input.workspaceSettings),
|
|
256
265
|
creditsAvailable: true,
|
|
266
|
+
...(input.creditBalance ? { creditBalance: input.creditBalance } : {}),
|
|
257
267
|
});
|
|
258
268
|
}
|
|
259
269
|
|
|
@@ -279,13 +289,14 @@ export async function resolveDefaultSessionModelForSelections(
|
|
|
279
289
|
if (
|
|
280
290
|
withoutCredits.source !== "deployment" ||
|
|
281
291
|
input.settings.billingMode !== "stripe" ||
|
|
282
|
-
|
|
292
|
+
!input.selections.some(
|
|
293
|
+
(selection) => selection.availability.selectable && selection.model.cost === "credits",
|
|
294
|
+
)
|
|
283
295
|
) {
|
|
284
296
|
return withoutCredits;
|
|
285
297
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
: withoutCredits;
|
|
298
|
+
const creditBalance = await getBillingBalance(db, input.accountId);
|
|
299
|
+
return selectDefaultSessionModel({ ...decision, creditsAvailable: true, creditBalance });
|
|
289
300
|
}
|
|
290
301
|
|
|
291
302
|
export type WorkspaceModelSelectionContext = {
|
|
@@ -40,7 +40,7 @@ const usage = mock(
|
|
|
40
40
|
);
|
|
41
41
|
mock.module("@opengeni/db", () => ({
|
|
42
42
|
listKnowledgeEntries: list,
|
|
43
|
-
|
|
43
|
+
getSpendableCreditBalance: async () => ({ balanceMicros: balance }),
|
|
44
44
|
applyCreditDebitAfterUse: debits,
|
|
45
45
|
checkWorkspaceAllowance: allowanceChecks,
|
|
46
46
|
creditDebitAttributionForTurn: async (_db: unknown, input: { turnId: string }) => ({
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
applyCreditDebitAfterUse,
|
|
5
5
|
checkWorkspaceAllowance,
|
|
6
6
|
creditDebitAttributionForTurn,
|
|
7
|
-
|
|
7
|
+
getSpendableCreditBalance,
|
|
8
8
|
listKnowledgeEntries,
|
|
9
9
|
recordUsageEvent,
|
|
10
10
|
sumUsageQuantity,
|
|
@@ -190,7 +190,7 @@ export async function searchKnowledgeEntries(
|
|
|
190
190
|
context.accountId,
|
|
191
191
|
context.workspaceId,
|
|
192
192
|
async (lockedDb) => {
|
|
193
|
-
const balance = await
|
|
193
|
+
const balance = await getSpendableCreditBalance(lockedDb, context.accountId);
|
|
194
194
|
if (balance.balanceMicros <= 0)
|
|
195
195
|
return keywordFallback(lockedDb, new KnowledgeVectorFundingError(), "awaiting_funding");
|
|
196
196
|
const attribution =
|