@opengeni/api-router 3.0.1 → 3.1.2

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 (546) hide show
  1. package/dist/access-grant-rls.d.ts +8 -1
  2. package/dist/admission-trace.d.ts +8 -0
  3. package/dist/app.d.ts +50 -3
  4. package/dist/app.js +10 -1
  5. package/dist/artifact-catalog-cursor.d.ts +1 -0
  6. package/dist/attached-browser-viewer.d.ts +11 -0
  7. package/dist/auth/managed-auth-attempt-context.d.ts +2 -0
  8. package/dist/auth/managed-auth-rate-limits.d.ts +167 -0
  9. package/dist/auth/managed-auth-session-adapter.d.ts +9 -1
  10. package/dist/auth/managed-auth.d.ts +72 -5
  11. package/dist/auth/managed-sign-in-notifications.d.ts +4 -0
  12. package/dist/auth/new-signups-gate.d.ts +23 -0
  13. package/dist/auth/signup-funnel-metrics.d.ts +44 -0
  14. package/dist/azure-live.d.ts +28 -0
  15. package/dist/browser-action-fences.d.ts +13 -0
  16. package/dist/browser-controller-authority.d.ts +1 -61
  17. package/dist/browser-ephemeral.d.ts +5 -0
  18. package/dist/browser-lost-runtime-end.d.ts +24 -0
  19. package/dist/browser-state-authority.d.ts +1 -27
  20. package/dist/chunk-OGQDXVXY.js +18 -0
  21. package/dist/chunk-OGQDXVXY.js.map +1 -0
  22. package/dist/{chunk-AJ62T5QN.js → chunk-OT56OY4V.js} +80503 -39869
  23. package/dist/chunk-OT56OY4V.js.map +1 -0
  24. package/dist/claude-default-models.d.ts +19 -0
  25. package/dist/claude-subscription-core.d.ts +50 -0
  26. package/dist/claude-subscription-oauth.d.ts +36 -0
  27. package/dist/claude-subscription-profile.d.ts +9 -0
  28. package/dist/claude-subscription-usage.d.ts +6 -0
  29. package/dist/claude-workspace-connection.d.ts +15 -0
  30. package/dist/codemode.d.ts +40 -5
  31. package/dist/codex-realtime.d.ts +29 -3
  32. package/dist/codex-session-pointers.d.ts +19 -0
  33. package/dist/connection-ownership.d.ts +6 -3
  34. package/dist/controller-data-plane.d.ts +16 -0
  35. package/dist/credit-promotion-snapshot.d.ts +6 -0
  36. package/dist/editable-artifact-source-session.d.ts +3 -0
  37. package/dist/editable-artifact-websocket.d.ts +5 -3
  38. package/dist/fatal-process-boundary.d.ts +9 -1
  39. package/dist/github-access.d.ts +7 -1
  40. package/dist/github-browser-flow.d.ts +1 -1
  41. package/dist/http/acting-person.d.ts +36 -0
  42. package/dist/http/api-contract-compat.d.ts +19 -0
  43. package/dist/http/api-error.d.ts +50 -1
  44. package/dist/http/auth.d.ts +8 -1
  45. package/dist/http/cors.d.ts +12 -0
  46. package/dist/http/deprecation.d.ts +42 -0
  47. package/dist/http/keyed-admission.d.ts +12 -0
  48. package/dist/http/local-browser-boundary.d.ts +72 -0
  49. package/dist/http/metrics-listener.d.ts +22 -0
  50. package/dist/http/path-identifier.d.ts +17 -0
  51. package/dist/http/registered-route-label.d.ts +16 -0
  52. package/dist/http/rejection-telemetry.d.ts +34 -0
  53. package/dist/http/request-body.d.ts +27 -0
  54. package/dist/http/request-source.d.ts +41 -3
  55. package/dist/http/sse.d.ts +12 -1
  56. package/dist/http/unmatched-route.d.ts +18 -0
  57. package/dist/http/user-content.d.ts +36 -0
  58. package/dist/http/variable-sets-removed.d.ts +41 -0
  59. package/dist/http/websocket-upgrade-dispatch.d.ts +18 -0
  60. package/dist/index.js +1279 -108
  61. package/dist/index.js.map +1 -1
  62. package/dist/integration-connect-metrics.d.ts +35 -0
  63. package/dist/integration-endpoint-test.d.ts +20 -0
  64. package/dist/integrations/api-integrations.d.ts +2 -2
  65. package/dist/integrations/atlassian.d.ts +8 -3
  66. package/dist/integrations/fiken.d.ts +3 -1
  67. package/dist/integrations/github-lens-connect.d.ts +37 -66
  68. package/dist/integrations/google-drive.d.ts +2 -0
  69. package/dist/integrations/integration-commit-authority.d.ts +13 -0
  70. package/dist/integrations/marketplace-plugin.d.ts +43 -43
  71. package/dist/integrations/oauth-client.d.ts +171 -12
  72. package/dist/integrations/oauth-profiles.d.ts +42 -13
  73. package/dist/integrations/onepassword-vault-provider.d.ts +9 -0
  74. package/dist/integrations/slack-bot.d.ts +285 -7
  75. package/dist/integrations/slack-file-upload-flow.d.ts +62 -0
  76. package/dist/integrations/slack-interactions.d.ts +89 -10
  77. package/dist/integrations/slack-mrkdwn.d.ts +41 -0
  78. package/dist/integrations/slack-routing.d.ts +1 -1
  79. package/dist/integrations/slack-session-defaults.d.ts +58 -0
  80. package/dist/integrations/slack-task-file-upload.d.ts +36 -0
  81. package/dist/integrations/slack-tool-review.d.ts +5 -0
  82. package/dist/interaction-agent-access.d.ts +13 -8
  83. package/dist/interaction-agent-version.d.ts +21 -0
  84. package/dist/interaction-holder-heartbeat.d.ts +5 -0
  85. package/dist/interaction-metrics.d.ts +27 -2
  86. package/dist/interaction-route-stages.d.ts +25 -0
  87. package/dist/interaction-secrets-vault.d.ts +20 -0
  88. package/dist/interaction-secrets.d.ts +103 -0
  89. package/dist/mcp/action-catalog-types.d.ts +14 -0
  90. package/dist/mcp/action-catalog.gen.d.ts +2 -0
  91. package/dist/mcp/action-descriptions.d.ts +4 -0
  92. package/dist/mcp/action-schema-imports.gen.d.ts +1 -0
  93. package/dist/mcp/capability-account-readiness.d.ts +7 -0
  94. package/dist/mcp/child-read-evidence.d.ts +9 -0
  95. package/dist/mcp/company-profile-agent-admin.d.ts +12 -0
  96. package/dist/mcp/contract-input.d.ts +13 -0
  97. package/dist/mcp/documents.d.ts +2 -0
  98. package/dist/mcp/first-party-tool-permissions.d.ts +3 -606
  99. package/dist/mcp/notification-tools.d.ts +26 -0
  100. package/dist/mcp/orchestration-failure-diagnostic.d.ts +43 -0
  101. package/dist/mcp/scheduled-task-input.d.ts +1309 -0
  102. package/dist/mcp/scheduled-task-view.d.ts +217 -3
  103. package/dist/mcp/secret-tools.d.ts +14 -0
  104. package/dist/mcp/server.d.ts +108 -5
  105. package/dist/mcp/session-admin-access-tools.d.ts +33 -0
  106. package/dist/mcp/session-event-errors.d.ts +28 -0
  107. package/dist/mcp/session-event-view.d.ts +62 -0
  108. package/dist/mcp/session-view.d.ts +15 -0
  109. package/dist/mcp/session-wait.d.ts +18 -0
  110. package/dist/mcp/site-agent-clamp-tools.d.ts +38 -0
  111. package/dist/mcp/site-data-tools.d.ts +22 -0
  112. package/dist/mcp/site-publication-tools.d.ts +19 -0
  113. package/dist/mcp/site-sql-tools.d.ts +27 -0
  114. package/dist/mcp-oauth.d.ts +37 -4
  115. package/dist/model-calls/chat-completions.d.ts +87 -0
  116. package/dist/model-calls/pending-usage-settlements.d.ts +57 -0
  117. package/dist/model-calls/service.d.ts +36 -0
  118. package/dist/model-catalog.d.ts +9 -3
  119. package/dist/native-push-dispatch.d.ts +69 -0
  120. package/dist/observability.d.ts +3 -1
  121. package/dist/organization-mcp.d.ts +82 -0
  122. package/dist/prepared-mcp-actor.d.ts +77 -0
  123. package/dist/prepared-mcp-permissions.d.ts +8 -0
  124. package/dist/routes/analytics-consent.d.ts +26 -0
  125. package/dist/routes/api-integrations.d.ts +11 -0
  126. package/dist/routes/api-keys.d.ts +7 -7
  127. package/dist/routes/automations.d.ts +2 -0
  128. package/dist/routes/billing.d.ts +59 -0
  129. package/dist/routes/browser-sessions.d.ts +65 -2
  130. package/dist/routes/chat-completions.d.ts +11 -0
  131. package/dist/routes/claude-core-usage.d.ts +20 -0
  132. package/dist/routes/claude-subscription-accounts.d.ts +3 -0
  133. package/dist/routes/claude-subscription-oauth.d.ts +3 -0
  134. package/dist/routes/client-errors.d.ts +182 -0
  135. package/dist/routes/codex-core.d.ts +401 -0
  136. package/dist/routes/codex.d.ts +64 -2
  137. package/dist/routes/computer-sessions.d.ts +6 -1
  138. package/dist/routes/desktop-distribution.d.ts +32 -0
  139. package/dist/routes/github-browser-fonts.gen.d.ts +2 -0
  140. package/dist/routes/github-browser-pages.d.ts +27 -0
  141. package/dist/routes/inbox.d.ts +13 -0
  142. package/dist/routes/insights-response-cache.d.ts +10 -0
  143. package/dist/routes/insights-usage.d.ts +12 -0
  144. package/dist/routes/insights.d.ts +28 -0
  145. package/dist/routes/interaction-placement-message.d.ts +7 -0
  146. package/dist/routes/managed-sign-in-methods.d.ts +6 -0
  147. package/dist/routes/model-account-choice.d.ts +13 -0
  148. package/dist/routes/model-account-settings.d.ts +19 -0
  149. package/dist/routes/native-app-auth.d.ts +12 -0
  150. package/dist/routes/organization-integration-policy.d.ts +8 -0
  151. package/dist/routes/organization-integrations.d.ts +6 -0
  152. package/dist/routes/organization-mcp-connections.d.ts +3 -0
  153. package/dist/routes/organization-model-defaults.d.ts +9 -0
  154. package/dist/routes/organization-service-accounts.d.ts +3 -0
  155. package/dist/routes/personal-api-keys.d.ts +3 -0
  156. package/dist/routes/session-admin-access.d.ts +3 -0
  157. package/dist/routes/session-artifact-associations.d.ts +4 -0
  158. package/dist/routes/session-history-imports.d.ts +20 -0
  159. package/dist/routes/sessions.d.ts +38 -2
  160. package/dist/routes/site-data.d.ts +18 -0
  161. package/dist/routes/site-publishing.d.ts +5 -0
  162. package/dist/routes/site-runtime.d.ts +31 -0
  163. package/dist/routes/site-sql.d.ts +16 -0
  164. package/dist/routes/slack-bot-access.d.ts +3 -0
  165. package/dist/routes/subscription-account-pools.d.ts +21 -0
  166. package/dist/routes/subscription-core-http.d.ts +5 -0
  167. package/dist/routes/subscription-pool-access.d.ts +37 -0
  168. package/dist/routes/subscription-pool-core.d.ts +153 -0
  169. package/dist/routes/supergrok.d.ts +6 -6
  170. package/dist/routes/usage-allowances.d.ts +12 -0
  171. package/dist/routes/variable-sets-removed.d.ts +2 -0
  172. package/dist/routes/vault-providers.d.ts +34 -0
  173. package/dist/routes/vaults.d.ts +3 -0
  174. package/dist/routes/workspace-integrations.d.ts +35 -0
  175. package/dist/routes/workspace-model-providers.d.ts +4 -0
  176. package/dist/routes/workspaces.d.ts +11 -2
  177. package/dist/sandbox/agent-update.d.ts +64 -0
  178. package/dist/sandbox/auth-callout.d.ts +8 -0
  179. package/dist/sandbox/channel-a.d.ts +15 -1
  180. package/dist/sandbox/enrollment.d.ts +26 -4
  181. package/dist/sandbox/machine-audience.d.ts +29 -0
  182. package/dist/sandbox/machines.d.ts +15 -2
  183. package/dist/sandbox/metrics-ingestion.d.ts +6 -2
  184. package/dist/sandbox/screen-control.d.ts +85 -0
  185. package/dist/sandbox/viewer.d.ts +5 -1
  186. package/dist/sandbox-file-artifacts.d.ts +17 -1
  187. package/dist/scheduled-task-blockers.d.ts +39 -0
  188. package/dist/scheduled-task-deletion.d.ts +3 -4
  189. package/dist/scheduled-task-lifecycle.d.ts +14 -0
  190. package/dist/secret-answers.d.ts +40 -0
  191. package/dist/session-create-observability.d.ts +5 -0
  192. package/dist/session-event-view-36GO3IL7.js +528 -0
  193. package/dist/session-event-view-36GO3IL7.js.map +1 -0
  194. package/dist/signed-state-secret.d.ts +18 -0
  195. package/dist/site-agent-guard.d.ts +53 -0
  196. package/dist/site-data/operations.d.ts +103 -0
  197. package/dist/site-data/service.d.ts +203 -0
  198. package/dist/site-frame-guard.d.ts +32 -0
  199. package/dist/site-publishing/agent-data-tools.d.ts +84 -0
  200. package/dist/site-publishing/agent-model.d.ts +58 -0
  201. package/dist/site-publishing/agent-session.d.ts +16 -0
  202. package/dist/site-publishing/content-scanner.d.ts +31 -0
  203. package/dist/site-publishing/edge-sync.d.ts +48 -0
  204. package/dist/site-publishing/gateway-signature.d.ts +56 -0
  205. package/dist/site-publishing/limits.d.ts +63 -0
  206. package/dist/site-publishing/page-tool-authority.d.ts +30 -0
  207. package/dist/site-publishing/platform-usage.d.ts +113 -0
  208. package/dist/site-publishing/platform.d.ts +21 -0
  209. package/dist/site-publishing/readiness.d.ts +62 -0
  210. package/dist/site-publishing/runtime.d.ts +179 -0
  211. package/dist/site-publishing/service.d.ts +264 -0
  212. package/dist/site-publishing/session-events.d.ts +22 -0
  213. package/dist/site-publishing/session-views.d.ts +36 -0
  214. package/dist/site-publishing/viewer.d.ts +40 -0
  215. package/dist/site-session-origin.d.ts +31 -6
  216. package/dist/site-sql/format.d.ts +47 -0
  217. package/dist/site-sql/gateway.d.ts +50 -0
  218. package/dist/site-sql/service.d.ts +213 -0
  219. package/dist/temporal-schedule-sync.d.ts +26 -0
  220. package/dist/transcription/billing-refusal.d.ts +8 -0
  221. package/dist/transcription/normalize.d.ts +23 -0
  222. package/dist/transcription/providers/azure-mai.d.ts +12 -0
  223. package/dist/transcription/providers/azure-openai.d.ts +4 -0
  224. package/dist/transcription/providers/openai.d.ts +16 -0
  225. package/dist/transcription/providers/subscription-core.d.ts +39 -0
  226. package/dist/transcription/service.d.ts +10 -1
  227. package/dist/transcription/wav-duration.d.ts +8 -0
  228. package/dist/workspace-tool-gateway-attestations.d.ts +37 -0
  229. package/dist/workspace-tool-gateway-observability.d.ts +16 -0
  230. package/dist/workspace-tool-gateway-secrets.d.ts +27 -0
  231. package/dist/workspace-tool-gateway.d.ts +102 -6
  232. package/dist/workspace-tool-target-fence.d.ts +116 -0
  233. package/dist/workspace-tool-target.d.ts +225 -0
  234. package/dist/workspace-webhook-dispatch.d.ts +26 -0
  235. package/dist/xai-subscription-core.d.ts +60 -0
  236. package/package.json +25 -20
  237. package/src/access-grant-rls.ts +64 -14
  238. package/src/admission-trace.ts +14 -0
  239. package/src/app.ts +1684 -252
  240. package/src/artifact-catalog-cursor.ts +1 -0
  241. package/src/attached-browser-viewer.ts +26 -0
  242. package/src/auth/managed-auth-attempt-context.ts +9 -0
  243. package/src/auth/managed-auth-rate-limits.ts +252 -0
  244. package/src/auth/managed-auth-session-adapter.ts +64 -29
  245. package/src/auth/managed-auth.ts +303 -35
  246. package/src/auth/managed-email.ts +18 -1
  247. package/src/auth/managed-sign-in-notifications.ts +81 -0
  248. package/src/auth/new-signups-gate.ts +49 -0
  249. package/src/auth/organization-user-setup.ts +3 -3
  250. package/src/auth/signup-funnel-metrics.ts +164 -0
  251. package/src/azure-live.ts +103 -0
  252. package/src/browser-action-fences.ts +45 -0
  253. package/src/browser-controller-authority.ts +1 -186
  254. package/src/browser-ephemeral.ts +39 -0
  255. package/src/browser-lost-runtime-end.ts +71 -0
  256. package/src/browser-state-authority.ts +1 -236
  257. package/src/claude-default-models.ts +37 -0
  258. package/src/claude-subscription-core.ts +154 -0
  259. package/src/claude-subscription-oauth.ts +352 -0
  260. package/src/claude-subscription-profile.ts +73 -0
  261. package/src/claude-subscription-usage.ts +51 -0
  262. package/src/claude-workspace-connection.ts +82 -0
  263. package/src/codemode.ts +236 -39
  264. package/src/codex-realtime.ts +181 -53
  265. package/src/codex-session-pointers.ts +80 -0
  266. package/src/company-brain-okf.ts +1 -1
  267. package/src/connected-machine-computer-access.ts +2 -1
  268. package/src/connection-ownership.ts +19 -13
  269. package/src/controller-data-plane.ts +34 -0
  270. package/src/credit-promotion-snapshot.ts +36 -0
  271. package/src/editable-artifact-production.ts +10 -0
  272. package/src/editable-artifact-source-session.ts +123 -0
  273. package/src/editable-artifact-websocket.ts +16 -3
  274. package/src/fatal-process-boundary.ts +67 -3
  275. package/src/gateway-realtime.ts +12 -8
  276. package/src/github-access.ts +31 -5
  277. package/src/github-browser-flow.ts +1 -1
  278. package/src/http/acting-person.ts +105 -0
  279. package/src/http/api-contract-compat.ts +49 -0
  280. package/src/http/api-error.ts +210 -2
  281. package/src/http/auth.ts +68 -4
  282. package/src/http/cors.ts +37 -5
  283. package/src/http/deprecation.ts +118 -0
  284. package/src/http/keyed-admission.ts +29 -0
  285. package/src/http/local-browser-boundary.ts +343 -0
  286. package/src/http/metrics-listener.ts +57 -0
  287. package/src/http/path-identifier.ts +54 -0
  288. package/src/http/registered-route-label.ts +34 -0
  289. package/src/http/rejection-telemetry.ts +120 -0
  290. package/src/http/request-body.ts +127 -0
  291. package/src/http/request-source.ts +154 -13
  292. package/src/http/sse.ts +88 -28
  293. package/src/http/unmatched-route.ts +60 -0
  294. package/src/http/user-content.ts +158 -0
  295. package/src/http/variable-sets-removed.ts +122 -0
  296. package/src/http/websocket-upgrade-dispatch.ts +29 -0
  297. package/src/index.ts +107 -57
  298. package/src/integration-connect-metrics.ts +228 -0
  299. package/src/integration-endpoint-test.ts +291 -0
  300. package/src/integrations/api-integrations.ts +81 -7
  301. package/src/integrations/atlassian.ts +202 -83
  302. package/src/integrations/fiken.ts +66 -38
  303. package/src/integrations/github-app-connect.ts +64 -18
  304. package/src/integrations/github-lens-connect.ts +8 -29
  305. package/src/integrations/google-drive.ts +448 -152
  306. package/src/integrations/integration-commit-authority.ts +69 -0
  307. package/src/integrations/marketplace-plugin.ts +1 -1
  308. package/src/integrations/oauth-client.ts +733 -193
  309. package/src/integrations/oauth-profiles.ts +116 -45
  310. package/src/integrations/onepassword-vault-provider.ts +16 -0
  311. package/src/integrations/personal-github-repositories.ts +1 -1
  312. package/src/integrations/personal-github.ts +61 -35
  313. package/src/integrations/provider-oauth.ts +67 -85
  314. package/src/integrations/slack-app-home.ts +5 -5
  315. package/src/integrations/slack-bot.ts +893 -201
  316. package/src/integrations/slack-file-upload-flow.ts +147 -0
  317. package/src/integrations/slack-install.ts +7 -7
  318. package/src/integrations/slack-interactions.ts +1589 -299
  319. package/src/integrations/slack-mrkdwn.ts +193 -0
  320. package/src/integrations/slack-routing.ts +2 -2
  321. package/src/integrations/slack-session-defaults.ts +224 -0
  322. package/src/integrations/slack-task-file-upload.ts +323 -0
  323. package/src/integrations/slack-tool-review.ts +65 -0
  324. package/src/integrations/social-oauth.ts +90 -58
  325. package/src/interaction-agent-access.ts +40 -20
  326. package/src/interaction-agent-version.ts +85 -0
  327. package/src/interaction-holder-heartbeat.ts +24 -8
  328. package/src/interaction-metrics.ts +164 -2
  329. package/src/interaction-route-stages.ts +94 -0
  330. package/src/interaction-secrets-vault.ts +213 -0
  331. package/src/interaction-secrets.ts +426 -0
  332. package/src/mcp/action-catalog-types.ts +14 -0
  333. package/src/mcp/action-catalog.gen.ts +8014 -0
  334. package/src/mcp/action-descriptions.ts +299 -0
  335. package/src/mcp/action-schema-imports.gen.ts +304 -0
  336. package/src/mcp/capability-account-readiness.ts +17 -0
  337. package/src/mcp/child-read-evidence.ts +19 -0
  338. package/src/mcp/company-profile-agent-admin.ts +13 -0
  339. package/src/mcp/contract-input.ts +165 -0
  340. package/src/mcp/documents.ts +9 -2
  341. package/src/mcp/editable-artifacts.ts +29 -11
  342. package/src/mcp/files.ts +5 -1
  343. package/src/mcp/first-party-tool-permissions.ts +7 -285
  344. package/src/mcp/knowledge-entries.ts +108 -34
  345. package/src/mcp/notification-tools.ts +480 -0
  346. package/src/mcp/orchestration-failure-diagnostic.ts +43 -0
  347. package/src/mcp/scheduled-task-input.ts +89 -0
  348. package/src/mcp/scheduled-task-view.ts +63 -3
  349. package/src/mcp/secret-tools.ts +185 -0
  350. package/src/mcp/server.ts +2213 -1116
  351. package/src/mcp/session-admin-access-tools.ts +163 -0
  352. package/src/mcp/session-event-errors.ts +123 -0
  353. package/src/mcp/session-event-view.ts +395 -13
  354. package/src/mcp/session-view.ts +3 -0
  355. package/src/mcp/session-wait.ts +67 -7
  356. package/src/mcp/site-agent-clamp-tools.ts +140 -0
  357. package/src/mcp/site-data-tools.ts +339 -0
  358. package/src/mcp/site-publication-tools.ts +201 -0
  359. package/src/mcp/site-sql-tools.ts +427 -0
  360. package/src/mcp-oauth.ts +635 -45
  361. package/src/model-calls/chat-completions.ts +352 -0
  362. package/src/model-calls/pending-usage-settlements.ts +125 -0
  363. package/src/model-calls/service.ts +694 -0
  364. package/src/model-catalog.ts +68 -17
  365. package/src/native-push-dispatch.ts +457 -0
  366. package/src/observability.ts +15 -1
  367. package/src/organization-mcp.ts +485 -0
  368. package/src/prepared-mcp-actor.ts +154 -0
  369. package/src/prepared-mcp-permissions.ts +38 -0
  370. package/src/routes/analytics-consent.ts +123 -0
  371. package/src/routes/api-integrations.ts +268 -9
  372. package/src/routes/api-keys.ts +164 -25
  373. package/src/routes/artifact-catalog.ts +111 -0
  374. package/src/routes/automations.ts +163 -90
  375. package/src/routes/billing.ts +535 -59
  376. package/src/routes/browser-sessions.ts +966 -342
  377. package/src/routes/canonical-human-identities.ts +61 -0
  378. package/src/routes/capabilities.ts +112 -5
  379. package/src/routes/channels.ts +4 -3
  380. package/src/routes/chat-completions.ts +235 -0
  381. package/src/routes/claude-core-usage.ts +147 -0
  382. package/src/routes/claude-subscription-accounts.ts +233 -0
  383. package/src/routes/claude-subscription-oauth.ts +175 -0
  384. package/src/routes/client-errors.ts +394 -0
  385. package/src/routes/codex-core.ts +687 -0
  386. package/src/routes/codex.ts +1277 -1119
  387. package/src/routes/company-brain.ts +4 -0
  388. package/src/routes/computer-sessions.ts +319 -85
  389. package/src/routes/connect.ts +277 -175
  390. package/src/routes/connections.ts +690 -363
  391. package/src/routes/desktop-distribution.ts +217 -0
  392. package/src/routes/documents.ts +13 -7
  393. package/src/routes/editable-artifacts.ts +74 -4
  394. package/src/routes/enrollments.ts +110 -16
  395. package/src/routes/external-identity-links.ts +13 -12
  396. package/src/routes/files.ts +41 -16
  397. package/src/routes/github-browser-fonts.gen.ts +12 -0
  398. package/src/routes/github-browser-pages.ts +258 -0
  399. package/src/routes/github.ts +200 -75
  400. package/src/routes/inbox.ts +353 -0
  401. package/src/routes/insights-response-cache.ts +83 -0
  402. package/src/routes/insights-usage.ts +345 -0
  403. package/src/routes/insights.ts +103 -8
  404. package/src/routes/install.ts +51 -10
  405. package/src/routes/integration-facets.ts +70 -28
  406. package/src/routes/interaction-placement-message.ts +12 -0
  407. package/src/routes/knowledge.ts +136 -27
  408. package/src/routes/machines.ts +189 -135
  409. package/src/routes/managed-auth-session-sets.ts +43 -10
  410. package/src/routes/managed-onboarding.ts +39 -19
  411. package/src/routes/managed-sign-in-methods.ts +433 -0
  412. package/src/routes/memory-slack-publications.ts +3 -2
  413. package/src/routes/model-account-choice.ts +194 -0
  414. package/src/routes/model-account-settings.ts +235 -0
  415. package/src/routes/model-connection-access.ts +210 -48
  416. package/src/routes/native-app-auth.ts +296 -0
  417. package/src/routes/organization-integration-policy.ts +116 -0
  418. package/src/routes/organization-integrations.ts +326 -0
  419. package/src/routes/organization-mcp-connections.ts +183 -0
  420. package/src/routes/organization-memberships.ts +205 -19
  421. package/src/routes/organization-model-defaults.ts +110 -0
  422. package/src/routes/organization-model-providers.ts +74 -3
  423. package/src/routes/organization-service-accounts.ts +128 -0
  424. package/src/routes/organization-sessions.ts +21 -5
  425. package/src/routes/personal-api-keys.ts +184 -0
  426. package/src/routes/personal-github-git-broker.ts +2 -2
  427. package/src/routes/personal-github.ts +9 -4
  428. package/src/routes/plugins.ts +46 -7
  429. package/src/routes/pr-review-github.ts +105 -118
  430. package/src/routes/pr-review.ts +88 -82
  431. package/src/routes/preference-registry.ts +18 -0
  432. package/src/routes/rigs.ts +48 -13
  433. package/src/routes/scheduled-tasks.ts +347 -27
  434. package/src/routes/session-admin-access.ts +271 -0
  435. package/src/routes/session-artifact-associations.ts +164 -0
  436. package/src/routes/session-history-imports.ts +305 -0
  437. package/src/routes/sessions.ts +1682 -363
  438. package/src/routes/site-data.ts +340 -0
  439. package/src/routes/site-publishing.ts +584 -0
  440. package/src/routes/site-runtime.ts +1944 -0
  441. package/src/routes/site-sql.ts +263 -0
  442. package/src/routes/skill-content.ts +72 -7
  443. package/src/routes/skills.ts +5 -4
  444. package/src/routes/slack-bot-access.ts +108 -0
  445. package/src/routes/social.ts +27 -15
  446. package/src/routes/subscription-account-pools.ts +209 -0
  447. package/src/routes/subscription-core-http.ts +18 -0
  448. package/src/routes/subscription-pool-access.ts +180 -0
  449. package/src/routes/subscription-pool-core.ts +552 -0
  450. package/src/routes/supergrok.ts +174 -655
  451. package/src/routes/transcription-recordings.ts +71 -4
  452. package/src/routes/transcriptions.ts +26 -2
  453. package/src/routes/usage-allowances.ts +381 -0
  454. package/src/routes/variable-sets-removed.ts +19 -0
  455. package/src/routes/vault-providers.ts +782 -0
  456. package/src/routes/vaults.ts +421 -0
  457. package/src/routes/video-generation.ts +30 -12
  458. package/src/routes/workspace-artifacts.ts +165 -53
  459. package/src/routes/workspace-integrations.ts +576 -0
  460. package/src/routes/workspace-model-providers.ts +148 -0
  461. package/src/routes/workspace-state.ts +4 -0
  462. package/src/routes/workspaces.ts +625 -117
  463. package/src/sandbox/agent-update.ts +315 -0
  464. package/src/sandbox/auth-callout.ts +104 -21
  465. package/src/sandbox/channel-a.ts +165 -55
  466. package/src/sandbox/enrollment.ts +131 -22
  467. package/src/sandbox/machine-audience.ts +192 -0
  468. package/src/sandbox/machines.ts +75 -31
  469. package/src/sandbox/metrics-ingestion.ts +133 -2
  470. package/src/sandbox/rematerialize.ts +51 -21
  471. package/src/sandbox/screen-control.ts +289 -0
  472. package/src/sandbox/viewer.ts +33 -58
  473. package/src/sandbox-file-artifacts.ts +53 -27
  474. package/src/scheduled-task-blockers.ts +293 -0
  475. package/src/scheduled-task-deletion.ts +13 -3
  476. package/src/scheduled-task-lifecycle.ts +84 -0
  477. package/src/secret-answers.ts +119 -0
  478. package/src/session-create-observability.ts +46 -0
  479. package/src/session-realtime-context.ts +26 -4
  480. package/src/signed-state-secret.ts +58 -0
  481. package/src/site-agent-guard.ts +261 -0
  482. package/src/site-data/operations.ts +777 -0
  483. package/src/site-data/service.ts +1043 -0
  484. package/src/site-frame-guard.ts +107 -0
  485. package/src/site-publishing/agent-data-tools.ts +250 -0
  486. package/src/site-publishing/agent-model.ts +127 -0
  487. package/src/site-publishing/agent-session.ts +148 -0
  488. package/src/site-publishing/content-scanner.ts +93 -0
  489. package/src/site-publishing/edge-sync.ts +148 -0
  490. package/src/site-publishing/gateway-signature.ts +198 -0
  491. package/src/site-publishing/limits.ts +315 -0
  492. package/src/site-publishing/page-tool-authority.ts +111 -0
  493. package/src/site-publishing/platform-usage.ts +479 -0
  494. package/src/site-publishing/platform.ts +98 -0
  495. package/src/site-publishing/readiness.ts +330 -0
  496. package/src/site-publishing/runtime.ts +831 -0
  497. package/src/site-publishing/service.ts +1282 -0
  498. package/src/site-publishing/session-events.ts +262 -0
  499. package/src/site-publishing/session-views.ts +262 -0
  500. package/src/site-publishing/viewer.ts +75 -0
  501. package/src/site-session-origin.ts +91 -6
  502. package/src/site-sql/format.ts +134 -0
  503. package/src/site-sql/gateway.ts +168 -0
  504. package/src/site-sql/service.ts +1636 -0
  505. package/src/site-uploads.ts +27 -5
  506. package/src/temporal-schedule-sync.ts +74 -0
  507. package/src/transcription/billing-refusal.ts +14 -0
  508. package/src/transcription/normalize.ts +77 -0
  509. package/src/transcription/providers/azure-mai.ts +98 -0
  510. package/src/transcription/providers/azure-openai.ts +16 -7
  511. package/src/transcription/providers/codex-subscription.ts +190 -67
  512. package/src/transcription/providers/openai.ts +79 -5
  513. package/src/transcription/providers/subscription-core.ts +125 -0
  514. package/src/transcription/providers/xai-subscription.ts +81 -83
  515. package/src/transcription/segmenter.ts +7 -1
  516. package/src/transcription/service.ts +239 -10
  517. package/src/transcription/wav-duration.ts +39 -0
  518. package/src/workspace-deletion.ts +25 -1
  519. package/src/workspace-tool-gateway-attestations.ts +133 -0
  520. package/src/workspace-tool-gateway-observability.ts +64 -0
  521. package/src/workspace-tool-gateway-secrets.ts +104 -0
  522. package/src/workspace-tool-gateway.ts +921 -103
  523. package/src/workspace-tool-target-fence.ts +380 -0
  524. package/src/workspace-tool-target.ts +554 -0
  525. package/src/workspace-webhook-dispatch.ts +225 -0
  526. package/src/xai-realtime.ts +85 -81
  527. package/src/xai-subscription-core.ts +172 -0
  528. package/dist/browser-auth-broker.d.ts +0 -20
  529. package/dist/chunk-AJ62T5QN.js.map +0 -1
  530. package/dist/connection-authority-owner.d.ts +0 -59
  531. package/dist/routes/connection-authorities.d.ts +0 -3
  532. package/dist/routes/environments.d.ts +0 -5
  533. package/dist/routes/host-mcp-bindings.d.ts +0 -5
  534. package/dist/routes/host-mcp-resolvers.d.ts +0 -3
  535. package/dist/routes/packs.d.ts +0 -3
  536. package/dist/session-event-view-MU3HMNV3.js +0 -264
  537. package/dist/session-event-view-MU3HMNV3.js.map +0 -1
  538. package/dist/xai-subscription-auth.d.ts +0 -23
  539. package/src/browser-auth-broker.ts +0 -155
  540. package/src/connection-authority-owner.ts +0 -61
  541. package/src/routes/connection-authorities.ts +0 -139
  542. package/src/routes/environments.ts +0 -388
  543. package/src/routes/host-mcp-bindings.ts +0 -122
  544. package/src/routes/host-mcp-resolvers.ts +0 -41
  545. package/src/routes/packs.ts +0 -784
  546. package/src/xai-subscription-auth.ts +0 -139
