@opengeni/api-router 3.0.1 → 3.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (546) hide show
  1. package/dist/access-grant-rls.d.ts +8 -1
  2. package/dist/admission-trace.d.ts +8 -0
  3. package/dist/app.d.ts +50 -3
  4. package/dist/app.js +10 -1
  5. package/dist/artifact-catalog-cursor.d.ts +1 -0
  6. package/dist/attached-browser-viewer.d.ts +11 -0
  7. package/dist/auth/managed-auth-attempt-context.d.ts +2 -0
  8. package/dist/auth/managed-auth-rate-limits.d.ts +167 -0
  9. package/dist/auth/managed-auth-session-adapter.d.ts +9 -1
  10. package/dist/auth/managed-auth.d.ts +72 -5
  11. package/dist/auth/managed-sign-in-notifications.d.ts +4 -0
  12. package/dist/auth/new-signups-gate.d.ts +23 -0
  13. package/dist/auth/signup-funnel-metrics.d.ts +44 -0
  14. package/dist/azure-live.d.ts +28 -0
  15. package/dist/browser-action-fences.d.ts +13 -0
  16. package/dist/browser-controller-authority.d.ts +1 -61
  17. package/dist/browser-ephemeral.d.ts +5 -0
  18. package/dist/browser-lost-runtime-end.d.ts +24 -0
  19. package/dist/browser-state-authority.d.ts +1 -27
  20. package/dist/chunk-OGQDXVXY.js +18 -0
  21. package/dist/chunk-OGQDXVXY.js.map +1 -0
  22. package/dist/{chunk-AJ62T5QN.js → chunk-OT56OY4V.js} +80503 -39869
  23. package/dist/chunk-OT56OY4V.js.map +1 -0
  24. package/dist/claude-default-models.d.ts +19 -0
  25. package/dist/claude-subscription-core.d.ts +50 -0
  26. package/dist/claude-subscription-oauth.d.ts +36 -0
  27. package/dist/claude-subscription-profile.d.ts +9 -0
  28. package/dist/claude-subscription-usage.d.ts +6 -0
  29. package/dist/claude-workspace-connection.d.ts +15 -0
  30. package/dist/codemode.d.ts +40 -5
  31. package/dist/codex-realtime.d.ts +29 -3
  32. package/dist/codex-session-pointers.d.ts +19 -0
  33. package/dist/connection-ownership.d.ts +6 -3
  34. package/dist/controller-data-plane.d.ts +16 -0
  35. package/dist/credit-promotion-snapshot.d.ts +6 -0
  36. package/dist/editable-artifact-source-session.d.ts +3 -0
  37. package/dist/editable-artifact-websocket.d.ts +5 -3
  38. package/dist/fatal-process-boundary.d.ts +9 -1
  39. package/dist/github-access.d.ts +7 -1
  40. package/dist/github-browser-flow.d.ts +1 -1
  41. package/dist/http/acting-person.d.ts +36 -0
  42. package/dist/http/api-contract-compat.d.ts +19 -0
  43. package/dist/http/api-error.d.ts +50 -1
  44. package/dist/http/auth.d.ts +8 -1
  45. package/dist/http/cors.d.ts +12 -0
  46. package/dist/http/deprecation.d.ts +42 -0
  47. package/dist/http/keyed-admission.d.ts +12 -0
  48. package/dist/http/local-browser-boundary.d.ts +72 -0
  49. package/dist/http/metrics-listener.d.ts +22 -0
  50. package/dist/http/path-identifier.d.ts +17 -0
  51. package/dist/http/registered-route-label.d.ts +16 -0
  52. package/dist/http/rejection-telemetry.d.ts +34 -0
  53. package/dist/http/request-body.d.ts +27 -0
  54. package/dist/http/request-source.d.ts +41 -3
  55. package/dist/http/sse.d.ts +12 -1
  56. package/dist/http/unmatched-route.d.ts +18 -0
  57. package/dist/http/user-content.d.ts +36 -0
  58. package/dist/http/variable-sets-removed.d.ts +41 -0
  59. package/dist/http/websocket-upgrade-dispatch.d.ts +18 -0
  60. package/dist/index.js +1279 -108
  61. package/dist/index.js.map +1 -1
  62. package/dist/integration-connect-metrics.d.ts +35 -0
  63. package/dist/integration-endpoint-test.d.ts +20 -0
  64. package/dist/integrations/api-integrations.d.ts +2 -2
  65. package/dist/integrations/atlassian.d.ts +8 -3
  66. package/dist/integrations/fiken.d.ts +3 -1
  67. package/dist/integrations/github-lens-connect.d.ts +37 -66
  68. package/dist/integrations/google-drive.d.ts +2 -0
  69. package/dist/integrations/integration-commit-authority.d.ts +13 -0
  70. package/dist/integrations/marketplace-plugin.d.ts +43 -43
  71. package/dist/integrations/oauth-client.d.ts +171 -12
  72. package/dist/integrations/oauth-profiles.d.ts +42 -13
  73. package/dist/integrations/onepassword-vault-provider.d.ts +9 -0
  74. package/dist/integrations/slack-bot.d.ts +285 -7
  75. package/dist/integrations/slack-file-upload-flow.d.ts +62 -0
  76. package/dist/integrations/slack-interactions.d.ts +89 -10
  77. package/dist/integrations/slack-mrkdwn.d.ts +41 -0
  78. package/dist/integrations/slack-routing.d.ts +1 -1
  79. package/dist/integrations/slack-session-defaults.d.ts +58 -0
  80. package/dist/integrations/slack-task-file-upload.d.ts +36 -0
  81. package/dist/integrations/slack-tool-review.d.ts +5 -0
  82. package/dist/interaction-agent-access.d.ts +13 -8
  83. package/dist/interaction-agent-version.d.ts +21 -0
  84. package/dist/interaction-holder-heartbeat.d.ts +5 -0
  85. package/dist/interaction-metrics.d.ts +27 -2
  86. package/dist/interaction-route-stages.d.ts +25 -0
  87. package/dist/interaction-secrets-vault.d.ts +20 -0
  88. package/dist/interaction-secrets.d.ts +103 -0
  89. package/dist/mcp/action-catalog-types.d.ts +14 -0
  90. package/dist/mcp/action-catalog.gen.d.ts +2 -0
  91. package/dist/mcp/action-descriptions.d.ts +4 -0
  92. package/dist/mcp/action-schema-imports.gen.d.ts +1 -0
  93. package/dist/mcp/capability-account-readiness.d.ts +7 -0
  94. package/dist/mcp/child-read-evidence.d.ts +9 -0
  95. package/dist/mcp/company-profile-agent-admin.d.ts +12 -0
  96. package/dist/mcp/contract-input.d.ts +13 -0
  97. package/dist/mcp/documents.d.ts +2 -0
  98. package/dist/mcp/first-party-tool-permissions.d.ts +3 -606
  99. package/dist/mcp/notification-tools.d.ts +26 -0
  100. package/dist/mcp/orchestration-failure-diagnostic.d.ts +43 -0
  101. package/dist/mcp/scheduled-task-input.d.ts +1309 -0
  102. package/dist/mcp/scheduled-task-view.d.ts +217 -3
  103. package/dist/mcp/secret-tools.d.ts +14 -0
  104. package/dist/mcp/server.d.ts +108 -5
  105. package/dist/mcp/session-admin-access-tools.d.ts +33 -0
  106. package/dist/mcp/session-event-errors.d.ts +28 -0
  107. package/dist/mcp/session-event-view.d.ts +62 -0
  108. package/dist/mcp/session-view.d.ts +15 -0
  109. package/dist/mcp/session-wait.d.ts +18 -0
  110. package/dist/mcp/site-agent-clamp-tools.d.ts +38 -0
  111. package/dist/mcp/site-data-tools.d.ts +22 -0
  112. package/dist/mcp/site-publication-tools.d.ts +19 -0
  113. package/dist/mcp/site-sql-tools.d.ts +27 -0
  114. package/dist/mcp-oauth.d.ts +37 -4
  115. package/dist/model-calls/chat-completions.d.ts +87 -0
  116. package/dist/model-calls/pending-usage-settlements.d.ts +57 -0
  117. package/dist/model-calls/service.d.ts +36 -0
  118. package/dist/model-catalog.d.ts +9 -3
  119. package/dist/native-push-dispatch.d.ts +69 -0
  120. package/dist/observability.d.ts +3 -1
  121. package/dist/organization-mcp.d.ts +82 -0
  122. package/dist/prepared-mcp-actor.d.ts +77 -0
  123. package/dist/prepared-mcp-permissions.d.ts +8 -0
  124. package/dist/routes/analytics-consent.d.ts +26 -0
  125. package/dist/routes/api-integrations.d.ts +11 -0
  126. package/dist/routes/api-keys.d.ts +7 -7
  127. package/dist/routes/automations.d.ts +2 -0
  128. package/dist/routes/billing.d.ts +59 -0
  129. package/dist/routes/browser-sessions.d.ts +65 -2
  130. package/dist/routes/chat-completions.d.ts +11 -0
  131. package/dist/routes/claude-core-usage.d.ts +20 -0
  132. package/dist/routes/claude-subscription-accounts.d.ts +3 -0
  133. package/dist/routes/claude-subscription-oauth.d.ts +3 -0
  134. package/dist/routes/client-errors.d.ts +182 -0
  135. package/dist/routes/codex-core.d.ts +401 -0
  136. package/dist/routes/codex.d.ts +64 -2
  137. package/dist/routes/computer-sessions.d.ts +6 -1
  138. package/dist/routes/desktop-distribution.d.ts +32 -0
  139. package/dist/routes/github-browser-fonts.gen.d.ts +2 -0
  140. package/dist/routes/github-browser-pages.d.ts +27 -0
  141. package/dist/routes/inbox.d.ts +13 -0
  142. package/dist/routes/insights-response-cache.d.ts +10 -0
  143. package/dist/routes/insights-usage.d.ts +12 -0
  144. package/dist/routes/insights.d.ts +28 -0
  145. package/dist/routes/interaction-placement-message.d.ts +7 -0
  146. package/dist/routes/managed-sign-in-methods.d.ts +6 -0
  147. package/dist/routes/model-account-choice.d.ts +13 -0
  148. package/dist/routes/model-account-settings.d.ts +19 -0
  149. package/dist/routes/native-app-auth.d.ts +12 -0
  150. package/dist/routes/organization-integration-policy.d.ts +8 -0
  151. package/dist/routes/organization-integrations.d.ts +6 -0
  152. package/dist/routes/organization-mcp-connections.d.ts +3 -0
  153. package/dist/routes/organization-model-defaults.d.ts +9 -0
  154. package/dist/routes/organization-service-accounts.d.ts +3 -0
  155. package/dist/routes/personal-api-keys.d.ts +3 -0
  156. package/dist/routes/session-admin-access.d.ts +3 -0
  157. package/dist/routes/session-artifact-associations.d.ts +4 -0
  158. package/dist/routes/session-history-imports.d.ts +20 -0
  159. package/dist/routes/sessions.d.ts +38 -2
  160. package/dist/routes/site-data.d.ts +18 -0
  161. package/dist/routes/site-publishing.d.ts +5 -0
  162. package/dist/routes/site-runtime.d.ts +31 -0
  163. package/dist/routes/site-sql.d.ts +16 -0
  164. package/dist/routes/slack-bot-access.d.ts +3 -0
  165. package/dist/routes/subscription-account-pools.d.ts +21 -0
  166. package/dist/routes/subscription-core-http.d.ts +5 -0
  167. package/dist/routes/subscription-pool-access.d.ts +37 -0
  168. package/dist/routes/subscription-pool-core.d.ts +153 -0
  169. package/dist/routes/supergrok.d.ts +6 -6
  170. package/dist/routes/usage-allowances.d.ts +12 -0
  171. package/dist/routes/variable-sets-removed.d.ts +2 -0
  172. package/dist/routes/vault-providers.d.ts +34 -0
  173. package/dist/routes/vaults.d.ts +3 -0
  174. package/dist/routes/workspace-integrations.d.ts +35 -0
  175. package/dist/routes/workspace-model-providers.d.ts +4 -0
  176. package/dist/routes/workspaces.d.ts +11 -2
  177. package/dist/sandbox/agent-update.d.ts +64 -0
  178. package/dist/sandbox/auth-callout.d.ts +8 -0
  179. package/dist/sandbox/channel-a.d.ts +15 -1
  180. package/dist/sandbox/enrollment.d.ts +26 -4
  181. package/dist/sandbox/machine-audience.d.ts +29 -0
  182. package/dist/sandbox/machines.d.ts +15 -2
  183. package/dist/sandbox/metrics-ingestion.d.ts +6 -2
  184. package/dist/sandbox/screen-control.d.ts +85 -0
  185. package/dist/sandbox/viewer.d.ts +5 -1
  186. package/dist/sandbox-file-artifacts.d.ts +17 -1
  187. package/dist/scheduled-task-blockers.d.ts +39 -0
  188. package/dist/scheduled-task-deletion.d.ts +3 -4
  189. package/dist/scheduled-task-lifecycle.d.ts +14 -0
  190. package/dist/secret-answers.d.ts +40 -0
  191. package/dist/session-create-observability.d.ts +5 -0
  192. package/dist/session-event-view-36GO3IL7.js +528 -0
  193. package/dist/session-event-view-36GO3IL7.js.map +1 -0
  194. package/dist/signed-state-secret.d.ts +18 -0
  195. package/dist/site-agent-guard.d.ts +53 -0
  196. package/dist/site-data/operations.d.ts +103 -0
  197. package/dist/site-data/service.d.ts +203 -0
  198. package/dist/site-frame-guard.d.ts +32 -0
  199. package/dist/site-publishing/agent-data-tools.d.ts +84 -0
  200. package/dist/site-publishing/agent-model.d.ts +58 -0
  201. package/dist/site-publishing/agent-session.d.ts +16 -0
  202. package/dist/site-publishing/content-scanner.d.ts +31 -0
  203. package/dist/site-publishing/edge-sync.d.ts +48 -0
  204. package/dist/site-publishing/gateway-signature.d.ts +56 -0
  205. package/dist/site-publishing/limits.d.ts +63 -0
  206. package/dist/site-publishing/page-tool-authority.d.ts +30 -0
  207. package/dist/site-publishing/platform-usage.d.ts +113 -0
  208. package/dist/site-publishing/platform.d.ts +21 -0
  209. package/dist/site-publishing/readiness.d.ts +62 -0
  210. package/dist/site-publishing/runtime.d.ts +179 -0
  211. package/dist/site-publishing/service.d.ts +264 -0
  212. package/dist/site-publishing/session-events.d.ts +22 -0
  213. package/dist/site-publishing/session-views.d.ts +36 -0
  214. package/dist/site-publishing/viewer.d.ts +40 -0
  215. package/dist/site-session-origin.d.ts +31 -6
  216. package/dist/site-sql/format.d.ts +47 -0
  217. package/dist/site-sql/gateway.d.ts +50 -0
  218. package/dist/site-sql/service.d.ts +213 -0
  219. package/dist/temporal-schedule-sync.d.ts +26 -0
  220. package/dist/transcription/billing-refusal.d.ts +8 -0
  221. package/dist/transcription/normalize.d.ts +23 -0
  222. package/dist/transcription/providers/azure-mai.d.ts +12 -0
  223. package/dist/transcription/providers/azure-openai.d.ts +4 -0
  224. package/dist/transcription/providers/openai.d.ts +16 -0
  225. package/dist/transcription/providers/subscription-core.d.ts +39 -0
  226. package/dist/transcription/service.d.ts +10 -1
  227. package/dist/transcription/wav-duration.d.ts +8 -0
  228. package/dist/workspace-tool-gateway-attestations.d.ts +37 -0
  229. package/dist/workspace-tool-gateway-observability.d.ts +16 -0
  230. package/dist/workspace-tool-gateway-secrets.d.ts +27 -0
  231. package/dist/workspace-tool-gateway.d.ts +102 -6
  232. package/dist/workspace-tool-target-fence.d.ts +116 -0
  233. package/dist/workspace-tool-target.d.ts +225 -0
  234. package/dist/workspace-webhook-dispatch.d.ts +26 -0
  235. package/dist/xai-subscription-core.d.ts +60 -0
  236. package/package.json +25 -20
  237. package/src/access-grant-rls.ts +64 -14
  238. package/src/admission-trace.ts +14 -0
  239. package/src/app.ts +1684 -252
  240. package/src/artifact-catalog-cursor.ts +1 -0
  241. package/src/attached-browser-viewer.ts +26 -0
  242. package/src/auth/managed-auth-attempt-context.ts +9 -0
  243. package/src/auth/managed-auth-rate-limits.ts +252 -0
  244. package/src/auth/managed-auth-session-adapter.ts +64 -29
  245. package/src/auth/managed-auth.ts +303 -35
  246. package/src/auth/managed-email.ts +18 -1
  247. package/src/auth/managed-sign-in-notifications.ts +81 -0
  248. package/src/auth/new-signups-gate.ts +49 -0
  249. package/src/auth/organization-user-setup.ts +3 -3
  250. package/src/auth/signup-funnel-metrics.ts +164 -0
  251. package/src/azure-live.ts +103 -0
  252. package/src/browser-action-fences.ts +45 -0
  253. package/src/browser-controller-authority.ts +1 -186
  254. package/src/browser-ephemeral.ts +39 -0
  255. package/src/browser-lost-runtime-end.ts +71 -0
  256. package/src/browser-state-authority.ts +1 -236
  257. package/src/claude-default-models.ts +37 -0
  258. package/src/claude-subscription-core.ts +154 -0
  259. package/src/claude-subscription-oauth.ts +352 -0
  260. package/src/claude-subscription-profile.ts +73 -0
  261. package/src/claude-subscription-usage.ts +51 -0
  262. package/src/claude-workspace-connection.ts +82 -0
  263. package/src/codemode.ts +236 -39
  264. package/src/codex-realtime.ts +181 -53
  265. package/src/codex-session-pointers.ts +80 -0
  266. package/src/company-brain-okf.ts +1 -1
  267. package/src/connected-machine-computer-access.ts +2 -1
  268. package/src/connection-ownership.ts +19 -13
  269. package/src/controller-data-plane.ts +34 -0
  270. package/src/credit-promotion-snapshot.ts +36 -0
  271. package/src/editable-artifact-production.ts +10 -0
  272. package/src/editable-artifact-source-session.ts +123 -0
  273. package/src/editable-artifact-websocket.ts +16 -3
  274. package/src/fatal-process-boundary.ts +67 -3
  275. package/src/gateway-realtime.ts +12 -8
  276. package/src/github-access.ts +31 -5
  277. package/src/github-browser-flow.ts +1 -1
  278. package/src/http/acting-person.ts +105 -0
  279. package/src/http/api-contract-compat.ts +49 -0
  280. package/src/http/api-error.ts +210 -2
  281. package/src/http/auth.ts +68 -4
  282. package/src/http/cors.ts +37 -5
  283. package/src/http/deprecation.ts +118 -0
  284. package/src/http/keyed-admission.ts +29 -0
  285. package/src/http/local-browser-boundary.ts +343 -0
  286. package/src/http/metrics-listener.ts +57 -0
  287. package/src/http/path-identifier.ts +54 -0
  288. package/src/http/registered-route-label.ts +34 -0
  289. package/src/http/rejection-telemetry.ts +120 -0
  290. package/src/http/request-body.ts +127 -0
  291. package/src/http/request-source.ts +154 -13
  292. package/src/http/sse.ts +88 -28
  293. package/src/http/unmatched-route.ts +60 -0
  294. package/src/http/user-content.ts +158 -0
  295. package/src/http/variable-sets-removed.ts +122 -0
  296. package/src/http/websocket-upgrade-dispatch.ts +29 -0
  297. package/src/index.ts +107 -57
  298. package/src/integration-connect-metrics.ts +228 -0
  299. package/src/integration-endpoint-test.ts +291 -0
  300. package/src/integrations/api-integrations.ts +81 -7
  301. package/src/integrations/atlassian.ts +202 -83
  302. package/src/integrations/fiken.ts +66 -38
  303. package/src/integrations/github-app-connect.ts +64 -18
  304. package/src/integrations/github-lens-connect.ts +8 -29
  305. package/src/integrations/google-drive.ts +448 -152
  306. package/src/integrations/integration-commit-authority.ts +69 -0
  307. package/src/integrations/marketplace-plugin.ts +1 -1
  308. package/src/integrations/oauth-client.ts +733 -193
  309. package/src/integrations/oauth-profiles.ts +116 -45
  310. package/src/integrations/onepassword-vault-provider.ts +16 -0
  311. package/src/integrations/personal-github-repositories.ts +1 -1
  312. package/src/integrations/personal-github.ts +61 -35
  313. package/src/integrations/provider-oauth.ts +67 -85
  314. package/src/integrations/slack-app-home.ts +5 -5
  315. package/src/integrations/slack-bot.ts +893 -201
  316. package/src/integrations/slack-file-upload-flow.ts +147 -0
  317. package/src/integrations/slack-install.ts +7 -7
  318. package/src/integrations/slack-interactions.ts +1589 -299
  319. package/src/integrations/slack-mrkdwn.ts +193 -0
  320. package/src/integrations/slack-routing.ts +2 -2
  321. package/src/integrations/slack-session-defaults.ts +224 -0
  322. package/src/integrations/slack-task-file-upload.ts +323 -0
  323. package/src/integrations/slack-tool-review.ts +65 -0
  324. package/src/integrations/social-oauth.ts +90 -58
  325. package/src/interaction-agent-access.ts +40 -20
  326. package/src/interaction-agent-version.ts +85 -0
  327. package/src/interaction-holder-heartbeat.ts +24 -8
  328. package/src/interaction-metrics.ts +164 -2
  329. package/src/interaction-route-stages.ts +94 -0
  330. package/src/interaction-secrets-vault.ts +213 -0
  331. package/src/interaction-secrets.ts +426 -0
  332. package/src/mcp/action-catalog-types.ts +14 -0
  333. package/src/mcp/action-catalog.gen.ts +8014 -0
  334. package/src/mcp/action-descriptions.ts +299 -0
  335. package/src/mcp/action-schema-imports.gen.ts +304 -0
  336. package/src/mcp/capability-account-readiness.ts +17 -0
  337. package/src/mcp/child-read-evidence.ts +19 -0
  338. package/src/mcp/company-profile-agent-admin.ts +13 -0
  339. package/src/mcp/contract-input.ts +165 -0
  340. package/src/mcp/documents.ts +9 -2
  341. package/src/mcp/editable-artifacts.ts +29 -11
  342. package/src/mcp/files.ts +5 -1
  343. package/src/mcp/first-party-tool-permissions.ts +7 -285
  344. package/src/mcp/knowledge-entries.ts +108 -34
  345. package/src/mcp/notification-tools.ts +480 -0
  346. package/src/mcp/orchestration-failure-diagnostic.ts +43 -0
  347. package/src/mcp/scheduled-task-input.ts +89 -0
  348. package/src/mcp/scheduled-task-view.ts +63 -3
  349. package/src/mcp/secret-tools.ts +185 -0
  350. package/src/mcp/server.ts +2213 -1116
  351. package/src/mcp/session-admin-access-tools.ts +163 -0
  352. package/src/mcp/session-event-errors.ts +123 -0
  353. package/src/mcp/session-event-view.ts +395 -13
  354. package/src/mcp/session-view.ts +3 -0
  355. package/src/mcp/session-wait.ts +67 -7
  356. package/src/mcp/site-agent-clamp-tools.ts +140 -0
  357. package/src/mcp/site-data-tools.ts +339 -0
  358. package/src/mcp/site-publication-tools.ts +201 -0
  359. package/src/mcp/site-sql-tools.ts +427 -0
  360. package/src/mcp-oauth.ts +635 -45
  361. package/src/model-calls/chat-completions.ts +352 -0
  362. package/src/model-calls/pending-usage-settlements.ts +125 -0
  363. package/src/model-calls/service.ts +694 -0
  364. package/src/model-catalog.ts +68 -17
  365. package/src/native-push-dispatch.ts +457 -0
  366. package/src/observability.ts +15 -1
  367. package/src/organization-mcp.ts +485 -0
  368. package/src/prepared-mcp-actor.ts +154 -0
  369. package/src/prepared-mcp-permissions.ts +38 -0
  370. package/src/routes/analytics-consent.ts +123 -0
  371. package/src/routes/api-integrations.ts +268 -9
  372. package/src/routes/api-keys.ts +164 -25
  373. package/src/routes/artifact-catalog.ts +111 -0
  374. package/src/routes/automations.ts +163 -90
  375. package/src/routes/billing.ts +535 -59
  376. package/src/routes/browser-sessions.ts +966 -342
  377. package/src/routes/canonical-human-identities.ts +61 -0
  378. package/src/routes/capabilities.ts +112 -5
  379. package/src/routes/channels.ts +4 -3
  380. package/src/routes/chat-completions.ts +235 -0
  381. package/src/routes/claude-core-usage.ts +147 -0
  382. package/src/routes/claude-subscription-accounts.ts +233 -0
  383. package/src/routes/claude-subscription-oauth.ts +175 -0
  384. package/src/routes/client-errors.ts +394 -0
  385. package/src/routes/codex-core.ts +687 -0
  386. package/src/routes/codex.ts +1277 -1119
  387. package/src/routes/company-brain.ts +4 -0
  388. package/src/routes/computer-sessions.ts +319 -85
  389. package/src/routes/connect.ts +277 -175
  390. package/src/routes/connections.ts +690 -363
  391. package/src/routes/desktop-distribution.ts +217 -0
  392. package/src/routes/documents.ts +13 -7
  393. package/src/routes/editable-artifacts.ts +74 -4
  394. package/src/routes/enrollments.ts +110 -16
  395. package/src/routes/external-identity-links.ts +13 -12
  396. package/src/routes/files.ts +41 -16
  397. package/src/routes/github-browser-fonts.gen.ts +12 -0
  398. package/src/routes/github-browser-pages.ts +258 -0
  399. package/src/routes/github.ts +200 -75
  400. package/src/routes/inbox.ts +353 -0
  401. package/src/routes/insights-response-cache.ts +83 -0
  402. package/src/routes/insights-usage.ts +345 -0
  403. package/src/routes/insights.ts +103 -8
  404. package/src/routes/install.ts +51 -10
  405. package/src/routes/integration-facets.ts +70 -28
  406. package/src/routes/interaction-placement-message.ts +12 -0
  407. package/src/routes/knowledge.ts +136 -27
  408. package/src/routes/machines.ts +189 -135
  409. package/src/routes/managed-auth-session-sets.ts +43 -10
  410. package/src/routes/managed-onboarding.ts +39 -19
  411. package/src/routes/managed-sign-in-methods.ts +433 -0
  412. package/src/routes/memory-slack-publications.ts +3 -2
  413. package/src/routes/model-account-choice.ts +194 -0
  414. package/src/routes/model-account-settings.ts +235 -0
  415. package/src/routes/model-connection-access.ts +210 -48
  416. package/src/routes/native-app-auth.ts +296 -0
  417. package/src/routes/organization-integration-policy.ts +116 -0
  418. package/src/routes/organization-integrations.ts +326 -0
  419. package/src/routes/organization-mcp-connections.ts +183 -0
  420. package/src/routes/organization-memberships.ts +205 -19
  421. package/src/routes/organization-model-defaults.ts +110 -0
  422. package/src/routes/organization-model-providers.ts +74 -3
  423. package/src/routes/organization-service-accounts.ts +128 -0
  424. package/src/routes/organization-sessions.ts +21 -5
  425. package/src/routes/personal-api-keys.ts +184 -0
  426. package/src/routes/personal-github-git-broker.ts +2 -2
  427. package/src/routes/personal-github.ts +9 -4
  428. package/src/routes/plugins.ts +46 -7
  429. package/src/routes/pr-review-github.ts +105 -118
  430. package/src/routes/pr-review.ts +88 -82
  431. package/src/routes/preference-registry.ts +18 -0
  432. package/src/routes/rigs.ts +48 -13
  433. package/src/routes/scheduled-tasks.ts +347 -27
  434. package/src/routes/session-admin-access.ts +271 -0
  435. package/src/routes/session-artifact-associations.ts +164 -0
  436. package/src/routes/session-history-imports.ts +305 -0
  437. package/src/routes/sessions.ts +1682 -363
  438. package/src/routes/site-data.ts +340 -0
  439. package/src/routes/site-publishing.ts +584 -0
  440. package/src/routes/site-runtime.ts +1944 -0
  441. package/src/routes/site-sql.ts +263 -0
  442. package/src/routes/skill-content.ts +72 -7
  443. package/src/routes/skills.ts +5 -4
  444. package/src/routes/slack-bot-access.ts +108 -0
  445. package/src/routes/social.ts +27 -15
  446. package/src/routes/subscription-account-pools.ts +209 -0
  447. package/src/routes/subscription-core-http.ts +18 -0
  448. package/src/routes/subscription-pool-access.ts +180 -0
  449. package/src/routes/subscription-pool-core.ts +552 -0
  450. package/src/routes/supergrok.ts +174 -655
  451. package/src/routes/transcription-recordings.ts +71 -4
  452. package/src/routes/transcriptions.ts +26 -2
  453. package/src/routes/usage-allowances.ts +381 -0
  454. package/src/routes/variable-sets-removed.ts +19 -0
  455. package/src/routes/vault-providers.ts +782 -0
  456. package/src/routes/vaults.ts +421 -0
  457. package/src/routes/video-generation.ts +30 -12
  458. package/src/routes/workspace-artifacts.ts +165 -53
  459. package/src/routes/workspace-integrations.ts +576 -0
  460. package/src/routes/workspace-model-providers.ts +148 -0
  461. package/src/routes/workspace-state.ts +4 -0
  462. package/src/routes/workspaces.ts +625 -117
  463. package/src/sandbox/agent-update.ts +315 -0
  464. package/src/sandbox/auth-callout.ts +104 -21
  465. package/src/sandbox/channel-a.ts +165 -55
  466. package/src/sandbox/enrollment.ts +131 -22
  467. package/src/sandbox/machine-audience.ts +192 -0
  468. package/src/sandbox/machines.ts +75 -31
  469. package/src/sandbox/metrics-ingestion.ts +133 -2
  470. package/src/sandbox/rematerialize.ts +51 -21
  471. package/src/sandbox/screen-control.ts +289 -0
  472. package/src/sandbox/viewer.ts +33 -58
  473. package/src/sandbox-file-artifacts.ts +53 -27
  474. package/src/scheduled-task-blockers.ts +293 -0
  475. package/src/scheduled-task-deletion.ts +13 -3
  476. package/src/scheduled-task-lifecycle.ts +84 -0
  477. package/src/secret-answers.ts +119 -0
  478. package/src/session-create-observability.ts +46 -0
  479. package/src/session-realtime-context.ts +26 -4
  480. package/src/signed-state-secret.ts +58 -0
  481. package/src/site-agent-guard.ts +261 -0
  482. package/src/site-data/operations.ts +777 -0
  483. package/src/site-data/service.ts +1043 -0
  484. package/src/site-frame-guard.ts +107 -0
  485. package/src/site-publishing/agent-data-tools.ts +250 -0
  486. package/src/site-publishing/agent-model.ts +127 -0
  487. package/src/site-publishing/agent-session.ts +148 -0
  488. package/src/site-publishing/content-scanner.ts +93 -0
  489. package/src/site-publishing/edge-sync.ts +148 -0
  490. package/src/site-publishing/gateway-signature.ts +198 -0
  491. package/src/site-publishing/limits.ts +315 -0
  492. package/src/site-publishing/page-tool-authority.ts +111 -0
  493. package/src/site-publishing/platform-usage.ts +479 -0
  494. package/src/site-publishing/platform.ts +98 -0
  495. package/src/site-publishing/readiness.ts +330 -0
  496. package/src/site-publishing/runtime.ts +831 -0
  497. package/src/site-publishing/service.ts +1282 -0
  498. package/src/site-publishing/session-events.ts +262 -0
  499. package/src/site-publishing/session-views.ts +262 -0
  500. package/src/site-publishing/viewer.ts +75 -0
  501. package/src/site-session-origin.ts +91 -6
  502. package/src/site-sql/format.ts +134 -0
  503. package/src/site-sql/gateway.ts +168 -0
  504. package/src/site-sql/service.ts +1636 -0
  505. package/src/site-uploads.ts +27 -5
  506. package/src/temporal-schedule-sync.ts +74 -0
  507. package/src/transcription/billing-refusal.ts +14 -0
  508. package/src/transcription/normalize.ts +77 -0
  509. package/src/transcription/providers/azure-mai.ts +98 -0
  510. package/src/transcription/providers/azure-openai.ts +16 -7
  511. package/src/transcription/providers/codex-subscription.ts +190 -67
  512. package/src/transcription/providers/openai.ts +79 -5
  513. package/src/transcription/providers/subscription-core.ts +125 -0
  514. package/src/transcription/providers/xai-subscription.ts +81 -83
  515. package/src/transcription/segmenter.ts +7 -1
  516. package/src/transcription/service.ts +239 -10
  517. package/src/transcription/wav-duration.ts +39 -0
  518. package/src/workspace-deletion.ts +25 -1
  519. package/src/workspace-tool-gateway-attestations.ts +133 -0
  520. package/src/workspace-tool-gateway-observability.ts +64 -0
  521. package/src/workspace-tool-gateway-secrets.ts +104 -0
  522. package/src/workspace-tool-gateway.ts +921 -103
  523. package/src/workspace-tool-target-fence.ts +380 -0
  524. package/src/workspace-tool-target.ts +554 -0
  525. package/src/workspace-webhook-dispatch.ts +225 -0
  526. package/src/xai-realtime.ts +85 -81
  527. package/src/xai-subscription-core.ts +172 -0
  528. package/dist/browser-auth-broker.d.ts +0 -20
  529. package/dist/chunk-AJ62T5QN.js.map +0 -1
  530. package/dist/connection-authority-owner.d.ts +0 -59
  531. package/dist/routes/connection-authorities.d.ts +0 -3
  532. package/dist/routes/environments.d.ts +0 -5
  533. package/dist/routes/host-mcp-bindings.d.ts +0 -5
  534. package/dist/routes/host-mcp-resolvers.d.ts +0 -3
  535. package/dist/routes/packs.d.ts +0 -3
  536. package/dist/session-event-view-MU3HMNV3.js +0 -264
  537. package/dist/session-event-view-MU3HMNV3.js.map +0 -1
  538. package/dist/xai-subscription-auth.d.ts +0 -23
  539. package/src/browser-auth-broker.ts +0 -155
  540. package/src/connection-authority-owner.ts +0 -61
  541. package/src/routes/connection-authorities.ts +0 -139
  542. package/src/routes/environments.ts +0 -388
  543. package/src/routes/host-mcp-bindings.ts +0 -122
  544. package/src/routes/host-mcp-resolvers.ts +0 -41
  545. package/src/routes/packs.ts +0 -784
  546. package/src/xai-subscription-auth.ts +0 -139
