@opengeni/core 0.28.0 → 1.0.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/README.md +1 -1
- package/dist/access/external-actor-authority.d.ts +47 -0
- package/dist/access/index.d.ts +182 -3
- package/dist/access/service-initiator.d.ts +3 -0
- package/dist/application/api-integration-servers.d.ts +20 -0
- package/dist/application/archived-session-imports.d.ts +6 -0
- package/dist/application/composer-submit.d.ts +24 -0
- package/dist/application/connect-authority.d.ts +13 -0
- package/dist/application/connect-operation.d.ts +31 -0
- package/dist/application/external-continuation.d.ts +15 -0
- package/dist/application/external-identity-lifecycle.d.ts +20 -0
- package/dist/application/external-link-work-admission.d.ts +12 -0
- package/dist/application/external-workspace-members.d.ts +34 -0
- package/dist/application/modal-native-worker-host-transport.d.ts +297 -0
- package/dist/application/new-session-drafts.d.ts +39 -2
- package/dist/application/sandbox-recovery.d.ts +58 -0
- package/dist/application/session-commands.d.ts +26 -13
- package/dist/application/session-mcp-credential-rotation.d.ts +29 -0
- package/dist/application/session-tenancy.d.ts +19 -0
- package/dist/application/user-resource-grants.d.ts +20 -0
- package/dist/billing/limits.d.ts +11 -0
- package/dist/canonical-human-identities.d.ts +7 -2
- package/dist/canonical-human-identities.js +17 -4
- package/dist/canonical-human-identities.js.map +1 -1
- package/dist/{chunk-6WKPWE5S.js → chunk-2MKMRGSY.js} +108 -24
- package/dist/chunk-2MKMRGSY.js.map +1 -0
- package/dist/{chunk-NYPMQ7NO.js → chunk-GVO3N37G.js} +205 -37
- package/dist/chunk-GVO3N37G.js.map +1 -0
- package/dist/{chunk-JJU6I5XD.js → chunk-LCQXNR62.js} +36 -10
- package/dist/chunk-LCQXNR62.js.map +1 -0
- package/dist/chunk-MM6N7CTO.js +387 -0
- package/dist/chunk-MM6N7CTO.js.map +1 -0
- package/dist/chunk-QO5GVFFO.js +235 -0
- package/dist/chunk-QO5GVFFO.js.map +1 -0
- package/dist/codex-model-availability.d.ts +14 -0
- package/dist/default-session-model.d.ts +139 -0
- package/dist/dependencies.d.ts +87 -4
- 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 +58 -0
- package/dist/domain/capabilities.d.ts +22 -4
- package/dist/domain/company-profile-agent-admin.d.ts +25 -0
- package/dist/domain/connection-account-selection-error.d.ts +8 -0
- package/dist/domain/connector-tool-permissions.d.ts +36 -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 +4 -3
- package/dist/domain/environments.d.ts +5 -9
- package/dist/domain/external-creation-attribution.d.ts +6 -0
- package/dist/domain/file-owner.d.ts +8 -0
- package/dist/domain/github-action-policies.d.ts +36 -0
- package/dist/domain/github-repository-bindings.d.ts +117 -0
- package/dist/domain/github-skill-source.d.ts +5 -0
- package/dist/domain/governed-learning-slack-publication.d.ts +45 -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 +28 -1
- package/dist/domain/knowledge-files.d.ts +20 -0
- package/dist/domain/knowledge-messages.d.ts +39 -0
- package/dist/domain/knowledge-messages.test.d.ts +1 -0
- 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 -0
- package/dist/domain/knowledge-search.test.d.ts +1 -0
- package/dist/domain/knowledge.d.ts +9 -0
- package/dist/domain/mcp-account-bindings.d.ts +19 -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/organization-membership-lifecycle.d.ts +1 -0
- package/dist/domain/personal-connection-delegations.d.ts +87 -4
- package/dist/domain/pr-review.d.ts +249 -0
- package/dist/domain/resources.d.ts +24 -3
- package/dist/domain/scheduled-task-access.d.ts +247 -0
- package/dist/domain/scheduled-tasks.d.ts +103 -6
- package/dist/domain/session-connection-accounts.d.ts +16 -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 +93 -4
- package/dist/domain/sessions.d.ts +275 -28
- package/dist/domain/skill-imports.d.ts +1 -0
- package/dist/domain/skill-search.d.ts +30 -0
- package/dist/domain/skills.d.ts +117 -0
- package/dist/domain/slack-bot.d.ts +46 -1
- package/dist/domain/workspace-members.d.ts +8 -1
- package/dist/editable-artifact-live/application.d.ts +12 -3
- package/dist/editable-artifact-live/ports.d.ts +0 -1
- package/dist/editable-artifact-live/server.d.ts +5 -2
- package/dist/editable-artifact-live/ticket.d.ts +2 -2
- package/dist/editable-artifact-live/types.d.ts +14 -2
- package/dist/editable-artifact-live.js +2 -2
- package/dist/editable-artifacts.js +10 -4
- package/dist/index.d.ts +49 -3
- package/dist/index.js +18826 -6847
- package/dist/index.js.map +1 -1
- package/dist/managed-auth-session-sets.d.ts +131 -0
- package/dist/managed-auth-session-sets.js +51 -0
- package/dist/managed-auth-session-sets.js.map +1 -0
- package/dist/managed-session.d.ts +98 -11
- package/dist/model-catalog.d.ts +117 -0
- package/dist/product-usage-metrics.d.ts +30 -0
- package/dist/rigs/index.d.ts +22 -5
- package/dist/rigs/provider-images.d.ts +1 -1
- package/dist/sandbox/fleet.d.ts +59 -10
- package/dist/sandbox/routing.d.ts +22 -2
- package/dist/sandbox/runtime-settings.d.ts +56 -0
- package/dist/session-authorization.d.ts +38 -1
- package/dist/site-session-origin.d.ts +9 -0
- package/dist/transcription.d.ts +12 -1
- package/dist/turn-surface.d.ts +18 -0
- package/dist/user-presence.d.ts +32 -0
- package/package.json +18 -11
- package/src/access/external-actor-authority.ts +94 -0
- package/src/access/index.ts +1386 -44
- package/src/access/service-initiator.ts +50 -0
- package/src/application/api-integration-servers.ts +215 -0
- package/src/application/archived-session-imports.ts +161 -0
- package/src/application/composer-submit.ts +77 -0
- package/src/application/connect-authority.ts +77 -0
- package/src/application/connect-operation.ts +80 -0
- package/src/application/external-continuation.ts +129 -0
- package/src/application/external-identity-lifecycle.ts +48 -0
- package/src/application/external-link-work-admission.ts +90 -0
- package/src/application/external-workspace-members.ts +232 -0
- package/src/application/modal-native-worker-host-transport.ts +263 -0
- package/src/application/new-session-drafts.ts +222 -29
- package/src/application/sandbox-recovery.ts +78 -0
- package/src/application/session-commands.ts +430 -73
- package/src/application/session-mcp-credential-rotation.ts +217 -0
- package/src/application/session-tenancy.ts +445 -0
- package/src/application/user-resource-grants.ts +154 -0
- package/src/billing/limits.ts +103 -24
- package/src/canonical-human-identities.ts +22 -3
- package/src/codex-model-availability.ts +136 -0
- package/src/default-session-model.ts +656 -0
- package/src/dependencies.ts +97 -5
- package/src/developer-setup-credential-ceiling.ts +60 -0
- package/src/domain/agent-config-resolution.ts +148 -0
- package/src/domain/automations.ts +270 -0
- package/src/domain/capabilities.ts +244 -151
- package/src/domain/company-profile-agent-admin.ts +56 -0
- package/src/domain/connection-account-selection-error.ts +19 -0
- package/src/domain/connector-tool-permissions.ts +371 -0
- package/src/domain/direct-model-provider.ts +62 -0
- package/src/domain/editable-artifacts/agent-application.ts +61 -15
- 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 +32 -1
- package/src/domain/editable-artifacts/snapshot-compaction.ts +9 -5
- package/src/domain/editable-artifacts/snapshot-verifier-genesis.ts +10 -8
- package/src/domain/editable-artifacts/snapshot-verifier.ts +10 -3
- package/src/domain/environments.ts +25 -45
- package/src/domain/external-creation-attribution.ts +22 -0
- package/src/domain/file-owner.ts +67 -0
- package/src/domain/github-action-policies.ts +169 -0
- package/src/domain/github-repository-bindings.ts +295 -0
- package/src/domain/github-skill-source.ts +233 -0
- package/src/domain/governed-learning-slack-publication.ts +157 -0
- package/src/domain/insights-timing.ts +95 -0
- package/src/domain/insights-usage.ts +67 -0
- package/src/domain/insights.ts +270 -169
- package/src/domain/knowledge-files.ts +46 -0
- package/src/domain/knowledge-messages.test.ts +91 -0
- package/src/domain/knowledge-messages.ts +106 -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 +561 -0
- package/src/domain/knowledge-search.ts +256 -0
- package/src/domain/knowledge.ts +133 -0
- package/src/domain/mcp-account-bindings.ts +171 -0
- package/src/domain/mcp-account-routes.ts +121 -0
- package/src/domain/native-mcp-connection-admission.ts +14 -0
- package/src/domain/organization-integration-catalog.ts +88 -0
- package/src/domain/organization-membership-lifecycle.ts +10 -0
- package/src/domain/personal-connection-delegations.ts +758 -115
- package/src/domain/pr-review.ts +687 -0
- package/src/domain/resources.ts +182 -25
- package/src/domain/scheduled-task-access.ts +1052 -0
- package/src/domain/scheduled-tasks.ts +1952 -215
- package/src/domain/session-connection-accounts.ts +67 -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 +55 -0
- package/src/domain/session-tool-policy.ts +443 -19
- package/src/domain/sessions.ts +3417 -619
- package/src/domain/skill-imports.ts +137 -17
- package/src/domain/skill-search.ts +221 -0
- package/src/domain/skills.ts +104 -0
- package/src/domain/slack-bot.ts +128 -3
- package/src/domain/workspace-members.ts +34 -2
- package/src/editable-artifact-live/application.ts +29 -4
- package/src/editable-artifact-live/ports.ts +0 -1
- package/src/editable-artifact-live/server.ts +167 -13
- package/src/editable-artifact-live/ticket.ts +87 -6
- package/src/editable-artifact-live/types.ts +16 -2
- package/src/editable-artifact-live/wire.ts +0 -10
- package/src/index.ts +69 -4
- package/src/managed-auth-session-sets.ts +371 -0
- package/src/managed-session.ts +520 -2
- package/src/model-catalog.ts +873 -0
- package/src/product-usage-metrics.ts +101 -0
- package/src/rigs/index.ts +179 -63
- package/src/rigs/provider-images.ts +2 -3
- package/src/sandbox/fleet.ts +471 -59
- package/src/sandbox/routing.ts +160 -33
- package/src/sandbox/runtime-settings.ts +281 -0
- package/src/session-authorization.ts +262 -78
- package/src/site-session-origin.ts +22 -0
- package/src/transcription.ts +13 -1
- package/src/turn-surface.ts +45 -0
- package/src/user-presence.ts +134 -0
- package/dist/chunk-6WKPWE5S.js.map +0 -1
- package/dist/chunk-IBOEYG6N.js +0 -34
- package/dist/chunk-IBOEYG6N.js.map +0 -1
- package/dist/chunk-JJU6I5XD.js.map +0 -1
- package/dist/chunk-NYPMQ7NO.js.map +0 -1
- package/dist/domain/durable-learning-slack-publication.d.ts +0 -72
- package/dist/domain/packs.d.ts +0 -55
- package/src/domain/durable-learning-slack-publication.ts +0 -202
- package/src/domain/packs.ts +0 -556
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
IssueUserResourceGrantRequest,
|
|
3
|
+
Permission,
|
|
4
|
+
SessionAuthorizationSurface,
|
|
5
|
+
UserResourceKind,
|
|
6
|
+
} from "@opengeni/contracts";
|
|
7
|
+
import {
|
|
8
|
+
issueSelfUserResourceGrant,
|
|
9
|
+
listSelfUserResourceAuthorities,
|
|
10
|
+
revokeSelfUserResourceGrant,
|
|
11
|
+
sessionTenancyProductActivated,
|
|
12
|
+
SessionTenancyNotActivatedError,
|
|
13
|
+
} from "@opengeni/db";
|
|
14
|
+
import { requirePermission, type AccessGrantAuthorization } from "../access";
|
|
15
|
+
import type { AppDependencies } from "../dependencies";
|
|
16
|
+
import { requireSessionAuthorization } from "../session-authorization";
|
|
17
|
+
import {
|
|
18
|
+
requireVerifiedOwningUser,
|
|
19
|
+
SessionTenancyManagedHumanRequiredError,
|
|
20
|
+
} from "./session-tenancy";
|
|
21
|
+
|
|
22
|
+
type UserResourceGrantDependencies = Pick<AppDependencies, "db" | "sessionAuthorization">;
|
|
23
|
+
|
|
24
|
+
const LIST_PERMISSIONS = {
|
|
25
|
+
connection: ["connections:read"],
|
|
26
|
+
document: ["documents:search"],
|
|
27
|
+
variable_set: ["variable-sets:list"],
|
|
28
|
+
rig: ["rigs:use"],
|
|
29
|
+
connected_machine: ["enrollments:read"],
|
|
30
|
+
} as const satisfies Record<UserResourceKind, readonly Permission[]>;
|
|
31
|
+
|
|
32
|
+
const ISSUE_PERMISSIONS = {
|
|
33
|
+
connection: ["connections:read"],
|
|
34
|
+
document: ["documents:search"],
|
|
35
|
+
variable_set: ["variable-sets:list", "variable-sets:attach", "variable-sets:use"],
|
|
36
|
+
rig: ["rigs:use"],
|
|
37
|
+
connected_machine: ["enrollments:read"],
|
|
38
|
+
} as const satisfies Record<UserResourceKind, readonly Permission[]>;
|
|
39
|
+
|
|
40
|
+
async function requireOwnerProductGate(
|
|
41
|
+
deps: Pick<UserResourceGrantDependencies, "db">,
|
|
42
|
+
authorization: AccessGrantAuthorization,
|
|
43
|
+
workspaceId: string,
|
|
44
|
+
permissions: readonly Permission[],
|
|
45
|
+
): Promise<void> {
|
|
46
|
+
// Target-free gates must run before an optional target-session lookup or host
|
|
47
|
+
// callback so rejected principals cannot use grant management as an oracle.
|
|
48
|
+
requireOwnerAuthority(authorization, workspaceId, permissions);
|
|
49
|
+
if (!(await sessionTenancyProductActivated(deps.db, workspaceId))) {
|
|
50
|
+
throw new SessionTenancyNotActivatedError();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function requireOwnerAuthority(
|
|
55
|
+
authorization: AccessGrantAuthorization,
|
|
56
|
+
workspaceId: string,
|
|
57
|
+
permissions: readonly Permission[],
|
|
58
|
+
): void {
|
|
59
|
+
if (!authorization.canonicalLocalHumanSession) {
|
|
60
|
+
requireVerifiedOwningUser(authorization, workspaceId);
|
|
61
|
+
} else if (
|
|
62
|
+
!authorization.contextIntegrity ||
|
|
63
|
+
authorization.authenticatedSubjectId !== authorization.grant.subjectId ||
|
|
64
|
+
authorization.grant.workspaceId !== workspaceId
|
|
65
|
+
) {
|
|
66
|
+
throw new SessionTenancyManagedHumanRequiredError();
|
|
67
|
+
}
|
|
68
|
+
for (const permission of permissions) requirePermission(authorization.grant, permission);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function listManagedHumanUserResourceAuthorities(
|
|
72
|
+
deps: Pick<UserResourceGrantDependencies, "db">,
|
|
73
|
+
authorization: AccessGrantAuthorization,
|
|
74
|
+
workspaceId: string,
|
|
75
|
+
input: { resourceKind: UserResourceKind; cursor?: string | undefined; limit: number },
|
|
76
|
+
) {
|
|
77
|
+
requireOwnerAuthority(authorization, workspaceId, LIST_PERMISSIONS[input.resourceKind]);
|
|
78
|
+
// Before activation there cannot be usable personal-resource authority in
|
|
79
|
+
// this organization. Discovery therefore has an exact empty answer; only
|
|
80
|
+
// issue/revoke and runtime use remain behind the forward-only product gate.
|
|
81
|
+
// This keeps ordinary workspace-owned Rig/Variable Set selection independent
|
|
82
|
+
// from an optional personal-resource product that is not enabled.
|
|
83
|
+
if (!(await sessionTenancyProductActivated(deps.db, workspaceId))) {
|
|
84
|
+
return { authorities: [], nextCursor: null };
|
|
85
|
+
}
|
|
86
|
+
return await listSelfUserResourceAuthorities(deps.db, {
|
|
87
|
+
accountId: authorization.grant.accountId,
|
|
88
|
+
workspaceId,
|
|
89
|
+
subjectId: authorization.grant.subjectId,
|
|
90
|
+
...input,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function issueManagedHumanUserResourceGrant(
|
|
95
|
+
deps: UserResourceGrantDependencies,
|
|
96
|
+
authorization: AccessGrantAuthorization,
|
|
97
|
+
workspaceId: string,
|
|
98
|
+
authorityId: string,
|
|
99
|
+
request: IssueUserResourceGrantRequest,
|
|
100
|
+
authorizationSurface: SessionAuthorizationSurface = "core",
|
|
101
|
+
) {
|
|
102
|
+
const modePermissions: Permission[] =
|
|
103
|
+
request.mode === "session" ? ["sessions:control"] : ["sessions:create"];
|
|
104
|
+
if (authorization.canonicalLocalHumanSession) {
|
|
105
|
+
throw new SessionTenancyManagedHumanRequiredError();
|
|
106
|
+
}
|
|
107
|
+
await requireOwnerProductGate(deps, authorization, workspaceId, [
|
|
108
|
+
...ISSUE_PERMISSIONS[request.resourceKind],
|
|
109
|
+
...modePermissions,
|
|
110
|
+
]);
|
|
111
|
+
|
|
112
|
+
if (request.mode === "session") {
|
|
113
|
+
// Contract refinement guarantees both fields. Keep the defensive check so
|
|
114
|
+
// direct core callers cannot accidentally weaken the target fence.
|
|
115
|
+
if (!request.sessionId || !request.expectedAuthorityEpoch) {
|
|
116
|
+
throw new TypeError("session grant requires its exact authority epoch");
|
|
117
|
+
}
|
|
118
|
+
await requireSessionAuthorization(deps, authorization.grant, {
|
|
119
|
+
sessionId: request.sessionId,
|
|
120
|
+
operation: "session.personal_resource.grant",
|
|
121
|
+
surface: authorizationSurface,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return await issueSelfUserResourceGrant(deps.db, {
|
|
126
|
+
accountId: authorization.grant.accountId,
|
|
127
|
+
workspaceId,
|
|
128
|
+
subjectId: authorization.grant.subjectId,
|
|
129
|
+
authorityId,
|
|
130
|
+
resourceKind: request.resourceKind,
|
|
131
|
+
mode: request.mode,
|
|
132
|
+
context: request.context,
|
|
133
|
+
sessionId: request.sessionId ?? null,
|
|
134
|
+
expectedAuthorityEpoch: request.expectedAuthorityEpoch ?? null,
|
|
135
|
+
workspaceSharedAcknowledged: request.workspaceSharedAcknowledged,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export async function revokeManagedHumanUserResourceGrant(
|
|
140
|
+
deps: Pick<UserResourceGrantDependencies, "db">,
|
|
141
|
+
authorization: AccessGrantAuthorization,
|
|
142
|
+
workspaceId: string,
|
|
143
|
+
grantId: string,
|
|
144
|
+
) {
|
|
145
|
+
// Revocation narrows authority. It intentionally does not require the
|
|
146
|
+
// resource-specific permission that may have been removed since issuance.
|
|
147
|
+
await requireOwnerProductGate(deps, authorization, workspaceId, []);
|
|
148
|
+
return await revokeSelfUserResourceGrant(deps.db, {
|
|
149
|
+
accountId: authorization.grant.accountId,
|
|
150
|
+
workspaceId,
|
|
151
|
+
subjectId: authorization.grant.subjectId,
|
|
152
|
+
grantId,
|
|
153
|
+
});
|
|
154
|
+
}
|
package/src/billing/limits.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
configuredStaticUsageLimits,
|
|
3
|
+
resolveModelProviderForTurn,
|
|
4
|
+
type Settings,
|
|
5
|
+
} from "@opengeni/config";
|
|
2
6
|
import type {
|
|
3
7
|
LimitAction,
|
|
4
8
|
LimitDecision,
|
|
@@ -7,7 +11,9 @@ import type {
|
|
|
7
11
|
TurnInitiatorContext,
|
|
8
12
|
} from "@opengeni/contracts";
|
|
9
13
|
import {
|
|
14
|
+
checkWorkspaceAllowance,
|
|
10
15
|
countActiveApiKeysForWorkspace,
|
|
16
|
+
countActiveOrganizationApiKeysForAccount,
|
|
11
17
|
countScheduledTasksForWorkspace,
|
|
12
18
|
countWorkspacesForAccount,
|
|
13
19
|
getBillingBalance,
|
|
@@ -20,28 +26,74 @@ import type { ApiRouteDeps } from "../dependencies";
|
|
|
20
26
|
|
|
21
27
|
export type LimitDependencies = Pick<ApiRouteDeps, "db" | "settings">;
|
|
22
28
|
|
|
29
|
+
/** Deterministic/local embeddings never debit credits, even in paid mode. */
|
|
30
|
+
export function paidDocumentEmbedding(settings: Settings): boolean {
|
|
31
|
+
return (
|
|
32
|
+
settings.documentEmbeddingBillingMode === "credits" &&
|
|
33
|
+
settings.documentEmbeddingProvider === "openai"
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Round up each provider request so a positive input cannot become a free debit. */
|
|
38
|
+
export function documentEmbeddingCostMicros(settings: Settings, inputBytes: number): number {
|
|
39
|
+
if (!Number.isSafeInteger(inputBytes) || inputBytes < 0)
|
|
40
|
+
throw new Error("invalid embedding bytes");
|
|
41
|
+
const amount =
|
|
42
|
+
(BigInt(inputBytes) * BigInt(settings.documentEmbeddingRateMicrosPerMillionBytes) + 999_999n) /
|
|
43
|
+
1_000_000n;
|
|
44
|
+
if (amount > BigInt(Number.MAX_SAFE_INTEGER))
|
|
45
|
+
throw new Error("embedding cost exceeds safe integer");
|
|
46
|
+
return Number(amount);
|
|
47
|
+
}
|
|
48
|
+
|
|
23
49
|
export type LimitCheckInput = {
|
|
24
50
|
accountId: string;
|
|
25
51
|
workspaceId?: string;
|
|
52
|
+
/** Frozen causal human, never the API key or service admitting the work. */
|
|
53
|
+
initiatingHumanSubjectId?: string | null;
|
|
26
54
|
action: LimitAction;
|
|
27
55
|
quantity?: number;
|
|
28
|
-
// The turn's model id, when the action represents an agent turn.
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
// skipped. Non-model infra actions (workspace/api_key/schedule create) leave
|
|
33
|
-
// this undefined and are unaffected.
|
|
56
|
+
// The turn's model id, when the action represents an agent turn. The model's
|
|
57
|
+
// deployment cost controls credit/cost gates; upstream metering independently
|
|
58
|
+
// controls the token cap. Connected subscriptions still require live workspace
|
|
59
|
+
// readiness. Non-model infra actions leave this undefined.
|
|
34
60
|
model?: string | null;
|
|
35
61
|
};
|
|
36
62
|
|
|
63
|
+
export function modelFundingForAdmission(
|
|
64
|
+
settings: Settings,
|
|
65
|
+
model: string | null | undefined,
|
|
66
|
+
codexBilled: boolean,
|
|
67
|
+
): { fundedWithoutCredits: boolean; countsTowardTokenCap: boolean } {
|
|
68
|
+
const resolvedModel = model ? resolveModelProviderForTurn(settings, model)?.model : null;
|
|
69
|
+
const codexSubscriptionModel =
|
|
70
|
+
resolvedModel?.credentialSource.kind === "connected_subscription" &&
|
|
71
|
+
resolvedModel.credentialSource.provider === "codex";
|
|
72
|
+
return {
|
|
73
|
+
// A Codex namespace/definition never bypasses credits by itself: the live
|
|
74
|
+
// workspace credential predicate above remains authoritative. SuperGrok's
|
|
75
|
+
// static overlay is likewise secret-free; its worker/provider admission
|
|
76
|
+
// owns live account selection before any upstream request can occur.
|
|
77
|
+
fundedWithoutCredits:
|
|
78
|
+
codexBilled ||
|
|
79
|
+
(resolvedModel != null && !codexSubscriptionModel && resolvedModel.cost !== "credits"),
|
|
80
|
+
countsTowardTokenCap:
|
|
81
|
+
!codexBilled && resolvedModel != null && resolvedModel.billing.upstreamPayer === "deployment",
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
37
85
|
export async function requireLimit(deps: LimitDependencies, input: LimitCheckInput): Promise<void> {
|
|
38
86
|
const decision = await checkLimit(deps, input);
|
|
39
87
|
if (decision.allowed) {
|
|
40
88
|
return;
|
|
41
89
|
}
|
|
42
|
-
throw new HTTPException(
|
|
43
|
-
|
|
44
|
-
|
|
90
|
+
throw new HTTPException(
|
|
91
|
+
decision.code === "insufficient_credits" || decision.code === "allowance_exhausted" ? 402 : 429,
|
|
92
|
+
{
|
|
93
|
+
message: decision.message,
|
|
94
|
+
cause: decision,
|
|
95
|
+
},
|
|
96
|
+
);
|
|
45
97
|
}
|
|
46
98
|
|
|
47
99
|
export async function checkLimit(
|
|
@@ -59,23 +111,39 @@ export async function checkLimit(
|
|
|
59
111
|
model: input.model,
|
|
60
112
|
})
|
|
61
113
|
: false;
|
|
62
|
-
const
|
|
114
|
+
const { fundedWithoutCredits, countsTowardTokenCap } = modelFundingForAdmission(
|
|
115
|
+
deps.settings,
|
|
116
|
+
input.model,
|
|
117
|
+
codexBilled,
|
|
118
|
+
);
|
|
119
|
+
const creditDecision = await checkCreditBalance(deps, input, fundedWithoutCredits);
|
|
63
120
|
if (!creditDecision.allowed) {
|
|
64
121
|
return creditDecision;
|
|
65
122
|
}
|
|
123
|
+
if (!fundedWithoutCredits && isCostlyAction(input.action) && input.workspaceId) {
|
|
124
|
+
const refusal = await checkWorkspaceAllowance(deps.db, {
|
|
125
|
+
accountId: input.accountId,
|
|
126
|
+
workspaceId: input.workspaceId,
|
|
127
|
+
subjectId: input.initiatingHumanSubjectId ?? null,
|
|
128
|
+
});
|
|
129
|
+
if (refusal) return { allowed: false, ...refusal };
|
|
130
|
+
}
|
|
66
131
|
if (deps.settings.usageLimitsMode !== "static" && deps.settings.usageLimitsMode !== "managed") {
|
|
67
132
|
return { allowed: true };
|
|
68
133
|
}
|
|
69
|
-
return await checkStaticCaps(deps, input,
|
|
134
|
+
return await checkStaticCaps(deps, input, {
|
|
135
|
+
fundedWithoutCredits,
|
|
136
|
+
countsTowardTokenCap,
|
|
137
|
+
});
|
|
70
138
|
}
|
|
71
139
|
|
|
72
140
|
async function checkCreditBalance(
|
|
73
141
|
deps: LimitDependencies,
|
|
74
142
|
input: LimitCheckInput,
|
|
75
|
-
|
|
143
|
+
externallyBilled: boolean,
|
|
76
144
|
): Promise<LimitDecision> {
|
|
77
|
-
if (
|
|
78
|
-
return { allowed: true }; // paid
|
|
145
|
+
if (externallyBilled) {
|
|
146
|
+
return { allowed: true }; // paid outside OpenGeni — zero OpenGeni credits
|
|
79
147
|
}
|
|
80
148
|
if (!usesCreditLimits(deps) || !isCostlyAction(input.action)) {
|
|
81
149
|
return { allowed: true };
|
|
@@ -84,16 +152,20 @@ async function checkCreditBalance(
|
|
|
84
152
|
if (balance.balanceMicros > 0) {
|
|
85
153
|
return { allowed: true };
|
|
86
154
|
}
|
|
87
|
-
return { allowed: false, code: "insufficient_credits", message: "insufficient
|
|
155
|
+
return { allowed: false, code: "insufficient_credits", message: "insufficient Opengeni credits" };
|
|
88
156
|
}
|
|
89
157
|
|
|
90
158
|
async function checkStaticCaps(
|
|
91
159
|
deps: LimitDependencies,
|
|
92
160
|
input: LimitCheckInput,
|
|
93
|
-
|
|
161
|
+
funding: { fundedWithoutCredits: boolean; countsTowardTokenCap: boolean },
|
|
94
162
|
): Promise<LimitDecision> {
|
|
95
163
|
const limits = configuredStaticUsageLimits(deps.settings);
|
|
96
|
-
if (
|
|
164
|
+
if (
|
|
165
|
+
limits.maxMonthlyCostMicrosPerAccount &&
|
|
166
|
+
isCostlyAction(input.action) &&
|
|
167
|
+
!funding.fundedWithoutCredits
|
|
168
|
+
) {
|
|
97
169
|
const used = await sumUsageQuantity(deps.db, {
|
|
98
170
|
accountId: input.accountId,
|
|
99
171
|
eventType: "model.cost",
|
|
@@ -120,10 +192,12 @@ async function checkStaticCaps(
|
|
|
120
192
|
);
|
|
121
193
|
}
|
|
122
194
|
case "api_key:create": {
|
|
123
|
-
if (!limits.maxApiKeysPerWorkspace
|
|
195
|
+
if (!limits.maxApiKeysPerWorkspace) {
|
|
124
196
|
return { allowed: true };
|
|
125
197
|
}
|
|
126
|
-
const count =
|
|
198
|
+
const count = input.workspaceId
|
|
199
|
+
? await countActiveApiKeysForWorkspace(deps.db, input.workspaceId)
|
|
200
|
+
: await countActiveOrganizationApiKeysForAccount(deps.db, input.accountId);
|
|
127
201
|
return count < limits.maxApiKeysPerWorkspace
|
|
128
202
|
? { allowed: true }
|
|
129
203
|
: blocked(
|
|
@@ -172,7 +246,11 @@ async function checkStaticCaps(
|
|
|
172
246
|
);
|
|
173
247
|
}
|
|
174
248
|
case "tokens:consume": {
|
|
175
|
-
if (
|
|
249
|
+
if (
|
|
250
|
+
!funding.countsTowardTokenCap ||
|
|
251
|
+
!limits.maxMonthlyTokensPerWorkspace ||
|
|
252
|
+
!input.workspaceId
|
|
253
|
+
) {
|
|
176
254
|
return { allowed: true };
|
|
177
255
|
}
|
|
178
256
|
const used = await sumUsageQuantity(deps.db, {
|
|
@@ -253,9 +331,10 @@ function usesCreditLimits(deps: LimitDependencies): boolean {
|
|
|
253
331
|
}
|
|
254
332
|
|
|
255
333
|
function isCostlyAction(action: LimitAction): boolean {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
334
|
+
// Document admission retains the monthly chunk quota above, but parsing and
|
|
335
|
+
// source retention do not consume embedding credits. The worker admits paid
|
|
336
|
+
// embedding separately and settles its actual provider input after use.
|
|
337
|
+
return action === "agent_run:create" || action === "tokens:consume";
|
|
259
338
|
}
|
|
260
339
|
|
|
261
340
|
function blocked(code: string, message: string): LimitDecision {
|
|
@@ -3,6 +3,13 @@ import type { Context } from "hono";
|
|
|
3
3
|
import { HTTPException } from "hono/http-exception";
|
|
4
4
|
import type { ManagedAuth } from "./managed-auth-type";
|
|
5
5
|
import { getManagedSession } from "./managed-session";
|
|
6
|
+
export {
|
|
7
|
+
getManagedAuthRequestActorAdmissionStamp,
|
|
8
|
+
getManagedAuthRequestActorLeaseStamp,
|
|
9
|
+
markManagedAuthRequestActorTransitionApplied,
|
|
10
|
+
} from "./managed-session";
|
|
11
|
+
import type { ManagedAuthSessionAdapter } from "./managed-auth-session-sets";
|
|
12
|
+
import type { ManagedAuthSessionSetMode } from "@opengeni/contracts/managed-auth-session-sets";
|
|
6
13
|
|
|
7
14
|
export type CanonicalHumanRequestIdentity = {
|
|
8
15
|
authUserId: string;
|
|
@@ -11,17 +18,29 @@ export type CanonicalHumanRequestIdentity = {
|
|
|
11
18
|
|
|
12
19
|
export async function requireCanonicalHumanRequestIdentity(
|
|
13
20
|
context: Context,
|
|
14
|
-
input: {
|
|
21
|
+
input: {
|
|
22
|
+
db: Database;
|
|
23
|
+
managedAuth?: ManagedAuth | null | undefined;
|
|
24
|
+
managedAuthSessionAdapter?: ManagedAuthSessionAdapter | null | undefined;
|
|
25
|
+
managedAuthSessionSetMode?: ManagedAuthSessionSetMode | undefined;
|
|
26
|
+
allowRecovery?: boolean | undefined;
|
|
27
|
+
},
|
|
15
28
|
): Promise<CanonicalHumanRequestIdentity> {
|
|
16
29
|
if (!input.managedAuth) {
|
|
17
|
-
throw new HTTPException(404, {
|
|
30
|
+
throw new HTTPException(404, {
|
|
31
|
+
message: "Canonical human identity is unavailable",
|
|
32
|
+
});
|
|
18
33
|
}
|
|
19
34
|
const session = await getManagedSession(context, input.managedAuth, {
|
|
20
35
|
db: input.db,
|
|
36
|
+
sessionAdapter: input.managedAuthSessionAdapter,
|
|
37
|
+
sessionSetMode: input.managedAuthSessionSetMode,
|
|
21
38
|
...(input.allowRecovery === undefined ? {} : { allowIdentityRecovery: input.allowRecovery }),
|
|
22
39
|
});
|
|
23
40
|
if (!session?.user || typeof session.session?.id !== "string") {
|
|
24
|
-
throw new HTTPException(401, {
|
|
41
|
+
throw new HTTPException(401, {
|
|
42
|
+
message: "Managed human authentication required",
|
|
43
|
+
});
|
|
25
44
|
}
|
|
26
45
|
return { authUserId: session.user.id, authSessionId: session.session.id };
|
|
27
46
|
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { CODEX_CLIENT_VERSION, fetchCodexModels } from "@opengeni/codex";
|
|
2
|
+
import { configuredModels, withCodexCatalogProvider, type Settings } from "@opengeni/config";
|
|
3
|
+
import {
|
|
4
|
+
buildCodexTokenResolver,
|
|
5
|
+
connectionModelAllowed,
|
|
6
|
+
getCodexRotationSettings,
|
|
7
|
+
listCodexAccountStatuses,
|
|
8
|
+
loadCodexCredentialForRun,
|
|
9
|
+
type Database,
|
|
10
|
+
} from "@opengeni/db";
|
|
11
|
+
import type { ModelAvailabilityObservation } from "./model-catalog";
|
|
12
|
+
|
|
13
|
+
const CATALOG_CACHE_MS = 60_000;
|
|
14
|
+
const CATALOG_ERROR_CACHE_MS = 5_000;
|
|
15
|
+
type CatalogCacheEntry = {
|
|
16
|
+
expiresAt: number;
|
|
17
|
+
result: Promise<{ ok: boolean; slugs: string[]; checkedAt: string }>;
|
|
18
|
+
};
|
|
19
|
+
const catalogs = new Map<string, CatalogCacheEntry>();
|
|
20
|
+
|
|
21
|
+
type Dependencies = {
|
|
22
|
+
listAccounts: typeof listCodexAccountStatuses;
|
|
23
|
+
getRotation: typeof getCodexRotationSettings;
|
|
24
|
+
loadCredential: typeof loadCodexCredentialForRun;
|
|
25
|
+
fetchModels: typeof fetchCodexModels;
|
|
26
|
+
getToken: (
|
|
27
|
+
db: Database,
|
|
28
|
+
settings: Settings,
|
|
29
|
+
workspaceId: string,
|
|
30
|
+
credentialId: string,
|
|
31
|
+
) => ReturnType<ReturnType<typeof buildCodexTokenResolver>["getToken"]>;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/** Live provider support is separate from deployment membership and model permissions. */
|
|
35
|
+
export async function loadWorkspaceCodexModelAvailability(
|
|
36
|
+
db: Database,
|
|
37
|
+
settings: Settings,
|
|
38
|
+
workspaceId: string,
|
|
39
|
+
deps: Dependencies = {
|
|
40
|
+
listAccounts: listCodexAccountStatuses,
|
|
41
|
+
getRotation: getCodexRotationSettings,
|
|
42
|
+
loadCredential: loadCodexCredentialForRun,
|
|
43
|
+
fetchModels: fetchCodexModels,
|
|
44
|
+
getToken: (targetDb, targetSettings, targetWorkspaceId, credentialId) =>
|
|
45
|
+
buildCodexTokenResolver(targetDb, targetSettings, targetWorkspaceId, credentialId).getToken(),
|
|
46
|
+
},
|
|
47
|
+
): Promise<Record<string, ModelAvailabilityObservation>> {
|
|
48
|
+
if (!settings.codexSubscriptionEnabled) return {};
|
|
49
|
+
const [accounts, rotation] = await Promise.all([
|
|
50
|
+
deps.listAccounts(db, workspaceId),
|
|
51
|
+
deps.getRotation(db, workspaceId),
|
|
52
|
+
]);
|
|
53
|
+
const candidates = accounts.filter(
|
|
54
|
+
(account) =>
|
|
55
|
+
account.status === "active" &&
|
|
56
|
+
account.allocatorEnabled &&
|
|
57
|
+
(rotation?.rotationEnabled || account.isActive),
|
|
58
|
+
);
|
|
59
|
+
const live = await Promise.all(
|
|
60
|
+
candidates.map(async (account) => {
|
|
61
|
+
const unavailable = () => ({
|
|
62
|
+
account,
|
|
63
|
+
ok: false,
|
|
64
|
+
slugs: [] as string[],
|
|
65
|
+
checkedAt: new Date().toISOString(),
|
|
66
|
+
});
|
|
67
|
+
try {
|
|
68
|
+
// Recheck current workspace/source authority before consulting the cache.
|
|
69
|
+
const credential = await deps.loadCredential(db, settings, workspaceId, account.id);
|
|
70
|
+
if (!credential || credential.status !== "active") return unavailable();
|
|
71
|
+
let key = `${workspaceId}:${credential.id}:${credential.version}`;
|
|
72
|
+
let cached = catalogs.get(key);
|
|
73
|
+
if (!cached || cached.expiresAt <= Date.now()) {
|
|
74
|
+
const token = await deps.getToken(db, settings, workspaceId, credential.id);
|
|
75
|
+
key = `${workspaceId}:${credential.id}:${token.credentialVersion}`;
|
|
76
|
+
cached = catalogs.get(key);
|
|
77
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
78
|
+
return { account, ...(await cached.result) };
|
|
79
|
+
}
|
|
80
|
+
for (const [id, entry] of catalogs) {
|
|
81
|
+
if (entry.expiresAt <= Date.now()) catalogs.delete(id);
|
|
82
|
+
}
|
|
83
|
+
if (catalogs.size >= 1_024) catalogs.delete(catalogs.keys().next().value!);
|
|
84
|
+
const entry: CatalogCacheEntry = {
|
|
85
|
+
expiresAt: Date.now() + CATALOG_CACHE_MS,
|
|
86
|
+
result: deps
|
|
87
|
+
.fetchModels({
|
|
88
|
+
accessToken: token.accessToken,
|
|
89
|
+
chatgptAccountId: token.chatgptAccountId,
|
|
90
|
+
isFedramp: token.isFedramp,
|
|
91
|
+
clientVersion: CODEX_CLIENT_VERSION,
|
|
92
|
+
})
|
|
93
|
+
.catch(() => ({ ok: false, slugs: [] as string[] }))
|
|
94
|
+
.then((result) => {
|
|
95
|
+
entry.expiresAt =
|
|
96
|
+
Date.now() + (result.ok ? CATALOG_CACHE_MS : CATALOG_ERROR_CACHE_MS);
|
|
97
|
+
return { ok: result.ok, slugs: result.slugs, checkedAt: new Date().toISOString() };
|
|
98
|
+
}),
|
|
99
|
+
};
|
|
100
|
+
catalogs.set(key, entry);
|
|
101
|
+
cached = entry;
|
|
102
|
+
}
|
|
103
|
+
return { account, ...(await cached.result) };
|
|
104
|
+
} catch {
|
|
105
|
+
return unavailable();
|
|
106
|
+
}
|
|
107
|
+
}),
|
|
108
|
+
);
|
|
109
|
+
return Object.fromEntries(
|
|
110
|
+
configuredModels(withCodexCatalogProvider(settings))
|
|
111
|
+
.filter(
|
|
112
|
+
(model) =>
|
|
113
|
+
model.credentialSource.kind === "connected_subscription" &&
|
|
114
|
+
model.credentialSource.provider === "codex",
|
|
115
|
+
)
|
|
116
|
+
.map((model) => {
|
|
117
|
+
// Support and permission must hold on the SAME serving account.
|
|
118
|
+
const permitted = live.filter(({ account }) =>
|
|
119
|
+
connectionModelAllowed(account.allowedModelIds, model.id),
|
|
120
|
+
);
|
|
121
|
+
// The allocator may choose any permitted candidate, so all must support it.
|
|
122
|
+
const supported =
|
|
123
|
+
permitted.length > 0 &&
|
|
124
|
+
permitted.every(({ ok, slugs }) => ok && slugs.includes(model.upstreamModelId));
|
|
125
|
+
const uncertain = permitted.find(({ ok }) => !ok);
|
|
126
|
+
return [
|
|
127
|
+
model.definitionVersion,
|
|
128
|
+
{
|
|
129
|
+
status: supported ? "available" : "unavailable",
|
|
130
|
+
reason: supported ? null : uncertain ? "provider_unhealthy" : "not_entitled",
|
|
131
|
+
checkedAt: (uncertain ?? permitted[0])?.checkedAt ?? new Date().toISOString(),
|
|
132
|
+
} satisfies ModelAvailabilityObservation,
|
|
133
|
+
];
|
|
134
|
+
}),
|
|
135
|
+
);
|
|
136
|
+
}
|