@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,825 @@
1
+ import { withOrganizationIntegrationAcquisition } from "@opengeni/db/organization-integration-policy";
2
+ import { claimOAuthAcquisition, finishOAuthAcquisition } from "./oauth-client";
3
+ import { createHash, randomBytes, randomUUID } from "node:crypto";
4
+ import { ExternalActorContinuation } from "@opengeni/contracts/external-identities";
5
+ import { requireConnectOwnerAuthority } from "./connect-authority";
6
+ import { personalGitHubOAuthCallbackUrl, type Settings } from "@opengeni/config";
7
+ import {
8
+ PERSONAL_GITHUB_AUTHORIZATION_URL,
9
+ PERSONAL_GITHUB_CREDENTIAL_ROLE,
10
+ PERSONAL_GITHUB_PROVIDER_DOMAIN,
11
+ PERSONAL_GITHUB_PROVIDER_FAMILY,
12
+ PERSONAL_GITHUB_REQUESTED_SCOPES,
13
+ PERSONAL_GITHUB_TOKEN_URL,
14
+ PERSONAL_GITHUB_USER_URL,
15
+ PersonalGitHubConnectionMetadata,
16
+ type PersonalGitHubDisconnectRequest,
17
+ PersonalGitHubOAuthStartResponse,
18
+ isPersonalGitHubConnection,
19
+ } from "@opengeni/contracts/personal-github";
20
+ import {
21
+ externalActorContinuationForAuthorization,
22
+ requireEnvironmentEncryption,
23
+ type AccessGrantAuthorization,
24
+ type ApiRouteDeps,
25
+ } from "@opengeni/core";
26
+ import {
27
+ consumeIntegrationOAuthStateNonce,
28
+ finishConnectOperation,
29
+ getConnectAttempt,
30
+ type Database,
31
+ ConnectionDisconnectGenerationError,
32
+ ConnectionDisconnectIdempotencyError,
33
+ decryptEnvironmentValue,
34
+ disconnectConnectionIdempotently,
35
+ encryptEnvironmentValue,
36
+ getConnectionMetadata,
37
+ listConnectionsMetadata,
38
+ persistProviderOAuthConnection,
39
+ } from "@opengeni/db";
40
+ import { createSignedState, readSignedState } from "@opengeni/github";
41
+ import { readResponseJsonBounded, type FetchLike } from "@opengeni/network";
42
+ import { HTTPException } from "hono/http-exception";
43
+ import {
44
+ PERSONAL_OWNER_VERIFIED_STATE_CLAIM,
45
+ personalOnlyConnectionPrincipalMessage,
46
+ personalOwnerStateAccepted,
47
+ personalOwnerVerifiedInState,
48
+ } from "../connection-ownership";
49
+ import {
50
+ integrationBaseUrl,
51
+ oauthStateFailureReturn,
52
+ oauthStateTtlMs,
53
+ requireIntegrationsStateSecret,
54
+ workspaceIntegrationsPath,
55
+ } from "./oauth-client";
56
+
57
+ const PERSONAL_GITHUB_OAUTH_STATE_KIND = "personal_github_oauth";
58
+ const GITHUB_RESPONSE_MAX_BYTES = 256 * 1024;
59
+ const GITHUB_REQUEST_TIMEOUT_MS = 10_000;
60
+ const GITHUB_API_VERSION = "2022-11-28";
61
+
62
+ type PersonalGitHubOAuthState = {
63
+ connectAttemptId?: string;
64
+ externalContinuation?: ExternalActorContinuation;
65
+ accountId: string;
66
+ workspaceId: string;
67
+ subjectId: string;
68
+ personalOwnerVerified: boolean;
69
+ canonicalManagedHumanSession: boolean;
70
+ returnPath: string;
71
+ encryptedPkceVerifier: string;
72
+ oauthEnvironment: string;
73
+ oauthClientMarker: string;
74
+ connectionId?: string;
75
+ connectionVersion?: number;
76
+ nonce: string;
77
+ iat: number;
78
+ };
79
+
80
+ type GitHubToken = {
81
+ accessToken: string;
82
+ refreshToken: string | null;
83
+ tokenType: "Bearer";
84
+ expiresAt: Date | null;
85
+ refreshTokenExpiresAt: Date | null;
86
+ scopes: string[];
87
+ };
88
+
89
+ type GitHubIdentity = {
90
+ id: string;
91
+ login: string;
92
+ };
93
+
94
+ type PersonalGitHubFailureReason =
95
+ | "account_mismatch"
96
+ | "client_changed"
97
+ | "connection_conflict"
98
+ | "disabled"
99
+ | "identity_failed"
100
+ | "invalid_state"
101
+ | "missing_code"
102
+ | "not_authorized"
103
+ | "provider_denied"
104
+ | "scope_not_granted"
105
+ | "state_replayed"
106
+ | "token_exchange_failed";
107
+
108
+ class PersonalGitHubCallbackError extends Error {
109
+ constructor(readonly reason: PersonalGitHubFailureReason) {
110
+ super(reason);
111
+ this.name = "PersonalGitHubCallbackError";
112
+ }
113
+ }
114
+
115
+ export function personalGitHubClientMarker(clientId: string): string {
116
+ return createHash("sha256").update(clientId).digest("hex").slice(0, 32);
117
+ }
118
+
119
+ export function requirePersonalGitHubOAuthSettings(settings: Settings): {
120
+ clientId: string;
121
+ clientSecret: string;
122
+ callbackUrl: string;
123
+ oauthEnvironment: string;
124
+ oauthClientMarker: string;
125
+ } {
126
+ if (!settings.githubPersonalOauthEnabled) {
127
+ throw new HTTPException(404, {
128
+ message: "personal GitHub OAuth is not enabled for this deployment",
129
+ });
130
+ }
131
+ const clientId = settings.githubPersonalOauthClientId?.trim();
132
+ const clientSecret = settings.githubPersonalOauthClientSecret?.trim();
133
+ const callbackUrl = personalGitHubOAuthCallbackUrl(settings.publicBaseUrl);
134
+ if (!clientId || !clientSecret || !callbackUrl) {
135
+ throw new HTTPException(503, { message: "personal GitHub OAuth is not configured" });
136
+ }
137
+ return {
138
+ clientId,
139
+ clientSecret,
140
+ callbackUrl,
141
+ oauthEnvironment: settings.environment,
142
+ oauthClientMarker: personalGitHubClientMarker(clientId),
143
+ };
144
+ }
145
+
146
+ export async function listPersonalGitHubConnections(
147
+ deps: ApiRouteDeps,
148
+ input: { workspaceId: string; subjectId: string },
149
+ ) {
150
+ const visible = await listConnectionsMetadata(deps.db, input.workspaceId, input.subjectId);
151
+ return visible.filter(
152
+ (connection) =>
153
+ connection.subjectId === input.subjectId && isPersonalGitHubConnection(connection),
154
+ );
155
+ }
156
+
157
+ export async function disconnectPersonalGitHub(
158
+ deps: ApiRouteDeps,
159
+ input: {
160
+ workspaceId: string;
161
+ subjectId: string;
162
+ connectionId: string;
163
+ payload: PersonalGitHubDisconnectRequest;
164
+ },
165
+ ) {
166
+ const existing = await getConnectionMetadata(
167
+ deps.db,
168
+ input.workspaceId,
169
+ input.connectionId,
170
+ input.subjectId,
171
+ );
172
+ if (
173
+ !existing ||
174
+ existing.subjectId !== input.subjectId ||
175
+ !isPersonalGitHubConnection(existing)
176
+ ) {
177
+ throw new HTTPException(404, { message: "personal GitHub connection not found" });
178
+ }
179
+ const metadata = PersonalGitHubConnectionMetadata.parse(existing.metadata);
180
+ try {
181
+ return await disconnectConnectionIdempotently(deps.db, {
182
+ accountId: existing.accountId,
183
+ workspaceId: input.workspaceId,
184
+ subjectId: input.subjectId,
185
+ connectionId: existing.id,
186
+ expectedVersion: input.payload.expectedVersion,
187
+ idempotencyKey: input.payload.idempotencyKey,
188
+ metadata: PersonalGitHubConnectionMetadata.parse({
189
+ ...metadata,
190
+ disconnectedAt: new Date().toISOString(),
191
+ }),
192
+ lastError: null,
193
+ updatedBySubjectId: input.subjectId,
194
+ });
195
+ } catch (error) {
196
+ if (error instanceof ConnectionDisconnectIdempotencyError) {
197
+ throw new HTTPException(409, {
198
+ message: "personal GitHub disconnect key was already used for another operation",
199
+ });
200
+ }
201
+ if (error instanceof ConnectionDisconnectGenerationError) {
202
+ throw new HTTPException(409, {
203
+ message: "personal GitHub connection changed; refresh before disconnecting",
204
+ });
205
+ }
206
+ throw error;
207
+ }
208
+ }
209
+
210
+ export async function startPersonalGitHubOAuth(
211
+ deps: ApiRouteDeps,
212
+ input: {
213
+ access: AccessGrantAuthorization;
214
+ workspaceId: string;
215
+ connectionId?: string;
216
+ returnPath?: string;
217
+ connectAttemptId?: string;
218
+ },
219
+ ) {
220
+ const oauth = requirePersonalGitHubOAuthSettings(deps.settings);
221
+ const { grant } = input.access;
222
+ await withOrganizationIntegrationAcquisition(deps.db, grant, ["github-personal"], async () => {});
223
+ const existing = input.connectionId
224
+ ? await getConnectionMetadata(deps.db, input.workspaceId, input.connectionId, grant.subjectId)
225
+ : null;
226
+ if (input.connectionId && !existing) {
227
+ throw new HTTPException(404, { message: "personal GitHub connection not found" });
228
+ }
229
+ if (
230
+ existing &&
231
+ (existing.subjectId !== grant.subjectId || !isPersonalGitHubConnection(existing))
232
+ ) {
233
+ throw new HTTPException(422, {
234
+ message: "connectionId is not the caller's personal GitHub connection",
235
+ });
236
+ }
237
+
238
+ const key = requireEnvironmentEncryption(deps.settings);
239
+ const verifier = randomBytes(48).toString("base64url");
240
+ const returnPath = personalGitHubReturnPath(input.workspaceId, input.returnPath);
241
+ const state = createSignedState(requireIntegrationsStateSecret(deps.settings), {
242
+ accountId: grant.accountId,
243
+ workspaceId: input.workspaceId,
244
+ subjectId: grant.subjectId,
245
+ kind: PERSONAL_GITHUB_OAUTH_STATE_KIND,
246
+ ...(input.connectAttemptId ? { connectAttemptId: input.connectAttemptId } : {}),
247
+ ...(externalActorContinuationForAuthorization(input.access)
248
+ ? {
249
+ encryptedExternalContinuation: encryptEnvironmentValue(
250
+ key,
251
+ JSON.stringify(externalActorContinuationForAuthorization(input.access)),
252
+ ),
253
+ }
254
+ : {}),
255
+ [PERSONAL_OWNER_VERIFIED_STATE_CLAIM]: true,
256
+ canonicalManagedHumanSession: input.access.canonicalManagedHumanSession,
257
+ returnPath,
258
+ encryptedPkceVerifier: encryptEnvironmentValue(key, verifier),
259
+ oauthEnvironment: oauth.oauthEnvironment,
260
+ oauthClientMarker: oauth.oauthClientMarker,
261
+ ...(existing ? { connectionId: existing.id, connectionVersion: existing.version } : {}),
262
+ });
263
+ const authorizationUrl = new URL(PERSONAL_GITHUB_AUTHORIZATION_URL);
264
+ authorizationUrl.searchParams.set("client_id", oauth.clientId);
265
+ authorizationUrl.searchParams.set("redirect_uri", oauth.callbackUrl);
266
+ authorizationUrl.searchParams.set("scope", PERSONAL_GITHUB_REQUESTED_SCOPES.join(" "));
267
+ authorizationUrl.searchParams.set("state", state);
268
+ authorizationUrl.searchParams.set(
269
+ "code_challenge",
270
+ createHash("sha256").update(verifier).digest("base64url"),
271
+ );
272
+ authorizationUrl.searchParams.set("code_challenge_method", "S256");
273
+ return PersonalGitHubOAuthStartResponse.parse({
274
+ authorizationUrl: authorizationUrl.toString(),
275
+ expiresAt: new Date(Date.now() + oauthStateTtlMs).toISOString(),
276
+ });
277
+ }
278
+
279
+ export async function completePersonalGitHubOAuthCallback(
280
+ deps: ApiRouteDeps,
281
+ input: {
282
+ code?: string;
283
+ state?: string;
284
+ error?: string;
285
+ requestUrl: string;
286
+ },
287
+ ): Promise<{ redirectTo: string; exactReturn?: boolean }> {
288
+ const apiBaseUrl = integrationBaseUrl(deps.settings.publicBaseUrl, input.requestUrl);
289
+ const returnBaseUrl = deps.settings.webBaseUrl?.replace(/\/+$/u, "") ?? apiBaseUrl;
290
+ let state: PersonalGitHubOAuthState | null = null;
291
+ let exactReturnUrl: string | undefined;
292
+ let operation: { attemptId: string; operationId: string; inputDigest: string } | undefined;
293
+ try {
294
+ state = readPersonalGitHubOAuthState(input.state, deps.settings);
295
+ if (state.connectAttemptId) {
296
+ const stored = await getConnectAttempt(deps.db, state, state.connectAttemptId);
297
+ if (
298
+ stored.attempt.providerId !== "github-personal" ||
299
+ stored.attempt.ownership !== "personal"
300
+ )
301
+ throw new PersonalGitHubCallbackError("invalid_state");
302
+ exactReturnUrl = stored.returnUrl;
303
+ operation = {
304
+ attemptId: state.connectAttemptId,
305
+ operationId: `oauth:${state.nonce}`,
306
+ inputDigest: createHash("sha256").update(input.state!).digest("hex"),
307
+ };
308
+ const claim = await claimOAuthAcquisition(
309
+ deps.db,
310
+ state,
311
+ {
312
+ ...operation,
313
+ expectedRevision: stored.attempt.revision,
314
+ authorize: (tx, _attempt, origin) =>
315
+ requireConnectOwnerAuthority(tx, state!, "connections:write", origin),
316
+ },
317
+ "github-personal",
318
+ Boolean(input.code && !input.error),
319
+ );
320
+ if (claim.status === "replayed") return { redirectTo: exactReturnUrl, exactReturn: true };
321
+ }
322
+ await requirePersonalGitHubCallbackGrant(deps, state);
323
+ const consumed = await consumeIntegrationOAuthStateNonce(deps.db, {
324
+ accountId: state.accountId,
325
+ workspaceId: state.workspaceId,
326
+ subjectId: state.subjectId,
327
+ nonce: state.nonce,
328
+ expiresAt: new Date(state.iat * 1000 + oauthStateTtlMs),
329
+ now: new Date(),
330
+ });
331
+ if (!consumed) throw new PersonalGitHubCallbackError("state_replayed");
332
+ if (operation && (input.error || !input.code)) {
333
+ await finishConnectOperation(deps.db, state, {
334
+ ...operation,
335
+ authorize: (tx, _attempt, origin) =>
336
+ requireConnectOwnerAuthority(tx, state!, "connections:write", origin),
337
+ commit: async (_tx, current) => ({
338
+ ...current,
339
+ revision: current.revision + 1,
340
+ state: input.error === "access_denied" ? "cancelled" : "failed",
341
+ nextAction: { type: "none" },
342
+ error: {
343
+ code: input.error ? "provider_denied" : "missing_code",
344
+ message: "Authorization was not completed. Start a new connection attempt.",
345
+ retryable: false,
346
+ },
347
+ }),
348
+ });
349
+ return { redirectTo: exactReturnUrl!, exactReturn: true };
350
+ }
351
+ if (input.error) throw new PersonalGitHubCallbackError("provider_denied");
352
+ if (!input.code) throw new PersonalGitHubCallbackError("missing_code");
353
+ await withOrganizationIntegrationAcquisition(
354
+ deps.db,
355
+ state,
356
+ ["github-personal"],
357
+ async () => {},
358
+ );
359
+
360
+ const oauth = requirePersonalGitHubOAuthSettings(deps.settings);
361
+ if (
362
+ oauth.oauthEnvironment !== state.oauthEnvironment ||
363
+ oauth.oauthClientMarker !== state.oauthClientMarker
364
+ ) {
365
+ throw new PersonalGitHubCallbackError("client_changed");
366
+ }
367
+ const key = requireEnvironmentEncryption(deps.settings);
368
+ let verifier: string;
369
+ try {
370
+ verifier = decryptEnvironmentValue(key, state.encryptedPkceVerifier);
371
+ } catch {
372
+ throw new PersonalGitHubCallbackError("invalid_state");
373
+ }
374
+ const fetchImpl = deps.githubPersonalFetch ?? fetch;
375
+ const token = await exchangePersonalGitHubAuthorizationCode(
376
+ fetchImpl,
377
+ oauth,
378
+ input.code,
379
+ verifier,
380
+ );
381
+ assertExactPersonalGitHubScopes(token.scopes);
382
+ const identity = await fetchPersonalGitHubIdentity(fetchImpl, token.accessToken);
383
+ await requirePersonalGitHubCallbackGrant(deps, state);
384
+
385
+ const requested = state.connectionId
386
+ ? await getConnectionMetadata(deps.db, state.workspaceId, state.connectionId, state.subjectId)
387
+ : null;
388
+ if (
389
+ state.connectionId &&
390
+ (!requested ||
391
+ requested.version !== state.connectionVersion ||
392
+ !isPersonalGitHubConnection(requested))
393
+ ) {
394
+ throw new PersonalGitHubCallbackError("connection_conflict");
395
+ }
396
+ const requestedMetadata = requested
397
+ ? PersonalGitHubConnectionMetadata.parse(requested.metadata)
398
+ : null;
399
+ if (requestedMetadata && requestedMetadata.providerPrincipalId !== identity.id) {
400
+ throw new PersonalGitHubCallbackError("account_mismatch");
401
+ }
402
+ if (!requested) {
403
+ const active = (
404
+ await listPersonalGitHubConnections(deps, {
405
+ workspaceId: state.workspaceId,
406
+ subjectId: state.subjectId,
407
+ })
408
+ ).filter((connection) => connection.status !== "revoked");
409
+ if (
410
+ active.some(
411
+ (connection) =>
412
+ PersonalGitHubConnectionMetadata.parse(connection.metadata).providerPrincipalId !==
413
+ identity.id,
414
+ )
415
+ ) {
416
+ throw new PersonalGitHubCallbackError("account_mismatch");
417
+ }
418
+ }
419
+
420
+ const now = new Date().toISOString();
421
+ const metadata = PersonalGitHubConnectionMetadata.parse({
422
+ ...(requested?.metadata ?? {}),
423
+ credentialRole: PERSONAL_GITHUB_CREDENTIAL_ROLE,
424
+ providerFamily: PERSONAL_GITHUB_PROVIDER_FAMILY,
425
+ providerPrincipalId: identity.id,
426
+ githubUserId: identity.id,
427
+ githubLogin: identity.login,
428
+ oauthEnvironment: oauth.oauthEnvironment,
429
+ oauthClientMarker: oauth.oauthClientMarker,
430
+ credentialBindingId: requestedMetadata?.credentialBindingId ?? randomUUID(),
431
+ connectedAt: requestedMetadata?.connectedAt ?? now,
432
+ lastVerifiedAt: now,
433
+ disconnectedAt: null,
434
+ refreshTokenExpiresAt: token.refreshTokenExpiresAt?.toISOString() ?? null,
435
+ });
436
+ const credentialEncrypted = encryptEnvironmentValue(
437
+ key,
438
+ JSON.stringify({
439
+ access_token: token.accessToken,
440
+ ...(token.refreshToken ? { refresh_token: token.refreshToken } : {}),
441
+ token_type: token.tokenType,
442
+ ...(token.expiresAt ? { expires_at: token.expiresAt.toISOString() } : {}),
443
+ ...(token.refreshTokenExpiresAt
444
+ ? { refresh_token_expires_at: token.refreshTokenExpiresAt.toISOString() }
445
+ : {}),
446
+ scope: token.scopes.join(" "),
447
+ token_endpoint: PERSONAL_GITHUB_TOKEN_URL,
448
+ client_id: oauth.clientId,
449
+ token_endpoint_auth_method: "client_secret_post",
450
+ scope_parameter_supported: false,
451
+ }),
452
+ );
453
+ const acceptedState = state;
454
+ const persist = (tx: Database) => {
455
+ // Capture the validated immutable callback state for delayed persistence.
456
+ // eslint-disable-next-line no-shadow
457
+ const state = acceptedState;
458
+ return persistProviderOAuthConnection(tx, {
459
+ accountId: state.accountId,
460
+ workspaceId: state.workspaceId,
461
+ subjectId: state.subjectId,
462
+ visibleToSubjectId: state.subjectId,
463
+ providerDomain: PERSONAL_GITHUB_PROVIDER_DOMAIN,
464
+ kind: "oauth2",
465
+ status: "active",
466
+ credentialEncrypted,
467
+ grantedScopes: token.scopes,
468
+ expiresAt: token.expiresAt,
469
+ metadata,
470
+ createdBySubjectId: state.subjectId,
471
+ updatedBySubjectId: state.subjectId,
472
+ credentialRole: PERSONAL_GITHUB_CREDENTIAL_ROLE,
473
+ providerFamily: PERSONAL_GITHUB_PROVIDER_FAMILY,
474
+ providerPrincipalId: identity.id,
475
+ requireLiveUserAuthority: true,
476
+ requiredLiveUserPermission: "connections:write",
477
+ // The external lane has passed its own owning-user proof; it does not
478
+ // impersonate a native login. Both use the same personal workspace anchor.
479
+ allowCanonicalPersonalWorkspaceOwner:
480
+ state.canonicalManagedHumanSession || Boolean(state.externalContinuation),
481
+ authorize: (locked) => requireConnectOwnerAuthority(locked, state, "connections:write"),
482
+ exclusiveProviderPrincipalPerOwner: true,
483
+ preserveExistingMetadataKeys: ["credentialBindingId", "connectedAt"],
484
+ ...(state.connectionId
485
+ ? {
486
+ requestedConnectionId: state.connectionId,
487
+ requestedConnectionVersion: state.connectionVersion,
488
+ }
489
+ : {}),
490
+ });
491
+ };
492
+ if (operation) {
493
+ await finishOAuthAcquisition(
494
+ deps.db,
495
+ acceptedState,
496
+ {
497
+ ...operation,
498
+ authorize: (tx, _attempt, origin) =>
499
+ requireConnectOwnerAuthority(tx, acceptedState, "connections:write", origin),
500
+ commit: async (tx, current) => {
501
+ const connection = await persist(tx);
502
+ if (!connection) throw new PersonalGitHubCallbackError("connection_conflict");
503
+ return {
504
+ ...current,
505
+ revision: current.revision + 1,
506
+ state: "complete",
507
+ credentialsCommitted: true,
508
+ nextAction: { type: "none" },
509
+ account: {
510
+ id: connection.id,
511
+ version: connection.version,
512
+ providerId: "github-personal",
513
+ label: identity.login,
514
+ ownership: "personal",
515
+ status: "connected",
516
+ },
517
+ };
518
+ },
519
+ },
520
+ "github-personal",
521
+ );
522
+ return { redirectTo: exactReturnUrl!, exactReturn: true };
523
+ }
524
+ const connection = await withOrganizationIntegrationAcquisition(
525
+ deps.db,
526
+ acceptedState,
527
+ ["github-personal"],
528
+ persist,
529
+ );
530
+ if (!connection) throw new PersonalGitHubCallbackError("connection_conflict");
531
+ return {
532
+ redirectTo: personalGitHubCallbackReturnUrl(returnBaseUrl, state.returnPath, "success", {
533
+ connectionId: connection.id,
534
+ }),
535
+ };
536
+ } catch (error) {
537
+ if (exactReturnUrl) return { redirectTo: exactReturnUrl, exactReturn: true };
538
+ return {
539
+ redirectTo: personalGitHubCallbackReturnUrl(
540
+ returnBaseUrl,
541
+ state?.returnPath ?? oauthStateFailureReturn(deps.settings, input.state).returnPath,
542
+ "error",
543
+ { reason: personalGitHubFailureReason(error) },
544
+ ),
545
+ };
546
+ }
547
+ }
548
+
549
+ export function personalGitHubReviewUrl(settings: Settings): string | null {
550
+ const clientId = settings.githubPersonalOauthClientId?.trim();
551
+ return clientId
552
+ ? `https://github.com/settings/connections/applications/${encodeURIComponent(clientId)}`
553
+ : null;
554
+ }
555
+
556
+ function readPersonalGitHubOAuthState(
557
+ raw: string | undefined,
558
+ settings: Settings,
559
+ ): PersonalGitHubOAuthState {
560
+ if (!raw) throw new PersonalGitHubCallbackError("invalid_state");
561
+ let secret: string;
562
+ try {
563
+ secret = requireIntegrationsStateSecret(settings);
564
+ } catch {
565
+ throw new PersonalGitHubCallbackError("disabled");
566
+ }
567
+ const payload = readSignedState(raw, secret) as Record<string, unknown> | null;
568
+ if (!payload || payload.kind !== PERSONAL_GITHUB_OAUTH_STATE_KIND) {
569
+ throw new PersonalGitHubCallbackError("invalid_state");
570
+ }
571
+ const iat = numberValue(payload.iat);
572
+ const connectionId = optionalString(payload.connectionId);
573
+ const connectionVersion = numberValue(payload.connectionVersion);
574
+ const issuedAtMs = iat === null ? null : iat * 1000;
575
+ const nowMs = Date.now();
576
+ if (
577
+ iat === null ||
578
+ !Number.isInteger(iat) ||
579
+ issuedAtMs === null ||
580
+ issuedAtMs > nowMs + 60_000 ||
581
+ nowMs >= issuedAtMs + oauthStateTtlMs ||
582
+ (connectionVersion !== null && !Number.isInteger(connectionVersion)) ||
583
+ Boolean(connectionId) !== Boolean(connectionVersion)
584
+ ) {
585
+ throw new PersonalGitHubCallbackError("invalid_state");
586
+ }
587
+ const workspaceId = requiredString(payload.workspaceId);
588
+ return {
589
+ accountId: requiredString(payload.accountId),
590
+ workspaceId,
591
+ subjectId: requiredString(payload.subjectId),
592
+ personalOwnerVerified: personalOwnerVerifiedInState(payload),
593
+ canonicalManagedHumanSession: payload.canonicalManagedHumanSession === true,
594
+ ...(typeof payload.connectAttemptId === "string"
595
+ ? { connectAttemptId: payload.connectAttemptId }
596
+ : {}),
597
+ ...(typeof payload.encryptedExternalContinuation === "string"
598
+ ? {
599
+ externalContinuation: ExternalActorContinuation.parse(
600
+ JSON.parse(
601
+ decryptEnvironmentValue(
602
+ requireEnvironmentEncryption(settings),
603
+ payload.encryptedExternalContinuation,
604
+ ),
605
+ ),
606
+ ),
607
+ }
608
+ : {}),
609
+ returnPath: personalGitHubReturnPath(workspaceId, requiredString(payload.returnPath)),
610
+ encryptedPkceVerifier: requiredString(payload.encryptedPkceVerifier),
611
+ oauthEnvironment: requiredString(payload.oauthEnvironment),
612
+ oauthClientMarker: requiredString(payload.oauthClientMarker),
613
+ ...(connectionId ? { connectionId, connectionVersion: connectionVersion! } : {}),
614
+ nonce: requiredString(payload.nonce),
615
+ iat,
616
+ };
617
+ }
618
+
619
+ async function requirePersonalGitHubCallbackGrant(
620
+ deps: ApiRouteDeps,
621
+ state: PersonalGitHubOAuthState,
622
+ ): Promise<void> {
623
+ if (
624
+ !personalOwnerStateAccepted({
625
+ ownership: "personal",
626
+ subjectId: state.subjectId,
627
+ personalOwnerVerified: state.personalOwnerVerified,
628
+ })
629
+ ) {
630
+ throw new PersonalGitHubCallbackError("not_authorized");
631
+ }
632
+ await requireConnectOwnerAuthority(deps.db, state, "connections:write");
633
+ }
634
+
635
+ async function exchangePersonalGitHubAuthorizationCode(
636
+ fetchImpl: FetchLike,
637
+ oauth: ReturnType<typeof requirePersonalGitHubOAuthSettings>,
638
+ code: string,
639
+ verifier: string,
640
+ ): Promise<GitHubToken> {
641
+ let response: Response;
642
+ try {
643
+ response = await fetchImpl(PERSONAL_GITHUB_TOKEN_URL, {
644
+ method: "POST",
645
+ redirect: "manual",
646
+ headers: {
647
+ accept: "application/json",
648
+ "content-type": "application/x-www-form-urlencoded",
649
+ "user-agent": "OpenGeni",
650
+ },
651
+ body: new URLSearchParams({
652
+ client_id: oauth.clientId,
653
+ client_secret: oauth.clientSecret,
654
+ code,
655
+ redirect_uri: oauth.callbackUrl,
656
+ code_verifier: verifier,
657
+ }),
658
+ signal: AbortSignal.timeout(GITHUB_REQUEST_TIMEOUT_MS),
659
+ });
660
+ } catch {
661
+ throw new PersonalGitHubCallbackError("token_exchange_failed");
662
+ }
663
+ if (!response.ok || (response.status >= 300 && response.status < 400)) {
664
+ await consumeBoundedJson(response, "GitHub OAuth error response");
665
+ throw new PersonalGitHubCallbackError("token_exchange_failed");
666
+ }
667
+ const payload = await readResponseJsonBounded<Record<string, unknown>>(
668
+ response,
669
+ GITHUB_RESPONSE_MAX_BYTES,
670
+ "GitHub OAuth token response",
671
+ ).catch(() => {
672
+ throw new PersonalGitHubCallbackError("token_exchange_failed");
673
+ });
674
+ const accessToken = optionalString(payload.access_token);
675
+ const tokenType = optionalString(payload.token_type)?.toLowerCase();
676
+ if (!accessToken || tokenType !== "bearer") {
677
+ throw new PersonalGitHubCallbackError("token_exchange_failed");
678
+ }
679
+ const expiresIn = positiveInteger(payload.expires_in);
680
+ const refreshToken = optionalString(payload.refresh_token);
681
+ if (expiresIn !== null && !refreshToken) {
682
+ throw new PersonalGitHubCallbackError("token_exchange_failed");
683
+ }
684
+ const refreshExpiresIn = positiveInteger(payload.refresh_token_expires_in);
685
+ return {
686
+ accessToken,
687
+ refreshToken,
688
+ tokenType: "Bearer",
689
+ expiresAt: expiresIn === null ? null : new Date(Date.now() + expiresIn * 1000),
690
+ refreshTokenExpiresAt:
691
+ refreshExpiresIn === null ? null : new Date(Date.now() + refreshExpiresIn * 1000),
692
+ scopes: parseGitHubScopes(optionalString(payload.scope)),
693
+ };
694
+ }
695
+
696
+ async function fetchPersonalGitHubIdentity(
697
+ fetchImpl: FetchLike,
698
+ accessToken: string,
699
+ ): Promise<GitHubIdentity> {
700
+ let response: Response;
701
+ try {
702
+ response = await fetchImpl(PERSONAL_GITHUB_USER_URL, {
703
+ method: "GET",
704
+ redirect: "manual",
705
+ headers: {
706
+ accept: "application/vnd.github+json",
707
+ authorization: `Bearer ${accessToken}`,
708
+ "user-agent": "OpenGeni",
709
+ "x-github-api-version": GITHUB_API_VERSION,
710
+ },
711
+ signal: AbortSignal.timeout(GITHUB_REQUEST_TIMEOUT_MS),
712
+ });
713
+ } catch {
714
+ throw new PersonalGitHubCallbackError("identity_failed");
715
+ }
716
+ if (!response.ok || (response.status >= 300 && response.status < 400)) {
717
+ await consumeBoundedJson(response, "GitHub identity error response");
718
+ throw new PersonalGitHubCallbackError("identity_failed");
719
+ }
720
+ const headerScopes = response.headers.get("x-oauth-scopes");
721
+ if (headerScopes) assertExactPersonalGitHubScopes(parseGitHubScopes(headerScopes));
722
+ const payload = await readResponseJsonBounded<Record<string, unknown>>(
723
+ response,
724
+ GITHUB_RESPONSE_MAX_BYTES,
725
+ "GitHub identity response",
726
+ ).catch(() => {
727
+ throw new PersonalGitHubCallbackError("identity_failed");
728
+ });
729
+ const id = positiveInteger(payload.id);
730
+ const login = optionalString(payload.login);
731
+ if (id === null || !Number.isSafeInteger(id) || !login || login.length > 100) {
732
+ throw new PersonalGitHubCallbackError("identity_failed");
733
+ }
734
+ return { id: String(id), login };
735
+ }
736
+
737
+ function assertExactPersonalGitHubScopes(scopes: string[]): void {
738
+ const exact = [...new Set(scopes)].sort();
739
+ if (exact.length !== 1 || exact[0] !== "repo") {
740
+ throw new PersonalGitHubCallbackError("scope_not_granted");
741
+ }
742
+ }
743
+
744
+ function parseGitHubScopes(raw: string | null): string[] {
745
+ if (!raw) return [];
746
+ return [
747
+ ...new Set(
748
+ raw
749
+ .split(/[\s,]+/u)
750
+ .map((scope) => scope.trim())
751
+ .filter(Boolean),
752
+ ),
753
+ ].sort();
754
+ }
755
+
756
+ function personalGitHubReturnPath(workspaceId: string, raw?: string): string {
757
+ const fallback = workspaceIntegrationsPath(workspaceId);
758
+ if (!raw) return fallback;
759
+ let parsed: URL;
760
+ try {
761
+ parsed = new URL(raw, "https://opengeni.invalid");
762
+ } catch {
763
+ throw new HTTPException(400, { message: "invalid personal GitHub return path" });
764
+ }
765
+ const workspacePrefix = `/workspaces/${workspaceId}`;
766
+ if (
767
+ parsed.origin !== "https://opengeni.invalid" ||
768
+ (parsed.pathname !== workspacePrefix && !parsed.pathname.startsWith(`${workspacePrefix}/`))
769
+ ) {
770
+ throw new HTTPException(400, { message: "invalid personal GitHub return path" });
771
+ }
772
+ return `${parsed.pathname}${parsed.search}${parsed.hash}`;
773
+ }
774
+
775
+ function personalGitHubCallbackReturnUrl(
776
+ baseUrl: string,
777
+ returnPath: string,
778
+ status: "success" | "error",
779
+ params: Record<string, string>,
780
+ ): string {
781
+ let safePath: string;
782
+ try {
783
+ const match = returnPath.match(/^\/workspaces\/([^/]+)/u);
784
+ safePath = match ? personalGitHubReturnPath(match[1]!, returnPath) : "/integrations";
785
+ } catch {
786
+ safePath = "/integrations";
787
+ }
788
+ const url = new URL(safePath, `${baseUrl.replace(/\/+$/u, "")}/`);
789
+ url.searchParams.set("github_personal_oauth", status);
790
+ for (const [key, value] of Object.entries(params)) url.searchParams.set(key, value.slice(0, 256));
791
+ return url.toString();
792
+ }
793
+
794
+ function personalGitHubFailureReason(error: unknown): PersonalGitHubFailureReason {
795
+ if (error instanceof PersonalGitHubCallbackError) return error.reason;
796
+ if (error instanceof HTTPException && error.status === 404) return "disabled";
797
+ return "connection_conflict";
798
+ }
799
+
800
+ async function consumeBoundedJson(response: Response, label: string): Promise<void> {
801
+ await readResponseJsonBounded(response, GITHUB_RESPONSE_MAX_BYTES, label).catch(async () => {
802
+ await response.body?.cancel().catch(() => undefined);
803
+ });
804
+ }
805
+
806
+ function requiredString(value: unknown): string {
807
+ const parsed = optionalString(value);
808
+ if (!parsed) throw new PersonalGitHubCallbackError("invalid_state");
809
+ return parsed;
810
+ }
811
+
812
+ function optionalString(value: unknown): string | null {
813
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
814
+ }
815
+
816
+ function numberValue(value: unknown): number | null {
817
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
818
+ }
819
+
820
+ function positiveInteger(value: unknown): number | null {
821
+ return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : null;
822
+ }
823
+
824
+ export const PERSONAL_GITHUB_CONNECTION_PRINCIPAL_MESSAGE =
825
+ personalOnlyConnectionPrincipalMessage("My GitHub account");