@@ -1,3 +1,18 @@
1
+ import { AZURE_LIVE_MODEL_ID } from "../azure-live";
2
+ import {
3
+ createRealtimeVoiceBilling,
4
+ deploymentRealtimeVoice,
5
+ realtimeVoiceOfferProblem,
6
+ } from "@opengeni/core";
7
+ import { withDirectModelProviders, workspaceModelCompactionPolicy } from "@opengeni/config";
8
+ import { listConnectionsMetadata } from "@opengeni/db";
9
+ import {
10
+ workspaceProviderApiKeyConnectionMetadataFromConnections,
11
+ listWorkspaceProviderCustomModelsByKind,
12
+ getOrganizationModelProviderCatalogForWorkspace,
13
+ type WorkspaceCustomModelProviderKind,
14
+ } from "@opengeni/db";
15
+ import { CLAUDE_CONNECTION_KINDS, type ClaudeConnectionCatalog } from "@opengeni/config";
1
16
  import { SessionControlConflictError, WorkspacePauseTimerInputError } from "@opengeni/db";
2
17
  import { updateWorkspaceSettingsWithToolDefaults } from "@opengeni/db/workspace-tool-defaults";
3
18
  import { WorkspacePauseTimerRequest } from "@opengeni/contracts";
@@ -17,6 +32,7 @@ import {
17
32
  DeleteWorkspaceGatewayCustomModelRequest,
18
33
  DeleteWorkspaceOpenRouterCustomModelRequest,
19
34
  UpdateWorkspaceModelPolicyRequest,
35
+ WorkspaceModelPolicyResponse,
20
36
  UpdateWorkspaceRequest,
21
37
  UpdateWorkspaceSettingsRequest,
22
38
  WORKSPACE_CONTROL_ACTOR_MAX_BYTES,
@@ -25,6 +41,9 @@ import {
25
41
  WorkspaceGatewayCustomModelsResponse,
26
42
  WorkspaceOpenRouterCustomModel,
27
43
  WorkspaceOpenRouterCustomModelsResponse,
44
+ WorkspaceOpperCustomModelsResponse,
45
+ CreateWorkspaceOpperCustomModelRequest,
46
+ DeleteWorkspaceOpperCustomModelRequest,
28
47
  WorkspaceRealtimeModelCatalogResponse,
29
48
  WorkspaceInferenceControlRequest,
30
49
  Workspace,
@@ -36,13 +55,20 @@ import {
36
55
  type WorkspaceMemberCandidate,
37
56
  type WorkspaceMember as WorkspaceMemberValue,
38
57
  } from "@opengeni/contracts";
58
+ import { loadWorkspaceCodexCatalogReadiness } from "@opengeni/core";
39
59
  import {
40
60
  allWorkspacePermissions,
61
+ getBillingBalance,
62
+ spendableCreditMicros,
41
63
  createWorkspace,
42
64
  ensureWorkspaceByExternalIdentity,
43
65
  findWorkspaceByExternalIdentity,
44
66
  getManagedUserProfilesByIds,
67
+ getWorkspace,
45
68
  getWorkspaceModelPolicy,
69
+ getWorkspaceModelPolicyLayers,
70
+ getOrganizationModelDefaults,
71
+ deleteWorkspaceModelPolicy,
46
72
  grantWorkspaceAccess,
47
73
  listWorkspaceMembers,
48
74
  listWorkspaceMemberManagementCandidates,
@@ -67,18 +93,21 @@ import {
67
93
  updateWorkspace,
68
94
  upsertWorkspaceMemberAsWorkspaceManager,
69
95
  upsertWorkspaceModelPolicy,
70
- workspaceCodexSubscriptionActive,
96
+ readCodexCutoverDisposition,
97
+ listSubscriptionCoreCodexOperationCandidates,
71
98
  workspaceControlRequestLockTimeoutMs,
72
- workspaceXaiSubscriptionActive,
73
99
  workspaceVercelAiGatewayConnectionActive,
74
- workspaceOpenRouterConnectionActive,
75
- organizationModelProviderConnectionActiveForWorkspace,
76
- listOrganizationModelProviderCustomModelsForWorkspace,
77
100
  WorkspaceGatewayCustomModelHistoryLimitError,
78
101
  WorkspaceOpenRouterCustomModelHistoryLimitError,
79
102
  WorkspaceExternalIdentityConflictError,
80
103
  WorkspaceGatewayCustomModelLimitError,
81
104
  WorkspaceOpenRouterCustomModelLimitError,
105
+ WorkspaceOpperCustomModelLimitError,
106
+ WorkspaceOpperCustomModelHistoryLimitError,
107
+ listWorkspaceProviderCustomModels,
108
+ createWorkspaceProviderCustomModel,
109
+ deleteWorkspaceProviderCustomModel,
110
+ replayWorkspaceProviderCustomModelCreate,
82
111
  WorkspaceLimitExceededError,
83
112
  } from "@opengeni/db";
84
113
  import { boundWorkspaceControlHttpPage } from "@opengeni/events";
@@ -87,14 +116,26 @@ import { HTTPException } from "hono/http-exception";
87
116
  import {
88
117
  getManagedAuthRequestActorEpoch,
89
118
  accountScopedApiKeyWorkspaceAuthority,
119
+ organizationWorkspaceInScope,
90
120
  hasPermission,
91
121
  requireAccessContext,
122
+ requireApiKeyDelegationContext,
123
+ requireExplicitPermissionDelegation,
124
+ isDeveloperSetupApiKeyContext,
92
125
  listExternalActorWorkspaces,
93
126
  addExternalWorkspaceMemberForRequest,
94
127
  requireAccessGrant,
128
+ requireWorkspaceMemberManagementAuthority,
95
129
  requireWorkspaceSettingsGrant,
96
130
  requireFreshAccessGrant,
97
131
  resolveWorkspaceCatalogSettings,
132
+ creditsDefaultSessionModel,
133
+ loadCoreRoutedXaiCatalogReadiness,
134
+ loadWorkspaceClaudeSubscriptionReadiness,
135
+ loadCoreRoutedClaudeCatalogReadiness,
136
+ withCoreRoutedXaiRestriction,
137
+ resolveDefaultSessionModelForSelections,
138
+ resolveWorkspaceModelSelection,
98
139
  } from "@opengeni/core";
99
140
  import { requireLimit } from "@opengeni/core";
100
141
  import type { ApiRouteDeps } from "@opengeni/core";
@@ -107,7 +148,7 @@ import {
107
148
  import { boundedLimit } from "../http/common";
108
149
  import { ApiHttpError } from "../http/api-error";
109
150
  import { browserSseDeliveryOptions, sseWorkspaceControlStream } from "../http/sse";
110
- import { buildWorkspaceModelCatalog } from "../model-catalog";
151
+ import { projectWorkspaceModelCatalog } from "../model-catalog";
111
152
  import { deleteWorkspaceForRequest } from "../workspace-deletion";
112
153
  import {
113
154
  AI_GATEWAY_REALTIME_MODELS,
@@ -120,10 +161,17 @@ import {
120
161
  configuredModelInputIdentities,
121
162
  configuredOpenRouterUpstreamModelIds,
122
163
  configuredOpenRouterWorkspaceProductModelIds,
164
+ configuredOpperUpstreamModelIds,
165
+ configuredOpperWorkspaceProductModelIds,
166
+ WORKSPACE_OPPER_MODEL_ID_PREFIX,
123
167
  WORKSPACE_GATEWAY_MODEL_ID_PREFIX,
124
168
  WORKSPACE_OPENROUTER_MODEL_ID_PREFIX,
169
+ sandboxImageAllowlist,
125
170
  type Settings,
126
171
  } from "@opengeni/config";
172
+ import { AddExternalWorkspaceMemberRequest } from "@opengeni/contracts/external-identities";
173
+ import { parseRequestBody, parseRequestJson, readRequestJson } from "../http/request-body";
174
+ import { workspaceXaiOperationAvailable } from "../xai-subscription-core";
127
175
 
128
176
  export function canonicalWorkspacePolicyModelIds(
129
177
  settings: Settings,
@@ -193,6 +241,14 @@ export function workspaceMembersResponse(members: readonly WorkspaceMemberProjec
193
241
  });
194
242
  }
195
243
 
244
+ export function workspaceUpdateRequestsSettings(value: unknown): boolean {
245
+ return (
246
+ typeof value === "object" &&
247
+ value !== null &&
248
+ Object.prototype.hasOwnProperty.call(value, "settings")
249
+ );
250
+ }
251
+
196
252
  export function workspaceUpdateRequestsAccountTransfer(value: unknown): boolean {
197
253
  return (
198
254
  typeof value === "object" &&
@@ -201,35 +257,90 @@ export function workspaceUpdateRequestsAccountTransfer(value: unknown): boolean
201
257
  );
202
258
  }
203
259
 
260
+ /**
261
+ * Owning organization for `PUT /v1/workspaces/external`. An organization API
262
+ * key can only ever create workspaces in its own organization, so an omitted
263
+ * `accountId` resolves to that organization. Every other caller - notably a
264
+ * human who may belong to several organizations - must name it explicitly;
265
+ * guessing a default there could create the workspace in the wrong tenant.
266
+ */
267
+ export function externalWorkspaceAccountId(
268
+ context: AccessContext,
269
+ requested: string | undefined,
270
+ ): string {
271
+ if (requested !== undefined) return requested;
272
+ const organizationKey = accountScopedApiKeyWorkspaceAuthority(context);
273
+ if (organizationKey) return organizationKey.accountId;
274
+ throw new ApiHttpError(400, {
275
+ code: "validation_failed",
276
+ message:
277
+ "Invalid request body: accountId: required - pass the id of the organization that will own the workspace (only an organization API key may omit it).",
278
+ retryable: false,
279
+ details: {
280
+ code: "invalid_request_body",
281
+ issues: [{ path: "accountId", message: "Required" }],
282
+ omittedIssueCount: 0,
283
+ },
284
+ });
285
+ }
286
+
287
+ /**
288
+ * Codex Live readiness: the legacy active subscription without a cutover
289
+ * row; on the shared core, any shared connection that could serve a
290
+ * realtime operation here; never ready during maintenance.
291
+ */
292
+ async function workspaceCodexRealtimeReady(
293
+ routeDeps: ApiRouteDeps,
294
+ grant: { accountId: string; subjectId: string },
295
+ workspaceId: string,
296
+ ): Promise<boolean> {
297
+ const disposition = await readCodexCutoverDisposition(routeDeps.db, grant.accountId, workspaceId);
298
+ if (disposition === "maintenance" || !routeDeps.settings.codexSubscriptionEnabled) return false;
299
+ const candidates = await listSubscriptionCoreCodexOperationCandidates(routeDeps.db, {
300
+ kind: "workspace",
301
+ accountId: grant.accountId,
302
+ workspaceId,
303
+ subjectId: grant.subjectId,
304
+ });
305
+ return candidates.length > 0;
306
+ }
307
+
204
308
  export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
205
309
  app.post("/v1/workspaces/:workspaceId/external-members", async (c) => {
310
+ // Validate at the HTTP boundary so a malformed body is a 400, not a raw
311
+ // schema error from the application layer.
312
+ const body = await readRequestJson(c);
313
+ parseRequestBody(AddExternalWorkspaceMemberRequest, body);
206
314
  return c.json(
207
- await addExternalWorkspaceMemberForRequest(
208
- c,
209
- deps,
210
- c.req.param("workspaceId"),
211
- await c.req.json(),
212
- ),
315
+ await addExternalWorkspaceMemberForRequest(c, deps, c.req.param("workspaceId"), body),
213
316
  );
214
317
  });
215
318
  app.get("/v1/access/me", async (c) => {
216
319
  return c.json(await requireAccessContext(c, deps));
217
320
  });
218
321
 
322
+ app.get("/v1/workspaces/:workspaceId/access/grant", async (c) => {
323
+ // Inventory may be empty for an external actor. Resolve the selected
324
+ // workspace through the canonical membership/key-ceiling boundary.
325
+ const grant = await requireAccessGrant(c, deps, c.req.param("workspaceId"));
326
+ c.header("cache-control", "private, no-store");
327
+ return c.json(grant);
328
+ });
329
+
219
330
  app.get("/v1/workspaces", async (c) => {
220
331
  const context = await requireAccessContext(c, deps);
221
332
  const externalWorkspaces = await listExternalActorWorkspaces(context, deps);
222
333
  if (externalWorkspaces !== null)
223
334
  return c.json(externalWorkspaces.map((workspace) => Workspace.parse(workspace)));
224
335
  const accountScopedAuthority = accountScopedApiKeyWorkspaceAuthority(context);
225
- if (
226
- accountScopedAuthority &&
227
- hasPermission(accountScopedAuthority.permissions, "workspace:read")
228
- ) {
336
+ if (accountScopedAuthority) {
337
+ if (!hasPermission(accountScopedAuthority.permissions, "workspace:read")) return c.json([]);
229
338
  return c.json(
230
- (await listSharedWorkspacesForAccount(deps.db, accountScopedAuthority.accountId)).map(
231
- (workspace) => Workspace.parse(workspace),
232
- ),
339
+ (await listSharedWorkspacesForAccount(deps.db, accountScopedAuthority.accountId))
340
+ .filter((workspace) =>
341
+ organizationWorkspaceInScope(accountScopedAuthority.workspaceScope, workspace.id),
342
+ )
343
+ .map((workspace) => Workspace.parse(workspace)),
233
344
  );
234
345
  }
235
346
  const readableWorkspaceIds = [
@@ -254,8 +365,10 @@ export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
254
365
 
255
366
  app.put("/v1/workspaces/external", async (c) => {
256
367
  const context = await requireAccessContext(c, deps);
257
- const payload = EnsureWorkspaceRequest.parse(await c.req.json());
258
- requireAccountPermission(context, payload.accountId, "workspace:create");
368
+ const request = await parseRequestJson(c, EnsureWorkspaceRequest);
369
+ const accountId = externalWorkspaceAccountId(context, request.accountId);
370
+ const payload = { ...request, accountId };
371
+ requireAccountPermission(context, accountId, "workspace:create");
259
372
  try {
260
373
  const existing = await findWorkspaceByExternalIdentity(deps.db, {
261
374
  accountId: payload.accountId,
@@ -266,6 +379,9 @@ export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
266
379
  if (existing.accountId !== payload.accountId || existing.kind !== "shared") {
267
380
  throw new WorkspaceExternalIdentityConflictError();
268
381
  }
382
+ const authority = accountScopedApiKeyWorkspaceAuthority(context);
383
+ if (authority && !organizationWorkspaceInScope(authority.workspaceScope, existing.id))
384
+ throw new HTTPException(403, { message: "workspace is outside organization key scope" });
269
385
  return c.json(
270
386
  EnsureWorkspaceResponse.parse({
271
387
  workspace: existing,
@@ -292,6 +408,13 @@ export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
292
408
  maxWorkspacesPerAccount: workspaceLimit(deps),
293
409
  });
294
410
  const response = EnsureWorkspaceResponse.parse(result);
411
+ const authority = accountScopedApiKeyWorkspaceAuthority(context);
412
+ if (
413
+ !result.created &&
414
+ authority &&
415
+ !organizationWorkspaceInScope(authority.workspaceScope, result.workspace.id)
416
+ )
417
+ throw new HTTPException(403, { message: "workspace is outside organization key scope" });
295
418
  return result.created ? c.json(response, 201) : c.json(response);
296
419
  } catch (error) {
297
420
  if (error instanceof WorkspaceExternalIdentityConflictError) {
@@ -308,7 +431,7 @@ export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
308
431
 
309
432
  app.post("/v1/workspaces", async (c) => {
310
433
  const context = await requireAccessContext(c, deps);
311
- const payload = CreateWorkspaceRequest.parse(await c.req.json());
434
+ const payload = await parseRequestJson(c, CreateWorkspaceRequest);
312
435
  const accountId = payload.accountId ?? context.defaultAccountId;
313
436
  if (!accountId) {
314
437
  throw new HTTPException(409, {
@@ -335,14 +458,21 @@ export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
335
458
  : {}),
336
459
  maxWorkspacesPerAccount: workspaceLimit(deps),
337
460
  });
338
- await grantWorkspaceAccess(deps.db, {
339
- accountId,
340
- workspaceId: workspace.id,
341
- subjectId: context.subjectId,
342
- role: "owner",
343
- permissions: allWorkspacePermissions,
344
- ...(context.subjectLabel ? { subjectLabel: context.subjectLabel } : {}),
345
- });
461
+ // Setup keys already have canonical same-organization workspace access.
462
+ // Do not widen that ceiling with an all-permissions creator membership.
463
+ if (
464
+ !isDeveloperSetupApiKeyContext(context) &&
465
+ accountScopedApiKeyWorkspaceAuthority(context)?.permissionMode !== "explicit"
466
+ ) {
467
+ await grantWorkspaceAccess(deps.db, {
468
+ accountId,
469
+ workspaceId: workspace.id,
470
+ subjectId: context.subjectId,
471
+ role: "owner",
472
+ permissions: allWorkspacePermissions,
473
+ ...(context.subjectLabel ? { subjectLabel: context.subjectLabel } : {}),
474
+ });
475
+ }
346
476
  return c.json(Workspace.parse(workspace), 201);
347
477
  } catch (error) {
348
478
  if (error instanceof WorkspaceLimitExceededError) {
@@ -361,7 +491,25 @@ export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
361
491
  app.patch("/v1/workspaces/:workspaceId", async (c) => {
362
492
  const workspaceId = c.req.param("workspaceId");
363
493
  await requireAccessGrant(c, deps, workspaceId, "workspace:admin");
364
- const body = await c.req.json();
494
+ const body = await readRequestJson(c);
495
+ if (workspaceUpdateRequestsSettings(body)) {
496
+ throw new ApiHttpError(400, {
497
+ code: "validation_failed",
498
+ message:
499
+ "Invalid request body: settings: workspace settings are not accepted here; send them to PATCH /v1/workspaces/:workspaceId/settings (SDK: updateWorkspaceSettings).",
500
+ retryable: false,
501
+ details: {
502
+ code: "invalid_request_body",
503
+ issues: [
504
+ {
505
+ path: "settings",
506
+ message: "Use PATCH /v1/workspaces/:workspaceId/settings",
507
+ },
508
+ ],
509
+ omittedIssueCount: 0,
510
+ },
511
+ });
512
+ }
365
513
  if (workspaceUpdateRequestsAccountTransfer(body)) {
366
514
  throw new ApiHttpError(409, {
367
515
  code: "conflict",
@@ -372,7 +520,7 @@ export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
372
520
  details: { code: "workspace_transfer_unsupported" },
373
521
  });
374
522
  }
375
- const payload = UpdateWorkspaceRequest.parse(body);
523
+ const payload = parseRequestBody(UpdateWorkspaceRequest, body);
376
524
  const workspace = await updateWorkspace(deps.db, workspaceId, {
377
525
  ...(payload.name !== undefined ? { name: payload.name.trim() } : {}),
378
526
  ...(payload.slug !== undefined ? { slug: payload.slug?.trim() || null } : {}),
@@ -396,6 +544,13 @@ export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
396
544
  message: "invalid workspace settings patch",
397
545
  });
398
546
  }
547
+ const requestedImage = parsed.data.defaultSandboxImage;
548
+ if (requestedImage && !sandboxImageAllowlist(deps.settings).includes(requestedImage)) {
549
+ throw new HTTPException(422, {
550
+ message:
551
+ "defaultSandboxImage must be one of the images in this deployment's OPENGENI_SANDBOX_IMAGE_ALLOWLIST",
552
+ });
553
+ }
399
554
  // Request-scoped: bound the exclusive control-prefix wait so a busy
400
555
  // workspace yields the retryable 503 instead of parking this request.
401
556
  try {
@@ -427,77 +582,188 @@ export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
427
582
  app.get("/v1/workspaces/:workspaceId/model-catalog", async (c) => {
428
583
  const workspaceId = c.req.param("workspaceId");
429
584
  const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:read");
585
+ const resolvedCatalog = await deps.resolveCatalogSettings();
586
+ const providerKinds: WorkspaceCustomModelProviderKind[] = [
587
+ "vercel_gateway",
588
+ "openrouter",
589
+ "opper",
590
+ ...CLAUDE_CONNECTION_KINDS.filter(
591
+ (kind) =>
592
+ kind !== "claude_subscription" || resolvedCatalog.settings.claudeSubscriptionEnabled,
593
+ ),
594
+ ];
595
+ // Claude by its provider route: the shared core's projection (readiness
596
+ // and both Claude model restrictions) once it has cut over, else legacy.
597
+ const claudeCore = loadCoreRoutedClaudeCatalogReadiness(deps.db, resolvedCatalog.settings, {
598
+ accountId: grant.accountId,
599
+ workspaceId,
600
+ subjectId: grant.subjectId,
601
+ });
430
602
  const [
431
- connectionModelRestrictions,
432
- resolvedCatalog,
603
+ currentConnectionModelRestrictions,
433
604
  policy,
434
- codexSubscriptionActive,
435
- xaiSubscriptionActive,
436
- workspaceGatewayConnectionActive,
437
- workspaceGatewayCustomModels,
438
- openRouterConnectionActive,
439
- workspaceOpenRouterCustomModels,
440
- organizationGatewayConnectionActive,
441
- organizationOpenRouterConnectionActive,
442
- organizationGatewayCustomModels,
443
- organizationOpenRouterCustomModels,
605
+ codexCatalog,
606
+ xaiCatalog,
607
+ claudePool,
608
+ workspaceConnections,
609
+ workspaceCustomModels,
610
+ organizationProviders,
611
+ workspace,
444
612
  ] = await Promise.all([
445
- getWorkspaceConnectionModelRestrictions(deps.db, workspaceId, grant.subjectId),
446
- deps.resolveCatalogSettings(),
613
+ claudeCore.then((core) =>
614
+ getWorkspaceConnectionModelRestrictions(
615
+ deps.db,
616
+ workspaceId,
617
+ grant.subjectId,
618
+ undefined,
619
+ core?.restrictions,
620
+ ),
621
+ ),
447
622
  getWorkspaceModelPolicy(deps.db, workspaceId),
448
- workspaceCodexSubscriptionActive(deps.db, deps.settings, workspaceId),
449
- workspaceXaiSubscriptionActive(deps.db, deps.settings, workspaceId, grant.subjectId),
450
- workspaceVercelAiGatewayConnectionActive(deps.db, workspaceId),
451
- listWorkspaceGatewayCustomModels(deps.db, {
452
- accountId: grant.accountId,
453
- workspaceId,
454
- }),
455
- workspaceOpenRouterConnectionActive(deps.db, workspaceId),
456
- listWorkspaceOpenRouterCustomModels(deps.db, {
457
- accountId: grant.accountId,
458
- workspaceId,
459
- }),
460
- organizationModelProviderConnectionActiveForWorkspace(deps.db, {
461
- accountId: grant.accountId,
462
- workspaceId,
463
- providerKind: "vercel_gateway",
464
- }),
465
- organizationModelProviderConnectionActiveForWorkspace(deps.db, {
623
+ // By the organization's Codex cutover row: legacy without one, the
624
+ // shared core for the caller when enabled, not ready while disabled.
625
+ loadWorkspaceCodexCatalogReadiness(
626
+ deps.db,
627
+ resolvedCatalog.settings,
628
+ { accountId: grant.accountId, workspaceId, subjectId: grant.subjectId },
629
+ { activeSettings: deps.settings },
630
+ ),
631
+ // SuperGrok runs only on the shared core: its projection, or not
632
+ // ready on any other route.
633
+ loadCoreRoutedXaiCatalogReadiness(deps.db, deps.settings, {
466
634
  accountId: grant.accountId,
467
635
  workspaceId,
468
- providerKind: "openrouter",
469
- }),
470
- listOrganizationModelProviderCustomModelsForWorkspace(deps.db, {
636
+ subjectId: grant.subjectId,
637
+ }).then(async (core) => ({
638
+ core,
639
+ active: core?.ready ?? false,
640
+ })),
641
+ // The route read above decides for both Claude readers (read once).
642
+ claudeCore.then(
643
+ async (core) =>
644
+ await loadWorkspaceClaudeSubscriptionReadiness(
645
+ deps.db,
646
+ resolvedCatalog.settings,
647
+ { accountId: grant.accountId, workspaceId, subjectId: grant.subjectId },
648
+ core,
649
+ ),
650
+ ),
651
+ listConnectionsMetadata(deps.db, workspaceId, null),
652
+ listWorkspaceProviderCustomModelsByKind(deps.db, {
471
653
  accountId: grant.accountId,
472
654
  workspaceId,
473
- providerKind: "vercel_gateway",
655
+ providerKinds,
474
656
  }),
475
- listOrganizationModelProviderCustomModelsForWorkspace(deps.db, {
657
+ getOrganizationModelProviderCatalogForWorkspace(deps.db, {
476
658
  accountId: grant.accountId,
477
659
  workspaceId,
478
- providerKind: "openrouter",
660
+ providerKinds,
479
661
  }),
662
+ getWorkspace(deps.db, workspaceId),
480
663
  ]);
481
- c.header("cache-control", "private, no-store");
482
- return c.json(
483
- WorkspaceModelCatalogResponse.parse(
484
- buildWorkspaceModelCatalog({
485
- connectionModelRestrictions,
486
- settings: resolvedCatalog.settings,
487
- policy,
488
- codexSubscriptionActive,
489
- xaiSubscriptionActive,
490
- workspaceGatewayConnectionActive,
491
- workspaceGatewayCustomModels,
492
- workspaceOpenRouterConnectionActive: openRouterConnectionActive,
493
- workspaceOpenRouterCustomModels,
494
- organizationGatewayConnectionActive,
495
- organizationOpenRouterConnectionActive,
496
- organizationGatewayCustomModels,
497
- organizationOpenRouterCustomModels,
498
- }),
499
- ),
664
+ const xaiSubscriptionActive = xaiCatalog.active;
665
+ const connectionModelRestrictions = xaiCatalog.core
666
+ ? withCoreRoutedXaiRestriction(currentConnectionModelRestrictions, xaiCatalog.core)
667
+ : currentConnectionModelRestrictions;
668
+ const organizationDefaults = await getOrganizationModelDefaults(deps.db, grant.accountId);
669
+ const claudeConnections: ClaudeConnectionCatalog = {};
670
+ const workspaceClaudeConnections: ClaudeConnectionCatalog = {};
671
+ const workspaceConnectionActive = (kind: WorkspaceCustomModelProviderKind) =>
672
+ workspaceProviderApiKeyConnectionMetadataFromConnections(workspaceConnections, kind) !== null;
673
+ for (const kind of CLAUDE_CONNECTION_KINDS) {
674
+ if (kind === "claude_subscription" && !resolvedCatalog.settings.claudeSubscriptionEnabled)
675
+ continue;
676
+ claudeConnections[kind] = {
677
+ active:
678
+ kind === "claude_subscription"
679
+ ? claudePool.organization
680
+ : organizationProviders[kind].active,
681
+ models: organizationProviders[kind].models,
682
+ };
683
+ workspaceClaudeConnections[kind] = {
684
+ active:
685
+ kind === "claude_subscription" ? claudePool.workspace : workspaceConnectionActive(kind),
686
+ models: workspaceCustomModels[kind],
687
+ };
688
+ }
689
+ const workspaceCatalogSettings = withDirectModelProviders(
690
+ resolvedCatalog.settings,
691
+ workspaceConnections,
500
692
  );
693
+ const selections = resolveWorkspaceModelSelection({
694
+ claudeConnections,
695
+ workspaceClaudeConnections,
696
+ connectionModelRestrictions,
697
+ settings: workspaceCatalogSettings,
698
+ policy,
699
+ observations: codexCatalog.observations,
700
+ codexSubscriptionActive: codexCatalog.active,
701
+ xaiSubscriptionActive,
702
+ workspaceGatewayConnectionActive: workspaceConnectionActive("vercel_gateway"),
703
+ workspaceGatewayCustomModels: workspaceCustomModels.vercel_gateway,
704
+ workspaceOpenRouterConnectionActive: workspaceConnectionActive("openrouter"),
705
+ workspaceOpenRouterCustomModels: workspaceCustomModels.openrouter,
706
+ organizationGatewayConnectionActive: organizationProviders.vercel_gateway.active,
707
+ organizationOpenRouterConnectionActive: organizationProviders.openrouter.active,
708
+ organizationGatewayCustomModels: organizationProviders.vercel_gateway.models,
709
+ organizationOpenRouterCustomModels: organizationProviders.openrouter.models,
710
+ workspaceOpperConnectionActive: workspaceConnectionActive("opper"),
711
+ workspaceOpperCustomModels: workspaceCustomModels.opper,
712
+ organizationOpperConnectionActive: organizationProviders.opper.active,
713
+ organizationOpperCustomModels: organizationProviders.opper.models,
714
+ });
715
+ // The same precedence the server applies when a new chat, API create, or
716
+ // scheduled occurrence names no model; published so pickers show it.
717
+ const workspaceSettings = workspace?.settings ?? {};
718
+ const creditBalance =
719
+ deps.settings.billingMode === "stripe"
720
+ ? await getBillingBalance(deps.db, grant.accountId)
721
+ : undefined;
722
+ const defaultSelection = await resolveDefaultSessionModelForSelections(deps.db, {
723
+ settings: workspaceCatalogSettings,
724
+ accountId: grant.accountId,
725
+ workspaceSettings,
726
+ selections,
727
+ organizationDefaults,
728
+ });
729
+ const catalog = projectWorkspaceModelCatalog(selections, {
730
+ defaultSelection,
731
+ creditsSelection: creditsDefaultSessionModel({
732
+ settings: workspaceCatalogSettings,
733
+ selections,
734
+ workspaceSettings,
735
+ organizationDefaults,
736
+ }),
737
+ });
738
+ for (const model of catalog.models) {
739
+ model.compactionPolicy = workspaceModelCompactionPolicy(
740
+ workspaceCatalogSettings,
741
+ {
742
+ id: model.id,
743
+ contextWindowTokens:
744
+ model.executionLimits?.contextWindowTokens ?? model.contextWindowTokens,
745
+ effectiveContextWindowTokens:
746
+ model.executionLimits?.effectiveContextWindowTokens ?? undefined,
747
+ autoCompactTokenLimit: model.executionLimits?.autoCompactTokenLimit ?? undefined,
748
+ },
749
+ workspaceSettings,
750
+ organizationDefaults,
751
+ );
752
+ }
753
+ if (creditBalance) {
754
+ for (const model of catalog.models) {
755
+ if (model.cost !== "credits") continue;
756
+ model.creditFunding = creditBalance.promotionalCredits?.some(
757
+ (credit) => credit.remainingMicros > 0 && credit.eligibleModelIds.includes(model.id),
758
+ )
759
+ ? "promotional"
760
+ : spendableCreditMicros(creditBalance, model.id) > 0
761
+ ? "general"
762
+ : "unavailable";
763
+ }
764
+ }
765
+ c.header("cache-control", "private, no-store");
766
+ return c.json(WorkspaceModelCatalogResponse.parse(catalog));
501
767
  });
502
768
 
503
769
  app.get("/v1/workspaces/:workspaceId/gateway-custom-models", async (c) => {
@@ -776,12 +1042,157 @@ export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
776
1042
  return c.body(null, 204);
777
1043
  });
778
1044
 
1045
+ app.get("/v1/workspaces/:workspaceId/opper-custom-models", async (c) => {
1046
+ const workspaceId = c.req.param("workspaceId");
1047
+ const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:read");
1048
+ const models = await listWorkspaceProviderCustomModels(deps.db, {
1049
+ accountId: grant.accountId,
1050
+ workspaceId,
1051
+ providerKind: "opper",
1052
+ });
1053
+ c.header("cache-control", "private, no-store");
1054
+ return c.json(
1055
+ WorkspaceOpperCustomModelsResponse.parse({
1056
+ models: models.map(projectWorkspaceOpenRouterCustomModel),
1057
+ }),
1058
+ );
1059
+ });
1060
+
1061
+ app.post("/v1/workspaces/:workspaceId/opper-custom-models", async (c) => {
1062
+ const workspaceId = c.req.param("workspaceId");
1063
+ const grant = await requireWorkspaceSettingsGrant(c, deps, workspaceId);
1064
+ const parsed = CreateWorkspaceOpperCustomModelRequest.safeParse(
1065
+ await c.req.json().catch(() => null),
1066
+ );
1067
+ if (!parsed.success) {
1068
+ throw new HTTPException(422, { message: "invalid Opper custom model" });
1069
+ }
1070
+ const requestHash = workspaceCustomModelRequestHash({
1071
+ action: "create",
1072
+ upstreamModelId: parsed.data.upstreamModelId,
1073
+ label: parsed.data.label ?? null,
1074
+ });
1075
+ const replay = await replayWorkspaceProviderCustomModelCreate(deps.db, {
1076
+ accountId: grant.accountId,
1077
+ workspaceId,
1078
+ providerKind: "opper",
1079
+ operationId: parsed.data.operationId,
1080
+ requestHash,
1081
+ });
1082
+ if (replay.outcome === "conflict") {
1083
+ throw new HTTPException(409, {
1084
+ message: "Opper custom model operation conflicts with current state",
1085
+ });
1086
+ }
1087
+ if (replay.outcome === "success") {
1088
+ return c.json(projectWorkspaceOpenRouterCustomModel(replay.model), 201);
1089
+ }
1090
+ const catalog = await deps.resolveCatalogSettings();
1091
+ if (configuredOpperUpstreamModelIds(catalog.settings).includes(parsed.data.upstreamModelId)) {
1092
+ throw new HTTPException(422, {
1093
+ message: "Opper model is already included in the deployment catalog",
1094
+ });
1095
+ }
1096
+ const customProductId = `${WORKSPACE_OPPER_MODEL_ID_PREFIX}${parsed.data.upstreamModelId}`;
1097
+ const deploymentProductIds = new Set([
1098
+ ...configuredModelInputIdentities(catalog.settings),
1099
+ ...configuredOpperWorkspaceProductModelIds(catalog.settings),
1100
+ ]);
1101
+ if (deploymentProductIds.has(customProductId)) {
1102
+ throw new HTTPException(422, {
1103
+ message: "Opper model product id conflicts with the deployment catalog",
1104
+ });
1105
+ }
1106
+ try {
1107
+ const model = await createWorkspaceProviderCustomModel(deps.db, {
1108
+ accountId: grant.accountId,
1109
+ workspaceId,
1110
+ providerKind: "opper",
1111
+ upstreamModelId: parsed.data.upstreamModelId,
1112
+ label: parsed.data.label ?? null,
1113
+ operationId: parsed.data.operationId,
1114
+ requestHash,
1115
+ createdBySubjectId: grant.subjectId,
1116
+ });
1117
+ if (!model || model.retiredAt) {
1118
+ throw new HTTPException(409, {
1119
+ message: "Opper custom model operation conflicts with current state",
1120
+ });
1121
+ }
1122
+ return c.json(projectWorkspaceOpenRouterCustomModel(model), 201);
1123
+ } catch (error) {
1124
+ if (
1125
+ error instanceof WorkspaceOpperCustomModelLimitError ||
1126
+ error instanceof WorkspaceOpperCustomModelHistoryLimitError
1127
+ ) {
1128
+ throw new HTTPException(422, { message: error.message });
1129
+ }
1130
+ if (nestedPostgresSqlState(error) === "23505") {
1131
+ throw new HTTPException(422, {
1132
+ message: "Opper custom model already exists",
1133
+ });
1134
+ }
1135
+ throw error;
1136
+ }
1137
+ });
1138
+
1139
+ app.delete("/v1/workspaces/:workspaceId/opper-custom-models/:customModelId", async (c) => {
1140
+ const workspaceId = c.req.param("workspaceId");
1141
+ const grant = await requireWorkspaceSettingsGrant(c, deps, workspaceId);
1142
+ const customModelId = c.req.param("customModelId");
1143
+ if (
1144
+ !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(
1145
+ customModelId,
1146
+ )
1147
+ ) {
1148
+ throw new HTTPException(422, {
1149
+ message: "invalid Opper custom model id",
1150
+ });
1151
+ }
1152
+ const parsed = DeleteWorkspaceOpperCustomModelRequest.safeParse(
1153
+ await c.req.json().catch(() => null),
1154
+ );
1155
+ if (!parsed.success) {
1156
+ throw new HTTPException(422, {
1157
+ message: "invalid Opper custom model deletion",
1158
+ });
1159
+ }
1160
+ const removed = await deleteWorkspaceProviderCustomModel(deps.db, {
1161
+ accountId: grant.accountId,
1162
+ workspaceId,
1163
+ providerKind: "opper",
1164
+ customModelId,
1165
+ expectedVersion: parsed.data.expectedVersion,
1166
+ operationId: parsed.data.operationId,
1167
+ requestHash: workspaceCustomModelRequestHash({
1168
+ action: "delete",
1169
+ customModelId,
1170
+ expectedVersion: parsed.data.expectedVersion,
1171
+ }),
1172
+ });
1173
+ if (removed.outcome === "not_found") {
1174
+ throw new HTTPException(404, {
1175
+ message: "Opper custom model not found",
1176
+ });
1177
+ }
1178
+ if (removed.outcome === "conflict") {
1179
+ throw new HTTPException(409, {
1180
+ message: "Opper custom model changed; reload and retry",
1181
+ });
1182
+ }
1183
+ return c.body(null, 204);
1184
+ });
1185
+
779
1186
  app.get("/v1/workspaces/:workspaceId/realtime-model-catalog", async (c) => {
780
1187
  const workspaceId = c.req.param("workspaceId");
781
1188
  const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:read");
782
1189
  const [codexConnected, supergrokConnected, workspaceGatewayConnected] = await Promise.all([
783
- workspaceCodexSubscriptionActive(deps.db, deps.settings, workspaceId),
784
- workspaceXaiSubscriptionActive(deps.db, deps.settings, workspaceId, grant.subjectId),
1190
+ workspaceCodexRealtimeReady(deps, grant, workspaceId),
1191
+ workspaceXaiOperationAvailable(deps.db, deps.settings, {
1192
+ accountId: grant.accountId,
1193
+ workspaceId,
1194
+ subjectId: grant.subjectId,
1195
+ }),
785
1196
  workspaceVercelAiGatewayConnectionActive(deps.db, workspaceId),
786
1197
  ]);
787
1198
  const availability = (
@@ -793,16 +1204,62 @@ export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
793
1204
  : { available: false, unavailableReason: credentialReason };
794
1205
  };
795
1206
  const gatewayModels = Object.values(AI_GATEWAY_REALTIME_MODELS);
796
- const models = [
797
- ...gatewayModels.map((model, index) => ({
1207
+ // Deployment-funded voice is listed only when this deployment can offer
1208
+ // it (configured, and priced while credits are enforced). Hosted GPT Live
1209
+ // replaces the managed Gateway choices when configured.
1210
+ const managedCandidates = [
1211
+ {
1212
+ id: AZURE_LIVE_MODEL_ID,
1213
+ label: "GPT Live 1",
1214
+ description: "Realtime voice with session delegation",
1215
+ },
1216
+ ...gatewayModels.map((model) => ({
798
1217
  id: model.managedModelId,
799
1218
  label: model.label,
800
- provider: "OpenGeni" as const,
801
1219
  description: model.description,
802
- ...availability(
803
- Boolean(deps.settings.vercelAiGatewayApiKey),
804
- "OpenGeni Gateway voice is not configured",
805
- ),
1220
+ })),
1221
+ ]
1222
+ .filter((model) => {
1223
+ const voice = deploymentRealtimeVoice(deps.settings, model.id);
1224
+ return voice !== null && realtimeVoiceOfferProblem(deps.settings, voice) === null;
1225
+ })
1226
+ .filter((model, _index, offered) =>
1227
+ offered.some((candidate) => candidate.id === AZURE_LIVE_MODEL_ID)
1228
+ ? model.id === AZURE_LIVE_MODEL_ID
1229
+ : true,
1230
+ );
1231
+ const realtimeVoiceBilling = createRealtimeVoiceBilling({
1232
+ db: deps.db,
1233
+ settings: deps.settings,
1234
+ });
1235
+ const [creditStanding, creditsDisabled] =
1236
+ managedCandidates.length > 0
1237
+ ? await Promise.all([
1238
+ realtimeVoiceBilling.creditStanding(grant.accountId),
1239
+ realtimeVoiceBilling.creditsDisabled(workspaceId),
1240
+ ])
1241
+ : (["none", false] as const);
1242
+ const hasCredits = creditStanding === "spendable";
1243
+ const models = [
1244
+ ...managedCandidates.map((model, index) => ({
1245
+ ...model,
1246
+ provider: "OpenGeni" as const,
1247
+ ...(creditsDisabled
1248
+ ? {
1249
+ available: false,
1250
+ unavailableReason: "Opengeni credits are turned off in this workspace",
1251
+ unavailableCode: "credits_disabled",
1252
+ }
1253
+ : hasCredits
1254
+ ? { available: true, unavailableReason: null, unavailableCode: null }
1255
+ : {
1256
+ available: false,
1257
+ unavailableReason:
1258
+ creditStanding === "promotional_only"
1259
+ ? "Promotional credits don't cover live voice. Add credits to use it."
1260
+ : "Add Opengeni credits to use live voice",
1261
+ unavailableCode: "insufficient_credits",
1262
+ }),
806
1263
  recommended: index === 0,
807
1264
  })),
808
1265
  {
@@ -834,35 +1291,42 @@ export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
834
1291
  return c.json(WorkspaceRealtimeModelCatalogResponse.parse({ models }));
835
1292
  });
836
1293
 
1294
+ // The policy the workspace runs with and where it comes from: its own
1295
+ // saved policy, else its organization's default, else no restriction.
837
1296
  app.get("/v1/workspaces/:workspaceId/model-policy", async (c) => {
838
1297
  const workspaceId = c.req.param("workspaceId");
839
1298
  await requireAccessGrant(c, deps, workspaceId, "workspace:read");
840
- const policy = await getWorkspaceModelPolicy(deps.db, workspaceId);
841
- return c.json({
842
- allowedProviders: policy?.allowedProviders ?? null,
843
- allowedModels: policy?.allowedModels ?? null,
844
- });
1299
+ return c.json(await workspaceModelPolicyResponse(deps, workspaceId));
1300
+ });
1301
+
1302
+ // Remove the workspace's own policy so it follows the organization default.
1303
+ app.delete("/v1/workspaces/:workspaceId/model-policy", async (c) => {
1304
+ const workspaceId = c.req.param("workspaceId");
1305
+ const grant = await requireWorkspaceSettingsGrant(c, deps, workspaceId);
1306
+ await deleteWorkspaceModelPolicy(deps.db, { accountId: grant.accountId, workspaceId });
1307
+ return c.json(await workspaceModelPolicyResponse(deps, workspaceId));
845
1308
  });
846
1309
 
847
1310
  // Full replace (PUT, not merge): null/omitted = unrestricted for that
848
- // dimension; an empty array is a valid explicit total block. Settings access
1311
+ // dimension; an empty array is a valid explicit total block. The credit
1312
+ // switch is a workspace setting and never changes here. Settings access
849
1313
  // admits workspace administrators and the verified Personal owner, without
850
1314
  // widening membership or API-key delegation authority.
851
1315
  app.put("/v1/workspaces/:workspaceId/model-policy", async (c) => {
852
1316
  const workspaceId = c.req.param("workspaceId");
853
1317
  const grant = await requireWorkspaceSettingsGrant(c, deps, workspaceId);
854
- const payload = UpdateWorkspaceModelPolicyRequest.parse(await c.req.json());
1318
+ const payload = await parseRequestJson(c, UpdateWorkspaceModelPolicyRequest);
855
1319
  const catalog = await resolveWorkspaceCatalogSettings(deps.db, deps.settings, {
856
1320
  accountId: grant.accountId,
857
1321
  workspaceId,
858
1322
  });
859
- const policy = await upsertWorkspaceModelPolicy(deps.db, {
1323
+ await upsertWorkspaceModelPolicy(deps.db, {
860
1324
  accountId: grant.accountId,
861
1325
  workspaceId,
862
1326
  allowedProviders: payload.allowedProviders ?? null,
863
1327
  allowedModels: canonicalWorkspacePolicyModelIds(catalog.settings, payload.allowedModels),
864
1328
  });
865
- return c.json(policy);
1329
+ return c.json(await workspaceModelPolicyResponse(deps, workspaceId));
866
1330
  });
867
1331
 
868
1332
  app.post("/v1/workspaces/:workspaceId/pause-timer", async (c) => {
@@ -968,10 +1432,10 @@ export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
968
1432
 
969
1433
  app.put("/v1/workspaces/:workspaceId/default-rig", async (c) => {
970
1434
  const workspaceId = c.req.param("workspaceId");
971
- await requireAccessGrant(c, deps, workspaceId, "rigs:manage");
972
- const payload = SetWorkspaceDefaultRigRequest.parse(await c.req.json());
1435
+ const grant = await requireAccessGrant(c, deps, workspaceId, "rigs:manage");
1436
+ const payload = await parseRequestJson(c, SetWorkspaceDefaultRigRequest);
973
1437
  if (payload.rigId) {
974
- const rig = await getRig(deps.db, workspaceId, payload.rigId);
1438
+ const rig = await getRig(deps.db, grant, payload.rigId);
975
1439
  if (!rig) {
976
1440
  throw new HTTPException(422, {
977
1441
  message: `unknown rigId: ${payload.rigId}`,
@@ -996,14 +1460,19 @@ export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
996
1460
 
997
1461
  app.get("/v1/workspaces/:workspaceId/members", async (c) => {
998
1462
  const workspaceId = c.req.param("workspaceId");
999
- await requireAccessGrant(c, deps, workspaceId, "workspace:read");
1463
+ await requireWorkspaceMemberManagementAuthority(c, deps, workspaceId, "workspace:read");
1000
1464
  const members = await listWorkspacePeople(deps, workspaceId);
1001
1465
  return c.json(workspaceMembersResponse(members));
1002
1466
  });
1003
1467
 
1004
1468
  app.get("/v1/workspaces/:workspaceId/member-candidates", async (c) => {
1005
1469
  const workspaceId = c.req.param("workspaceId");
1006
- const grant = await requireAccessGrant(c, deps, workspaceId, "members:manage");
1470
+ const { grant } = await requireWorkspaceMemberManagementAuthority(
1471
+ c,
1472
+ deps,
1473
+ workspaceId,
1474
+ "members:manage",
1475
+ );
1007
1476
  try {
1008
1477
  return c.json(
1009
1478
  ListWorkspaceMemberCandidatesResponse.parse({
@@ -1021,8 +1490,15 @@ export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
1021
1490
 
1022
1491
  app.post("/v1/workspaces/:workspaceId/members", async (c) => {
1023
1492
  const workspaceId = c.req.param("workspaceId");
1024
- const grant = await requireAccessGrant(c, deps, workspaceId, "members:manage");
1025
- const payload = AddWorkspaceMemberRequest.parse(await c.req.json());
1493
+ const { grant } = await requireWorkspaceMemberManagementAuthority(
1494
+ c,
1495
+ deps,
1496
+ workspaceId,
1497
+ "members:manage",
1498
+ );
1499
+ const payload = await parseRequestJson(c, AddWorkspaceMemberRequest);
1500
+ requireApiKeyDelegationContext(await requireAccessContext(c, deps), payload.permissions);
1501
+ requireExplicitPermissionDelegation(grant, payload.permissions);
1026
1502
  let candidates: WorkspaceMemberCandidate[];
1027
1503
  try {
1028
1504
  candidates = await listWorkspaceMemberManagementCandidates(deps.db, {
@@ -1072,9 +1548,16 @@ export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
1072
1548
 
1073
1549
  app.patch("/v1/workspaces/:workspaceId/members/:subjectId", async (c) => {
1074
1550
  const workspaceId = c.req.param("workspaceId");
1075
- const grant = await requireAccessGrant(c, deps, workspaceId, "members:manage");
1551
+ const { grant } = await requireWorkspaceMemberManagementAuthority(
1552
+ c,
1553
+ deps,
1554
+ workspaceId,
1555
+ "members:manage",
1556
+ );
1076
1557
  const subjectId = decodeURIComponent(c.req.param("subjectId"));
1077
- const payload = UpdateWorkspaceMemberRequest.parse(await c.req.json());
1558
+ const payload = await parseRequestJson(c, UpdateWorkspaceMemberRequest);
1559
+ requireApiKeyDelegationContext(await requireAccessContext(c, deps), payload.permissions);
1560
+ requireExplicitPermissionDelegation(grant, payload.permissions);
1078
1561
  const existing = await listWorkspacePeople(deps, workspaceId);
1079
1562
  const current = existing.find((member) => member.subjectId === subjectId);
1080
1563
  if (!current) {
@@ -1110,7 +1593,12 @@ export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
1110
1593
 
1111
1594
  app.delete("/v1/workspaces/:workspaceId/members/:subjectId", async (c) => {
1112
1595
  const workspaceId = c.req.param("workspaceId");
1113
- const grant = await requireAccessGrant(c, deps, workspaceId, "members:manage");
1596
+ const { grant, organizationAdministrator } = await requireWorkspaceMemberManagementAuthority(
1597
+ c,
1598
+ deps,
1599
+ workspaceId,
1600
+ "members:manage",
1601
+ );
1114
1602
  const subjectId = decodeURIComponent(c.req.param("subjectId"));
1115
1603
  const members = await listWorkspaceMembers(deps.db, workspaceId);
1116
1604
  // Never remove yourself, and never remove the last administering member.
@@ -1125,11 +1613,31 @@ export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
1125
1613
  workspaceId,
1126
1614
  actorSubjectId: grant.subjectId,
1127
1615
  targetSubjectId: subjectId,
1616
+ // An organization owner/admin acting without their own workspace grant
1617
+ // proves that authority through the organization capability instead.
1618
+ ...(organizationAdministrator
1619
+ ? { requireOrganizationSharedWorkspaceAdministration: true }
1620
+ : {}),
1128
1621
  });
1129
1622
  return c.body(null, 204);
1130
1623
  });
1131
1624
  }
1132
1625
 
1626
+ async function workspaceModelPolicyResponse(
1627
+ deps: ApiRouteDeps,
1628
+ workspaceId: string,
1629
+ ): Promise<WorkspaceModelPolicyResponse> {
1630
+ const layers = await getWorkspaceModelPolicyLayers(deps.db, workspaceId);
1631
+ const effective = layers.workspace ?? layers.organization;
1632
+ return WorkspaceModelPolicyResponse.parse({
1633
+ allowedProviders: effective?.allowedProviders ?? null,
1634
+ allowedModels: effective?.allowedModels ?? null,
1635
+ source: layers.workspace ? "workspace" : layers.organization ? "organization" : "none",
1636
+ organization: layers.organization,
1637
+ allowCreditModels: layers.allowCreditModels,
1638
+ });
1639
+ }
1640
+
1133
1641
  function workspaceLimit(deps: ApiRouteDeps): number | null {
1134
1642
  if (deps.settings.usageLimitsMode !== "static" && deps.settings.usageLimitsMode !== "managed") {
1135
1643
  return null;