@@ -1,4 +1,25 @@
1
1
  import { createHash, createHmac } from "node:crypto";
2
+ import { verifyDirectModelAccess } from "@opengeni/core";
3
+ import {
4
+ assertClaudeWorkspaceCredential,
5
+ prepareClaudeWorkspaceCredential,
6
+ } from "../claude-workspace-connection";
7
+ import {
8
+ createConnectionIdempotently,
9
+ upsertWorkspaceProviderApiKeyConnection,
10
+ rotateWorkspaceProviderApiKeyConnection,
11
+ revokeWorkspaceProviderApiKeyConnections,
12
+ workspaceProviderApiKeyConnectionSpec,
13
+ type WorkspaceProviderApiKeyConnectionKind,
14
+ getConnectionCreationResult,
15
+ ConnectionCreateIdempotencyError,
16
+ } from "@opengeni/db";
17
+ import { sql } from "drizzle-orm";
18
+ import { assertOrganizationIntegrationAllowed } from "@opengeni/contracts";
19
+ import {
20
+ withOrganizationIntegrationAcquisition,
21
+ withOrganizationIntegrationPolicyFence,
22
+ } from "@opengeni/db/organization-integration-policy";
2
23
  import { ExternalActorContinuation } from "@opengeni/contracts/external-identities";
