@opengeni/api-router 0.26.1 → 0.30.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.
Files changed (87) hide show
  1. package/dist/app.d.ts +1 -0
  2. package/dist/app.js +5 -3
  3. package/dist/auth/canonical-human-session-admission.d.ts +27 -0
  4. package/dist/browser-auth-broker.d.ts +20 -0
  5. package/dist/browser-controller-authority.d.ts +19 -1
  6. package/dist/browser-network-route.d.ts +8 -0
  7. package/dist/{chunk-JIKNR5YL.js → chunk-2PQMSRCB.js} +15518 -6863
  8. package/dist/chunk-2PQMSRCB.js.map +1 -0
  9. package/dist/controller-data-plane.d.ts +12 -0
  10. package/dist/http/auth.d.ts +1 -1
  11. package/dist/http/sse.d.ts +18 -1
  12. package/dist/index.js +1 -1
  13. package/dist/integrations/api-integrations.d.ts +2 -2
  14. package/dist/integrations/fiken.d.ts +233 -0
  15. package/dist/integrations/google-drive.d.ts +6 -6
  16. package/dist/integrations/provider-oauth.d.ts +0 -2
  17. package/dist/integrations/slack-bot.d.ts +171 -1
  18. package/dist/integrations/slack-interactions.d.ts +17 -5
  19. package/dist/interaction-holder-heartbeat.d.ts +17 -0
  20. package/dist/interaction-metrics.d.ts +11 -0
  21. package/dist/mcp/server.d.ts +3 -2
  22. package/dist/model-catalog.d.ts +1 -0
  23. package/dist/routes/canonical-human-identities.d.ts +3 -0
  24. package/dist/routes/channels.d.ts +3 -0
  25. package/dist/routes/integration-facets.d.ts +3 -0
  26. package/dist/routes/interaction-resources.d.ts +5 -0
  27. package/dist/routes/organization-memberships.d.ts +3 -0
  28. package/dist/routes/sessions.d.ts +2 -1
  29. package/dist/routes/slack-task-policy.d.ts +3 -0
  30. package/dist/routes/supergrok.d.ts +3 -0
  31. package/dist/sandbox/channel-a.d.ts +8 -1
  32. package/dist/sandbox/machines.d.ts +2 -2
  33. package/dist/sandbox/viewer.d.ts +3 -1
  34. package/dist/slack-reaction-files.d.ts +27 -0
  35. package/dist/transcription/providers/xai-subscription.d.ts +8 -0
  36. package/dist/xai-realtime.d.ts +26 -0
  37. package/dist/xai-subscription-auth.d.ts +23 -0
  38. package/package.json +17 -16
  39. package/src/app.ts +352 -49
  40. package/src/auth/canonical-human-session-admission.ts +116 -0
  41. package/src/auth/managed-auth.ts +101 -0
  42. package/src/browser-auth-broker.ts +155 -0
  43. package/src/browser-controller-authority.ts +50 -1
  44. package/src/browser-network-route.ts +34 -0
  45. package/src/controller-data-plane.ts +35 -0
  46. package/src/http/auth.ts +27 -9
  47. package/src/http/sse.ts +229 -5
  48. package/src/integrations/api-integrations.ts +57 -42
  49. package/src/integrations/fiken.ts +1230 -0
  50. package/src/integrations/google-drive.ts +31 -26
  51. package/src/integrations/provider-oauth.ts +70 -91
  52. package/src/integrations/slack-bot.ts +638 -9
  53. package/src/integrations/slack-interactions.ts +1583 -73
  54. package/src/interaction-holder-heartbeat.ts +95 -0
  55. package/src/interaction-metrics.ts +159 -0
  56. package/src/mcp/documents.ts +115 -6
  57. package/src/mcp/server.ts +865 -242
  58. package/src/model-catalog.ts +19 -6
  59. package/src/routes/api-integrations.ts +32 -29
  60. package/src/routes/browser-identities.ts +24 -0
  61. package/src/routes/browser-sessions.ts +2122 -183
  62. package/src/routes/canonical-human-identities.ts +156 -0
  63. package/src/routes/channels.ts +90 -0
  64. package/src/routes/computer-sessions.ts +441 -86
  65. package/src/routes/connections.ts +141 -4
  66. package/src/routes/{integration-features.ts → integration-facets.ts} +63 -63
  67. package/src/routes/interaction-resources.ts +595 -0
  68. package/src/routes/organization-memberships.ts +53 -0
  69. package/src/routes/plugins.ts +2 -1
  70. package/src/routes/sessions.ts +419 -43
  71. package/src/routes/skills.ts +92 -10
  72. package/src/routes/slack-task-policy.ts +115 -0
  73. package/src/routes/supergrok.ts +717 -0
  74. package/src/routes/transcription-recordings.ts +9 -2
  75. package/src/routes/transcriptions.ts +2 -1
  76. package/src/routes/video-generation.ts +34 -4
  77. package/src/routes/workspaces.ts +22 -4
  78. package/src/sandbox/channel-a.ts +29 -2
  79. package/src/sandbox/machines.ts +5 -5
  80. package/src/sandbox/viewer.ts +10 -3
  81. package/src/slack-reaction-files.ts +181 -0
  82. package/src/transcription/providers/xai-subscription.ts +110 -0
  83. package/src/transcription/service.ts +17 -2
  84. package/src/xai-realtime.ts +216 -0
  85. package/src/xai-subscription-auth.ts +132 -0
  86. package/dist/chunk-JIKNR5YL.js.map +0 -1
  87. package/dist/routes/integration-features.d.ts +0 -3
