@opengeni/core 0.4.0 → 0.4.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/core",
3
- "version": "0.4.0",
3
+ "version": "0.4.3",
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": {
@@ -32,24 +32,20 @@
32
32
  },
33
33
  "scripts": {
34
34
  "typecheck": "tsc --noEmit",
35
- "build": "tsup"
35
+ "build": "tsup",
36
+ "prepublishOnly": "bash ../../scripts/prepublish-guard"
36
37
  },
37
38
  "dependencies": {
38
39
  "@modelcontextprotocol/sdk": "^1.29.0",
39
40
  "@opengeni/codex": "^0.2.1",
40
- "@opengeni/config": "^0.2.4",
41
- "@opengeni/contracts": "^0.6.0",
42
- "@opengeni/db": "^0.4.0",
43
- "@opengeni/documents": "^0.2.4",
44
- "@opengeni/events": "^0.2.4",
41
+ "@opengeni/config": "^0.3.0",
42
+ "@opengeni/contracts": "^0.9.0",
43
+ "@opengeni/db": "^0.6.0",
44
+ "@opengeni/documents": "^0.2.7",
45
+ "@opengeni/events": "^0.2.7",
45
46
  "@opengeni/observability": "^0.2.1",
46
- "@opengeni/runtime": "^0.3.0",
47
- "@opengeni/storage": "^0.2.4",
47
+ "@opengeni/runtime": "^0.4.0",
48
+ "@opengeni/storage": "^0.2.7",
48
49
  "hono": "^4.12.18"
49
- },
50
- "devDependencies": {
51
- "better-auth": "^1.6.14",
52
- "tsup": "^8.5.0",
53
- "typescript": "^6.0.3"
54
50
  }
55
51
  }
@@ -24,6 +24,7 @@ export type SessionWorkflowClient = {
24
24
  syncScheduledTask: (input: { task: ScheduledTask }) => Promise<void>;
25
25
  deleteScheduledTaskSchedule: (input: { temporalScheduleId: string }) => Promise<void>;
26
26
  triggerScheduledTask: (input: { task: ScheduledTask; agentRunUsageIdempotencyKey?: string; triggerWorkflowId?: string }) => Promise<void>;
27
+ check?: () => Promise<void>;
27
28
  };
28
29
 
