@opengeni/api-router 0.30.1 → 1.0.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 (436) hide show
  1. package/dist/access-grant-rls.d.ts +3 -0
  2. package/dist/admission-trace.d.ts +8 -0
  3. package/dist/api-websocket.d.ts +18 -0
  4. package/dist/app.d.ts +50 -2
  5. package/dist/app.js +12 -1
  6. package/dist/artifact-catalog-cursor.d.ts +27 -0
  7. package/dist/auth/managed-auth-attempt-context.d.ts +10 -0
  8. package/dist/auth/managed-auth-rate-limits.d.ts +167 -0
  9. package/dist/auth/managed-auth-session-adapter.d.ts +6 -0
  10. package/dist/auth/managed-auth.d.ts +102 -3
  11. package/dist/auth/managed-email.d.ts +29 -0
  12. package/dist/auth/managed-sign-in-notifications.d.ts +4 -0
  13. package/dist/auth/new-signups-gate.d.ts +23 -0
  14. package/dist/auth/organization-user-setup.d.ts +80 -0
  15. package/dist/auth/signup-funnel-metrics.d.ts +44 -0
  16. package/dist/browser-controller-authority.d.ts +1 -61
  17. package/dist/browser-ephemeral.d.ts +5 -0
  18. package/dist/browser-state-authority.d.ts +1 -27
  19. package/dist/chunk-6TZ5LG3O.js +123948 -0
  20. package/dist/chunk-6TZ5LG3O.js.map +1 -0
  21. package/dist/chunk-OGQDXVXY.js +18 -0
  22. package/dist/chunk-OGQDXVXY.js.map +1 -0
  23. package/dist/claude-subscription-account-conflicts.d.ts +1 -0
  24. package/dist/claude-subscription-account-usage.d.ts +39 -0
  25. package/dist/claude-subscription-oauth.d.ts +25 -0
  26. package/dist/claude-subscription-profile.d.ts +9 -0
  27. package/dist/claude-subscription-usage.d.ts +44 -0
  28. package/dist/claude-workspace-connection.d.ts +15 -0
  29. package/dist/codemode.d.ts +32 -1
  30. package/dist/company-brain-okf.d.ts +24 -0
  31. package/dist/connected-machine-computer-access.d.ts +11 -0
  32. package/dist/connection-ownership.d.ts +54 -0
  33. package/dist/controller-data-plane.d.ts +32 -0
  34. package/dist/editable-artifact-office-import.d.ts +7 -4
  35. package/dist/editable-artifact-source-session.d.ts +3 -0
  36. package/dist/editable-artifact-websocket.d.ts +10 -18
  37. package/dist/editable-artifact-workspace-files.d.ts +1 -1
  38. package/dist/fatal-process-boundary.d.ts +25 -0
  39. package/dist/github-access.d.ts +31 -2
  40. package/dist/http/acting-person.d.ts +24 -0
  41. package/dist/http/api-contract-compat.d.ts +19 -0
  42. package/dist/http/api-error.d.ts +27 -1
  43. package/dist/http/cors.d.ts +17 -0
  44. package/dist/http/deprecation.d.ts +42 -0
  45. package/dist/http/interaction-control-error.d.ts +13 -0
  46. package/dist/http/keyed-admission.d.ts +12 -0
  47. package/dist/http/local-browser-boundary.d.ts +72 -0
  48. package/dist/http/metrics-listener.d.ts +22 -0
  49. package/dist/http/path-identifier.d.ts +17 -0
  50. package/dist/http/registered-route-label.d.ts +16 -0
  51. package/dist/http/request-body.d.ts +27 -0
  52. package/dist/http/request-source.d.ts +52 -0
  53. package/dist/http/sse.d.ts +17 -6
  54. package/dist/http/unmatched-route.d.ts +18 -0
  55. package/dist/http/user-content.d.ts +36 -0
  56. package/dist/http/websocket-upgrade-dispatch.d.ts +18 -0
  57. package/dist/index.d.ts +10 -3
  58. package/dist/index.js +1129 -166
  59. package/dist/index.js.map +1 -1
  60. package/dist/integration-endpoint-test.d.ts +20 -0
  61. package/dist/integrations/api-integrations.d.ts +2 -2
  62. package/dist/integrations/atlassian.d.ts +29 -3
  63. package/dist/integrations/connect-authority.d.ts +1 -0
  64. package/dist/integrations/connect-callback-return.d.ts +6 -0
  65. package/dist/integrations/fiken.d.ts +16 -2
  66. package/dist/integrations/github-app-connect.d.ts +47 -0
  67. package/dist/integrations/github-installation-proof.d.ts +3 -0
  68. package/dist/integrations/github-lens-connect.d.ts +116 -0
  69. package/dist/integrations/github-skill-source.d.ts +1 -5
  70. package/dist/integrations/google-drive.d.ts +25 -0
  71. package/dist/integrations/integration-commit-authority.d.ts +13 -0
  72. package/dist/integrations/marketplace-plugin.d.ts +2691 -0
  73. package/dist/integrations/oauth-client.d.ts +178 -18
  74. package/dist/integrations/oauth-profiles.d.ts +222 -0
  75. package/dist/integrations/oauth-return-path.d.ts +1 -0
  76. package/dist/integrations/personal-github-repositories.d.ts +84 -0
  77. package/dist/integrations/personal-github.d.ts +42 -0
  78. package/dist/integrations/pr-review-provider.d.ts +22 -0
  79. package/dist/integrations/provider-oauth.d.ts +19 -1
  80. package/dist/integrations/slack-app-home.d.ts +24 -0
  81. package/dist/integrations/slack-bot.d.ts +367 -12
  82. package/dist/integrations/slack-file-upload-flow.d.ts +62 -0
  83. package/dist/integrations/slack-install.d.ts +20 -0
  84. package/dist/integrations/slack-interactions.d.ts +113 -10
  85. package/dist/integrations/slack-mrkdwn.d.ts +41 -0
  86. package/dist/integrations/slack-routing.d.ts +120 -0
  87. package/dist/integrations/slack-session-defaults.d.ts +58 -0
  88. package/dist/integrations/slack-task-file-upload.d.ts +36 -0
  89. package/dist/integrations/social-oauth.d.ts +7 -0
  90. package/dist/interaction-agent-access.d.ts +15 -0
  91. package/dist/interaction-frame-proxy.d.ts +68 -0
  92. package/dist/interaction-metrics.d.ts +2 -0
  93. package/dist/mcp/action-catalog-types.d.ts +9 -0
  94. package/dist/mcp/action-catalog.gen.d.ts +2 -0
  95. package/dist/mcp/capability-account-readiness.d.ts +7 -0
  96. package/dist/mcp/child-read-evidence.d.ts +9 -0
  97. package/dist/mcp/command-read.d.ts +24 -0
  98. package/dist/mcp/company-profile-agent-admin.d.ts +44 -0
  99. package/dist/mcp/contract-input.d.ts +11 -0
  100. package/dist/mcp/documents.d.ts +7 -5
  101. package/dist/mcp/files.d.ts +2 -2
  102. package/dist/mcp/first-party-tool-permissions.d.ts +650 -0
  103. package/dist/mcp/knowledge-entries.d.ts +5 -0
  104. package/dist/mcp/orchestration-failure-diagnostic.d.ts +43 -0
  105. package/dist/mcp/receipts.d.ts +9 -0
  106. package/dist/mcp/request-abort.d.ts +19 -0
  107. package/dist/mcp/scheduled-task-input.d.ts +1189 -0
  108. package/dist/mcp/scheduled-task-view.d.ts +22 -4
  109. package/dist/mcp/server.d.ts +255 -3
  110. package/dist/mcp/session-event-view.d.ts +94 -0
  111. package/dist/mcp/session-view.d.ts +66 -2
  112. package/dist/mcp/session-wait.d.ts +174 -0
  113. package/dist/mcp-oauth.d.ts +52 -0
  114. package/dist/model-catalog.d.ts +11 -31
  115. package/dist/observability.d.ts +3 -1
  116. package/dist/organization-mcp.d.ts +52 -0
  117. package/dist/organization-recovery-notifications.d.ts +41 -0
  118. package/dist/routes/analytics-consent.d.ts +26 -0
  119. package/dist/routes/api-integrations.d.ts +25 -0
  120. package/dist/routes/api-keys.d.ts +17 -0
  121. package/dist/routes/artifact-catalog.d.ts +6 -0
  122. package/dist/routes/automations.d.ts +19 -0
  123. package/dist/routes/billing.d.ts +49 -0
  124. package/dist/routes/browser-sessions.d.ts +71 -3
  125. package/dist/routes/claude-subscription-accounts.d.ts +3 -0
  126. package/dist/routes/claude-subscription-oauth.d.ts +3 -0
  127. package/dist/routes/client-errors.d.ts +146 -0
  128. package/dist/routes/codex.d.ts +76 -2
  129. package/dist/routes/company-brain.d.ts +3 -0
  130. package/dist/routes/company-profile.d.ts +2 -1
  131. package/dist/routes/computer-sessions.d.ts +14 -0
  132. package/dist/routes/connect.d.ts +5 -0
  133. package/dist/routes/editable-artifacts.d.ts +2 -0
  134. package/dist/routes/external-identity-links.d.ts +6 -0
  135. package/dist/routes/feedback.d.ts +3 -0
  136. package/dist/routes/files.d.ts +4 -0
  137. package/dist/routes/github-browser-pages.d.ts +12 -0
  138. package/dist/routes/github.d.ts +2 -0
  139. package/dist/routes/insights-response-cache.d.ts +10 -0
  140. package/dist/routes/insights-usage.d.ts +12 -0
  141. package/dist/routes/insights.d.ts +30 -1
  142. package/dist/routes/knowledge.d.ts +3 -0
  143. package/dist/routes/managed-auth-session-sets.d.ts +26 -0
  144. package/dist/routes/managed-onboarding.d.ts +29 -0
  145. package/dist/routes/managed-sign-in-methods.d.ts +6 -0
  146. package/dist/routes/model-connection-access.d.ts +3 -0
  147. package/dist/routes/organization-integration-policy.d.ts +3 -0
  148. package/dist/routes/organization-integrations.d.ts +6 -0
  149. package/dist/routes/organization-mcp-connections.d.ts +3 -0
  150. package/dist/routes/organization-model-providers.d.ts +3 -0
  151. package/dist/routes/organization-recovery.d.ts +19 -0
  152. package/dist/routes/organization-service-accounts.d.ts +3 -0
  153. package/dist/routes/organization-sessions.d.ts +26 -0
  154. package/dist/routes/personal-github-git-broker.d.ts +29 -0
  155. package/dist/routes/personal-github.d.ts +3 -0
  156. package/dist/routes/pr-review-github.d.ts +3 -0
  157. package/dist/routes/pr-review.d.ts +3 -0
  158. package/dist/routes/session-artifact-associations.d.ts +4 -0
  159. package/dist/routes/session-history-imports.d.ts +9 -0
  160. package/dist/routes/sessions.d.ts +44 -6
  161. package/dist/routes/skill-content.d.ts +4 -0
  162. package/dist/routes/skill-install-authority.d.ts +6 -0
  163. package/dist/routes/subscription-account-pools.d.ts +34 -0
  164. package/dist/routes/subscription-pool-access.d.ts +16 -0
  165. package/dist/routes/supergrok.d.ts +6 -1
  166. package/dist/routes/usage-allowances.d.ts +12 -0
  167. package/dist/routes/user-resource-authorities.d.ts +3 -0
  168. package/dist/routes/workspace-artifacts.d.ts +2 -1
  169. package/dist/routes/workspace-capture.d.ts +10 -2
  170. package/dist/routes/workspace-integrations.d.ts +35 -0
  171. package/dist/routes/workspace-learning.d.ts +3 -0
  172. package/dist/routes/workspace-model-providers.d.ts +4 -0
  173. package/dist/routes/workspaces.d.ts +29 -0
  174. package/dist/sandbox/auth-callout.d.ts +8 -0
  175. package/dist/sandbox/channel-a.d.ts +19 -1
  176. package/dist/sandbox/connection-authority.d.ts +3 -0
  177. package/dist/sandbox/enrollment.d.ts +9 -1
  178. package/dist/sandbox/machines.d.ts +5 -2
  179. package/dist/sandbox/metrics-ingestion.d.ts +28 -20
  180. package/dist/sandbox/rematerialize.d.ts +2 -0
  181. package/dist/sandbox/session-attach-variable-sets.d.ts +30 -0
  182. package/dist/sandbox/viewer.d.ts +26 -1
  183. package/dist/sandbox-file-artifacts.d.ts +50 -0
  184. package/dist/scheduled-task-deletion.d.ts +13 -0
  185. package/dist/session-create-observability.d.ts +5 -0
  186. package/dist/session-event-view-MPS2OZD3.js +292 -0
  187. package/dist/session-event-view-MPS2OZD3.js.map +1 -0
  188. package/dist/site-session-origin.d.ts +13 -0
  189. package/dist/site-uploads.d.ts +41 -0
  190. package/dist/slack-reaction-files.d.ts +1 -1
  191. package/dist/temporal-schedule-cleanup.d.ts +1 -0
  192. package/dist/temporal-schedule-sync.d.ts +26 -0
  193. package/dist/transcription/providers/xai-subscription.d.ts +1 -0
  194. package/dist/transcription/service.d.ts +2 -1
  195. package/dist/work-discovery-observability.d.ts +33 -0
  196. package/dist/workspace-artifact-content.d.ts +28 -0
  197. package/dist/workspace-artifact-provenance.d.ts +7 -0
  198. package/dist/workspace-delete-observability.d.ts +10 -0
  199. package/dist/workspace-deletion.d.ts +6 -0
  200. package/dist/workspace-state-projection.d.ts +3 -5
  201. package/dist/workspace-tool-gateway-observability.d.ts +17 -0
  202. package/dist/workspace-tool-gateway.d.ts +123 -0
  203. package/dist/workspace-webhook-dispatch.d.ts +26 -0
  204. package/package.json +23 -19
  205. package/src/access-grant-rls.ts +75 -0
  206. package/src/admission-trace.ts +14 -0
  207. package/src/api-websocket.ts +23 -0
  208. package/src/app.ts +1974 -256
  209. package/src/artifact-catalog-cursor.ts +60 -0
  210. package/src/auth/managed-auth-attempt-context.ts +70 -0
  211. package/src/auth/managed-auth-rate-limits.ts +252 -0
  212. package/src/auth/managed-auth-session-adapter.ts +222 -0
  213. package/src/auth/managed-auth.ts +536 -60
  214. package/src/auth/managed-email.ts +174 -0
  215. package/src/auth/managed-sign-in-notifications.ts +81 -0
  216. package/src/auth/new-signups-gate.ts +49 -0
  217. package/src/auth/organization-user-setup.ts +307 -0
  218. package/src/auth/signup-funnel-metrics.ts +164 -0
  219. package/src/browser-controller-authority.ts +1 -186
  220. package/src/browser-ephemeral.ts +39 -0
  221. package/src/browser-state-authority.ts +1 -236
  222. package/src/claude-subscription-account-conflicts.ts +12 -0
  223. package/src/claude-subscription-account-usage.ts +48 -0
  224. package/src/claude-subscription-oauth.ts +362 -0
  225. package/src/claude-subscription-profile.ts +73 -0
  226. package/src/claude-subscription-usage.ts +97 -0
  227. package/src/claude-workspace-connection.ts +82 -0
  228. package/src/codemode.ts +194 -30
  229. package/src/codex-realtime.ts +8 -2
  230. package/src/company-brain-okf.ts +340 -0
  231. package/src/connected-machine-computer-access.ts +33 -0
  232. package/src/connection-ownership.ts +199 -0
  233. package/src/controller-data-plane.ts +81 -0
  234. package/src/editable-artifact-native-kernel.ts +26 -15
  235. package/src/editable-artifact-office-import.ts +77 -9
  236. package/src/editable-artifact-production.ts +23 -7
  237. package/src/editable-artifact-source-session.ts +123 -0
  238. package/src/editable-artifact-websocket.ts +27 -21
  239. package/src/editable-artifact-workspace-files.ts +31 -6
  240. package/src/fatal-process-boundary.ts +246 -0
  241. package/src/gateway-realtime.ts +1 -1
  242. package/src/github-access.ts +147 -5
  243. package/src/http/acting-person.ts +71 -0
  244. package/src/http/api-contract-compat.ts +49 -0
  245. package/src/http/api-error.ts +115 -1
  246. package/src/http/auth.ts +48 -0
  247. package/src/http/cors.ts +67 -0
  248. package/src/http/deprecation.ts +118 -0
  249. package/src/http/interaction-control-error.ts +164 -0
  250. package/src/http/keyed-admission.ts +29 -0
  251. package/src/http/local-browser-boundary.ts +343 -0
  252. package/src/http/metrics-listener.ts +57 -0
  253. package/src/http/path-identifier.ts +54 -0
  254. package/src/http/registered-route-label.ts +34 -0
  255. package/src/http/request-body.ts +127 -0
  256. package/src/http/request-source.ts +178 -0
  257. package/src/http/sse.ts +486 -132
  258. package/src/http/unmatched-route.ts +60 -0
  259. package/src/http/user-content.ts +158 -0
  260. package/src/http/websocket-upgrade-dispatch.ts +29 -0
  261. package/src/index.ts +157 -52
  262. package/src/integration-endpoint-test.ts +291 -0
  263. package/src/integrations/api-integrations.ts +98 -11
  264. package/src/integrations/atlassian.ts +474 -136
  265. package/src/integrations/connect-authority.ts +2 -0
  266. package/src/integrations/connect-callback-return.ts +86 -0
  267. package/src/integrations/fiken.ts +354 -64
  268. package/src/integrations/github-app-connect.ts +435 -0
  269. package/src/integrations/github-installation-proof.ts +60 -0
  270. package/src/integrations/github-lens-connect.ts +76 -0
  271. package/src/integrations/github-skill-source.ts +2 -142
  272. package/src/integrations/google-drive.ts +820 -222
  273. package/src/integrations/integration-commit-authority.ts +69 -0
  274. package/src/integrations/marketplace-plugin.ts +48 -0
  275. package/src/integrations/oauth-client.ts +1356 -459
  276. package/src/integrations/oauth-profiles.ts +537 -0
  277. package/src/integrations/oauth-return-path.ts +18 -0
  278. package/src/integrations/personal-github-repositories.ts +674 -0
  279. package/src/integrations/personal-github.ts +825 -0
  280. package/src/integrations/pr-review-provider.ts +246 -0
  281. package/src/integrations/provider-oauth.ts +250 -22
  282. package/src/integrations/slack-app-home.ts +300 -0
  283. package/src/integrations/slack-bot.ts +963 -239
  284. package/src/integrations/slack-file-upload-flow.ts +147 -0
  285. package/src/integrations/slack-install.ts +83 -0
  286. package/src/integrations/slack-interactions.ts +3170 -474
  287. package/src/integrations/slack-mrkdwn.ts +193 -0
  288. package/src/integrations/slack-routing.ts +337 -0
  289. package/src/integrations/slack-session-defaults.ts +224 -0
  290. package/src/integrations/slack-task-file-upload.ts +323 -0
  291. package/src/integrations/social-oauth.ts +213 -15
  292. package/src/interaction-agent-access.ts +48 -0
  293. package/src/interaction-frame-proxy.ts +409 -0
  294. package/src/interaction-metrics.ts +30 -0
  295. package/src/mcp/action-catalog-types.ts +9 -0
  296. package/src/mcp/action-catalog.gen.ts +6696 -0
  297. package/src/mcp/capability-account-readiness.ts +17 -0
  298. package/src/mcp/child-read-evidence.ts +19 -0
  299. package/src/mcp/command-read.ts +109 -0
  300. package/src/mcp/company-profile-agent-admin.ts +198 -0
  301. package/src/mcp/contract-input.ts +141 -0
  302. package/src/mcp/documents.ts +65 -377
  303. package/src/mcp/editable-artifacts.ts +12 -6
  304. package/src/mcp/files.ts +80 -38
  305. package/src/mcp/first-party-tool-permissions.ts +305 -0
  306. package/src/mcp/knowledge-entries.ts +322 -0
  307. package/src/mcp/orchestration-failure-diagnostic.ts +43 -0
  308. package/src/mcp/receipts.ts +34 -0
  309. package/src/mcp/request-abort.ts +44 -0
  310. package/src/mcp/scheduled-task-input.ts +76 -0
  311. package/src/mcp/scheduled-task-view.ts +44 -3
  312. package/src/mcp/server.ts +3030 -1267
  313. package/src/mcp/session-event-view.ts +383 -0
  314. package/src/mcp/session-view.ts +84 -3
  315. package/src/mcp/session-wait.ts +665 -0
  316. package/src/mcp-oauth.ts +1123 -0
  317. package/src/model-catalog.ts +91 -333
  318. package/src/observability.ts +15 -1
  319. package/src/organization-mcp.ts +397 -0
  320. package/src/organization-recovery-notifications.ts +103 -0
  321. package/src/routes/analytics-consent.ts +123 -0
  322. package/src/routes/api-integrations.ts +364 -74
  323. package/src/routes/api-keys.ts +336 -10
  324. package/src/routes/artifact-catalog.ts +279 -0
  325. package/src/routes/automations.ts +768 -0
  326. package/src/routes/billing.ts +461 -46
  327. package/src/routes/browser-sessions.ts +839 -131
  328. package/src/routes/canonical-human-identities.ts +90 -14
  329. package/src/routes/capabilities.ts +107 -6
  330. package/src/routes/channels.ts +20 -3
  331. package/src/routes/claude-subscription-accounts.ts +334 -0
  332. package/src/routes/claude-subscription-oauth.ts +142 -0
  333. package/src/routes/client-errors.ts +362 -0
  334. package/src/routes/codex.ts +833 -116
  335. package/src/routes/company-brain.ts +322 -0
  336. package/src/routes/company-profile.ts +65 -1
  337. package/src/routes/computer-sessions.ts +557 -106
  338. package/src/routes/connect.ts +1938 -0
  339. package/src/routes/connections.ts +1040 -349
  340. package/src/routes/documents.ts +488 -287
  341. package/src/routes/editable-artifacts.ts +79 -9
  342. package/src/routes/enrollments.ts +135 -39
  343. package/src/routes/environments.ts +154 -58
  344. package/src/routes/external-identity-links.ts +201 -0
  345. package/src/routes/feedback.ts +113 -0
  346. package/src/routes/files.ts +337 -39
  347. package/src/routes/github-browser-pages.ts +162 -0
  348. package/src/routes/github.ts +446 -144
  349. package/src/routes/insights-response-cache.ts +83 -0
  350. package/src/routes/insights-usage.ts +345 -0
  351. package/src/routes/insights.ts +153 -19
  352. package/src/routes/install.ts +133 -38
  353. package/src/routes/integration-facets.ts +107 -33
  354. package/src/routes/interaction-resources.ts +30 -3
  355. package/src/routes/knowledge.ts +488 -0
  356. package/src/routes/machines.ts +313 -15
  357. package/src/routes/managed-auth-session-sets.ts +1218 -0
  358. package/src/routes/managed-onboarding.ts +337 -0
  359. package/src/routes/managed-sign-in-methods.ts +433 -0
  360. package/src/routes/memory-slack-publications.ts +3 -2
  361. package/src/routes/model-connection-access.ts +208 -0
  362. package/src/routes/organization-integration-policy.ts +116 -0
  363. package/src/routes/organization-integrations.ts +326 -0
  364. package/src/routes/organization-mcp-connections.ts +178 -0
  365. package/src/routes/organization-memberships.ts +1019 -27
  366. package/src/routes/organization-model-providers.ts +293 -0
  367. package/src/routes/organization-recovery.ts +325 -0
  368. package/src/routes/organization-service-accounts.ts +128 -0
  369. package/src/routes/organization-sessions.ts +350 -0
  370. package/src/routes/personal-github-git-broker.ts +785 -0
  371. package/src/routes/personal-github.ts +370 -0
  372. package/src/routes/plugins.ts +251 -22
  373. package/src/routes/pr-review-github.ts +881 -0
  374. package/src/routes/pr-review.ts +622 -0
  375. package/src/routes/preference-registry.ts +12 -93
  376. package/src/routes/rigs.ts +167 -50
  377. package/src/routes/scheduled-tasks.ts +346 -41
  378. package/src/routes/session-artifact-associations.ts +87 -0
  379. package/src/routes/session-history-imports.ts +280 -0
  380. package/src/routes/sessions.ts +2306 -264
  381. package/src/routes/skill-content.ts +297 -0
  382. package/src/routes/skill-install-authority.ts +64 -0
  383. package/src/routes/skills.ts +62 -9
  384. package/src/routes/social.ts +46 -18
  385. package/src/routes/subscription-account-pools.ts +436 -0
  386. package/src/routes/subscription-pool-access.ts +126 -0
  387. package/src/routes/supergrok.ts +219 -303
  388. package/src/routes/transcription-recordings.ts +38 -3
  389. package/src/routes/transcriptions.ts +4 -0
  390. package/src/routes/usage-allowances.ts +378 -0
  391. package/src/routes/user-resource-authorities.ts +138 -0
  392. package/src/routes/video-generation.ts +14 -10
  393. package/src/routes/workspace-artifacts.ts +275 -69
  394. package/src/routes/workspace-capture.ts +21 -0
  395. package/src/routes/workspace-integrations.ts +576 -0
  396. package/src/routes/workspace-learning.ts +159 -0
  397. package/src/routes/workspace-model-providers.ts +148 -0
  398. package/src/routes/workspace-state.ts +38 -25
  399. package/src/routes/workspaces.ts +1004 -127
  400. package/src/sandbox/auth-callout.ts +96 -19
  401. package/src/sandbox/channel-a.ts +323 -24
  402. package/src/sandbox/connection-authority.ts +3 -0
  403. package/src/sandbox/enrollment.ts +81 -6
  404. package/src/sandbox/machines.ts +218 -76
  405. package/src/sandbox/metrics-ingestion.ts +411 -62
  406. package/src/sandbox/rematerialize.ts +121 -19
  407. package/src/sandbox/session-attach-variable-sets.ts +131 -0
  408. package/src/sandbox/viewer.ts +280 -25
  409. package/src/sandbox-file-artifacts.ts +413 -0
  410. package/src/scheduled-task-deletion.ts +138 -0
  411. package/src/session-create-observability.ts +46 -0
  412. package/src/session-realtime-context.ts +6 -3
  413. package/src/site-session-origin.ts +48 -0
  414. package/src/site-uploads.ts +224 -0
  415. package/src/slack-reaction-files.ts +16 -5
  416. package/src/temporal-schedule-cleanup.ts +13 -0
  417. package/src/temporal-schedule-sync.ts +74 -0
  418. package/src/transcription/providers/codex-subscription.ts +2 -0
  419. package/src/transcription/providers/openai.ts +1 -0
  420. package/src/transcription/providers/xai-subscription.ts +49 -3
  421. package/src/transcription/segmenter.ts +8 -2
  422. package/src/transcription/service.ts +43 -6
  423. package/src/work-discovery-observability.ts +121 -0
  424. package/src/workspace-artifact-content.ts +144 -0
  425. package/src/workspace-artifact-provenance.ts +104 -0
  426. package/src/workspace-delete-observability.ts +75 -0
  427. package/src/workspace-deletion.ts +73 -0
  428. package/src/workspace-state-projection.ts +18 -148
  429. package/src/workspace-tool-gateway-observability.ts +100 -0
  430. package/src/workspace-tool-gateway.ts +870 -0
  431. package/src/workspace-webhook-dispatch.ts +190 -0
  432. package/src/xai-subscription-auth.ts +9 -2
  433. package/dist/chunk-2PQMSRCB.js +0 -49759
  434. package/dist/chunk-2PQMSRCB.js.map +0 -1
  435. package/dist/routes/packs.d.ts +0 -3
  436. package/src/routes/packs.ts +0 -729