@@ -27,9 +27,12 @@ import {
27
27
  import {
28
28
  API_INTEGRATION_OAUTH_CREDENTIAL_ROLE,
29
29
  ApiIntegrationOAuthConnectionMetadata,
30
- IntegrationFeatureMutationResult,
30
+ IntegrationFacetMutationResult,
31
31
  } from "@opengeni/contracts";
32
- import { GOOGLE_DRIVE_PRESET, providerDomainForPreset } from "@opengeni/capabilities";
32
+ import {
33
+ GOOGLE_DRIVE_INTEGRATION_DEFINITION,
34
+ integrationDefinitionProviderDomain,
35
+ } from "@opengeni/capabilities";
33
36
  import {
34
37
  captureScheduledTaskRestoreState,
35
38
  createValidatedScheduledTask,
@@ -42,7 +45,7 @@ import {
42
45
  import type { ApiRouteDeps } from "@opengeni/core";
43
46
  import {
44
47
  buildConnectionTokenResolver,
45
- configureIntegrationFeature,
48
+ configureIntegrationFacet,
46
49
  appendKnowledgeSourceAclVersion,
47
50
  deauthorizeKnowledgeSourceRetrieval,
48
51
  ConnectionDisconnectGenerationError,
@@ -58,7 +61,7 @@ import {
58
61
  getWorkspaceGrant,
59
62
  listKnowledgeSourceSyncTasksForConnection,
60
63
  loadConnectionCredentialForBroker,
61
- listIntegrationInstanceFeatures,
64
+ listIntegrationInstanceFacets,
62
65
  transitionConnectionState,
63
66
  updateConnection,
64
67
  updateScheduledTask,
@@ -731,19 +734,19 @@ export async function browseGoogleDrive(
731
734
  });
732
735
  }
733
736
 
734
- export async function browseGoogleDriveIntegrationSource(
737
+ export async function browseGoogleDriveFacetSource(
735
738
  deps: ApiRouteDeps,
736
739
  input: {
737
740
  workspaceId: string;
738
741
  subjectId: string;
739
742
  capabilityId: string;
740
743
  instanceKey: string;
741
- featureKey: string;
744
+ facetKey: string;
742
745
  parentId: string;
743
746
  pageToken?: string | undefined;
744
747
  },
745
748
  ) {
746
- const context = await requireGoogleDriveIntegrationFeature(deps, input);
749
+ const context = await requireGoogleDriveIntegrationFacet(deps, input);
747
750
  return await browseGoogleDrive(deps, {
748
751
  workspaceId: input.workspaceId,
749
752
  subjectId: input.subjectId,
@@ -753,7 +756,7 @@ export async function browseGoogleDriveIntegrationSource(
753
756
  });
754
757
  }
755
758
 
756
- export async function saveGoogleDriveIntegrationSource(
759
+ export async function saveGoogleDriveFacetSource(
757
760
  deps: ApiRouteDeps,
758
761
  input: {
759
762
  accountId: string;
@@ -761,19 +764,19 @@ export async function saveGoogleDriveIntegrationSource(
761
764
  subjectId: string;
762
765
  capabilityId: string;
763
766
  instanceKey: string;
764
- featureKey: string;
767
+ facetKey: string;
765
768
  payload: unknown;
766
769
  canManageOrganizationDestination: boolean;
767
770
  canManageWorkspaceDestination: boolean;
768
771
  canManagePersonalDestination: boolean;
769
772
  },
770
- ): Promise<IntegrationFeatureMutationResult> {
773
+ ): Promise<IntegrationFacetMutationResult> {
771
774
  const parsedPayload = SaveGoogleDriveIntegrationSourceRequest.safeParse(input.payload);
772
775
  if (!parsedPayload.success) {
773
776
  throw new HTTPException(400, { message: "invalid Google Drive source selection" });
774
777
  }
775
778
  const payload = parsedPayload.data;
776
- const context = await requireGoogleDriveIntegrationFeature(deps, input);
779
+ const context = await requireGoogleDriveIntegrationFacet(deps, input);
777
780
  const documentDestination = googleDriveDocumentDestination(input, payload);
778
781
  const verifiedSources = await verifyGoogleDriveSources(deps, {
779
782
  workspaceId: input.workspaceId,
@@ -811,16 +814,16 @@ export async function saveGoogleDriveIntegrationSource(
811
814
  syncCadence: payload.syncCadence,
812
815
  readPolicy: payload.readPolicy,
813
816
  });
814
- return IntegrationFeatureMutationResult.parse(
815
- await configureIntegrationFeature(deps.db, {
817
+ return IntegrationFacetMutationResult.parse(
818
+ await configureIntegrationFacet(deps.db, {
816
819
  accountId: input.accountId,
817
820
  workspaceId: input.workspaceId,
818
821
  subjectId: input.subjectId,
819
822
  capabilityId: input.capabilityId,
820
823
  instanceKey: input.instanceKey,
821
- featureKey: input.featureKey,
824
+ facetKey: input.facetKey,
822
825
  displayName:
823
- context.feature.binding?.displayName ?? `${input.instanceKey} — Google Drive content`,
826
+ context.facet.binding?.displayName ?? `${input.instanceKey} — Google Drive content`,
824
827
  config,
825
828
  ...(payload.expectedVersion !== undefined
826
829
  ? { expectedVersion: payload.expectedVersion }
@@ -932,30 +935,30 @@ export async function saveGoogleDriveSource(
932
935
  return updated;
933
936
  }
934
937
 
935
- async function requireGoogleDriveIntegrationFeature(
938
+ async function requireGoogleDriveIntegrationFacet(
936
939
  deps: ApiRouteDeps,
937
940
  input: {
938
941
  workspaceId: string;
939
942
  subjectId: string;
940
943
  capabilityId: string;
941
944
  instanceKey: string;
942
- featureKey: string;
945
+ facetKey: string;
943
946
  },
944
947
  ) {
945
- const instance = await listIntegrationInstanceFeatures(
948
+ const instance = await listIntegrationInstanceFacets(
946
949
  deps.db,
947
950
  input.workspaceId,
948
951
  input.subjectId,
949
952
  input.capabilityId,
950
953
  input.instanceKey,
951
954
  );
952
- const feature = instance.features.find(
953
- (candidate) => candidate.definition.featureKey === input.featureKey,
955
+ const facet = instance.facets.find(
956
+ (candidate) => candidate.definition.facetKey === input.facetKey,
954
957
  );
955
958
  if (
956
- !feature ||
957
- feature.definition.kind !== "knowledge_source" ||
958
- feature.definition.capabilities.provider !== "google-drive"
959
+ !facet ||
960
+ facet.definition.kind !== "knowledge_source" ||
961
+ facet.definition.capabilities.provider !== "google-drive"
959
962
  ) {
960
963
  throw new HTTPException(404, { message: "Google Drive knowledge source not found" });
961
964
  }
@@ -972,7 +975,7 @@ async function requireGoogleDriveIntegrationFeature(
972
975
  throw new HTTPException(404, { message: "Google Drive Connection not found" });
973
976
  }
974
977
  await requireGoogleDriveApiConnection(deps, connection, input.subjectId);
975
- return { instance, feature, connection };
978
+ return { instance, facet, connection };
976
979
  }
977
980
 
978
981
  function googleDriveDocumentDestination(
@@ -1531,7 +1534,9 @@ async function requireGoogleDriveApiConnection(
1531
1534
  ): Promise<GoogleDriveApiConnection> {
1532
1535
  const legacy = GoogleDriveConnectionMetadata.safeParse(connection.metadata);
1533
1536
  const integration = ApiIntegrationOAuthConnectionMetadata.safeParse(connection.metadata);
1534
- const genericProviderDomain = providerDomainForPreset(GOOGLE_DRIVE_PRESET);
1537
+ const genericProviderDomain = integrationDefinitionProviderDomain(
1538
+ GOOGLE_DRIVE_INTEGRATION_DEFINITION,
1539
+ );
1535
1540
  const authority: GoogleDriveApiConnection | null =
1536
1541
  connection.kind === "oauth2" &&
1537
1542
  connection.subjectId === subjectId &&
@@ -1544,7 +1549,7 @@ async function requireGoogleDriveApiConnection(
1544
1549
  integration.success &&
1545
1550
  integration.data.credentialRole === API_INTEGRATION_OAUTH_CREDENTIAL_ROLE &&
1546
1551
  integration.data.providerFamily === "google" &&
1547
- integration.data.authorizedPresetIds.includes(GOOGLE_DRIVE_PRESET.id)
1552
+ integration.data.authorizedDefinitionIds.includes(GOOGLE_DRIVE_INTEGRATION_DEFINITION.id)
1548
1553
  ? { kind: "integration", connection, metadata: integration.data }
1549
1554
  : null;
1550
1555
  if (!authority) {
@@ -2,9 +2,9 @@ import { Buffer } from "node:buffer";
2
2
  import { createHash, randomBytes } from "node:crypto";
3
3
 
4
4
  import {
5
- providerDomainForPreset,
6
- providerPresetById,
7
- type OpenApiProviderPreset,
5
+ integrationDefinitionProviderDomain,
6
+ integrationDefinitionById,
7
+ type IntegrationDefinition,
8
8
  } from "@opengeni/capabilities";
9
9
  import {
10
10
  parseIntegrationsOauthClientsJson,
@@ -60,8 +60,8 @@ type ProviderOAuthState = {
60
60
  workspaceId: string;
61
61
  subjectId: string;
62
62
  ownership: ConnectionOwnership;
63
- presetId: string;
64
- presetFingerprint: string;
63
+ definitionId: string;
64
+ definitionFingerprint: string;
65
65
  providerDomain: string;
66
66
  authorizeScopes: string[];
67
67
  encryptedPkceVerifier: string;
@@ -121,8 +121,8 @@ export async function startApiIntegrationProviderOAuth(
121
121
  payload: ApiIntegrationOAuthStartRequest;
122
122
  },
123
123
  ): Promise<OAuthStartResponse> {
124
- const preset = requiredPreset(input.payload.presetId);
125
- const providerDomain = providerDomainForPreset(preset);
124
+ const definition = requiredDefinition(input.payload.definitionId);
125
+ const providerDomain = integrationDefinitionProviderDomain(definition);
126
126
  const existing = input.payload.connectionId
127
127
  ? await getConnectionMetadata(
128
128
  deps.db,
@@ -138,7 +138,7 @@ export async function startApiIntegrationProviderOAuth(
138
138
  ? requireProviderOAuthConnection(existing, {
139
139
  subjectId: input.subjectId,
140
140
  providerDomain,
141
- providerFamily: preset.family,
141
+ providerFamily: definition.provider.id,
142
142
  })
143
143
  : null;
144
144
  const existingOwnership: ConnectionOwnership | null = existing
@@ -158,13 +158,13 @@ export async function startApiIntegrationProviderOAuth(
158
158
  const ownership = existingOwnership ?? input.payload.ownership ?? "personal";
159
159
  const authorizeScopes = uniqueStrings([
160
160
  ...(existing?.grantedScopes ?? []),
161
- ...preset.oauth.scopes,
161
+ ...definition.authentication.scopes,
162
162
  ]);
163
- const client = providerClientForPreset(deps.settings, preset);
163
+ const client = providerClientForDefinition(deps.settings, definition);
164
164
  const verifier = randomBytes(48).toString("base64url");
165
165
  const key = requireEnvironmentEncryption(deps.settings);
166
166
  const baseUrl = integrationBaseUrl(deps.settings.publicBaseUrl, input.requestUrl);
167
- const redirectUri = `${baseUrl}${providerOAuthCallbackPath(preset)}`;
167
+ const redirectUri = `${baseUrl}${PROVIDER_OAUTH_CALLBACK_PATH}`;
168
168
  const returnPath = safeReturnPath(
169
169
  input.payload.returnPath ?? `/workspaces/${input.workspaceId}/capabilities`,
170
170
  );
@@ -173,8 +173,8 @@ export async function startApiIntegrationProviderOAuth(
173
173
  workspaceId: input.workspaceId,
174
174
  subjectId: input.subjectId,
175
175
  ownership,
176
- presetId: preset.id,
177
- presetFingerprint: providerPresetFingerprint(preset),
176
+ definitionId: definition.id,
177
+ definitionFingerprint: providerDefinitionFingerprint(definition),
178
178
  providerDomain,
179
179
  authorizeScopes,
180
180
  encryptedPkceVerifier: encryptEnvironmentValue(key, verifier),
@@ -189,7 +189,7 @@ export async function startApiIntegrationProviderOAuth(
189
189
  }
190
190
  : {}),
191
191
  });
192
- const authorizationUrl = new URL(preset.oauth.authorizationUrl);
192
+ const authorizationUrl = new URL(definition.authentication.authorizationUrl);
193
193
  authorizationUrl.searchParams.set("response_type", "code");
194
194
  authorizationUrl.searchParams.set("client_id", client.clientId);
195
195
  authorizationUrl.searchParams.set("redirect_uri", redirectUri);
@@ -201,7 +201,7 @@ export async function startApiIntegrationProviderOAuth(
201
201
  createHash("sha256").update(verifier).digest("base64url"),
202
202
  );
203
203
  authorizationUrl.searchParams.set("prompt", "select_account");
204
- if (preset.family === "google") {
204
+ if (definition.provider.id === "google") {
205
205
  authorizationUrl.searchParams.set("access_type", "offline");
206
206
  authorizationUrl.searchParams.set("include_granted_scopes", "true");
207
207
  authorizationUrl.searchParams.set("prompt", "consent select_account");
@@ -240,16 +240,16 @@ export async function completeApiIntegrationProviderOAuth(
240
240
  if (input.error) throw new ProviderOAuthCallbackError("provider_denied");
241
241
  if (!input.code) throw new ProviderOAuthCallbackError("missing_code");
242
242
 
243
- const preset = providerPresetById(state.presetId);
244
- if (!preset) throw new ProviderOAuthCallbackError("state_invalid");
243
+ const definition = integrationDefinitionById(state.definitionId);
244
+ if (!definition) throw new ProviderOAuthCallbackError("state_invalid");
245
245
  if (
246
- state.presetFingerprint !== providerPresetFingerprint(preset) ||
247
- state.providerDomain !== providerDomainForPreset(preset) ||
248
- preset.oauth.scopes.some((scope) => !state!.authorizeScopes.includes(scope))
246
+ state.definitionFingerprint !== providerDefinitionFingerprint(definition) ||
247
+ state.providerDomain !== integrationDefinitionProviderDomain(definition) ||
248
+ definition.authentication.scopes.some((scope) => !state!.authorizeScopes.includes(scope))
249
249
  ) {
250
250
  throw new ProviderOAuthCallbackError("state_invalid");
251
251
  }
252
- const client = providerClientForPreset(deps.settings, preset);
252
+ const client = providerClientForDefinition(deps.settings, definition);
253
253
  if (
254
254
  client.clientId !== state.clientId ||
255
255
  client.tokenEndpointAuthMethod !== state.tokenEndpointAuthMethod
@@ -258,17 +258,17 @@ export async function completeApiIntegrationProviderOAuth(
258
258
  }
259
259
  const key = requireEnvironmentEncryption(deps.settings);
260
260
  const verifier = decryptEnvironmentValue(key, state.encryptedPkceVerifier);
261
- const redirectUri = `${apiBaseUrl}${providerOAuthCallbackPath(preset)}`;
262
- const token = await exchangeProviderAuthorizationCode(deps, preset, client, {
261
+ const redirectUri = `${apiBaseUrl}${PROVIDER_OAUTH_CALLBACK_PATH}`;
262
+ const token = await exchangeProviderAuthorizationCode(deps, definition, client, {
263
263
  code: input.code,
264
264
  verifier,
265
265
  redirectUri,
266
266
  });
267
- if (!providerScopesInclude(preset, token.scopes, state.authorizeScopes)) {
267
+ if (!providerScopesInclude(definition, token.scopes, state.authorizeScopes)) {
268
268
  throw new ProviderOAuthCallbackError("scope_not_granted");
269
269
  }
270
270
  const grantedScopes = token.scopes.length > 0 ? token.scopes : state.authorizeScopes;
271
- const identity = await verifyProviderIdentity(deps, preset, token);
271
+ const identity = await verifyProviderIdentity(deps, definition, token);
272
272
  if (
273
273
  state.expectedProviderPrincipalId &&
274
274
  state.expectedProviderPrincipalId !== identity.principalId
@@ -287,7 +287,7 @@ export async function completeApiIntegrationProviderOAuth(
287
287
  ? requireProviderOAuthConnection(existing, {
288
288
  subjectId: state.subjectId,
289
289
  providerDomain: state.providerDomain,
290
- providerFamily: preset.family,
290
+ providerFamily: definition.provider.id,
291
291
  })
292
292
  : null;
293
293
  if (existingMetadata && existingMetadata.providerPrincipalId !== identity.principalId) {
@@ -315,7 +315,7 @@ export async function completeApiIntegrationProviderOAuth(
315
315
  token_type: token.tokenType,
316
316
  ...(token.expiresAt ? { expires_at: token.expiresAt.toISOString() } : {}),
317
317
  scope: grantedScopes.join(" "),
318
- token_endpoint: preset.oauth.tokenUrl,
318
+ token_endpoint: definition.authentication.tokenUrl,
319
319
  client_id: client.clientId,
320
320
  ...(client.clientSecret
321
321
  ? {
@@ -329,13 +329,13 @@ export async function completeApiIntegrationProviderOAuth(
329
329
  const metadata = ApiIntegrationOAuthConnectionMetadata.parse({
330
330
  ...(existing?.metadata ?? {}),
331
331
  credentialRole: API_INTEGRATION_OAUTH_CREDENTIAL_ROLE,
332
- providerFamily: preset.family,
332
+ providerFamily: definition.provider.id,
333
333
  providerPrincipalId: identity.principalId,
334
334
  providerEmail: identity.email,
335
335
  providerDisplayName: identity.displayName,
336
- authorizedPresetIds: uniqueStrings([
337
- ...(existingMetadata?.authorizedPresetIds ?? []),
338
- preset.id,
336
+ authorizedDefinitionIds: uniqueStrings([
337
+ ...(existingMetadata?.authorizedDefinitionIds ?? []),
338
+ definition.id,
339
339
  ]),
340
340
  verifiedAt: new Date().toISOString(),
341
341
  });
@@ -354,7 +354,7 @@ export async function completeApiIntegrationProviderOAuth(
354
354
  createdBySubjectId: state.subjectId,
355
355
  updatedBySubjectId: state.subjectId,
356
356
  credentialRole: API_INTEGRATION_OAUTH_CREDENTIAL_ROLE,
357
- providerFamily: preset.family,
357
+ providerFamily: definition.provider.id,
358
358
  providerPrincipalId: identity.principalId,
359
359
  ...(state.connectionId
360
360
  ? {
@@ -366,7 +366,7 @@ export async function completeApiIntegrationProviderOAuth(
366
366
  if (!connection) throw new ProviderOAuthCallbackError("connection_conflict");
367
367
  return {
368
368
  redirectTo: providerOAuthReturnUrl(returnBaseUrl, state.returnPath, "success", {
369
- presetId: preset.id,
369
+ definitionId: definition.id,
370
370
  connectionId: connection.id,
371
371
  providerDomain: connection.providerDomain,
372
372
  ownership: state.ownership,
@@ -379,7 +379,7 @@ export async function completeApiIntegrationProviderOAuth(
379
379
  state?.returnPath ?? "/integrations",
380
380
  "error",
381
381
  {
382
- ...(state?.presetId ? { presetId: state.presetId } : {}),
382
+ ...(state?.definitionId ? { definitionId: state.definitionId } : {}),
383
383
  reason: providerOAuthFailureReason(error),
384
384
  },
385
385
  ),
@@ -387,39 +387,18 @@ export async function completeApiIntegrationProviderOAuth(
387
387
  }
388
388
  }
389
389
 
390
- function requiredPreset(id: string): OpenApiProviderPreset {
391
- const preset = providerPresetById(id);
392
- if (!preset) throw new HTTPException(404, { message: "Unknown Integration preset" });
393
- return preset;
390
+ function requiredDefinition(id: string): IntegrationDefinition {
391
+ const definition = integrationDefinitionById(id);
392
+ if (!definition) throw new HTTPException(404, { message: "Unknown Integration definition" });
393
+ return definition;
394
394
  }
395
395
 
396
- export function isApiIntegrationProviderOAuthState(
397
- value: string | undefined,
396
+ function providerClientForDefinition(
398
397
  settings: Settings,
399
- ): boolean {
400
- try {
401
- readProviderOAuthState(value, settings);
402
- return true;
403
- } catch {
404
- return false;
405
- }
406
- }
407
-
408
- function providerOAuthCallbackPath(preset: OpenApiProviderPreset): string {
409
- // The existing Google OAuth client is already registered against this
410
- // loopback callback. Keep that stable while the handler below distinguishes
411
- // legacy Drive state from the protocol-neutral provider state.
412
- return preset.family === "google"
413
- ? "/v1/integrations/google-drive/callback"
414
- : PROVIDER_OAUTH_CALLBACK_PATH;
415
- }
416
-
417
- function providerClientForPreset(
418
- settings: Settings,
419
- preset: OpenApiProviderPreset,
398
+ definition: IntegrationDefinition,
420
399
  ): ProviderOAuthClient {
421
400
  const configured = parseIntegrationsOauthClientsJson(settings.integrationsOauthClientsJson);
422
- const candidates = providerClientKeys(preset);
401
+ const candidates = providerClientKeys(definition);
423
402
  let entry: IntegrationOAuthClientConfig | undefined;
424
403
  for (const candidate of candidates) {
425
404
  entry = configured[candidate] ?? configured[candidate.replace(/\/+$/, "")];
@@ -433,7 +412,7 @@ function providerClientForPreset(
433
412
  }
434
413
  if (
435
414
  !entry &&
436
- preset.family === "google" &&
415
+ definition.provider.id === "google" &&
437
416
  settings.googleDriveClientId?.trim() &&
438
417
  settings.googleDriveClientSecret?.trim()
439
418
  ) {
@@ -445,7 +424,7 @@ function providerClientForPreset(
445
424
  }
446
425
  if (!entry) {
447
426
  throw new HTTPException(503, {
448
- message: `${preset.name} requires an operator OAuth client configuration`,
427
+ message: `${definition.name} requires an operator OAuth client configuration`,
449
428
  });
450
429
  }
451
430
  return {
@@ -455,15 +434,15 @@ function providerClientForPreset(
455
434
  };
456
435
  }
457
436
 
458
- function providerClientKeys(preset: OpenApiProviderPreset): string[] {
459
- const authorization = new URL(preset.oauth.authorizationUrl);
460
- const token = new URL(preset.oauth.tokenUrl);
437
+ function providerClientKeys(definition: IntegrationDefinition): string[] {
438
+ const authorization = new URL(definition.authentication.authorizationUrl);
439
+ const token = new URL(definition.authentication.tokenUrl);
461
440
  return uniqueStrings([
462
- preset.oauth.authorizationUrl,
441
+ definition.authentication.authorizationUrl,
463
442
  authorization.origin,
464
- preset.oauth.tokenUrl,
443
+ definition.authentication.tokenUrl,
465
444
  token.origin,
466
- ...(preset.family === "google"
445
+ ...(definition.provider.id === "google"
467
446
  ? ["https://accounts.google.com"]
468
447
  : [
469
448
  "https://login.microsoftonline.com/common/v2.0",
@@ -473,16 +452,16 @@ function providerClientKeys(preset: OpenApiProviderPreset): string[] {
473
452
  ]);
474
453
  }
475
454
 
476
- function providerPresetFingerprint(preset: OpenApiProviderPreset): string {
455
+ function providerDefinitionFingerprint(definition: IntegrationDefinition): string {
477
456
  return createHash("sha256")
478
457
  .update(
479
458
  JSON.stringify({
480
- id: preset.id,
481
- family: preset.family,
482
- baseUrl: preset.baseUrl,
483
- authorizationUrl: preset.oauth.authorizationUrl,
484
- tokenUrl: preset.oauth.tokenUrl,
485
- scopes: [...preset.oauth.scopes],
459
+ id: definition.id,
460
+ provider: definition.provider,
461
+ baseUrl: definition.baseUrl,
462
+ authorizationUrl: definition.authentication.authorizationUrl,
463
+ tokenUrl: definition.authentication.tokenUrl,
464
+ scopes: [...definition.authentication.scopes],
486
465
  }),
487
466
  )
488
467
  .digest("hex");
@@ -524,8 +503,8 @@ function readProviderOAuthState(raw: string | undefined, settings: Settings): Pr
524
503
  workspaceId: requiredStateString(payload.workspaceId),
525
504
  subjectId: requiredStateString(payload.subjectId),
526
505
  ownership,
527
- presetId: requiredStateString(payload.presetId),
528
- presetFingerprint: requiredStateString(payload.presetFingerprint),
506
+ definitionId: requiredStateString(payload.definitionId),
507
+ definitionFingerprint: requiredStateString(payload.definitionFingerprint),
529
508
  providerDomain: requiredStateString(payload.providerDomain),
530
509
  authorizeScopes,
531
510
  encryptedPkceVerifier: requiredStateString(payload.encryptedPkceVerifier),
@@ -543,7 +522,7 @@ function readProviderOAuthState(raw: string | undefined, settings: Settings): Pr
543
522
 
544
523
  async function exchangeProviderAuthorizationCode(
545
524
  deps: ApiRouteDeps,
546
- preset: OpenApiProviderPreset,
525
+ definition: IntegrationDefinition,
547
526
  client: ProviderOAuthClient,
548
527
  input: { code: string; verifier: string; redirectUri: string },
549
528
  ): Promise<ProviderToken> {
@@ -565,7 +544,7 @@ async function exchangeProviderAuthorizationCode(
565
544
  } else {
566
545
  body.set("client_id", client.clientId);
567
546
  }
568
- const response = await providerFetch(deps, preset.oauth.tokenUrl, {
547
+ const response = await providerFetch(deps, definition.authentication.tokenUrl, {
569
548
  method: "POST",
570
549
  headers,
571
550
  body,
@@ -577,7 +556,7 @@ async function exchangeProviderAuthorizationCode(
577
556
  const payload = await readResponseJsonBounded<Record<string, unknown>>(
578
557
  response,
579
558
  OAUTH_MAX_RESPONSE_BYTES,
580
- `${preset.name} OAuth token response`,
559
+ `${definition.name} OAuth token response`,
581
560
  ).catch(() => {
582
561
  throw new ProviderOAuthCallbackError("token_exchange_failed");
583
562
  });
@@ -597,10 +576,10 @@ async function exchangeProviderAuthorizationCode(
597
576
 
598
577
  async function verifyProviderIdentity(
599
578
  deps: ApiRouteDeps,
600
- preset: OpenApiProviderPreset,
579
+ definition: IntegrationDefinition,
601
580
  token: ProviderToken,
602
581
  ): Promise<ProviderIdentity> {
603
- const url = preset.family === "google" ? GOOGLE_USERINFO_URL : MICROSOFT_USERINFO_URL;
582
+ const url = definition.provider.id === "google" ? GOOGLE_USERINFO_URL : MICROSOFT_USERINFO_URL;
604
583
  const response = await providerFetch(deps, url, {
605
584
  headers: {
606
585
  accept: "application/json",
@@ -614,11 +593,11 @@ async function verifyProviderIdentity(
614
593
  const payload = await readResponseJsonBounded<Record<string, unknown>>(
615
594
  response,
616
595
  OAUTH_MAX_RESPONSE_BYTES,
617
- `${preset.name} identity response`,
596
+ `${definition.name} identity response`,
618
597
  ).catch(() => {
619
598
  throw new ProviderOAuthCallbackError("identity_verification_failed");
620
599
  });
621
- if (preset.family === "google") {
600
+ if (definition.provider.id === "google") {
622
601
  const principalId = optionalString(payload.sub);
623
602
  if (!principalId) throw new ProviderOAuthCallbackError("identity_verification_failed");
624
603
  return {
@@ -683,7 +662,7 @@ function requireProviderOAuthConnection(
683
662
  input: {
684
663
  subjectId: string;
685
664
  providerDomain: string;
686
- providerFamily: OpenApiProviderPreset["family"];
665
+ providerFamily: IntegrationDefinition["provider"]["id"];
687
666
  },
688
667
  ) {
689
668
  const metadata = ApiIntegrationOAuthConnectionMetadata.safeParse(connection.metadata);
@@ -695,22 +674,22 @@ function requireProviderOAuthConnection(
695
674
  metadata.data.providerFamily !== input.providerFamily
696
675
  ) {
697
676
  throw new HTTPException(422, {
698
- message: "Connection is not compatible with this Integration preset",
677
+ message: "Connection is not compatible with this Integration definition",
699
678
  });
700
679
  }
701
680
  return metadata.data;
702
681
  }
703
682
 
704
683
  function providerScopesInclude(
705
- preset: OpenApiProviderPreset,
684
+ definition: IntegrationDefinition,
706
685
  returnedScopes: string[],
707
686
  fallbackScopes: string[],
708
687
  ): boolean {
709
688
  const effective = returnedScopes.length > 0 ? returnedScopes : fallbackScopes;
710
689
  const normalize = (value: string) =>
711
- preset.family === "microsoft" ? value.toLowerCase() : value;
690
+ definition.provider.id === "microsoft" ? value.toLowerCase() : value;
712
691
  const granted = new Set(effective.map(normalize));
713
- return preset.oauth.scopes.every((scope) => granted.has(normalize(scope)));
692
+ return definition.authentication.scopes.every((scope) => granted.has(normalize(scope)));
714
693
  }
715
694
 
716
695
  function providerOAuthReturnUrl(