@opengeni/core 0.4.6 → 0.4.8
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/index.d.ts +293 -32
- package/dist/index.js +1750 -472
- package/dist/index.js.map +1 -1
- package/package.json +17 -17
- package/src/access/index.ts +136 -57
- package/src/application/session-commands.ts +527 -0
- package/src/billing/limits.ts +76 -34
- package/src/dependencies.ts +67 -14
- package/src/domain/capabilities.ts +380 -181
- package/src/domain/environments.ts +58 -38
- package/src/domain/packs.ts +49 -16
- package/src/domain/resources.ts +71 -28
- package/src/domain/scheduled-tasks.ts +107 -41
- package/src/domain/sessions.ts +531 -255
- package/src/domain/workspace-members.ts +6 -2
- package/src/index.ts +3 -0
- package/src/rigs/index.ts +540 -0
- package/src/sandbox/fleet.ts +97 -18
- package/src/sandbox/routing.ts +6 -1
- package/src/sandbox-types.ts +17 -5
- package/src/workflow-wake-contract.ts +6 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/core",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.8",
|
|
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": {
|
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
"url": "git+https://github.com/Cloudgeni-ai/opengeni.git",
|
|
9
9
|
"directory": "packages/core"
|
|
10
10
|
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"src"
|
|
14
|
+
],
|
|
11
15
|
"type": "module",
|
|
12
16
|
"sideEffects": false,
|
|
13
17
|
"main": "./dist/index.js",
|
|
@@ -19,33 +23,29 @@
|
|
|
19
23
|
"import": "./dist/index.js"
|
|
20
24
|
}
|
|
21
25
|
},
|
|
22
|
-
"files": [
|
|
23
|
-
"dist",
|
|
24
|
-
"src"
|
|
25
|
-
],
|
|
26
|
-
"engines": {
|
|
27
|
-
"node": ">=18"
|
|
28
|
-
},
|
|
29
26
|
"publishConfig": {
|
|
30
27
|
"access": "public",
|
|
31
28
|
"provenance": true
|
|
32
29
|
},
|
|
33
30
|
"scripts": {
|
|
34
|
-
"typecheck": "
|
|
31
|
+
"typecheck": "tsgo --noEmit",
|
|
35
32
|
"build": "tsup",
|
|
36
33
|
"prepublishOnly": "bash ../../scripts/prepublish-guard"
|
|
37
34
|
},
|
|
38
35
|
"dependencies": {
|
|
39
36
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
40
|
-
"@opengeni/codex": "^0.2.
|
|
41
|
-
"@opengeni/config": "^0.
|
|
42
|
-
"@opengeni/contracts": "^0.
|
|
43
|
-
"@opengeni/db": "^0.
|
|
44
|
-
"@opengeni/documents": "^0.2.
|
|
45
|
-
"@opengeni/events": "^0.
|
|
37
|
+
"@opengeni/codex": "^0.2.2",
|
|
38
|
+
"@opengeni/config": "^0.5.0",
|
|
39
|
+
"@opengeni/contracts": "^0.10.0",
|
|
40
|
+
"@opengeni/db": "^0.7.1",
|
|
41
|
+
"@opengeni/documents": "^0.2.10",
|
|
42
|
+
"@opengeni/events": "^0.3.1",
|
|
46
43
|
"@opengeni/observability": "^0.3.0",
|
|
47
|
-
"@opengeni/runtime": "^0.
|
|
48
|
-
"@opengeni/storage": "^0.2.
|
|
44
|
+
"@opengeni/runtime": "^0.7.0",
|
|
45
|
+
"@opengeni/storage": "^0.2.9",
|
|
49
46
|
"hono": "^4.12.18"
|
|
47
|
+
},
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=18"
|
|
50
50
|
}
|
|
51
51
|
}
|
package/src/access/index.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { Settings } from "@opengeni/config";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
verifyDelegatedAccessToken,
|
|
4
|
+
type AccessContext,
|
|
5
|
+
type AccessGrant,
|
|
6
|
+
type Permission,
|
|
7
|
+
} from "@opengeni/contracts";
|
|
3
8
|
import {
|
|
4
9
|
bootstrapWorkspace,
|
|
5
10
|
ensureManagedAccessForUser,
|
|
@@ -28,10 +33,16 @@ export async function requireAccessContext(c: Context, deps: AccessDeps): Promis
|
|
|
28
33
|
return context;
|
|
29
34
|
}
|
|
30
35
|
|
|
31
|
-
export async function requireAccessGrant(
|
|
36
|
+
export async function requireAccessGrant(
|
|
37
|
+
c: Context,
|
|
38
|
+
deps: AccessDeps,
|
|
39
|
+
workspaceId: string,
|
|
40
|
+
permission?: Permission,
|
|
41
|
+
): Promise<AccessGrant> {
|
|
32
42
|
const context = await requireAccessContext(c, deps);
|
|
33
|
-
const grant =
|
|
34
|
-
|
|
43
|
+
const grant =
|
|
44
|
+
context.workspaceGrants.find((candidate) => candidate.workspaceId === workspaceId) ??
|
|
45
|
+
(await getWorkspaceGrant(deps.db, context.subjectId, workspaceId));
|
|
35
46
|
if (!grant) {
|
|
36
47
|
const workspace = await requireWorkspace(deps.db, workspaceId).catch(() => null);
|
|
37
48
|
if (!workspace) {
|
|
@@ -47,12 +58,30 @@ export async function requireAccessGrant(c: Context, deps: AccessDeps, workspace
|
|
|
47
58
|
|
|
48
59
|
export function requirePermission(grant: AccessGrant, permission: Permission): void {
|
|
49
60
|
if (!hasPermission(grant.permissions, permission)) {
|
|
61
|
+
if (permission === "variable-sets:use") {
|
|
62
|
+
throw new HTTPException(403, {
|
|
63
|
+
message: "missing permission: variable-sets:use (deprecated alias: environments:use)",
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
if (permission === "variable-sets:manage") {
|
|
67
|
+
throw new HTTPException(403, {
|
|
68
|
+
message: "missing permission: variable-sets:manage (deprecated alias: environments:manage)",
|
|
69
|
+
});
|
|
70
|
+
}
|
|
50
71
|
throw new HTTPException(403, { message: `missing permission: ${permission}` });
|
|
51
72
|
}
|
|
52
73
|
}
|
|
53
74
|
|
|
54
75
|
export function hasPermission(permissions: Permission[], permission: Permission): boolean {
|
|
55
|
-
|
|
76
|
+
const aliases: Partial<Record<Permission, Permission[]>> = {
|
|
77
|
+
"variable-sets:use": ["environments:use" as Permission],
|
|
78
|
+
"variable-sets:manage": ["environments:manage" as Permission],
|
|
79
|
+
};
|
|
80
|
+
return (
|
|
81
|
+
permissions.includes(permission) ||
|
|
82
|
+
(aliases[permission]?.some((alias) => permissions.includes(alias)) ?? false) ||
|
|
83
|
+
permissions.includes("workspace:admin")
|
|
84
|
+
);
|
|
56
85
|
}
|
|
57
86
|
|
|
58
87
|
async function resolveAccessContext(c: Context, deps: AccessDeps): Promise<AccessContext | null> {
|
|
@@ -69,16 +98,20 @@ async function resolveAccessContext(c: Context, deps: AccessDeps): Promise<Acces
|
|
|
69
98
|
});
|
|
70
99
|
}
|
|
71
100
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
101
|
+
if (deps.settings.productAccessMode === "configured") {
|
|
102
|
+
const delegated = await delegatedAccessContext(c, deps, "configured");
|
|
103
|
+
if (delegated) {
|
|
104
|
+
return delegated;
|
|
105
|
+
}
|
|
106
|
+
const apiKey = await apiKeyAccessContext(c, deps, "configured");
|
|
107
|
+
if (apiKey) {
|
|
108
|
+
return apiKey;
|
|
109
|
+
}
|
|
110
|
+
if (deps.settings.delegationSecret) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
return await bootstrapWorkspace(deps.db, {
|
|
114
|
+
accountExternalSource: "opengeni:configured",
|
|
82
115
|
accountExternalId: "default",
|
|
83
116
|
accountName: "Configured",
|
|
84
117
|
workspaceExternalSource: "opengeni:configured",
|
|
@@ -95,31 +128,9 @@ async function resolveAccessContext(c: Context, deps: AccessDeps): Promise<Acces
|
|
|
95
128
|
if (delegated) {
|
|
96
129
|
return delegated;
|
|
97
130
|
}
|
|
98
|
-
const apiKey = await
|
|
131
|
+
const apiKey = await apiKeyAccessContext(c, deps, "managed");
|
|
99
132
|
if (apiKey) {
|
|
100
|
-
|
|
101
|
-
? apiKey.permissions.filter((permission) => permission === "billing:read" || permission === "billing:manage")
|
|
102
|
-
: apiKey.permissions;
|
|
103
|
-
return {
|
|
104
|
-
mode: "managed",
|
|
105
|
-
subjectId: `api_key:${apiKey.id}`,
|
|
106
|
-
subjectLabel: apiKey.name,
|
|
107
|
-
accountGrants: [{
|
|
108
|
-
accountId: apiKey.accountId,
|
|
109
|
-
subjectId: `api_key:${apiKey.id}`,
|
|
110
|
-
subjectLabel: apiKey.name,
|
|
111
|
-
permissions: accountPermissions,
|
|
112
|
-
}],
|
|
113
|
-
workspaceGrants: apiKey.workspaceId ? [{
|
|
114
|
-
workspaceId: apiKey.workspaceId,
|
|
115
|
-
accountId: apiKey.accountId,
|
|
116
|
-
subjectId: `api_key:${apiKey.id}`,
|
|
117
|
-
subjectLabel: apiKey.name,
|
|
118
|
-
permissions: apiKey.permissions,
|
|
119
|
-
}] : [],
|
|
120
|
-
defaultAccountId: apiKey.accountId,
|
|
121
|
-
defaultWorkspaceId: apiKey.workspaceId,
|
|
122
|
-
} satisfies AccessContext;
|
|
133
|
+
return apiKey;
|
|
123
134
|
}
|
|
124
135
|
}
|
|
125
136
|
|
|
@@ -137,7 +148,59 @@ async function resolveAccessContext(c: Context, deps: AccessDeps): Promise<Acces
|
|
|
137
148
|
return null;
|
|
138
149
|
}
|
|
139
150
|
|
|
140
|
-
async function
|
|
151
|
+
async function apiKeyAccessContext(
|
|
152
|
+
c: Context,
|
|
153
|
+
deps: AccessDeps,
|
|
154
|
+
mode: "configured" | "managed",
|
|
155
|
+
): Promise<AccessContext | null> {
|
|
156
|
+
const bearer = bearerToken(c);
|
|
157
|
+
if (!bearer) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
const apiKey = await findActiveApiKeyByHash(deps.db, await sha256Hex(bearer));
|
|
161
|
+
if (!apiKey) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
const subjectId = `api_key:${apiKey.id}`;
|
|
165
|
+
const accountPermissions = apiKey.workspaceId
|
|
166
|
+
? apiKey.permissions.filter(
|
|
167
|
+
(permission) => permission === "billing:read" || permission === "billing:manage",
|
|
168
|
+
)
|
|
169
|
+
: apiKey.permissions;
|
|
170
|
+
return {
|
|
171
|
+
mode,
|
|
172
|
+
subjectId,
|
|
173
|
+
subjectLabel: apiKey.name,
|
|
174
|
+
accountGrants: [
|
|
175
|
+
{
|
|
176
|
+
accountId: apiKey.accountId,
|
|
177
|
+
subjectId,
|
|
178
|
+
subjectLabel: apiKey.name,
|
|
179
|
+
permissions: accountPermissions,
|
|
180
|
+
},
|
|
181
|
+
],
|
|
182
|
+
workspaceGrants: apiKey.workspaceId
|
|
183
|
+
? [
|
|
184
|
+
{
|
|
185
|
+
workspaceId: apiKey.workspaceId,
|
|
186
|
+
accountId: apiKey.accountId,
|
|
187
|
+
subjectId,
|
|
188
|
+
subjectLabel: apiKey.name,
|
|
189
|
+
permissions: apiKey.permissions,
|
|
190
|
+
},
|
|
191
|
+
]
|
|
192
|
+
: [],
|
|
193
|
+
defaultAccountId: apiKey.accountId,
|
|
194
|
+
defaultWorkspaceId: apiKey.workspaceId,
|
|
195
|
+
} satisfies AccessContext;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function delegatedAccessContext(
|
|
199
|
+
c: Context,
|
|
200
|
+
deps: AccessDeps,
|
|
201
|
+
mode: "configured" | "managed",
|
|
202
|
+
token = bearerToken(c),
|
|
203
|
+
): Promise<AccessContext | null> {
|
|
141
204
|
if (!token || !deps.settings.delegationSecret) {
|
|
142
205
|
return null;
|
|
143
206
|
}
|
|
@@ -149,22 +212,36 @@ async function delegatedAccessContext(c: Context, deps: AccessDeps, mode: "confi
|
|
|
149
212
|
mode,
|
|
150
213
|
subjectId: payload.subjectId,
|
|
151
214
|
...(payload.subjectLabel ? { subjectLabel: payload.subjectLabel } : {}),
|
|
152
|
-
accountGrants: [
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
215
|
+
accountGrants: [
|
|
216
|
+
{
|
|
217
|
+
accountId: payload.accountId,
|
|
218
|
+
subjectId: payload.subjectId,
|
|
219
|
+
...(payload.subjectLabel ? { subjectLabel: payload.subjectLabel } : {}),
|
|
220
|
+
permissions: payload.permissions,
|
|
221
|
+
},
|
|
222
|
+
],
|
|
223
|
+
workspaceGrants: [
|
|
224
|
+
{
|
|
225
|
+
workspaceId: payload.workspaceId,
|
|
226
|
+
accountId: payload.accountId,
|
|
227
|
+
subjectId: payload.subjectId,
|
|
228
|
+
...(payload.subjectLabel ? { subjectLabel: payload.subjectLabel } : {}),
|
|
229
|
+
permissions: payload.permissions,
|
|
230
|
+
// sessionId is worker-asserted (HMAC-signed token claim), not agent
|
|
231
|
+
// controlled; it scopes session-bound MCP tools such as goal management.
|
|
232
|
+
metadata: {
|
|
233
|
+
delegated: true,
|
|
234
|
+
...(payload.sessionId ? { sessionId: payload.sessionId } : {}),
|
|
235
|
+
// Caller identity: the turn that minted this token. Tools classify the
|
|
236
|
+
// CALLER from this instead of re-reading the live active pointer.
|
|
237
|
+
...(payload.turnId ? { turnId: payload.turnId } : {}),
|
|
238
|
+
...(payload.attemptId ? { attemptId: payload.attemptId } : {}),
|
|
239
|
+
...(payload.executionGeneration
|
|
240
|
+
? { executionGeneration: payload.executionGeneration }
|
|
241
|
+
: {}),
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
],
|
|
168
245
|
defaultAccountId: payload.accountId,
|
|
169
246
|
defaultWorkspaceId: payload.workspaceId,
|
|
170
247
|
};
|
|
@@ -182,5 +259,7 @@ function bearerToken(c: Context): string | null {
|
|
|
182
259
|
|
|
183
260
|
async function sha256Hex(value: string): Promise<string> {
|
|
184
261
|
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value));
|
|
185
|
-
return Array.from(new Uint8Array(digest))
|
|
262
|
+
return Array.from(new Uint8Array(digest))
|
|
263
|
+
.map((byte) => byte.toString(16).padStart(2, "0"))
|
|
264
|
+
.join("");
|
|
186
265
|
}
|