@opengeni/api-router 0.30.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (436) hide show
  1. package/dist/access-grant-rls.d.ts +3 -0
  2. package/dist/admission-trace.d.ts +8 -0
  3. package/dist/api-websocket.d.ts +18 -0
  4. package/dist/app.d.ts +50 -2
  5. package/dist/app.js +12 -1
  6. package/dist/artifact-catalog-cursor.d.ts +27 -0
  7. package/dist/auth/managed-auth-attempt-context.d.ts +10 -0
  8. package/dist/auth/managed-auth-rate-limits.d.ts +167 -0
  9. package/dist/auth/managed-auth-session-adapter.d.ts +6 -0
  10. package/dist/auth/managed-auth.d.ts +102 -3
  11. package/dist/auth/managed-email.d.ts +29 -0
  12. package/dist/auth/managed-sign-in-notifications.d.ts +4 -0
  13. package/dist/auth/new-signups-gate.d.ts +23 -0
  14. package/dist/auth/organization-user-setup.d.ts +80 -0
  15. package/dist/auth/signup-funnel-metrics.d.ts +44 -0
  16. package/dist/browser-controller-authority.d.ts +1 -61
  17. package/dist/browser-ephemeral.d.ts +5 -0
  18. package/dist/browser-state-authority.d.ts +1 -27
  19. package/dist/chunk-6TZ5LG3O.js +123948 -0
  20. package/dist/chunk-6TZ5LG3O.js.map +1 -0
  21. package/dist/chunk-OGQDXVXY.js +18 -0
  22. package/dist/chunk-OGQDXVXY.js.map +1 -0
  23. package/dist/claude-subscription-account-conflicts.d.ts +1 -0
  24. package/dist/claude-subscription-account-usage.d.ts +39 -0
  25. package/dist/claude-subscription-oauth.d.ts +25 -0
  26. package/dist/claude-subscription-profile.d.ts +9 -0
  27. package/dist/claude-subscription-usage.d.ts +44 -0
  28. package/dist/claude-workspace-connection.d.ts +15 -0
  29. package/dist/codemode.d.ts +32 -1
  30. package/dist/company-brain-okf.d.ts +24 -0
  31. package/dist/connected-machine-computer-access.d.ts +11 -0
  32. package/dist/connection-ownership.d.ts +54 -0
  33. package/dist/controller-data-plane.d.ts +32 -0
  34. package/dist/editable-artifact-office-import.d.ts +7 -4
  35. package/dist/editable-artifact-source-session.d.ts +3 -0
  36. package/dist/editable-artifact-websocket.d.ts +10 -18
  37. package/dist/editable-artifact-workspace-files.d.ts +1 -1
  38. package/dist/fatal-process-boundary.d.ts +25 -0
  39. package/dist/github-access.d.ts +31 -2
  40. package/dist/http/acting-person.d.ts +24 -0
  41. package/dist/http/api-contract-compat.d.ts +19 -0
  42. package/dist/http/api-error.d.ts +27 -1
  43. package/dist/http/cors.d.ts +17 -0
  44. package/dist/http/deprecation.d.ts +42 -0
  45. package/dist/http/interaction-control-error.d.ts +13 -0
  46. package/dist/http/keyed-admission.d.ts +12 -0
  47. package/dist/http/local-browser-boundary.d.ts +72 -0
  48. package/dist/http/metrics-listener.d.ts +22 -0
  49. package/dist/http/path-identifier.d.ts +17 -0
  50. package/dist/http/registered-route-label.d.ts +16 -0
  51. package/dist/http/request-body.d.ts +27 -0
  52. package/dist/http/request-source.d.ts +52 -0
  53. package/dist/http/sse.d.ts +17 -6
  54. package/dist/http/unmatched-route.d.ts +18 -0
  55. package/dist/http/user-content.d.ts +36 -0
  56. package/dist/http/websocket-upgrade-dispatch.d.ts +18 -0
  57. package/dist/index.d.ts +10 -3
  58. package/dist/index.js +1129 -166
  59. package/dist/index.js.map +1 -1
  60. package/dist/integration-endpoint-test.d.ts +20 -0
  61. package/dist/integrations/api-integrations.d.ts +2 -2
  62. package/dist/integrations/atlassian.d.ts +29 -3
  63. package/dist/integrations/connect-authority.d.ts +1 -0
  64. package/dist/integrations/connect-callback-return.d.ts +6 -0
  65. package/dist/integrations/fiken.d.ts +16 -2
  66. package/dist/integrations/github-app-connect.d.ts +47 -0
  67. package/dist/integrations/github-installation-proof.d.ts +3 -0
  68. package/dist/integrations/github-lens-connect.d.ts +116 -0
  69. package/dist/integrations/github-skill-source.d.ts +1 -5
  70. package/dist/integrations/google-drive.d.ts +25 -0
  71. package/dist/integrations/integration-commit-authority.d.ts +13 -0
  72. package/dist/integrations/marketplace-plugin.d.ts +2691 -0
  73. package/dist/integrations/oauth-client.d.ts +178 -18
  74. package/dist/integrations/oauth-profiles.d.ts +222 -0
  75. package/dist/integrations/oauth-return-path.d.ts +1 -0
  76. package/dist/integrations/personal-github-repositories.d.ts +84 -0
  77. package/dist/integrations/personal-github.d.ts +42 -0
  78. package/dist/integrations/pr-review-provider.d.ts +22 -0
  79. package/dist/integrations/provider-oauth.d.ts +19 -1
  80. package/dist/integrations/slack-app-home.d.ts +24 -0
  81. package/dist/integrations/slack-bot.d.ts +367 -12
  82. package/dist/integrations/slack-file-upload-flow.d.ts +62 -0
  83. package/dist/integrations/slack-install.d.ts +20 -0
  84. package/dist/integrations/slack-interactions.d.ts +113 -10
  85. package/dist/integrations/slack-mrkdwn.d.ts +41 -0
  86. package/dist/integrations/slack-routing.d.ts +120 -0
  87. package/dist/integrations/slack-session-defaults.d.ts +58 -0
  88. package/dist/integrations/slack-task-file-upload.d.ts +36 -0
  89. package/dist/integrations/social-oauth.d.ts +7 -0
  90. package/dist/interaction-agent-access.d.ts +15 -0
  91. package/dist/interaction-frame-proxy.d.ts +68 -0
  92. package/dist/interaction-metrics.d.ts +2 -0
  93. package/dist/mcp/action-catalog-types.d.ts +9 -0
  94. package/dist/mcp/action-catalog.gen.d.ts +2 -0
  95. package/dist/mcp/capability-account-readiness.d.ts +7 -0
  96. package/dist/mcp/child-read-evidence.d.ts +9 -0
  97. package/dist/mcp/command-read.d.ts +24 -0
  98. package/dist/mcp/company-profile-agent-admin.d.ts +44 -0
  99. package/dist/mcp/contract-input.d.ts +11 -0
  100. package/dist/mcp/documents.d.ts +7 -5
  101. package/dist/mcp/files.d.ts +2 -2
  102. package/dist/mcp/first-party-tool-permissions.d.ts +650 -0
  103. package/dist/mcp/knowledge-entries.d.ts +5 -0
  104. package/dist/mcp/orchestration-failure-diagnostic.d.ts +43 -0
  105. package/dist/mcp/receipts.d.ts +9 -0
  106. package/dist/mcp/request-abort.d.ts +19 -0
  107. package/dist/mcp/scheduled-task-input.d.ts +1189 -0
  108. package/dist/mcp/scheduled-task-view.d.ts +22 -4
  109. package/dist/mcp/server.d.ts +255 -3
  110. package/dist/mcp/session-event-view.d.ts +94 -0
  111. package/dist/mcp/session-view.d.ts +66 -2
  112. package/dist/mcp/session-wait.d.ts +174 -0
  113. package/dist/mcp-oauth.d.ts +52 -0
  114. package/dist/model-catalog.d.ts +11 -31
  115. package/dist/observability.d.ts +3 -1
  116. package/dist/organization-mcp.d.ts +52 -0
  117. package/dist/organization-recovery-notifications.d.ts +41 -0
  118. package/dist/routes/analytics-consent.d.ts +26 -0
  119. package/dist/routes/api-integrations.d.ts +25 -0
  120. package/dist/routes/api-keys.d.ts +17 -0
  121. package/dist/routes/artifact-catalog.d.ts +6 -0
  122. package/dist/routes/automations.d.ts +19 -0
  123. package/dist/routes/billing.d.ts +49 -0
  124. package/dist/routes/browser-sessions.d.ts +71 -3
  125. package/dist/routes/claude-subscription-accounts.d.ts +3 -0
  126. package/dist/routes/claude-subscription-oauth.d.ts +3 -0
  127. package/dist/routes/client-errors.d.ts +146 -0
  128. package/dist/routes/codex.d.ts +76 -2
  129. package/dist/routes/company-brain.d.ts +3 -0
  130. package/dist/routes/company-profile.d.ts +2 -1
  131. package/dist/routes/computer-sessions.d.ts +14 -0
  132. package/dist/routes/connect.d.ts +5 -0
  133. package/dist/routes/editable-artifacts.d.ts +2 -0
  134. package/dist/routes/external-identity-links.d.ts +6 -0
  135. package/dist/routes/feedback.d.ts +3 -0
  136. package/dist/routes/files.d.ts +4 -0
  137. package/dist/routes/github-browser-pages.d.ts +12 -0
  138. package/dist/routes/github.d.ts +2 -0
  139. package/dist/routes/insights-response-cache.d.ts +10 -0
  140. package/dist/routes/insights-usage.d.ts +12 -0
  141. package/dist/routes/insights.d.ts +30 -1
  142. package/dist/routes/knowledge.d.ts +3 -0
  143. package/dist/routes/managed-auth-session-sets.d.ts +26 -0
  144. package/dist/routes/managed-onboarding.d.ts +29 -0
  145. package/dist/routes/managed-sign-in-methods.d.ts +6 -0
  146. package/dist/routes/model-connection-access.d.ts +3 -0
  147. package/dist/routes/organization-integration-policy.d.ts +3 -0
  148. package/dist/routes/organization-integrations.d.ts +6 -0
  149. package/dist/routes/organization-mcp-connections.d.ts +3 -0
  150. package/dist/routes/organization-model-providers.d.ts +3 -0
  151. package/dist/routes/organization-recovery.d.ts +19 -0
  152. package/dist/routes/organization-service-accounts.d.ts +3 -0
  153. package/dist/routes/organization-sessions.d.ts +26 -0
  154. package/dist/routes/personal-github-git-broker.d.ts +29 -0
  155. package/dist/routes/personal-github.d.ts +3 -0
  156. package/dist/routes/pr-review-github.d.ts +3 -0
  157. package/dist/routes/pr-review.d.ts +3 -0
  158. package/dist/routes/session-artifact-associations.d.ts +4 -0
  159. package/dist/routes/session-history-imports.d.ts +9 -0
  160. package/dist/routes/sessions.d.ts +44 -6
  161. package/dist/routes/skill-content.d.ts +4 -0
  162. package/dist/routes/skill-install-authority.d.ts +6 -0
  163. package/dist/routes/subscription-account-pools.d.ts +34 -0
  164. package/dist/routes/subscription-pool-access.d.ts +16 -0
  165. package/dist/routes/supergrok.d.ts +6 -1
  166. package/dist/routes/usage-allowances.d.ts +12 -0
  167. package/dist/routes/user-resource-authorities.d.ts +3 -0
  168. package/dist/routes/workspace-artifacts.d.ts +2 -1
  169. package/dist/routes/workspace-capture.d.ts +10 -2
  170. package/dist/routes/workspace-integrations.d.ts +35 -0
  171. package/dist/routes/workspace-learning.d.ts +3 -0
  172. package/dist/routes/workspace-model-providers.d.ts +4 -0
  173. package/dist/routes/workspaces.d.ts +29 -0
  174. package/dist/sandbox/auth-callout.d.ts +8 -0
  175. package/dist/sandbox/channel-a.d.ts +19 -1
  176. package/dist/sandbox/connection-authority.d.ts +3 -0
  177. package/dist/sandbox/enrollment.d.ts +9 -1
  178. package/dist/sandbox/machines.d.ts +5 -2
  179. package/dist/sandbox/metrics-ingestion.d.ts +28 -20
  180. package/dist/sandbox/rematerialize.d.ts +2 -0
  181. package/dist/sandbox/session-attach-variable-sets.d.ts +30 -0
  182. package/dist/sandbox/viewer.d.ts +26 -1
  183. package/dist/sandbox-file-artifacts.d.ts +50 -0
  184. package/dist/scheduled-task-deletion.d.ts +13 -0
  185. package/dist/session-create-observability.d.ts +5 -0
  186. package/dist/session-event-view-MPS2OZD3.js +292 -0
  187. package/dist/session-event-view-MPS2OZD3.js.map +1 -0
  188. package/dist/site-session-origin.d.ts +13 -0
  189. package/dist/site-uploads.d.ts +41 -0
  190. package/dist/slack-reaction-files.d.ts +1 -1
  191. package/dist/temporal-schedule-cleanup.d.ts +1 -0
  192. package/dist/temporal-schedule-sync.d.ts +26 -0
  193. package/dist/transcription/providers/xai-subscription.d.ts +1 -0
  194. package/dist/transcription/service.d.ts +2 -1
  195. package/dist/work-discovery-observability.d.ts +33 -0
  196. package/dist/workspace-artifact-content.d.ts +28 -0
  197. package/dist/workspace-artifact-provenance.d.ts +7 -0
  198. package/dist/workspace-delete-observability.d.ts +10 -0
  199. package/dist/workspace-deletion.d.ts +6 -0
  200. package/dist/workspace-state-projection.d.ts +3 -5
  201. package/dist/workspace-tool-gateway-observability.d.ts +17 -0
  202. package/dist/workspace-tool-gateway.d.ts +123 -0
  203. package/dist/workspace-webhook-dispatch.d.ts +26 -0
  204. package/package.json +23 -19
  205. package/src/access-grant-rls.ts +75 -0
  206. package/src/admission-trace.ts +14 -0
  207. package/src/api-websocket.ts +23 -0
  208. package/src/app.ts +1974 -256
  209. package/src/artifact-catalog-cursor.ts +60 -0
  210. package/src/auth/managed-auth-attempt-context.ts +70 -0
  211. package/src/auth/managed-auth-rate-limits.ts +252 -0
  212. package/src/auth/managed-auth-session-adapter.ts +222 -0
  213. package/src/auth/managed-auth.ts +536 -60
  214. package/src/auth/managed-email.ts +174 -0
  215. package/src/auth/managed-sign-in-notifications.ts +81 -0
  216. package/src/auth/new-signups-gate.ts +49 -0
  217. package/src/auth/organization-user-setup.ts +307 -0
  218. package/src/auth/signup-funnel-metrics.ts +164 -0
  219. package/src/browser-controller-authority.ts +1 -186
  220. package/src/browser-ephemeral.ts +39 -0
  221. package/src/browser-state-authority.ts +1 -236
  222. package/src/claude-subscription-account-conflicts.ts +12 -0
  223. package/src/claude-subscription-account-usage.ts +48 -0
  224. package/src/claude-subscription-oauth.ts +362 -0
  225. package/src/claude-subscription-profile.ts +73 -0
  226. package/src/claude-subscription-usage.ts +97 -0
  227. package/src/claude-workspace-connection.ts +82 -0
  228. package/src/codemode.ts +194 -30
  229. package/src/codex-realtime.ts +8 -2
  230. package/src/company-brain-okf.ts +340 -0
  231. package/src/connected-machine-computer-access.ts +33 -0
  232. package/src/connection-ownership.ts +199 -0
  233. package/src/controller-data-plane.ts +81 -0
  234. package/src/editable-artifact-native-kernel.ts +26 -15
  235. package/src/editable-artifact-office-import.ts +77 -9
  236. package/src/editable-artifact-production.ts +23 -7
  237. package/src/editable-artifact-source-session.ts +123 -0
  238. package/src/editable-artifact-websocket.ts +27 -21
  239. package/src/editable-artifact-workspace-files.ts +31 -6
  240. package/src/fatal-process-boundary.ts +246 -0
  241. package/src/gateway-realtime.ts +1 -1
  242. package/src/github-access.ts +147 -5
  243. package/src/http/acting-person.ts +71 -0
  244. package/src/http/api-contract-compat.ts +49 -0
  245. package/src/http/api-error.ts +115 -1
  246. package/src/http/auth.ts +48 -0
  247. package/src/http/cors.ts +67 -0
  248. package/src/http/deprecation.ts +118 -0
  249. package/src/http/interaction-control-error.ts +164 -0
  250. package/src/http/keyed-admission.ts +29 -0
  251. package/src/http/local-browser-boundary.ts +343 -0
  252. package/src/http/metrics-listener.ts +57 -0
  253. package/src/http/path-identifier.ts +54 -0
  254. package/src/http/registered-route-label.ts +34 -0
  255. package/src/http/request-body.ts +127 -0
  256. package/src/http/request-source.ts +178 -0
  257. package/src/http/sse.ts +486 -132
  258. package/src/http/unmatched-route.ts +60 -0
  259. package/src/http/user-content.ts +158 -0
  260. package/src/http/websocket-upgrade-dispatch.ts +29 -0
  261. package/src/index.ts +157 -52
  262. package/src/integration-endpoint-test.ts +291 -0
  263. package/src/integrations/api-integrations.ts +98 -11
  264. package/src/integrations/atlassian.ts +474 -136
  265. package/src/integrations/connect-authority.ts +2 -0
  266. package/src/integrations/connect-callback-return.ts +86 -0
  267. package/src/integrations/fiken.ts +354 -64
  268. package/src/integrations/github-app-connect.ts +435 -0
  269. package/src/integrations/github-installation-proof.ts +60 -0
  270. package/src/integrations/github-lens-connect.ts +76 -0
  271. package/src/integrations/github-skill-source.ts +2 -142
  272. package/src/integrations/google-drive.ts +820 -222
  273. package/src/integrations/integration-commit-authority.ts +69 -0
  274. package/src/integrations/marketplace-plugin.ts +48 -0
  275. package/src/integrations/oauth-client.ts +1356 -459
  276. package/src/integrations/oauth-profiles.ts +537 -0
  277. package/src/integrations/oauth-return-path.ts +18 -0
  278. package/src/integrations/personal-github-repositories.ts +674 -0
  279. package/src/integrations/personal-github.ts +825 -0
  280. package/src/integrations/pr-review-provider.ts +246 -0
  281. package/src/integrations/provider-oauth.ts +250 -22
  282. package/src/integrations/slack-app-home.ts +300 -0
  283. package/src/integrations/slack-bot.ts +963 -239
  284. package/src/integrations/slack-file-upload-flow.ts +147 -0
  285. package/src/integrations/slack-install.ts +83 -0
  286. package/src/integrations/slack-interactions.ts +3170 -474
  287. package/src/integrations/slack-mrkdwn.ts +193 -0
  288. package/src/integrations/slack-routing.ts +337 -0
  289. package/src/integrations/slack-session-defaults.ts +224 -0
  290. package/src/integrations/slack-task-file-upload.ts +323 -0
  291. package/src/integrations/social-oauth.ts +213 -15
  292. package/src/interaction-agent-access.ts +48 -0
  293. package/src/interaction-frame-proxy.ts +409 -0
  294. package/src/interaction-metrics.ts +30 -0
  295. package/src/mcp/action-catalog-types.ts +9 -0
  296. package/src/mcp/action-catalog.gen.ts +6696 -0
  297. package/src/mcp/capability-account-readiness.ts +17 -0
  298. package/src/mcp/child-read-evidence.ts +19 -0
  299. package/src/mcp/command-read.ts +109 -0
  300. package/src/mcp/company-profile-agent-admin.ts +198 -0
  301. package/src/mcp/contract-input.ts +141 -0
  302. package/src/mcp/documents.ts +65 -377
  303. package/src/mcp/editable-artifacts.ts +12 -6
  304. package/src/mcp/files.ts +80 -38
  305. package/src/mcp/first-party-tool-permissions.ts +305 -0
  306. package/src/mcp/knowledge-entries.ts +322 -0
  307. package/src/mcp/orchestration-failure-diagnostic.ts +43 -0
  308. package/src/mcp/receipts.ts +34 -0
  309. package/src/mcp/request-abort.ts +44 -0
  310. package/src/mcp/scheduled-task-input.ts +76 -0
  311. package/src/mcp/scheduled-task-view.ts +44 -3
  312. package/src/mcp/server.ts +3030 -1267
  313. package/src/mcp/session-event-view.ts +383 -0
  314. package/src/mcp/session-view.ts +84 -3
  315. package/src/mcp/session-wait.ts +665 -0
  316. package/src/mcp-oauth.ts +1123 -0
  317. package/src/model-catalog.ts +91 -333
  318. package/src/observability.ts +15 -1
  319. package/src/organization-mcp.ts +397 -0
  320. package/src/organization-recovery-notifications.ts +103 -0
  321. package/src/routes/analytics-consent.ts +123 -0
  322. package/src/routes/api-integrations.ts +364 -74
  323. package/src/routes/api-keys.ts +336 -10
  324. package/src/routes/artifact-catalog.ts +279 -0
  325. package/src/routes/automations.ts +768 -0
  326. package/src/routes/billing.ts +461 -46
  327. package/src/routes/browser-sessions.ts +839 -131
  328. package/src/routes/canonical-human-identities.ts +90 -14
  329. package/src/routes/capabilities.ts +107 -6
  330. package/src/routes/channels.ts +20 -3
  331. package/src/routes/claude-subscription-accounts.ts +334 -0
  332. package/src/routes/claude-subscription-oauth.ts +142 -0
  333. package/src/routes/client-errors.ts +362 -0
  334. package/src/routes/codex.ts +833 -116
  335. package/src/routes/company-brain.ts +322 -0
  336. package/src/routes/company-profile.ts +65 -1
  337. package/src/routes/computer-sessions.ts +557 -106
  338. package/src/routes/connect.ts +1938 -0
  339. package/src/routes/connections.ts +1040 -349
  340. package/src/routes/documents.ts +488 -287
  341. package/src/routes/editable-artifacts.ts +79 -9
  342. package/src/routes/enrollments.ts +135 -39
  343. package/src/routes/environments.ts +154 -58
  344. package/src/routes/external-identity-links.ts +201 -0
  345. package/src/routes/feedback.ts +113 -0
  346. package/src/routes/files.ts +337 -39
  347. package/src/routes/github-browser-pages.ts +162 -0
  348. package/src/routes/github.ts +446 -144
  349. package/src/routes/insights-response-cache.ts +83 -0
  350. package/src/routes/insights-usage.ts +345 -0
  351. package/src/routes/insights.ts +153 -19
  352. package/src/routes/install.ts +133 -38
  353. package/src/routes/integration-facets.ts +107 -33
  354. package/src/routes/interaction-resources.ts +30 -3
  355. package/src/routes/knowledge.ts +488 -0
  356. package/src/routes/machines.ts +313 -15
  357. package/src/routes/managed-auth-session-sets.ts +1218 -0
  358. package/src/routes/managed-onboarding.ts +337 -0
  359. package/src/routes/managed-sign-in-methods.ts +433 -0
  360. package/src/routes/memory-slack-publications.ts +3 -2
  361. package/src/routes/model-connection-access.ts +208 -0
  362. package/src/routes/organization-integration-policy.ts +116 -0
  363. package/src/routes/organization-integrations.ts +326 -0
  364. package/src/routes/organization-mcp-connections.ts +178 -0
  365. package/src/routes/organization-memberships.ts +1019 -27
  366. package/src/routes/organization-model-providers.ts +293 -0
  367. package/src/routes/organization-recovery.ts +325 -0
  368. package/src/routes/organization-service-accounts.ts +128 -0
  369. package/src/routes/organization-sessions.ts +350 -0
  370. package/src/routes/personal-github-git-broker.ts +785 -0
  371. package/src/routes/personal-github.ts +370 -0
  372. package/src/routes/plugins.ts +251 -22
  373. package/src/routes/pr-review-github.ts +881 -0
  374. package/src/routes/pr-review.ts +622 -0
  375. package/src/routes/preference-registry.ts +12 -93
  376. package/src/routes/rigs.ts +167 -50
  377. package/src/routes/scheduled-tasks.ts +346 -41
  378. package/src/routes/session-artifact-associations.ts +87 -0
  379. package/src/routes/session-history-imports.ts +280 -0
  380. package/src/routes/sessions.ts +2306 -264
  381. package/src/routes/skill-content.ts +297 -0
  382. package/src/routes/skill-install-authority.ts +64 -0
  383. package/src/routes/skills.ts +62 -9
  384. package/src/routes/social.ts +46 -18
  385. package/src/routes/subscription-account-pools.ts +436 -0
  386. package/src/routes/subscription-pool-access.ts +126 -0
  387. package/src/routes/supergrok.ts +219 -303
  388. package/src/routes/transcription-recordings.ts +38 -3
  389. package/src/routes/transcriptions.ts +4 -0
  390. package/src/routes/usage-allowances.ts +378 -0
  391. package/src/routes/user-resource-authorities.ts +138 -0
  392. package/src/routes/video-generation.ts +14 -10
  393. package/src/routes/workspace-artifacts.ts +275 -69
  394. package/src/routes/workspace-capture.ts +21 -0
  395. package/src/routes/workspace-integrations.ts +576 -0
  396. package/src/routes/workspace-learning.ts +159 -0
  397. package/src/routes/workspace-model-providers.ts +148 -0
  398. package/src/routes/workspace-state.ts +38 -25
  399. package/src/routes/workspaces.ts +1004 -127
  400. package/src/sandbox/auth-callout.ts +96 -19
  401. package/src/sandbox/channel-a.ts +323 -24
  402. package/src/sandbox/connection-authority.ts +3 -0
  403. package/src/sandbox/enrollment.ts +81 -6
  404. package/src/sandbox/machines.ts +218 -76
  405. package/src/sandbox/metrics-ingestion.ts +411 -62
  406. package/src/sandbox/rematerialize.ts +121 -19
  407. package/src/sandbox/session-attach-variable-sets.ts +131 -0
  408. package/src/sandbox/viewer.ts +280 -25
  409. package/src/sandbox-file-artifacts.ts +413 -0
  410. package/src/scheduled-task-deletion.ts +138 -0
  411. package/src/session-create-observability.ts +46 -0
  412. package/src/session-realtime-context.ts +6 -3
  413. package/src/site-session-origin.ts +48 -0
  414. package/src/site-uploads.ts +224 -0
  415. package/src/slack-reaction-files.ts +16 -5
  416. package/src/temporal-schedule-cleanup.ts +13 -0
  417. package/src/temporal-schedule-sync.ts +74 -0
  418. package/src/transcription/providers/codex-subscription.ts +2 -0
  419. package/src/transcription/providers/openai.ts +1 -0
  420. package/src/transcription/providers/xai-subscription.ts +49 -3
  421. package/src/transcription/segmenter.ts +8 -2
  422. package/src/transcription/service.ts +43 -6
  423. package/src/work-discovery-observability.ts +121 -0
  424. package/src/workspace-artifact-content.ts +144 -0
  425. package/src/workspace-artifact-provenance.ts +104 -0
  426. package/src/workspace-delete-observability.ts +75 -0
  427. package/src/workspace-deletion.ts +73 -0
  428. package/src/workspace-state-projection.ts +18 -148
  429. package/src/workspace-tool-gateway-observability.ts +100 -0
  430. package/src/workspace-tool-gateway.ts +870 -0
  431. package/src/workspace-webhook-dispatch.ts +190 -0
  432. package/src/xai-subscription-auth.ts +9 -2
  433. package/dist/chunk-2PQMSRCB.js +0 -49759
  434. package/dist/chunk-2PQMSRCB.js.map +0 -1
  435. package/dist/routes/packs.d.ts +0 -3
  436. package/src/routes/packs.ts +0 -729
