@opengeni/api-router 0.30.3 → 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 (434) 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-source-session.d.ts +3 -0
  35. package/dist/editable-artifact-websocket.d.ts +10 -18
  36. package/dist/editable-artifact-workspace-files.d.ts +1 -1
  37. package/dist/fatal-process-boundary.d.ts +25 -0
  38. package/dist/github-access.d.ts +31 -2
  39. package/dist/http/acting-person.d.ts +24 -0
  40. package/dist/http/api-contract-compat.d.ts +19 -0
  41. package/dist/http/api-error.d.ts +27 -1
  42. package/dist/http/cors.d.ts +17 -0
  43. package/dist/http/deprecation.d.ts +42 -0
  44. package/dist/http/interaction-control-error.d.ts +13 -0
  45. package/dist/http/keyed-admission.d.ts +12 -0
  46. package/dist/http/local-browser-boundary.d.ts +72 -0
  47. package/dist/http/metrics-listener.d.ts +22 -0
  48. package/dist/http/path-identifier.d.ts +17 -0
  49. package/dist/http/registered-route-label.d.ts +16 -0
  50. package/dist/http/request-body.d.ts +27 -0
  51. package/dist/http/request-source.d.ts +52 -0
  52. package/dist/http/sse.d.ts +17 -6
  53. package/dist/http/unmatched-route.d.ts +18 -0
  54. package/dist/http/user-content.d.ts +36 -0
  55. package/dist/http/websocket-upgrade-dispatch.d.ts +18 -0
  56. package/dist/index.d.ts +10 -3
  57. package/dist/index.js +1091 -162
  58. package/dist/index.js.map +1 -1
  59. package/dist/integration-endpoint-test.d.ts +20 -0
  60. package/dist/integrations/api-integrations.d.ts +2 -2
  61. package/dist/integrations/atlassian.d.ts +29 -3
  62. package/dist/integrations/connect-authority.d.ts +1 -0
  63. package/dist/integrations/connect-callback-return.d.ts +6 -0
  64. package/dist/integrations/fiken.d.ts +16 -2
  65. package/dist/integrations/github-app-connect.d.ts +47 -0
  66. package/dist/integrations/github-installation-proof.d.ts +3 -0
  67. package/dist/integrations/github-lens-connect.d.ts +116 -0
  68. package/dist/integrations/github-skill-source.d.ts +1 -5
  69. package/dist/integrations/google-drive.d.ts +24 -0
  70. package/dist/integrations/integration-commit-authority.d.ts +13 -0
  71. package/dist/integrations/marketplace-plugin.d.ts +2691 -0
  72. package/dist/integrations/oauth-client.d.ts +178 -18
  73. package/dist/integrations/oauth-profiles.d.ts +222 -0
  74. package/dist/integrations/oauth-return-path.d.ts +1 -0
  75. package/dist/integrations/personal-github-repositories.d.ts +84 -0
  76. package/dist/integrations/personal-github.d.ts +42 -0
  77. package/dist/integrations/pr-review-provider.d.ts +22 -0
  78. package/dist/integrations/provider-oauth.d.ts +19 -1
  79. package/dist/integrations/slack-app-home.d.ts +24 -0
  80. package/dist/integrations/slack-bot.d.ts +367 -12
  81. package/dist/integrations/slack-file-upload-flow.d.ts +62 -0
  82. package/dist/integrations/slack-install.d.ts +20 -0
  83. package/dist/integrations/slack-interactions.d.ts +107 -9
  84. package/dist/integrations/slack-mrkdwn.d.ts +41 -0
  85. package/dist/integrations/slack-routing.d.ts +120 -0
  86. package/dist/integrations/slack-session-defaults.d.ts +58 -0
  87. package/dist/integrations/slack-task-file-upload.d.ts +36 -0
  88. package/dist/integrations/social-oauth.d.ts +7 -0
  89. package/dist/interaction-agent-access.d.ts +15 -0
  90. package/dist/interaction-frame-proxy.d.ts +68 -0
  91. package/dist/interaction-metrics.d.ts +2 -0
  92. package/dist/mcp/action-catalog-types.d.ts +9 -0
  93. package/dist/mcp/action-catalog.gen.d.ts +2 -0
  94. package/dist/mcp/capability-account-readiness.d.ts +7 -0
  95. package/dist/mcp/child-read-evidence.d.ts +9 -0
  96. package/dist/mcp/command-read.d.ts +24 -0
  97. package/dist/mcp/company-profile-agent-admin.d.ts +44 -0
  98. package/dist/mcp/contract-input.d.ts +11 -0
  99. package/dist/mcp/documents.d.ts +7 -5
  100. package/dist/mcp/files.d.ts +2 -2
  101. package/dist/mcp/first-party-tool-permissions.d.ts +650 -0
  102. package/dist/mcp/knowledge-entries.d.ts +5 -0
  103. package/dist/mcp/orchestration-failure-diagnostic.d.ts +43 -0
  104. package/dist/mcp/receipts.d.ts +9 -0
  105. package/dist/mcp/request-abort.d.ts +19 -0
  106. package/dist/mcp/scheduled-task-input.d.ts +1189 -0
  107. package/dist/mcp/scheduled-task-view.d.ts +22 -4
  108. package/dist/mcp/server.d.ts +255 -3
  109. package/dist/mcp/session-event-view.d.ts +94 -0
  110. package/dist/mcp/session-view.d.ts +66 -2
  111. package/dist/mcp/session-wait.d.ts +174 -0
  112. package/dist/mcp-oauth.d.ts +52 -0
  113. package/dist/model-catalog.d.ts +11 -31
  114. package/dist/observability.d.ts +3 -1
  115. package/dist/organization-mcp.d.ts +52 -0
  116. package/dist/organization-recovery-notifications.d.ts +41 -0
  117. package/dist/routes/analytics-consent.d.ts +26 -0
  118. package/dist/routes/api-integrations.d.ts +25 -0
  119. package/dist/routes/api-keys.d.ts +17 -0
  120. package/dist/routes/artifact-catalog.d.ts +6 -0
  121. package/dist/routes/automations.d.ts +19 -0
  122. package/dist/routes/billing.d.ts +49 -0
  123. package/dist/routes/browser-sessions.d.ts +59 -2
  124. package/dist/routes/claude-subscription-accounts.d.ts +3 -0
  125. package/dist/routes/claude-subscription-oauth.d.ts +3 -0
  126. package/dist/routes/client-errors.d.ts +146 -0
  127. package/dist/routes/codex.d.ts +76 -2
  128. package/dist/routes/company-brain.d.ts +3 -0
  129. package/dist/routes/company-profile.d.ts +2 -1
  130. package/dist/routes/computer-sessions.d.ts +14 -0
  131. package/dist/routes/connect.d.ts +5 -0
  132. package/dist/routes/editable-artifacts.d.ts +2 -0
  133. package/dist/routes/external-identity-links.d.ts +6 -0
  134. package/dist/routes/feedback.d.ts +3 -0
  135. package/dist/routes/github-browser-pages.d.ts +12 -0
  136. package/dist/routes/github.d.ts +2 -0
  137. package/dist/routes/insights-response-cache.d.ts +10 -0
  138. package/dist/routes/insights-usage.d.ts +12 -0
  139. package/dist/routes/insights.d.ts +30 -1
  140. package/dist/routes/knowledge.d.ts +3 -0
  141. package/dist/routes/managed-auth-session-sets.d.ts +26 -0
  142. package/dist/routes/managed-onboarding.d.ts +29 -0
  143. package/dist/routes/managed-sign-in-methods.d.ts +6 -0
  144. package/dist/routes/model-connection-access.d.ts +3 -0
  145. package/dist/routes/organization-integration-policy.d.ts +3 -0
  146. package/dist/routes/organization-integrations.d.ts +6 -0
  147. package/dist/routes/organization-mcp-connections.d.ts +3 -0
  148. package/dist/routes/organization-model-providers.d.ts +3 -0
  149. package/dist/routes/organization-recovery.d.ts +19 -0
  150. package/dist/routes/organization-service-accounts.d.ts +3 -0
  151. package/dist/routes/organization-sessions.d.ts +26 -0
  152. package/dist/routes/personal-github-git-broker.d.ts +29 -0
  153. package/dist/routes/personal-github.d.ts +3 -0
  154. package/dist/routes/pr-review-github.d.ts +3 -0
  155. package/dist/routes/pr-review.d.ts +3 -0
  156. package/dist/routes/session-artifact-associations.d.ts +4 -0
  157. package/dist/routes/session-history-imports.d.ts +9 -0
  158. package/dist/routes/sessions.d.ts +44 -6
  159. package/dist/routes/skill-content.d.ts +4 -0
  160. package/dist/routes/skill-install-authority.d.ts +6 -0
  161. package/dist/routes/subscription-account-pools.d.ts +34 -0
  162. package/dist/routes/subscription-pool-access.d.ts +16 -0
  163. package/dist/routes/supergrok.d.ts +6 -1
  164. package/dist/routes/usage-allowances.d.ts +12 -0
  165. package/dist/routes/user-resource-authorities.d.ts +3 -0
  166. package/dist/routes/workspace-artifacts.d.ts +2 -1
  167. package/dist/routes/workspace-capture.d.ts +10 -2
  168. package/dist/routes/workspace-integrations.d.ts +35 -0
  169. package/dist/routes/workspace-learning.d.ts +3 -0
  170. package/dist/routes/workspace-model-providers.d.ts +4 -0
  171. package/dist/routes/workspaces.d.ts +29 -0
  172. package/dist/sandbox/auth-callout.d.ts +8 -0
  173. package/dist/sandbox/channel-a.d.ts +19 -1
  174. package/dist/sandbox/connection-authority.d.ts +3 -0
  175. package/dist/sandbox/enrollment.d.ts +9 -1
  176. package/dist/sandbox/machines.d.ts +5 -2
  177. package/dist/sandbox/metrics-ingestion.d.ts +28 -20
  178. package/dist/sandbox/rematerialize.d.ts +2 -0
  179. package/dist/sandbox/session-attach-variable-sets.d.ts +30 -0
  180. package/dist/sandbox/viewer.d.ts +26 -1
  181. package/dist/sandbox-file-artifacts.d.ts +50 -0
  182. package/dist/scheduled-task-deletion.d.ts +13 -0
  183. package/dist/session-create-observability.d.ts +5 -0
  184. package/dist/session-event-view-MPS2OZD3.js +292 -0
  185. package/dist/session-event-view-MPS2OZD3.js.map +1 -0
  186. package/dist/site-session-origin.d.ts +13 -0
  187. package/dist/site-uploads.d.ts +41 -0
  188. package/dist/slack-reaction-files.d.ts +1 -1
  189. package/dist/temporal-schedule-cleanup.d.ts +1 -0
  190. package/dist/temporal-schedule-sync.d.ts +26 -0
  191. package/dist/transcription/providers/xai-subscription.d.ts +1 -0
  192. package/dist/transcription/service.d.ts +2 -1
  193. package/dist/work-discovery-observability.d.ts +33 -0
  194. package/dist/workspace-artifact-content.d.ts +28 -0
  195. package/dist/workspace-artifact-provenance.d.ts +7 -0
  196. package/dist/workspace-delete-observability.d.ts +10 -0
  197. package/dist/workspace-deletion.d.ts +6 -0
  198. package/dist/workspace-state-projection.d.ts +3 -5
  199. package/dist/workspace-tool-gateway-observability.d.ts +17 -0
  200. package/dist/workspace-tool-gateway.d.ts +123 -0
  201. package/dist/workspace-webhook-dispatch.d.ts +26 -0
  202. package/package.json +23 -19
  203. package/src/access-grant-rls.ts +75 -0
  204. package/src/admission-trace.ts +14 -0
  205. package/src/api-websocket.ts +23 -0
  206. package/src/app.ts +1967 -252
  207. package/src/artifact-catalog-cursor.ts +60 -0
  208. package/src/auth/managed-auth-attempt-context.ts +70 -0
  209. package/src/auth/managed-auth-rate-limits.ts +252 -0
  210. package/src/auth/managed-auth-session-adapter.ts +222 -0
  211. package/src/auth/managed-auth.ts +536 -60
  212. package/src/auth/managed-email.ts +174 -0
  213. package/src/auth/managed-sign-in-notifications.ts +81 -0
  214. package/src/auth/new-signups-gate.ts +49 -0
  215. package/src/auth/organization-user-setup.ts +307 -0
  216. package/src/auth/signup-funnel-metrics.ts +164 -0
  217. package/src/browser-controller-authority.ts +1 -186
  218. package/src/browser-ephemeral.ts +39 -0
  219. package/src/browser-state-authority.ts +1 -236
  220. package/src/claude-subscription-account-conflicts.ts +12 -0
  221. package/src/claude-subscription-account-usage.ts +48 -0
  222. package/src/claude-subscription-oauth.ts +362 -0
  223. package/src/claude-subscription-profile.ts +73 -0
  224. package/src/claude-subscription-usage.ts +97 -0
  225. package/src/claude-workspace-connection.ts +82 -0
  226. package/src/codemode.ts +194 -30
  227. package/src/codex-realtime.ts +8 -2
  228. package/src/company-brain-okf.ts +340 -0
  229. package/src/connected-machine-computer-access.ts +33 -0
  230. package/src/connection-ownership.ts +199 -0
  231. package/src/controller-data-plane.ts +81 -0
  232. package/src/editable-artifact-native-kernel.ts +26 -15
  233. package/src/editable-artifact-office-import.ts +28 -3
  234. package/src/editable-artifact-production.ts +23 -7
  235. package/src/editable-artifact-source-session.ts +123 -0
  236. package/src/editable-artifact-websocket.ts +27 -21
  237. package/src/editable-artifact-workspace-files.ts +31 -6
  238. package/src/fatal-process-boundary.ts +246 -0
  239. package/src/gateway-realtime.ts +1 -1
  240. package/src/github-access.ts +147 -5
  241. package/src/http/acting-person.ts +71 -0
  242. package/src/http/api-contract-compat.ts +49 -0
  243. package/src/http/api-error.ts +115 -1
  244. package/src/http/auth.ts +48 -0
  245. package/src/http/cors.ts +67 -0
  246. package/src/http/deprecation.ts +118 -0
  247. package/src/http/interaction-control-error.ts +164 -0
  248. package/src/http/keyed-admission.ts +29 -0
  249. package/src/http/local-browser-boundary.ts +343 -0
  250. package/src/http/metrics-listener.ts +57 -0
  251. package/src/http/path-identifier.ts +54 -0
  252. package/src/http/registered-route-label.ts +34 -0
  253. package/src/http/request-body.ts +127 -0
  254. package/src/http/request-source.ts +178 -0
  255. package/src/http/sse.ts +486 -132
  256. package/src/http/unmatched-route.ts +60 -0
  257. package/src/http/user-content.ts +158 -0
  258. package/src/http/websocket-upgrade-dispatch.ts +29 -0
  259. package/src/index.ts +157 -52
  260. package/src/integration-endpoint-test.ts +291 -0
  261. package/src/integrations/api-integrations.ts +98 -11
  262. package/src/integrations/atlassian.ts +474 -136
  263. package/src/integrations/connect-authority.ts +2 -0
  264. package/src/integrations/connect-callback-return.ts +86 -0
  265. package/src/integrations/fiken.ts +354 -64
  266. package/src/integrations/github-app-connect.ts +435 -0
  267. package/src/integrations/github-installation-proof.ts +60 -0
  268. package/src/integrations/github-lens-connect.ts +76 -0
  269. package/src/integrations/github-skill-source.ts +2 -142
  270. package/src/integrations/google-drive.ts +717 -225
  271. package/src/integrations/integration-commit-authority.ts +69 -0
  272. package/src/integrations/marketplace-plugin.ts +48 -0
  273. package/src/integrations/oauth-client.ts +1356 -459
  274. package/src/integrations/oauth-profiles.ts +537 -0
  275. package/src/integrations/oauth-return-path.ts +18 -0
  276. package/src/integrations/personal-github-repositories.ts +674 -0
  277. package/src/integrations/personal-github.ts +825 -0
  278. package/src/integrations/pr-review-provider.ts +246 -0
  279. package/src/integrations/provider-oauth.ts +250 -22
  280. package/src/integrations/slack-app-home.ts +300 -0
  281. package/src/integrations/slack-bot.ts +963 -239
  282. package/src/integrations/slack-file-upload-flow.ts +147 -0
  283. package/src/integrations/slack-install.ts +83 -0
  284. package/src/integrations/slack-interactions.ts +3167 -474
  285. package/src/integrations/slack-mrkdwn.ts +193 -0
  286. package/src/integrations/slack-routing.ts +337 -0
  287. package/src/integrations/slack-session-defaults.ts +224 -0
  288. package/src/integrations/slack-task-file-upload.ts +323 -0
  289. package/src/integrations/social-oauth.ts +213 -15
  290. package/src/interaction-agent-access.ts +48 -0
  291. package/src/interaction-frame-proxy.ts +409 -0
  292. package/src/interaction-metrics.ts +30 -0
  293. package/src/mcp/action-catalog-types.ts +9 -0
  294. package/src/mcp/action-catalog.gen.ts +6696 -0
  295. package/src/mcp/capability-account-readiness.ts +17 -0
  296. package/src/mcp/child-read-evidence.ts +19 -0
  297. package/src/mcp/command-read.ts +109 -0
  298. package/src/mcp/company-profile-agent-admin.ts +198 -0
  299. package/src/mcp/contract-input.ts +141 -0
  300. package/src/mcp/documents.ts +65 -377
  301. package/src/mcp/editable-artifacts.ts +12 -6
  302. package/src/mcp/files.ts +80 -43
  303. package/src/mcp/first-party-tool-permissions.ts +305 -0
  304. package/src/mcp/knowledge-entries.ts +322 -0
  305. package/src/mcp/orchestration-failure-diagnostic.ts +43 -0
  306. package/src/mcp/receipts.ts +34 -0
  307. package/src/mcp/request-abort.ts +44 -0
  308. package/src/mcp/scheduled-task-input.ts +76 -0
  309. package/src/mcp/scheduled-task-view.ts +44 -3
  310. package/src/mcp/server.ts +3030 -1267
  311. package/src/mcp/session-event-view.ts +383 -0
  312. package/src/mcp/session-view.ts +84 -3
  313. package/src/mcp/session-wait.ts +665 -0
  314. package/src/mcp-oauth.ts +1123 -0
  315. package/src/model-catalog.ts +91 -333
  316. package/src/observability.ts +15 -1
  317. package/src/organization-mcp.ts +397 -0
  318. package/src/organization-recovery-notifications.ts +103 -0
  319. package/src/routes/analytics-consent.ts +123 -0
  320. package/src/routes/api-integrations.ts +364 -74
  321. package/src/routes/api-keys.ts +336 -10
  322. package/src/routes/artifact-catalog.ts +279 -0
  323. package/src/routes/automations.ts +768 -0
  324. package/src/routes/billing.ts +461 -46
  325. package/src/routes/browser-sessions.ts +780 -112
  326. package/src/routes/canonical-human-identities.ts +90 -14
  327. package/src/routes/capabilities.ts +107 -6
  328. package/src/routes/channels.ts +20 -3
  329. package/src/routes/claude-subscription-accounts.ts +334 -0
  330. package/src/routes/claude-subscription-oauth.ts +142 -0
  331. package/src/routes/client-errors.ts +362 -0
  332. package/src/routes/codex.ts +833 -116
  333. package/src/routes/company-brain.ts +322 -0
  334. package/src/routes/company-profile.ts +65 -1
  335. package/src/routes/computer-sessions.ts +557 -106
  336. package/src/routes/connect.ts +1938 -0
  337. package/src/routes/connections.ts +1039 -349
  338. package/src/routes/documents.ts +488 -287
  339. package/src/routes/editable-artifacts.ts +79 -9
  340. package/src/routes/enrollments.ts +135 -39
  341. package/src/routes/environments.ts +154 -58
  342. package/src/routes/external-identity-links.ts +201 -0
  343. package/src/routes/feedback.ts +113 -0
  344. package/src/routes/files.ts +267 -33
  345. package/src/routes/github-browser-pages.ts +162 -0
  346. package/src/routes/github.ts +446 -144
  347. package/src/routes/insights-response-cache.ts +83 -0
  348. package/src/routes/insights-usage.ts +345 -0
  349. package/src/routes/insights.ts +153 -19
  350. package/src/routes/install.ts +133 -38
  351. package/src/routes/integration-facets.ts +107 -33
  352. package/src/routes/interaction-resources.ts +30 -3
  353. package/src/routes/knowledge.ts +488 -0
  354. package/src/routes/machines.ts +313 -15
  355. package/src/routes/managed-auth-session-sets.ts +1218 -0
  356. package/src/routes/managed-onboarding.ts +337 -0
  357. package/src/routes/managed-sign-in-methods.ts +433 -0
  358. package/src/routes/memory-slack-publications.ts +3 -2
  359. package/src/routes/model-connection-access.ts +208 -0
  360. package/src/routes/organization-integration-policy.ts +116 -0
  361. package/src/routes/organization-integrations.ts +326 -0
  362. package/src/routes/organization-mcp-connections.ts +178 -0
  363. package/src/routes/organization-memberships.ts +1019 -27
  364. package/src/routes/organization-model-providers.ts +293 -0
  365. package/src/routes/organization-recovery.ts +325 -0
  366. package/src/routes/organization-service-accounts.ts +128 -0
  367. package/src/routes/organization-sessions.ts +350 -0
  368. package/src/routes/personal-github-git-broker.ts +785 -0
  369. package/src/routes/personal-github.ts +370 -0
  370. package/src/routes/plugins.ts +251 -22
  371. package/src/routes/pr-review-github.ts +881 -0
  372. package/src/routes/pr-review.ts +622 -0
  373. package/src/routes/preference-registry.ts +12 -93
  374. package/src/routes/rigs.ts +167 -50
  375. package/src/routes/scheduled-tasks.ts +346 -41
  376. package/src/routes/session-artifact-associations.ts +87 -0
  377. package/src/routes/session-history-imports.ts +280 -0
  378. package/src/routes/sessions.ts +2306 -264
  379. package/src/routes/skill-content.ts +297 -0
  380. package/src/routes/skill-install-authority.ts +64 -0
  381. package/src/routes/skills.ts +62 -9
  382. package/src/routes/social.ts +46 -18
  383. package/src/routes/subscription-account-pools.ts +436 -0
  384. package/src/routes/subscription-pool-access.ts +126 -0
  385. package/src/routes/supergrok.ts +219 -303
  386. package/src/routes/transcription-recordings.ts +38 -3
  387. package/src/routes/transcriptions.ts +4 -0
  388. package/src/routes/usage-allowances.ts +378 -0
  389. package/src/routes/user-resource-authorities.ts +138 -0
  390. package/src/routes/video-generation.ts +14 -10
  391. package/src/routes/workspace-artifacts.ts +275 -69
  392. package/src/routes/workspace-capture.ts +21 -0
  393. package/src/routes/workspace-integrations.ts +576 -0
  394. package/src/routes/workspace-learning.ts +159 -0
  395. package/src/routes/workspace-model-providers.ts +148 -0
  396. package/src/routes/workspace-state.ts +38 -25
  397. package/src/routes/workspaces.ts +1004 -127
  398. package/src/sandbox/auth-callout.ts +96 -19
  399. package/src/sandbox/channel-a.ts +323 -24
  400. package/src/sandbox/connection-authority.ts +3 -0
  401. package/src/sandbox/enrollment.ts +81 -6
  402. package/src/sandbox/machines.ts +218 -76
  403. package/src/sandbox/metrics-ingestion.ts +411 -62
  404. package/src/sandbox/rematerialize.ts +121 -19
  405. package/src/sandbox/session-attach-variable-sets.ts +131 -0
  406. package/src/sandbox/viewer.ts +280 -25
  407. package/src/sandbox-file-artifacts.ts +413 -0
  408. package/src/scheduled-task-deletion.ts +138 -0
  409. package/src/session-create-observability.ts +46 -0
  410. package/src/session-realtime-context.ts +6 -3
  411. package/src/site-session-origin.ts +48 -0
  412. package/src/site-uploads.ts +224 -0
  413. package/src/slack-reaction-files.ts +16 -5
  414. package/src/temporal-schedule-cleanup.ts +13 -0
  415. package/src/temporal-schedule-sync.ts +74 -0
  416. package/src/transcription/providers/codex-subscription.ts +2 -0
  417. package/src/transcription/providers/openai.ts +1 -0
  418. package/src/transcription/providers/xai-subscription.ts +49 -3
  419. package/src/transcription/segmenter.ts +8 -2
  420. package/src/transcription/service.ts +43 -6
  421. package/src/work-discovery-observability.ts +121 -0
  422. package/src/workspace-artifact-content.ts +144 -0
  423. package/src/workspace-artifact-provenance.ts +104 -0
  424. package/src/workspace-delete-observability.ts +75 -0
  425. package/src/workspace-deletion.ts +73 -0
  426. package/src/workspace-state-projection.ts +18 -148
  427. package/src/workspace-tool-gateway-observability.ts +100 -0
  428. package/src/workspace-tool-gateway.ts +870 -0
  429. package/src/workspace-webhook-dispatch.ts +190 -0
  430. package/src/xai-subscription-auth.ts +9 -2
  431. package/dist/chunk-WCXHI3FF.js +0 -49920
  432. package/dist/chunk-WCXHI3FF.js.map +0 -1
  433. package/dist/routes/packs.d.ts +0 -3
  434. package/src/routes/packs.ts +0 -729
