@opengeni/core 2.7.2-canary.0 → 2.7.5-canary.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/access/index.d.ts +10 -0
- package/dist/application/api-integration-servers.d.ts +20 -0
- package/dist/domain/pr-review.d.ts +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +152 -11
- package/dist/index.js.map +1 -1
- package/package.json +12 -10
- package/src/access/index.ts +25 -0
- package/src/application/api-integration-servers.ts +215 -0
- package/src/application/session-commands.ts +2 -2
- package/src/index.ts +1 -0
- package/src/sandbox/routing.ts +6 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/core",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.5-canary.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": {
|
|
@@ -54,15 +54,17 @@
|
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
57
|
-
"@opengeni/
|
|
58
|
-
"@opengeni/
|
|
59
|
-
"@opengeni/
|
|
60
|
-
"@opengeni/
|
|
61
|
-
"@opengeni/
|
|
62
|
-
"@opengeni/
|
|
63
|
-
"@opengeni/
|
|
64
|
-
"@opengeni/
|
|
65
|
-
"@opengeni/
|
|
57
|
+
"@opengeni/capabilities": "^0.3.2-canary.0",
|
|
58
|
+
"@opengeni/codex": "^0.2.21-canary.0",
|
|
59
|
+
"@opengeni/config": "^1.0.0-canary.0",
|
|
60
|
+
"@opengeni/contracts": "^2.13.0-canary.0",
|
|
61
|
+
"@opengeni/db": "^4.0.0-canary.0",
|
|
62
|
+
"@opengeni/documents": "^0.8.19-canary.0",
|
|
63
|
+
"@opengeni/events": "^0.4.17-canary.0",
|
|
64
|
+
"@opengeni/network": "^0.3.0-canary.0",
|
|
65
|
+
"@opengeni/observability": "^0.8.19-canary.0",
|
|
66
|
+
"@opengeni/runtime": "^2.3.0-canary.0",
|
|
67
|
+
"@opengeni/storage": "^0.2.120-canary.0",
|
|
66
68
|
"hono": "^4.12.18",
|
|
67
69
|
"zod": "^4.2.1"
|
|
68
70
|
},
|
package/src/access/index.ts
CHANGED
|
@@ -243,6 +243,31 @@ export function requireAccountAdminAuthorizationStamp(
|
|
|
243
243
|
});
|
|
244
244
|
}
|
|
245
245
|
|
|
246
|
+
/**
|
|
247
|
+
* Verify that an access authorization was minted by the canonical request
|
|
248
|
+
* resolver for this exact subject and workspace.
|
|
249
|
+
*
|
|
250
|
+
* This is the protocol-neutral boundary for request-local services that need
|
|
251
|
+
* the authenticated grant rather than a caller-supplied grant-shaped object.
|
|
252
|
+
* Object identity is intentional: matching fields alone are not proof that the
|
|
253
|
+
* request authenticated the named subject.
|
|
254
|
+
*/
|
|
255
|
+
export function requireResolvedAccessGrantAuthorization(
|
|
256
|
+
authorization: AccessGrantAuthorization,
|
|
257
|
+
workspaceId: string,
|
|
258
|
+
): AccessGrant {
|
|
259
|
+
const { grant } = authorization;
|
|
260
|
+
if (
|
|
261
|
+
!resolvedAccessGrantAuthorizations.has(authorization) ||
|
|
262
|
+
!authorization.contextIntegrity ||
|
|
263
|
+
authorization.authenticatedSubjectId !== grant.subjectId ||
|
|
264
|
+
grant.workspaceId !== workspaceId
|
|
265
|
+
) {
|
|
266
|
+
throw new HTTPException(403, { message: "workspace access authorization is invalid" });
|
|
267
|
+
}
|
|
268
|
+
return grant;
|
|
269
|
+
}
|
|
270
|
+
|
|
246
271
|
/**
|
|
247
272
|
* Resolve the exact built-in single-user local administrator for an account.
|
|
248
273
|
*
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createGraphqlMcpServer,
|
|
3
|
+
createOpenApiMcpServer,
|
|
4
|
+
createPinnedIntegrationTransport,
|
|
5
|
+
directIntegrationTransport,
|
|
6
|
+
IntegrationInvocationError,
|
|
7
|
+
type IntegrationCredentialResolver,
|
|
8
|
+
type IntegrationInvocationAuthority,
|
|
9
|
+
type IntegrationTransport,
|
|
10
|
+
} from "@opengeni/capabilities";
|
|
11
|
+
import type { Settings } from "@opengeni/config";
|
|
12
|
+
import type { ToolAuthNeededPayload } from "@opengeni/contracts";
|
|
13
|
+
import type {
|
|
14
|
+
ApiIntegrationRuntime,
|
|
15
|
+
ResolveConnectionCredentialInput,
|
|
16
|
+
ResolveConnectionCredentialResult,
|
|
17
|
+
} from "@opengeni/db";
|
|
18
|
+
import type { FetchLike } from "@opengeni/network";
|
|
19
|
+
import type { LocalMcpServerRegistration } from "@opengeni/runtime";
|
|
20
|
+
|
|
21
|
+
export type BuildApiIntegrationServersInput = {
|
|
22
|
+
settings: Settings;
|
|
23
|
+
integrations: readonly ApiIntegrationRuntime[];
|
|
24
|
+
authority: Omit<IntegrationInvocationAuthority, "connectionRef">;
|
|
25
|
+
resolveCredential: (
|
|
26
|
+
input: ResolveConnectionCredentialInput,
|
|
27
|
+
) => Promise<ResolveConnectionCredentialResult>;
|
|
28
|
+
onAuthNeeded?: (payload: ToolAuthNeededPayload) => Promise<void> | void;
|
|
29
|
+
fetchImpl?: FetchLike;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Compile active persisted API facets into ordinary in-process MCP providers.
|
|
34
|
+
* The caller supplies its authority resolver, so agent attempts and current
|
|
35
|
+
* humans use the same provider assembly without sharing credentials or policy.
|
|
36
|
+
*/
|
|
37
|
+
export function buildApiIntegrationMcpServers(
|
|
38
|
+
input: BuildApiIntegrationServersInput,
|
|
39
|
+
): LocalMcpServerRegistration[] {
|
|
40
|
+
const transport = createPinnedIntegrationTransport({
|
|
41
|
+
network: input.settings,
|
|
42
|
+
...(input.fetchImpl ? { fetchImpl: input.fetchImpl } : {}),
|
|
43
|
+
});
|
|
44
|
+
return input.integrations.map((integration) => {
|
|
45
|
+
const authority: IntegrationInvocationAuthority = {
|
|
46
|
+
...input.authority,
|
|
47
|
+
...(integration.connectionRef?.connectionId
|
|
48
|
+
? { connectionRef: integration.connectionRef.connectionId }
|
|
49
|
+
: {}),
|
|
50
|
+
};
|
|
51
|
+
const credentialResolver = integration.connectionRef
|
|
52
|
+
? integrationCredentialResolver(input, integration, "execution")
|
|
53
|
+
: undefined;
|
|
54
|
+
const buildServer = (
|
|
55
|
+
serverTransport: IntegrationTransport,
|
|
56
|
+
serverCredentialResolver = credentialResolver,
|
|
57
|
+
) =>
|
|
58
|
+
integration.revision.protocol === "openapi"
|
|
59
|
+
? createOpenApiMcpServer({
|
|
60
|
+
revision: integration.revision,
|
|
61
|
+
transport: serverTransport,
|
|
62
|
+
authority,
|
|
63
|
+
...(serverCredentialResolver ? { credentialResolver: serverCredentialResolver } : {}),
|
|
64
|
+
})
|
|
65
|
+
: createGraphqlMcpServer({
|
|
66
|
+
revision: integration.revision,
|
|
67
|
+
endpoint: integration.baseUrl,
|
|
68
|
+
transport: serverTransport,
|
|
69
|
+
authority,
|
|
70
|
+
...(serverCredentialResolver ? { credentialResolver: serverCredentialResolver } : {}),
|
|
71
|
+
});
|
|
72
|
+
const server = buildServer(transport);
|
|
73
|
+
const preflightCredentialResolver = integration.connectionRef
|
|
74
|
+
? integrationCredentialResolver(input, integration, "preflight")
|
|
75
|
+
: undefined;
|
|
76
|
+
const preflightServer = preflightCredentialResolver
|
|
77
|
+
? buildServer(providerBlockingPreflightTransport, preflightCredentialResolver)
|
|
78
|
+
: null;
|
|
79
|
+
return {
|
|
80
|
+
id: integration.serverId,
|
|
81
|
+
server,
|
|
82
|
+
approvalAuthority: {
|
|
83
|
+
kind: "api_integration",
|
|
84
|
+
capabilityId: integration.capabilityId,
|
|
85
|
+
pluginKey: integration.pluginKey,
|
|
86
|
+
pluginInstallationId: integration.pluginInstallationId,
|
|
87
|
+
installationVersion: integration.installationVersion,
|
|
88
|
+
instanceId: integration.instanceId,
|
|
89
|
+
instanceKey: integration.instanceKey,
|
|
90
|
+
instanceVersion: integration.instanceVersion,
|
|
91
|
+
definitionId: integration.definitionId,
|
|
92
|
+
definitionProvenance: integration.definitionProvenance,
|
|
93
|
+
revisionId: integration.revision.id,
|
|
94
|
+
baseUrl: integration.baseUrl,
|
|
95
|
+
providerDomain: integration.providerDomain,
|
|
96
|
+
connectionRef: integration.connectionRef,
|
|
97
|
+
connectionAuthorityGeneration: integration.connectionAuthorityGeneration,
|
|
98
|
+
},
|
|
99
|
+
...(integration.connectionRef?.connectionId
|
|
100
|
+
? { resolvedConnectionId: integration.connectionRef.connectionId }
|
|
101
|
+
: {}),
|
|
102
|
+
...(credentialResolver
|
|
103
|
+
? {
|
|
104
|
+
preflightCall: async (
|
|
105
|
+
toolName: string,
|
|
106
|
+
args: Record<string, unknown>,
|
|
107
|
+
options?: { signal?: AbortSignal },
|
|
108
|
+
) => await preflightApiIntegrationCall(preflightServer!, toolName, args, options),
|
|
109
|
+
}
|
|
110
|
+
: {}),
|
|
111
|
+
};
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const providerBlockingPreflightTransport = directIntegrationTransport(async () => {
|
|
116
|
+
throw new Error("integration provider request blocked by preflight");
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
async function preflightApiIntegrationCall(
|
|
120
|
+
server: LocalMcpServerRegistration["server"],
|
|
121
|
+
toolName: string,
|
|
122
|
+
args: Record<string, unknown>,
|
|
123
|
+
options?: { signal?: AbortSignal },
|
|
124
|
+
): Promise<void> {
|
|
125
|
+
try {
|
|
126
|
+
await server.callTool(toolName, args, null, options);
|
|
127
|
+
} catch (error) {
|
|
128
|
+
if (error instanceof IntegrationInvocationError && error.code === "request_failed") return;
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
throw new Error("integration preflight unexpectedly crossed the provider request boundary");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function integrationCredentialResolver(
|
|
135
|
+
input: BuildApiIntegrationServersInput,
|
|
136
|
+
integration: ApiIntegrationRuntime,
|
|
137
|
+
credentialResolutionMode: "execution" | "preflight",
|
|
138
|
+
): IntegrationCredentialResolver {
|
|
139
|
+
const connectionRef = integration.connectionRef;
|
|
140
|
+
if (!connectionRef) throw new Error("Integration credential resolver requires a connection");
|
|
141
|
+
const expectedAuthorityGeneration = integration.connectionAuthorityGeneration;
|
|
142
|
+
if (expectedAuthorityGeneration === null) {
|
|
143
|
+
throw new Error("Integration credential resolver requires a connection authority generation");
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
resolve: async (request) => {
|
|
147
|
+
const result = await input.resolveCredential({
|
|
148
|
+
workspaceId: input.authority.workspaceId,
|
|
149
|
+
serverId: integration.serverId,
|
|
150
|
+
toolName: request.operationKey,
|
|
151
|
+
connectionRef,
|
|
152
|
+
destinationUrl: request.destinationUrl,
|
|
153
|
+
credentialTarget: "http_api",
|
|
154
|
+
forceRefresh: request.forceRefresh === true,
|
|
155
|
+
credentialResolutionMode,
|
|
156
|
+
expectedAuthorityGeneration,
|
|
157
|
+
});
|
|
158
|
+
if (result.status === "auth_needed") {
|
|
159
|
+
await publishAuthNeeded(
|
|
160
|
+
input,
|
|
161
|
+
integration.serverId,
|
|
162
|
+
request.operationKey,
|
|
163
|
+
result,
|
|
164
|
+
connectionRef,
|
|
165
|
+
);
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
const destination = new URL(request.destinationUrl);
|
|
169
|
+
return {
|
|
170
|
+
audience: { origin: destination.origin, pathPrefix: "/" },
|
|
171
|
+
placements:
|
|
172
|
+
result.placements ??
|
|
173
|
+
Object.entries(result.headers).map(([name, value]) => ({
|
|
174
|
+
carrier: "header" as const,
|
|
175
|
+
name,
|
|
176
|
+
value,
|
|
177
|
+
})),
|
|
178
|
+
...(result.authorizeProviderRequest
|
|
179
|
+
? { authorizeProviderRequest: result.authorizeProviderRequest }
|
|
180
|
+
: {}),
|
|
181
|
+
...(result.expiresAt ? { expiresAt: result.expiresAt.toISOString() } : {}),
|
|
182
|
+
...(connectionRef.scopes ? { scope: [...connectionRef.scopes] } : {}),
|
|
183
|
+
};
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async function publishAuthNeeded(
|
|
189
|
+
input: BuildApiIntegrationServersInput,
|
|
190
|
+
serverId: string,
|
|
191
|
+
toolName: string,
|
|
192
|
+
result: Extract<ResolveConnectionCredentialResult, { status: "auth_needed" }>,
|
|
193
|
+
connectionRef: NonNullable<ApiIntegrationRuntime["connectionRef"]>,
|
|
194
|
+
): Promise<void> {
|
|
195
|
+
try {
|
|
196
|
+
await input.onAuthNeeded?.({
|
|
197
|
+
serverId,
|
|
198
|
+
toolName,
|
|
199
|
+
providerDomain: result.providerDomain,
|
|
200
|
+
...(result.provider ? { provider: result.provider } : {}),
|
|
201
|
+
reason: result.reason,
|
|
202
|
+
...(result.connectionId ? { connectionId: result.connectionId } : {}),
|
|
203
|
+
...(result.authoritySource === "host" || connectionRef.authoritySource === "host"
|
|
204
|
+
? { authoritySource: "host" as const }
|
|
205
|
+
: {}),
|
|
206
|
+
...(result.scopes ? { scopes: result.scopes } : {}),
|
|
207
|
+
...(result.resource ? { resource: result.resource } : {}),
|
|
208
|
+
...(result.selectedResources ? { selectedResources: result.selectedResources } : {}),
|
|
209
|
+
...(result.authorizationUrl ? { authorizationUrl: result.authorizationUrl } : {}),
|
|
210
|
+
});
|
|
211
|
+
} catch {
|
|
212
|
+
// Authentication notices are advisory UI/audit signals. The local tool
|
|
213
|
+
// still returns the fixed connection-required result when publication fails.
|
|
214
|
+
}
|
|
215
|
+
}
|
|
@@ -555,7 +555,7 @@ export async function controlAgentSessionWorkstream(
|
|
|
555
555
|
wakeRevision: result.workflowWake.wakeRevision,
|
|
556
556
|
shouldSignal: true,
|
|
557
557
|
interruptionCount: result.interruptionCount,
|
|
558
|
-
controlRequested:
|
|
558
|
+
controlRequested: input.action === "pause",
|
|
559
559
|
});
|
|
560
560
|
},
|
|
561
561
|
},
|
|
@@ -928,7 +928,7 @@ export async function controlHumanSessionWorkstreamWithOutcome(
|
|
|
928
928
|
wakeRevision: result.workflowWake.wakeRevision,
|
|
929
929
|
shouldSignal: true,
|
|
930
930
|
interruptionCount: result.interruptionCount,
|
|
931
|
-
controlRequested:
|
|
931
|
+
controlRequested: input.action === "pause",
|
|
932
932
|
});
|
|
933
933
|
},
|
|
934
934
|
},
|
package/src/index.ts
CHANGED
|
@@ -106,6 +106,7 @@ export * from "./application/composer-submit";
|
|
|
106
106
|
export * from "./application/session-commands";
|
|
107
107
|
export * from "./application/session-tenancy";
|
|
108
108
|
export * from "./application/user-resource-grants";
|
|
109
|
+
export * from "./application/api-integration-servers";
|
|
109
110
|
|
|
110
111
|
// Durable editable-artifact live broker, ticket, ports, and projection types.
|
|
111
112
|
export * from "./editable-artifact-live";
|
package/src/sandbox/routing.ts
CHANGED
|
@@ -29,7 +29,6 @@ import {
|
|
|
29
29
|
type SandboxRetainedProcess,
|
|
30
30
|
type SandboxWorkspaceMutationAdmission,
|
|
31
31
|
} from "@opengeni/db";
|
|
32
|
-
import { settleSessionBackgroundCommandForRetainedProcess } from "@opengeni/db/session-background-commands";
|
|
33
32
|
import { appendAndPublishEvents, type EventBus } from "@opengeni/events";
|
|
34
33
|
import {
|
|
35
34
|
isProviderSandboxGoneDuringRoutedOperation,
|
|
@@ -449,14 +448,11 @@ export function wrapChannelABoxWithRouting(
|
|
|
449
448
|
reason: proof.reason,
|
|
450
449
|
idleGraceMs: settings.sandboxIdleGraceMs,
|
|
451
450
|
});
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
retainedProcessId: process.id,
|
|
458
|
-
...backgroundSettlement,
|
|
459
|
-
});
|
|
451
|
+
if (settlement.backgroundCommandEvents.length > 0 && bus) {
|
|
452
|
+
await bus
|
|
453
|
+
.publish(ids.workspaceId, ids.sessionId, settlement.backgroundCommandEvents)
|
|
454
|
+
.catch(() => undefined);
|
|
455
|
+
}
|
|
460
456
|
}
|
|
461
457
|
: undefined;
|
|
462
458
|
const resolver = makeActiveBackendResolver({
|
|
@@ -520,6 +516,7 @@ export function wrapChannelABoxWithRouting(
|
|
|
520
516
|
});
|
|
521
517
|
|
|
522
518
|
const proxy = new RoutingSandboxSession({
|
|
519
|
+
bindActiveRouteOnFirstResolve: true,
|
|
523
520
|
defaultResolved: {
|
|
524
521
|
session: established.session as RoutableBackendSession,
|
|
525
522
|
sandboxId: null,
|