29
30
  export type DocumentIndexClient = {
@@ -38,6 +39,7 @@ export type AppDependencies = {
38
39
  documentIndexer?: DocumentIndexClient;
39
40
  documentServices?: DocumentServices;
40
41
  observability?: Observability;
42
+ readinessChecks?: Partial<Record<"db" | "nats" | "temporal", () => Promise<void> | void>>;
41
43
  githubStateSecret?: string;
42
44
  managedAuth?: ManagedAuth | null;
43
45
  // The API process's OWN agent-loop-free sandbox client (constructed from
@@ -11,6 +11,7 @@ import {
11
11
  type CapabilityKind,
12
12
  type CreateCapabilityCatalogItemRequest,
13
13
  type EnableCapabilityRequest,
14
+ type McpServerConnectionRef,
14
15
  } from "@opengeni/contracts";
15
16
  import {
16
17
  decryptEnvironmentValue,
@@ -21,6 +22,7 @@ import {
21
22
  encryptEnvironmentValue,
22
23
  getCapabilityCatalogItem,
23
24
  getCapabilityInstallation,
25
+ getConnectionMetadata,
24
26
  getPackInstallation,
25
27
  getStoredCapabilityHeaderCiphertext,
26
28
  getWorkspaceEnvironment,
@@ -94,7 +96,7 @@ export async function createCatalogItem(input: {
94
96
  if (id.startsWith("pack:")) {
95
97
  throw new HTTPException(422, { message: "packs are managed by OpenGeni and cannot be manually created" });
96
98
  }
97
- const source = input.payload.source === "built_in" || input.payload.source === "configured" ? "manual" : input.payload.source;
99
+ const source = input.payload.source === "built_in" || input.payload.source === "configured" || input.payload.source === "registry" ? "manual" : input.payload.source;
98
100
  const metadata = {
99
101
  ...input.payload.metadata,
100
102
  ...(input.payload.kind === "mcp" && input.payload.endpointUrl && !input.payload.metadata.mcpServerId
@@ -141,13 +143,22 @@ export async function enableCapability(input: {
141
143
  delete installationConfig.headers;
142
144
  delete installationConfig.headersEncrypted;
143
145
  delete installationConfig.headerNames;
146
+ delete installationConfig.connectionRef;
144
147
  if (item.kind === "mcp") {
145
148
  const headers = await resolveMcpCredentialHeaders(input, item);
146
- assertRequiredMcpCredentialHeaders(item, headers);
149
+ const connectionRef = input.payload.connectionRef
150
+ ? await validateMcpCapabilityConnectionRef(input, item, input.payload.connectionRef)
151
+ : null;
152
+ assertRequiredMcpCredentialHeaders(item, headers, connectionRef);
147
153
  installationMetadata = {
148
154
  ...installationMetadata,
149
- ...await validateMcpCapabilityConnection(item, input.probeMcpServer, headers ?? undefined),
155
+ ...(connectionRef && !headers
156
+ ? authDeferredMcpConnectivity()
157
+ : await validateMcpCapabilityConnection(item, input.probeMcpServer, headers ?? undefined)),
150
158
  };
159
+ if (connectionRef) {
160
+ installationConfig.connectionRef = connectionRef;
161
+ }
151
162
  if (headers) {
152
163
  const key = requireCapabilityHeaderEncryption(input.settings);
153
164
  installationConfig.headersEncrypted = Object.fromEntries(
@@ -291,7 +302,67 @@ function normalizedMcpCredentialHeaders(headers: Record<string, string>): Record
291
302
  return Object.fromEntries(entries);
292
303
  }
293
304
 
294
- function assertRequiredMcpCredentialHeaders(item: CapabilityCatalogItem, headers: Record<string, string> | null): void {
305
+ async function validateMcpCapabilityConnectionRef(
306
+ input: { db: Database; grant: AccessGrant; workspaceId: string },
307
+ item: CapabilityCatalogItem,
308
+ ref: McpServerConnectionRef,
309
+ ): Promise<McpServerConnectionRef> {
310
+ if (ref.subjectScope === "subject") {
311
+ throw new HTTPException(422, { message: "subject-owned connection refs are not supported for agent runtime use yet" });
312
+ }
313
+ const normalized: McpServerConnectionRef = {
314
+ providerDomain: ref.providerDomain.trim(),
315
+ subjectScope: "workspace",
316
+ ...(ref.connectionId ? { connectionId: ref.connectionId } : {}),
317
+ ...(ref.kind ? { kind: ref.kind } : {}),
318
+ ...(ref.scopes ? { scopes: uniqueStrings(ref.scopes) } : {}),
319
+ ...(ref.resource ? { resource: ref.resource } : {}),
320
+ };
321
+ if (!normalized.providerDomain) {
322
+ throw new HTTPException(422, { message: "connectionRef.providerDomain is required" });
323
+ }
324
+ if (!item.endpointUrl || !item.runtime.mcpServerId) {
325
+ throw new HTTPException(422, { message: "MCP capabilities need a remote streamable HTTP endpoint before they can use a connectionRef" });
326
+ }
327
+ if (!normalized.connectionId) {
328
+ return normalized;
329
+ }
330
+ const connection = await getConnectionMetadata(input.db, input.workspaceId, normalized.connectionId, input.grant.subjectId);
331
+ if (!connection) {
332
+ throw new HTTPException(422, { message: "connectionRef.connectionId does not reference a visible connection" });
333
+ }
334
+ if (connection.subjectId !== null) {
335
+ throw new HTTPException(422, { message: "agent runtime connection refs must reference workspace-shared connections in I1" });
336
+ }
337
+ if (connection.status !== "active") {
338
+ throw new HTTPException(422, { message: `connectionRef.connectionId is not active (${connection.status})` });
339
+ }
340
+ if (connection.providerDomain !== normalized.providerDomain) {
341
+ throw new HTTPException(422, { message: "connectionRef.providerDomain does not match the referenced connection" });
342
+ }
343
+ if (normalized.kind && connection.kind !== normalized.kind) {
344
+ throw new HTTPException(422, { message: "connectionRef.kind does not match the referenced connection" });
345
+ }
346
+ return normalized;
347
+ }
348
+
349
+ function authDeferredMcpConnectivity(): Record<string, unknown> {
350
+ return {
351
+ mcpConnectivity: {
352
+ status: "auth_deferred",
353
+ checkedAt: new Date().toISOString(),
354
+ },
355
+ };
356
+ }
357
+
358
+ function assertRequiredMcpCredentialHeaders(
359
+ item: CapabilityCatalogItem,
360
+ headers: Record<string, string> | null,
361
+ connectionRef: McpServerConnectionRef | null,
362
+ ): void {
363
+ if (connectionRef) {
364
+ return;
365
+ }
295
366
  const required = requiredCapabilityHeaders(item.metadata);
296
367
  const names = new Set(Object.keys(headers ?? {}).map((name) => name.toLowerCase()));
297
368
  const missing = required.filter((name) => !names.has(name.toLowerCase()));
@@ -439,7 +510,7 @@ export function settingsWithMcpCapabilityServers(settings: Settings, enabled: En
439
510
  .filter((server) => !existingIds.has(server.id))
440
511
  .flatMap((server) => {
441
512
  const headers = decryptedCapabilityHeaders(server, encryptionKey);
442
- if (headers === "unavailable") {
513
+ if (headers === "unavailable" && !server.connectionRef) {
443
514
  // Without its credential headers this server can only fail auth at
444
515
  // connect time and break agent turns; leave it out of the run.
445
516
  return [];
@@ -451,7 +522,8 @@ export function settingsWithMcpCapabilityServers(settings: Settings, enabled: En
451
522
  ...(server.allowedTools ? { allowedTools: server.allowedTools } : {}),
452
523
  ...(server.timeoutMs ? { timeoutMs: server.timeoutMs } : {}),
453
524
  cacheToolsList: server.cacheToolsList ?? false,
454
- ...(headers ? { headers } : {}),
525
+ ...(headers && headers !== "unavailable" ? { headers } : {}),
526
+ ...(server.connectionRef ? { connectionRef: server.connectionRef } : {}),
455
527
  }];
456
528
  });
457
529
  return dynamicServers.length ? { ...settings, mcpServers: [...settings.mcpServers, ...dynamicServers] } : settings;
@@ -786,6 +858,10 @@ function uniqueTags(tags: string[]): string[] {
786
858
  return [...new Set(tags.map((tag) => tag.trim()).filter(Boolean))];
787
859
  }
788
860
 
861
+ function uniqueStrings(values: string[]): string[] {
862
+ return [...new Set(values.map((value) => value.trim()).filter(Boolean))];
863
+ }
864
+
789
865
  function slugify(value: string): string {
790
866
  return value.toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60) || "capability";
791
867
  }
@@ -938,7 +1014,10 @@ function capabilityInstallationRuntimeReady(
938
1014
  return false;
939
1015
  }
940
1016
  const connectivity = installation.metadata.mcpConnectivity;
941
- return !!connectivity && typeof connectivity === "object" && "status" in connectivity && connectivity.status === "ok";
1017
+ return !!connectivity
1018
+ && typeof connectivity === "object"
1019
+ && "status" in connectivity
1020
+ && (connectivity.status === "ok" || connectivity.status === "auth_deferred");
942
1021
  }
943
1022
 
944
1023
  /**
@@ -946,6 +1025,9 @@ function capabilityInstallationRuntimeReady(
946
1025
  * capability's declared credential requirements.
947
1026
  */
948
1027
  function storedCredentialHeadersSatisfy(item: CapabilityCatalogItem, installation: CapabilityInstallation): boolean {
1028
+ if (storedConnectionRef(installation.config)) {
1029
+ return true;
1030
+ }
949
1031
  const storedNames = new Set(
950
1032
  (Array.isArray(installation.config.headerNames) ? installation.config.headerNames : [])
951
1033
  .filter((name): name is string => typeof name === "string")
@@ -957,3 +1039,9 @@ function storedCredentialHeadersSatisfy(item: CapabilityCatalogItem, installatio
957
1039
  }
958
1040
  return !item.authModel || storedNames.size > 0;
959
1041
  }
1042
+
1043
+ function storedConnectionRef(config: Record<string, unknown>): boolean {
1044
+ const ref = config.connectionRef;
1045
+ return !!ref && typeof ref === "object" && !Array.isArray(ref)
1046
+ && typeof (ref as { providerDomain?: unknown }).providerDomain === "string";
1047
+ }
@@ -105,6 +105,7 @@ function mcpServerConfigFromInput(server: SessionMcpServerInput): Settings["mcpS
105
105
  ...(server.allowedTools ? { allowedTools: server.allowedTools } : {}),
106
106
  ...(server.timeoutMs ? { timeoutMs: server.timeoutMs } : {}),
107
107
  cacheToolsList: server.cacheToolsList ?? false,
108
+ ...(server.requireApproval !== undefined ? { requireApproval: server.requireApproval } : {}),
108
109
  };
109
110
  }
110
111
 
@@ -170,6 +171,7 @@ function validateSessionMcpServersForCreate(
170
171
  allowedTools: server.allowedTools ?? null,
171
172
  timeoutMs: server.timeoutMs ?? null,
172
173
  cacheToolsList: server.cacheToolsList ?? false,
174
+ requireApproval: server.requireApproval ?? null,
173
175
  headersEncrypted,
174
176
  });
175
177
  metadata.push({