@@ -0,0 +1,224 @@
1
+ import { WorkspaceArtifactSourceBundle, type AccessGrant } from "@opengeni/contracts";
2
+ import type { ApiRouteDeps, ObjectStorageDependency } from "@opengeni/core";
3
+ import { retryWhileMissing } from "@opengeni/storage";
4
+ import {
5
+ createWorkspaceArtifactUpload,
6
+ getWorkspaceArtifactUpload,
7
+ expireWorkspaceArtifactUploads,
8
+ WorkspaceArtifactOperationError,
9
+ } from "@opengeni/db";
10
+ import {
11
+ prepareWorkspaceArtifactContent,
12
+ type PreparedWorkspaceArtifactContent,
13
+ } from "./workspace-artifact-content";
14
+ import { userContentSignedGetUrlOptions } from "./http/user-content";
15
+ type Storage = NonNullable<ObjectStorageDependency>;
16
+ export const MAX_SITE_SOURCE_BYTES = 64 * 1024 * 1024;
17
+
18
+ export async function validateSiteSource(storage: Storage, key: string, sizeBytes: number) {
19
+ if (sizeBytes > MAX_SITE_SOURCE_BYTES) {
20
+ throw new WorkspaceArtifactOperationError(
21
+ "Editable source JSON must be at most 64 MiB. Exclude dependencies and build output; HTML has a separate storage limit.",
22
+ );
23
+ }
24
+ const content = await retryWhileMissing(() => storage.getObjectBytes(key));
25
+ if (!content) {
26
+ throw new WorkspaceArtifactOperationError(
27
+ "Site source is not available yet. Retry publication with the same upload.",
28
+ );
29
+ }
30
+ try {
31
+ WorkspaceArtifactSourceBundle.parse(
32
+ JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(content.bytes)),
33
+ );
34
+ } catch {
35
+ throw new WorkspaceArtifactOperationError(
36
+ "Source must be JSON {entrypoint, files: [{path, content}]}. Correct it and prepare a new upload.",
37
+ );
38
+ }
39
+ }
40
+ function keys(workspaceId: string, id: string) {
41
+ const base = `workspaces/${workspaceId}/workspace-artifacts`;
42
+ return {
43
+ html: `${base}/uploads/${id}/index.html`,
44
+ source: `${base}/uploads/${id}/source.json`,
45
+ frozenHtml: `${base}/blobs/${id}.html`,
46
+ frozenSource: `${base}/sources/${id}.json`,
47
+ };
48
+ }
49
+ function owner(grant: AccessGrant) {
50
+ return typeof grant.metadata?.sessionId === "string"
51
+ ? `session:${grant.metadata.sessionId}`
52
+ : `subject:${grant.subjectId}`;
53
+ }
54
+ export async function prepareWorkspaceArtifactUpload(deps: ApiRouteDeps, grant: AccessGrant) {
55
+ const storage = deps.objectStorage;
56
+ if (!storage) throw new Error("Object storage is not configured");
57
+ const expired = await expireWorkspaceArtifactUploads(deps.db, grant.workspaceId);
58
+ await Promise.allSettled(
59
+ expired.flatMap((row) => {
60
+ const paths = keys(row.workspaceId, row.id);
61
+ return (row.status === "published" ? [paths.html, paths.source] : Object.values(paths)).map(
62
+ (key) => storage.deleteObject(key),
63
+ );
64
+ }),
65
+ );
66
+ const row = await createWorkspaceArtifactUpload(deps.db, {
67
+ accountId: grant.accountId,
68
+ workspaceId: grant.workspaceId,
69
+ ownerId: owner(grant),
70
+ });
71
+ const paths = keys(grant.workspaceId, row.id);
72
+ const audience = grant.principalKind === "agent_attempt" ? "sandbox" : "public";
73
+ const [html, source] = await Promise.all([
74
+ storage.createPutUrl({ key: paths.html, contentType: "text/html", audience }),
75
+ storage.createPutUrl({ key: paths.source, contentType: "application/json", audience }),
76
+ ]);
77
+ return {
78
+ uploadId: row.id,
79
+ html: { putUrl: html.url, requiredHeaders: html.requiredHeaders },
80
+ source: { putUrl: source.url, requiredHeaders: source.requiredHeaders },
81
+ expiresAt: new Date(
82
+ Math.min(html.expiresAt.getTime(), source.expiresAt.getTime()),
83
+ ).toISOString(),
84
+ };
85
+ }
86
+ /** Provider version-pinned reads, without Site SHA checks or full HTML buffering. */
87
+ export async function* readArtifactObject(
88
+ storage: Storage,
89
+ key: string,
90
+ expectedHead?: Awaited<ReturnType<NonNullable<Storage["headObject"]>>>,
91
+ ) {
92
+ if (!storage.headObject || !storage.getObjectRange)
93
+ throw new Error("Object storage cannot stream Site content");
94
+ const head = expectedHead ?? (await storage.headObject(key));
95
+ if (!head || !head.VersionToken || head.ContentLength === undefined)
96
+ throw new Error("Site content is unavailable");
97
+ for (let start = 0; start < head.ContentLength; start += 1024 * 1024) {
98
+ const endInclusive = Math.min(head.ContentLength - 1, start + 1024 * 1024 - 1);
99
+ const part = await storage.getObjectRange({
100
+ key,
101
+ start,
102
+ endInclusive,
103
+ expectedVersionToken: head.VersionToken,
104
+ });
105
+ if (
106
+ !part ||
107
+ part.versionToken !== head.VersionToken ||
108
+ part.bytes.length !== endInclusive - start + 1
109
+ )
110
+ throw new Error("Site object changed during transfer");
111
+ yield part.bytes;
112
+ }
113
+ }
114
+ async function freeze(
115
+ storage: Storage,
116
+ from: string,
117
+ to: string,
118
+ contentType: string,
119
+ optional = false,
120
+ ) {
121
+ if (!storage.headObject || !storage.putObjectStreamIfAbsent)
122
+ throw new Error("Object storage cannot freeze Site uploads");
123
+ const existing = await storage.headObject(to);
124
+ if (existing) return existing.ContentLength!;
125
+ const head = await storage.headObject(from);
126
+ if (!head && optional) return null;
127
+ if (!head || !head.ContentLength)
128
+ throw new WorkspaceArtifactOperationError("Upload HTML before publishing the Site");
129
+ if (head.ContentLength > storage.maxSinglePutSizeBytes)
130
+ throw new Error("Site exceeds the storage upload limit");
131
+ const decoder = new TextDecoder("utf-8", { fatal: true });
132
+ async function* chunks() {
133
+ for await (const part of readArtifactObject(storage, from, head)) {
134
+ decoder.decode(part, { stream: true });
135
+ yield part;
136
+ }
137
+ decoder.decode();
138
+ }
139
+ const created = await storage.putObjectStreamIfAbsent({
140
+ key: to,
141
+ contentType,
142
+ byteSize: head.ContentLength,
143
+ chunks: chunks(),
144
+ });
145
+ // A successful conditional PUT is the receipt. A following visibility miss
146
+ // cannot invalidate it. Only a conflict needs the existing winner's metadata.
147
+ if (created) return head.ContentLength;
148
+ const stored = await retryWhileMissing(() => storage.headObject!(to));
149
+ if (!stored || stored.ContentLength === undefined) {
150
+ throw new WorkspaceArtifactOperationError(
151
+ "Frozen Site upload is unavailable. Retry publication with the same upload.",
152
+ );
153
+ }
154
+ return stored.ContentLength;
155
+ }
156
+ type Input = Partial<Parameters<typeof prepareWorkspaceArtifactContent>[2]> & { uploadId?: string };
157
+ export async function prepareWorkspaceArtifactPublication(
158
+ deps: ApiRouteDeps,
159
+ grant: AccessGrant,
160
+ input: Input,
161
+ ): Promise<PreparedWorkspaceArtifactContent & { uploadId?: string }> {
162
+ const storage = deps.objectStorage;
163
+ if (!storage) throw new Error("Object storage is not configured");
164
+ if (Boolean(input.uploadId) === (input.html !== undefined))
165
+ throw new Error("Provide either uploadId or inline html, not both");
166
+ if (!input.uploadId) {
167
+ return prepareWorkspaceArtifactContent(storage, grant.workspaceId, {
168
+ ...input,
169
+ html: input.html!,
170
+ });
171
+ }
172
+ if (input.source) throw new Error("Upload source JSON using the source upload URL");
173
+ const row = await getWorkspaceArtifactUpload(deps.db, grant.workspaceId, input.uploadId);
174
+ if (
175
+ !row ||
176
+ row.ownerId !== owner(grant) ||
177
+ row.status === "expired" ||
178
+ (row.status === "pending" && row.expiresAt.getTime() <= Date.now())
179
+ )
180
+ throw new WorkspaceArtifactOperationError("Site upload expired or unavailable");
181
+ const paths = keys(grant.workspaceId, row.id);
182
+ const sizeBytes = await freeze(storage, paths.html, paths.frozenHtml, "text/html; charset=utf-8");
183
+ const sourceSizeBytes =
184
+ row.status === "published" && !(await storage.headObject!(paths.frozenSource))
185
+ ? null
186
+ : await freeze(storage, paths.source, paths.frozenSource, "application/json", true);
187
+ if (sourceSizeBytes !== null) {
188
+ await validateSiteSource(storage, paths.frozenSource, sourceSizeBytes);
189
+ }
190
+ return {
191
+ uploadId: row.id,
192
+ contentKey: paths.frozenHtml,
193
+ contentSha256: null,
194
+ sizeBytes: sizeBytes!,
195
+ sourceKey: sourceSizeBytes === null ? null : paths.frozenSource,
196
+ sourceSha256: null,
197
+ sourceSizeBytes,
198
+ ...(input.requestedTools === undefined ? {} : { requestedTools: input.requestedTools }),
199
+ persistContent: async () => {},
200
+ discardContent: async () => {},
201
+ };
202
+ }
203
+ export async function workspaceArtifactDownloads(
204
+ storage: Storage,
205
+ ref: { contentKey: string; sourceKey: string | null },
206
+ audience: "sandbox" | "public",
207
+ ) {
208
+ const [html, source] = await Promise.all([
209
+ // Site HTML runs publisher scripts; opened directly from storage it would
210
+ // render outside the console's opaque-origin frame, so it downloads.
211
+ storage.createGetUrl({
212
+ key: ref.contentKey,
213
+ audience,
214
+ ...userContentSignedGetUrlOptions("text/html", "site.html"),
215
+ }),
216
+ ref.sourceKey ? storage.createGetUrl({ key: ref.sourceKey, audience }) : null,
217
+ ]);
218
+ return {
219
+ html: { url: html.url, expiresAt: html.expiresAt },
220
+ source: source
221
+ ? { url: source.url, expiresAt: source.expiresAt, format: "source-bundle-json" }
222
+ : null,
223
+ };
224
+ }
@@ -1,7 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import type { FileResourceRef } from "@opengeni/contracts";
3
3
  import { completeFileUpload, prepareGeneratedWorkspaceFile, type Database } from "@opengeni/db";
