@opengeni/api-router 2.6.3-canary.0 → 2.6.4-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.
@@ -82,8 +82,8 @@ export declare function scheduledTaskMcpSummary(task: ScheduledTask): {
82
82
  runMode: "existing_session" | "new_session_per_run" | "reusable_session";
83
83
  overlapPolicy: "allow_concurrent" | "buffer_one" | "skip";
84
84
  action: {
85
+ readonly sourceId?: never;
85
86
  kind: "agent_turn";
86
- sourceId?: never;
87
87
  sourceGeneration?: never;
88
88
  sourceLifecycleGeneration?: never;
89
89
  destination?: never;
@@ -205,8 +205,8 @@ export declare function boundScheduledTaskDetailMcp(task: ScheduledTask): {
205
205
  runMode: "existing_session" | "new_session_per_run" | "reusable_session";
206
206
  overlapPolicy: "allow_concurrent" | "buffer_one" | "skip";
207
207
  action: {
208
+ readonly sourceId?: never;
208
209
  kind: "agent_turn";
209
- sourceId?: never;
210
210
  sourceGeneration?: never;
211
211
  sourceLifecycleGeneration?: never;
212
212
  destination?: never;
@@ -389,8 +389,8 @@ export declare function boundScheduledTaskMcpPage(input: {
389
389
  runMode: "existing_session" | "new_session_per_run" | "reusable_session";
390
390
  overlapPolicy: "allow_concurrent" | "buffer_one" | "skip";
391
391
  action: {
392
+ readonly sourceId?: never;
392
393
  kind: "agent_turn";
393
- sourceId?: never;
394
394
  sourceGeneration?: never;
395
395
  sourceLifecycleGeneration?: never;
396
396
  destination?: never;
@@ -1,23 +1,7 @@
1
- import { type ConfiguredModel, type Settings } from "@opengeni/config";
2
- import { ClientModel, type WorkspaceModelCatalogResponse as WorkspaceModelCatalogResponseType, type WorkspaceModelPolicyContract } from "@opengeni/contracts";
3
- export type ModelAvailabilityObservation = {
4
- status: "available" | "degraded" | "unavailable";
5
- reason: "not_entitled" | "provider_unhealthy" | null;
6
- checkedAt: string;
7
- };
8
- export type ModelCredentialReadinessObservation = {
9
- status: "ready";
10
- checkedAt: string;
11
- } | {
12
- status: "not_ready";
13
- reason: "prerequisites_missing" | "needs_reauth";
14
- checkedAt: string;
15
- } | {
16
- status: "error";
17
- reason: "resolver_error";
18
- checkedAt: string;
19
- };
20
- export declare const MODEL_CREDENTIAL_READINESS_OBSERVATION_MAX_AGE_MS: number;
1
+ import { type ConfiguredModel } from "@opengeni/config";
2
+ import { ClientModel, type WorkspaceModelCatalogResponse as WorkspaceModelCatalogResponseType } from "@opengeni/contracts";
3
+ import { type WorkspaceModelSelectionInput } from "@opengeni/core";
4
+ export { MODEL_CREDENTIAL_READINESS_OBSERVATION_MAX_AGE_MS, type ModelAvailabilityObservation, type ModelCredentialReadinessObservation, } from "@opengeni/core";
21
5
  /** Static, client-safe definition projection. No provider secret is reachable. */
22
6
  export declare function projectClientModel(model: ConfiguredModel): ClientModel;
23
7
  /**
@@ -28,14 +12,4 @@ export declare function projectClientModel(model: ConfiguredModel): ClientModel;
28
12
  * successful typed resolver observation; credential health and provider health
29
13
  * are separate inputs and neither is fabricated.
30
14
  */
31
- export declare function buildWorkspaceModelCatalog(input: {
32
- settings: Settings;
33
- policy: WorkspaceModelPolicyContract | null;
34
- codexSubscriptionActive: boolean;
35
- xaiSubscriptionActive?: boolean;
36
- workspaceGatewayConnectionActive?: boolean;
37
- credentialReadinessObservations?: Readonly<Record<string, ModelCredentialReadinessObservation>> | undefined;
38
- observations?: Readonly<Record<string, ModelAvailabilityObservation>> | undefined;
39
- now?: Date | undefined;
40
- credentialReadinessMaxAgeMs?: number | undefined;
41
- }): WorkspaceModelCatalogResponseType;
15
+ export declare function buildWorkspaceModelCatalog(input: WorkspaceModelSelectionInput): WorkspaceModelCatalogResponseType;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/api-router",
3
- "version": "2.6.3-canary.0",
3
+ "version": "2.6.4-canary.0",
4
4
  "description": "OpenGeni HTTP surface: the Hono adapter/router (createApp), routes, MCP HTTP transport, and HTTP access adapters over @opengeni/core. An engine-distribution surface — its runtime closure includes engine-internal packages.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -43,23 +43,23 @@
43
43
  "@hono/zod-validator": "^0.7.6",
44
44
  "@llamaindex/liteparse": "2.14.2",
45
45
  "@modelcontextprotocol/sdk": "^1.29.0",
46
- "@opengeni/agent-proto": "^0.5.1-canary.6",
47
- "@opengeni/artifact-tool": "^0.3.14-canary.0",
48
- "@opengeni/capabilities": "^0.3.1-canary.5",
49
- "@opengeni/codemode": "^0.4.22-canary.0",
50
- "@opengeni/codex": "^0.2.20-canary.0",
51
- "@opengeni/config": "^0.22.5-canary.0",
52
- "@opengeni/contracts": "^2.9.2-canary.0",
53
- "@opengeni/core": "^2.6.3-canary.0",
54
- "@opengeni/db": "^3.7.3-canary.0",
55
- "@opengeni/documents": "^0.8.13-canary.0",
56
- "@opengeni/events": "^0.4.11-canary.0",
57
- "@opengeni/github": "^0.6.7-canary.0",
58
- "@opengeni/network": "^0.2.3-canary.5",
59
- "@opengeni/observability": "^0.8.14-canary.0",
60
- "@opengeni/runtime": "^2.0.1-canary.0",
61
- "@opengeni/storage": "^0.2.115-canary.0",
62
- "@opengeni/xai-subscription": "^0.1.2-canary.5",
46
+ "@opengeni/agent-proto": "^0.5.1-canary.7",
47
+ "@opengeni/artifact-tool": "^0.3.14-canary.1",
48
+ "@opengeni/capabilities": "^0.3.1-canary.6",
49
+ "@opengeni/codemode": "^0.4.22-canary.1",
50
+ "@opengeni/codex": "^0.2.20-canary.1",
51
+ "@opengeni/config": "^0.22.5-canary.1",
52
+ "@opengeni/contracts": "^2.9.2-canary.1",
53
+ "@opengeni/core": "^2.6.4-canary.0",
54
+ "@opengeni/db": "^3.7.4-canary.0",
55
+ "@opengeni/documents": "^0.8.14-canary.0",
56
+ "@opengeni/events": "^0.4.12-canary.0",
57
+ "@opengeni/github": "^0.6.7-canary.1",
58
+ "@opengeni/network": "^0.2.3-canary.6",
59
+ "@opengeni/observability": "^0.8.14-canary.1",
60
+ "@opengeni/runtime": "^2.0.1-canary.1",
61
+ "@opengeni/storage": "^0.2.115-canary.1",
62
+ "@opengeni/xai-subscription": "^0.1.2-canary.6",
63
63
  "@temporalio/client": "^1.17.0",
64
64
  "better-auth": "^1.6.14",
65
65
  "hono": "^4.12.18",
package/src/app.ts CHANGED
@@ -66,6 +66,8 @@ import {
66
66
  requireLiveAgentAttemptAuthorization,
67
67
  requirePermission,
68
68
  releaseManagedAuthRequestActorLease,
69
+ resolveCatalogSettings,
70
+ resolveWorkspaceCatalogSettings,
69
71
  validateManagedAuthRequestActorLease,
70
72
  requireSessionAuthorization,
71
73
  SessionAuthorizationDeniedError,
@@ -222,6 +224,22 @@ export function createApp(deps: AppDependencies): Hono {
222
224
  return createAppComposition(deps).app;
223
225
  }
224
226
 
227
+ export async function resolveWorkspaceMcpRouteDeps(
228
+ routeDeps: ApiRouteDeps,
229
+ grant: AccessGrant,
230
+ ): Promise<ApiRouteDeps> {
231
+ const catalogSourceSettings = routeDeps.catalogSourceSettings ?? routeDeps.settings;
232
+ const resolvedCatalog = await resolveWorkspaceCatalogSettings(
233
+ routeDeps.db,
234
+ catalogSourceSettings,
235
+ {
236
+ accountId: grant.accountId,
237
+ workspaceId: grant.workspaceId,
238
+ },
239
+ );
240
+ return { ...routeDeps, catalogSourceSettings, settings: resolvedCatalog.settings };
241
+ }
242
+
225
243
  export function createAppComposition(deps: AppDependencies): {
226
244
  app: Hono;
227
245
  routeDeps: ApiRouteDeps;
@@ -358,6 +376,7 @@ export function createAppComposition(deps: AppDependencies): {
358
376
  : deps.transcriptionSegmenter;
359
377
  const routeDeps: ApiRouteDeps = {
360
378
  ...deps,
379
+ resolveCatalogSettings: () => resolveCatalogSettings(deps.db, deps.settings),
361
380
  observability,
362
381
  githubStateSecret:
363
382
  deps.githubStateSecret ?? deps.settings.githubAppManifestStateSecret ?? crypto.randomUUID(),
@@ -766,10 +785,12 @@ export function createAppComposition(deps: AppDependencies): {
766
785
 
767
786
  app.get("/v1/config/client", async (c) => {
768
787
  c.header("cache-control", "no-store");
769
- const codexCatalogSettings = deps.settings.codexSubscriptionEnabled
770
- ? withCodexCatalogProvider(deps.settings)
771
- : deps.settings;
772
- const catalogSettings = deps.settings.supergrokSubscriptionEnabled
788
+ const resolvedCatalog = await resolveCatalogSettings(deps.db, deps.settings);
789
+ const baseCatalogSettings = resolvedCatalog.settings;
790
+ const codexCatalogSettings = baseCatalogSettings.codexSubscriptionEnabled
791
+ ? withCodexCatalogProvider(baseCatalogSettings)
792
+ : baseCatalogSettings;
793
+ const catalogSettings = baseCatalogSettings.supergrokSubscriptionEnabled
773
794
  ? withXaiSubscriptionCatalogProvider(codexCatalogSettings)
774
795
  : codexCatalogSettings;
775
796
  return c.json(
@@ -889,7 +910,8 @@ export function createAppComposition(deps: AppDependencies): {
889
910
  const transport = new WebStandardStreamableHTTPServerTransport({
890
911
  enableJsonResponse: true,
891
912
  });
892
- const mcp = buildOpenGeniMcpServer(routeDeps, grant, {
913
+ const mcpDeps = await resolveWorkspaceMcpRouteDeps(routeDeps, grant);
914
+ const mcp = buildOpenGeniMcpServer(mcpDeps, grant, {
893
915
  requestOrigin: new URL(c.req.url).origin,
894
916
  workspaceMemoryEnabled,
895
917
  workspaceMemoryPromptMode,
@@ -1379,11 +1401,14 @@ type ReadinessCheckResult = { ok: boolean; error?: string };
1379
1401
  function readinessChecks(deps: AppDependencies): ReadinessChecks {
1380
1402
  const configuredNatsCheck = deps.readinessChecks?.nats;
1381
1403
  return {
1382
- db:
1383
- deps.readinessChecks?.db ??
1384
- (async () => {
1404
+ db: async () => {
1405
+ if (deps.readinessChecks?.db) {
1406
+ await deps.readinessChecks.db();
1407
+ } else {
1385
1408
  await deps.db.execute(dbSql`select 1`);
1386
- }),
1409
+ }
1410
+ await resolveCatalogSettings(deps.db, deps.settings);
1411
+ },
1387
1412
  nats: async () => {
1388
1413
  requireSessionEventDurableFanoutCapability(deps.bus);
1389
1414
  if (configuredNatsCheck) {
package/src/index.ts CHANGED
@@ -28,7 +28,10 @@ import {
28
28
  } from "@opengeni/observability";
29
29
  import { createObjectStorage } from "@opengeni/storage";
30
30
  import { isArtifactRuntimeConfigured } from "@opengeni/artifact-tool/runtime/development";
31
- import { SESSION_WORKFLOW_WAKE_DISPATCHER_SCHEDULE_ID } from "@opengeni/core";
31
+ import {
32
+ resolveCatalogSettings,
33
+ SESSION_WORKFLOW_WAKE_DISPATCHER_SCHEDULE_ID,
34
+ } from "@opengeni/core";
32
35
  import {
33
36
  Connection,
34
37
  Client as TemporalClient,
@@ -357,6 +360,15 @@ export async function startApi(
357
360
  () => assertRuntimeDatabasePosture(dbClient.db, databasePosture),
358
361
  { ...retryOptions, onRetry },
359
362
  );
363
+ const resolvedCatalog = await retryStartupDependency(
364
+ "model catalog",
365
+ () => resolveCatalogSettings(dbClient.db, settings),
366
+ { ...retryOptions, onRetry },
367
+ );
368
+ observability.info("OpenGeni model catalog resolved", {
369
+ catalogSource: resolvedCatalog.source,
370
+ catalogVersion: resolvedCatalog.version,
371
+ });
360
372
  bus = await retryStartupDependency(
361
373
  "NATS",
362
374
  () =>
@@ -123,6 +123,7 @@ import {
123
123
  requireAccessContext,
124
124
  requireAccessGrant,
125
125
  requireSessionAuthorizationListScope,
126
+ resolveWorkspaceCatalogSettings,
126
127
  type ApiRouteDeps,
127
128
  } from "@opengeni/core";
128
129
  import { publishDurableSessionEvents } from "@opengeni/events";
@@ -1058,6 +1059,23 @@ export function registerSlackInteractionRoutes(app: Hono, deps: ApiRouteDeps): v
1058
1059
  });
1059
1060
  }
1060
1061
 
1062
+ async function withCatalogSettings(
1063
+ deps: ApiRouteDeps,
1064
+ grant: Pick<AccessGrant, "accountId" | "workspaceId">,
1065
+ ): Promise<ApiRouteDeps> {
1066
+ const catalogSourceSettings = deps.catalogSourceSettings ?? deps.settings;
1067
+ return {
1068
+ ...deps,
1069
+ catalogSourceSettings,
1070
+ settings: (
1071
+ await resolveWorkspaceCatalogSettings(deps.db, catalogSourceSettings, {
1072
+ accountId: grant.accountId,
1073
+ workspaceId: grant.workspaceId,
1074
+ })
1075
+ ).settings,
1076
+ };
1077
+ }
1078
+
1061
1079
  async function publishSlackAppHome(
1062
1080
  deps: ApiRouteDeps,
1063
1081
  installation: SlackInstallationRoute,
@@ -2152,17 +2170,22 @@ async function processSlackInboxEntry(deps: ApiRouteDeps, entry: SlackInteractio
2152
2170
  preparedAttachments,
2153
2171
  preparedModelContext,
2154
2172
  );
2155
- session = await createSessionForRequest(deps, grant, interaction.workspaceId, {
2156
- requestedSessionId: interaction.sessionReservationId,
2157
- initialMessage: prepared.entry.text,
2158
- ...(prepared.modelContext ? { modelContext: prepared.modelContext } : {}),
2159
- instructions: SLACK_SESSION_INSTRUCTIONS,
2160
- firstPartyMcpTools: slackTaskFirstPartyMcpTools(deps.settings),
2161
- resources: preparedAttachments.resources,
2162
- ...(preferredModel ? { model: preferredModel } : {}),
2163
- idempotencyKey: `slack:${entry.connectionId}:${entry.providerEventId}`,
2164
- clientEventId: `slack:${entry.providerEventId}`,
2165
- });
2173
+ session = await createSessionForRequest(
2174
+ await withCatalogSettings(deps, grant),
2175
+ grant,
2176
+ interaction.workspaceId,
2177
+ {
2178
+ requestedSessionId: interaction.sessionReservationId,
2179
+ initialMessage: prepared.entry.text,
2180
+ ...(prepared.modelContext ? { modelContext: prepared.modelContext } : {}),
2181
+ instructions: SLACK_SESSION_INSTRUCTIONS,
2182
+ firstPartyMcpTools: slackTaskFirstPartyMcpTools(deps.settings),
2183
+ resources: preparedAttachments.resources,
2184
+ ...(preferredModel ? { model: preferredModel } : {}),
2185
+ idempotencyKey: `slack:${entry.connectionId}:${entry.providerEventId}`,
2186
+ clientEventId: `slack:${entry.providerEventId}`,
2187
+ },
2188
+ );
2166
2189
  } catch (error) {
2167
2190
  if (error instanceof HTTPException) {
2168
2191
  await client.postMessage({
@@ -2741,21 +2764,26 @@ async function processSlackReactionInboxEntry(
2741
2764
  const preparedEntry = slackReactionPreparedEntry(entry, context, preparedTask);
2742
2765
  let session: Awaited<ReturnType<typeof createSessionForRequest>>;
2743
2766
  try {
2744
- session = await createSessionForRequest(deps, grant, interaction.workspaceId, {
2745
- requestedSessionId: interaction.sessionReservationId,
2746
- initialMessage: preparedEntry.text,
2747
- instructions: SLACK_SESSION_INSTRUCTIONS,
2748
- // The exact reacted message and bounded containing thread are already in
2749
- // the prompt; do not expose general Slack history tools for this trigger.
2750
- firstPartyMcpTools: resolveFirstPartyMcpToolPolicy(deps.settings).default,
2751
- resources: preparedTask.resources,
2752
- ...(preferredModel ? { model: preferredModel } : {}),
2753
- // Every reaction entry converging on this route must use the same create
2754
- // key. This closes the same-owner multi-event race while the owner check
2755
- // above prevents a different subject from winning creation authority.
2756
- idempotencyKey: `slack-interaction:${interaction.id}`,
2757
- clientEventId: `slack:${entry.providerEventId}`,
2758
- });
2767
+ session = await createSessionForRequest(
2768
+ await withCatalogSettings(deps, grant),
2769
+ grant,
2770
+ interaction.workspaceId,
2771
+ {
2772
+ requestedSessionId: interaction.sessionReservationId,
2773
+ initialMessage: preparedEntry.text,
2774
+ instructions: SLACK_SESSION_INSTRUCTIONS,
2775
+ // The exact reacted message and bounded containing thread are already in
2776
+ // the prompt; do not expose general Slack history tools for this trigger.
2777
+ firstPartyMcpTools: resolveFirstPartyMcpToolPolicy(deps.settings).default,
2778
+ resources: preparedTask.resources,
2779
+ ...(preferredModel ? { model: preferredModel } : {}),
2780
+ // Every reaction entry converging on this route must use the same create
2781
+ // key. This closes the same-owner multi-event race while the owner check
2782
+ // above prevents a different subject from winning creation authority.
2783
+ idempotencyKey: `slack-interaction:${interaction.id}`,
2784
+ clientEventId: `slack:${entry.providerEventId}`,
2785
+ },
2786
+ );
2759
2787
  // The route-wide create key converges every replica on one reserved
2760
2788
  // session, but its first writer's initial message is the only event created
2761
2789
  // by that operation. Replay this exact Slack event through the normal
@@ -3143,11 +3171,17 @@ async function acceptSlackReactionTask(
3143
3171
  }
3144
3172
  return;
3145
3173
  }
3146
- await acceptSessionUserMessage(deps, grant, grant.workspaceId, sessionId, {
3147
- text: entry.text,
3148
- resources,
3149
- clientEventId,
3150
- });
3174
+ await acceptSessionUserMessage(
3175
+ await withCatalogSettings(deps, grant),
3176
+ grant,
3177
+ grant.workspaceId,
3178
+ sessionId,
3179
+ {
3180
+ text: entry.text,
3181
+ resources,
3182
+ clientEventId,
3183
+ },
3184
+ );
3151
3185
  }
3152
3186
 
3153
3187
  async function continueSlackSession(
@@ -3230,12 +3264,18 @@ async function continueSlackSession(
3230
3264
  if (!hasPermission(grant.permissions, "sessions:control")) {
3231
3265
  throw new SlackInteractionPermanentError("sessions_control_denied");
3232
3266
  }
3233
- await acceptSessionUserMessage(deps, grant, interaction.workspaceId, interaction.sessionId, {
3234
- text: entry.text,
3235
- ...(options.modelContext ? { modelContext: options.modelContext } : {}),
3236
- resources,
3237
- clientEventId: `slack:${entry.providerEventId}`,
3238
- });
3267
+ await acceptSessionUserMessage(
3268
+ await withCatalogSettings(deps, grant),
3269
+ grant,
3270
+ interaction.workspaceId,
3271
+ interaction.sessionId,
3272
+ {
3273
+ text: entry.text,
3274
+ ...(options.modelContext ? { modelContext: options.modelContext } : {}),
3275
+ resources,
3276
+ clientEventId: `slack:${entry.providerEventId}`,
3277
+ },
3278
+ );
3239
3279
  }
3240
3280
 
3241
3281
  const SLACK_ACTION_ID_BY_KIND: Record<SlackInteractionActionKind, string> = {
package/src/mcp/server.ts CHANGED
@@ -154,6 +154,7 @@ import {
154
154
  requireLiveAgentAttemptAuthorization,
155
155
  requireSessionAuthorization,
156
156
  requireSessionAuthorizationListScope,
157
+ resolveWorkspaceCatalogSettings,
157
158
  SessionAuthorizationDeniedError,
158
159
  SessionAuthorizationUnavailableError,
159
160
  saveWorkspaceMemoryWithSlackPublication,
@@ -1718,7 +1719,7 @@ export function buildOpenGeniMcpServer(
1718
1719
  async ({ id, triggerId }) => {
1719
1720
  const task = await requireScheduledTask(deps.db, grant.workspaceId, id);
1720
1721
  if (task.action.kind === "agent_turn") {
1721
- await validateScheduledTaskTarget({
1722
+ const targetSession = await validateScheduledTaskTarget({
1722
1723
  db: deps.db,
1723
1724
  sessionAuthorization: deps.sessionAuthorization,
1724
1725
  authorizationSurface: "first_party_mcp",
@@ -1730,8 +1731,16 @@ export function buildOpenGeniMcpServer(
1730
1731
  agentConfig: task.agentConfig,
1731
1732
  missingTargetStatus: 404,
1732
1733
  });
1734
+ const catalogSourceSettings = deps.catalogSourceSettings ?? deps.settings;
1735
+ const catalogSettings = (
1736
+ await resolveWorkspaceCatalogSettings(deps.db, catalogSourceSettings, {
1737
+ accountId: grant.accountId,
1738
+ workspaceId: grant.workspaceId,
1739
+ ...(targetSession ? { retainedProductModelId: targetSession.model } : {}),
1740
+ })
1741
+ ).settings;
1733
1742
  await validateScheduledTaskMachineTarget({
1734
- settings: deps.settings,
1743
+ settings: catalogSettings,
1735
1744
  db: deps.db,
1736
1745
  grant,
1737
1746
  runMode: task.runMode,