@@ -0,0 +1,1938 @@
1
+ import { createHash, createHmac, randomUUID } from "node:crypto";
2
+ import type { Hono } from "hono";
3
+ import { HTTPException } from "hono/http-exception";
4
+ import { getConnectBeginReplay } from "@opengeni/db/connect-attempts";
5
+ import {
6
+ AdvanceConnectRequest,
7
+ BeginConnectRequest,
8
+ ConnectOperationRequest,
9
+ ConnectProvider,
10
+ ConnectAccount,
11
+ } from "@opengeni/contracts/connect";
12
+ import {
13
+ API_INTEGRATION_OAUTH_CREDENTIAL_ROLE,
14
+ InstallApiIntegrationRequest,
15
+ stableJson,
16
+ assertOrganizationIntegrationAllowed,
17
+ OPENGENI_PERSONAL_SLACK_MCP_URL,
18
+ } from "@opengeni/contracts";
19
+ import {
20
+ CORE_INTEGRATION_DEFINITIONS,
21
+ createPinnedIntegrationTransport,
22
+ } from "@opengeni/capabilities";
23
+ import {
24
+ beginConnectAttempt as persistConnectAttempt,
25
+ getConnectAttempt,
26
+ listPendingConnectAttempts,
27
+ withWorkspaceSubjectRls,
28
+ installApiIntegration,
29
+ listConnectionsMetadata,
30
+ getConnectionMetadata,
31
+ createConnection,
32
+ updateConnection,
33
+ encryptEnvironmentValue,
34
+ normalizedCredentialHeaders,
35
+ listGitHubInstallationAccessForWorkspace,
36
+ listPrReviewAppRegistrations,
37
+ listSocialConnections,
38
+ getSocialConnection,
39
+ } from "@opengeni/db";
40
+ import {
41
+ externalActorContinuationForAuthorization,
42
+ requireAccessGrantAuthorization,
43
+ hasPermission,
44
+ requireEnvironmentEncryption,
45
+ isFikenConnection,
46
+ isOpenGeniSlackBotConnection,
47
+ prepareCapabilityEnable,
48
+ buildCapabilityCatalog,
49
+ integrationKeyForConnectProvider,
50
+ type ApiRouteDeps,
51
+ } from "@opengeni/core";
52
+ import { isPersonalConnectionOwnerPrincipal } from "../connection-ownership";
53
+ import { requireConnectOwnerAuthority } from "../integrations/connect-authority";
54
+ import { prepareFikenTokenInstall, startFikenOAuth } from "../integrations/fiken";
55
+ import { startAtlassianOAuth } from "../integrations/atlassian";
56
+ import { startSlackBotInstall } from "../integrations/slack-install";
57
+ import { startPersonalGitHubOAuth } from "../integrations/personal-github";
58
+ import { socialOAuthClientFor, startSocialOAuth } from "../integrations/social-oauth";
59
+ import {
60
+ githubAppConnectNavigation,
61
+ prepareGitHubAppConnectAction,
62
+ } from "../integrations/github-app-connect";
63
+ import { githubAppMissingSettings, prReviewGitHubAppMissingSettings } from "@opengeni/github";
64
+ import { requireGitHubLensConnect } from "../integrations/github-lens-connect";
65
+ import { isPersonalGitHubConnection } from "@opengeni/contracts/personal-github";
66
+ import { startGoogleDriveOAuth } from "../integrations/google-drive";
67
+ import {
68
+ GOOGLE_DRIVE_CREDENTIAL_ROLE,
69
+ googleDriveScopesAllowCapability,
70
+ } from "@opengeni/contracts/google-drive";
71
+ import { ATLASSIAN_NATIVE_RETIRED_MESSAGE } from "@opengeni/contracts/atlassian-native-retirement";
72
+ import {
73
+ curatedOAuthReadiness,
74
+ startApiIntegrationProviderOAuth,
75
+ } from "../integrations/provider-oauth";
76
+ import { resolveForRoute, validatedIntegrationInstallInput } from "./api-integrations";
77
+ import { executeConnectOperation } from "@opengeni/core";
78
+ import { withOrganizationIntegrationPolicyFence } from "@opengeni/db/organization-integration-policy";
79
+ import {
80
+ startMcpOAuth,
81
+ requireIntegrationsStateSecret,
82
+ gmailOAuthClientConfigured,
83
+ } from "../integrations/oauth-client";
84
+ import { OFFICIAL_GMAIL_MCP_URL } from "../integrations/oauth-profiles";
85
+ import { z } from "zod";
86
+ import {
87
+ WORKSPACE_OPENROUTER_CONNECTION_DOMAIN,
88
+ VERCEL_AI_GATEWAY_CONNECTION_DOMAIN,
89
+ } from "@opengeni/config";
90
+ import { parseRequestJson } from "../http/request-body";
91
+
92
+ /** Shared durable setup entry. Provider completion is distinct from
93
+ * subsequent preview/install; never report an OAuth token as a ready integration. */
94
+ export function registerConnectRoutes(app: Hono, deps: ApiRouteDeps): void {
95
+ const canReadAttempt = (permissions: Parameters<typeof hasPermission>[0], providerId: string) =>
96
+ ["x", "reddit", "mcp-install"].includes(providerId)
97
+ ? hasPermission(permissions, "workspace:read")
98
+ : providerId === "github-app"
99
+ ? hasPermission(permissions, "github:use") || hasPermission(permissions, "github:manage")
100
+ : providerId === "github-lens"
101
+ ? hasPermission(permissions, "workspace:read")
102
+ : hasPermission(permissions, "connections:read");
103
+ app.get("/v1/workspaces/:workspaceId/connect/catalog", async (c) => {
104
+ const authorization = await requireAccessGrantAuthorization(
105
+ c,
106
+ deps,
107
+ c.req.param("workspaceId"),
108
+ "workspace:read",
109
+ );
110
+ const external = authorization.contextIntegrity;
111
+ const policy = await withOrganizationIntegrationPolicyFence(
112
+ deps.db,
113
+ authorization.grant,
114
+ async (_tx, current) => current,
115
+ );
116
+ const canWrite = hasPermission(authorization.grant.permissions, "connections:write");
117
+ const personal = isPersonalConnectionOwnerPrincipal(authorization);
118
+ let mcpConfigured = false;
119
+ let credentialConfigured = false;
120
+ try {
121
+ requireEnvironmentEncryption(deps.settings);
122
+ credentialConfigured = deps.settings.integrationsEnabled;
123
+ requireIntegrationsStateSecret(deps.settings);
124
+ mcpConfigured = deps.settings.integrationsEnabled;
125
+ } catch {
126
+ /* Missing operator configuration is catalog readiness, not actor authority. */
127
+ }
128
+ const providers = [
129
+ ...(["x", "reddit"] as const).map((providerId) => {
130
+ let configured = mcpConfigured;
131
+ try {
132
+ socialOAuthClientFor(deps.settings, providerId);
133
+ } catch {
134
+ configured = false;
135
+ }
136
+ const ownership = [
137
+ ...(hasPermission(authorization.grant.permissions, "workspace:admin")
138
+ ? ["workspace" as const]
139
+ : []),
140
+ ...(personal ? ["personal" as const] : []),
141
+ ];
142
+ return ConnectProvider.parse({
143
+ id: providerId,
144
+ label: providerId === "x" ? "X" : "Reddit",
145
+ family: "social",
146
+ readiness:
147
+ !external || !ownership.length
148
+ ? "unsupported"
149
+ : configured
150
+ ? "available"
151
+ : "needs_configuration",
152
+ ownership,
153
+ setup: ["oauth"],
154
+ });
155
+ }),
156
+ ConnectProvider.parse({
157
+ id: "mcp-install",
158
+ label: "Install MCP capability",
159
+ family: "mcp",
160
+ readiness:
161
+ !external || !hasPermission(authorization.grant.permissions, "capabilities:manage")
162
+ ? "unsupported"
163
+ : deps.settings.integrationsEnabled
164
+ ? "available"
165
+ : "needs_configuration",
166
+ ownership: ["workspace"],
167
+ setup: ["installation"],
168
+ }),
169
+ ...CORE_INTEGRATION_DEFINITIONS.map((definition) => {
170
+ const readiness = curatedOAuthReadiness(deps.settings, definition);
171
+ return ConnectProvider.parse({
172
+ id: definition.id,
173
+ label: definition.name,
174
+ family: definition.provider.id,
175
+ readiness:
176
+ !external || !canWrite
177
+ ? "unsupported"
178
+ : readiness.configured
179
+ ? "available"
180
+ : "needs_configuration",
181
+ ...(!external
182
+ ? { reason: "A verified user is required by this Connect adapter" }
183
+ : !canWrite
184
+ ? { reason: "connections:write permission required" }
185
+ : !readiness.configured
186
+ ? { reason: "Deployment OAuth or encryption configuration is incomplete" }
187
+ : {}),
188
+ ownership: readiness.ownership.filter((owner) => owner !== "personal" || personal),
189
+ setup: ["oauth"],
190
+ });
191
+ }),
192
+ ...(["openapi", "graphql"] as const).map((protocol) =>
193
+ ConnectProvider.parse({
194
+ id: protocol,
195
+ label: protocol === "openapi" ? "OpenAPI service" : "GraphQL service",
196
+ family: protocol,
197
+ readiness:
198
+ !external || !canWrite
199
+ ? "unsupported"
200
+ : deps.settings.integrationsEnabled
201
+ ? "available"
202
+ : "needs_configuration",
203
+ ownership: personal ? ["workspace", "personal"] : ["workspace"],
204
+ setup: [protocol, "installation"],
205
+ }),
206
+ ),
207
+ ConnectProvider.parse({
208
+ id: "slack-bot",
209
+ label: "Slack workspace bot",
210
+ family: "slack",
211
+ readiness:
212
+ !external || !canWrite
213
+ ? "unsupported"
214
+ : mcpConfigured &&
215
+ deps.settings.slackClientId?.trim() &&
216
+ deps.settings.slackClientSecret?.trim() &&
217
+ deps.settings.slackSigningSecret?.trim()
218
+ ? "available"
219
+ : "needs_configuration",
220
+ ownership: ["workspace"],
221
+ setup: ["oauth"],
222
+ }),
223
+ ConnectProvider.parse({
224
+ id: "gmail",
225
+ label: "Gmail",
226
+ family: "google",
227
+ readiness:
228
+ !external || !canWrite || !personal
229
+ ? "unsupported"
230
+ : mcpConfigured && gmailOAuthClientConfigured(deps.settings)
231
+ ? "available"
232
+ : "needs_configuration",
233
+ ownership: personal ? ["personal"] : [],
234
+ setup: ["oauth"],
235
+ }),
236
+ ConnectProvider.parse({
237
+ id: "slack-personal",
238
+ label: "Slack account",
239
+ family: "slack",
240
+ readiness:
241
+ !external || !canWrite
242
+ ? "unsupported"
243
+ : mcpConfigured &&
244
+ deps.settings.slackClientId?.trim() &&
245
+ deps.settings.slackClientSecret?.trim()
246
+ ? "available"
247
+ : "needs_configuration",
248
+ ownership: personal ? ["workspace", "personal"] : ["workspace"],
249
+ setup: ["oauth"],
250
+ }),
251
+ ConnectProvider.parse({
252
+ id: "github-personal",
253
+ label: "My GitHub account",
254
+ family: "github",
255
+ readiness:
256
+ !external || !canWrite || !personal
257
+ ? "unsupported"
258
+ : mcpConfigured &&
259
+ deps.settings.githubPersonalOauthEnabled &&
260
+ deps.settings.githubPersonalOauthClientId?.trim() &&
261
+ deps.settings.githubPersonalOauthClientSecret?.trim()
262
+ ? "available"
263
+ : "needs_configuration",
264
+ ownership: personal ? ["personal"] : [],
265
+ setup: ["oauth"],
266
+ }),
267
+ ConnectProvider.parse({
268
+ id: "github-app",
269
+ label: "GitHub App installation",
270
+ family: "github",
271
+ readiness:
272
+ !external || !hasPermission(authorization.grant.permissions, "github:manage")
273
+ ? "unsupported"
274
+ : githubAppMissingSettings(deps.settings).length === 0 &&
275
+ deps.settings.githubAppSlug?.trim()
276
+ ? "available"
277
+ : "needs_configuration",
278
+ ownership: ["workspace"],
279
+ setup: ["oauth", "installation"],
280
+ }),
281
+ ConnectProvider.parse({
282
+ id: "github-lens",
283
+ label: "Opengeni Lens PR review",
284
+ family: "github-lens",
285
+ readiness:
286
+ !external ||
287
+ !hasPermission(authorization.grant.permissions, "workspace:admin") ||
288
+ !hasPermission(authorization.grant.permissions, "secrets:write")
289
+ ? "unsupported"
290
+ : credentialConfigured &&
291
+ deps.settings.sandboxBackend !== "selfhosted" &&
292
+ prReviewGitHubAppMissingSettings(deps.settings).length === 0
293
+ ? "available"
294
+ : "needs_configuration",
295
+ ownership: ["workspace"],
296
+ setup: ["oauth", "installation"],
297
+ }),
298
+ ...(["google-drive-knowledge", "google-drive-publish"] as const).map((id) =>
299
+ ConnectProvider.parse({
300
+ id,
301
+ label:
302
+ id === "google-drive-publish"
303
+ ? "Google Drive publishing"
304
+ : "Google Drive knowledge sources",
305
+ family: "google-drive",
306
+ readiness:
307
+ !external || !canWrite || !personal
308
+ ? "unsupported"
309
+ : mcpConfigured &&
310
+ deps.settings.googleDriveClientId?.trim() &&
311
+ deps.settings.googleDriveClientSecret?.trim()
312
+ ? "available"
313
+ : "needs_configuration",
314
+ ownership: personal ? ["personal"] : [],
315
+ setup: ["oauth"],
316
+ }),
317
+ ),
318
+ ConnectProvider.parse({
319
+ id: "fiken-token",
320
+ label: "Fiken",
321
+ family: "fiken",
322
+ readiness:
323
+ !external || !canWrite
324
+ ? "unsupported"
325
+ : credentialConfigured
326
+ ? "available"
327
+ : "needs_configuration",
328
+ ownership: ["workspace"],
329
+ setup: ["credentials"],
330
+ }),
331
+ ConnectProvider.parse({
332
+ id: "fiken-oauth",
333
+ label: "Fiken (OAuth)",
334
+ family: "fiken",
335
+ readiness:
336
+ !external || !canWrite
337
+ ? "unsupported"
338
+ : mcpConfigured &&
339
+ deps.settings.fikenClientId?.trim() &&
340
+ deps.settings.fikenClientSecret?.trim()
341
+ ? "available"
342
+ : "needs_configuration",
343
+ ownership: ["workspace"],
344
+ setup: ["oauth"],
345
+ }),
346
+ ConnectProvider.parse({
347
+ id: "mcp-oauth",
348
+ label: "MCP server (OAuth)",
349
+ family: "mcp",
350
+ readiness:
351
+ !external || !canWrite
352
+ ? "unsupported"
353
+ : mcpConfigured
354
+ ? "available"
355
+ : "needs_configuration",
356
+ ownership: personal ? ["workspace", "personal"] : ["workspace"],
357
+ setup: ["credentials", "oauth"],
358
+ }),
359
+ ConnectProvider.parse({
360
+ id: "mcp-headers",
361
+ label: "MCP server (custom headers)",
362
+ family: "mcp",
363
+ readiness:
364
+ !external || !canWrite
365
+ ? "unsupported"
366
+ : credentialConfigured
367
+ ? "available"
368
+ : "needs_configuration",
369
+ ownership: personal ? ["workspace", "personal"] : ["workspace"],
370
+ setup: ["credentials"],
371
+ }),
372
+ ConnectProvider.parse({
373
+ id: "mcp-bearer",
374
+ label: "MCP server (bearer credential)",
375
+ family: "mcp",
376
+ readiness:
377
+ !external || !canWrite
378
+ ? "unsupported"
379
+ : credentialConfigured
380
+ ? "available"
381
+ : "needs_configuration",
382
+ ownership: personal ? ["workspace", "personal"] : ["workspace"],
383
+ setup: ["credentials"],
384
+ }),
385
+ ];
386
+ return c.json(
387
+ providers.filter(
388
+ (provider) =>
389
+ policy.mode === "unrestricted" ||
390
+ policy.allowedIntegrationKeys.includes(
391
+ integrationKeyForConnectProvider(provider.id) ?? "",
392
+ ),
393
+ ),
394
+ );
395
+ });
396
+ app.get("/v1/workspaces/:workspaceId/connect/accounts", async (c) => {
397
+ const workspaceId = c.req.param("workspaceId");
398
+ const { grant } = await requireAccessGrantAuthorization(c, deps, workspaceId, "workspace:read");
399
+ const connections = hasPermission(grant.permissions, "connections:read")
400
+ ? await listConnectionsMetadata(deps.db, workspaceId, grant.subjectId)
401
+ : [];
402
+ const githubInstallations =
403
+ hasPermission(grant.permissions, "github:use") ||
404
+ hasPermission(grant.permissions, "github:manage")
405
+ ? await listGitHubInstallationAccessForWorkspace(deps.db, workspaceId)
406
+ : [];
407
+ const lensRegistrations = await listPrReviewAppRegistrations(
408
+ deps.db,
409
+ grant.accountId,
410
+ workspaceId,
411
+ );
412
+ const socialConnections = await listSocialConnections(
413
+ deps.db,
414
+ workspaceId,
415
+ 200,
416
+ grant.subjectId,
417
+ );
418
+ return c.json([
419
+ ...socialConnections
420
+ .filter((connection) => ["x", "reddit"].includes(connection.provider))
421
+ .map((connection) =>
422
+ ConnectAccount.parse({
423
+ id: `social:${connection.id}`,
424
+ version: connection.version,
425
+ providerId: connection.provider,
426
+ label: connection.accountHandle,
427
+ ownership: connection.ownership,
428
+ status:
429
+ connection.status === "connected"
430
+ ? "connected"
431
+ : connection.status === "disabled"
432
+ ? "disabled"
433
+ : "auth_needed",
434
+ }),
435
+ ),
436
+ ...lensRegistrations
437
+ .filter((registration) => registration.credentialKind === "managed_github_app")
438
+ .map((registration) =>
439
+ ConnectAccount.parse({
440
+ id: `lens-registration:${registration.id}`,
441
+ providerId: "github-lens",
442
+ label: registration.providerAccountLogin ?? "Lens installation",
443
+ ownership: "workspace",
444
+ status: registration.status === "active" ? "connected" : "disabled",
445
+ }),
446
+ ),
447
+ ...githubInstallations.map((installation) =>
448
+ ConnectAccount.parse({
449
+ id: `github-installation:${installation.installationId}`,
450
+ providerId: "github-app",
451
+ label: installation.accountLogin ?? `Installation ${installation.installationId}`,
452
+ ownership: "workspace",
453
+ status: "connected",
454
+ }),
455
+ ),
456
+ ...connections.flatMap((connection) => {
457
+ if (isPersonalGitHubConnection(connection))
458
+ return [
459
+ ConnectAccount.parse({
460
+ id: connection.id,
461
+ version: connection.version,
462
+ providerId: "github-personal",
463
+ label: String(connection.metadata.githubLogin ?? "GitHub"),
464
+ ownership: "personal",
465
+ status:
466
+ connection.status === "revoked"
467
+ ? "disabled"
468
+ : connection.status === "active"
469
+ ? "connected"
470
+ : "auth_needed",
471
+ }),
472
+ ];
473
+ if (isOpenGeniSlackBotConnection(connection))
474
+ return [
475
+ ConnectAccount.parse({
476
+ id: connection.id,
477
+ version: connection.version,
478
+ providerId: "slack-bot",
479
+ label: String(connection.metadata.slackTeamName ?? "Slack workspace bot"),
480
+ ownership: "workspace",
481
+ status:
482
+ connection.status === "revoked"
483
+ ? "disabled"
484
+ : connection.status === "active"
485
+ ? "connected"
486
+ : "auth_needed",
487
+ }),
488
+ ];
489
+ if (connection.metadata.credentialRole === GOOGLE_DRIVE_CREDENTIAL_ROLE)
490
+ return [
491
+ ConnectAccount.parse({
492
+ id: connection.id,
493
+ version: connection.version,
494
+ providerId: googleDriveScopesAllowCapability(connection.grantedScopes, "publish_file")
495
+ ? "google-drive-publish"
496
+ : "google-drive-knowledge",
497
+ label: String(
498
+ connection.metadata.googleEmail ??
499
+ connection.metadata.googleDisplayName ??
500
+ "Google Drive",
501
+ ),
502
+ ownership: "personal",
503
+ status:
504
+ connection.status === "revoked"
505
+ ? "disabled"
506
+ : connection.status === "active"
507
+ ? "connected"
508
+ : "auth_needed",
509
+ }),
510
+ ];
511
+ if (isFikenConnection(connection))
512
+ return [
513
+ ConnectAccount.parse({
514
+ id: connection.id,
515
+ version: connection.version,
516
+ providerId: connection.kind === "oauth2" ? "fiken-oauth" : "fiken-token",
517
+ label: "Fiken",
518
+ ownership: "workspace",
519
+ status:
520
+ connection.status === "revoked"
521
+ ? "disabled"
522
+ : connection.status === "active"
523
+ ? "connected"
524
+ : "auth_needed",
525
+ }),
526
+ ];
527
+ if (
528
+ connection.kind === "api_key" &&
529
+ ["mcp-bearer", "mcp-headers"].includes(String(connection.metadata.connectAdapter))
530
+ )
531
+ return [
532
+ ConnectAccount.parse({
533
+ id: connection.id,
534
+ version: connection.version,
535
+ providerId: connection.metadata.connectAdapter,
536
+ label: connection.providerDomain,
537
+ ownership: connection.subjectId === null ? "workspace" : "personal",
538
+ status:
539
+ connection.status === "revoked"
540
+ ? "disabled"
541
+ : connection.status === "active"
542
+ ? "connected"
543
+ : "auth_needed",
544
+ }),
545
+ ];
546
+ if (
547
+ connection.kind === "oauth2" &&
548
+ connection.metadata.oauthDiscovery &&
549
+ typeof connection.metadata.mcpUrl === "string"
550
+ )
551
+ return [
552
+ ConnectAccount.parse({
553
+ id: connection.id,
554
+ version: connection.version,
555
+ providerId:
556
+ connection.metadata.mcpUrl === OPENGENI_PERSONAL_SLACK_MCP_URL
557
+ ? "slack-personal"
558
+ : connection.metadata.mcpUrl === OFFICIAL_GMAIL_MCP_URL
559
+ ? "gmail"
560
+ : "mcp-oauth",
561
+ label: connection.providerDomain,
562
+ ownership: connection.subjectId === null ? "workspace" : "personal",
563
+ status:
564
+ connection.status === "revoked"
565
+ ? "disabled"
566
+ : connection.metadata.mcpUrl === OFFICIAL_GMAIL_MCP_URL &&
567
+ connection.subjectId === null
568
+ ? "auth_needed"
569
+ : connection.status === "active"
570
+ ? "connected"
571
+ : "auth_needed",
572
+ }),
573
+ ];
574
+ if (connection.metadata.credentialRole !== API_INTEGRATION_OAUTH_CREDENTIAL_ROLE) return [];
575
+ const ids = connection.metadata.authorizedDefinitionIds;
576
+ if (!Array.isArray(ids)) return [];
577
+ return CORE_INTEGRATION_DEFINITIONS.filter(
578
+ (definition) =>
579
+ ids.includes(definition.id) &&
580
+ definition.provider.id === connection.metadata.providerFamily,
581
+ ).map((definition) =>
582
+ ConnectAccount.parse({
583
+ id: connection.id,
584
+ version: connection.version,
585
+ providerId: definition.id,
586
+ label: String(
587
+ connection.metadata.providerDisplayName ??
588
+ connection.metadata.providerEmail ??
589
+ connection.metadata.providerPrincipalId ??
590
+ connection.id,
591
+ ).slice(0, 256),
592
+ ownership: connection.subjectId === null ? "workspace" : "personal",
593
+ status:
594
+ connection.status === "revoked"
595
+ ? "disabled"
596
+ : connection.status !== "active"
597
+ ? "auth_needed"
598
+ : "connected",
599
+ }),
600
+ );
601
+ }),
602
+ ]);
603
+ });
604
+ const transport = createPinnedIntegrationTransport({
605
+ network: deps.settings,
606
+ ...(deps.apiIntegrationSourceFetch ? { fetchImpl: deps.apiIntegrationSourceFetch } : {}),
607
+ });
608
+ app.post("/v1/workspaces/:workspaceId/connect/attempts/:attemptId/advance", async (c) => {
609
+ const input = await parseRequestJson(c, AdvanceConnectRequest);
610
+ const action = input.action;
611
+ const workspaceId = c.req.param("workspaceId");
612
+ const permission =
613
+ action.type === "install"
614
+ ? "capabilities:manage"
615
+ : action.type === "credentials"
616
+ ? "connections:write"
617
+ : "workspace:read";
618
+ // Resolve the authenticated workspace actor first, then enforce the exact
619
+ // stored provider/action permission below. Setup managers need not also
620
+ // carry the unrelated workspace metadata read permission.
621
+ const authorization = await requireAccessGrantAuthorization(c, deps, workspaceId);
622
+ const continuation = externalActorContinuationForAuthorization(authorization);
623
+ if (!authorization.contextIntegrity)
624
+ throw new HTTPException(403, { message: "Verified Connect authority required" });
625
+ const scope = {
626
+ accountId: authorization.grant.accountId,
627
+ workspaceId,
628
+ subjectId: authorization.grant.subjectId,
629
+ personalOwnerVerified: isPersonalConnectionOwnerPrincipal(authorization),
630
+ ...(continuation ? { externalContinuation: continuation } : {}),
631
+ };
632
+ // Reject invalid caller selections before taking a durable operation claim.
633
+ // Older revisions still go through the receipt-aware claim path for replay.
634
+ const stored = await getConnectAttempt(deps.db, scope, c.req.param("attemptId"));
635
+ const before = stored.attempt;
636
+ const advancePermission =
637
+ before.providerId === "mcp-install" ? "capabilities:manage" : permission;
638
+ if (!hasPermission(authorization.grant.permissions, advancePermission))
639
+ throw new HTTPException(403, { message: `${advancePermission} required` });
640
+ if (before.providerId === "mcp-install") {
641
+ if (action.type !== "credentials")
642
+ throw new HTTPException(422, { message: "Choose an MCP capability" });
643
+ const values = z
644
+ .object({ capabilityId: z.string().min(1).max(512) })
645
+ .strict()
646
+ .parse(action.values);
647
+ return c.json(
648
+ await executeConnectOperation({
649
+ db: deps.db,
650
+ scope,
651
+ attemptId: before.id,
652
+ expectedRevision: input.expectedRevision,
653
+ operationId: input.idempotencyKey,
654
+ inputDigest: createHash("sha256").update(stableJson(input)).digest("hex"),
655
+ authorize: (tx, _attempt, origin) =>
656
+ requireConnectOwnerAuthority(tx, scope, "capabilities:manage", origin),
657
+ execute: async (attempt) => {
658
+ if (attempt.state !== "credential_input")
659
+ throw new HTTPException(409, { message: "Reload MCP setup" });
660
+ const prepared = await prepareCapabilityEnable({
661
+ db: deps.db,
662
+ settings: deps.settings,
663
+ grant: authorization.grant,
664
+ accountId: scope.accountId,
665
+ workspaceId,
666
+ capabilityId: values.capabilityId,
667
+ payload: { config: {}, metadata: {}, headers: {} },
668
+ });
669
+ return {
670
+ commit: async (tx, current) => {
671
+ await prepared.commit(tx);
672
+ return {
673
+ ...current,
674
+ revision: current.revision + 1,
675
+ state: "complete",
676
+ completionRequirement: "provider_setup",
677
+ integrationInstalled: true,
678
+ nextAction: { type: "none" },
679
+ };
680
+ },
681
+ };
682
+ },
683
+ }),
684
+ );
685
+ }
686
+ if (["github-app", "github-lens"].includes(before.providerId)) {
687
+ const installationPermission =
688
+ before.providerId === "github-lens" ? "workspace:admin" : "github:manage";
689
+ if (
690
+ !hasPermission(authorization.grant.permissions, installationPermission) ||
691
+ (before.providerId === "github-lens" &&
692
+ !hasPermission(authorization.grant.permissions, "secrets:write"))
693
+ )
694
+ throw new HTTPException(403, { message: "Installation management permission required" });
695
+ return c.json(
696
+ await executeConnectOperation({
697
+ db: deps.db,
698
+ scope,
699
+ attemptId: before.id,
700
+ expectedRevision: input.expectedRevision,
701
+ operationId: input.idempotencyKey,
702
+ inputDigest: createHash("sha256").update(stableJson(input)).digest("hex"),
703
+ authorize: async (tx, _attempt, origin) => {
704
+ await requireConnectOwnerAuthority(tx, scope, installationPermission, origin);
705
+ if (before.providerId === "github-lens") {
706
+ await requireConnectOwnerAuthority(tx, scope, "secrets:write", origin);
707
+ await requireGitHubLensConnect({ ...deps, db: tx });
708
+ }
709
+ },
710
+ execute: async (attempt) =>
711
+ prepareGitHubAppConnectAction(deps, scope, c.req.url, attempt, action),
712
+ }),
713
+ );
714
+ }
715
+ if (["openapi", "graphql"].includes(before.providerId) && action.type === "credentials") {
716
+ const values = z
717
+ .object({
718
+ url: z.string().url().max(2048),
719
+ connectionId: z.string().uuid().or(z.literal("")).optional(),
720
+ })
721
+ .strict()
722
+ .parse(action.values);
723
+ const source =
724
+ before.providerId === "openapi"
725
+ ? { kind: "openapi" as const, url: values.url }
726
+ : { kind: "graphql" as const, endpoint: values.url };
727
+ const connectionId = values.connectionId || undefined;
728
+ return c.json(
729
+ await executeConnectOperation({
730
+ db: deps.db,
731
+ scope,
732
+ attemptId: before.id,
733
+ expectedRevision: input.expectedRevision,
734
+ operationId: input.idempotencyKey,
735
+ inputDigest: createHash("sha256").update(stableJson(input)).digest("hex"),
736
+ authorize: (tx, _attempt, origin) =>
737
+ requireConnectOwnerAuthority(tx, scope, "connections:write", origin),
738
+ execute: async (attempt) => {
739
+ if (attempt.state !== "credential_input")
740
+ throw new HTTPException(409, { message: "Reload setup before changing the source" });
741
+ const resolved = await resolveForRoute({
742
+ deps,
743
+ transport,
744
+ accountId: scope.accountId,
745
+ workspaceId,
746
+ subjectId: scope.subjectId,
747
+ payload: {
748
+ source,
749
+ ownership: attempt.ownership,
750
+ ...(connectionId ? { connectionId } : {}),
751
+ },
752
+ });
753
+ const account = connectionId
754
+ ? await getConnectionMetadata(deps.db, workspaceId, connectionId, scope.subjectId)
755
+ : null;
756
+ if (
757
+ connectionId &&
758
+ (!account ||
759
+ (account.subjectId === null ? "workspace" : "personal") !== attempt.ownership)
760
+ )
761
+ throw new HTTPException(404, {
762
+ message: "Connection unavailable for the requested ownership",
763
+ });
764
+ if (!account && attempt.ownership === "personal")
765
+ throw new HTTPException(422, {
766
+ message: "Personal installation requires a personal connection",
767
+ });
768
+ if (!account && resolved.preview.auth.kind !== "none")
769
+ throw new HTTPException(422, {
770
+ message: "Select a connection for this authenticated service",
771
+ });
772
+ return {
773
+ commit: async (_tx, current) => ({
774
+ ...current,
775
+ source,
776
+ revision: current.revision + 1,
777
+ state: "preview",
778
+ credentialsCommitted: !!account,
779
+ ...(account
780
+ ? {
781
+ account: {
782
+ id: account.id,
783
+ version: account.version,
784
+ providerId: attempt.providerId,
785
+ label: account.providerDomain,
786
+ ownership: attempt.ownership,
787
+ status: "connected" as const,
788
+ },
789
+ }
790
+ : {}),
791
+ nextAction: {
792
+ type: "preview",
793
+ previewId: resolved.preview.revisionId,
794
+ contentHash: resolved.preview.contentSha256,
795
+ operations: resolved.preview.tools.map((tool) => ({
796
+ id: tool.id,
797
+ label: tool.name,
798
+ kind: tool.safety,
799
+ })),
800
+ },
801
+ }),
802
+ };
803
+ },
804
+ }),
805
+ );
806
+ }
807
+ if (before.providerId === "fiken-token") {
808
+ if (action.type !== "credentials" || before.ownership !== "workspace")
809
+ throw new HTTPException(422, { message: "Fiken requires workspace-owned token setup" });
810
+ const values = z
811
+ .object({
812
+ apiToken: z.string().min(1).max(16_384),
813
+ defaultCompanySlug: z.string().max(128).optional(),
814
+ })
815
+ .strict()
816
+ .parse(action.values);
817
+ return c.json(
818
+ await executeConnectOperation({
819
+ db: deps.db,
820
+ scope,
821
+ attemptId: before.id,
822
+ expectedRevision: input.expectedRevision,
823
+ operationId: input.idempotencyKey,
824
+ inputDigest: createHmac("sha256", requireEnvironmentEncryption(deps.settings))
825
+ .update(stableJson(input))
826
+ .digest("hex"),
827
+ authorize: (tx, _attempt, origin) =>
828
+ requireConnectOwnerAuthority(tx, scope, "connections:write", origin),
829
+ execute: async (attempt) => {
830
+ if (attempt.state !== "credential_input")
831
+ throw new HTTPException(409, { message: "Reload Fiken setup" });
832
+ const persist = await prepareFikenTokenInstall(
833
+ deps,
834
+ scope,
835
+ {
836
+ apiToken: values.apiToken,
837
+ ...(values.defaultCompanySlug
838
+ ? { defaultCompanySlug: values.defaultCompanySlug }
839
+ : {}),
840
+ ...(attempt.account ? { connectionId: attempt.account.id } : {}),
841
+ },
842
+ attempt.account?.version,
843
+ );
844
+ return {
845
+ commit: async (tx, current) => {
846
+ const connection = await persist(tx);
847
+ return {
848
+ ...current,
849
+ revision: current.revision + 1,
850
+ state: "complete",
851
+ credentialsCommitted: true,
852
+ nextAction: { type: "none" },
853
+ account: {
854
+ id: connection.id,
855
+ version: connection.version,
856
+ providerId: "fiken-token",
857
+ label: "Fiken",
858
+ ownership: "workspace",
859
+ status: "connected",
860
+ },
861
+ };
862
+ },
863
+ };
864
+ },
865
+ }),
866
+ );
867
+ }
868
+ if (["mcp-bearer", "mcp-headers"].includes(before.providerId)) {
869
+ if (action.type !== "credentials")
870
+ throw new HTTPException(422, { message: "MCP credentials required" });
871
+ const values =
872
+ before.providerId === "mcp-headers"
873
+ ? z
874
+ .object({
875
+ mcpUrl: z.string().url().max(4096),
876
+ headers: z.string().min(2).max(65_536),
877
+ })
878
+ .strict()
879
+ .parse(action.values)
880
+ : z
881
+ .object({
882
+ mcpUrl: z.string().url().max(4096),
883
+ token: z
884
+ .string()
885
+ .min(1)
886
+ .max(16_384)
887
+ .regex(/^[^\u0000-\u0020\u007f]+$/),
888
+ })
889
+ .strict()
890
+ .parse(action.values);
891
+ let credentialHeaders: Record<string, string>;
892
+ try {
893
+ credentialHeaders =
894
+ "headers" in values
895
+ ? normalizedCredentialHeaders(
896
+ z.record(z.string(), z.string()).parse(JSON.parse(values.headers)),
897
+ )
898
+ : { authorization: `Bearer ${values.token}` };
899
+ } catch {
900
+ throw new HTTPException(422, {
901
+ message: "Supply a JSON object of valid credential headers",
902
+ });
903
+ }
904
+ const destination = new URL(values.mcpUrl);
905
+ if (
906
+ [WORKSPACE_OPENROUTER_CONNECTION_DOMAIN, VERCEL_AI_GATEWAY_CONNECTION_DOMAIN].some(
907
+ (domain) => domain === destination.hostname,
908
+ )
909
+ )
910
+ throw new HTTPException(422, {
911
+ message: "Use the dedicated workspace provider credential flow",
912
+ });
913
+ if (
914
+ destination.protocol !== "https:" ||
915
+ destination.username ||
916
+ destination.password ||
917
+ destination.hash
918
+ )
919
+ throw new HTTPException(422, {
920
+ message: "MCP credential destination requires HTTPS without userinfo or fragment",
921
+ });
922
+ const encryptionKey = requireEnvironmentEncryption(deps.settings);
923
+ const mcpUrl = destination.toString();
924
+ return c.json(
925
+ await executeConnectOperation({
926
+ db: deps.db,
927
+ scope,
928
+ attemptId: before.id,
929
+ expectedRevision: input.expectedRevision,
930
+ operationId: input.idempotencyKey,
931
+ inputDigest: createHmac("sha256", encryptionKey).update(stableJson(input)).digest("hex"),
932
+ authorize: (tx, _attempt, origin) =>
933
+ requireConnectOwnerAuthority(tx, scope, "connections:write", origin),
934
+ execute: async (attempt) => {
935
+ if (attempt.state !== "credential_input")
936
+ throw new HTTPException(409, { message: "Reload the current credential setup" });
937
+ const credentialEncrypted = encryptEnvironmentValue(
938
+ encryptionKey,
939
+ JSON.stringify({ headers: credentialHeaders }),
940
+ );
941
+ return {
942
+ commit: async (tx, current) => {
943
+ const existing = current.account
944
+ ? await getConnectionMetadata(
945
+ tx,
946
+ workspaceId,
947
+ current.account.id,
948
+ scope.subjectId,
949
+ )
950
+ : null;
951
+ if (
952
+ current.account &&
953
+ (!existing ||
954
+ existing.metadata.connectAdapter !== current.providerId ||
955
+ existing.metadata.mcpUrl !== mcpUrl ||
956
+ existing.version !== current.account.version ||
957
+ (existing.subjectId === null ? "workspace" : "personal") !== current.ownership)
958
+ )
959
+ throw new HTTPException(409, {
960
+ message: "MCP account changed; reload before replacing credentials",
961
+ });
962
+ const metadata = { connectAdapter: current.providerId, mcpUrl, resource: mcpUrl };
963
+ const subjectId = current.ownership === "personal" ? scope.subjectId : null;
964
+ const connection = existing
965
+ ? await updateConnection(tx, {
966
+ workspaceId,
967
+ connectionId: existing.id,
968
+ expectedVersion: existing.version,
969
+ visibleToSubjectId: scope.subjectId,
970
+ subjectId,
971
+ providerDomain: destination.hostname,
972
+ kind: "api_key",
973
+ status: "active",
974
+ credentialEncrypted,
975
+ grantedScopes: [],
976
+ expiresAt: null,
977
+ metadata,
978
+ updatedBySubjectId: scope.subjectId,
979
+ })
980
+ : await createConnection(tx, {
981
+ accountId: scope.accountId,
982
+ workspaceId,
983
+ subjectId,
984
+ providerDomain: destination.hostname,
985
+ kind: "api_key",
986
+ credentialEncrypted,
987
+ grantedScopes: [],
988
+ expiresAt: null,
989
+ metadata,
990
+ createdBySubjectId: scope.subjectId,
991
+ });
992
+ if (!connection) throw new HTTPException(409, { message: "MCP account changed" });
993
+ return {
994
+ ...current,
995
+ revision: current.revision + 1,
996
+ state: "complete",
997
+ credentialsCommitted: true,
998
+ nextAction: { type: "none" },
999
+ account: {
1000
+ id: connection.id,
1001
+ version: connection.version,
1002
+ providerId: current.providerId,
1003
+ label: connection.providerDomain,
1004
+ ownership: current.ownership,
1005
+ status: "connected",
1006
+ },
1007
+ };
1008
+ },
1009
+ };
1010
+ },
1011
+ }),
1012
+ );
1013
+ }
1014
+ if (["mcp-oauth", "slack-personal", "gmail"].includes(before.providerId)) {
1015
+ if (action.type !== "credentials")
1016
+ throw new HTTPException(422, { message: "MCP OAuth requires a server URL" });
1017
+ const values =
1018
+ before.providerId === "slack-personal"
1019
+ ? { mcpUrl: OPENGENI_PERSONAL_SLACK_MCP_URL }
1020
+ : before.providerId === "gmail"
1021
+ ? { mcpUrl: OFFICIAL_GMAIL_MCP_URL }
1022
+ : z
1023
+ .object({ mcpUrl: z.string().url().max(4096) })
1024
+ .strict()
1025
+ .parse(action.values);
1026
+ return c.json(
1027
+ await executeConnectOperation({
1028
+ db: deps.db,
1029
+ scope,
1030
+ attemptId: before.id,
1031
+ expectedRevision: input.expectedRevision,
1032
+ operationId: input.idempotencyKey,
1033
+ inputDigest: createHmac("sha256", requireIntegrationsStateSecret(deps.settings))
1034
+ .update(stableJson(input))
1035
+ .digest("hex"),
1036
+ authorize: (tx, _attempt, origin) =>
1037
+ requireConnectOwnerAuthority(tx, scope, "connections:write", origin),
1038
+ execute: async (attempt) => {
1039
+ if (attempt.state !== "credential_input")
1040
+ throw new HTTPException(409, { message: "Reload the current connection setup" });
1041
+ const started = await startMcpOAuth(deps, {
1042
+ ...scope,
1043
+ ...(before.providerId === "gmail" || before.providerId === "slack-personal"
1044
+ ? { integrationKey: before.providerId }
1045
+ : {}),
1046
+ ...(continuation ? { externalContinuation: continuation } : {}),
1047
+ connectAttemptId: attempt.id,
1048
+ personalOwnershipAllowed: isPersonalConnectionOwnerPrincipal(authorization),
1049
+ requestUrl: c.req.url,
1050
+ payload: {
1051
+ requestedScopes: [],
1052
+ mcpUrl: values.mcpUrl,
1053
+ ownership: attempt.ownership,
1054
+ returnUrl: stored.returnUrl,
1055
+ ...(attempt.account ? { connectionId: attempt.account.id } : {}),
1056
+ },
1057
+ });
1058
+ const authorizationUrl = started.authorizationUrl;
1059
+ if (!authorizationUrl)
1060
+ throw new HTTPException(502, {
1061
+ message: "provider did not return an authorization URL",
1062
+ });
1063
+ return {
1064
+ commit: async (_tx, current) => ({
1065
+ ...current,
1066
+ revision: current.revision + 1,
1067
+ state: "requires_user_action",
1068
+ nextAction: { type: "authorize", url: authorizationUrl },
1069
+ }),
1070
+ };
1071
+ },
1072
+ }),
1073
+ );
1074
+ }
1075
+ if (action.type !== "retry" && action.type !== "install")
1076
+ throw new HTTPException(422, {
1077
+ message: "this provider action is not supported by curated OAuth",
1078
+ });
1079
+ if (before.revision === input.expectedRevision) {
1080
+ if (
1081
+ (!before.source && (!before.credentialsCommitted || !before.account)) ||
1082
+ !["connected_but_incomplete", "preview"].includes(before.state)
1083
+ )
1084
+ throw new HTTPException(409, { message: "connection is not ready for preview or install" });
1085
+ if (action.type === "install") {
1086
+ const preview = before.nextAction;
1087
+ if (
1088
+ preview.type !== "preview" ||
1089
+ preview.previewId !== action.previewId ||
1090
+ preview.contentHash !== action.contentHash ||
1091
+ action.operationIds.some(
1092
+ (id) => !preview.operations.some((operation) => operation.id === id),
1093
+ )
1094
+ )
1095
+ throw new HTTPException(409, {
1096
+ message: "review the current preview before installation",
1097
+ });
1098
+ }
1099
+ }
1100
+ return c.json(
1101
+ await executeConnectOperation({
1102
+ db: deps.db,
1103
+ scope,
1104
+ attemptId: c.req.param("attemptId"),
1105
+ expectedRevision: input.expectedRevision,
1106
+ operationId: input.idempotencyKey,
1107
+ inputDigest: createHash("sha256").update(stableJson(input)).digest("hex"),
1108
+ authorize: (tx, _attempt, origin) =>
1109
+ requireConnectOwnerAuthority(tx, scope, permission, origin),
1110
+ execute: async (attempt) => {
1111
+ if (
1112
+ (!attempt.source && (!attempt.credentialsCommitted || !attempt.account)) ||
1113
+ !["connected_but_incomplete", "preview"].includes(attempt.state)
1114
+ )
1115
+ throw new HTTPException(409, {
1116
+ message: "connection is not ready for preview or install",
1117
+ });
1118
+ if (
1119
+ action.type === "install" &&
1120
+ (attempt.nextAction.type !== "preview" ||
1121
+ attempt.nextAction.previewId !== action.previewId ||
1122
+ attempt.nextAction.contentHash !== action.contentHash)
1123
+ )
1124
+ throw new HTTPException(409, {
1125
+ message: "review the current preview before installation",
1126
+ });
1127
+ const previewRequest = {
1128
+ source: attempt.source ?? {
1129
+ kind: "definition" as const,
1130
+ definitionId: attempt.providerId,
1131
+ },
1132
+ ...(attempt.account ? { connectionId: attempt.account.id } : {}),
1133
+ ownership: attempt.ownership,
1134
+ };
1135
+ const resolved = await resolveForRoute({
1136
+ deps,
1137
+ transport,
1138
+ accountId: scope.accountId,
1139
+ workspaceId,
1140
+ subjectId: scope.subjectId,
1141
+ payload: previewRequest,
1142
+ });
1143
+ const preparedPreview = (changed = false) => ({
1144
+ commit: async () => ({
1145
+ ...attempt,
1146
+ error: changed
1147
+ ? {
1148
+ code: "source_changed",
1149
+ message: "The source changed; review the new operations before installing.",
1150
+ retryable: true,
1151
+ }
1152
+ : undefined,
1153
+ revision: attempt.revision + 1,
1154
+ state: "preview" as const,
1155
+ nextAction: {
1156
+ type: "preview" as const,
1157
+ previewId: resolved.preview.revisionId,
1158
+ contentHash: resolved.preview.contentSha256,
1159
+ operations: resolved.preview.tools.map((tool) => ({
1160
+ id: tool.id,
1161
+ label: tool.name,
1162
+ kind: tool.safety,
1163
+ })),
1164
+ },
1165
+ }),
1166
+ });
1167
+ if (action.type === "retry") return preparedPreview();
1168
+ // Always pass the explicit selection; omission must never select all tools.
1169
+ const payload = InstallApiIntegrationRequest.parse({
1170
+ ...previewRequest,
1171
+ expectedRevisionId: action.previewId,
1172
+ expectedContentSha256: action.contentHash,
1173
+ allowedTools: action.operationIds,
1174
+ ...attempt.installationTarget,
1175
+ });
1176
+ let installation: ReturnType<typeof validatedIntegrationInstallInput>;
1177
+ try {
1178
+ installation = validatedIntegrationInstallInput(
1179
+ authorization.grant,
1180
+ workspaceId,
1181
+ payload,
1182
+ resolved,
1183
+ );
1184
+ } catch (error) {
1185
+ // The shared validator's 409 means the reviewed source changed.
1186
+ // No installation write has run; persist a fresh, unselected preview.
1187
+ if (error instanceof HTTPException && error.status === 409)
1188
+ return preparedPreview(true);
1189
+ throw error;
1190
+ }
1191
+ return {
1192
+ commit: async (tx) => {
1193
+ await installApiIntegration(tx, installation);
1194
+ return {
1195
+ ...attempt,
1196
+ error: undefined,
1197
+ revision: attempt.revision + 1,
1198
+ state: "complete" as const,
1199
+ integrationInstalled: true,
1200
+ nextAction: { type: "none" as const },
1201
+ };
1202
+ },
1203
+ };
1204
+ },
1205
+ }),
1206
+ );
1207
+ });
1208
+ app.post("/v1/workspaces/:workspaceId/connect/attempts/:attemptId/cancel", async (c) => {
1209
+ const input = await parseRequestJson(c, ConnectOperationRequest);
1210
+ const workspaceId = c.req.param("workspaceId");
1211
+ const authorization = await requireAccessGrantAuthorization(c, deps, workspaceId);
1212
+ const continuation = externalActorContinuationForAuthorization(authorization);
1213
+ if (!authorization.contextIntegrity)
1214
+ throw new HTTPException(403, { message: "Verified Connect authority required" });
1215
+ const scope = {
1216
+ accountId: authorization.grant.accountId,
1217
+ workspaceId,
1218
+ subjectId: authorization.grant.subjectId,
1219
+ personalOwnerVerified: isPersonalConnectionOwnerPrincipal(authorization),
1220
+ ...(continuation ? { externalContinuation: continuation } : {}),
1221
+ };
1222
+ return c.json(
1223
+ await executeConnectOperation({
1224
+ db: deps.db,
1225
+ scope,
1226
+ attemptId: c.req.param("attemptId"),
1227
+ expectedRevision: input.expectedRevision,
1228
+ operationId: input.idempotencyKey,
1229
+ inputDigest: createHash("sha256")
1230
+ .update(stableJson({ ...input, action: "cancel" }))
1231
+ .digest("hex"),
1232
+ purpose: "cancellation",
1233
+ authorize: async (tx, attempt, origin) => {
1234
+ const permission =
1235
+ attempt.providerId === "mcp-install"
1236
+ ? "capabilities:manage"
1237
+ : ["x", "reddit"].includes(attempt.providerId)
1238
+ ? attempt.ownership === "workspace"
1239
+ ? "workspace:admin"
1240
+ : "workspace:read"
1241
+ : attempt.providerId === "github-app"
1242
+ ? "github:manage"
1243
+ : attempt.providerId === "github-lens"
1244
+ ? "workspace:admin"
1245
+ : "connections:write";
1246
+ if (
1247
+ !hasPermission(authorization.grant.permissions, permission) ||
1248
+ (attempt.providerId === "github-lens" &&
1249
+ !hasPermission(authorization.grant.permissions, "secrets:write"))
1250
+ )
1251
+ throw new HTTPException(403, { message: "Connect management permission required" });
1252
+ await requireConnectOwnerAuthority(tx, scope, permission, origin);
1253
+ if (attempt.providerId === "github-lens")
1254
+ await requireConnectOwnerAuthority(tx, scope, "secrets:write", origin);
1255
+ },
1256
+ execute: async () => ({
1257
+ commit: async (_tx, current) => ({
1258
+ ...current,
1259
+ revision: current.revision + 1,
1260
+ state: "cancelled",
1261
+ nextAction: { type: "none" },
1262
+ }),
1263
+ }),
1264
+ }),
1265
+ );
1266
+ });
1267
+ app.post("/v1/workspaces/:workspaceId/connect/attempts", async (c) => {
1268
+ if (!deps.settings.integrationsEnabled)
1269
+ throw new HTTPException(403, { message: "integrations disabled" });
1270
+ const workspaceId = c.req.param("workspaceId");
1271
+ const input = await parseRequestJson(c, BeginConnectRequest);
1272
+ const setupPermission =
1273
+ input.providerId === "mcp-install"
1274
+ ? "capabilities:manage"
1275
+ : ["x", "reddit"].includes(input.providerId)
1276
+ ? input.ownership === "workspace"
1277
+ ? "workspace:admin"
1278
+ : "workspace:read"
1279
+ : input.providerId === "github-app"
1280
+ ? "github:manage"
1281
+ : input.providerId === "github-lens"
1282
+ ? "workspace:admin"
1283
+ : "connections:write";
1284
+ const authorization = await requireAccessGrantAuthorization(
1285
+ c,
1286
+ deps,
1287
+ workspaceId,
1288
+ setupPermission,
1289
+ );
1290
+ const continuation = externalActorContinuationForAuthorization(authorization);
1291
+ if (!authorization.contextIntegrity)
1292
+ throw new HTTPException(403, {
1293
+ message: "Verified Connect authority required",
1294
+ });
1295
+ if (input.providerId === "atlassian")
1296
+ throw new HTTPException(410, { message: ATLASSIAN_NATIVE_RETIRED_MESSAGE });
1297
+ if (input.providerId === "gmail" && input.ownership !== "personal")
1298
+ throw new HTTPException(422, { message: "Gmail connections must be personal-owned" });
1299
+ let target: URL;
1300
+ try {
1301
+ target = new URL(input.returnUrl);
1302
+ } catch {
1303
+ throw new HTTPException(400, { message: "invalid return URL" });
1304
+ }
1305
+ if (
1306
+ !["https:", "http:"].includes(target.protocol) ||
1307
+ target.username ||
1308
+ target.password ||
1309
+ /[\u0000-\u0020\u007f]/.test(input.returnUrl)
1310
+ )
1311
+ throw new HTTPException(400, { message: "invalid return URL" });
1312
+ const scope = {
1313
+ accountId: authorization.grant.accountId,
1314
+ workspaceId,
1315
+ subjectId: authorization.grant.subjectId,
1316
+ personalOwnerVerified: isPersonalConnectionOwnerPrincipal(authorization),
1317
+ ...(continuation ? { externalContinuation: continuation } : {}),
1318
+ };
1319
+ return c.json(
1320
+ await (async () => {
1321
+ // Remote setup preparation stays outside the policy and actor transactions.
1322
+ // Repeat both admissions when persisting the resulting attempt.
1323
+ const tx = deps.db;
1324
+ const admit = () =>
1325
+ withOrganizationIntegrationPolicyFence(deps.db, scope, async (policyTx, policy) => {
1326
+ return withWorkspaceSubjectRls(
1327
+ policyTx,
1328
+ workspaceId,
1329
+ scope.subjectId,
1330
+ async (actorTx) => {
1331
+ await requireConnectOwnerAuthority(actorTx, scope, setupPermission);
1332
+ const replay = await getConnectBeginReplay(actorTx, scope, {
1333
+ idempotencyKey: input.idempotencyKey,
1334
+ requestDigest: createHash("sha256").update(stableJson(input)).digest("hex"),
1335
+ returnUrl: input.returnUrl,
1336
+ ...(continuation ? { externalContinuation: continuation } : {}),
1337
+ });
1338
+ if (replay) return replay;
1339
+ assertOrganizationIntegrationAllowed(
1340
+ policy,
1341
+ integrationKeyForConnectProvider(input.providerId),
1342
+ );
1343
+ return null;
1344
+ },
1345
+ );
1346
+ });
1347
+ const replay = await admit();
1348
+ if (replay) return replay;
1349
+ const beginConnectAttempt = async (
1350
+ _db: Parameters<typeof persistConnectAttempt>[0],
1351
+ _scope: Parameters<typeof persistConnectAttempt>[1],
1352
+ attemptInput: Parameters<typeof persistConnectAttempt>[2],
1353
+ ) =>
1354
+ withOrganizationIntegrationPolicyFence(deps.db, scope, async (policyTx, policy) => {
1355
+ return withWorkspaceSubjectRls(
1356
+ policyTx,
1357
+ workspaceId,
1358
+ scope.subjectId,
1359
+ async (actorTx) => {
1360
+ await requireConnectOwnerAuthority(actorTx, scope, setupPermission);
1361
+ return persistConnectAttempt(actorTx, scope, {
1362
+ ...attemptInput,
1363
+ authorizeAcquisition: async () => {
1364
+ assertOrganizationIntegrationAllowed(
1365
+ policy,
1366
+ integrationKeyForConnectProvider(input.providerId),
1367
+ );
1368
+ },
1369
+ });
1370
+ },
1371
+ );
1372
+ });
1373
+ const id = randomUUID();
1374
+ if (input.providerId === "mcp-install") {
1375
+ if (
1376
+ input.ownership !== "workspace" ||
1377
+ input.reconnectAccountId ||
1378
+ input.installationTarget
1379
+ )
1380
+ throw new HTTPException(422, {
1381
+ message: "MCP capabilities are workspace installations, not credential accounts",
1382
+ });
1383
+ const catalog = await buildCapabilityCatalog({
1384
+ db: tx,
1385
+ workspaceId,
1386
+ settings: deps.settings,
1387
+ subjectId: scope.subjectId,
1388
+ });
1389
+ const options = catalog.items
1390
+ .filter(
1391
+ (item) =>
1392
+ item.kind === "mcp" &&
1393
+ item.surfaceType !== "codex_apps" &&
1394
+ item.runtime.available &&
1395
+ item.authKind === "none",
1396
+ )
1397
+ .map((item) => ({ value: item.id, label: item.name }));
1398
+ if (options.length > 1000)
1399
+ throw new HTTPException(422, {
1400
+ message: "MCP catalog exceeds the setup chooser limit",
1401
+ });
1402
+ return beginConnectAttempt(tx, scope, {
1403
+ ...(continuation ? { externalContinuation: continuation } : {}),
1404
+ idempotencyKey: input.idempotencyKey,
1405
+ requestDigest: createHash("sha256").update(stableJson(input)).digest("hex"),
1406
+ returnUrl: input.returnUrl,
1407
+ attempt: {
1408
+ id,
1409
+ workspaceId,
1410
+ providerId: input.providerId,
1411
+ ownership: "workspace",
1412
+ revision: 1,
1413
+ state: "credential_input",
1414
+ credentialsCommitted: false,
1415
+ integrationInstalled: false,
1416
+ completionRequirement: "provider_setup",
1417
+ nextAction: {
1418
+ type: "credentials",
1419
+ fields: [
1420
+ {
1421
+ name: "capabilityId",
1422
+ label: "MCP capability",
1423
+ required: true,
1424
+ secret: false,
1425
+ options,
1426
+ },
1427
+ ],
1428
+ },
1429
+ expiresAt: new Date(Date.now() + 30 * 60_000).toISOString(),
1430
+ },
1431
+ });
1432
+ }
1433
+ if (input.providerId === "x" || input.providerId === "reddit") {
1434
+ if (input.installationTarget)
1435
+ throw new HTTPException(422, {
1436
+ message: "Social setup does not create an API installation",
1437
+ });
1438
+ if (input.ownership === "personal" && !scope.personalOwnerVerified)
1439
+ throw new HTTPException(403, { message: "Personal ownership unavailable" });
1440
+ const reconnectId = input.reconnectAccountId?.startsWith("social:")
1441
+ ? input.reconnectAccountId.slice("social:".length)
1442
+ : null;
1443
+ const existing =
1444
+ reconnectId && z.string().uuid().safeParse(reconnectId).success
1445
+ ? await getSocialConnection(tx, workspaceId, reconnectId, scope.subjectId)
1446
+ : null;
1447
+ if (
1448
+ input.reconnectAccountId &&
1449
+ (!existing ||
1450
+ existing.provider !== input.providerId ||
1451
+ existing.ownership !== input.ownership ||
1452
+ !existing.version ||
1453
+ !existing.externalAccountId)
1454
+ )
1455
+ throw new HTTPException(409, {
1456
+ message: "Social account changed or cannot be safely reconnected",
1457
+ });
1458
+ const started = await startSocialOAuth(
1459
+ { db: tx, settings: deps.settings, observability: deps.observability },
1460
+ {
1461
+ accountId: scope.accountId,
1462
+ workspaceId,
1463
+ subjectId: scope.subjectId,
1464
+ personalOwnershipAllowed: scope.personalOwnerVerified,
1465
+ connectAttemptId: id,
1466
+ requestUrl: c.req.url,
1467
+ payload: { provider: input.providerId, ownership: input.ownership },
1468
+ },
1469
+ );
1470
+ if (!started.authorizationUrl)
1471
+ throw new HTTPException(503, { message: "Social authorization is unavailable" });
1472
+ return beginConnectAttempt(tx, scope, {
1473
+ ...(continuation ? { externalContinuation: continuation } : {}),
1474
+ idempotencyKey: input.idempotencyKey,
1475
+ requestDigest: createHash("sha256").update(stableJson(input)).digest("hex"),
1476
+ returnUrl: input.returnUrl,
1477
+ attempt: {
1478
+ id,
1479
+ workspaceId,
1480
+ providerId: input.providerId,
1481
+ ownership: input.ownership,
1482
+ revision: 1,
1483
+ state: "requires_user_action",
1484
+ credentialsCommitted: false,
1485
+ integrationInstalled: false,
1486
+ completionRequirement: "connection",
1487
+ ...(existing
1488
+ ? {
1489
+ account: {
1490
+ id: `social:${existing.id}`,
1491
+ version: existing.version!,
1492
+ providerId: input.providerId,
1493
+ label: existing.accountHandle,
1494
+ ownership: input.ownership,
1495
+ status: "auth_needed" as const,
1496
+ },
1497
+ }
1498
+ : {}),
1499
+ nextAction: { type: "authorize", url: started.authorizationUrl },
1500
+ expiresAt: started.expiresAt,
1501
+ },
1502
+ });
1503
+ }
1504
+ if (["github-app", "github-lens"].includes(input.providerId)) {
1505
+ if (
1506
+ input.ownership !== "workspace" ||
1507
+ input.installationTarget ||
1508
+ input.reconnectAccountId
1509
+ )
1510
+ throw new HTTPException(422, {
1511
+ message:
1512
+ "GitHub App setup requires workspace ownership and explicit installation selection",
1513
+ });
1514
+ if (input.providerId === "github-lens") {
1515
+ if (!hasPermission(authorization.grant.permissions, "secrets:write"))
1516
+ throw new HTTPException(403, { message: "secrets:write required" });
1517
+ await requireConnectOwnerAuthority(tx, scope, "secrets:write");
1518
+ await requireGitHubLensConnect({ ...deps, db: tx });
1519
+ }
1520
+ const navigation = githubAppConnectNavigation(
1521
+ deps,
1522
+ scope,
1523
+ id,
1524
+ c.req.url,
1525
+ "discover",
1526
+ undefined,
1527
+ input.providerId === "github-lens" ? "github-lens" : "github-app",
1528
+ );
1529
+ return beginConnectAttempt(tx, scope, {
1530
+ ...(continuation ? { externalContinuation: continuation } : {}),
1531
+ idempotencyKey: input.idempotencyKey,
1532
+ requestDigest: createHash("sha256").update(stableJson(input)).digest("hex"),
1533
+ returnUrl: input.returnUrl,
1534
+ attempt: {
1535
+ id,
1536
+ workspaceId,
1537
+ providerId: input.providerId,
1538
+ ownership: "workspace",
1539
+ revision: 1,
1540
+ state: "requires_user_action",
1541
+ credentialsCommitted: false,
1542
+ integrationInstalled: false,
1543
+ completionRequirement: "provider_setup",
1544
+ nextAction: { type: "authorize", url: navigation.authorizationUrl },
1545
+ expiresAt: navigation.expiresAt,
1546
+ },
1547
+ });
1548
+ }
1549
+ if (["openapi", "graphql"].includes(input.providerId)) {
1550
+ if (input.reconnectAccountId)
1551
+ throw new HTTPException(422, { message: "Select the service connection during setup" });
1552
+ if (input.ownership === "personal" && !isPersonalConnectionOwnerPrincipal(authorization))
1553
+ throw new HTTPException(403, { message: "personal ownership unavailable" });
1554
+ const choices = hasPermission(authorization.grant.permissions, "connections:read")
1555
+ ? (await listConnectionsMetadata(tx, workspaceId, scope.subjectId))
1556
+ .filter(
1557
+ (connection) =>
1558
+ connection.status === "active" &&
1559
+ (input.ownership === "workspace"
1560
+ ? connection.subjectId === null
1561
+ : connection.subjectId === scope.subjectId),
1562
+ )
1563
+ .map((connection) => ({
1564
+ value: connection.id,
1565
+ label: `${connection.providerDomain} · ${connection.id}`,
1566
+ }))
1567
+ : [];
1568
+ if (choices.length > 1000)
1569
+ throw new HTTPException(422, {
1570
+ message: "Account inventory exceeds the setup chooser limit",
1571
+ });
1572
+ return beginConnectAttempt(tx, scope, {
1573
+ ...(continuation ? { externalContinuation: continuation } : {}),
1574
+ idempotencyKey: input.idempotencyKey,
1575
+ requestDigest: createHash("sha256").update(stableJson(input)).digest("hex"),
1576
+ returnUrl: input.returnUrl,
1577
+ attempt: {
1578
+ id,
1579
+ workspaceId,
1580
+ providerId: input.providerId,
1581
+ ownership: input.ownership,
1582
+ revision: 1,
1583
+ state: "credential_input",
1584
+ credentialsCommitted: false,
1585
+ integrationInstalled: false,
1586
+ completionRequirement: "integration",
1587
+ installationTarget: input.installationTarget ?? {
1588
+ instanceKey: `account-${id}`,
1589
+ displayName: input.providerId === "openapi" ? "OpenAPI service" : "GraphQL service",
1590
+ },
1591
+ nextAction: {
1592
+ type: "credentials",
1593
+ fields: [
1594
+ {
1595
+ name: "url",
1596
+ label:
1597
+ input.providerId === "openapi"
1598
+ ? "OpenAPI document URL"
1599
+ : "GraphQL endpoint URL",
1600
+ required: true,
1601
+ secret: false,
1602
+ },
1603
+ {
1604
+ name: "connectionId",
1605
+ label: "Service account (optional for public services)",
1606
+ required: false,
1607
+ secret: false,
1608
+ options: choices,
1609
+ },
1610
+ ],
1611
+ },
1612
+ expiresAt: new Date(Date.now() + 15 * 60_000).toISOString(),
1613
+ },
1614
+ });
1615
+ }
1616
+ if (
1617
+ input.installationTarget &&
1618
+ [
1619
+ "mcp-oauth",
1620
+ "slack-personal",
1621
+ "gmail",
1622
+ "mcp-bearer",
1623
+ "mcp-headers",
1624
+ "fiken-token",
1625
+ "fiken-oauth",
1626
+ "atlassian",
1627
+ "google-drive-knowledge",
1628
+ "google-drive-publish",
1629
+ "slack-bot",
1630
+ "github-personal",
1631
+ ].includes(input.providerId)
1632
+ )
1633
+ throw new HTTPException(422, {
1634
+ message: "MCP credential setup does not install an API integration instance",
1635
+ });
1636
+ if (
1637
+ [
1638
+ "mcp-oauth",
1639
+ "slack-personal",
1640
+ "gmail",
1641
+ "mcp-bearer",
1642
+ "mcp-headers",
1643
+ "fiken-token",
1644
+ ].includes(input.providerId)
1645
+ ) {
1646
+ if (input.providerId === "fiken-token" && input.ownership !== "workspace")
1647
+ throw new HTTPException(422, { message: "Fiken is workspace-owned" });
1648
+ requireEnvironmentEncryption(deps.settings);
1649
+ if (["mcp-oauth", "slack-personal", "gmail"].includes(input.providerId))
1650
+ requireIntegrationsStateSecret(deps.settings);
1651
+ const existing = input.reconnectAccountId
1652
+ ? await getConnectionMetadata(
1653
+ tx,
1654
+ workspaceId,
1655
+ input.reconnectAccountId,
1656
+ scope.subjectId,
1657
+ )
1658
+ : null;
1659
+ if (
1660
+ input.reconnectAccountId &&
1661
+ (!existing ||
1662
+ (input.providerId === "fiken-token"
1663
+ ? !isFikenConnection(existing)
1664
+ : ["mcp-oauth", "slack-personal", "gmail"].includes(input.providerId)
1665
+ ? existing.kind !== "oauth2" ||
1666
+ !existing.metadata.oauthDiscovery ||
1667
+ (input.providerId === "slack-personal" &&
1668
+ existing.metadata.mcpUrl !== OPENGENI_PERSONAL_SLACK_MCP_URL) ||
1669
+ (input.providerId === "gmail" &&
1670
+ existing.metadata.mcpUrl !== OFFICIAL_GMAIL_MCP_URL)
1671
+ : existing.kind !== "api_key" ||
1672
+ existing.metadata.connectAdapter !== input.providerId) ||
1673
+ (existing.subjectId === null ? "workspace" : "personal") !== input.ownership)
1674
+ )
1675
+ throw new HTTPException(404, { message: "MCP account not found for this ownership" });
1676
+ if (input.ownership === "personal" && !isPersonalConnectionOwnerPrincipal(authorization))
1677
+ throw new HTTPException(403, { message: "personal ownership unavailable" });
1678
+ return beginConnectAttempt(tx, scope, {
1679
+ ...(continuation ? { externalContinuation: continuation } : {}),
1680
+ idempotencyKey: input.idempotencyKey,
1681
+ requestDigest: createHash("sha256").update(stableJson(input)).digest("hex"),
1682
+ returnUrl: input.returnUrl,
1683
+ attempt: {
1684
+ id,
1685
+ workspaceId,
1686
+ providerId: input.providerId,
1687
+ ownership: input.ownership,
1688
+ revision: 1,
1689
+ state: "credential_input",
1690
+ credentialsCommitted: false,
1691
+ integrationInstalled: false,
1692
+ completionRequirement: "connection",
1693
+ ...(existing
1694
+ ? {
1695
+ account: {
1696
+ id: existing.id,
1697
+ version: existing.version,
1698
+ providerId: input.providerId,
1699
+ label: existing.providerDomain,
1700
+ ownership: input.ownership,
1701
+ status: "auth_needed" as const,
1702
+ },
1703
+ }
1704
+ : {}),
1705
+ nextAction: {
1706
+ type: "credentials",
1707
+ fields: ["slack-personal", "gmail"].includes(input.providerId)
1708
+ ? []
1709
+ : input.providerId === "fiken-token"
1710
+ ? [
1711
+ {
1712
+ name: "apiToken",
1713
+ label: "Fiken API token",
1714
+ required: true,
1715
+ secret: true,
1716
+ },
1717
+ {
1718
+ name: "defaultCompanySlug",
1719
+ label: "Default company slug (optional)",
1720
+ required: false,
1721
+ secret: false,
1722
+ },
1723
+ ]
1724
+ : [
1725
+ {
1726
+ name: "mcpUrl",
1727
+ label: "MCP server URL",
1728
+ required: true,
1729
+ secret: false,
1730
+ },
1731
+ ...(input.providerId === "mcp-bearer"
1732
+ ? [
1733
+ {
1734
+ name: "token",
1735
+ label: "Bearer credential",
1736
+ required: true,
1737
+ secret: true,
1738
+ },
1739
+ ]
1740
+ : input.providerId === "mcp-headers"
1741
+ ? [
1742
+ {
1743
+ name: "headers",
1744
+ label: "Credential headers (JSON)",
1745
+ required: true,
1746
+ secret: true,
1747
+ },
1748
+ ]
1749
+ : []),
1750
+ ],
1751
+ },
1752
+ expiresAt: new Date(Date.now() + 15 * 60_000).toISOString(),
1753
+ },
1754
+ });
1755
+ }
1756
+ if (
1757
+ ["fiken-oauth", "slack-bot"].includes(input.providerId) &&
1758
+ input.ownership !== "workspace"
1759
+ )
1760
+ throw new HTTPException(422, { message: "This connector is workspace-owned" });
1761
+ if (
1762
+ [
1763
+ "atlassian",
1764
+ "google-drive-knowledge",
1765
+ "google-drive-publish",
1766
+ "github-personal",
1767
+ ].includes(input.providerId) &&
1768
+ (input.ownership !== "personal" || !isPersonalConnectionOwnerPrincipal(authorization))
1769
+ )
1770
+ throw new HTTPException(422, { message: "This connector requires personal ownership" });
1771
+ const started =
1772
+ input.providerId === "github-personal"
1773
+ ? await startPersonalGitHubOAuth(
1774
+ { ...deps, db: tx },
1775
+ {
1776
+ access: authorization,
1777
+ workspaceId,
1778
+ connectAttemptId: id,
1779
+ ...(input.reconnectAccountId ? { connectionId: input.reconnectAccountId } : {}),
1780
+ },
1781
+ )
1782
+ : input.providerId === "slack-bot"
1783
+ ? await startSlackBotInstall(
1784
+ { ...deps, db: tx },
1785
+ {
1786
+ ...scope,
1787
+ requestUrl: c.req.url,
1788
+ connectAttemptId: id,
1789
+ ...(input.reconnectAccountId ? { connectionId: input.reconnectAccountId } : {}),
1790
+ },
1791
+ )
1792
+ : ["google-drive-knowledge", "google-drive-publish"].includes(input.providerId)
1793
+ ? await startGoogleDriveOAuth(
1794
+ { ...deps, db: tx },
1795
+ {
1796
+ ...scope,
1797
+ requestUrl: c.req.url,
1798
+ connectAttemptId: id,
1799
+ payload: {
1800
+ capability:
1801
+ input.providerId === "google-drive-publish" ? "publish" : "source_read",
1802
+ ...(input.reconnectAccountId
1803
+ ? { connectionId: input.reconnectAccountId }
1804
+ : {}),
1805
+ },
1806
+ },
1807
+ )
1808
+ : input.providerId === "atlassian"
1809
+ ? await startAtlassianOAuth(
1810
+ { ...deps, db: tx },
1811
+ {
1812
+ ...scope,
1813
+ requestUrl: c.req.url,
1814
+ connectAttemptId: id,
1815
+ payload: input.reconnectAccountId
1816
+ ? { connectionId: input.reconnectAccountId }
1817
+ : {},
1818
+ },
1819
+ )
1820
+ : input.providerId === "fiken-oauth"
1821
+ ? await startFikenOAuth(
1822
+ { ...deps, db: tx },
1823
+ {
1824
+ ...scope,
1825
+ requestUrl: c.req.url,
1826
+ connectAttemptId: id,
1827
+ payload: input.reconnectAccountId
1828
+ ? { connectionId: input.reconnectAccountId }
1829
+ : {},
1830
+ },
1831
+ )
1832
+ : await startApiIntegrationProviderOAuth(
1833
+ { ...deps, db: tx },
1834
+ {
1835
+ ...scope,
1836
+ personalOwnershipAllowed:
1837
+ isPersonalConnectionOwnerPrincipal(authorization),
1838
+ ...(continuation ? { externalContinuation: continuation } : {}),
1839
+ connectAttemptId: id,
1840
+ requestUrl: c.req.url,
1841
+ payload: {
1842
+ definitionId: input.providerId,
1843
+ ownership: input.ownership,
1844
+ ...(input.reconnectAccountId
1845
+ ? { connectionId: input.reconnectAccountId }
1846
+ : {}),
1847
+ },
1848
+ },
1849
+ );
1850
+ if (!started.authorizationUrl)
1851
+ throw new HTTPException(502, { message: "provider did not return an authorization URL" });
1852
+ const reconnect = input.reconnectAccountId
1853
+ ? await getConnectionMetadata(tx, workspaceId, input.reconnectAccountId, scope.subjectId)
1854
+ : null;
1855
+ return beginConnectAttempt(tx, scope, {
1856
+ ...(continuation ? { externalContinuation: continuation } : {}),
1857
+ idempotencyKey: input.idempotencyKey,
1858
+ requestDigest: createHash("sha256").update(stableJson(input)).digest("hex"),
1859
+ returnUrl: input.returnUrl,
1860
+ attempt: {
1861
+ id,
1862
+ workspaceId,
1863
+ providerId: input.providerId,
1864
+ ownership: input.ownership,
1865
+ revision: 1,
1866
+ state: "requires_user_action",
1867
+ credentialsCommitted: false,
1868
+ integrationInstalled: false,
1869
+ completionRequirement: [
1870
+ "github-personal",
1871
+ "slack-bot",
1872
+ "fiken-oauth",
1873
+ "atlassian",
1874
+ "google-drive-knowledge",
1875
+ "google-drive-publish",
1876
+ ].includes(input.providerId)
1877
+ ? "connection"
1878
+ : "integration",
1879
+ ...([
1880
+ "github-personal",
1881
+ "slack-bot",
1882
+ "fiken-oauth",
1883
+ "atlassian",
1884
+ "google-drive-knowledge",
1885
+ "google-drive-publish",
1886
+ ].includes(input.providerId)
1887
+ ? {}
1888
+ : {
1889
+ installationTarget: input.installationTarget ?? {
1890
+ instanceKey: `account-${id}`,
1891
+ displayName: input.providerId,
1892
+ },
1893
+ }),
1894
+ ...(reconnect
1895
+ ? {
1896
+ account: {
1897
+ id: reconnect.id,
1898
+ version: reconnect.version,
1899
+ providerId: input.providerId,
1900
+ label: reconnect.providerDomain,
1901
+ ownership: input.ownership,
1902
+ status: "auth_needed" as const,
1903
+ },
1904
+ }
1905
+ : {}),
1906
+ nextAction: { type: "authorize", url: started.authorizationUrl },
1907
+ expiresAt: started.expiresAt,
1908
+ },
1909
+ });
1910
+ })(),
1911
+ );
1912
+ });
1913
+ app.get("/v1/workspaces/:workspaceId/connect/attempts", async (c) => {
1914
+ const workspaceId = c.req.param("workspaceId");
1915
+ const { grant } = await requireAccessGrantAuthorization(c, deps, workspaceId);
1916
+ return c.json(
1917
+ (
1918
+ await listPendingConnectAttempts(deps.db, {
1919
+ accountId: grant.accountId,
1920
+ workspaceId,
1921
+ subjectId: grant.subjectId,
1922
+ })
1923
+ ).filter((attempt) => canReadAttempt(grant.permissions, attempt.providerId)),
1924
+ );
1925
+ });
1926
+ app.get("/v1/workspaces/:workspaceId/connect/attempts/:attemptId", async (c) => {
1927
+ const workspaceId = c.req.param("workspaceId");
1928
+ const { grant } = await requireAccessGrantAuthorization(c, deps, workspaceId);
1929
+ const { attempt } = await getConnectAttempt(
1930
+ deps.db,
1931
+ { accountId: grant.accountId, workspaceId, subjectId: grant.subjectId },
1932
+ c.req.param("attemptId"),
1933
+ );
1934
+ if (!canReadAttempt(grant.permissions, attempt.providerId))
1935
+ throw new HTTPException(403, { message: "Connect read permission required" });
1936
+ return c.json(attempt);
1937
+ });
1938
+ }