4
- import type { ObjectHead, ObjectStorage } from "@opengeni/storage";
4
+ import { retryWhileMissing, type ObjectHead, type ObjectStorage } from "@opengeni/storage";
5
5
  import type { DownloadedSlackReactionImage } from "./integrations/slack-bot";
6
6
 
7
7
  const IMPORT_IDENTITY_VERSION = "slack-reaction-image-v1";
@@ -77,13 +77,15 @@ export async function importSlackReactionImage(
77
77
  if (existing) {
78
78
  assertStoredSlackImage(existing, image, sha256);
79
79
  } else if (storage.putObjectIfAbsent) {
80
- await storage.putObjectIfAbsent({
80
+ const created = await storage.putObjectIfAbsent({
81
81
  key: objectKey,
82
82
  contentType: image.contentType,
83
83
  body: image.bytes,
84
84
  sha256,
85
85
  });
86
- assertStoredSlackImage(await storage.headObject(objectKey), image, sha256);
86
+ if (!created) {
87
+ await assertVisibleSlackImage(storage, objectKey, image, sha256);
88
+ }
87
89
  } else {
88
90
  await storage.putObject({
89
91
  key: objectKey,
@@ -91,7 +93,6 @@ export async function importSlackReactionImage(
91
93
  body: image.bytes,
92
94
  sha256,
93
95
  });
94
- assertStoredSlackImage(await storage.headObject(objectKey), image, sha256);
95
96
  }
96
97
  file = await (dependencies.completeFile ?? completeFileUpload)(
97
98
  dependencies.db,
@@ -99,7 +100,7 @@ export async function importSlackReactionImage(
99
100
  uploadId,
100
101
  );
101
102
  } else {
102
- assertStoredSlackImage(await storage.headObject(objectKey), image, sha256);
103
+ await assertVisibleSlackImage(storage, objectKey, image, sha256);
103
104
  }
104
105
 
105
106
  if (
@@ -153,6 +154,16 @@ export function safeSlackImageFilename(
153
154
  return `${stem}${extension}`;
154
155
  }
155
156
 
157
+ async function assertVisibleSlackImage(
158
+ storage: ObjectStorage,
159
+ objectKey: string,
160
+ image: DownloadedSlackReactionImage,
161
+ sha256: string,
162
+ ): Promise<void> {
163
+ const head = await retryWhileMissing(async () => await storage.headObject!(objectKey));
164
+ assertStoredSlackImage(head, image, sha256);
165
+ }
166
+
156
167
  function assertStoredSlackImage(
157
168
  head: ObjectHead | null,
158
169
  image: DownloadedSlackReactionImage,
@@ -1,6 +1,7 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import {
3
3
  claimTemporalScheduleCleanups,
4
+ completeTemporalScheduleConnectorCleanup,
4
5
  settleTemporalScheduleCleanup,
5
6
  type Database,
6
7
  type TemporalScheduleCleanupClaim,
@@ -13,6 +14,7 @@ const CLEANUP_POLL_INTERVAL_MS = 1_000;
13
14
 
14
15
  type TemporalScheduleCleanupDependencies = {
15
16
  db: Database;
17
+ cleanupConnectorAuthorization?: (claim: TemporalScheduleCleanupClaim) => Promise<void>;
16
18
  deleteSchedule: (temporalScheduleId: string) => Promise<void>;
17
19
  observability?: Pick<Observability, "info" | "warn" | "error">;
18
20
  };
@@ -39,6 +41,17 @@ export async function processTemporalScheduleCleanupClaims(
39
41
  const results = await Promise.all(
40
42
  claims.map(async (claim): Promise<"deleted" | "failed" | "stale"> => {
41
43
  try {
44
+ if (claim.connectorCleanupSnapshot && !claim.connectorCleanupCompletedAt) {
45
+ if (!deps.cleanupConnectorAuthorization) {
46
+ throw new Error("scheduled connector cleanup dependency is unavailable");
47
+ }
48
+ await deps.cleanupConnectorAuthorization(claim);
49
+ const completed = await completeTemporalScheduleConnectorCleanup(deps.db, {
50
+ id: claim.id,
51
+ claimId: claim.claimId,
52
+ });
53
+ if (!completed) return "stale";
54
+ }
42
55
  await deps.deleteSchedule(claim.temporalScheduleId);
43
56
  const settled = await settleTemporalScheduleCleanup(deps.db, {
44
57
  id: claim.id,
@@ -0,0 +1,74 @@
1
+ import type { ScheduledTask } from "@opengeni/contracts";
2
+ import { getScheduledTaskIncludingDeleted, type Database } from "@opengeni/db";
3
+ import { sql } from "drizzle-orm";
4
+
5
+ const LOCK_TIMEOUT = "5s";
6
+ const WRITE_TIMEOUT_MS = 5_000;
7
+
8
+ type TemporalScheduleSyncDependencies = {
9
+ db: Database;
10
+ withDeadline<T>(deadline: number, work: () => Promise<T>): Promise<T>;
11
+ upsert(task: ScheduledTask): Promise<void>;
12
+ /** Idempotent: an absent Temporal schedule is already removed. */
13
+ remove(temporalScheduleId: string): Promise<void>;
14
+ };
15
+
16
+ /**
17
+ * Serialize external writes across API replicas, independently of task edits.
18
+ * A queued sync rereads the newest committed row after acquiring this lock;
19
+ * input snapshots never become desired state. Cleanup uses the same key, so
20
+ * an in-flight upsert finishes before its tombstone's external deletion.
21
+ * Failure compensation commits before releasing the lock, so a queued writer
22
+ * cannot read the row being restored and then publish that failed edit.
23
+ *
24
+ * Deadlines bound both lock admission and network occupancy. A thrown network
25
+ * error retains its unknown-outcome semantics; this is not durable recovery
26
+ * for a process crash or a lost Temporal acknowledgement.
27
+ */
28
+ export function createTemporalScheduleSynchronizer(deps: TemporalScheduleSyncDependencies) {
29
+ const write = async <T>(
30
+ temporalScheduleId: string,
31
+ work: (tx: Database) => Promise<T>,
32
+ ): Promise<T> =>
33
+ deps.db.transaction(async (tx) => {
34
+ await tx.execute(sql`select set_config('lock_timeout', ${LOCK_TIMEOUT}, true)`);
35
+ await tx.execute(sql`select pg_advisory_xact_lock(
36
+ hashtextextended(${`scheduled-task-temporal:${temporalScheduleId}`}, 0)
37
+ )`);
38
+ return await work(tx);
39
+ });
40
+
41
+ return {
42
+ async sync(
43
+ task: Pick<ScheduledTask, "id" | "workspaceId" | "temporalScheduleId">,
44
+ onFailure?: (tx: Database, error: unknown) => Promise<Error>,
45
+ ) {
46
+ const result = await write(task.temporalScheduleId, async (tx) => {
47
+ try {
48
+ await deps.withDeadline(Date.now() + WRITE_TIMEOUT_MS, async () => {
49
+ const current = await getScheduledTaskIncludingDeleted(tx, task.workspaceId, task.id);
50
+ if (current && current.temporalScheduleId !== task.temporalScheduleId)
51
+ throw new Error("Scheduled task Temporal identity changed");
52
+ if (!current || current.deletedAt !== null || current.schedule.type === "manual") {
53
+ await deps.remove(task.temporalScheduleId);
54
+ return;
55
+ }
56
+ const { deletedAt: _deletedAt, ...latest } = current;
57
+ await deps.upsert(latest);
58
+ });
59
+ return { ok: true as const };
60
+ } catch (error) {
61
+ return { ok: false as const, error: onFailure ? await onFailure(tx, error) : error };
62
+ }
63
+ });
64
+ // Throw after commit: throwing inside the transaction would also undo a
65
+ // successful compensation. A failed commit cannot claim that receipt.
66
+ if (!result.ok) throw result.error;
67
+ },
68
+ async remove(temporalScheduleId: string) {
69
+ await write(temporalScheduleId, async () =>
70
+ deps.withDeadline(Date.now() + WRITE_TIMEOUT_MS, () => deps.remove(temporalScheduleId)),
71
+ );
72
+ },
73
+ };
74
+ }
@@ -45,6 +45,7 @@ export function createCodexSubscriptionTranscriptionProvider(input: {
45
45
  );
46
46
  if (!account) {
47
47
  throw new TranscriptionServiceError({
48
+ fallbackSafe: true,
48
49
  code: "unavailable",
49
50
  message: "Transcription is unavailable.",
50
51
  });
@@ -55,6 +56,7 @@ export function createCodexSubscriptionTranscriptionProvider(input: {
55
56
  token = await resolver.getToken();
56
57
  } catch {
57
58
  throw new TranscriptionServiceError({
59
+ fallbackSafe: true,
58
60
  code: "unavailable",
59
61
  message: "Transcription is unavailable.",
60
62
  });
@@ -53,6 +53,7 @@ function audioBlob(audio: Uint8Array, mimeType: string): Blob {
53
53
  export function responseError(status: number): TranscriptionServiceError {
54
54
  if (status === 401 || status === 403) {
55
55
  return new TranscriptionServiceError({
56
+ fallbackSafe: true,
56
57
  code: "unavailable",
57
58
  message: "Transcription is unavailable.",
58
59
  });
@@ -62,6 +62,7 @@ export function createXaiSubscriptionTranscriptionProvider(input: {
62
62
  } catch {
63
63
  throw new TranscriptionServiceError({
64
64
  code: "unavailable",
65
+ fallbackSafe: true,
65
66
  message: "Transcription is unavailable.",
66
67
  });
67
68
  }
@@ -86,16 +87,28 @@ export function createXaiSubscriptionTranscriptionProvider(input: {
86
87
  ...(signal ? { signal } : {}),
87
88
  });
88
89
  };
90
+ const tokenForRequest = async (refresh: boolean) => {
91
+ try {
92
+ return await (refresh ? auth.context.refresh() : auth.context.getToken());
93
+ } catch {
94
+ throw new TranscriptionServiceError({
95
+ code: "unavailable",
96
+ message: "Reconnect the SuperGrok account to use transcription.",
97
+ fallbackSafe: true,
98
+ });
99
+ }
100
+ };
89
101
  let response: Response;
90
102
  try {
91
- let token = await auth.context.getToken();
103
+ let token = await tokenForRequest(false);
92
104
  response = await request(token.accessToken);
93
- if (response.status === 401) {
105
+ if (response.status === 401 || (await isXaiInvalidCredentialResponse(response))) {
94
106
  await response.body?.cancel().catch(() => undefined);
95
- token = await auth.context.refresh();
107
+ token = await tokenForRequest(true);
96
108
  response = await request(token.accessToken);
97
109
  }
98
110
  } catch (error) {
111
+ if (error instanceof TranscriptionServiceError) throw error;
99
112
  throw fetchError(error);
100
113
  }
101
114
  if (!response.ok) throw responseError(response.status);
@@ -108,3 +121,36 @@ export function createXaiSubscriptionTranscriptionProvider(input: {
108
121
  },
109
122
  };
110
123
  }
124
+
125
+ export async function isXaiInvalidCredentialResponse(response: Response): Promise<boolean> {
126
+ if (response.status !== 403) return false;
127
+ const reader = response.clone().body?.getReader();
128
+ if (!reader) return false;
129
+ const chunks: Uint8Array[] = [];
130
+ let size = 0;
131
+ try {
132
+ while (true) {
133
+ const chunk = await reader.read();
134
+ if (chunk.done) break;
135
+ size += chunk.value.byteLength;
136
+ if (size > 16_384) return false;
137
+ chunks.push(chunk.value);
138
+ }
139
+ const bytes = new Uint8Array(size);
140
+ let offset = 0;
141
+ for (const chunk of chunks) {
142
+ bytes.set(chunk, offset);
143
+ offset += chunk.byteLength;
144
+ }
145
+ const body = JSON.parse(new TextDecoder().decode(bytes));
146
+ return (
147
+ typeof body?.error === "string" &&
148
+ body.error.includes("[WKE=unauthenticated:bad-credentials]")
149
+ );
150
+ } catch {
151
+ return false;
152
+ } finally {
153
+ void reader.cancel().catch(() => undefined);
154
+ reader.releaseLock();
155
+ }
156
+ }
@@ -220,8 +220,14 @@ function extensionForMimeType(mimeType: string): string {
220
220
  }
221
221
 
222
222
  async function commandSucceeds(command: string, args: string[]): Promise<boolean> {
223
- const result = await runCommand(command, args);
224
- return !result.spawnError && result.exitCode === 0;
223
+ try {
224
+ const result = await runCommand(command, args);
225
+ return !result.spawnError && result.exitCode === 0;
226
+ } catch {
227
+ // Some runtimes throw during spawn instead of emitting an error event.
228
+ // An unavailable optional audio tool must not prevent client startup.
229
+ return false;
230
+ }
225
231
  }
226
232
 
227
233
  async function runCommand(
@@ -74,7 +74,7 @@ export function createTranscriptionService(input: {
74
74
  );
75
75
  },
76
76
  async selectProvider(context) {
77
- return (await firstAvailable(providers, context))?.id ?? null;
77
+ return (await firstAvailable(orderedProviders(providers, context), context))?.id ?? null;
78
78
  },
79
79
  async transcribe(request) {
80
80
  const mimeType = normalizeMimeType(request.mimeType);
@@ -106,12 +106,10 @@ export function createTranscriptionService(input: {
106
106
  workspaceId: request.workspaceId,
107
107
  subjectId: request.subjectId,
108
108
  })
109
- : await firstAvailable(providers, {
110
- workspaceId: request.workspaceId,
111
- subjectId: request.subjectId,
112
- });
109
+ : await firstAvailable(orderedProviders(providers, request), request);
113
110
  if (!provider) {
114
111
  throw new TranscriptionServiceError({
112
+ fallbackSafe: true,
115
113
  code: "unavailable",
116
114
  message: "Transcription is unavailable.",
117
115
  });
@@ -164,6 +162,23 @@ export function createTranscriptionService(input: {
164
162
  retryable: true,
165
163
  });
166
164
  }
165
+ if (
166
+ error instanceof TranscriptionServiceError &&
167
+ error.fallbackSafe &&
168
+ !request.providerId &&
169
+ request.fallbackEnabled !== false &&
170
+ !request.signal?.aborted
171
+ ) {
172
+ const excludedProviders = [...(request.excludedProviders ?? []), provider.id];
173
+ if (
174
+ await firstAvailable(
175
+ orderedProviders(providers, { ...request, excludedProviders }),
176
+ request,
177
+ )
178
+ ) {
179
+ return await this.transcribe({ ...request, excludedProviders });
180
+ }
181
+ }
167
182
  throw error;
168
183
  } finally {
169
184
  deadline.dispose();
@@ -217,12 +232,34 @@ function createProviderRequestDeadline(
217
232
  };
218
233
  }
219
234
 
235
+ export function orderedProviders(
236
+ providers: readonly TranscriptionProvider[],
237
+ context: TranscriptionAvailabilityContext,
238
+ ): TranscriptionProvider[] {
239
+ const preferred = providers.filter((provider) => provider.id === context.preferredProvider);
240
+ const ordered = context.preferredProvider
241
+ ? context.fallbackEnabled === false
242
+ ? preferred
243
+ : [...preferred, ...providers.filter((provider) => provider.id !== context.preferredProvider)]
244
+ : context.fallbackEnabled === false
245
+ ? providers.slice(0, 1)
246
+ : [...providers];
247
+ const remaining = context.afterProvider
248
+ ? ordered.slice(ordered.findIndex((provider) => provider.id === context.afterProvider) + 1)
249
+ : ordered;
250
+ return remaining.filter((provider) => !context.excludedProviders?.includes(provider.id));
251
+ }
252
+
220
253
  async function firstAvailable(
221
254
  providers: readonly TranscriptionProvider[],
222
255
  context: TranscriptionAvailabilityContext,
223
256
  ) {
224
257
  for (const provider of providers) {
225
- if (await provider.available(context)) return provider;
258
+ try {
259
+ if (await provider.available(context)) return provider;
260
+ } catch {
261
+ /* No audio sent; another configured provider may be ready. */
262
+ }
226
263
  }
227
264
  return null;
228
265
  }