@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.
@@ -3,10 +3,10 @@ import {
3
3
  canonicalizeConfiguredModelId as canonicalizeConfiguredModelId2,
4
4
  configuredAllowedModels,
5
5
  configuredAllowedReasoningEfforts,
6
- configuredModels as configuredModels3,
6
+ configuredModels as configuredModels2,
7
7
  resolveFirstPartyMcpToolPolicy as resolveFirstPartyMcpToolPolicy2,
8
- withCodexCatalogProvider as withCodexCatalogProvider2,
9
- withXaiSubscriptionCatalogProvider as withXaiSubscriptionCatalogProvider3
8
+ withCodexCatalogProvider,
9
+ withXaiSubscriptionCatalogProvider as withXaiSubscriptionCatalogProvider2
10
10
  } from "@opengeni/config";
11
11
  import {
12
12
  ClientConfig,
@@ -116,6 +116,8 @@ import {
116
116
  requireLiveAgentAttemptAuthorization as requireLiveAgentAttemptAuthorization3,
117
117
  requirePermission as requirePermission9,
118
118
  releaseManagedAuthRequestActorLease,
119
+ resolveCatalogSettings,
120
+ resolveWorkspaceCatalogSettings as resolveWorkspaceCatalogSettings8,
119
121
  validateManagedAuthRequestActorLease,
120
122
  requireSessionAuthorization as requireSessionAuthorization9,
121
123
  SessionAuthorizationDeniedError as SessionAuthorizationDeniedError10,
@@ -2379,6 +2381,7 @@ import {
2379
2381
  requireLiveAgentAttemptAuthorization as requireLiveAgentAttemptAuthorization2,
2380
2382
  requireSessionAuthorization as requireSessionAuthorization2,
2381
2383
  requireSessionAuthorizationListScope,
2384
+ resolveWorkspaceCatalogSettings,
2382
2385
  SessionAuthorizationDeniedError as SessionAuthorizationDeniedError3,
2383
2386
  SessionAuthorizationUnavailableError as SessionAuthorizationUnavailableError2,
2384
2387
  saveWorkspaceMemoryWithSlackPublication,
@@ -19891,7 +19894,7 @@ function buildOpenGeniMcpServer(deps, grant, options = {}) {
19891
19894
  async ({ id, triggerId }) => {
19892
19895
  const task = await requireScheduledTask(deps.db, grant.workspaceId, id);
19893
19896
  if (task.action.kind === "agent_turn") {
19894
- await validateScheduledTaskTarget({
19897
+ const targetSession = await validateScheduledTaskTarget({
19895
19898
  db: deps.db,
19896
19899
  sessionAuthorization: deps.sessionAuthorization,
19897
19900
  authorizationSurface: "first_party_mcp",
@@ -19903,8 +19906,14 @@ function buildOpenGeniMcpServer(deps, grant, options = {}) {
19903
19906
  agentConfig: task.agentConfig,
19904
19907
  missingTargetStatus: 404
19905
19908
  });
19909
+ const catalogSourceSettings = deps.catalogSourceSettings ?? deps.settings;
19910
+ const catalogSettings = (await resolveWorkspaceCatalogSettings(deps.db, catalogSourceSettings, {
19911
+ accountId: grant.accountId,
19912
+ workspaceId: grant.workspaceId,
19913
+ ...targetSession ? { retainedProductModelId: targetSession.model } : {}
19914
+ })).settings;
19906
19915
  await validateScheduledTaskMachineTarget({
19907
- settings: deps.settings,
19916
+ settings: catalogSettings,
19908
19917
  db: deps.db,
19909
19918
  grant,
19910
19919
  runMode: task.runMode,
@@ -26078,9 +26087,9 @@ function registerCodexRoutes(app, deps) {
26078
26087
 
26079
26088
  // src/routes/supergrok.ts
26080
26089
  import {
26081
- configuredModels as configuredModels2,
26090
+ configuredModels,
26082
26091
  environmentsEncryptionKeyBytes as environmentsEncryptionKeyBytes3,
26083
- withXaiSubscriptionCatalogProvider as withXaiSubscriptionCatalogProvider2
26092
+ withXaiSubscriptionCatalogProvider
26084
26093
  } from "@opengeni/config";
26085
26094
  import {
26086
26095
  WORKSPACE_XAI_PROVIDER_ACCOUNT_AUTHORITY_SNAPSHOT_V1
@@ -26119,22 +26128,21 @@ import * as z10 from "zod/v4";
26119
26128
 
26120
26129
  // src/model-catalog.ts
26121
26130
  import {
26122
- configuredModels,
26123
- configuredProviders,
26124
- withCodexCatalogProvider,
26125
- withXaiSubscriptionCatalogProvider,
26126
- withWorkspaceGatewayCatalogProvider
26131
+ OPENROUTER_PROVIDER_ID,
26132
+ WORKSPACE_OPENROUTER_PROVIDER_ID
26127
26133
  } from "@opengeni/config";
26128
26134
  import {
26129
26135
  ClientModel,
26130
- WorkspaceModelCatalogResponse,
26131
- evaluateWorkspaceModelPolicy
26136
+ WorkspaceModelCatalogResponse
26132
26137
  } from "@opengeni/contracts";
26133
- var MODEL_CREDENTIAL_READINESS_OBSERVATION_MAX_AGE_MS = 5 * 6e4;
26138
+ import { resolveWorkspaceModelSelection } from "@opengeni/core";
26139
+ import {
26140
+ MODEL_CREDENTIAL_READINESS_OBSERVATION_MAX_AGE_MS
26141
+ } from "@opengeni/core";
26134
26142
  function projectClientModel(model) {
26135
26143
  const anonymousProvider = model.credentialSource.kind === "deployment" && model.credentialSource.mechanism === "none";
26136
- const source = model.credentialSource.kind === "connected_subscription" ? model.credentialSource.provider === "xai" ? "supergrok" : "codex" : model.credentialSource.kind === "workspace_connection" ? "workspace_gateway" : anonymousProvider ? void 0 : "opengeni";
26137
- const publicProvider = anonymousProvider ? { provider: model.providerId, providerLabel: model.providerLabel } : source === "codex" ? { provider: "codex", providerLabel: "Codex" } : source === "supergrok" ? { provider: "supergrok", providerLabel: "SuperGrok" } : source === "workspace_gateway" ? { provider: "workspace-gateway", providerLabel: "Your Gateway" } : { provider: "opengeni", providerLabel: "OpenGeni" };
26144
+ const source = model.providerId === OPENROUTER_PROVIDER_ID || model.providerId === WORKSPACE_OPENROUTER_PROVIDER_ID ? void 0 : model.credentialSource.kind === "connected_subscription" ? model.credentialSource.provider === "xai" ? "supergrok" : "codex" : model.credentialSource.kind === "workspace_connection" ? "workspace_gateway" : anonymousProvider ? void 0 : "opengeni";
26145
+ const publicProvider = anonymousProvider ? { provider: model.providerId, providerLabel: model.providerLabel } : model.providerId === OPENROUTER_PROVIDER_ID ? { provider: "openrouter", providerLabel: "OpenRouter" } : model.providerId === WORKSPACE_OPENROUTER_PROVIDER_ID ? { provider: "workspace-openrouter", providerLabel: "Your OpenRouter" } : source === "codex" ? { provider: "codex", providerLabel: "Codex" } : source === "supergrok" ? { provider: "supergrok", providerLabel: "SuperGrok" } : source === "workspace_gateway" ? { provider: "workspace-gateway", providerLabel: "Your Gateway" } : { provider: "opengeni", providerLabel: "OpenGeni" };
26138
26146
  return ClientModel.parse({
26139
26147
  id: model.id,
26140
26148
  label: model.label,
@@ -26147,217 +26155,19 @@ function projectClientModel(model) {
26147
26155
  aliases: model.aliases,
26148
26156
  executionLimits: model.executionLimits,
26149
26157
  billing: model.billing,
26158
+ cost: model.cost,
26150
26159
  capabilities: model.capabilities,
26151
26160
  ...model.pricing === void 0 ? {} : { pricing: model.pricing },
26152
26161
  definitionVersion: model.definitionVersion
26153
26162
  });
26154
26163
  }
26155
- function modelDefinitionRunnable(model) {
26156
- return model.capabilities.inputModalities.includes("text") && model.capabilities.outputModalities.includes("text") && model.capabilities.transports.sse.runnable;
26157
- }
26158
- function observedCredentialReadiness(input) {
26159
- if (!input.observation) {
26160
- return {
26161
- status: "not_ready",
26162
- reason: "prerequisites_missing",
26163
- basis: input.basis,
26164
- checkedAt: null
26165
- };
26166
- }
26167
- const checkedAtMs = Date.parse(input.observation.checkedAt);
26168
- if (!Number.isFinite(checkedAtMs)) {
26169
- return {
26170
- status: "error",
26171
- reason: "resolver_error",
26172
- basis: input.basis,
26173
- checkedAt: null
26174
- };
26175
- }
26176
- const checkedAt = new Date(checkedAtMs).toISOString();
26177
- if (Math.abs(input.nowMs - checkedAtMs) > input.maxAgeMs) {
26178
- return {
26179
- status: "not_ready",
26180
- reason: "observation_stale",
26181
- basis: input.basis,
26182
- checkedAt
26183
- };
26184
- }
26185
- if (input.observation.status === "ready") {
26186
- return { status: "ready", reason: null, basis: input.basis, checkedAt };
26187
- }
26188
- if (input.observation.status === "not_ready") {
26189
- return {
26190
- status: "not_ready",
26191
- reason: input.observation.reason === "needs_reauth" ? "needs_reauth" : "prerequisites_missing",
26192
- basis: input.basis,
26193
- checkedAt
26194
- };
26195
- }
26196
- return {
26197
- status: "error",
26198
- reason: "resolver_error",
26199
- basis: input.basis,
26200
- checkedAt
26201
- };
26202
- }
26203
- function credentialReadinessFor(input) {
26204
- const source = input.model.credentialSource;
26205
- if (source.kind === "connected_subscription") {
26206
- const active = source.provider === "xai" ? input.xaiSubscriptionActive : input.codexSubscriptionActive;
26207
- return active ? { status: "ready", reason: null, basis: "connection", checkedAt: null } : {
26208
- status: "not_ready",
26209
- reason: "needs_reauth",
26210
- basis: "connection",
26211
- checkedAt: null
26212
- };
26213
- }
26214
- if (source.kind === "workspace_connection") {
26215
- return input.workspaceGatewayConnectionActive ? { status: "ready", reason: null, basis: "connection", checkedAt: null } : {
26216
- status: "not_ready",
26217
- reason: "needs_reauth",
26218
- basis: "connection",
26219
- checkedAt: null
26220
- };
26221
- }
26222
- if (source.kind === "deployment" && source.mechanism === "none") {
26223
- return { status: "ready", reason: null, basis: "configuration", checkedAt: null };
26224
- }
26225
- if (source.kind === "deployment" && source.mechanism === "api_key") {
26226
- return input.provider?.apiKey ? { status: "ready", reason: null, basis: "configuration", checkedAt: null } : {
26227
- status: "not_ready",
26228
- reason: "missing_credential",
26229
- basis: "configuration",
26230
- checkedAt: null
26231
- };
26232
- }
26233
- return observedCredentialReadiness({
26234
- observation: input.observation,
26235
- basis: "resolver",
26236
- nowMs: input.nowMs,
26237
- maxAgeMs: input.maxAgeMs
26238
- });
26239
- }
26240
- function isXaiGrokModel(model) {
26241
- return model.providerId === "xai" && model.id.startsWith("xai/grok-");
26242
- }
26243
- function observationTimestamp(observation) {
26244
- if (!observation || typeof observation.checkedAt !== "string") {
26245
- return { checkedAt: null, checkedAtMs: null };
26246
- }
26247
- const checkedAtMs = Date.parse(observation.checkedAt);
26248
- if (!Number.isFinite(checkedAtMs)) {
26249
- return { checkedAt: null, checkedAtMs: null };
26250
- }
26251
- return { checkedAt: new Date(checkedAtMs).toISOString(), checkedAtMs };
26252
- }
26253
- function xaiGrokAvailabilityFor(input) {
26254
- const { checkedAt, checkedAtMs } = observationTimestamp(input.observation);
26255
- const freshSuccessfulObservation = input.observation?.status === "available" && input.observation.reason === null && checkedAtMs !== null && checkedAtMs <= input.nowMs && input.nowMs - checkedAtMs <= input.maxAgeMs;
26256
- if (freshSuccessfulObservation) {
26257
- return {
26258
- status: "available",
26259
- selectable: true,
26260
- reason: null,
26261
- checkedAt
26262
- };
26263
- }
26264
- return {
26265
- status: "unavailable",
26266
- selectable: false,
26267
- reason: input.observation?.status === "unavailable" ? input.observation.reason ?? "provider_unhealthy" : "provider_unhealthy",
26268
- checkedAt
26269
- };
26270
- }
26271
- function availabilityFor(input) {
26272
- if (!modelDefinitionRunnable(input.model)) {
26273
- return {
26274
- status: "unavailable",
26275
- selectable: false,
26276
- reason: "unsupported",
26277
- checkedAt: null
26278
- };
26279
- }
26280
- if (input.credentialReadiness.status !== "ready") {
26281
- return {
26282
- status: "unavailable",
26283
- selectable: false,
26284
- reason: input.credentialReadiness.reason === "missing_credential" ? "missing_credential" : input.credentialReadiness.reason === "needs_reauth" ? "needs_reauth" : "credential_not_ready",
26285
- checkedAt: input.credentialReadiness.checkedAt
26286
- };
26287
- }
26288
- if (!input.policyAllowed) {
26289
- return {
26290
- status: "unavailable",
26291
- selectable: false,
26292
- reason: "policy_blocked",
26293
- checkedAt: null
26294
- };
26295
- }
26296
- if (isXaiGrokModel(input.model)) {
26297
- return xaiGrokAvailabilityFor({
26298
- observation: input.observation,
26299
- nowMs: input.nowMs,
26300
- maxAgeMs: input.maxAgeMs
26301
- });
26302
- }
26303
- if (!input.observation) {
26304
- return { status: "unknown", selectable: true, reason: null, checkedAt: null };
26305
- }
26306
- if (input.observation.status === "unavailable") {
26307
- return {
26308
- status: "unavailable",
26309
- selectable: false,
26310
- reason: input.observation.reason ?? "provider_unhealthy",
26311
- checkedAt: input.observation.checkedAt
26312
- };
26313
- }
26314
- return {
26315
- status: input.observation.status,
26316
- selectable: true,
26317
- reason: null,
26318
- checkedAt: input.observation.checkedAt
26319
- };
26320
- }
26321
26164
  function buildWorkspaceModelCatalog(input) {
26322
- const codexSettings = input.settings.codexSubscriptionEnabled ? withCodexCatalogProvider(input.settings) : input.settings;
26323
- const xaiSettings = input.settings.supergrokSubscriptionEnabled ? withXaiSubscriptionCatalogProvider(codexSettings) : codexSettings;
26324
- const catalogSettings = withWorkspaceGatewayCatalogProvider(xaiSettings);
26325
- const providers = new Map(
26326
- configuredProviders(catalogSettings).map((provider) => [provider.id, provider])
26327
- );
26328
- const requestedNowMs = input.now?.getTime();
26329
- const nowMs = typeof requestedNowMs === "number" && Number.isFinite(requestedNowMs) ? requestedNowMs : Date.now();
26330
- const maxAgeMs = typeof input.credentialReadinessMaxAgeMs === "number" && Number.isFinite(input.credentialReadinessMaxAgeMs) && input.credentialReadinessMaxAgeMs >= 0 ? input.credentialReadinessMaxAgeMs : MODEL_CREDENTIAL_READINESS_OBSERVATION_MAX_AGE_MS;
26331
- const models = configuredModels(catalogSettings).map((model) => {
26332
- const provider = providers.get(model.providerId);
26333
- const policyAllowed = evaluateWorkspaceModelPolicy(input.policy, {
26334
- providerId: model.providerId,
26335
- modelId: model.id
26336
- }).allowed;
26337
- const credentialReadiness = credentialReadinessFor({
26338
- model,
26339
- provider,
26340
- codexSubscriptionActive: input.codexSubscriptionActive,
26341
- xaiSubscriptionActive: input.xaiSubscriptionActive === true,
26342
- workspaceGatewayConnectionActive: input.workspaceGatewayConnectionActive === true,
26343
- observation: input.credentialReadinessObservations?.[model.definitionVersion],
26344
- nowMs,
26345
- maxAgeMs
26346
- });
26347
- return {
26348
- ...projectClientModel(model),
26349
- credentialReadiness,
26350
- policyAllowed,
26351
- availability: availabilityFor({
26352
- model,
26353
- credentialReadiness,
26354
- policyAllowed,
26355
- observation: input.observations?.[model.definitionVersion],
26356
- nowMs,
26357
- maxAgeMs
26358
- })
26359
- };
26360
- });
26165
+ const models = resolveWorkspaceModelSelection(input).map((selection) => ({
26166
+ ...projectClientModel(selection.model),
26167
+ credentialReadiness: selection.credentialReadiness,
26168
+ policyAllowed: selection.policyAllowed,
26169
+ availability: selection.availability
26170
+ }));
26361
26171
  return WorkspaceModelCatalogResponse.parse({ models });
26362
26172
  }
26363
26173
 
@@ -26771,7 +26581,8 @@ function registerSuperGrokRoutes(app, deps) {
26771
26581
  } catch {
26772
26582
  valid = false;
26773
26583
  }
26774
- const catalog = valid ? configuredModels2(withXaiSubscriptionCatalogProvider2(deps.settings)).filter(
26584
+ const catalogSettings = valid ? (await deps.resolveCatalogSettings()).settings : null;
26585
+ const catalog = catalogSettings ? configuredModels(withXaiSubscriptionCatalogProvider(catalogSettings)).filter(
26775
26586
  (model) => model.credentialSource.kind === "connected_subscription" && model.credentialSource.provider === "xai"
26776
26587
  ).map(projectClientModel) : [];
26777
26588
  return c.json({
@@ -26948,7 +26759,13 @@ function registerSuperGrokRoutes(app, deps) {
26948
26759
  }
26949
26760
 
26950
26761
  // src/routes/connections.ts
26951
- import { createHash as createHash10 } from "crypto";
26762
+ import { createHash as createHash10, createHmac as createHmac4 } from "crypto";
26763
+ import {
26764
+ WORKSPACE_OPENROUTER_CONNECTION_DOMAIN,
26765
+ WORKSPACE_OPENROUTER_CONNECTION_ROLE,
26766
+ VERCEL_AI_GATEWAY_CONNECTION_DOMAIN,
26767
+ VERCEL_AI_GATEWAY_CONNECTION_ROLE
26768
+ } from "@opengeni/config";
26952
26769
  import {
26953
26770
  ATLASSIAN_PROVIDER_DOMAIN as ATLASSIAN_PROVIDER_DOMAIN2,
26954
26771
  AtlassianConnectionMetadata as AtlassianConnectionMetadata3,
@@ -26971,11 +26788,16 @@ import {
26971
26788
  IntegrationClientMetadata,
26972
26789
  ListSlackInstallationBindingsResponse,
26973
26790
  ListConnectionsResponse,
26791
+ OPENROUTER_CREDENTIAL_OPERATION_DIGEST_METADATA_KEY,
26792
+ OPENROUTER_CREDENTIAL_OPERATION_ID_METADATA_KEY,
26974
26793
  OpenGeniSlackBotInstallRequest,
26975
26794
  OpenGeniSlackBotInstallStart,
26976
26795
  OAuthStartRequest,
26977
26796
  OAuthStartResponse as OAuthStartResponse4,
26978
- UpdateConnectionRequest
26797
+ UpdateConnectionRequest,
26798
+ VERCEL_AI_GATEWAY_CREDENTIAL_OPERATION_DIGEST_METADATA_KEY,
26799
+ VERCEL_AI_GATEWAY_CREDENTIAL_OPERATION_ID_METADATA_KEY,
26800
+ stableJson as stableJson2
26979
26801
  } from "@opengeni/contracts";
26980
26802
  import {
26981
26803
  bindConnectorDocumentDestination as bindConnectorDocumentDestination3,
@@ -27019,11 +26841,17 @@ import {
27019
26841
  persistSlackBotInstallationWithSuccessAudit,
27020
26842
  recordSlackBotInstallCallbackFailure,
27021
26843
  revokeConnection,
26844
+ revokeWorkspaceOpenRouterConnections,
26845
+ revokeWorkspaceVercelAiGatewayConnections,
27022
26846
  revokeConnectionWithSlackBotSuccessAudit,
26847
+ rotateWorkspaceVercelAiGatewayConnection,
26848
+ rotateWorkspaceOpenRouterConnection,
27023
26849
  SlackBotLifecycleSuccessAuditError,
27024
26850
  SlackInstallationBindingConflictError,
27025
26851
  updateConnection as updateConnection5,
27026
- updateSlackBotDocumentDestination
26852
+ updateSlackBotDocumentDestination,
26853
+ upsertWorkspaceVercelAiGatewayConnection,
26854
+ upsertWorkspaceOpenRouterConnection
27027
26855
  } from "@opengeni/db";
27028
26856
  import { HTTPException as HTTPException25 } from "hono/http-exception";
27029
26857
 
@@ -28183,13 +28011,59 @@ function registerConnectionRoutes(app, deps) {
28183
28011
  assertNotDirectGoogleDriveOAuth(providerDomain, payload.kind, payload.metadata);
28184
28012
  assertNotDirectAtlassianOAuth(providerDomain, payload.kind, payload.metadata);
28185
28013
  assertNotDirectPersonalGitHubOAuth(providerDomain, payload.kind, payload.metadata);
28186
- const connection = await createConnection5(db, {
28014
+ const credentialEncrypted = encryptCredentialBundle(key, payload.credential);
28015
+ const workspaceProviderKind = workspaceProviderApiKeyConnectionKind({
28016
+ subjectId,
28017
+ providerDomain,
28018
+ kind: payload.kind,
28019
+ metadata: payload.metadata
28020
+ });
28021
+ const connection = workspaceProviderKind ? await (async () => {
28022
+ const provider = workspaceProviderApiKeyConnectionSpec(workspaceProviderKind);
28023
+ if (!payload.operationId) {
28024
+ throw new HTTPException25(400, {
28025
+ message: `connecting ${provider.label} requires an operationId`
28026
+ });
28027
+ }
28028
+ const expiresAt = payload.expiresAt ? new Date(payload.expiresAt) : null;
28029
+ const metadata = workspaceProviderCredentialMetadata(
28030
+ workspaceProviderKind,
28031
+ payload.metadata
28032
+ );
28033
+ const input = {
28034
+ accountId: grant.accountId,
28035
+ workspaceId,
28036
+ operationId: payload.operationId,
28037
+ requestDigest: workspaceProviderCredentialRequestDigest(key, {
28038
+ action: "create",
28039
+ providerDomain,
28040
+ kind: payload.kind,
28041
+ subjectId,
28042
+ credential: payload.credential,
28043
+ grantedScopes: payload.grantedScopes,
28044
+ expiresAt: expiresAt?.toISOString() ?? null,
28045
+ metadata
28046
+ }),
28047
+ credentialEncrypted,
28048
+ grantedScopes: payload.grantedScopes,
28049
+ expiresAt,
28050
+ metadata,
28051
+ updatedBySubjectId: grant.subjectId
28052
+ };
28053
+ const created = workspaceProviderKind === "vercel_gateway" ? await upsertWorkspaceVercelAiGatewayConnection(db, input) : await upsertWorkspaceOpenRouterConnection(db, input);
28054
+ if (!created || created.status === "revoked") {
28055
+ throw new HTTPException25(409, {
28056
+ message: `${provider.label} is already connected; reload before replacing its key`
28057
+ });
28058
+ }
28059
+ return created;
28060
+ })() : await createConnection5(db, {
28187
28061
  accountId: grant.accountId,
28188
28062
  workspaceId,
28189
28063
  subjectId,
28190
28064
  providerDomain,
28191
28065
  kind: payload.kind,
28192
- credentialEncrypted: encryptCredentialBundle(key, payload.credential),
28066
+ credentialEncrypted,
28193
28067
  grantedScopes: payload.grantedScopes,
28194
28068
  expiresAt: payload.expiresAt ? new Date(payload.expiresAt) : null,
28195
28069
  metadata: payload.metadata,
@@ -28723,6 +28597,18 @@ function registerConnectionRoutes(app, deps) {
28723
28597
  payload.providerDomain ?? existing.providerDomain
28724
28598
  );
28725
28599
  const kind = payload.kind ?? existing.kind;
28600
+ const subjectId2 = payload.subjectId === void 0 ? existing.subjectId : writableSubjectId(payload.subjectId, grant.subjectId);
28601
+ const targetWorkspaceProviderKind = workspaceProviderApiKeyConnectionKind({
28602
+ subjectId: subjectId2,
28603
+ providerDomain,
28604
+ kind,
28605
+ metadata: payload.metadata ?? existing.metadata
28606
+ });
28607
+ if (!workspaceProviderApiKeyConnectionKind(existing) && targetWorkspaceProviderKind) {
28608
+ throw new HTTPException25(422, {
28609
+ message: targetWorkspaceProviderKind === "vercel_gateway" ? "use the Vercel AI Gateway connect flow to create this connection" : "use the OpenRouter connect flow to create this connection"
28610
+ });
28611
+ }
28726
28612
  assertNotDirectPersonalSlackOAuth(providerDomain, kind);
28727
28613
  assertNotDirectGoogleDriveOAuth(providerDomain, kind, payload.metadata ?? existing.metadata);
28728
28614
  assertNotDirectAtlassianOAuth(providerDomain, kind, payload.metadata ?? existing.metadata);
@@ -28744,6 +28630,65 @@ function registerConnectionRoutes(app, deps) {
28744
28630
  });
28745
28631
  }
28746
28632
  }
28633
+ const existingWorkspaceProviderKind = existing ? workspaceProviderApiKeyConnectionKind(existing) : null;
28634
+ if (existing && existingWorkspaceProviderKind) {
28635
+ const provider = workspaceProviderApiKeyConnectionSpec(existingWorkspaceProviderKind);
28636
+ const providerDomain = canonicalProviderDomain(
28637
+ payload.providerDomain ?? existing.providerDomain
28638
+ );
28639
+ const subjectId2 = payload.subjectId === void 0 ? existing.subjectId : writableSubjectId(payload.subjectId, grant.subjectId);
28640
+ const kind = payload.kind ?? existing.kind;
28641
+ if (subjectId2 !== null || providerDomain !== provider.providerDomain || kind !== "api_key" || payload.metadata?.credentialRole !== void 0 && payload.metadata.credentialRole !== provider.credentialRole) {
28642
+ throw new HTTPException25(422, {
28643
+ message: `${provider.label} connection identity cannot be changed`
28644
+ });
28645
+ }
28646
+ if (payload.credential === void 0) {
28647
+ throw new HTTPException25(400, {
28648
+ message: `updating a ${provider.label} connection requires a new credential`
28649
+ });
28650
+ }
28651
+ if (payload.expectedVersion === void 0 || payload.operationId === void 0) {
28652
+ throw new HTTPException25(400, {
28653
+ message: `updating a ${provider.label} connection requires expectedVersion and operationId`
28654
+ });
28655
+ }
28656
+ const key2 = requireEnvironmentEncryption8(settings);
28657
+ const grantedScopes2 = payload.grantedScopes ?? existing.grantedScopes;
28658
+ const expiresAt = payload.expiresAt !== void 0 ? payload.expiresAt ? new Date(payload.expiresAt) : null : existing.expiresAt ? new Date(existing.expiresAt) : null;
28659
+ const metadata = workspaceProviderCredentialMetadata(existingWorkspaceProviderKind, {
28660
+ ...existing.metadata,
28661
+ ...payload.metadata ?? {}
28662
+ });
28663
+ const input = {
28664
+ accountId: grant.accountId,
28665
+ workspaceId,
28666
+ connectionId: existing.id,
28667
+ expectedVersion: payload.expectedVersion,
28668
+ operationId: payload.operationId,
28669
+ requestDigest: workspaceProviderCredentialRequestDigest(key2, {
28670
+ action: "rotate",
28671
+ connectionId: existing.id,
28672
+ expectedVersion: payload.expectedVersion,
28673
+ credential: payload.credential,
28674
+ grantedScopes: grantedScopes2,
28675
+ expiresAt: expiresAt?.toISOString() ?? null,
28676
+ metadata
28677
+ }),
28678
+ credentialEncrypted: encryptCredentialBundle(key2, payload.credential),
28679
+ grantedScopes: grantedScopes2,
28680
+ expiresAt,
28681
+ metadata,
28682
+ updatedBySubjectId: grant.subjectId
28683
+ };
28684
+ const connection2 = existingWorkspaceProviderKind === "vercel_gateway" ? await rotateWorkspaceVercelAiGatewayConnection(db, input) : await rotateWorkspaceOpenRouterConnection(db, input);
28685
+ if (!connection2) {
28686
+ throw new HTTPException25(409, {
28687
+ message: `${provider.label} connection changed; reload before replacing its key`
28688
+ });
28689
+ }
28690
+ return c.json(ConnectionResponse.parse({ connection: connection2 }));
28691
+ }
28747
28692
  const key = payload.credential === void 0 ? null : requireEnvironmentEncryption8(settings);
28748
28693
  const subjectId = payload.subjectId === void 0 ? void 0 : writableSubjectId(payload.subjectId, grant.subjectId);
28749
28694
  const connection = await updateConnection5(db, {
@@ -28785,6 +28730,7 @@ function registerConnectionRoutes(app, deps) {
28785
28730
  }
28786
28731
  const isGoogleDrive = existing.subjectId === grant.subjectId && existing.providerDomain === GOOGLE_DRIVE_PROVIDER_DOMAIN2 && existing.kind === "oauth2" && GoogleDriveConnectionMetadata2.safeParse(existing.metadata).success;
28787
28732
  const isAtlassian = existing.subjectId === grant.subjectId && existing.providerDomain === ATLASSIAN_PROVIDER_DOMAIN2 && existing.kind === "oauth2" && AtlassianConnectionMetadata3.safeParse(existing.metadata).success;
28733
+ const workspaceProviderKind = workspaceProviderApiKeyConnectionKind(existing);
28788
28734
  const disconnectPayload = await c.req.json().catch(() => null);
28789
28735
  const googleDriveDisconnect = isGoogleDrive ? GoogleDriveDisconnectRequest.safeParse(disconnectPayload) : null;
28790
28736
  if (googleDriveDisconnect && !googleDriveDisconnect.success) {
@@ -28804,7 +28750,7 @@ function registerConnectionRoutes(app, deps) {
28804
28750
  message: personalGitHubDisconnect.error.issues[0]?.message ?? "invalid personal GitHub disconnect request"
28805
28751
  });
28806
28752
  }
28807
- if (existing.status === "revoked" && !isGoogleDrive && !isAtlassian && !isPersonalGitHub) {
28753
+ if (existing.status === "revoked" && !isGoogleDrive && !isAtlassian && !isPersonalGitHub && !workspaceProviderKind) {
28808
28754
  return c.json(ConnectionResponse.parse({ connection: existing }));
28809
28755
  }
28810
28756
  const connection = isGoogleDrive ? await disconnectGoogleDrive(deps, {
@@ -28831,6 +28777,18 @@ function registerConnectionRoutes(app, deps) {
28831
28777
  credentialRole: OPENGENI_SLACK_BOT_CREDENTIAL_ROLE2,
28832
28778
  credentialLabel: OPENGENI_SLACK_BOT_CREDENTIAL_LABEL2,
28833
28779
  slackTeamId: openGeniSlackBotMetadata2(existing.metadata).slackTeamId
28780
+ }) : workspaceProviderKind ? workspaceProviderKind === "vercel_gateway" ? await revokeWorkspaceVercelAiGatewayConnections(db, {
28781
+ accountId: grant.accountId,
28782
+ workspaceId,
28783
+ connectionId,
28784
+ expectedVersion: existing.version,
28785
+ updatedBySubjectId: grant.subjectId
28786
+ }) : await revokeWorkspaceOpenRouterConnections(db, {
28787
+ accountId: grant.accountId,
28788
+ workspaceId,
28789
+ connectionId,
28790
+ expectedVersion: existing.version,
28791
+ updatedBySubjectId: grant.subjectId
28834
28792
  }) : await revokeConnection(
28835
28793
  db,
28836
28794
  workspaceId,
@@ -29152,6 +29110,28 @@ function assertNotReservedPersonalGitHubMetadata(metadata) {
29152
29110
  });
29153
29111
  }
29154
29112
  }
29113
+ function workspaceProviderApiKeyConnectionSpec(providerKind) {
29114
+ return providerKind === "vercel_gateway" ? {
29115
+ providerDomain: VERCEL_AI_GATEWAY_CONNECTION_DOMAIN,
29116
+ credentialRole: VERCEL_AI_GATEWAY_CONNECTION_ROLE,
29117
+ label: "Vercel AI Gateway"
29118
+ } : {
29119
+ providerDomain: WORKSPACE_OPENROUTER_CONNECTION_DOMAIN,
29120
+ credentialRole: WORKSPACE_OPENROUTER_CONNECTION_ROLE,
29121
+ label: "OpenRouter"
29122
+ };
29123
+ }
29124
+ function workspaceProviderApiKeyConnectionKind(input) {
29125
+ if (input.subjectId != null || input.kind !== "api_key") return null;
29126
+ const providerDomain = input.providerDomain.toLowerCase();
29127
+ if (providerDomain === VERCEL_AI_GATEWAY_CONNECTION_DOMAIN && input.metadata?.credentialRole === VERCEL_AI_GATEWAY_CONNECTION_ROLE) {
29128
+ return "vercel_gateway";
29129
+ }
29130
+ if (providerDomain === WORKSPACE_OPENROUTER_CONNECTION_DOMAIN && input.metadata?.credentialRole === WORKSPACE_OPENROUTER_CONNECTION_ROLE) {
29131
+ return "openrouter";
29132
+ }
29133
+ return null;
29134
+ }
29155
29135
  function assertNotReservedSlackBotMetadata(metadata) {
29156
29136
  if (hasReservedOpenGeniSlackBotMetadata(metadata)) {
29157
29137
  throw new HTTPException25(422, {
@@ -29197,6 +29177,22 @@ function createConnectionSubjectId(payload, grantSubjectId) {
29197
29177
  function encryptCredentialBundle(key, credential) {
29198
29178
  return encryptEnvironmentValue9(key, JSON.stringify(credential));
29199
29179
  }
29180
+ function workspaceProviderCredentialMetadata(providerKind, metadata) {
29181
+ const {
29182
+ [OPENROUTER_CREDENTIAL_OPERATION_ID_METADATA_KEY]: _openRouterOperationId,
29183
+ [OPENROUTER_CREDENTIAL_OPERATION_DIGEST_METADATA_KEY]: _openRouterOperationDigest,
29184
+ [VERCEL_AI_GATEWAY_CREDENTIAL_OPERATION_ID_METADATA_KEY]: _operationId,
29185
+ [VERCEL_AI_GATEWAY_CREDENTIAL_OPERATION_DIGEST_METADATA_KEY]: _operationDigest,
29186
+ ...effectiveMetadata
29187
+ } = metadata ?? {};
29188
+ return {
29189
+ ...effectiveMetadata,
29190
+ credentialRole: workspaceProviderApiKeyConnectionSpec(providerKind).credentialRole
29191
+ };
29192
+ }
29193
+ function workspaceProviderCredentialRequestDigest(key, value) {
29194
+ return createHmac4("sha256", key).update(stableJson2(value), "utf8").digest("hex");
29195
+ }
29200
29196
  function slackBotCredentialBundle(token) {
29201
29197
  const headerName = ["author", "ization"].join("");
29202
29198
  const scheme = ["Bear", "er"].join("");
@@ -33403,7 +33399,7 @@ import { retryWhileMissing as retryWhileMissing4 } from "@opengeni/storage";
33403
33399
  import { HTTPException as HTTPException35 } from "hono/http-exception";
33404
33400
 
33405
33401
  // src/browser-controller-authority.ts
33406
- import { createHmac as createHmac4 } from "crypto";
33402
+ import { createHmac as createHmac5 } from "crypto";
33407
33403
  function deriveBrowserControllerAdminToken(scope) {
33408
33404
  return derive(scope.rootSecret, "placement-admin.v1", [
33409
33405
  scope.accountId,
@@ -33484,7 +33480,7 @@ function deriveComputerViewGrantToken(scope) {
33484
33480
  }
33485
33481
  function derive(rootSecret, domain, fields) {
33486
33482
  if (!rootSecret) throw new Error("browser controller authority root is unavailable");
33487
- const hmac2 = createHmac4("sha256", rootSecret);
33483
+ const hmac2 = createHmac5("sha256", rootSecret);
33488
33484
  hmac2.update("opengeni.browser-controller-authority\0", "utf8");
33489
33485
  writeField(hmac2, domain);
33490
33486
  for (const field of fields) writeField(hmac2, String(field));
@@ -33643,7 +33639,7 @@ async function withInteractionHolderHeartbeat(deps, input, run) {
33643
33639
  }
33644
33640
 
33645
33641
  // src/browser-state-authority.ts
33646
- import { createCipheriv, createDecipheriv, createHash as createHash11, createHmac as createHmac5, randomBytes as randomBytes7 } from "crypto";
33642
+ import { createCipheriv, createDecipheriv, createHash as createHash11, createHmac as createHmac6, randomBytes as randomBytes7 } from "crypto";
33647
33643
  import { BROWSER_PROFILE_ARTIFACT_FORMAT } from "@opengeni/contracts";
33648
33644
  var ROOT_KEY_BYTES = 32;
33649
33645
  var DATA_KEY_BYTES = 32;
@@ -33654,7 +33650,7 @@ var SHA256_PATTERN = /^[0-9a-f]{64}$/u;
33654
33650
  var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
33655
33651
  function deriveBrowserStateDataKey(rootKey, scopeInput) {
33656
33652
  const scope = operationScope(scopeInput);
33657
- return createHmac5("sha256", root(rootKey)).update("opengeni.browser-state.data-key.v1\0", "utf8").update(canonicalJson2(scope), "utf8").digest();
33653
+ return createHmac6("sha256", root(rootKey)).update("opengeni.browser-state.data-key.v1\0", "utf8").update(canonicalJson2(scope), "utf8").digest();
33658
33654
  }
33659
33655
  function browserStateArtifactAad(scopeInput) {
33660
33656
  const scope = baseArtifactScope(scopeInput);
@@ -33739,7 +33735,7 @@ function wrapAad(scope) {
33739
33735
  return Buffer.from(`opengeni.browser-state.data-key-wrap.v1\0${canonicalJson2(scope)}`, "utf8");
33740
33736
  }
33741
33737
  function browserStateWrappingKey(rootKey, scope) {
33742
- return createHmac5("sha256", root(rootKey)).update("opengeni.browser-state.wrapping-key.v1\0", "utf8").update(canonicalJson2(scope), "utf8").digest();
33738
+ return createHmac6("sha256", root(rootKey)).update("opengeni.browser-state.wrapping-key.v1\0", "utf8").update(canonicalJson2(scope), "utf8").digest();
33743
33739
  }
33744
33740
  function browserStateObjectKeyValue(value, workspaceId) {
33745
33741
  if (typeof value !== "string" || Buffer.byteLength(value) > 2048 || !new RegExp(
@@ -33805,7 +33801,7 @@ function canonicalValue(value) {
33805
33801
  }
33806
33802
 
33807
33803
  // src/browser-auth-broker.ts
33808
- import { createHmac as createHmac6 } from "crypto";
33804
+ import { createHmac as createHmac7 } from "crypto";
33809
33805
  var BrowserAuthCredentialError = class extends Error {
33810
33806
  name = "BrowserAuthCredentialError";
33811
33807
  };
@@ -33856,7 +33852,7 @@ function totpCode(secretValue, options = {}) {
33856
33852
  const secret = decodeBase32(uri?.secret ?? secretValue);
33857
33853
  const counter = Buffer.alloc(8);
33858
33854
  counter.writeBigUInt64BE(BigInt(Math.floor(nowMs / (periodSeconds * 1e3))));
33859
- const digest2 = createHmac6(algorithm, secret).update(counter).digest();
33855
+ const digest2 = createHmac7(algorithm, secret).update(counter).digest();
33860
33856
  const offset = digest2[digest2.length - 1] & 15;
33861
33857
  const binary = (digest2[offset] & 127) << 24 | digest2[offset + 1] << 16 | digest2[offset + 2] << 8 | digest2[offset + 3];
33862
33858
  const code = String(binary % 10 ** digits).padStart(digits, "0");
@@ -34079,7 +34075,7 @@ function safeControlRequestId(value) {
34079
34075
  }
34080
34076
 
34081
34077
  // src/interaction-frame-proxy.ts
34082
- import { createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2, createHmac as createHmac7, randomBytes as randomBytes8 } from "crypto";
34078
+ import { createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2, createHmac as createHmac8, randomBytes as randomBytes8 } from "crypto";
34083
34079
  import { canonicalPublicOrigin as canonicalPublicOrigin2 } from "@opengeni/config";
34084
34080
  import { WebSocket as UpstreamWebSocket } from "ws";
34085
34081
  var INTERACTION_FRAME_PROXY_PATH = "/v1/interaction/frame-proxy";
@@ -34301,7 +34297,7 @@ function decryptGrant(token, rootSecret) {
34301
34297
  }
34302
34298
  function tokenKey(rootSecret) {
34303
34299
  if (rootSecret.length === 0) throw new Error("proxy authority is empty");
34304
- return createHmac7("sha256", rootSecret).update(TOKEN_KEY_CONTEXT).digest();
34300
+ return createHmac8("sha256", rootSecret).update(TOKEN_KEY_CONTEXT).digest();
34305
34301
  }
34306
34302
  function validateGrant(value) {
34307
34303
  if (!value || typeof value !== "object") throw new Error("invalid proxy grant");
@@ -40261,14 +40257,6 @@ function registerGitHubRoutes(app, deps) {
40261
40257
  if (candidates.length === 0) {
40262
40258
  return redirectToGitHubInstallation(c, deps, selectionState);
40263
40259
  }
40264
- if (candidates.length === 1) {
40265
- return redirectToExactGitHubAuthorization(
40266
- c,
40267
- deps,
40268
- selectionState,
40269
- candidates[0].installation.installationId
40270
- );
40271
- }
40272
40260
  setGitHubStateCookie(c, deps, selectionState);
40273
40261
  return c.html(
40274
40262
  githubInstallationChooserHtml(
@@ -44161,16 +44149,18 @@ import {
44161
44149
  UpdatePrReviewRepositoryBindingRequest
44162
44150
  } from "@opengeni/contracts";
44163
44151
  import {
44164
- assertWorkspaceModelPolicyAllows,
44165
- canonicalConfiguredModel,
44152
+ assertWorkspaceModelPolicyAllows as assertWorkspaceModelPolicyAllows2,
44153
+ canonicalConfiguredModel as canonicalConfiguredModel2,
44166
44154
  defaultPrReviewProviderBaseUrl,
44167
44155
  getCapabilityPack as getCapabilityPack2,
44156
+ workspaceCustomModelReference as workspaceCustomModelReference2,
44168
44157
  prReviewWebhookAuthKind,
44169
44158
  normalizePrReviewProviderBaseUrl,
44170
44159
  prReviewPackConnectorId as prReviewPackConnectorId2,
44171
44160
  PR_REVIEW_AUTOMATION_TEMPLATE_ID as PR_REVIEW_AUTOMATION_TEMPLATE_ID2,
44172
44161
  requireAccessGrant as requireAccessGrant23,
44173
- requirePermission as requirePermission6
44162
+ requirePermission as requirePermission6,
44163
+ resolveWorkspaceCatalogSettings as resolveWorkspaceCatalogSettings3
44174
44164
  } from "@opengeni/core";
44175
44165
  import {
44176
44166
  createPrReviewAppRegistration,
@@ -44183,6 +44173,8 @@ import {
44183
44173
  getPackInstallation as getPackInstallation2,
44184
44174
  listPrReviewAppRegistrations as listPrReviewAppRegistrations2,
44185
44175
  listPrReviewRepositoryBindings as listPrReviewRepositoryBindings2,
44176
+ lockActiveWorkspaceGatewayCustomModelForAdmission as lockActiveWorkspaceGatewayCustomModelForAdmission2,
44177
+ lockActiveWorkspaceOpenRouterCustomModelForAdmission as lockActiveWorkspaceOpenRouterCustomModelForAdmission2,
44186
44178
  nestedPostgresSqlState as nestedPostgresSqlState3,
44187
44179
  recordAuditEvent as recordAuditEvent9,
44188
44180
  updatePrReviewAppRegistration,
@@ -44435,7 +44427,7 @@ import {
44435
44427
  TriggerAutomationManuallyRequest,
44436
44428
  UpdateAutomationSourceRequest,
44437
44429
  UpdateAutomationTriggerRequest,
44438
- stableJson as stableJson2
44430
+ stableJson as stableJson3
44439
44431
  } from "@opengeni/contracts";
44440
44432
  import {
44441
44433
  AutomationDeliveryConflictError,
@@ -44451,17 +44443,25 @@ import {
44451
44443
  listAutomationRuns,
44452
44444
  listAutomationSources,
44453
44445
  listAutomationTriggers,
44446
+ lockActiveWorkspaceGatewayCustomModelForAdmission,
44447
+ lockActiveWorkspaceOpenRouterCustomModelForAdmission,
44454
44448
  recordAutomationEvent,
44455
44449
  resolveAutomationWebhookEndpoint,
44456
44450
  updateAutomationSource,
44457
- updateAutomationTrigger
44451
+ updateAutomationTrigger,
44452
+ withWorkspaceGatewayCustomModelReadLock,
44453
+ withWorkspaceOpenRouterCustomModelReadLock
44458
44454
  } from "@opengeni/db";
44459
44455
  import {
44460
44456
  automationRequestDigest,
44457
+ assertWorkspaceModelPolicyAllows,
44461
44458
  buildAutomationAcceptedExecution,
44459
+ canonicalConfiguredModel,
44460
+ workspaceCustomModelReference,
44462
44461
  requireAccessGrant as requireAccessGrant21,
44463
44462
  requireAutomationAdapter,
44464
- requirePermission as requirePermission4
44463
+ requirePermission as requirePermission4,
44464
+ resolveWorkspaceCatalogSettings as resolveWorkspaceCatalogSettings2
44465
44465
  } from "@opengeni/core";
44466
44466
  import { HTTPException as HTTPException43 } from "hono/http-exception";
44467
44467
  function registerAutomationRoutes(app, deps) {
@@ -44574,13 +44574,32 @@ function registerAutomationRoutes(app, deps) {
44574
44574
  for (const permission of request.sessionTemplate.firstPartyMcpPermissions) {
44575
44575
  requirePermission4(grant, permission);
44576
44576
  }
44577
+ const catalogSettings = (await resolveWorkspaceCatalogSettings2(deps.db, deps.settings, {
44578
+ accountId: grant.accountId,
44579
+ workspaceId
44580
+ })).settings;
44581
+ const model = canonicalConfiguredModel(catalogSettings, request.sessionTemplate.model) ?? null;
44582
+ if (model) {
44583
+ await assertWorkspaceModelPolicyAllows(deps.db, catalogSettings, workspaceId, model);
44584
+ }
44585
+ const normalizedRequest = {
44586
+ ...request,
44587
+ sessionTemplate: { ...request.sessionTemplate, model }
44588
+ };
44589
+ const beforeCreateCommit = model ? workspaceCustomModelCommitGuard({
44590
+ settings: catalogSettings,
44591
+ accountId: grant.accountId,
44592
+ workspaceId,
44593
+ modelId: model
44594
+ }) : void 0;
44577
44595
  return c.json(
44578
44596
  await createAutomationTrigger(deps.db, {
44579
44597
  accountId: grant.accountId,
44580
44598
  workspaceId,
44581
44599
  createdBySubjectId: grant.subjectId,
44582
- request,
44583
- adapterId: adapter.id
44600
+ request: normalizedRequest,
44601
+ adapterId: adapter.id,
44602
+ ...beforeCreateCommit ? { beforeCreateCommit } : {}
44584
44603
  }),
44585
44604
  201
44586
44605
  );
@@ -44610,12 +44629,40 @@ function registerAutomationRoutes(app, deps) {
44610
44629
  requirePermission4(grant, permission);
44611
44630
  }
44612
44631
  }
44632
+ const catalogSettings = (await resolveWorkspaceCatalogSettings2(deps.db, deps.settings, {
44633
+ accountId: grant.accountId,
44634
+ workspaceId
44635
+ })).settings;
44636
+ const normalizedRequest = request.sessionTemplate ? {
44637
+ ...request,
44638
+ sessionTemplate: {
44639
+ ...request.sessionTemplate,
44640
+ model: canonicalConfiguredModel(catalogSettings, request.sessionTemplate.model) ?? null
44641
+ }
44642
+ } : request;
44643
+ if (normalizedRequest.sessionTemplate?.model) {
44644
+ await assertWorkspaceModelPolicyAllows(
44645
+ deps.db,
44646
+ catalogSettings,
44647
+ workspaceId,
44648
+ normalizedRequest.sessionTemplate.model
44649
+ );
44650
+ }
44651
+ const effectiveModel = normalizedRequest.sessionTemplate ? normalizedRequest.sessionTemplate.model : existing.sessionTemplate.model;
44652
+ const materialExecutionChange = request.eventTypes !== void 0 || request.configuration !== void 0 || request.parameters !== void 0 || request.sessionTemplate !== void 0 || request.status === "active" && existing.status !== "active";
44653
+ const beforeUpdateCommit = materialExecutionChange && effectiveModel ? workspaceCustomModelCommitGuard({
44654
+ settings: catalogSettings,
44655
+ accountId: grant.accountId,
44656
+ workspaceId,
44657
+ modelId: effectiveModel
44658
+ }) : void 0;
44613
44659
  try {
44614
44660
  const trigger = await updateAutomationTrigger(deps.db, {
44615
44661
  workspaceId,
44616
44662
  triggerId: c.req.param("triggerId"),
44617
44663
  subjectId: grant.subjectId,
44618
- request
44664
+ request: normalizedRequest,
44665
+ ...beforeUpdateCommit ? { beforeUpdateCommit } : {}
44619
44666
  });
44620
44667
  if (!trigger)
44621
44668
  throw new HTTPException43(404, {
@@ -44689,7 +44736,7 @@ function registerAutomationRoutes(app, deps) {
44689
44736
  resource: request.resource,
44690
44737
  payload: request.payload
44691
44738
  });
44692
- const bytes = new TextEncoder().encode(stableJson2(request));
44739
+ const bytes = new TextEncoder().encode(stableJson3(request));
44693
44740
  return c.json(
44694
44741
  await acceptAutomationEvent(deps, source, {
44695
44742
  deliveryKey: request.deliveryId ?? `manual:${automationRequestDigest(source.adapterId, bytes)}`,
@@ -44775,29 +44822,78 @@ async function acceptAutomationEvent(deps, source, input) {
44775
44822
  workspaceId: source.workspaceId,
44776
44823
  sourceId: source.id
44777
44824
  });
44778
- const matchingAtAcceptance = triggers.filter(
44825
+ const matchingByEvent = triggers.filter(
44779
44826
  (trigger) => adapter.matches({ event: input.normalizedEvent, trigger })
44780
44827
  );
44781
- if (matchingAtAcceptance.length > AUTOMATION_MAX_MATCHED_TRIGGERS) {
44828
+ if (matchingByEvent.length > AUTOMATION_MAX_MATCHED_TRIGGERS) {
44782
44829
  throw new HTTPException43(422, {
44783
44830
  message: `automation event matches more than ${AUTOMATION_MAX_MATCHED_TRIGGERS} triggers`
44784
44831
  });
44785
44832
  }
44786
- const stored = await recordAutomationEvent(deps.db, {
44833
+ const stored = matchingByEvent.length === 0 ? await recordAutomationEvent(deps.db, {
44787
44834
  accountId: source.accountId,
44788
44835
  workspaceId: source.workspaceId,
44789
44836
  sourceId: source.id,
44790
44837
  sourceVersion: source.version,
44791
44838
  sourceConfiguration: source.configuration,
44792
- matchedTriggerRevisions: matchingAtAcceptance.map((trigger) => ({
44793
- triggerId: trigger.id,
44794
- revision: trigger.revision
44795
- })),
44839
+ matchedTriggerRevisions: [],
44796
44840
  deliveryKey: input.deliveryKey,
44797
44841
  requestDigest: input.requestDigest,
44798
44842
  normalizedEvent: input.normalizedEvent,
44799
- ignoredReason: matchingAtAcceptance.length === 0 ? "no_matching_triggers" : null
44800
- });
44843
+ ignoredReason: "no_matching_triggers"
44844
+ }) : await withWorkspaceGatewayCustomModelReadLock(
44845
+ deps.db,
44846
+ { accountId: source.accountId, workspaceId: source.workspaceId },
44847
+ async (gatewayLockedDb) => await withWorkspaceOpenRouterCustomModelReadLock(
44848
+ gatewayLockedDb,
44849
+ { accountId: source.accountId, workspaceId: source.workspaceId },
44850
+ async (lockedDb) => {
44851
+ const catalogSourceSettings = deps.catalogSourceSettings ?? deps.settings;
44852
+ const catalogSettings = (await resolveWorkspaceCatalogSettings2(lockedDb, catalogSourceSettings, {
44853
+ accountId: source.accountId,
44854
+ workspaceId: source.workspaceId
44855
+ })).settings;
44856
+ const matchingAtAcceptance = [];
44857
+ for (const trigger of matchingByEvent) {
44858
+ try {
44859
+ const render = adapter.render({
44860
+ event: input.normalizedEvent,
44861
+ trigger,
44862
+ source
44863
+ });
44864
+ const requestedModel = render.sessionTemplate.model ?? catalogSettings.openaiModel;
44865
+ const model = canonicalConfiguredModel(catalogSettings, requestedModel);
44866
+ if (!model) continue;
44867
+ await assertWorkspaceModelPolicyAllows(
44868
+ lockedDb,
44869
+ catalogSettings,
44870
+ source.workspaceId,
44871
+ model
44872
+ );
44873
+ matchingAtAcceptance.push(trigger);
44874
+ } catch (error) {
44875
+ if (isUnprocessableEntity(error)) continue;
44876
+ throw error;
44877
+ }
44878
+ }
44879
+ return await recordAutomationEvent(lockedDb, {
44880
+ accountId: source.accountId,
44881
+ workspaceId: source.workspaceId,
44882
+ sourceId: source.id,
44883
+ sourceVersion: source.version,
44884
+ sourceConfiguration: source.configuration,
44885
+ matchedTriggerRevisions: matchingAtAcceptance.map((trigger) => ({
44886
+ triggerId: trigger.id,
44887
+ revision: trigger.revision
44888
+ })),
44889
+ deliveryKey: input.deliveryKey,
44890
+ requestDigest: input.requestDigest,
44891
+ normalizedEvent: input.normalizedEvent,
44892
+ ignoredReason: matchingAtAcceptance.length === 0 ? "no_executable_triggers" : null
44893
+ });
44894
+ }
44895
+ )
44896
+ );
44801
44897
  const matching = await getAutomationTriggerRevisions(deps.db, {
44802
44898
  workspaceId: source.workspaceId,
44803
44899
  refs: stored.event.matchedTriggerRevisions
@@ -44854,6 +44950,29 @@ async function acceptAutomationEvent(deps, source, input) {
44854
44950
  runIds
44855
44951
  });
44856
44952
  }
44953
+ function isUnprocessableEntity(error) {
44954
+ return error instanceof Error && "status" in error && error.status === 422;
44955
+ }
44956
+ function workspaceCustomModelCommitGuard(input) {
44957
+ const reference = workspaceCustomModelReference(input.settings, input.modelId);
44958
+ if (!reference) return void 0;
44959
+ return async (tx) => {
44960
+ const active = reference.providerKind === "openrouter" ? await lockActiveWorkspaceOpenRouterCustomModelForAdmission(tx, {
44961
+ accountId: input.accountId,
44962
+ workspaceId: input.workspaceId,
44963
+ upstreamModelId: reference.upstreamModelId
44964
+ }) : await lockActiveWorkspaceGatewayCustomModelForAdmission(tx, {
44965
+ accountId: input.accountId,
44966
+ workspaceId: input.workspaceId,
44967
+ upstreamModelId: reference.upstreamModelId
44968
+ });
44969
+ if (!active) {
44970
+ throw new HTTPException43(422, {
44971
+ message: `model is not available: ${input.modelId}`
44972
+ });
44973
+ }
44974
+ };
44975
+ }
44857
44976
  async function requireSource(deps, accountId, workspaceId, sourceId) {
44858
44977
  const source = await getAutomationSourceSecret(deps.db, {
44859
44978
  accountId,
@@ -45826,8 +45945,18 @@ function registerPrReviewRoutes(app, deps) {
45826
45945
  throw error;
45827
45946
  }
45828
45947
  }
45829
- const model = payload.model ? canonicalConfiguredModel(settings, payload.model) ?? null : null;
45830
- if (model) await assertWorkspaceModelPolicyAllows(db, settings, workspaceId, model);
45948
+ const catalogSettings = (await resolveWorkspaceCatalogSettings3(db, deps.settings, {
45949
+ accountId: grant.accountId,
45950
+ workspaceId
45951
+ })).settings;
45952
+ const model = payload.model ? canonicalConfiguredModel2(catalogSettings, payload.model) ?? null : null;
45953
+ if (model) await assertWorkspaceModelPolicyAllows2(db, catalogSettings, workspaceId, model);
45954
+ const beforeCreateCommit = model ? prReviewCustomModelCommitGuard({
45955
+ settings: catalogSettings,
45956
+ accountId: grant.accountId,
45957
+ workspaceId,
45958
+ modelId: model
45959
+ }) : void 0;
45831
45960
  const template = getCapabilityPack2(OPENGENI_PR_REVIEW_PACK_ID2)?.automationTemplates?.find(
45832
45961
  (candidate) => candidate.id === PR_REVIEW_AUTOMATION_TEMPLATE_ID2
45833
45962
  );
@@ -45854,7 +45983,8 @@ function registerPrReviewRoutes(app, deps) {
45854
45983
  adapterId: template.adapterId,
45855
45984
  eventTypes: template.eventTypes,
45856
45985
  configuration: template.configuration,
45857
- sessionTemplate: template.sessionTemplate
45986
+ sessionTemplate: template.sessionTemplate,
45987
+ ...beforeCreateCommit ? { beforeCreateCommit } : {}
45858
45988
  }),
45859
45989
  "This repository is already bound to the selected PR Review registration"
45860
45990
  );
@@ -45878,8 +46008,22 @@ function registerPrReviewRoutes(app, deps) {
45878
46008
  const grant = await requireAccessGrant23(c, deps, workspaceId, "workspace:admin");
45879
46009
  await requirePrReviewPackActive(db, workspaceId);
45880
46010
  const payload = UpdatePrReviewRepositoryBindingRequest.parse(await c.req.json());
45881
- const model = payload.model === void 0 ? void 0 : payload.model === null ? null : canonicalConfiguredModel(settings, payload.model) ?? null;
45882
- if (model) await assertWorkspaceModelPolicyAllows(db, settings, workspaceId, model);
46011
+ const catalogSettings = (await resolveWorkspaceCatalogSettings3(db, deps.settings, {
46012
+ accountId: grant.accountId,
46013
+ workspaceId
46014
+ })).settings;
46015
+ const model = payload.model === void 0 ? void 0 : payload.model === null ? null : canonicalConfiguredModel2(catalogSettings, payload.model) ?? null;
46016
+ if (model) await assertWorkspaceModelPolicyAllows2(db, catalogSettings, workspaceId, model);
46017
+ const beforeUpdateCommit = async (tx, context) => {
46018
+ const materialExecutionChange = payload.model !== void 0 || payload.additionalInstructions !== void 0 || context.currentStatus === "disabled" && context.nextStatus === "active";
46019
+ if (!materialExecutionChange || !context.nextModel) return;
46020
+ await prReviewCustomModelCommitGuard({
46021
+ settings: catalogSettings,
46022
+ accountId: grant.accountId,
46023
+ workspaceId,
46024
+ modelId: context.nextModel
46025
+ })?.(tx);
46026
+ };
45883
46027
  const binding = await updatePrReviewRepositoryBinding(db, {
45884
46028
  accountId: grant.accountId,
45885
46029
  workspaceId,
@@ -45887,7 +46031,8 @@ function registerPrReviewRoutes(app, deps) {
45887
46031
  subjectId: grant.subjectId,
45888
46032
  ...model !== void 0 ? { model } : {},
45889
46033
  ...payload.additionalInstructions !== void 0 ? { additionalInstructions: payload.additionalInstructions } : {},
45890
- ...payload.status !== void 0 ? { status: payload.status } : {}
46034
+ ...payload.status !== void 0 ? { status: payload.status } : {},
46035
+ beforeUpdateCommit
45891
46036
  });
45892
46037
  if (!binding)
45893
46038
  throw new HTTPException45(404, {
@@ -45929,6 +46074,26 @@ function registerPrReviewRoutes(app, deps) {
45929
46074
  return c.body(null, 204);
45930
46075
  });
45931
46076
  }
46077
+ function prReviewCustomModelCommitGuard(input) {
46078
+ const reference = workspaceCustomModelReference2(input.settings, input.modelId);
46079
+ if (!reference) return void 0;
46080
+ return async (tx) => {
46081
+ const active = reference.providerKind === "openrouter" ? await lockActiveWorkspaceOpenRouterCustomModelForAdmission2(tx, {
46082
+ accountId: input.accountId,
46083
+ workspaceId: input.workspaceId,
46084
+ upstreamModelId: reference.upstreamModelId
46085
+ }) : await lockActiveWorkspaceGatewayCustomModelForAdmission2(tx, {
46086
+ accountId: input.accountId,
46087
+ workspaceId: input.workspaceId,
46088
+ upstreamModelId: reference.upstreamModelId
46089
+ });
46090
+ if (!active) {
46091
+ throw new HTTPException45(422, {
46092
+ message: `model is not available: ${input.modelId}`
46093
+ });
46094
+ }
46095
+ };
46096
+ }
45932
46097
  function assertPrReviewSandboxBackend(deps) {
45933
46098
  if (deps.settings.sandboxBackend === "selfhosted") {
45934
46099
  throw new HTTPException45(409, {
@@ -45986,7 +46151,7 @@ import {
45986
46151
  RegisterCapabilityPackRequest,
45987
46152
  UninstallPackRequest,
45988
46153
  UninstallPackResult,
45989
- stableJson as stableJson3
46154
+ stableJson as stableJson4
45990
46155
  } from "@opengeni/contracts";
45991
46156
  import {
45992
46157
  adoptPackComponentReferences,
@@ -46161,7 +46326,7 @@ function registerPackRoutes(app, deps) {
46161
46326
  ...preview.variableSetId ? { variableSetId: preview.variableSetId } : {}
46162
46327
  };
46163
46328
  const requestDigest = sha2562(
46164
- stableJson3({
46329
+ stableJson4({
46165
46330
  packId: pack.id,
46166
46331
  manifestDigest: preview.manifestDigest,
46167
46332
  expectedInstallationVersion: payload.expectedInstallationVersion ?? null,
@@ -46339,7 +46504,7 @@ function registerPackRoutes(app, deps) {
46339
46504
  const pack = await requirePack(db, workspaceId, c.req.param("packId"));
46340
46505
  const payload = UninstallPackRequest.parse(await c.req.json());
46341
46506
  const requestDigest = sha2562(
46342
- stableJson3({
46507
+ stableJson4({
46343
46508
  packId: pack.id,
46344
46509
  expectedInstallationVersion: payload.expectedInstallationVersion
46345
46510
  })
@@ -46642,7 +46807,7 @@ import {
46642
46807
  PreviewPluginRequest,
46643
46808
  UninstallPluginRequest,
46644
46809
  UninstallPluginResult,
46645
- stableJson as stableJson4
46810
+ stableJson as stableJson5
46646
46811
  } from "@opengeni/contracts";
46647
46812
  import {
46648
46813
  portableSkillCapabilityId,
@@ -46849,7 +47014,7 @@ function registerPluginRoutes(app, deps, overrides = {}) {
46849
47014
  });
46850
47015
  }
46851
47016
  const requestDigest = sha2563(
46852
- stableJson4({
47017
+ stableJson5({
46853
47018
  url: payload.url,
46854
47019
  expectedManifestDigest: payload.expectedManifestDigest,
46855
47020
  expectedComponents: [...expected.entries()].sort(),
@@ -46986,14 +47151,14 @@ async function resolvePluginPackage(input) {
46986
47151
  message: error instanceof Error ? error.message : "Plugin manifest is invalid"
46987
47152
  });
46988
47153
  }
46989
- const manifestDigest = sha2563(stableJson4(manifest));
47154
+ const manifestDigest = sha2563(stableJson5(manifest));
46990
47155
  const components = [];
46991
47156
  for (const component of manifest.components) {
46992
47157
  if (component.kind === "skill") {
46993
47158
  const resolved = await resolveSkillImport(component.url, input.github);
46994
47159
  const capabilityId = portableSkillCapabilityId(resolved.preview);
46995
47160
  const digest3 = sha2563(
46996
- stableJson4({
47161
+ stableJson5({
46997
47162
  capabilityId,
46998
47163
  sourceCommit: resolved.preview.sourceCommit,
46999
47164
  sourcePath: resolved.preview.sourcePath,
@@ -47142,7 +47307,7 @@ async function resolvePluginPackage(input) {
47142
47307
  continue;
47143
47308
  }
47144
47309
  const server = configuredMcpServer(input.deps.settings.mcpServers, component.serverId);
47145
- const digest2 = sha2563(stableJson4(safeMcpFacts(server)));
47310
+ const digest2 = sha2563(stableJson5(safeMcpFacts(server)));
47146
47311
  components.push({
47147
47312
  preview: {
47148
47313
  key: component.key,
@@ -47292,7 +47457,7 @@ function safeMcpFacts(server) {
47292
47457
  }
47293
47458
  function integrationDigest(resolved) {
47294
47459
  return sha2563(
47295
- stableJson4({
47460
+ stableJson5({
47296
47461
  capabilityId: resolved.preview.capabilityId,
47297
47462
  revisionId: resolved.preview.revisionId,
47298
47463
  contentSha256: resolved.preview.contentSha256,
@@ -47961,7 +48126,7 @@ import {
47961
48126
  } from "@opengeni/contracts";
47962
48127
  import { listScheduledTaskRuns as listScheduledTaskRuns2, listScheduledTasks as listScheduledTasks2 } from "@opengeni/db";
47963
48128
  import { HTTPException as HTTPException51 } from "hono/http-exception";
47964
- import { requireAccessGrant as requireAccessGrant29 } from "@opengeni/core";
48129
+ import { requireAccessGrant as requireAccessGrant29, resolveWorkspaceCatalogSettings as resolveWorkspaceCatalogSettings4 } from "@opengeni/core";
47965
48130
  import { recordWorkspaceUsage as recordWorkspaceUsage6, requireLimit as requireLimit8 } from "@opengeni/core";
47966
48131
  import {
47967
48132
  captureScheduledTaskRestoreState as captureScheduledTaskRestoreState4,
@@ -47981,7 +48146,7 @@ import {
47981
48146
  validatedScheduledTaskUpdate as validatedScheduledTaskUpdate2
47982
48147
  } from "@opengeni/core";
47983
48148
  function registerScheduledTaskRoutes(app, deps) {
47984
- const { settings, db, workflowClient, objectStorage } = deps;
48149
+ const { db, workflowClient, objectStorage } = deps;
47985
48150
  app.post("/v1/workspaces/:workspaceId/scheduled-tasks", async (c) => {
47986
48151
  const workspaceId = c.req.param("workspaceId");
47987
48152
  const grant = await requireAccessGrant29(c, deps, workspaceId, "scheduled_tasks:manage");
@@ -47993,6 +48158,10 @@ function registerScheduledTaskRoutes(app, deps) {
47993
48158
  });
47994
48159
  }
47995
48160
  const payload = parsedPayload.data;
48161
+ const catalogSettings = (await resolveWorkspaceCatalogSettings4(db, deps.settings, {
48162
+ accountId: grant.accountId,
48163
+ workspaceId
48164
+ })).settings;
47996
48165
  await requireLimit8(deps, {
47997
48166
  accountId: grant.accountId,
47998
48167
  workspaceId,
@@ -48000,7 +48169,7 @@ function registerScheduledTaskRoutes(app, deps) {
48000
48169
  quantity: 1
48001
48170
  });
48002
48171
  const task = await createValidatedScheduledTask5({
48003
- settings,
48172
+ settings: catalogSettings,
48004
48173
  db,
48005
48174
  objectStorage,
48006
48175
  grant,
@@ -48038,8 +48207,12 @@ function registerScheduledTaskRoutes(app, deps) {
48038
48207
  });
48039
48208
  }
48040
48209
  const payload = parsedPayload.data;
48210
+ const catalogSettings = (await resolveWorkspaceCatalogSettings4(db, deps.settings, {
48211
+ accountId: grant.accountId,
48212
+ workspaceId
48213
+ })).settings;
48041
48214
  const update = await validatedScheduledTaskUpdate2({
48042
- settings,
48215
+ settings: catalogSettings,
48043
48216
  db,
48044
48217
  objectStorage,
48045
48218
  grant,
@@ -48069,8 +48242,12 @@ function registerScheduledTaskRoutes(app, deps) {
48069
48242
  const grant = await requireAccessGrant29(c, deps, workspaceId, "scheduled_tasks:manage");
48070
48243
  const existing = await requireScheduledTaskForApi(db, workspaceId, c.req.param("taskId"));
48071
48244
  const previous = await captureScheduledTaskRestoreState4(db, existing);
48245
+ const catalogSettings = (await resolveWorkspaceCatalogSettings4(db, deps.settings, {
48246
+ accountId: grant.accountId,
48247
+ workspaceId
48248
+ })).settings;
48072
48249
  const update = await validatedScheduledTaskUpdate2({
48073
- settings,
48250
+ settings: catalogSettings,
48074
48251
  db,
48075
48252
  objectStorage,
48076
48253
  grant,
@@ -48088,6 +48265,10 @@ function registerScheduledTaskRoutes(app, deps) {
48088
48265
  const grant = await requireAccessGrant29(c, deps, workspaceId, "scheduled_tasks:run");
48089
48266
  const task = await requireScheduledTaskForApi(db, workspaceId, c.req.param("taskId"));
48090
48267
  if (task.action.kind === "agent_turn") {
48268
+ const catalogSettings = (await resolveWorkspaceCatalogSettings4(db, deps.settings, {
48269
+ accountId: grant.accountId,
48270
+ workspaceId
48271
+ })).settings;
48091
48272
  await validateScheduledTaskTarget2({
48092
48273
  db,
48093
48274
  sessionAuthorization: deps.sessionAuthorization,
@@ -48101,20 +48282,23 @@ function registerScheduledTaskRoutes(app, deps) {
48101
48282
  missingTargetStatus: 404
48102
48283
  });
48103
48284
  await validateScheduledTaskMachineTarget2({
48104
- settings,
48285
+ settings: catalogSettings,
48105
48286
  db,
48106
48287
  grant,
48107
48288
  runMode: task.runMode,
48108
48289
  agentConfig: task.agentConfig,
48109
48290
  requireOnline: true
48110
48291
  });
48111
- await requireLimit8(deps, {
48112
- accountId: grant.accountId,
48113
- workspaceId,
48114
- action: "agent_run:create",
48115
- quantity: 1,
48116
- model: task.agentConfig.model ?? deps.settings.openaiModel
48117
- });
48292
+ await requireLimit8(
48293
+ { ...deps, settings: catalogSettings },
48294
+ {
48295
+ accountId: grant.accountId,
48296
+ workspaceId,
48297
+ action: "agent_run:create",
48298
+ quantity: 1,
48299
+ model: task.agentConfig.model ?? catalogSettings.openaiModel
48300
+ }
48301
+ );
48118
48302
  }
48119
48303
  const body4 = await c.req.json().catch(() => ({}));
48120
48304
  const { triggerId } = TriggerScheduledTaskRequest.parse(body4 ?? {});
@@ -48181,6 +48365,7 @@ import {
48181
48365
  GatewayRealtimeConnectRequest,
48182
48366
  ClientSessionEvent,
48183
48367
  CompactSessionContextRequest,
48368
+ CreateSessionRequest,
48184
48369
  DeleteSessionQueueItemRequest,
48185
48370
  EditSessionQueueItemRequest,
48186
48371
  EndSessionRealtimeRequest,
@@ -48216,6 +48401,7 @@ import {
48216
48401
  SessionEventSemanticClass as SessionEventSemanticClass2,
48217
48402
  SessionEventType as SessionEventType2,
48218
48403
  SessionMcpServerId,
48404
+ SaveNewSessionDraftRequest,
48219
48405
  compactSessionEventResult as compactSessionEventResult3,
48220
48406
  sessionEventLatestClassToSemanticClass as sessionEventLatestClassToSemanticClass2,
48221
48407
  SaveComposerDraftRequest,
@@ -49096,6 +49282,7 @@ import {
49096
49282
  requirePermission as requirePermission8,
49097
49283
  requireSessionAuthorization as requireSessionAuthorization7,
49098
49284
  requireSessionAuthorizationListScope as requireSessionAuthorizationListScope2,
49285
+ resolveWorkspaceCatalogSettings as resolveWorkspaceCatalogSettings5,
49099
49286
  withResolvedSessionAuthorization,
49100
49287
  SESSION_AUTHORIZATION_DEFAULT_REAUTHORIZE_MS,
49101
49288
  SessionAuthorizationDeniedError as SessionAuthorizationDeniedError7,
@@ -49552,6 +49739,7 @@ function registerSessionRoutes(app, deps) {
49552
49739
  }
49553
49740
  let session;
49554
49741
  try {
49742
+ CreateSessionRequest.parse(payload);
49555
49743
  session = await createSessionForRequest(deps, grant, workspaceId, payload, authorization);
49556
49744
  } catch (error) {
49557
49745
  return sessionCreateErrorResponse(c, error);
@@ -49561,7 +49749,13 @@ function registerSessionRoutes(app, deps) {
49561
49749
  app.get("/v1/workspaces/:workspaceId/new-session-draft", async (c) => {
49562
49750
  const workspaceId = c.req.param("workspaceId");
49563
49751
  const grant = await requireAccessGrant30(c, deps, workspaceId, "sessions:read");
49564
- return c.json(await getActorNewSessionDraft({ settings, db }, grant, workspaceId));
49752
+ const catalog = await resolveWorkspaceCatalogSettings5(db, settings, {
49753
+ accountId: grant.accountId,
49754
+ workspaceId
49755
+ });
49756
+ return c.json(
49757
+ await getActorNewSessionDraft({ settings: catalog.settings, db }, grant, workspaceId)
49758
+ );
49565
49759
  });
49566
49760
  app.put("/v1/workspaces/:workspaceId/new-session-draft", async (c) => {
49567
49761
  const workspaceId = c.req.param("workspaceId");
@@ -49585,9 +49779,14 @@ function registerSessionRoutes(app, deps) {
49585
49779
  );
49586
49780
  }
49587
49781
  try {
49782
+ SaveNewSessionDraftRequest.parse(payload);
49783
+ const catalog = await resolveWorkspaceCatalogSettings5(db, settings, {
49784
+ accountId: grant.accountId,
49785
+ workspaceId
49786
+ });
49588
49787
  return c.json(
49589
49788
  await saveActorNewSessionDraft(
49590
- { settings, db, objectStorage },
49789
+ { settings: catalog.settings, db, objectStorage },
49591
49790
  grant,
49592
49791
  workspaceId,
49593
49792
  payload,
@@ -51734,24 +51933,29 @@ function registerSessionRoutes(app, deps) {
51734
51933
  const sessionId = c.req.param("sessionId");
51735
51934
  await assertSessionExists(db, workspaceId, sessionId);
51736
51935
  const payload = parseSteerSessionAdmission(await c.req.json().catch(() => null));
51737
- const result = await acceptSessionUserMessage(deps, grant, workspaceId, sessionId, {
51738
- text: payload.text,
51739
- annotations: payload.annotations,
51740
- modelContext: payload.modelContext ?? null,
51741
- resources: payload.resources,
51742
- model: payload.model ?? null,
51743
- reasoningEffort: payload.reasoningEffort ?? null,
51744
- latencyMode: payload.latencyMode ?? null,
51745
- mcpCredentialUpdates: payload.mcpCredentialUpdates ?? [],
51746
- connectionAuthorities: payload.connectionAuthorities,
51747
- ...payload.personalResourceAttachment ? { personalResourceAttachment: payload.personalResourceAttachment } : {},
51748
- authorization,
51749
- delivery: "steer",
51750
- origin: "human",
51751
- ...payload.controlEtag !== void 0 ? { controlEtag: payload.controlEtag } : {},
51752
- ...payload.expectedDraftRevision !== void 0 ? { expectedDraftRevision: payload.expectedDraftRevision } : {},
51753
- ...payload.clientEventId ? { clientEventId: payload.clientEventId } : {}
51754
- });
51936
+ let result;
51937
+ try {
51938
+ result = await acceptSessionUserMessage(deps, grant, workspaceId, sessionId, {
51939
+ text: payload.text,
51940
+ annotations: payload.annotations,
51941
+ modelContext: payload.modelContext ?? null,
51942
+ resources: payload.resources,
51943
+ model: payload.model ?? null,
51944
+ reasoningEffort: payload.reasoningEffort ?? null,
51945
+ latencyMode: payload.latencyMode ?? null,
51946
+ mcpCredentialUpdates: payload.mcpCredentialUpdates ?? [],
51947
+ connectionAuthorities: payload.connectionAuthorities,
51948
+ ...payload.personalResourceAttachment ? { personalResourceAttachment: payload.personalResourceAttachment } : {},
51949
+ authorization,
51950
+ delivery: "steer",
51951
+ origin: "human",
51952
+ ...payload.controlEtag !== void 0 ? { controlEtag: payload.controlEtag } : {},
51953
+ ...payload.expectedDraftRevision !== void 0 ? { expectedDraftRevision: payload.expectedDraftRevision } : {},
51954
+ ...payload.clientEventId ? { clientEventId: payload.clientEventId } : {}
51955
+ });
51956
+ } catch (error) {
51957
+ return commandConflictResponse(c, error);
51958
+ }
51755
51959
  return c.json(result, 202);
51756
51960
  });
51757
51961
  app.post("/v1/workspaces/:workspaceId/sessions/:sessionId/composer-draft/submit", async (c) => {
@@ -51800,23 +52004,28 @@ function registerSessionRoutes(app, deps) {
51800
52004
  }
51801
52005
  }
51802
52006
  if (event.type === "user.message") {
51803
- const { accepted } = await acceptSessionUserMessage(deps, grant, workspaceId, sessionId, {
51804
- text: event.payload.text,
51805
- annotations: event.payload.annotations,
51806
- modelContext: event.payload.modelContext ?? null,
51807
- resources: event.payload.resources ?? [],
51808
- model: event.payload.model ?? null,
51809
- reasoningEffort: event.payload.reasoningEffort ?? null,
51810
- latencyMode: event.payload.latencyMode ?? null,
51811
- mcpCredentialUpdates: event.payload.mcpCredentialUpdates ?? [],
51812
- connectionAuthorities: event.payload.connectionAuthorities,
51813
- ...event.payload.personalResourceAttachment ? { personalResourceAttachment: event.payload.personalResourceAttachment } : {},
51814
- authorization,
51815
- ...event.payload.controlEtag !== void 0 ? { controlEtag: event.payload.controlEtag } : {},
51816
- ...event.payload.expectedDraftRevision !== void 0 ? { expectedDraftRevision: event.payload.expectedDraftRevision } : {},
51817
- ...event.clientEventId ? { clientEventId: event.clientEventId } : {}
51818
- });
51819
- return c.json(accepted, 202);
52007
+ let result;
52008
+ try {
52009
+ result = await acceptSessionUserMessage(deps, grant, workspaceId, sessionId, {
52010
+ text: event.payload.text,
52011
+ annotations: event.payload.annotations,
52012
+ modelContext: event.payload.modelContext ?? null,
52013
+ resources: event.payload.resources ?? [],
52014
+ model: event.payload.model ?? null,
52015
+ reasoningEffort: event.payload.reasoningEffort ?? null,
52016
+ latencyMode: event.payload.latencyMode ?? null,
52017
+ mcpCredentialUpdates: event.payload.mcpCredentialUpdates ?? [],
52018
+ connectionAuthorities: event.payload.connectionAuthorities,
52019
+ ...event.payload.personalResourceAttachment ? { personalResourceAttachment: event.payload.personalResourceAttachment } : {},
52020
+ authorization,
52021
+ ...event.payload.controlEtag !== void 0 ? { controlEtag: event.payload.controlEtag } : {},
52022
+ ...event.payload.expectedDraftRevision !== void 0 ? { expectedDraftRevision: event.payload.expectedDraftRevision } : {},
52023
+ ...event.clientEventId ? { clientEventId: event.clientEventId } : {}
52024
+ });
52025
+ } catch (error) {
52026
+ return commandConflictResponse(c, error);
52027
+ }
52028
+ return c.json(result.accepted, 202);
51820
52029
  }
51821
52030
  if (event.type === "user.approvalDecision") {
51822
52031
  const accepted = await acceptSessionApprovalDecision2(db, {
@@ -53674,6 +53883,7 @@ function socialHttpException(error) {
53674
53883
  }
53675
53884
 
53676
53885
  // src/routes/workspaces.ts
53886
+ import { createHash as createHash16 } from "crypto";
53677
53887
  import {
53678
53888
  AddWorkspaceMemberRequest,
53679
53889
  CreateWorkspaceRequest,
@@ -53683,16 +53893,25 @@ import {
53683
53893
  ListWorkspaceMembersResponse,
53684
53894
  SetWorkspaceDefaultRigRequest,
53685
53895
  UpdateWorkspaceMemberRequest,
53896
+ CreateWorkspaceGatewayCustomModelRequest,
53897
+ CreateWorkspaceOpenRouterCustomModelRequest,
53898
+ DeleteWorkspaceGatewayCustomModelRequest,
53899
+ DeleteWorkspaceOpenRouterCustomModelRequest,
53686
53900
  UpdateWorkspaceModelPolicyRequest,
53687
53901
  UpdateWorkspaceRequest,
53688
53902
  UpdateWorkspaceSettingsRequest,
53689
53903
  WORKSPACE_CONTROL_ACTOR_MAX_BYTES as WORKSPACE_CONTROL_ACTOR_MAX_BYTES2,
53690
53904
  WorkspaceModelCatalogResponse as WorkspaceModelCatalogResponse2,
53905
+ WorkspaceGatewayCustomModel,
53906
+ WorkspaceGatewayCustomModelsResponse,
53907
+ WorkspaceOpenRouterCustomModel,
53908
+ WorkspaceOpenRouterCustomModelsResponse,
53691
53909
  WorkspaceRealtimeModelCatalogResponse,
53692
53910
  WorkspaceInferenceControlRequest,
53693
53911
  Workspace,
53694
53912
  WorkspaceMember,
53695
- workspaceControlUtf8Bytes as workspaceControlUtf8Bytes2
53913
+ workspaceControlUtf8Bytes as workspaceControlUtf8Bytes2,
53914
+ stableJson as stableJson6
53696
53915
  } from "@opengeni/contracts";
53697
53916
  import {
53698
53917
  allWorkspacePermissions,
@@ -53708,6 +53927,14 @@ import {
53708
53927
  normalizeWorkspaceMembershipPermissions,
53709
53928
  listWorkspaceControlEvents as listWorkspaceControlEvents2,
53710
53929
  listSharedWorkspacesForAccount,
53930
+ listWorkspaceGatewayCustomModels,
53931
+ listWorkspaceOpenRouterCustomModels,
53932
+ createWorkspaceGatewayCustomModel,
53933
+ createWorkspaceOpenRouterCustomModel,
53934
+ deleteWorkspaceGatewayCustomModel,
53935
+ deleteWorkspaceOpenRouterCustomModel,
53936
+ replayWorkspaceGatewayCustomModelCreate,
53937
+ replayWorkspaceOpenRouterCustomModelCreate,
53711
53938
  listWorkspacesForSubject,
53712
53939
  nestedPostgresSqlState as nestedPostgresSqlState4,
53713
53940
  removeWorkspaceMember,
@@ -53722,7 +53949,12 @@ import {
53722
53949
  workspaceControlRequestLockTimeoutMs as workspaceControlRequestLockTimeoutMs2,
53723
53950
  workspaceXaiSubscriptionActive,
53724
53951
  workspaceVercelAiGatewayConnectionActive,
53952
+ workspaceOpenRouterConnectionActive,
53953
+ WorkspaceGatewayCustomModelHistoryLimitError,
53954
+ WorkspaceOpenRouterCustomModelHistoryLimitError,
53725
53955
  WorkspaceExternalIdentityConflictError,
53956
+ WorkspaceGatewayCustomModelLimitError,
53957
+ WorkspaceOpenRouterCustomModelLimitError,
53726
53958
  WorkspaceLimitExceededError
53727
53959
  } from "@opengeni/db";
53728
53960
  import { boundWorkspaceControlHttpPage } from "@opengeni/events";
@@ -53733,7 +53965,8 @@ import {
53733
53965
  hasPermission as hasPermission19,
53734
53966
  requireAccessContext as requireAccessContext3,
53735
53967
  requireAccessGrant as requireAccessGrant32,
53736
- requireFreshAccessGrant as requireFreshAccessGrant3
53968
+ requireFreshAccessGrant as requireFreshAccessGrant3,
53969
+ resolveWorkspaceCatalogSettings as resolveWorkspaceCatalogSettings6
53737
53970
  } from "@opengeni/core";
53738
53971
  import { requireLimit as requireLimit9 } from "@opengeni/core";
53739
53972
  import {
@@ -53825,7 +54058,14 @@ import {
53825
54058
  CODEX_REALTIME_MODEL_ID,
53826
54059
  SUPERGROK_REALTIME_MODEL_ID as SUPERGROK_REALTIME_MODEL_ID2,
53827
54060
  canonicalizeConfiguredModelId,
53828
- configuredStaticUsageLimits as configuredStaticUsageLimits2
54061
+ configuredStaticUsageLimits as configuredStaticUsageLimits2,
54062
+ configuredGatewayUpstreamModelIds,
54063
+ configuredGatewayWorkspaceProductModelIds,
54064
+ configuredModelInputIdentities,
54065
+ configuredOpenRouterUpstreamModelIds,
54066
+ configuredOpenRouterWorkspaceProductModelIds,
54067
+ WORKSPACE_GATEWAY_MODEL_ID_PREFIX,
54068
+ WORKSPACE_OPENROUTER_MODEL_ID_PREFIX
53829
54069
  } from "@opengeni/config";
53830
54070
  function canonicalWorkspacePolicyModelIds(settings, modelIds) {
53831
54071
  if (modelIds === null || modelIds === void 0) {
@@ -53833,6 +54073,29 @@ function canonicalWorkspacePolicyModelIds(settings, modelIds) {
53833
54073
  }
53834
54074
  return [...new Set(modelIds.map((modelId) => canonicalizeConfiguredModelId(settings, modelId)))];
53835
54075
  }
54076
+ function projectWorkspaceGatewayCustomModel(model) {
54077
+ return WorkspaceGatewayCustomModel.parse({
54078
+ id: model.id,
54079
+ upstreamModelId: model.upstreamModelId,
54080
+ label: model.label,
54081
+ version: model.version,
54082
+ createdAt: model.createdAt.toISOString(),
54083
+ updatedAt: model.updatedAt.toISOString()
54084
+ });
54085
+ }
54086
+ function projectWorkspaceOpenRouterCustomModel(model) {
54087
+ return WorkspaceOpenRouterCustomModel.parse({
54088
+ id: model.id,
54089
+ upstreamModelId: model.upstreamModelId,
54090
+ label: model.label,
54091
+ version: model.version,
54092
+ createdAt: model.createdAt.toISOString(),
54093
+ updatedAt: model.updatedAt.toISOString()
54094
+ });
54095
+ }
54096
+ function workspaceCustomModelRequestHash(value) {
54097
+ return createHash16("sha256").update(stableJson6(value), "utf8").digest("hex");
54098
+ }
53836
54099
  function workspaceMembersResponse(members) {
53837
54100
  return ListWorkspaceMembersResponse.parse({
53838
54101
  members: members.map((member) => ({
@@ -53906,7 +54169,9 @@ function registerWorkspaceRoutes(app, deps) {
53906
54169
  externalId: payload.externalId,
53907
54170
  name: payload.name,
53908
54171
  slug: payload.slug ?? null,
53909
- ...payload.agentInstructions !== void 0 ? { agentInstructions: normalizeAgentInstructions(payload.agentInstructions) } : {},
54172
+ ...payload.agentInstructions !== void 0 ? {
54173
+ agentInstructions: normalizeAgentInstructions(payload.agentInstructions)
54174
+ } : {},
53910
54175
  maxWorkspacesPerAccount: workspaceLimit(deps)
53911
54176
  });
53912
54177
  const response = EnsureWorkspaceResponse.parse(result);
@@ -54012,29 +54277,304 @@ function registerWorkspaceRoutes(app, deps) {
54012
54277
  const workspaceId = c.req.param("workspaceId");
54013
54278
  const grant = await requireAccessGrant32(c, deps, workspaceId, "workspace:read");
54014
54279
  const [
54280
+ resolvedCatalog,
54015
54281
  policy,
54016
54282
  codexSubscriptionActive,
54017
54283
  xaiSubscriptionActive,
54018
- workspaceGatewayConnectionActive
54284
+ workspaceGatewayConnectionActive,
54285
+ workspaceGatewayCustomModels,
54286
+ openRouterConnectionActive,
54287
+ workspaceOpenRouterCustomModels
54019
54288
  ] = await Promise.all([
54289
+ deps.resolveCatalogSettings(),
54020
54290
  getWorkspaceModelPolicy(deps.db, workspaceId),
54021
54291
  workspaceCodexSubscriptionActive(deps.db, deps.settings, workspaceId),
54022
54292
  workspaceXaiSubscriptionActive(deps.db, deps.settings, workspaceId, grant.subjectId),
54023
- workspaceVercelAiGatewayConnectionActive(deps.db, workspaceId)
54293
+ workspaceVercelAiGatewayConnectionActive(deps.db, workspaceId),
54294
+ listWorkspaceGatewayCustomModels(deps.db, {
54295
+ accountId: grant.accountId,
54296
+ workspaceId
54297
+ }),
54298
+ workspaceOpenRouterConnectionActive(deps.db, workspaceId),
54299
+ listWorkspaceOpenRouterCustomModels(deps.db, {
54300
+ accountId: grant.accountId,
54301
+ workspaceId
54302
+ })
54024
54303
  ]);
54025
54304
  c.header("cache-control", "private, no-store");
54026
54305
  return c.json(
54027
54306
  WorkspaceModelCatalogResponse2.parse(
54028
54307
  buildWorkspaceModelCatalog({
54029
- settings: deps.settings,
54308
+ settings: resolvedCatalog.settings,
54030
54309
  policy,
54031
54310
  codexSubscriptionActive,
54032
54311
  xaiSubscriptionActive,
54033
- workspaceGatewayConnectionActive
54312
+ workspaceGatewayConnectionActive,
54313
+ workspaceGatewayCustomModels,
54314
+ workspaceOpenRouterConnectionActive: openRouterConnectionActive,
54315
+ workspaceOpenRouterCustomModels
54034
54316
  })
54035
54317
  )
54036
54318
  );
54037
54319
  });
54320
+ app.get("/v1/workspaces/:workspaceId/gateway-custom-models", async (c) => {
54321
+ const workspaceId = c.req.param("workspaceId");
54322
+ const grant = await requireAccessGrant32(c, deps, workspaceId, "workspace:read");
54323
+ const models = await listWorkspaceGatewayCustomModels(deps.db, {
54324
+ accountId: grant.accountId,
54325
+ workspaceId
54326
+ });
54327
+ c.header("cache-control", "private, no-store");
54328
+ return c.json(
54329
+ WorkspaceGatewayCustomModelsResponse.parse({
54330
+ models: models.map(projectWorkspaceGatewayCustomModel)
54331
+ })
54332
+ );
54333
+ });
54334
+ app.post("/v1/workspaces/:workspaceId/gateway-custom-models", async (c) => {
54335
+ const workspaceId = c.req.param("workspaceId");
54336
+ const grant = await requireAccessGrant32(c, deps, workspaceId, "workspace:admin");
54337
+ const parsed = CreateWorkspaceGatewayCustomModelRequest.safeParse(
54338
+ await c.req.json().catch(() => null)
54339
+ );
54340
+ if (!parsed.success) {
54341
+ throw new HTTPException55(422, { message: "invalid Gateway custom model" });
54342
+ }
54343
+ const requestHash = workspaceCustomModelRequestHash({
54344
+ action: "create",
54345
+ upstreamModelId: parsed.data.upstreamModelId,
54346
+ label: parsed.data.label ?? null
54347
+ });
54348
+ const replay = await replayWorkspaceGatewayCustomModelCreate(deps.db, {
54349
+ accountId: grant.accountId,
54350
+ workspaceId,
54351
+ operationId: parsed.data.operationId,
54352
+ requestHash
54353
+ });
54354
+ if (replay.outcome === "conflict") {
54355
+ throw new HTTPException55(409, {
54356
+ message: "Gateway custom model operation conflicts with current state"
54357
+ });
54358
+ }
54359
+ if (replay.outcome === "success") {
54360
+ return c.json(projectWorkspaceGatewayCustomModel(replay.model), 201);
54361
+ }
54362
+ const catalog = await deps.resolveCatalogSettings();
54363
+ if (configuredGatewayUpstreamModelIds(catalog.settings).includes(parsed.data.upstreamModelId)) {
54364
+ throw new HTTPException55(422, {
54365
+ message: "Gateway model is already included in the deployment catalog"
54366
+ });
54367
+ }
54368
+ const customProductId = `${WORKSPACE_GATEWAY_MODEL_ID_PREFIX}${parsed.data.upstreamModelId}`;
54369
+ const deploymentProductIds = /* @__PURE__ */ new Set([
54370
+ ...configuredModelInputIdentities(catalog.settings),
54371
+ ...configuredGatewayWorkspaceProductModelIds(catalog.settings)
54372
+ ]);
54373
+ if (deploymentProductIds.has(customProductId)) {
54374
+ throw new HTTPException55(422, {
54375
+ message: "Gateway model product id conflicts with the deployment catalog"
54376
+ });
54377
+ }
54378
+ try {
54379
+ const model = await createWorkspaceGatewayCustomModel(deps.db, {
54380
+ accountId: grant.accountId,
54381
+ workspaceId,
54382
+ upstreamModelId: parsed.data.upstreamModelId,
54383
+ label: parsed.data.label ?? null,
54384
+ operationId: parsed.data.operationId,
54385
+ requestHash,
54386
+ createdBySubjectId: grant.subjectId
54387
+ });
54388
+ if (!model || model.retiredAt) {
54389
+ throw new HTTPException55(409, {
54390
+ message: "Gateway custom model operation conflicts with current state"
54391
+ });
54392
+ }
54393
+ return c.json(projectWorkspaceGatewayCustomModel(model), 201);
54394
+ } catch (error) {
54395
+ if (error instanceof WorkspaceGatewayCustomModelLimitError || error instanceof WorkspaceGatewayCustomModelHistoryLimitError) {
54396
+ throw new HTTPException55(422, { message: error.message });
54397
+ }
54398
+ if (nestedPostgresSqlState4(error) === "23505") {
54399
+ throw new HTTPException55(422, {
54400
+ message: "Gateway custom model already exists"
54401
+ });
54402
+ }
54403
+ throw error;
54404
+ }
54405
+ });
54406
+ app.delete("/v1/workspaces/:workspaceId/gateway-custom-models/:customModelId", async (c) => {
54407
+ const workspaceId = c.req.param("workspaceId");
54408
+ const grant = await requireAccessGrant32(c, deps, workspaceId, "workspace:admin");
54409
+ const customModelId = c.req.param("customModelId");
54410
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(
54411
+ customModelId
54412
+ )) {
54413
+ throw new HTTPException55(422, {
54414
+ message: "invalid Gateway custom model id"
54415
+ });
54416
+ }
54417
+ const parsed = DeleteWorkspaceGatewayCustomModelRequest.safeParse(
54418
+ await c.req.json().catch(() => null)
54419
+ );
54420
+ if (!parsed.success) {
54421
+ throw new HTTPException55(422, {
54422
+ message: "invalid Gateway custom model deletion"
54423
+ });
54424
+ }
54425
+ const removed = await deleteWorkspaceGatewayCustomModel(deps.db, {
54426
+ accountId: grant.accountId,
54427
+ workspaceId,
54428
+ customModelId,
54429
+ expectedVersion: parsed.data.expectedVersion,
54430
+ operationId: parsed.data.operationId,
54431
+ requestHash: workspaceCustomModelRequestHash({
54432
+ action: "delete",
54433
+ customModelId,
54434
+ expectedVersion: parsed.data.expectedVersion
54435
+ })
54436
+ });
54437
+ if (removed.outcome === "not_found") {
54438
+ throw new HTTPException55(404, {
54439
+ message: "Gateway custom model not found"
54440
+ });
54441
+ }
54442
+ if (removed.outcome === "conflict") {
54443
+ throw new HTTPException55(409, {
54444
+ message: "Gateway custom model changed; reload and retry"
54445
+ });
54446
+ }
54447
+ return c.body(null, 204);
54448
+ });
54449
+ app.get("/v1/workspaces/:workspaceId/openrouter-custom-models", async (c) => {
54450
+ const workspaceId = c.req.param("workspaceId");
54451
+ const grant = await requireAccessGrant32(c, deps, workspaceId, "workspace:read");
54452
+ const models = await listWorkspaceOpenRouterCustomModels(deps.db, {
54453
+ accountId: grant.accountId,
54454
+ workspaceId
54455
+ });
54456
+ c.header("cache-control", "private, no-store");
54457
+ return c.json(
54458
+ WorkspaceOpenRouterCustomModelsResponse.parse({
54459
+ models: models.map(projectWorkspaceOpenRouterCustomModel)
54460
+ })
54461
+ );
54462
+ });
54463
+ app.post("/v1/workspaces/:workspaceId/openrouter-custom-models", async (c) => {
54464
+ const workspaceId = c.req.param("workspaceId");
54465
+ const grant = await requireAccessGrant32(c, deps, workspaceId, "workspace:admin");
54466
+ const parsed = CreateWorkspaceOpenRouterCustomModelRequest.safeParse(
54467
+ await c.req.json().catch(() => null)
54468
+ );
54469
+ if (!parsed.success) {
54470
+ throw new HTTPException55(422, { message: "invalid OpenRouter custom model" });
54471
+ }
54472
+ const requestHash = workspaceCustomModelRequestHash({
54473
+ action: "create",
54474
+ upstreamModelId: parsed.data.upstreamModelId,
54475
+ label: parsed.data.label ?? null
54476
+ });
54477
+ const replay = await replayWorkspaceOpenRouterCustomModelCreate(deps.db, {
54478
+ accountId: grant.accountId,
54479
+ workspaceId,
54480
+ operationId: parsed.data.operationId,
54481
+ requestHash
54482
+ });
54483
+ if (replay.outcome === "conflict") {
54484
+ throw new HTTPException55(409, {
54485
+ message: "OpenRouter custom model operation conflicts with current state"
54486
+ });
54487
+ }
54488
+ if (replay.outcome === "success") {
54489
+ return c.json(projectWorkspaceOpenRouterCustomModel(replay.model), 201);
54490
+ }
54491
+ const catalog = await deps.resolveCatalogSettings();
54492
+ if (configuredOpenRouterUpstreamModelIds(catalog.settings).includes(parsed.data.upstreamModelId)) {
54493
+ throw new HTTPException55(422, {
54494
+ message: "OpenRouter model is already included in the deployment catalog"
54495
+ });
54496
+ }
54497
+ const customProductId = `${WORKSPACE_OPENROUTER_MODEL_ID_PREFIX}${parsed.data.upstreamModelId}`;
54498
+ const deploymentProductIds = /* @__PURE__ */ new Set([
54499
+ ...configuredModelInputIdentities(catalog.settings),
54500
+ ...configuredOpenRouterWorkspaceProductModelIds(catalog.settings)
54501
+ ]);
54502
+ if (deploymentProductIds.has(customProductId)) {
54503
+ throw new HTTPException55(422, {
54504
+ message: "OpenRouter model product id conflicts with the deployment catalog"
54505
+ });
54506
+ }
54507
+ try {
54508
+ const model = await createWorkspaceOpenRouterCustomModel(deps.db, {
54509
+ accountId: grant.accountId,
54510
+ workspaceId,
54511
+ upstreamModelId: parsed.data.upstreamModelId,
54512
+ label: parsed.data.label ?? null,
54513
+ operationId: parsed.data.operationId,
54514
+ requestHash,
54515
+ createdBySubjectId: grant.subjectId
54516
+ });
54517
+ if (!model || model.retiredAt) {
54518
+ throw new HTTPException55(409, {
54519
+ message: "OpenRouter custom model operation conflicts with current state"
54520
+ });
54521
+ }
54522
+ return c.json(projectWorkspaceOpenRouterCustomModel(model), 201);
54523
+ } catch (error) {
54524
+ if (error instanceof WorkspaceOpenRouterCustomModelLimitError || error instanceof WorkspaceOpenRouterCustomModelHistoryLimitError) {
54525
+ throw new HTTPException55(422, { message: error.message });
54526
+ }
54527
+ if (nestedPostgresSqlState4(error) === "23505") {
54528
+ throw new HTTPException55(422, {
54529
+ message: "OpenRouter custom model already exists"
54530
+ });
54531
+ }
54532
+ throw error;
54533
+ }
54534
+ });
54535
+ app.delete("/v1/workspaces/:workspaceId/openrouter-custom-models/:customModelId", async (c) => {
54536
+ const workspaceId = c.req.param("workspaceId");
54537
+ const grant = await requireAccessGrant32(c, deps, workspaceId, "workspace:admin");
54538
+ const customModelId = c.req.param("customModelId");
54539
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(
54540
+ customModelId
54541
+ )) {
54542
+ throw new HTTPException55(422, {
54543
+ message: "invalid OpenRouter custom model id"
54544
+ });
54545
+ }
54546
+ const parsed = DeleteWorkspaceOpenRouterCustomModelRequest.safeParse(
54547
+ await c.req.json().catch(() => null)
54548
+ );
54549
+ if (!parsed.success) {
54550
+ throw new HTTPException55(422, {
54551
+ message: "invalid OpenRouter custom model deletion"
54552
+ });
54553
+ }
54554
+ const removed = await deleteWorkspaceOpenRouterCustomModel(deps.db, {
54555
+ accountId: grant.accountId,
54556
+ workspaceId,
54557
+ customModelId,
54558
+ expectedVersion: parsed.data.expectedVersion,
54559
+ operationId: parsed.data.operationId,
54560
+ requestHash: workspaceCustomModelRequestHash({
54561
+ action: "delete",
54562
+ customModelId,
54563
+ expectedVersion: parsed.data.expectedVersion
54564
+ })
54565
+ });
54566
+ if (removed.outcome === "not_found") {
54567
+ throw new HTTPException55(404, {
54568
+ message: "OpenRouter custom model not found"
54569
+ });
54570
+ }
54571
+ if (removed.outcome === "conflict") {
54572
+ throw new HTTPException55(409, {
54573
+ message: "OpenRouter custom model changed; reload and retry"
54574
+ });
54575
+ }
54576
+ return c.body(null, 204);
54577
+ });
54038
54578
  app.get("/v1/workspaces/:workspaceId/realtime-model-catalog", async (c) => {
54039
54579
  const workspaceId = c.req.param("workspaceId");
54040
54580
  const grant = await requireAccessGrant32(c, deps, workspaceId, "workspace:read");
@@ -54100,11 +54640,15 @@ function registerWorkspaceRoutes(app, deps) {
54100
54640
  const workspaceId = c.req.param("workspaceId");
54101
54641
  const grant = await requireAccessGrant32(c, deps, workspaceId, "workspace:admin");
54102
54642
  const payload = UpdateWorkspaceModelPolicyRequest.parse(await c.req.json());
54643
+ const catalog = await resolveWorkspaceCatalogSettings6(deps.db, deps.settings, {
54644
+ accountId: grant.accountId,
54645
+ workspaceId
54646
+ });
54103
54647
  const policy = await upsertWorkspaceModelPolicy(deps.db, {
54104
54648
  accountId: grant.accountId,
54105
54649
  workspaceId,
54106
54650
  allowedProviders: payload.allowedProviders ?? null,
54107
- allowedModels: canonicalWorkspacePolicyModelIds(deps.settings, payload.allowedModels)
54651
+ allowedModels: canonicalWorkspacePolicyModelIds(catalog.settings, payload.allowedModels)
54108
54652
  });
54109
54653
  return c.json(policy);
54110
54654
  });
@@ -55288,7 +55832,7 @@ function registerCompanyProfileRoutes(app, deps) {
55288
55832
  }
55289
55833
 
55290
55834
  // src/routes/company-brain.ts
55291
- import { createHash as createHash17 } from "crypto";
55835
+ import { createHash as createHash18 } from "crypto";
55292
55836
  import {
55293
55837
  COMPANY_BRAIN_INSPECTOR_CURSOR_MAX_CHARS,
55294
55838
  COMPANY_BRAIN_INSPECTOR_DEFAULT_LIMIT,
@@ -55565,7 +56109,7 @@ function serializeCompanyBrainOkf(value) {
55565
56109
  }
55566
56110
 
55567
56111
  // src/workspace-state-projection.ts
55568
- import { createHash as createHash16 } from "crypto";
56112
+ import { createHash as createHash17 } from "crypto";
55569
56113
  import {
55570
56114
  KnowledgeMemoryKind,
55571
56115
  KnowledgeMemoryStatus,
@@ -55579,7 +56123,7 @@ import {
55579
56123
  WorkspaceStateResponse
55580
56124
  } from "@opengeni/contracts";
55581
56125
  function hashIdentities(values) {
55582
- return createHash16("sha256").update(values.join("\n"), "utf8").digest("hex");
56126
+ return createHash17("sha256").update(values.join("\n"), "utf8").digest("hex");
55583
56127
  }
55584
56128
  function policyTargetKey(value) {
55585
56129
  return `${value.kind}:${value.scope}:${value.roleKey ?? ""}`;
@@ -55922,7 +56466,7 @@ var inspectorQuery = z18.object({
55922
56466
  message: "attemptId cannot be combined with cursor"
55923
56467
  });
55924
56468
  function receiptCursorScope(workspaceId, subjectId) {
55925
- return createHash17("sha256").update("opengeni:company-brain-context-receipts:v1\0").update(workspaceId).update("\0").update(subjectId).digest("hex");
56469
+ return createHash18("sha256").update("opengeni:company-brain-context-receipts:v1\0").update(workspaceId).update("\0").update(subjectId).digest("hex");
55926
56470
  }
55927
56471
  function encodeReceiptCursor(workspaceId, subjectId, cursor) {
55928
56472
  return Buffer.from(
@@ -56294,7 +56838,7 @@ import { getDocumentInventory as getDocumentInventory2 } from "@opengeni/documen
56294
56838
  import { HTTPException as HTTPException61 } from "hono/http-exception";
56295
56839
 
56296
56840
  // src/workspace-state-export.ts
56297
- import { createHash as createHash18 } from "crypto";
56841
+ import { createHash as createHash19 } from "crypto";
56298
56842
  import {
56299
56843
  WORKSPACE_STATE_EXPORT_SCHEMA_VERSION,
56300
56844
  WorkspaceStateExportResponse
@@ -56325,7 +56869,7 @@ function createWorkspaceStateExport(state) {
56325
56869
  kind: "opengeni.workspace_state.sanitized_export",
56326
56870
  schemaVersion: WORKSPACE_STATE_EXPORT_SCHEMA_VERSION,
56327
56871
  generatedAt: state.generatedAt,
56328
- stateSha256: createHash18("sha256").update(canonicalState, "utf8").digest("hex"),
56872
+ stateSha256: createHash19("sha256").update(canonicalState, "utf8").digest("hex"),
56329
56873
  omissions: WORKSPACE_STATE_EXPORT_OMISSIONS,
56330
56874
  state
56331
56875
  });
@@ -56425,7 +56969,7 @@ function registerWorkspaceStateRoutes(app, deps) {
56425
56969
  }
56426
56970
 
56427
56971
  // src/routes/workspace-artifacts.ts
56428
- import { createHash as createHash19 } from "crypto";
56972
+ import { createHash as createHash20 } from "crypto";
56429
56973
  import {
56430
56974
  CreateWorkspaceArtifactRequest,
56431
56975
  PublishWorkspaceArtifactVersionRequest,
@@ -56495,7 +57039,7 @@ function errorResponse(context, error) {
56495
57039
  }
56496
57040
  function contentMetadata(workspaceId, html) {
56497
57041
  const bytes = encoder3.encode(html);
56498
- const sha2564 = createHash19("sha256").update(bytes).digest("hex");
57042
+ const sha2564 = createHash20("sha256").update(bytes).digest("hex");
56499
57043
  return {
56500
57044
  bytes,
56501
57045
  contentSha256: sha2564,
@@ -56521,7 +57065,7 @@ function prepareHtml(deps, workspaceId, html) {
56521
57065
  }
56522
57066
  function provenance(subjectId, idempotencyKey) {
56523
57067
  return {
56524
- operationKey: `subject:${createHash19("sha256").update(`${subjectId}:${idempotencyKey}`).digest("hex")}`,
57068
+ operationKey: `subject:${createHash20("sha256").update(`${subjectId}:${idempotencyKey}`).digest("hex")}`,
56525
57069
  actorSubjectId: subjectId,
56526
57070
  sourceSessionId: null,
56527
57071
  sourceTurnId: null,
@@ -56614,7 +57158,7 @@ function registerWorkspaceArtifactRoutes(app, deps) {
56614
57158
  async () => deps.objectStorage.getObjectBytes(ref.contentKey)
56615
57159
  );
56616
57160
  if (!object10) throw new HTTPException62(503, { message: "Artifact content is unavailable" });
56617
- const actualHash = createHash19("sha256").update(object10.bytes).digest("hex");
57161
+ const actualHash = createHash20("sha256").update(object10.bytes).digest("hex");
56618
57162
  if (actualHash !== ref.version.contentSha256) {
56619
57163
  throw new HTTPException62(503, { message: "Artifact content failed integrity verification" });
56620
57164
  }
@@ -57181,7 +57725,7 @@ import { requireAccessGrant as requireAccessGrant43, TranscriptionServiceError a
57181
57725
  import { getWorkspace as getWorkspace5 } from "@opengeni/db";
57182
57726
 
57183
57727
  // src/routes/transcription-recordings.ts
57184
- import { createHash as createHash20 } from "crypto";
57728
+ import { createHash as createHash21 } from "crypto";
57185
57729
  import {
57186
57730
  CreateTranscriptionRecordingRequest,
57187
57731
  FinalizeTranscriptionRecordingRequest,
@@ -58004,7 +58548,7 @@ function valueAsUuid(value) {
58004
58548
  if (/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value)) {
58005
58549
  return value;
58006
58550
  }
58007
- const hex2 = createHash20("sha256").update(value).digest("hex").slice(0, 32);
58551
+ const hex2 = createHash21("sha256").update(value).digest("hex").slice(0, 32);
58008
58552
  return `${hex2.slice(0, 8)}-${hex2.slice(8, 12)}-4${hex2.slice(13, 16)}-a${hex2.slice(17, 20)}-${hex2.slice(20, 32)}`;
58009
58553
  }
58010
58554
  function nonnegativeInteger(value) {
@@ -58068,7 +58612,7 @@ async function readBoundedBody(request, maxBytes) {
58068
58612
  return body4;
58069
58613
  }
58070
58614
  function sha256Hex3(bytes) {
58071
- return createHash20("sha256").update(bytes).digest("hex");
58615
+ return createHash21("sha256").update(bytes).digest("hex");
58072
58616
  }
58073
58617
 
58074
58618
  // src/routes/transcriptions.ts
@@ -61541,7 +62085,7 @@ async function exactAvailable(providers, providerId, context) {
61541
62085
  }
61542
62086
 
61543
62087
  // src/integrations/slack-interactions.ts
61544
- import { createHash as createHash22, createHmac as createHmac8, timingSafeEqual as timingSafeEqual3 } from "crypto";
62088
+ import { createHash as createHash23, createHmac as createHmac9, timingSafeEqual as timingSafeEqual3 } from "crypto";
61545
62089
  import {
61546
62090
  approvalIdentifier,
61547
62091
  ApproveSlackUserLinkAccessRequest,
@@ -61647,7 +62191,8 @@ import {
61647
62191
  hasPermission as hasPermission24,
61648
62192
  requireAccessContext as requireAccessContext4,
61649
62193
  requireAccessGrant as requireAccessGrant45,
61650
- requireSessionAuthorizationListScope as requireSessionAuthorizationListScope3
62194
+ requireSessionAuthorizationListScope as requireSessionAuthorizationListScope3,
62195
+ resolveWorkspaceCatalogSettings as resolveWorkspaceCatalogSettings7
61651
62196
  } from "@opengeni/core";
61652
62197
  import { publishDurableSessionEvents as publishDurableSessionEvents4 } from "@opengeni/events";
61653
62198
  import { HTTPException as HTTPException74 } from "hono/http-exception";
@@ -62018,7 +62563,7 @@ function record5(value) {
62018
62563
  }
62019
62564
 
62020
62565
  // src/slack-reaction-files.ts
62021
- import { createHash as createHash21 } from "crypto";
62566
+ import { createHash as createHash22 } from "crypto";
62022
62567
  import { completeFileUpload as completeFileUpload5, prepareGeneratedWorkspaceFile as prepareGeneratedWorkspaceFile2 } from "@opengeni/db";
62023
62568
  import { retryWhileMissing as retryWhileMissing7 } from "@opengeni/storage";
62024
62569
  var IMPORT_IDENTITY_VERSION = "slack-reaction-image-v1";
@@ -62035,7 +62580,7 @@ async function importSlackReactionImage(dependencies, source, image, ordinal) {
62035
62580
  if (image.bytes.byteLength < 1 || image.bytes.byteLength > 4 * 1024 * 1024) {
62036
62581
  throw new Error("Slack reaction image size is invalid");
62037
62582
  }
62038
- const sha2564 = createHash21("sha256").update(image.bytes).digest("hex");
62583
+ const sha2564 = createHash22("sha256").update(image.bytes).digest("hex");
62039
62584
  const identity = slackReactionImageIdentity(source, image.fileId);
62040
62585
  const fileId = deterministicUuid(`${identity}:file`);
62041
62586
  const uploadId = deterministicUuid(`${identity}:upload`);
@@ -62129,7 +62674,7 @@ function assertStoredSlackImage(head, image, sha2564) {
62129
62674
  }
62130
62675
  }
62131
62676
  function deterministicUuid(seed) {
62132
- const bytes = createHash21("sha256").update(seed, "utf8").digest().subarray(0, 16);
62677
+ const bytes = createHash22("sha256").update(seed, "utf8").digest().subarray(0, 16);
62133
62678
  bytes[6] = (bytes[6] ?? 0) & 15 | 80;
62134
62679
  bytes[8] = (bytes[8] ?? 0) & 63 | 128;
62135
62680
  const hex2 = bytes.toString("hex");
@@ -62229,7 +62774,7 @@ function verifySlackRequestSignature(input, signingSecret, nowMs = Date.now()) {
62229
62774
  if (!Number.isSafeInteger(timestamp)) return false;
62230
62775
  const nowSeconds = Math.floor(nowMs / 1e3);
62231
62776
  if (Math.abs(nowSeconds - timestamp) > SLACK_SIGNATURE_REPLAY_WINDOW_SECONDS) return false;
62232
- const expected = `v0=${createHmac8("sha256", signingSecret).update(`v0:${input.timestamp}:${input.rawBody}`).digest("hex")}`;
62777
+ const expected = `v0=${createHmac9("sha256", signingSecret).update(`v0:${input.timestamp}:${input.rawBody}`).digest("hex")}`;
62233
62778
  const actualBytes = Buffer.from(input.signature, "utf8");
62234
62779
  const expectedBytes = Buffer.from(expected, "utf8");
62235
62780
  return actualBytes.length === expectedBytes.length && timingSafeEqual3(actualBytes, expectedBytes);
@@ -62288,7 +62833,7 @@ function slackReactionInboxEntry(payload, bot, settings) {
62288
62833
  if (!settings.enabled || !event || event.type !== "reaction_added" || item?.type !== "message" || !teamId || !eventId || !userId || userId === bot.botUserId || !reaction || reaction !== settings.emoji || !channelId || !workspaceSlackReactionChannelAllowed(settings, channelId) || !timestamp) {
62289
62834
  return null;
62290
62835
  }
62291
- const stableReactionIdentity = createHash22("sha256").update([teamId, userId, channelId, timestamp, reaction].join("\n")).digest("hex");
62836
+ const stableReactionIdentity = createHash23("sha256").update([teamId, userId, channelId, timestamp, reaction].join("\n")).digest("hex");
62292
62837
  return {
62293
62838
  providerEventId: eventId,
62294
62839
  providerMessageId: `reaction:${stableReactionIdentity}`,
@@ -62478,7 +63023,7 @@ function registerSlackInteractionRoutes(app, deps) {
62478
63023
  const prepared = await prepareSlackUserLinkAccessRequest(deps.db, {
62479
63024
  accountId: installation.accountId,
62480
63025
  workspaceId,
62481
- tokenDigest: createHash22("sha256").update(payload.linkToken).digest("hex"),
63026
+ tokenDigest: createHash23("sha256").update(payload.linkToken).digest("hex"),
62482
63027
  connectionId: link.connectionId,
62483
63028
  slackTeamId: link.slackTeamId,
62484
63029
  slackUserId: link.slackUserId,
@@ -62685,7 +63230,7 @@ function registerSlackInteractionRoutes(app, deps) {
62685
63230
  const prepared = await prepareSlackUserLinkAccessRequest(deps.db, {
62686
63231
  accountId: grant.accountId,
62687
63232
  workspaceId,
62688
- tokenDigest: createHash22("sha256").update(linkToken).digest("hex"),
63233
+ tokenDigest: createHash23("sha256").update(linkToken).digest("hex"),
62689
63234
  connectionId: link.connectionId,
62690
63235
  slackTeamId: link.slackTeamId,
62691
63236
  slackUserId: link.slackUserId,
@@ -62845,6 +63390,17 @@ function registerSlackInteractionRoutes(app, deps) {
62845
63390
  );
62846
63391
  });
62847
63392
  }
63393
+ async function withCatalogSettings(deps, grant) {
63394
+ const catalogSourceSettings = deps.catalogSourceSettings ?? deps.settings;
63395
+ return {
63396
+ ...deps,
63397
+ catalogSourceSettings,
63398
+ settings: (await resolveWorkspaceCatalogSettings7(deps.db, catalogSourceSettings, {
63399
+ accountId: grant.accountId,
63400
+ workspaceId: grant.workspaceId
63401
+ })).settings
63402
+ };
63403
+ }
62848
63404
  async function publishSlackAppHome(deps, installation, refresh, renewLease) {
62849
63405
  const link = await getSlackBotUserLink(
62850
63406
  deps.db,
@@ -63642,17 +64198,22 @@ async function processSlackInboxEntry(deps, entry2) {
63642
64198
  preparedAttachments,
63643
64199
  preparedModelContext
63644
64200
  );
63645
- session = await createSessionForRequest2(deps, grant, interaction.workspaceId, {
63646
- requestedSessionId: interaction.sessionReservationId,
63647
- initialMessage: prepared.entry.text,
63648
- ...prepared.modelContext ? { modelContext: prepared.modelContext } : {},
63649
- instructions: SLACK_SESSION_INSTRUCTIONS,
63650
- firstPartyMcpTools: slackTaskFirstPartyMcpTools(deps.settings),
63651
- resources: preparedAttachments.resources,
63652
- ...preferredModel ? { model: preferredModel } : {},
63653
- idempotencyKey: `slack:${entry2.connectionId}:${entry2.providerEventId}`,
63654
- clientEventId: `slack:${entry2.providerEventId}`
63655
- });
64201
+ session = await createSessionForRequest2(
64202
+ await withCatalogSettings(deps, grant),
64203
+ grant,
64204
+ interaction.workspaceId,
64205
+ {
64206
+ requestedSessionId: interaction.sessionReservationId,
64207
+ initialMessage: prepared.entry.text,
64208
+ ...prepared.modelContext ? { modelContext: prepared.modelContext } : {},
64209
+ instructions: SLACK_SESSION_INSTRUCTIONS,
64210
+ firstPartyMcpTools: slackTaskFirstPartyMcpTools(deps.settings),
64211
+ resources: preparedAttachments.resources,
64212
+ ...preferredModel ? { model: preferredModel } : {},
64213
+ idempotencyKey: `slack:${entry2.connectionId}:${entry2.providerEventId}`,
64214
+ clientEventId: `slack:${entry2.providerEventId}`
64215
+ }
64216
+ );
63656
64217
  } catch (error) {
63657
64218
  if (error instanceof HTTPException74) {
63658
64219
  await client.postMessage({
@@ -64069,21 +64630,26 @@ async function processSlackReactionInboxEntry(deps, entry2) {
64069
64630
  const preparedEntry = slackReactionPreparedEntry(entry2, context, preparedTask);
64070
64631
  let session;
64071
64632
  try {
64072
- session = await createSessionForRequest2(deps, grant, interaction.workspaceId, {
64073
- requestedSessionId: interaction.sessionReservationId,
64074
- initialMessage: preparedEntry.text,
64075
- instructions: SLACK_SESSION_INSTRUCTIONS,
64076
- // The exact reacted message and bounded containing thread are already in
64077
- // the prompt; do not expose general Slack history tools for this trigger.
64078
- firstPartyMcpTools: resolveFirstPartyMcpToolPolicy(deps.settings).default,
64079
- resources: preparedTask.resources,
64080
- ...preferredModel ? { model: preferredModel } : {},
64081
- // Every reaction entry converging on this route must use the same create
64082
- // key. This closes the same-owner multi-event race while the owner check
64083
- // above prevents a different subject from winning creation authority.
64084
- idempotencyKey: `slack-interaction:${interaction.id}`,
64085
- clientEventId: `slack:${entry2.providerEventId}`
64086
- });
64633
+ session = await createSessionForRequest2(
64634
+ await withCatalogSettings(deps, grant),
64635
+ grant,
64636
+ interaction.workspaceId,
64637
+ {
64638
+ requestedSessionId: interaction.sessionReservationId,
64639
+ initialMessage: preparedEntry.text,
64640
+ instructions: SLACK_SESSION_INSTRUCTIONS,
64641
+ // The exact reacted message and bounded containing thread are already in
64642
+ // the prompt; do not expose general Slack history tools for this trigger.
64643
+ firstPartyMcpTools: resolveFirstPartyMcpToolPolicy(deps.settings).default,
64644
+ resources: preparedTask.resources,
64645
+ ...preferredModel ? { model: preferredModel } : {},
64646
+ // Every reaction entry converging on this route must use the same create
64647
+ // key. This closes the same-owner multi-event race while the owner check
64648
+ // above prevents a different subject from winning creation authority.
64649
+ idempotencyKey: `slack-interaction:${interaction.id}`,
64650
+ clientEventId: `slack:${entry2.providerEventId}`
64651
+ }
64652
+ );
64087
64653
  await acceptSlackReactionTask(deps, grant, session.id, preparedEntry, preparedTask.resources);
64088
64654
  } catch (error) {
64089
64655
  if (error instanceof HTTPException74) {
@@ -64368,11 +64934,17 @@ async function acceptSlackReactionTask(deps, grant, sessionId, entry2, resources
64368
64934
  }
64369
64935
  return;
64370
64936
  }
64371
- await acceptSessionUserMessage2(deps, grant, grant.workspaceId, sessionId, {
64372
- text: entry2.text,
64373
- resources,
64374
- clientEventId
64375
- });
64937
+ await acceptSessionUserMessage2(
64938
+ await withCatalogSettings(deps, grant),
64939
+ grant,
64940
+ grant.workspaceId,
64941
+ sessionId,
64942
+ {
64943
+ text: entry2.text,
64944
+ resources,
64945
+ clientEventId
64946
+ }
64947
+ );
64376
64948
  }
64377
64949
  async function continueSlackSession(deps, grant, interaction, entry2, resources = [], options = {}) {
64378
64950
  if (!interaction.sessionId || interaction.visibility === "private" && interaction.owningSubjectId !== grant.subjectId) {
@@ -64444,12 +65016,18 @@ async function continueSlackSession(deps, grant, interaction, entry2, resources
64444
65016
  if (!hasPermission24(grant.permissions, "sessions:control")) {
64445
65017
  throw new SlackInteractionPermanentError("sessions_control_denied");
64446
65018
  }
64447
- await acceptSessionUserMessage2(deps, grant, interaction.workspaceId, interaction.sessionId, {
64448
- text: entry2.text,
64449
- ...options.modelContext ? { modelContext: options.modelContext } : {},
64450
- resources,
64451
- clientEventId: `slack:${entry2.providerEventId}`
64452
- });
65019
+ await acceptSessionUserMessage2(
65020
+ await withCatalogSettings(deps, grant),
65021
+ grant,
65022
+ interaction.workspaceId,
65023
+ interaction.sessionId,
65024
+ {
65025
+ text: entry2.text,
65026
+ ...options.modelContext ? { modelContext: options.modelContext } : {},
65027
+ resources,
65028
+ clientEventId: `slack:${entry2.providerEventId}`
65029
+ }
65030
+ );
64453
65031
  }
64454
65032
  var SLACK_ACTION_ID_BY_KIND = {
64455
65033
  approval_approve: "opengeni.approval.approve",
@@ -65533,7 +66111,7 @@ function normalizedBlockActionInteraction(payload) {
65533
66111
  if (!teamId || !userId || !channelId || !messageTs || !actionTs || !actionId || !SLACK_BLOCK_ACTION_IDS.has(actionId) || !handleId || !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(handleId) || !Array.isArray(payload.actions) || payload.actions.length !== 1) {
65534
66112
  throw new HTTPException74(400, { message: "invalid Slack block action" });
65535
66113
  }
65536
- const identity = createHash22("sha256").update([teamId, userId, channelId, messageTs, actionTs, actionId, handleId].join("\n")).digest("hex");
66114
+ const identity = createHash23("sha256").update([teamId, userId, channelId, messageTs, actionTs, actionId, handleId].join("\n")).digest("hex");
65537
66115
  return {
65538
66116
  providerEventId: `block:${identity}`,
65539
66117
  providerMessageId: `block:${identity}`,
@@ -65760,14 +66338,14 @@ function createSlackUserLinkToken(signingSecret, entry2, nowMs = Date.now()) {
65760
66338
  }),
65761
66339
  "utf8"
65762
66340
  ).toString("base64url");
65763
- const signature = createHmac8("sha256", signingSecret).update(payload).digest("base64url");
66341
+ const signature = createHmac9("sha256", signingSecret).update(payload).digest("base64url");
65764
66342
  return `${payload}.${signature}`;
65765
66343
  }
65766
66344
  function verifySlackUserLinkToken(signingSecret, token, nowMs = Date.now()) {
65767
66345
  const [payload, signature, extra] = token.split(".");
65768
66346
  if (!payload || !signature || extra || payload.length > 1500 || signature.length > 128)
65769
66347
  return null;
65770
- const expected = createHmac8("sha256", signingSecret).update(payload).digest("base64url");
66348
+ const expected = createHmac9("sha256", signingSecret).update(payload).digest("base64url");
65771
66349
  const actualBytes = Buffer.from(signature, "utf8");
65772
66350
  const expectedBytes = Buffer.from(expected, "utf8");
65773
66351
  if (actualBytes.length !== expectedBytes.length || !timingSafeEqual3(actualBytes, expectedBytes)) {
@@ -65798,7 +66376,7 @@ function usesPrivateBotDm(interaction, entry2) {
65798
66376
  return isDirectMessageShortcut(entry2) || interaction.visibility === "private" && entry2.triggerKind !== "dm";
65799
66377
  }
65800
66378
  function deterministicUuid2(value) {
65801
- const bytes = createHash22("sha256").update(value).digest().subarray(0, 16);
66379
+ const bytes = createHash23("sha256").update(value).digest().subarray(0, 16);
65802
66380
  bytes[6] = bytes[6] & 15 | 80;
65803
66381
  bytes[8] = bytes[8] & 63 | 128;
65804
66382
  const hex2 = bytes.toString("hex");
@@ -65933,6 +66511,18 @@ var API_PUBLIC_ERROR_MESSAGE_MAX_BYTES = 512;
65933
66511
  function createApp(deps) {
65934
66512
  return createAppComposition(deps).app;
65935
66513
  }
66514
+ async function resolveWorkspaceMcpRouteDeps(routeDeps, grant) {
66515
+ const catalogSourceSettings = routeDeps.catalogSourceSettings ?? routeDeps.settings;
66516
+ const resolvedCatalog = await resolveWorkspaceCatalogSettings8(
66517
+ routeDeps.db,
66518
+ catalogSourceSettings,
66519
+ {
66520
+ accountId: grant.accountId,
66521
+ workspaceId: grant.workspaceId
66522
+ }
66523
+ );
66524
+ return { ...routeDeps, catalogSourceSettings, settings: resolvedCatalog.settings };
66525
+ }
65936
66526
  function createAppComposition(deps) {
65937
66527
  configureWorkspaceControlRequestLockTimeoutMs(deps.settings.workspaceControlLockTimeoutMs);
65938
66528
  configureChildLifecycleNotices({ enabled: deps.settings.childLifecycleNoticesEnabled });
@@ -66019,6 +66609,7 @@ function createAppComposition(deps) {
66019
66609
  }) : deps.transcriptionSegmenter;
66020
66610
  const routeDeps = {
66021
66611
  ...deps,
66612
+ resolveCatalogSettings: () => resolveCatalogSettings(deps.db, deps.settings),
66022
66613
  observability,
66023
66614
  githubStateSecret: deps.githubStateSecret ?? deps.settings.githubAppManifestStateSecret ?? crypto.randomUUID(),
66024
66615
  managedAuth,
@@ -66374,8 +66965,10 @@ function createAppComposition(deps) {
66374
66965
  );
66375
66966
  app.get("/v1/config/client", async (c) => {
66376
66967
  c.header("cache-control", "no-store");
66377
- const codexCatalogSettings = deps.settings.codexSubscriptionEnabled ? withCodexCatalogProvider2(deps.settings) : deps.settings;
66378
- const catalogSettings = deps.settings.supergrokSubscriptionEnabled ? withXaiSubscriptionCatalogProvider3(codexCatalogSettings) : codexCatalogSettings;
66968
+ const resolvedCatalog = await resolveCatalogSettings(deps.db, deps.settings);
66969
+ const baseCatalogSettings = resolvedCatalog.settings;
66970
+ const codexCatalogSettings = baseCatalogSettings.codexSubscriptionEnabled ? withCodexCatalogProvider(baseCatalogSettings) : baseCatalogSettings;
66971
+ const catalogSettings = baseCatalogSettings.supergrokSubscriptionEnabled ? withXaiSubscriptionCatalogProvider2(codexCatalogSettings) : codexCatalogSettings;
66379
66972
  return c.json(
66380
66973
  ClientConfig.parse({
66381
66974
  deploymentRevision: deps.settings.deploymentRevision,
@@ -66387,7 +66980,7 @@ function createAppComposition(deps) {
66387
66980
  // union of the built-in allow-list and every registry provider's models, in
66388
66981
  // selection order (default model first); project each to the client-safe
66389
66982
  // provider-blind ClientModel shape (execution topology remains server-side).
66390
- models: configuredModels3(catalogSettings).map(projectClientModel),
66983
+ models: configuredModels2(catalogSettings).map(projectClientModel),
66391
66984
  defaultReasoningEffort: deps.settings.openaiReasoningEffort,
66392
66985
  allowedReasoningEfforts: configuredAllowedReasoningEfforts(deps.settings),
66393
66986
  defaultSandboxBackend: deps.settings.sandboxBackend,
@@ -66484,7 +67077,8 @@ function createAppComposition(deps) {
66484
67077
  const transport = new WebStandardStreamableHTTPServerTransport3({
66485
67078
  enableJsonResponse: true
66486
67079
  });
66487
- const mcp = buildOpenGeniMcpServer(routeDeps, grant, {
67080
+ const mcpDeps = await resolveWorkspaceMcpRouteDeps(routeDeps, grant);
67081
+ const mcp = buildOpenGeniMcpServer(mcpDeps, grant, {
66488
67082
  requestOrigin: new URL(c.req.url).origin,
66489
67083
  workspaceMemoryEnabled,
66490
67084
  workspaceMemoryPromptMode
@@ -66882,9 +67476,14 @@ function boundedCorrelationId(value) {
66882
67476
  function readinessChecks(deps) {
66883
67477
  const configuredNatsCheck = deps.readinessChecks?.nats;
66884
67478
  return {
66885
- db: deps.readinessChecks?.db ?? (async () => {
66886
- await deps.db.execute(dbSql`select 1`);
66887
- }),
67479
+ db: async () => {
67480
+ if (deps.readinessChecks?.db) {
67481
+ await deps.readinessChecks.db();
67482
+ } else {
67483
+ await deps.db.execute(dbSql`select 1`);
67484
+ }
67485
+ await resolveCatalogSettings(deps.db, deps.settings);
67486
+ },
66888
67487
  nats: async () => {
66889
67488
  requireSessionEventDurableFanoutCapability2(deps.bus);
66890
67489
  if (configuredNatsCheck) {
@@ -67762,6 +68361,7 @@ export {
67762
68361
  API_MAX_REQUEST_BODY_BYTES,
67763
68362
  apiRequestBodyLimitBytes,
67764
68363
  createApp,
68364
+ resolveWorkspaceMcpRouteDeps,
67765
68365
  createAppComposition,
67766
68366
  appendVary,
67767
68367
  workspaceActorContextExempt,
@@ -67780,4 +68380,4 @@ export {
67780
68380
  withDefaultEnabledCapabilityMcpTools,
67781
68381
  workflowIdForSession3 as workflowIdForSession
67782
68382
  };
67783
- //# sourceMappingURL=chunk-XTLI3CBH.js.map
68383
+ //# sourceMappingURL=chunk-UOI7KAP3.js.map