@@ -0,0 +1,881 @@
1
+ import { environmentsEncryptionKeyBytes } from "@opengeni/config";
2
+ import { withOrganizationIntegrationAcquisition } from "@opengeni/db/organization-integration-policy";
3
+ import {
4
+ AUTOMATION_WEBHOOK_MAX_BYTES,
5
+ PrReviewManagedGitHubSetup,
6
+ type AccessGrant,
7
+ type GitHubInstallationBindingCandidate,
8
+ type GitHubInstallationBindingProof,
9
+ } from "@opengeni/contracts";
10
+ import {
11
+ automationRequestDigest,
12
+ hasPermission,
13
+ requireAutomationAdapter,
14
+ requireAccessGrantAuthorization,
15
+ externalActorContinuationForAuthorization,
16
+ requirePermission,
17
+ verifyPrReviewWebhook,
18
+ type ApiRouteDeps,
19
+ PR_REVIEW_AUTOMATION_SETUP,
20
+ } from "@opengeni/core";
21
+ import {
22
+ AutomationDeliveryConflictError,
23
+ encryptVariableSetValue,
24
+ getAutomationSourceSecret,
25
+ listPrReviewAppRegistrations,
26
+ listPrReviewRepositoryBindings,
27
+ nestedPostgresSqlState,
28
+ PrReviewDispatchAuthorityError,
29
+ recordAuditEvent,
30
+ resolveManagedGitHubPrReviewRoute,
31
+ syncManagedGitHubPrReviewInstallation,
32
+ } from "@opengeni/db";
33
+ import {
34
+ authorizeGitHubInstallationBinding,
35
+ createSignedState,
36
+ discoverGitHubInstallationBindingCandidates,
37
+ GitHubAppApiError,
38
+ GitHubAppConfigurationError,
39
+ GitHubInstallationAuthorityError,
40
+ githubOAuthAuthorizeUrl,
41
+ prReviewGitHubAppMissingSettings,
42
+ readSignedState,
43
+ settingsForPrReviewGitHubApp,
44
+ stateMaxAgeSeconds,
45
+ type GitHubSignedStatePayload,
46
+ } from "@opengeni/github";
47
+ import type { Context, Hono } from "hono";
48
+ import { requireLegacyOAuthActor } from "../connection-ownership";
49
+ import {
50
+ integrationCommitGrant,
51
+ type IntegrationCommitGrant,
52
+ } from "../integrations/integration-commit-authority";
53
+ import { deleteCookie, setCookie } from "hono/cookie";
54
+ import { HTTPException } from "hono/http-exception";
55
+ import { githubBrowserBaseUrl } from "../github-browser-flow";
56
+ import { acceptAutomationEvent, readAutomationWebhookBody } from "./automations";
57
+ import {
58
+ completeGitHubAppConnect,
59
+ isGitHubAppConnectState,
60
+ } from "../integrations/github-app-connect";
61
+
62
+ const stateCookie = "opengeni_pr_review_github_state";
63
+ const bindingStateMaxAgeSeconds = 10 * 60;
64
+ const appName = "OpenGeni Lens" as const;
65
+
66
+ export function registerPrReviewGitHubRoutes(app: Hono, deps: ApiRouteDeps): void {
67
+ app.post("/v1/webhooks/pr-review/github", async (c) => {
68
+ const secret = deps.settings.prReviewGithubWebhookSecret?.trim();
69
+ if (!secret) {
70
+ throw new HTTPException(503, { message: "Opengeni Lens webhook is unavailable" });
71
+ }
72
+ const rawBody = await readAutomationWebhookBody(c.req.raw, AUTOMATION_WEBHOOK_MAX_BYTES);
73
+ if (
74
+ !verifyPrReviewWebhook({
75
+ provider: "github",
76
+ rawBody,
77
+ headers: c.req.raw.headers,
78
+ secret,
79
+ webhookUsername: null,
80
+ })
81
+ ) {
82
+ throw new HTTPException(401, { message: "Opengeni Lens signature is invalid" });
83
+ }
84
+ let payload: unknown;
85
+ try {
86
+ payload = JSON.parse(new TextDecoder().decode(rawBody));
87
+ } catch {
88
+ throw new HTTPException(400, { message: "Opengeni Lens payload is invalid JSON" });
89
+ }
90
+ const record = asRecord(payload);
91
+ const installationId = positiveInteger(asRecord(record?.installation)?.id);
92
+ const repositoryId = positiveInteger(asRecord(record?.repository)?.id);
93
+ if (installationId === null || repositoryId === null) {
94
+ return c.json(ignoredWebhook("unsupported_event"), 202);
95
+ }
96
+ const route = await resolveManagedGitHubPrReviewRoute(deps.db, {
97
+ installationId: String(installationId),
98
+ providerRepositoryId: String(repositoryId),
99
+ });
100
+ if (!route) return c.json(ignoredWebhook("repository_not_connected"), 202);
101
+ const source = await getAutomationSourceSecret(deps.db, route);
102
+ if (!source || source.status !== "active") {
103
+ return c.json(ignoredWebhook("source_disabled"), 202);
104
+ }
105
+ const adapter = requireAutomationAdapter(source.adapterId);
106
+ const requestDigest = automationRequestDigest(source.adapterId, rawBody);
107
+ try {
108
+ return c.json(
109
+ await acceptAutomationEvent(deps, source, {
110
+ deliveryKey: adapter.deliveryKey({
111
+ headers: c.req.raw.headers,
112
+ requestDigest,
113
+ }),
114
+ requestDigest,
115
+ normalizedEvent: adapter.normalize({
116
+ rawBody,
117
+ headers: c.req.raw.headers,
118
+ sourceConfiguration: source.configuration,
119
+ }),
120
+ }),
121
+ 202,
122
+ );
123
+ } catch (error) {
124
+ if (error instanceof AutomationDeliveryConflictError) {
125
+ throw new HTTPException(409, { message: error.message });
126
+ }
127
+ throw error;
128
+ }
129
+ });
130
+
131
+ app.get("/v1/workspaces/:workspaceId/pr-review/github", async (c) => {
132
+ const workspaceId = c.req.param("workspaceId");
133
+ const access = await requireAccessGrantAuthorization(c, deps, workspaceId, "workspace:read");
134
+ const grant = access.grant;
135
+
136
+ const missing = prReviewGitHubAppMissingSettings(deps.settings);
137
+ const configured = missing.length === 0;
138
+ const registrations = (
139
+ await listPrReviewAppRegistrations(deps.db, grant.accountId, workspaceId)
140
+ ).filter((registration) => registration.credentialKind === "managed_github_app");
141
+ const repositories = await listPrReviewRepositoryBindings(
142
+ deps.db,
143
+ grant.accountId,
144
+ workspaceId,
145
+ );
146
+ const canManage =
147
+ !externalActorContinuationForAuthorization(access) &&
148
+ hasPermission(grant.permissions, "workspace:admin") &&
149
+ hasPermission(grant.permissions, "secrets:write");
150
+ const connectState =
151
+ configured && canManage
152
+ ? createSignedState(deps.githubStateSecret, {
153
+ accountId: grant.accountId,
154
+ workspaceId,
155
+ intent: "pr_review_github_authority",
156
+ ...prReviewBrowserGrantClaims(deps, grant),
157
+ })
158
+ : null;
159
+ const baseUrl = openGeniBaseUrl(deps, c);
160
+ const connectUrl = connectState
161
+ ? `${baseUrl}/v1/workspaces/${workspaceId}/pr-review/github/connect?state=${encodeURIComponent(connectState)}`
162
+ : null;
163
+ return c.json(
164
+ PrReviewManagedGitHubSetup.parse({
165
+ configured,
166
+ status: !configured
167
+ ? "unavailable"
168
+ : registrations.some((registration) => registration.status === "active")
169
+ ? "connected"
170
+ : "not_connected",
171
+ appName,
172
+ connectUrl,
173
+ installations: registrations.map((registration) => {
174
+ const installationId = registration.installationId!;
175
+ const configureState = connectState
176
+ ? createSignedState(deps.githubStateSecret, {
177
+ accountId: grant.accountId,
178
+ workspaceId,
179
+ expectedInstallationId: Number(installationId),
180
+ intent: "pr_review_github_install",
181
+ ...prReviewBrowserGrantClaims(deps, grant),
182
+ })
183
+ : null;
184
+ return {
185
+ registrationId: registration.id,
186
+ installationId,
187
+ accountLogin: registration.providerAccountLogin,
188
+ configureUrl: configureState
189
+ ? `${baseUrl}/v1/workspaces/${workspaceId}/pr-review/github/installations/${installationId}/configure?state=${encodeURIComponent(configureState)}`
190
+ : null,
191
+ repositoryCount: repositories.filter(
192
+ (repository) =>
193
+ repository.registrationId === registration.id && repository.status === "active",
194
+ ).length,
195
+ };
196
+ }),
197
+ missing: deps.settings.productAccessMode === "managed" ? [] : missing,
198
+ }),
199
+ );
200
+ });
201
+
202
+ app.get("/v1/workspaces/:workspaceId/pr-review/github/connect", async (c) => {
203
+ const workspaceId = c.req.param("workspaceId");
204
+ const state = requireStateQuery(c, "missing Opengeni Lens installation state");
205
+ const payload = requireFreshState(state, deps, "pr_review_github_authority", workspaceId);
206
+ await requirePrReviewManageGrant(c, deps, workspaceId, payload);
207
+
208
+ assertManagedCompute(deps);
209
+ requireConfiguredApp(deps);
210
+ const discoveryState = createSignedState(deps.githubStateSecret, {
211
+ accountId: payload.accountId,
212
+ workspaceId,
213
+ intent: "pr_review_github_discovery",
214
+ ...continuedBrowserGrantClaims(payload),
215
+ });
216
+ setStateCookie(c, deps, discoveryState);
217
+ return c.redirect(
218
+ githubOAuthAuthorizeUrl({
219
+ clientId: deps.settings.prReviewGithubClientId!,
220
+ state: discoveryState,
221
+ redirectUri: `${openGeniBaseUrl(deps, c)}/v1/pr-review/github/oauth/callback`,
222
+ }),
223
+ );
224
+ });
225
+
226
+ app.get(
227
+ "/v1/workspaces/:workspaceId/pr-review/github/installations/:installationId/configure",
228
+ async (c) => {
229
+ const workspaceId = c.req.param("workspaceId");
230
+ const installationId = positiveInteger(c.req.param("installationId"));
231
+ const state = requireStateQuery(c, "missing Opengeni Lens configuration state");
232
+ const payload = requireFreshState(state, deps, "pr_review_github_install", workspaceId);
233
+ if (installationId === null || payload.expectedInstallationId !== installationId) {
234
+ throw new HTTPException(400, { message: "invalid Opengeni Lens installation" });
235
+ }
236
+ await requirePrReviewManageGrant(c, deps, workspaceId, payload);
237
+
238
+ assertManagedCompute(deps);
239
+ const registrations = await listPrReviewAppRegistrations(
240
+ deps.db,
241
+ payload.accountId!,
242
+ workspaceId,
243
+ );
244
+ const registration = registrations.find(
245
+ (candidate) =>
246
+ candidate.credentialKind === "managed_github_app" &&
247
+ candidate.installationId === String(installationId),
248
+ );
249
+ if (!registration) {
250
+ throw new HTTPException(404, { message: "Opengeni Lens installation is not connected" });
251
+ }
252
+ setStateCookie(c, deps, state);
253
+ const configureUrl = githubInstallationSettingsUrl(
254
+ installationId,
255
+ registration.providerAccountLogin,
256
+ registration.providerAccountType,
257
+ );
258
+ configureUrl.searchParams.set("state", state);
259
+ return c.redirect(configureUrl.toString());
260
+ },
261
+ );
262
+
263
+ const handleInstallCallback = async (c: Context) => {
264
+ if (isGitHubAppConnectState(deps, c.req.query("state")))
265
+ return completeGitHubAppConnect(deps, {
266
+ expectedProvider: "github-lens",
267
+ state: c.req.query("state"),
268
+ installationId: c.req.query("installation_id"),
269
+ setupAction: c.req.query("setup_action"),
270
+ error: c.req.query("error"),
271
+ requestUrl: c.req.url,
272
+ });
273
+ const state =
274
+ c.req.query("state") ??
275
+ allCookieValues(c, stateCookie).find((candidate) => {
276
+ const payload = readSignedState(candidate, deps.githubStateSecret);
277
+ return payload?.intent === "pr_review_github_install" && isFreshState(payload);
278
+ });
279
+ if (!state) throw new HTTPException(400, { message: "missing Opengeni Lens state" });
280
+ const payload = requireFreshState(state, deps, "pr_review_github_install");
281
+ requireStateCookie(c, state);
282
+ await requirePrReviewManageGrant(c, deps, payload.workspaceId!, payload);
283
+
284
+ assertManagedCompute(deps);
285
+ const setupAction = c.req.query("setup_action");
286
+ if (setupAction === "request") return c.html(setupPendingHtml());
287
+ if (setupAction !== "install" && setupAction !== "update") {
288
+ throw new HTTPException(400, { message: "unsupported GitHub setup action" });
289
+ }
290
+ const installationId = positiveInteger(c.req.query("installation_id"));
291
+ if (installationId === null) {
292
+ throw new HTTPException(400, { message: "missing or invalid GitHub installation_id" });
293
+ }
294
+ if (
295
+ payload.expectedInstallationId !== undefined &&
296
+ payload.expectedInstallationId !== installationId
297
+ ) {
298
+ throw new HTTPException(409, {
299
+ message: "GitHub returned a different Opengeni Lens installation",
300
+ });
301
+ }
302
+ requireConfiguredApp(deps);
303
+ const oauthState = createSignedState(deps.githubStateSecret, {
304
+ accountId: payload.accountId,
305
+ workspaceId: payload.workspaceId,
306
+ installationId,
307
+ intent: "pr_review_github_oauth",
308
+ ...continuedBrowserGrantClaims(payload),
309
+ });
310
+ setStateCookie(c, deps, oauthState);
311
+ return c.redirect(
312
+ githubOAuthAuthorizeUrl({
313
+ clientId: deps.settings.prReviewGithubClientId!,
314
+ state: oauthState,
315
+ redirectUri: `${openGeniBaseUrl(deps, c)}/v1/pr-review/github/oauth/callback`,
316
+ }),
317
+ );
318
+ };
319
+
320
+ app.get("/v1/pr-review/github/setup", handleInstallCallback);
321
+ app.get("/v1/pr-review/github/install/callback", handleInstallCallback);
322
+
323
+ app.get("/v1/pr-review/github/oauth/callback", async (c) => {
324
+ if (isGitHubAppConnectState(deps, c.req.query("state")))
325
+ return completeGitHubAppConnect(deps, {
326
+ expectedProvider: "github-lens",
327
+ state: c.req.query("state"),
328
+ code: c.req.query("code"),
329
+ error: c.req.query("error"),
330
+ requestUrl: c.req.url,
331
+ });
332
+ const code = c.req.query("code");
333
+ const state = c.req.query("state");
334
+ if (!code || !state) {
335
+ throw new HTTPException(400, { message: "missing Opengeni Lens OAuth code or state" });
336
+ }
337
+ const payload = requireFreshState(state, deps);
338
+ requireStateCookie(c, state);
339
+ const grant = await requirePrReviewManageGrant(c, deps, payload.workspaceId!, payload);
340
+
341
+ assertManagedCompute(deps);
342
+ requireConfiguredApp(deps);
343
+
344
+ await withOrganizationIntegrationAcquisition(deps.db, grant, ["github-lens"], async () => {});
345
+ if (payload.intent === "pr_review_github_discovery") {
346
+ let candidates: GitHubInstallationBindingCandidate[] | null;
347
+ try {
348
+ candidates = deps.prReviewGithubAppApi?.discoverInstallationBindingCandidates
349
+ ? await deps.prReviewGithubAppApi.discoverInstallationBindingCandidates({ code })
350
+ : deps.prReviewGithubAppApi
351
+ ? null
352
+ : await discoverGitHubInstallationBindingCandidates(
353
+ settingsForPrReviewGitHubApp(deps.settings),
354
+ { code },
355
+ );
356
+ } catch (error) {
357
+ throw authorityHttpError(error);
358
+ }
359
+ if (!candidates || !consistentCandidates(candidates)) {
360
+ throw new HTTPException(409, {
361
+ message: "Opengeni Lens could not prove owner-authorized installations",
362
+ });
363
+ }
364
+ const selectionState = createSignedState(deps.githubStateSecret, {
365
+ accountId: grant.accountId,
366
+ workspaceId: grant.workspaceId,
367
+ intent: "pr_review_github_selection",
368
+ allowedInstallationIds: candidates.map(({ installation }) => installation.installationId),
369
+ ...continuedBrowserGrantClaims(payload),
370
+ });
371
+ if (candidates.length === 0) return redirectToInstallation(c, deps, selectionState);
372
+ if (candidates.length === 1) {
373
+ return redirectToExactAuthorization(
374
+ c,
375
+ deps,
376
+ selectionState,
377
+ candidates[0]!.installation.installationId,
378
+ );
379
+ }
380
+ setStateCookie(c, deps, selectionState);
381
+ return c.html(
382
+ installationChooserHtml(candidates, selectionState, grant.workspaceId, deps, c),
383
+ );
384
+ }
385
+
386
+ if (payload.intent !== "pr_review_github_oauth") {
387
+ throw new HTTPException(400, { message: "invalid or expired Opengeni Lens OAuth state" });
388
+ }
389
+ const installationId = positiveInteger(payload.installationId);
390
+ if (installationId === null) {
391
+ throw new HTTPException(400, { message: "invalid Opengeni Lens installation id" });
392
+ }
393
+ let proof: GitHubInstallationBindingProof | null;
394
+ try {
395
+ proof = deps.prReviewGithubAppApi?.authorizeInstallationBinding
396
+ ? await deps.prReviewGithubAppApi.authorizeInstallationBinding({ code, installationId })
397
+ : deps.prReviewGithubAppApi
398
+ ? null
399
+ : await authorizeGitHubInstallationBinding(settingsForPrReviewGitHubApp(deps.settings), {
400
+ code,
401
+ installationId,
402
+ });
403
+ } catch (error) {
404
+ throw authorityHttpError(error);
405
+ }
406
+ if (!proof || !consistentProof(proof, installationId)) {
407
+ throw new HTTPException(409, {
408
+ message: "Opengeni Lens installation proof is stale or invalid",
409
+ });
410
+ }
411
+ const repositoryIds = new Set(proof.repositories.map((repository) => repository.id));
412
+ if (repositoryIds.size !== proof.repositories.length) {
413
+ throw new HTTPException(409, { message: "GitHub returned duplicate repository identities" });
414
+ }
415
+ const template = PR_REVIEW_AUTOMATION_SETUP;
416
+
417
+ const encryptionKey = environmentsEncryptionKeyBytes(deps.settings);
418
+ if (!encryptionKey) {
419
+ throw new HTTPException(503, {
420
+ message: "Opengeni Lens requires configured secret encryption",
421
+ });
422
+ }
423
+ let synchronized;
424
+ try {
425
+ synchronized = await withOrganizationIntegrationAcquisition(
426
+ deps.db,
427
+ grant,
428
+ ["github-lens"],
429
+ async (tx) => {
430
+ await grant.authorizeCommit(tx);
431
+ return syncManagedGitHubPrReviewInstallation(tx, {
432
+ accountId: grant.accountId,
433
+ workspaceId: grant.workspaceId,
434
+ installationId,
435
+ providerAccountLogin: proof.installation.accountLogin,
436
+ providerAccountType: proof.installation.accountType as "User" | "Organization",
437
+ githubActorId: proof.actorId,
438
+ authorityKind: proof.authorityKind,
439
+ authorityCheckedAt: new Date(),
440
+ authorityExpiresAt: new Date((payload.iat + bindingStateMaxAgeSeconds) * 1_000),
441
+ authorityNonce: payload.nonce,
442
+ appId: deps.settings.prReviewGithubAppId!,
443
+ webhookSecretEncrypted: encryptVariableSetValue(
444
+ encryptionKey,
445
+ deps.settings.prReviewGithubWebhookSecret!,
446
+ ),
447
+ repositories: proof.repositories,
448
+ createdBySubjectId: grant.subjectId,
449
+
450
+ adapterId: template.adapterId,
451
+ eventTypes: template.eventTypes,
452
+ configuration: template.configuration,
453
+ sessionTemplate: template.sessionTemplate,
454
+ });
455
+ },
456
+ );
457
+ } catch (error) {
458
+ if (error instanceof PrReviewDispatchAuthorityError) {
459
+ throw new HTTPException(409, { message: error.message });
460
+ }
461
+ if (nestedPostgresSqlState(error) === "23505") {
462
+ throw new HTTPException(409, {
463
+ message:
464
+ "One of these repositories is already connected to Opengeni Lens in another workspace",
465
+ });
466
+ }
467
+ throw error;
468
+ }
469
+ await recordAuditEvent(deps.db, {
470
+ accountId: grant.accountId,
471
+ workspaceId: grant.workspaceId,
472
+ subjectId: grant.subjectId,
473
+ action: "prReview.managed_github.connected",
474
+ targetType: "pr_review_app_registration",
475
+ targetId: synchronized.registration.id,
476
+ metadata: {
477
+ installationId,
478
+ providerAccountLogin: proof.installation.accountLogin,
479
+ repositoryCount: synchronized.repositories.length,
480
+ authorityKind: proof.authorityKind,
481
+ githubActorId: proof.actorId,
482
+ },
483
+ });
484
+ deleteCookie(c, stateCookie, { path: "/v1" });
485
+ return c.html(
486
+ setupSuccessHtml(
487
+ proof.installation.accountLogin ?? `installation ${installationId}`,
488
+ `${openGeniBaseUrl(deps, c)}/workspaces/${grant.workspaceId}/capabilities`,
489
+ ),
490
+ );
491
+ });
492
+
493
+ app.get("/v1/workspaces/:workspaceId/pr-review/github/installations/select", async (c) => {
494
+ const workspaceId = c.req.param("workspaceId");
495
+ const state = requireStateQuery(c, "missing Opengeni Lens selection state");
496
+ const payload = requireFreshState(state, deps, "pr_review_github_selection", workspaceId);
497
+ requireStateCookie(c, state);
498
+ await requirePrReviewManageGrant(c, deps, workspaceId, payload);
499
+
500
+ const selected = c.req.query("installation_id");
501
+ if (selected === "new") return redirectToInstallation(c, deps, state);
502
+ const installationId = positiveInteger(selected);
503
+ if (
504
+ installationId === null ||
505
+ !Array.isArray(payload.allowedInstallationIds) ||
506
+ !payload.allowedInstallationIds.includes(installationId)
507
+ ) {
508
+ throw new HTTPException(403, {
509
+ message: "GitHub installation was not in the owner-authorized selection",
510
+ });
511
+ }
512
+ return redirectToExactAuthorization(c, deps, state, installationId);
513
+ });
514
+ }
515
+
516
+ function requireConfiguredApp(deps: ApiRouteDeps): void {
517
+ const missing = prReviewGitHubAppMissingSettings(deps.settings);
518
+ if (missing.length > 0) {
519
+ throw new HTTPException(409, {
520
+ message: JSON.stringify({ message: "Opengeni Lens is not configured", missing }),
521
+ });
522
+ }
523
+ }
524
+
525
+ function assertManagedCompute(deps: ApiRouteDeps): void {
526
+ if (deps.settings.sandboxBackend === "selfhosted") {
527
+ throw new HTTPException(409, { message: "Opengeni Lens requires managed compute" });
528
+ }
529
+ }
530
+
531
+ async function requirePrReviewManageGrant(
532
+ c: Context,
533
+ deps: ApiRouteDeps,
534
+ workspaceId: string,
535
+ state: GitHubSignedStatePayload,
536
+ ): Promise<IntegrationCommitGrant> {
537
+ let grant: IntegrationCommitGrant;
538
+ try {
539
+ const access = await requireAccessGrantAuthorization(c, deps, workspaceId, "workspace:admin");
540
+ requireLegacyOAuthActor(access);
541
+ grant = await integrationCommitGrant(access, ["workspace:admin", "secrets:write"], {
542
+ settings: deps.settings,
543
+ authorizationHeader: c.req.header("authorization"),
544
+ });
545
+ } catch (error) {
546
+ if (!(error instanceof HTTPException) || error.status !== 401) throw error;
547
+ const handedOff = prReviewBrowserGrantFromState(deps, state, workspaceId);
548
+ if (!handedOff) throw error;
549
+ grant = {
550
+ ...handedOff,
551
+ authorizeCommit: async () => {
552
+ const current = prReviewBrowserGrantFromState(deps, state, workspaceId);
553
+ if (
554
+ !current ||
555
+ current.accountId !== handedOff.accountId ||
556
+ current.subjectId !== handedOff.subjectId
557
+ )
558
+ throw new HTTPException(403, {
559
+ message: "Opengeni Lens browser handoff expired or changed",
560
+ });
561
+ },
562
+ };
563
+ }
564
+ requirePermission(grant, "secrets:write");
565
+ if (grant.accountId !== state.accountId) {
566
+ throw new HTTPException(403, { message: "Opengeni Lens state does not match this workspace" });
567
+ }
568
+ return grant;
569
+ }
570
+
571
+ function prReviewBrowserGrantClaims(deps: ApiRouteDeps, grant: AccessGrant) {
572
+ if (
573
+ deps.settings.productAccessMode !== "configured" ||
574
+ !hasPermission(grant.permissions, "workspace:admin") ||
575
+ !hasPermission(grant.permissions, "secrets:write")
576
+ ) {
577
+ return {};
578
+ }
579
+ return {
580
+ prReviewBrowserGrantSubjectId: grant.subjectId,
581
+ prReviewBrowserGrantExpiresAt: Math.floor(Date.now() / 1_000) + bindingStateMaxAgeSeconds,
582
+ };
583
+ }
584
+
585
+ function continuedBrowserGrantClaims(payload: GitHubSignedStatePayload) {
586
+ return typeof payload.prReviewBrowserGrantSubjectId === "string" &&
587
+ typeof payload.prReviewBrowserGrantExpiresAt === "number"
588
+ ? {
589
+ prReviewBrowserGrantSubjectId: payload.prReviewBrowserGrantSubjectId,
590
+ prReviewBrowserGrantExpiresAt: payload.prReviewBrowserGrantExpiresAt,
591
+ }
592
+ : {};
593
+ }
594
+
595
+ function prReviewBrowserGrantFromState(
596
+ deps: ApiRouteDeps,
597
+ payload: GitHubSignedStatePayload,
598
+ workspaceId: string,
599
+ ): AccessGrant | null {
600
+ const subjectId = payload.prReviewBrowserGrantSubjectId;
601
+ const expiresAt = payload.prReviewBrowserGrantExpiresAt;
602
+ const now = Math.floor(Date.now() / 1_000);
603
+ if (
604
+ deps.settings.productAccessMode !== "configured" ||
605
+ payload.workspaceId !== workspaceId ||
606
+ typeof payload.accountId !== "string" ||
607
+ typeof subjectId !== "string" ||
608
+ typeof expiresAt !== "number" ||
609
+ !Number.isInteger(expiresAt) ||
610
+ expiresAt < now ||
611
+ expiresAt > payload.iat + bindingStateMaxAgeSeconds
612
+ ) {
613
+ return null;
614
+ }
615
+ return {
616
+ accountId: payload.accountId,
617
+ workspaceId,
618
+ subjectId,
619
+ permissions: ["workspace:admin", "secrets:write"],
620
+ metadata: { prReviewGithubBrowserHandoff: true, expiresAt },
621
+ };
622
+ }
623
+
624
+ function redirectToInstallation(c: Context, deps: ApiRouteDeps, sourceState: string): Response {
625
+ const payload = readSignedState(sourceState, deps.githubStateSecret);
626
+ const slug = deps.settings.prReviewGithubAppSlug?.trim();
627
+ if (!payload?.accountId || !payload.workspaceId || !slug) {
628
+ throw new HTTPException(409, { message: "Opengeni Lens installation is unavailable" });
629
+ }
630
+ const installState = createSignedState(deps.githubStateSecret, {
631
+ accountId: payload.accountId,
632
+ workspaceId: payload.workspaceId,
633
+ intent: "pr_review_github_install",
634
+ ...continuedBrowserGrantClaims(payload),
635
+ });
636
+ setStateCookie(c, deps, installState);
637
+ return c.redirect(
638
+ `https://github.com/apps/${encodeURIComponent(slug)}/installations/new?state=${encodeURIComponent(installState)}`,
639
+ );
640
+ }
641
+
642
+ function redirectToExactAuthorization(
643
+ c: Context,
644
+ deps: ApiRouteDeps,
645
+ sourceState: string,
646
+ installationId: number,
647
+ ): Response {
648
+ const payload = readSignedState(sourceState, deps.githubStateSecret);
649
+ const clientId = deps.settings.prReviewGithubClientId?.trim();
650
+ if (!payload?.accountId || !payload.workspaceId || !clientId) {
651
+ throw new HTTPException(409, { message: "Opengeni Lens authorization is unavailable" });
652
+ }
653
+ const oauthState = createSignedState(deps.githubStateSecret, {
654
+ accountId: payload.accountId,
655
+ workspaceId: payload.workspaceId,
656
+ installationId,
657
+ intent: "pr_review_github_oauth",
658
+ ...continuedBrowserGrantClaims(payload),
659
+ });
660
+ setStateCookie(c, deps, oauthState);
661
+ return c.redirect(
662
+ githubOAuthAuthorizeUrl({
663
+ clientId,
664
+ state: oauthState,
665
+ redirectUri: `${openGeniBaseUrl(deps, c)}/v1/pr-review/github/oauth/callback`,
666
+ }),
667
+ );
668
+ }
669
+
670
+ function requireFreshState(
671
+ state: string,
672
+ deps: ApiRouteDeps,
673
+ intent?: string,
674
+ workspaceId?: string,
675
+ ): GitHubSignedStatePayload {
676
+ const payload = readSignedState(state, deps.githubStateSecret);
677
+ if (
678
+ !payload ||
679
+ !isFreshState(payload) ||
680
+ typeof payload.accountId !== "string" ||
681
+ typeof payload.workspaceId !== "string" ||
682
+ (intent !== undefined && payload.intent !== intent) ||
683
+ (workspaceId !== undefined && payload.workspaceId !== workspaceId)
684
+ ) {
685
+ throw new HTTPException(400, { message: "invalid or expired Opengeni Lens state" });
686
+ }
687
+ return payload;
688
+ }
689
+
690
+ function isFreshState(payload: GitHubSignedStatePayload): boolean {
691
+ const age = Math.floor(Date.now() / 1_000) - payload.iat;
692
+ return age >= 0 && age < bindingStateMaxAgeSeconds;
693
+ }
694
+
695
+ function requireStateQuery(c: Context, message: string): string {
696
+ const state = c.req.query("state");
697
+ if (!state) throw new HTTPException(400, { message });
698
+ return state;
699
+ }
700
+
701
+ function setStateCookie(c: Context, deps: ApiRouteDeps, state: string): void {
702
+ setCookie(c, stateCookie, state, {
703
+ httpOnly: true,
704
+ sameSite: "Lax",
705
+ secure:
706
+ deps.settings.publicBaseUrl?.startsWith("https://") ||
707
+ c.req.header("x-forwarded-proto") === "https" ||
708
+ new URL(c.req.url).protocol === "https:",
709
+ path: "/v1",
710
+ maxAge: stateMaxAgeSeconds,
711
+ });
712
+ }
713
+
714
+ function requireStateCookie(c: Context, state: string): void {
715
+ if (!allCookieValues(c, stateCookie).includes(state)) {
716
+ throw new HTTPException(400, { message: "invalid Opengeni Lens browser state" });
717
+ }
718
+ }
719
+
720
+ function allCookieValues(c: Context, name: string): string[] {
721
+ const prefix = `${name}=`;
722
+ return (c.req.header("cookie") ?? "")
723
+ .split(";")
724
+ .map((part) => part.trim())
725
+ .filter((part) => part.startsWith(prefix))
726
+ .map((part) => {
727
+ try {
728
+ return decodeURIComponent(part.slice(prefix.length));
729
+ } catch {
730
+ return part.slice(prefix.length);
731
+ }
732
+ });
733
+ }
734
+
735
+ function consistentCandidates(candidates: GitHubInstallationBindingCandidate[]): boolean {
736
+ const ids = new Set<number>();
737
+ return candidates.every(({ installation, authorityKind }) => {
738
+ if (
739
+ !Number.isSafeInteger(installation.installationId) ||
740
+ installation.installationId <= 0 ||
741
+ !Number.isSafeInteger(installation.accountId) ||
742
+ installation.accountId <= 0 ||
743
+ !installation.accountLogin?.trim() ||
744
+ installation.suspended ||
745
+ ids.has(installation.installationId)
746
+ ) {
747
+ return false;
748
+ }
749
+ ids.add(installation.installationId);
750
+ return authorityKind === "personal_owner"
751
+ ? installation.accountType === "User"
752
+ : installation.accountType === "Organization";
753
+ });
754
+ }
755
+
756
+ function consistentProof(proof: GitHubInstallationBindingProof, installationId: number): boolean {
757
+ const installation = proof.installation;
758
+ if (
759
+ installation.installationId !== installationId ||
760
+ !Number.isSafeInteger(installation.accountId) ||
761
+ installation.accountId <= 0 ||
762
+ !installation.accountLogin?.trim() ||
763
+ installation.suspended ||
764
+ !Number.isSafeInteger(proof.actorId) ||
765
+ proof.actorId <= 0 ||
766
+ !proof.actorLogin.trim() ||
767
+ proof.repositories.length === 0
768
+ ) {
769
+ return false;
770
+ }
771
+ if (
772
+ proof.authorityKind === "personal_owner"
773
+ ? installation.accountType !== "User" || proof.actorId !== installation.accountId
774
+ : installation.accountType !== "Organization"
775
+ ) {
776
+ return false;
777
+ }
778
+ return proof.repositories.every(
779
+ (repository) =>
780
+ Number.isSafeInteger(repository.id) &&
781
+ repository.id > 0 &&
782
+ repository.installationId === installationId &&
783
+ repository.accountLogin === installation.accountLogin &&
784
+ repository.accountType === installation.accountType,
785
+ );
786
+ }
787
+
788
+ function authorityHttpError(error: unknown): HTTPException {
789
+ if (error instanceof HTTPException) return error;
790
+ if (error instanceof GitHubInstallationAuthorityError) {
791
+ if (error.reason === "authority_denied") {
792
+ return new HTTPException(403, { message: error.message });
793
+ }
794
+ if (error.reason === "installation_missing") {
795
+ return new HTTPException(404, { message: error.message });
796
+ }
797
+ return new HTTPException(409, { message: error.message });
798
+ }
799
+ if (error instanceof GitHubAppConfigurationError) {
800
+ return new HTTPException(409, {
801
+ message: JSON.stringify({ message: error.message, missing: error.missing }),
802
+ });
803
+ }
804
+ if (error instanceof GitHubAppApiError) {
805
+ return new HTTPException(502, { message: error.message });
806
+ }
807
+ return new HTTPException(502, { message: "Opengeni Lens authority verification failed" });
808
+ }
809
+
810
+ function positiveInteger(value: unknown): number | null {
811
+ if (typeof value === "number" && Number.isSafeInteger(value) && value > 0) return value;
812
+ if (typeof value === "string" && /^[1-9][0-9]*$/.test(value)) {
813
+ const parsed = Number(value);
814
+ return Number.isSafeInteger(parsed) ? parsed : null;
815
+ }
816
+ return null;
817
+ }
818
+
819
+ function asRecord(value: unknown): Record<string, unknown> | null {
820
+ return value && typeof value === "object" && !Array.isArray(value)
821
+ ? (value as Record<string, unknown>)
822
+ : null;
823
+ }
824
+
825
+ function ignoredWebhook(reason: string) {
826
+ return {
827
+ accepted: true,
828
+ duplicate: false,
829
+ ignoredReason: reason,
830
+ eventId: null,
831
+ runIds: [],
832
+ };
833
+ }
834
+
835
+ function githubInstallationSettingsUrl(
836
+ installationId: number,
837
+ accountLogin: string | null,
838
+ accountType: "User" | "Organization" | null,
839
+ ): URL {
840
+ return accountType === "Organization" && accountLogin
841
+ ? new URL(
842
+ `https://github.com/organizations/${encodeURIComponent(accountLogin)}/settings/installations/${installationId}`,
843
+ )
844
+ : new URL(`https://github.com/settings/installations/${installationId}`);
845
+ }
846
+
847
+ function openGeniBaseUrl(deps: ApiRouteDeps, c: Context): string {
848
+ return githubBrowserBaseUrl(deps.settings, new URL(c.req.url).origin);
849
+ }
850
+
851
+ function escapeHtml(value: string): string {
852
+ return value.replace(
853
+ /[&<>"']/g,
854
+ (char) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[char]!,
855
+ );
856
+ }
857
+
858
+ function installationChooserHtml(
859
+ candidates: GitHubInstallationBindingCandidate[],
860
+ state: string,
861
+ workspaceId: string,
862
+ deps: ApiRouteDeps,
863
+ c: Context,
864
+ ): string {
865
+ const action = `${openGeniBaseUrl(deps, c)}/v1/workspaces/${workspaceId}/pr-review/github/installations/select`;
866
+ const options = candidates
867
+ .map(
868
+ ({ installation, authorityKind }) =>
869
+ `<label class="option"><input type="radio" name="installation_id" value="${installation.installationId}" required><span><strong>${escapeHtml(installation.accountLogin!)}</strong><small>${authorityKind === "personal_owner" ? "Personal account" : "Organization owner"}</small></span></label>`,
870
+ )
871
+ .join("");
872
+ return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Choose GitHub account</title><style>body{font-family:system-ui,sans-serif;margin:0;min-height:100vh;display:grid;place-items:center;background:#0b0b0d;color:#f4f4f5}main{width:min(640px,calc(100vw - 32px));border:1px solid #27272a;border-radius:12px;padding:28px;background:#111114}h1{margin:0 0 10px;font-size:24px}p{color:#d4d4d8}.options{display:grid;gap:8px;margin-bottom:18px}.option{display:flex;gap:12px;border:1px solid #3f3f46;border-radius:8px;padding:12px}.option span{display:grid}.option small{color:#a1a1aa}button{min-height:38px;border-radius:7px;border:1px solid #3f3f46;padding:0 14px;font-weight:600}.secondary{margin-left:8px;background:transparent;color:#f4f4f5}</style></head><body><main><h1>Connect Opengeni Lens</h1><p>Choose an account where GitHub proved you are the owner.</p><form method="get" action="${escapeHtml(action)}"><input type="hidden" name="state" value="${escapeHtml(state)}"><div class="options">${options}</div><button type="submit">Connect selected</button><button class="secondary" type="submit" name="installation_id" value="new" formnovalidate>Install on another account</button></form></main></body></html>`;
873
+ }
874
+
875
+ function setupSuccessHtml(account: string, returnUrl: string): string {
876
+ return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Opengeni Lens Connected</title><style>body{font-family:system-ui,sans-serif;margin:0;min-height:100vh;display:grid;place-items:center;background:#0b0b0d;color:#f4f4f5}main{width:min(640px,calc(100vw - 32px));border:1px solid #27272a;border-radius:8px;padding:28px;background:#111114}p{color:#d4d4d8}.button{display:inline-flex;min-height:36px;align-items:center;border-radius:6px;padding:0 12px;background:#f4f4f5;color:#09090b;font-weight:600;text-decoration:none}</style></head><body><main><h1>Opengeni Lens connected</h1><p>${escapeHtml(account)} and its selected repositories are ready for pull-request review.</p><a class="button" href="${escapeHtml(returnUrl)}">Back to Opengeni</a></main></body></html>`;
877
+ }
878
+
879
+ function setupPendingHtml(): string {
880
+ return `<!doctype html><html><head><meta charset="utf-8"><title>Opengeni Lens Requested</title></head><body><main><h1>Installation requested</h1><p>A GitHub organization owner must approve Opengeni Lens. No repository was connected yet.</p></main></body></html>`;
881
+ }