3
24
  import {
4
25
  startSlackBotInstall,
@@ -7,6 +28,9 @@ import {
7
28
  import {
8
29
  WORKSPACE_OPENROUTER_CONNECTION_DOMAIN,
9
30
  WORKSPACE_OPENROUTER_CONNECTION_ROLE,
31
+ WORKSPACE_OPPER_CONNECTION_DOMAIN,
32
+ WORKSPACE_OPPER_CONNECTION_ROLE,
33
+ opperCredentialProblem,
10
34
  VERCEL_AI_GATEWAY_CONNECTION_DOMAIN,
11
35
  VERCEL_AI_GATEWAY_CONNECTION_ROLE,
12
36
  } from "@opengeni/config";
@@ -21,6 +45,7 @@ import {
21
45
  import {
22
46
  API_INTEGRATION_OAUTH_CREDENTIAL_ROLE,
23
47
  ApiIntegrationOAuthStartRequest,
48
+ DirectModelProviderMetadata,
24
49
  ConnectionResponse,
25
50
  CreateConnectionRequest,
26
51
  FikenInstallRequest,
@@ -31,6 +56,8 @@ import {
31
56
  ListConnectionsResponse,
32
57
  OPENROUTER_CREDENTIAL_OPERATION_DIGEST_METADATA_KEY,
33
58
  OPENROUTER_CREDENTIAL_OPERATION_ID_METADATA_KEY,
59
+ OPPER_CREDENTIAL_OPERATION_DIGEST_METADATA_KEY,
60
+ OPPER_CREDENTIAL_OPERATION_ID_METADATA_KEY,
34
61
  OpenGeniSlackBotInstallRequest,
35
62
  OAuthStartRequest,
36
63
  OAuthStartResponse,
@@ -59,7 +86,9 @@ import {
59
86
  } from "@opengeni/contracts/personal-github";
60
87
  import {
61
88
  hasPermission,
89
+ listOwnConnectionAccountsForGrant,
62
90
  hasReservedFikenMetadata,
91
+ ONEPASSWORD_CREDENTIAL_ROLE,
63
92
  hasReservedOpenGeniSlackBotMetadata,
64
93
  isOpenGeniSlackBotConnection,
65
94
  openGeniSlackBotMetadata,
@@ -74,27 +103,21 @@ import {
74
103
  finishConnectOperation,
75
104
  getConnectAttempt,
76
105
  decryptEnvironmentValue,
77
- createConnection,
78
106
  withWorkspaceSubjectRls,
79
107
  type Database,
80
108
  encryptEnvironmentValue,
109
+ brokeredCredentialBundleProblem,
81
110
  getConnectionMetadata,
82
111
  listConnectionsMetadata,
83
112
  listSlackInstallationBindings,
84
113
  persistSlackBotInstallationWithSuccessAudit,
85
114
  recordSlackBotInstallCallbackFailure,
86
115
  revokeConnection,
87
- revokeWorkspaceOpenRouterConnections,
88
- revokeWorkspaceVercelAiGatewayConnections,
89
116
  revokeConnectionWithSlackBotSuccessAudit,
90
- rotateWorkspaceVercelAiGatewayConnection,
91
- rotateWorkspaceOpenRouterConnection,
92
117
  SlackBotLifecycleSuccessAuditError,
93
118
  SlackInstallationBindingConflictError,
94
119
  updateConnection,
95
120
  updateSlackBotDocumentDestination,
96
- upsertWorkspaceVercelAiGatewayConnection,
97
- upsertWorkspaceOpenRouterConnection,
98
121
  type SlackBotInstallCallbackFailureReason,
99
122
  type SlackBotInstallCallbackFailureStage,
100
123
  } from "@opengeni/db";
@@ -114,6 +137,7 @@ import {
114
137
  isApiIntegrationProviderOAuthState,
115
138
  startApiIntegrationProviderOAuth,
116
139
  } from "../integrations/provider-oauth";
140
+ import { offerClaudeDefaultModels } from "../claude-default-models";
117
141
  import { disconnectPersonalGitHub } from "../integrations/personal-github";
118
142
  import {
119
143
  browseAtlassianSources,
@@ -126,12 +150,14 @@ import {
126
150
  import {
127
151
  completeMcpOAuthCallback,
128
152
  integrationBaseUrl,
153
+ oauthStateFailureReturn,
129
154
  startMcpOAuth,
130
155
  } from "../integrations/oauth-client";
131
156
  import {
132
157
  assertPersonalConnectionOwnerPrincipal,
133
158
  isPersonalConnectionOwnerPrincipal,
134
159
  requireLegacyOAuthActor,
160
+ requireProviderConsentInBrowser,
135
161
  } from "../connection-ownership";
136
162
  import { canonicalProviderDomain } from "../integrations/provider-domain";
137
163
  import { externalActorContinuationForAuthorization } from "@opengeni/core";
@@ -152,6 +178,7 @@ import {
152
178
  } from "@opengeni/contracts";
153
179
  import { readSignedState } from "@opengeni/github";
154
180
  import { oauthStateTtlMs, requireIntegrationsStateSecret } from "../integrations/oauth-client";
181
+ import { parseRequestJson } from "../http/request-body";
155
182
 
156
183
  type OpenGeniSlackInstallState = {
157
184
  connectAttemptId?: string;
@@ -185,6 +212,26 @@ export function registerConnectionRoutes(app: Hono, deps: ApiRouteDeps): void {
185
212
  );
186
213
  });
187
214
 
215
+ app.get("/v1/workspaces/:workspaceId/connections/accounts", async (c) => {
216
+ const workspaceId = c.req.param("workspaceId");
217
+ const grant = await requireAccessGrant(c, deps, workspaceId, "connections:read");
218
+ const includeInactive = c.req.query("includeInactive");
219
+ if (
220
+ includeInactive !== undefined &&
221
+ includeInactive !== "true" &&
222
+ includeInactive !== "false"
223
+ ) {
224
+ throw new HTTPException(400, { message: "includeInactive must be true or false" });
225
+ }
226
+ return c.json(
227
+ ListConnectionsResponse.parse({
228
+ connections: await listOwnConnectionAccountsForGrant(db, grant, {
229
+ includeInactive: includeInactive === "true",
230
+ }),
231
+ }),
232
+ );
233
+ });
234
+
188
235
  app.get("/v1/workspaces/:workspaceId/connections/slack-bot/bindings", async (c) => {
189
236
  const workspaceId = c.req.param("workspaceId");
190
237
  const grant = await requireAccessGrant(c, deps, workspaceId, "connections:read");
@@ -198,17 +245,72 @@ export function registerConnectionRoutes(app: Hono, deps: ApiRouteDeps): void {
198
245
  );
199
246
  });
200
247
 
248
+ app.get("/v1/workspaces/:workspaceId/connections/operations/:operationId", async (c) => {
249
+ const workspaceId = c.req.param("workspaceId");
250
+ const grant = await requireAccessGrant(c, deps, workspaceId, "connections:read");
251
+ const operationId = CreateConnectionRequest.shape.operationId
252
+ .unwrap()
253
+ .parse(c.req.param("operationId"));
254
+ const connection = await getConnectionCreationResult(db, {
255
+ accountId: grant.accountId,
256
+ workspaceId,
257
+ subjectId: grant.subjectId,
258
+ operationId,
259
+ });
260
+ if (!connection) throw new HTTPException(404, { message: "Connection operation not found" });
261
+ return c.json(ConnectionResponse.parse({ connection }));
262
+ });
263
+
201
264
  app.post("/v1/workspaces/:workspaceId/connections", async (c) => {
202
265
  const workspaceId = c.req.param("workspaceId");
203
266
  const access = await requireAccessGrantAuthorization(c, deps, workspaceId, "connections:write");
204
267
  const grant = access.grant;
205
268
  const beforeCommit = externalContinuationCommitAuthorizer(access);
206
- const payload = CreateConnectionRequest.parse(await c.req.json());
269
+ const payload = await parseRequestJson(c, CreateConnectionRequest);
270
+ if (
271
+ payload.metadata?.directModelProvider !== undefined ||
272
+ String(payload.metadata?.credentialRole ?? "").startsWith("direct_")
273
+ ) {
274
+ const config = DirectModelProviderMetadata.safeParse(payload.metadata?.directModelProvider);
275
+ const domain = config.success
276
+ ? new URL(config.data.endpoint ?? "https://api.openai.com/v1").hostname
277
+ : null;
278
+ if (
279
+ !config.success ||
280
+ payload.subjectId !== null ||
281
+ payload.kind !== "api_key" ||
282
+ canonicalProviderDomain(payload.providerDomain) !== domain ||
283
+ payload.metadata?.credentialRole !== `direct_${config.data.provider}` ||
284
+ typeof payload.credential.apiKey !== "string" ||
285
+ !payload.credential.apiKey.trim() ||
286
+ !payload.operationId ||
287
+ payload.expiresAt
288
+ ) {
289
+ throw new HTTPException(422, {
290
+ message:
291
+ "Invalid OpenAI or Azure OpenAI connection: provide a key, model, and official provider endpoint",
292
+ });
293
+ }
294
+ payload.metadata = { ...payload.metadata, directModelProvider: config.data };
295
+ if (payload.verifyModelAccess) {
296
+ await verifyDirectModelAccess(
297
+ config.data,
298
+ payload.credential.apiKey as string,
299
+ deps.directModelFetch,
300
+ );
301
+ }
302
+ } else if (payload.verifyModelAccess) {
303
+ throw new HTTPException(422, {
304
+ message: "Model checks require an OpenAI or Azure OpenAI connection",
305
+ });
306
+ }
307
+ const directModelKey = payload.metadata?.directModelProvider !== undefined;
207
308
  // All writes in this closure must use the caller-owned scoped transaction.
208
309
  // eslint-disable-next-line no-shadow
209
310
  const persist = async (db: Database) => {
210
311
  assertNotReservedSlackBotMetadata(payload.metadata);
211
312
  assertNotReservedFikenMetadata(payload.metadata);
313
+ assertNotReservedOnePasswordMetadata(payload.metadata);
212
314
  assertNotReservedApiIntegrationOAuthMetadata(payload.metadata);
213
315
  assertNotReservedPersonalGitHubMetadata(payload.metadata);
214
316
  const key = requireEnvironmentEncryption(settings);
@@ -217,6 +319,19 @@ export function registerConnectionRoutes(app: Hono, deps: ApiRouteDeps): void {
217
319
  assertPersonalConnectionOwnerPrincipal(access);
218
320
  }
219
321
  const providerDomain = canonicalProviderDomain(payload.providerDomain);
322
+ payload.credential = prepareClaudeWorkspaceCredential(
323
+ settings,
324
+ workspaceId,
325
+ payload.metadata,
326
+ payload.credential,
327
+ );
328
+ assertClaudeWorkspaceCredential(settings, {
329
+ subjectId,
330
+ providerDomain,
331
+ kind: payload.kind,
332
+ metadata: payload.metadata,
333
+ credential: payload.credential,
334
+ });
220
335
  assertNotDirectPersonalSlackOAuth(providerDomain, payload.kind);
221
336
  assertNotDirectGoogleDriveOAuth(providerDomain, payload.kind, payload.metadata);
222
337
  assertNotDirectAtlassianOAuth(providerDomain, payload.kind, payload.metadata);
@@ -228,6 +343,11 @@ export function registerConnectionRoutes(app: Hono, deps: ApiRouteDeps): void {
228
343
  kind: payload.kind,
229
344
  metadata: payload.metadata,
230
345
  });
346
+ // Validated model keys store `{ apiKey }` for model execution. Generic
347
+ // integration credentials still require an explicit broker destination.
348
+ if (!workspaceProviderKind && !directModelKey)
349
+ assertBrokeredApiKeyCredential(payload.kind, payload.credential);
350
+ if (workspaceProviderKind === "opper") assertOpperCredential(payload.credential);
231
351
  const connection = workspaceProviderKind
232
352
  ? await (async () => {
233
353
  const provider = workspaceProviderApiKeyConnectionSpec(workspaceProviderKind);
@@ -261,18 +381,26 @@ export function registerConnectionRoutes(app: Hono, deps: ApiRouteDeps): void {
261
381
  metadata,
262
382
  updatedBySubjectId: grant.subjectId,
263
383
  };
264
- const created =
265
- workspaceProviderKind === "vercel_gateway"
266
- ? await upsertWorkspaceVercelAiGatewayConnection(db, input)
267
- : await upsertWorkspaceOpenRouterConnection(db, input);
384
+ const created = await upsertWorkspaceProviderApiKeyConnection(
385
+ db,
386
+ workspaceProviderKind,
387
+ input,
388
+ );
268
389
  if (!created || created.status === "revoked") {
269
390
  throw new HTTPException(409, {
270
391
  message: `${provider.label} is already connected; reload before replacing its key`,
271
392
  });
272
393
  }
394
+ if (workspaceProviderKind === "anthropic")
395
+ await offerClaudeDefaultModels(deps, {
396
+ accountId: grant.accountId,
397
+ workspaceId,
398
+ actorSubjectId: grant.subjectId,
399
+ providerKind: "anthropic",
400
+ });
273
401
  return created;
274
402
  })()
275
- : await createConnection(db, {
403
+ : await createConnectionIdempotently(db, {
276
404
  accountId: grant.accountId,
277
405
  workspaceId,
278
406
  subjectId,
@@ -283,15 +411,56 @@ export function registerConnectionRoutes(app: Hono, deps: ApiRouteDeps): void {
283
411
  expiresAt: payload.expiresAt ? new Date(payload.expiresAt) : null,
284
412
  metadata: payload.metadata,
285
413
  createdBySubjectId: grant.subjectId,
414
+ ...(payload.operationId
415
+ ? {
416
+ operation: {
417
+ id: payload.operationId,
418
+ requestDigest: workspaceProviderCredentialRequestDigest(key, {
419
+ action: "create",
420
+ providerDomain,
421
+ kind: payload.kind,
422
+ subjectId,
423
+ credential: payload.credential,
424
+ grantedScopes: payload.grantedScopes,
425
+ expiresAt: payload.expiresAt
426
+ ? new Date(payload.expiresAt).toISOString()
427
+ : null,
428
+ metadata: payload.metadata,
429
+ }),
430
+ },
431
+ }
432
+ : {}),
433
+ }).catch((error: unknown) => {
434
+ if (error instanceof ConnectionCreateIdempotencyError) {
435
+ throw new HTTPException(409, { message: error.message });
436
+ }
437
+ throw error;
286
438
  });
287
439
  return c.json(ConnectionResponse.parse({ connection }), 201);
288
440
  };
289
- return beforeCommit
290
- ? withWorkspaceSubjectRls(db, workspaceId, grant.subjectId, async (tx) => {
291
- await beforeCommit(tx);
292
- return persist(tx);
293
- })
294
- : persist(db);
441
+ const commit = (database: Database) =>
442
+ beforeCommit
443
+ ? withWorkspaceSubjectRls(database, workspaceId, grant.subjectId, async (tx) => {
444
+ await beforeCommit(tx);
445
+ return persist(tx);
446
+ })
447
+ : persist(database);
448
+ // Only the canonical workspace model-key lane is outside integration policy.
449
+ // Generic credentials have no validated protocol/curated target in this contract.
450
+ const modelKey = workspaceProviderApiKeyConnectionKind({
451
+ subjectId: createConnectionSubjectId(payload, grant.subjectId),
452
+ providerDomain: canonicalProviderDomain(payload.providerDomain),
453
+ kind: payload.kind,
454
+ metadata: payload.metadata,
455
+ });
456
+ return modelKey || directModelKey
457
+ ? commit(db)
458
+ : withOrganizationIntegrationAcquisition(
459
+ db,
460
+ { accountId: grant.accountId, workspaceId },
461
+ [null],
462
+ commit,
463
+ );
295
464
  });
296
465
 
297
466
  app.post("/v1/workspaces/:workspaceId/connections/slack-bot/install", async (c) => {
@@ -299,15 +468,24 @@ export function registerConnectionRoutes(app: Hono, deps: ApiRouteDeps): void {
299
468
  const access = await requireAccessGrantAuthorization(c, deps, workspaceId, "connections:write");
300
469
  requireLegacyOAuthActor(access);
301
470
  const grant = access.grant;
302
- const payload = OpenGeniSlackBotInstallRequest.parse(await c.req.json());
471
+ const payload = await parseRequestJson(c, OpenGeniSlackBotInstallRequest);
303
472
  return c.json(
304
- await startSlackBotInstall(deps, {
305
- accountId: grant.accountId,
306
- workspaceId,
307
- subjectId: grant.subjectId,
308
- requestUrl: c.req.url,
309
- ...(payload.connectionId ? { connectionId: payload.connectionId } : {}),
310
- }),
473
+ await withOrganizationIntegrationAcquisition(
474
+ db,
475
+ { accountId: grant.accountId, workspaceId },
476
+ ["slack-bot"],
477
+ (tx) =>
478
+ startSlackBotInstall(
479
+ { ...deps, db: tx },
480
+ {
481
+ accountId: grant.accountId,
482
+ workspaceId,
483
+ subjectId: grant.subjectId,
484
+ requestUrl: c.req.url,
485
+ ...(payload.connectionId ? { connectionId: payload.connectionId } : {}),
486
+ },
487
+ ),
488
+ ),
311
489
  );
312
490
  });
313
491
 
@@ -329,12 +507,25 @@ export function registerConnectionRoutes(app: Hono, deps: ApiRouteDeps): void {
329
507
  operationId: `oauth:${state.nonce}`,
330
508
  inputDigest: createHash("sha256").update(c.req.query("state")!).digest("hex"),
331
509
  };
332
- const claim = await claimConnectOperation(db, state, {
333
- ...operation,
334
- expectedRevision: stored.attempt.revision,
335
- authorize: (tx, _attempt, origin) =>
336
- requireConnectOwnerAuthority(tx, state!, "connections:write", origin),
337
- });
510
+ const callbackState = state;
511
+ const callbackOperation = operation;
512
+ const claim = await withOrganizationIntegrationPolicyFence(
513
+ db,
514
+ callbackState,
515
+ (policyTx, policy) =>
516
+ claimConnectOperation(policyTx, callbackState, {
517
+ ...callbackOperation,
518
+ expectedRevision: stored.attempt.revision,
519
+ authorize: (tx, _attempt, origin) =>
520
+ requireConnectOwnerAuthority(tx, callbackState, "connections:write", origin),
521
+ authorizeAcquisition: async () => {
522
+ // Provider denial/cancellation acquires nothing; completed exact
523
+ // receipts replay before this callback is invoked.
524
+ if (!c.req.query("error") && c.req.query("code"))
525
+ assertOrganizationIntegrationAllowed(policy, "slack-bot");
526
+ },
527
+ }),
528
+ );
338
529
  if (claim.status === "replayed")
339
530
  return new Response(null, { status: 302, headers: { Location: exactReturnUrl } });
340
531
  }
@@ -391,6 +582,7 @@ export function registerConnectionRoutes(app: Hono, deps: ApiRouteDeps): void {
391
582
  "Slack installation callback is missing code",
392
583
  );
393
584
  }
585
+ await withOrganizationIntegrationAcquisition(db, state, ["slack-bot"], async () => {});
394
586
  const slack = requireOpenGeniSlackOAuthSettings(settings);
395
587
  const redirectUri = `${baseUrl}/v1/integrations/slack/callback`;
396
588
  const authorization = await exchangeOpenGeniSlackAuthorizationCode(
@@ -424,35 +616,44 @@ export function registerConnectionRoutes(app: Hono, deps: ApiRouteDeps): void {
424
616
  verified,
425
617
  });
426
618
  if (operation) {
427
- await finishConnectOperation(db, acceptedState, {
428
- ...operation,
429
- authorize: (tx, _attempt, origin) =>
430
- requireConnectOwnerAuthority(tx, acceptedState, "connections:write", origin),
431
- commit: async (tx, current) => {
432
- const connection = await persist(tx);
433
- return {
434
- ...current,
435
- revision: current.revision + 1,
436
- state: "complete",
437
- credentialsCommitted: true,
438
- nextAction: { type: "none" },
439
- account: {
440
- id: connection.id,
441
- version: connection.version,
442
- providerId: "slack-bot",
443
- label: "Slack workspace bot",
444
- ownership: "workspace",
445
- status: "connected",
446
- },
447
- };
448
- },
449
- });
619
+ const acceptedOperation = operation;
620
+ await withOrganizationIntegrationPolicyFence(db, acceptedState, (policyTx, policy) =>
621
+ finishConnectOperation(policyTx, acceptedState, {
622
+ ...acceptedOperation,
623
+ authorize: (tx, _attempt, origin) =>
624
+ requireConnectOwnerAuthority(tx, acceptedState, "connections:write", origin),
625
+ commit: async (tx, current) => {
626
+ assertOrganizationIntegrationAllowed(policy, "slack-bot");
627
+ const connection = await persist(tx);
628
+ return {
629
+ ...current,
630
+ revision: current.revision + 1,
631
+ state: "complete",
632
+ credentialsCommitted: true,
633
+ nextAction: { type: "none" },
634
+ account: {
635
+ id: connection.id,
636
+ version: connection.version,
637
+ providerId: "slack-bot",
638
+ label: "Slack workspace bot",
639
+ ownership: "workspace",
640
+ status: "connected",
641
+ },
642
+ };
643
+ },
644
+ }),
645
+ );
450
646
  return new Response(null, { status: 302, headers: { Location: exactReturnUrl! } });
451
647
  }
452
- const connection = await db.transaction(async (tx) => {
453
- await requireSlackInstallCallbackGrant(tx, acceptedState);
454
- return persist(tx);
455
- });
648
+ const connection = await withOrganizationIntegrationAcquisition(
649
+ db,
650
+ acceptedState,
651
+ ["slack-bot"],
652
+ async (tx) => {
653
+ await requireSlackInstallCallbackGrant(tx, acceptedState);
654
+ return persist(tx);
655
+ },
656
+ );
456
657
  return c.redirect(
457
658
  slackInstallReturnUrl(baseUrl, state.returnPath, "connected", connection.id),
458
659
  302,
@@ -480,7 +681,12 @@ export function registerConnectionRoutes(app: Hono, deps: ApiRouteDeps): void {
480
681
  }
481
682
  const reason = slackInstallErrorReason(error);
482
683
  return c.redirect(
483
- slackInstallReturnUrl(baseUrl, state?.returnPath ?? "/integrations", "error", reason),
684
+ slackInstallReturnUrl(
685
+ baseUrl,
686
+ state?.returnPath ?? oauthStateFailureReturn(settings, c.req.query("state")).returnPath,
687
+ "error",
688
+ reason,
689
+ ),
484
690
  302,
485
691
  );
486
692
  }
@@ -499,19 +705,24 @@ export function registerConnectionRoutes(app: Hono, deps: ApiRouteDeps): void {
499
705
  "connections:write",
500
706
  );
501
707
  const grant = authorization.grant;
502
- const payload = FikenInstallRequest.parse(await c.req.json());
708
+ const payload = await parseRequestJson(c, FikenInstallRequest);
503
709
  const persist = await prepareFikenTokenInstall(deps, grant, payload);
504
710
  const continuation = externalActorContinuationForAuthorization(authorization);
505
- const connection = await db.transaction(async (tx) => {
506
- await requireConnectOwnerAuthority(tx, {
507
- accountId: grant.accountId,
508
- workspaceId,
509
- subjectId: grant.subjectId,
510
- personalOwnerVerified: isPersonalConnectionOwnerPrincipal(authorization),
511
- ...(continuation ? { externalContinuation: continuation } : {}),
512
- });
513
- return persist(tx);
514
- });
711
+ const connection = await withOrganizationIntegrationAcquisition(
712
+ db,
713
+ { accountId: grant.accountId, workspaceId },
714
+ ["fiken"],
715
+ async (tx) => {
716
+ await requireConnectOwnerAuthority(tx, {
717
+ accountId: grant.accountId,
718
+ workspaceId,
719
+ subjectId: grant.subjectId,
720
+ personalOwnerVerified: isPersonalConnectionOwnerPrincipal(authorization),
721
+ ...(continuation ? { externalContinuation: continuation } : {}),
722
+ });
723
+ return persist(tx);
724
+ },
725
+ );
515
726
  return c.json(ConnectionResponse.parse({ connection }), payload.connectionId ? 200 : 201);
516
727
  });
517
728
 
@@ -523,7 +734,7 @@ export function registerConnectionRoutes(app: Hono, deps: ApiRouteDeps): void {
523
734
  const access = await requireAccessGrantAuthorization(c, deps, workspaceId, "connections:write");
524
735
  requireLegacyOAuthActor(access);
525
736
  const grant = access.grant;
526
- const payload = FikenOAuthStartRequest.parse(await c.req.json());
737
+ const payload = await parseRequestJson(c, FikenOAuthStartRequest);
527
738
  requireEnvironmentEncryption(settings);
528
739
  return c.json(
529
740
  FikenOAuthStartResponse.parse(
@@ -788,259 +999,356 @@ export function registerConnectionRoutes(app: Hono, deps: ApiRouteDeps): void {
788
999
  "connections:write",
789
1000
  );
790
1001
  const { grant } = authorization;
791
- const payload = UpdateConnectionRequest.parse(await c.req.json());
792
- assertNotReservedSlackBotMetadata(payload.metadata);
793
- assertNotReservedFikenMetadata(payload.metadata);
794
- assertNotReservedApiIntegrationOAuthMetadata(payload.metadata);
795
- assertNotReservedPersonalGitHubMetadata(payload.metadata);
796
- const existing = await getConnectionMetadata(
1002
+ const payload = await parseRequestJson(c, UpdateConnectionRequest);
1003
+ // Policy first, then the existing tenancy prefix and exact credential row.
1004
+ // The locked live identity makes unchanged/reducing updates safe against a
1005
+ // concurrent replacement; all final writes below use this same transaction.
1006
+ return withOrganizationIntegrationPolicyFence(
797
1007
  db,
798
- workspaceId,
799
- c.req.param("connectionId"),
800
- grant.subjectId,
801
- );
802
- if (existing && isOpenGeniSlackBotConnection(existing)) {
803
- const destination = ConnectorDocumentDestinationSelection.safeParse(
804
- payload.metadata?.documentDestination,
805
- );
806
- const destinationOnlyUpdate =
807
- payload.metadata !== undefined &&
808
- Object.keys(payload.metadata).length === 1 &&
809
- payload.providerDomain === undefined &&
810
- payload.subjectId === undefined &&
811
- payload.kind === undefined &&
812
- payload.status === undefined &&
813
- payload.credential === undefined &&
814
- payload.grantedScopes === undefined &&
815
- payload.expiresAt === undefined;
816
- if (!destination.success || !destinationOnlyUpdate) {
817
- throw new HTTPException(422, {
818
- message: "use the dedicated OpenGeni Slack bot reinstall flow to update this connection",
819
- });
820
- }
821
- const destinationSelection = destination.data;
822
- if (
823
- destinationSelection.authorityKind === "organization" &&
824
- authorization.accountGrant?.permissions.includes("account:admin") !== true
825
- ) {
826
- throw new HTTPException(403, { message: "missing permission: account:admin" });
827
- }
828
- if (
829
- destinationSelection.authorityKind === "workspace" &&
830
- !hasPermission(grant.permissions, "workspace:admin")
831
- ) {
832
- throw new HTTPException(403, { message: "missing permission: workspace:admin" });
833
- }
834
- if (
835
- destinationSelection.authorityKind === "personal" &&
836
- (!authorization.contextIntegrity ||
837
- authorization.authenticatedSubjectId !== grant.subjectId)
838
- ) {
839
- throw new HTTPException(403, {
840
- message: "personal destination requires the exact actor",
841
- });
842
- }
843
- const metadata = {
844
- ...existing.metadata,
845
- documentDestination: bindConnectorDocumentDestination(destinationSelection, {
846
- accountId: grant.accountId,
847
- workspaceId,
848
- initiatingSubjectId: grant.subjectId,
849
- }),
850
- };
851
- const connection = await updateSlackBotDocumentDestination(db, {
852
- accountId: grant.accountId,
853
- workspaceId,
854
- connectionId: existing.id,
855
- visibleToSubjectId: grant.subjectId,
856
- expectedVersion: existing.version,
857
- metadata,
858
- updatedBySubjectId: grant.subjectId,
859
- });
860
- if (!connection) {
861
- throw new HTTPException(409, {
862
- message: "Slack bot connection changed; reload before saving its destination",
863
- });
864
- }
865
- return c.json(ConnectionResponse.parse({ connection }));
866
- }
867
- if (existing && GoogleDriveConnectionMetadata.safeParse(existing.metadata).success) {
868
- throw new HTTPException(422, {
869
- message: "use the dedicated Google Drive reconnect or source-selection flow",
870
- });
871
- }
872
- if (existing?.metadata.credentialRole === API_INTEGRATION_OAUTH_CREDENTIAL_ROLE) {
873
- throw new HTTPException(422, {
874
- message: "use the dedicated Integration provider OAuth flow to update this connection",
875
- });
876
- }
877
- if (existing && AtlassianConnectionMetadata.safeParse(existing.metadata).success) {
878
- throw new HTTPException(422, {
879
- message: "use the dedicated Atlassian reconnect or source-selection flow",
880
- });
881
- }
882
- if (existing && isPersonalGitHubConnection(existing)) {
883
- throw new HTTPException(422, {
884
- message: "use the dedicated personal GitHub reconnect flow to update this connection",
885
- });
886
- }
887
- if (existing) {
888
- const providerDomain = canonicalProviderDomain(
889
- payload.providerDomain ?? existing.providerDomain,
890
- );
891
- const kind = payload.kind ?? existing.kind;
892
- const subjectId =
893
- payload.subjectId === undefined
894
- ? existing.subjectId
895
- : writableSubjectId(payload.subjectId, grant.subjectId);
896
- const targetWorkspaceProviderKind = workspaceProviderApiKeyConnectionKind({
897
- subjectId,
898
- providerDomain,
899
- kind,
900
- metadata: payload.metadata ?? existing.metadata,
901
- });
902
- if (!workspaceProviderApiKeyConnectionKind(existing) && targetWorkspaceProviderKind) {
903
- throw new HTTPException(422, {
904
- message:
905
- targetWorkspaceProviderKind === "vercel_gateway"
906
- ? "use the Vercel AI Gateway connect flow to create this connection"
907
- : "use the OpenRouter connect flow to create this connection",
908
- });
909
- }
910
- assertNotDirectPersonalSlackOAuth(providerDomain, kind);
911
- assertNotDirectGoogleDriveOAuth(providerDomain, kind, payload.metadata ?? existing.metadata);
912
- assertNotDirectAtlassianOAuth(providerDomain, kind, payload.metadata ?? existing.metadata);
913
- assertNotDirectPersonalGitHubOAuth(
914
- providerDomain,
915
- kind,
916
- payload.metadata ?? existing.metadata,
917
- );
918
- }
919
- // Status is not a free-form field: revocation goes through DELETE, and the
920
- // broker owns needs_reauth/error. Reactivating a connection is only
921
- // meaningful together with a fresh credential bundle — otherwise a PATCH
922
- // could clear the broker's re-auth signal while stale tokens stay in place.
923
- if (payload.status !== undefined) {
924
- if (payload.status !== "active") {
925
- throw new HTTPException(400, {
926
- message: 'status can only be set to "active"; use DELETE to revoke',
927
- });
928
- }
929
- if (payload.credential === undefined) {
930
- throw new HTTPException(400, {
931
- message: "reactivating a connection requires a new credential",
932
- });
933
- }
934
- }
935
- const existingWorkspaceProviderKind = existing
936
- ? workspaceProviderApiKeyConnectionKind(existing)
937
- : null;
938
- if (existing && existingWorkspaceProviderKind) {
939
- const provider = workspaceProviderApiKeyConnectionSpec(existingWorkspaceProviderKind);
940
- const providerDomain = canonicalProviderDomain(
941
- payload.providerDomain ?? existing.providerDomain,
942
- );
943
- const subjectId =
944
- payload.subjectId === undefined
945
- ? existing.subjectId
946
- : writableSubjectId(payload.subjectId, grant.subjectId);
947
- const kind = payload.kind ?? existing.kind;
948
- if (
949
- subjectId !== null ||
950
- providerDomain !== provider.providerDomain ||
951
- kind !== "api_key" ||
952
- (payload.metadata?.credentialRole !== undefined &&
953
- payload.metadata.credentialRole !== provider.credentialRole)
954
- ) {
955
- throw new HTTPException(422, {
956
- message: `${provider.label} connection identity cannot be changed`,
957
- });
958
- }
959
- if (payload.credential === undefined) {
960
- throw new HTTPException(400, {
961
- message: `updating a ${provider.label} connection requires a new credential`,
962
- });
963
- }
964
- if (payload.expectedVersion === undefined || payload.operationId === undefined) {
965
- throw new HTTPException(400, {
966
- message: `updating a ${provider.label} connection requires expectedVersion and operationId`,
967
- });
968
- }
969
- const key = requireEnvironmentEncryption(settings);
970
- const grantedScopes = payload.grantedScopes ?? existing.grantedScopes;
971
- const expiresAt =
972
- payload.expiresAt !== undefined
973
- ? payload.expiresAt
974
- ? new Date(payload.expiresAt)
975
- : null
976
- : existing.expiresAt
977
- ? new Date(existing.expiresAt)
1008
+ { accountId: grant.accountId, workspaceId },
1009
+ (policyTx, policy) =>
1010
+ // eslint-disable-next-line no-shadow -- every existing final writer must use this exact scoped transaction
1011
+ withWorkspaceSubjectRls(policyTx, workspaceId, grant.subjectId, async (db) => {
1012
+ const initial = await getConnectionMetadata(
1013
+ db,
1014
+ workspaceId,
1015
+ c.req.param("connectionId"),
1016
+ grant.subjectId,
1017
+ );
1018
+ if (
1019
+ initial?.metadata.directModelProvider !== undefined ||
1020
+ payload.metadata?.directModelProvider !== undefined ||
1021
+ String(payload.metadata?.credentialRole ?? "").startsWith("direct_")
1022
+ ) {
1023
+ throw new HTTPException(422, {
1024
+ message:
1025
+ "Disconnect and reconnect OpenAI or Azure OpenAI to replace its key or deployment",
1026
+ });
1027
+ }
1028
+ // Model-key rotation owns an advisory -> credential-row prefix. Do
1029
+ // not take its row first or change that excluded lane's lock order.
1030
+ if (!initial || !workspaceProviderApiKeyConnectionKind(initial)) {
1031
+ await db.execute(
1032
+ sql`select id from connections where workspace_id = ${workspaceId}::uuid and id = ${c.req.param("connectionId")}::uuid and (subject_id is null or subject_id = ${grant.subjectId}) for update`,
1033
+ );
1034
+ }
1035
+ assertNotReservedSlackBotMetadata(payload.metadata);
1036
+ assertNotReservedFikenMetadata(payload.metadata);
1037
+ assertNotReservedOnePasswordMetadata(payload.metadata);
1038
+ assertNotReservedApiIntegrationOAuthMetadata(payload.metadata);
1039
+ assertNotReservedPersonalGitHubMetadata(payload.metadata);
1040
+ const existing = await getConnectionMetadata(
1041
+ db,
1042
+ workspaceId,
1043
+ c.req.param("connectionId"),
1044
+ grant.subjectId,
1045
+ );
1046
+ if (existing && isOpenGeniSlackBotConnection(existing)) {
1047
+ const destination = ConnectorDocumentDestinationSelection.safeParse(
1048
+ payload.metadata?.documentDestination,
1049
+ );
1050
+ const destinationOnlyUpdate =
1051
+ payload.metadata !== undefined &&
1052
+ Object.keys(payload.metadata).length === 1 &&
1053
+ payload.providerDomain === undefined &&
1054
+ payload.subjectId === undefined &&
1055
+ payload.kind === undefined &&
1056
+ payload.status === undefined &&
1057
+ payload.credential === undefined &&
1058
+ payload.grantedScopes === undefined &&
1059
+ payload.expiresAt === undefined;
1060
+ if (!destination.success || !destinationOnlyUpdate) {
1061
+ throw new HTTPException(422, {
1062
+ message:
1063
+ "use the dedicated Opengeni Slack bot reinstall flow to update this connection",
1064
+ });
1065
+ }
1066
+ const destinationSelection = destination.data;
1067
+ if (
1068
+ destinationSelection.authorityKind === "organization" &&
1069
+ authorization.accountGrant?.permissions.includes("account:admin") !== true
1070
+ ) {
1071
+ throw new HTTPException(403, { message: "missing permission: account:admin" });
1072
+ }
1073
+ if (
1074
+ destinationSelection.authorityKind === "workspace" &&
1075
+ !hasPermission(grant.permissions, "workspace:admin")
1076
+ ) {
1077
+ throw new HTTPException(403, { message: "missing permission: workspace:admin" });
1078
+ }
1079
+ if (
1080
+ destinationSelection.authorityKind === "personal" &&
1081
+ (!authorization.contextIntegrity ||
1082
+ authorization.authenticatedSubjectId !== grant.subjectId)
1083
+ ) {
1084
+ throw new HTTPException(403, {
1085
+ message: "personal destination requires the exact actor",
1086
+ });
1087
+ }
1088
+ const metadata = {
1089
+ ...existing.metadata,
1090
+ documentDestination: bindConnectorDocumentDestination(destinationSelection, {
1091
+ accountId: grant.accountId,
1092
+ workspaceId,
1093
+ initiatingSubjectId: grant.subjectId,
1094
+ }),
1095
+ };
1096
+ if (
1097
+ stableJson(metadata.documentDestination) !==
1098
+ stableJson(existing.metadata.documentDestination)
1099
+ ) {
1100
+ assertOrganizationIntegrationAllowed(policy, "slack-bot");
1101
+ }
1102
+ const connection = await updateSlackBotDocumentDestination(db, {
1103
+ accountId: grant.accountId,
1104
+ workspaceId,
1105
+ connectionId: existing.id,
1106
+ visibleToSubjectId: grant.subjectId,
1107
+ expectedVersion: existing.version,
1108
+ metadata,
1109
+ updatedBySubjectId: grant.subjectId,
1110
+ });
1111
+ if (!connection) {
1112
+ throw new HTTPException(409, {
1113
+ message: "Slack bot connection changed; reload before saving its destination",
1114
+ });
1115
+ }
1116
+ return c.json(ConnectionResponse.parse({ connection }));
1117
+ }
1118
+ if (existing && GoogleDriveConnectionMetadata.safeParse(existing.metadata).success) {
1119
+ throw new HTTPException(422, {
1120
+ message: "use the dedicated Google Drive reconnect or source-selection flow",
1121
+ });
1122
+ }
1123
+ if (existing?.metadata.credentialRole === API_INTEGRATION_OAUTH_CREDENTIAL_ROLE) {
1124
+ throw new HTTPException(422, {
1125
+ message:
1126
+ "use the dedicated Integration provider OAuth flow to update this connection",
1127
+ });
1128
+ }
1129
+ if (existing && AtlassianConnectionMetadata.safeParse(existing.metadata).success) {
1130
+ throw new HTTPException(422, {
1131
+ message: "use the dedicated Atlassian reconnect or source-selection flow",
1132
+ });
1133
+ }
1134
+ if (existing && isPersonalGitHubConnection(existing)) {
1135
+ throw new HTTPException(422, {
1136
+ message: "use the dedicated personal GitHub reconnect flow to update this connection",
1137
+ });
1138
+ }
1139
+ if (existing) {
1140
+ const providerDomain = canonicalProviderDomain(
1141
+ payload.providerDomain ?? existing.providerDomain,
1142
+ );
1143
+ const kind = payload.kind ?? existing.kind;
1144
+ const subjectId =
1145
+ payload.subjectId === undefined
1146
+ ? existing.subjectId
1147
+ : writableSubjectId(payload.subjectId, grant.subjectId);
1148
+ const targetWorkspaceProviderKind = workspaceProviderApiKeyConnectionKind({
1149
+ subjectId,
1150
+ providerDomain,
1151
+ kind,
1152
+ metadata: payload.metadata ?? existing.metadata,
1153
+ });
1154
+ if (!workspaceProviderApiKeyConnectionKind(existing) && targetWorkspaceProviderKind) {
1155
+ throw new HTTPException(422, {
1156
+ message:
1157
+ targetWorkspaceProviderKind === "vercel_gateway"
1158
+ ? "use the Vercel AI Gateway connect flow to create this connection"
1159
+ : targetWorkspaceProviderKind === "opper"
1160
+ ? "use the Opper connect flow to create this connection"
1161
+ : "use the OpenRouter connect flow to create this connection",
1162
+ });
1163
+ }
1164
+ assertNotDirectPersonalSlackOAuth(providerDomain, kind);
1165
+ assertNotDirectGoogleDriveOAuth(
1166
+ providerDomain,
1167
+ kind,
1168
+ payload.metadata ?? existing.metadata,
1169
+ );
1170
+ assertNotDirectAtlassianOAuth(
1171
+ providerDomain,
1172
+ kind,
1173
+ payload.metadata ?? existing.metadata,
1174
+ );
1175
+ assertNotDirectPersonalGitHubOAuth(
1176
+ providerDomain,
1177
+ kind,
1178
+ payload.metadata ?? existing.metadata,
1179
+ );
1180
+ }
1181
+ // Status is not a free-form field: revocation goes through DELETE, and the
1182
+ // broker owns needs_reauth/error. Reactivating a connection is only
1183
+ // meaningful together with a fresh credential bundle — otherwise a PATCH
1184
+ // could clear the broker's re-auth signal while stale tokens stay in place.
1185
+ if (payload.status !== undefined) {
1186
+ if (payload.status !== "active") {
1187
+ throw new HTTPException(400, {
1188
+ message: 'status can only be set to "active"; use DELETE to revoke',
1189
+ });
1190
+ }
1191
+ if (payload.credential === undefined) {
1192
+ throw new HTTPException(400, {
1193
+ message: "reactivating a connection requires a new credential",
1194
+ });
1195
+ }
1196
+ }
1197
+ const existingWorkspaceProviderKind = existing
1198
+ ? workspaceProviderApiKeyConnectionKind(existing)
978
1199
  : null;
979
- const metadata = workspaceProviderCredentialMetadata(existingWorkspaceProviderKind, {
980
- ...existing.metadata,
981
- ...(payload.metadata ?? {}),
982
- });
983
- const input = {
984
- accountId: grant.accountId,
985
- workspaceId,
986
- connectionId: existing.id,
987
- expectedVersion: payload.expectedVersion,
988
- operationId: payload.operationId,
989
- requestDigest: workspaceProviderCredentialRequestDigest(key, {
990
- action: "rotate",
991
- connectionId: existing.id,
992
- expectedVersion: payload.expectedVersion,
993
- credential: payload.credential,
994
- grantedScopes,
995
- expiresAt: expiresAt?.toISOString() ?? null,
996
- metadata,
1200
+ if (existing && existingWorkspaceProviderKind) {
1201
+ const provider = workspaceProviderApiKeyConnectionSpec(existingWorkspaceProviderKind);
1202
+ const providerDomain = canonicalProviderDomain(
1203
+ payload.providerDomain ?? existing.providerDomain,
1204
+ );
1205
+ const subjectId =
1206
+ payload.subjectId === undefined
1207
+ ? existing.subjectId
1208
+ : writableSubjectId(payload.subjectId, grant.subjectId);
1209
+ const kind = payload.kind ?? existing.kind;
1210
+ if (
1211
+ subjectId !== null ||
1212
+ providerDomain !== provider.providerDomain ||
1213
+ kind !== "api_key" ||
1214
+ (payload.metadata?.credentialRole !== undefined &&
1215
+ payload.metadata.credentialRole !== provider.credentialRole)
1216
+ ) {
1217
+ throw new HTTPException(422, {
1218
+ message: `${provider.label} connection identity cannot be changed`,
1219
+ });
1220
+ }
1221
+ if (payload.credential === undefined) {
1222
+ throw new HTTPException(400, {
1223
+ message: `updating a ${provider.label} connection requires a new credential`,
1224
+ });
1225
+ }
1226
+ if (payload.expectedVersion === undefined || payload.operationId === undefined) {
1227
+ throw new HTTPException(400, {
1228
+ message: `updating a ${provider.label} connection requires expectedVersion and operationId`,
1229
+ });
1230
+ }
1231
+ payload.credential = prepareClaudeWorkspaceCredential(
1232
+ settings,
1233
+ workspaceId,
1234
+ existing.metadata,
1235
+ payload.credential,
1236
+ );
1237
+ assertClaudeWorkspaceCredential(settings, {
1238
+ subjectId,
1239
+ providerDomain,
1240
+ kind,
1241
+ metadata: existing.metadata,
1242
+ credential: payload.credential,
1243
+ });
1244
+ if (existingWorkspaceProviderKind === "opper")
1245
+ assertOpperCredential(payload.credential);
1246
+ const key = requireEnvironmentEncryption(settings);
1247
+ const grantedScopes = payload.grantedScopes ?? existing.grantedScopes;
1248
+ const expiresAt =
1249
+ payload.expiresAt !== undefined
1250
+ ? payload.expiresAt
1251
+ ? new Date(payload.expiresAt)
1252
+ : null
1253
+ : existing.expiresAt
1254
+ ? new Date(existing.expiresAt)
1255
+ : null;
1256
+ const metadata = workspaceProviderCredentialMetadata(existingWorkspaceProviderKind, {
1257
+ ...existing.metadata,
1258
+ ...(payload.metadata ?? {}),
1259
+ });
1260
+ const input = {
1261
+ accountId: grant.accountId,
1262
+ workspaceId,
1263
+ connectionId: existing.id,
1264
+ expectedVersion: payload.expectedVersion,
1265
+ operationId: payload.operationId,
1266
+ requestDigest: workspaceProviderCredentialRequestDigest(key, {
1267
+ action: "rotate",
1268
+ connectionId: existing.id,
1269
+ expectedVersion: payload.expectedVersion,
1270
+ credential: payload.credential,
1271
+ grantedScopes,
1272
+ expiresAt: expiresAt?.toISOString() ?? null,
1273
+ metadata,
1274
+ }),
1275
+ credentialEncrypted: encryptCredentialBundle(key, payload.credential),
1276
+ grantedScopes,
1277
+ expiresAt,
1278
+ metadata,
1279
+ updatedBySubjectId: grant.subjectId,
1280
+ };
1281
+ const connection = await rotateWorkspaceProviderApiKeyConnection(
1282
+ db,
1283
+ existingWorkspaceProviderKind,
1284
+ input,
1285
+ );
1286
+ if (!connection) {
1287
+ throw new HTTPException(409, {
1288
+ message: `${provider.label} connection changed; reload before replacing its key`,
1289
+ });
1290
+ }
1291
+ return c.json(ConnectionResponse.parse({ connection }));
1292
+ }
1293
+ const key =
1294
+ payload.credential === undefined ? null : requireEnvironmentEncryption(settings);
1295
+ const subjectId =
1296
+ payload.subjectId === undefined
1297
+ ? undefined
1298
+ : writableSubjectId(payload.subjectId, grant.subjectId);
1299
+ if (
1300
+ existing &&
1301
+ (payload.credential !== undefined ||
1302
+ (payload.providerDomain !== undefined &&
1303
+ canonicalProviderDomain(payload.providerDomain) !== existing.providerDomain) ||
1304
+ (payload.kind !== undefined && payload.kind !== existing.kind) ||
1305
+ (subjectId !== undefined && subjectId !== existing.subjectId) ||
1306
+ payload.grantedScopes?.some((scope) => !existing.grantedScopes.includes(scope)) ||
1307
+ (payload.metadata !== undefined &&
1308
+ ["mcpUrl", "resource", "credentialRole"].some(
1309
+ (field) =>
1310
+ stableJson(payload.metadata?.[field] ?? null) !==
1311
+ stableJson(existing.metadata[field] ?? null),
1312
+ )) ||
1313
+ (payload.expiresAt !== undefined &&
1314
+ existing.expiresAt !== null &&
1315
+ (payload.expiresAt === null ||
1316
+ Date.parse(payload.expiresAt) > Date.parse(existing.expiresAt))) ||
1317
+ (payload.status === "active" && existing.status !== "active"))
1318
+ ) {
1319
+ assertOrganizationIntegrationAllowed(policy, null);
1320
+ }
1321
+ if (payload.credential !== undefined) {
1322
+ assertBrokeredApiKeyCredential(payload.kind ?? existing?.kind, payload.credential);
1323
+ }
1324
+ const connection = await updateConnection(db, {
1325
+ workspaceId,
1326
+ connectionId: c.req.param("connectionId"),
1327
+ visibleToSubjectId: grant.subjectId,
1328
+ updatedBySubjectId: grant.subjectId,
1329
+ ...(payload.providerDomain !== undefined
1330
+ ? { providerDomain: canonicalProviderDomain(payload.providerDomain) }
1331
+ : {}),
1332
+ ...(subjectId !== undefined ? { subjectId } : {}),
1333
+ ...(payload.kind !== undefined ? { kind: payload.kind } : {}),
1334
+ ...(payload.status !== undefined ? { status: payload.status } : {}),
1335
+ ...(payload.credential !== undefined && key
1336
+ ? { credentialEncrypted: encryptCredentialBundle(key, payload.credential) }
1337
+ : {}),
1338
+ ...(payload.grantedScopes !== undefined
1339
+ ? { grantedScopes: payload.grantedScopes }
1340
+ : {}),
1341
+ ...(payload.expiresAt !== undefined
1342
+ ? { expiresAt: payload.expiresAt ? new Date(payload.expiresAt) : null }
1343
+ : {}),
1344
+ ...(payload.metadata !== undefined ? { metadata: payload.metadata } : {}),
1345
+ });
1346
+ if (!connection) {
1347
+ throw new HTTPException(404, { message: "connection not found" });
1348
+ }
1349
+ return c.json(ConnectionResponse.parse({ connection }));
997
1350
  }),
998
- credentialEncrypted: encryptCredentialBundle(key, payload.credential),
999
- grantedScopes,
1000
- expiresAt,
1001
- metadata,
1002
- updatedBySubjectId: grant.subjectId,
1003
- };
1004
- const connection =
1005
- existingWorkspaceProviderKind === "vercel_gateway"
1006
- ? await rotateWorkspaceVercelAiGatewayConnection(db, input)
1007
- : await rotateWorkspaceOpenRouterConnection(db, input);
1008
- if (!connection) {
1009
- throw new HTTPException(409, {
1010
- message: `${provider.label} connection changed; reload before replacing its key`,
1011
- });
1012
- }
1013
- return c.json(ConnectionResponse.parse({ connection }));
1014
- }
1015
- const key = payload.credential === undefined ? null : requireEnvironmentEncryption(settings);
1016
- const subjectId =
1017
- payload.subjectId === undefined
1018
- ? undefined
1019
- : writableSubjectId(payload.subjectId, grant.subjectId);
1020
- const connection = await updateConnection(db, {
1021
- workspaceId,
1022
- connectionId: c.req.param("connectionId"),
1023
- visibleToSubjectId: grant.subjectId,
1024
- updatedBySubjectId: grant.subjectId,
1025
- ...(payload.providerDomain !== undefined
1026
- ? { providerDomain: canonicalProviderDomain(payload.providerDomain) }
1027
- : {}),
1028
- ...(subjectId !== undefined ? { subjectId } : {}),
1029
- ...(payload.kind !== undefined ? { kind: payload.kind } : {}),
1030
- ...(payload.status !== undefined ? { status: payload.status } : {}),
1031
- ...(payload.credential !== undefined && key
1032
- ? { credentialEncrypted: encryptCredentialBundle(key, payload.credential) }
1033
- : {}),
1034
- ...(payload.grantedScopes !== undefined ? { grantedScopes: payload.grantedScopes } : {}),
1035
- ...(payload.expiresAt !== undefined
1036
- ? { expiresAt: payload.expiresAt ? new Date(payload.expiresAt) : null }
1037
- : {}),
1038
- ...(payload.metadata !== undefined ? { metadata: payload.metadata } : {}),
1039
- });
1040
- if (!connection) {
1041
- throw new HTTPException(404, { message: "connection not found" });
1042
- }
1043
- return c.json(ConnectionResponse.parse({ connection }));
1351
+ );
1044
1352
  });
1045
1353
 
1046
1354
  app.delete("/v1/workspaces/:workspaceId/connections/:connectionId", async (c) => {
@@ -1154,21 +1462,13 @@ export function registerConnectionRoutes(app: Hono, deps: ApiRouteDeps): void {
1154
1462
  slackTeamId: openGeniSlackBotMetadata(existing.metadata)!.slackTeamId,
1155
1463
  })
1156
1464
  : workspaceProviderKind
1157
- ? workspaceProviderKind === "vercel_gateway"
1158
- ? await revokeWorkspaceVercelAiGatewayConnections(db, {
1159
- accountId: grant.accountId,
1160
- workspaceId,
1161
- connectionId,
1162
- expectedVersion: existing.version,
1163
- updatedBySubjectId: grant.subjectId,
1164
- })
1165
- : await revokeWorkspaceOpenRouterConnections(db, {
1166
- accountId: grant.accountId,
1167
- workspaceId,
1168
- connectionId,
1169
- expectedVersion: existing.version,
1170
- updatedBySubjectId: grant.subjectId,
1171
- })
1465
+ ? await revokeWorkspaceProviderApiKeyConnections(db, workspaceProviderKind, {
1466
+ accountId: grant.accountId,
1467
+ workspaceId,
1468
+ connectionId,
1469
+ expectedVersion: existing.version,
1470
+ updatedBySubjectId: grant.subjectId,
1471
+ })
1172
1472
  : await revokeConnection(
1173
1473
  db,
1174
1474
  workspaceId,
@@ -1186,6 +1486,7 @@ export function registerConnectionRoutes(app: Hono, deps: ApiRouteDeps): void {
1186
1486
  assertIntegrationsEnabled();
1187
1487
  const workspaceId = c.req.param("workspaceId");
1188
1488
  const access = await requireAccessGrantAuthorization(c, deps, workspaceId, "connections:write");
1489
+ requireProviderConsentInBrowser(access);
1189
1490
  const grant = access.grant;
1190
1491
  const parsed = OAuthStartRequest.safeParse(await c.req.json());
1191
1492
  if (!parsed.success) {
@@ -1215,6 +1516,7 @@ export function registerConnectionRoutes(app: Hono, deps: ApiRouteDeps): void {
1215
1516
  assertIntegrationsEnabled();
1216
1517
  const workspaceId = c.req.param("workspaceId");
1217
1518
  const access = await requireAccessGrantAuthorization(c, deps, workspaceId, "connections:write");
1519
+ requireProviderConsentInBrowser(access);
1218
1520
  const grant = access.grant;
1219
1521
  const parsed = ApiIntegrationOAuthStartRequest.safeParse(await c.req.json());
1220
1522
  if (!parsed.success) {
@@ -1280,7 +1582,7 @@ export function registerConnectionRoutes(app: Hono, deps: ApiRouteDeps): void {
1280
1582
  return c.json(
1281
1583
  IntegrationClientMetadata.parse({
1282
1584
  client_id: metadataUrl,
1283
- client_name: "OpenGeni",
1585
+ client_name: "Opengeni",
1284
1586
  redirect_uris: [`${baseUrl}/v1/integrations/oauth/callback`],
1285
1587
  token_endpoint_auth_method: "none",
1286
1588
  grant_types: ["authorization_code", "refresh_token"],
@@ -1319,7 +1621,7 @@ async function persistOpenGeniSlackBotConnection(input: {
1319
1621
  throw new SlackInstallCallbackError(
1320
1622
  422,
1321
1623
  "connection_conflict",
1322
- "connectionId is not an OpenGeni Slack bot connection",
1624
+ "connectionId is not an Opengeni Slack bot connection",
1323
1625
  "principal_validation",
1324
1626
  );
1325
1627
  }
@@ -1598,28 +1900,6 @@ function assertNotReservedPersonalGitHubMetadata(
1598
1900
  }
1599
1901
  }
1600
1902
 
1601
- type WorkspaceProviderApiKeyConnectionKind = "vercel_gateway" | "openrouter";
1602
-
1603
- function workspaceProviderApiKeyConnectionSpec(
1604
- providerKind: WorkspaceProviderApiKeyConnectionKind,
1605
- ): {
1606
- providerDomain: string;
1607
- credentialRole: string;
1608
- label: string;
1609
- } {
1610
- return providerKind === "vercel_gateway"
1611
- ? {
1612
- providerDomain: VERCEL_AI_GATEWAY_CONNECTION_DOMAIN,
1613
- credentialRole: VERCEL_AI_GATEWAY_CONNECTION_ROLE,
1614
- label: "Vercel AI Gateway",
1615
- }
1616
- : {
1617
- providerDomain: WORKSPACE_OPENROUTER_CONNECTION_DOMAIN,
1618
- credentialRole: WORKSPACE_OPENROUTER_CONNECTION_ROLE,
1619
- label: "OpenRouter",
1620
- };
1621
- }
1622
-
1623
1903
  function workspaceProviderApiKeyConnectionKind(input: {
1624
1904
  subjectId?: string | null;
1625
1905
  providerDomain: string;
@@ -1628,6 +1908,12 @@ function workspaceProviderApiKeyConnectionKind(input: {
1628
1908
  }): WorkspaceProviderApiKeyConnectionKind | null {
1629
1909
  if (input.subjectId != null || input.kind !== "api_key") return null;
1630
1910
  const providerDomain = input.providerDomain.toLowerCase();
1911
+ if (
1912
+ providerDomain === "api.anthropic.com" &&
1913
+ (input.metadata?.credentialRole === "anthropic" ||
1914
+ input.metadata?.credentialRole === "claude_subscription")
1915
+ )
1916
+ return input.metadata.credentialRole;
1631
1917
  if (
1632
1918
  providerDomain === VERCEL_AI_GATEWAY_CONNECTION_DOMAIN &&
1633
1919
  input.metadata?.credentialRole === VERCEL_AI_GATEWAY_CONNECTION_ROLE
@@ -1640,13 +1926,40 @@ function workspaceProviderApiKeyConnectionKind(input: {
1640
1926
  ) {
1641
1927
  return "openrouter";
1642
1928
  }
1929
+ if (
1930
+ providerDomain === WORKSPACE_OPPER_CONNECTION_DOMAIN &&
1931
+ input.metadata?.credentialRole === WORKSPACE_OPPER_CONNECTION_ROLE
1932
+ ) {
1933
+ return "opper";
1934
+ }
1643
1935
  return null;
1644
1936
  }
1645
1937
 
1938
+ /** Opper management keys are refused by inference routes; never store one as a model key. */
1939
+ function assertOpperCredential(credential: Record<string, unknown>): void {
1940
+ const apiKey = typeof credential.apiKey === "string" ? credential.apiKey : "";
1941
+ const problem = opperCredentialProblem(apiKey);
1942
+ if (problem) throw new HTTPException(422, { message: problem });
1943
+ }
1944
+
1945
+ /**
1946
+ * A brokered api_key Connection is usable only through `headers` or
1947
+ * `placements`; anything else was previously accepted and then failed every
1948
+ * tool call with a misleading "connect an account" auth-needed notice.
1949
+ */
1950
+ function assertBrokeredApiKeyCredential(
1951
+ kind: string | undefined,
1952
+ credential: Record<string, unknown>,
1953
+ ): void {
1954
+ if (kind !== "api_key") return;
1955
+ const problem = brokeredCredentialBundleProblem(credential);
1956
+ if (problem) throw new HTTPException(422, { message: problem });
1957
+ }
1958
+
1646
1959
  function assertNotReservedSlackBotMetadata(metadata: Record<string, unknown> | undefined): void {
1647
1960
  if (hasReservedOpenGeniSlackBotMetadata(metadata)) {
1648
1961
  throw new HTTPException(422, {
1649
- message: "OpenGeni Slack bot metadata is reserved for the dedicated connection flow",
1962
+ message: "Opengeni Slack bot metadata is reserved for the dedicated connection flow",
1650
1963
  });
1651
1964
  }
1652
1965
  }
@@ -1659,6 +1972,14 @@ function assertNotReservedFikenMetadata(metadata: Record<string, unknown> | unde
1659
1972
  }
1660
1973
  }
1661
1974
 
1975
+ function assertNotReservedOnePasswordMetadata(metadata: Record<string, unknown> | undefined): void {
1976
+ if (metadata?.credentialRole === ONEPASSWORD_CREDENTIAL_ROLE) {
1977
+ throw new HTTPException(422, {
1978
+ message: "1Password connections are created by the verified 1Password install flow",
1979
+ });
1980
+ }
1981
+ }
1982
+
1662
1983
  function assertNotReservedApiIntegrationOAuthMetadata(
1663
1984
  metadata: Record<string, unknown> | undefined,
1664
1985
  ): void {
@@ -1711,8 +2032,14 @@ function workspaceProviderCredentialMetadata(
1711
2032
  [OPENROUTER_CREDENTIAL_OPERATION_DIGEST_METADATA_KEY]: _openRouterOperationDigest,
1712
2033
  [VERCEL_AI_GATEWAY_CREDENTIAL_OPERATION_ID_METADATA_KEY]: _operationId,
1713
2034
  [VERCEL_AI_GATEWAY_CREDENTIAL_OPERATION_DIGEST_METADATA_KEY]: _operationDigest,
2035
+ [OPPER_CREDENTIAL_OPERATION_ID_METADATA_KEY]: _opperOperationId,
2036
+ [OPPER_CREDENTIAL_OPERATION_DIGEST_METADATA_KEY]: _opperOperationDigest,
1714
2037
  ...effectiveMetadata
1715
2038
  } = metadata ?? {};
2039
+ for (const role of ["anthropic", "claude_subscription"]) {
2040
+ delete effectiveMetadata[`${role}CredentialOperationId`];
2041
+ delete effectiveMetadata[`${role}CredentialOperationDigest`];
2042
+ }
1716
2043
  return {
1717
2044
  ...effectiveMetadata,
1718
2045
  credentialRole: workspaceProviderApiKeyConnectionSpec(providerKind).credentialRole,