@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.
- package/dist/access-grant-rls.d.ts +3 -0
- package/dist/admission-trace.d.ts +8 -0
- package/dist/api-websocket.d.ts +18 -0
- package/dist/app.d.ts +50 -2
- package/dist/app.js +12 -1
- package/dist/artifact-catalog-cursor.d.ts +27 -0
- package/dist/auth/managed-auth-attempt-context.d.ts +10 -0
- package/dist/auth/managed-auth-rate-limits.d.ts +167 -0
- package/dist/auth/managed-auth-session-adapter.d.ts +6 -0
- package/dist/auth/managed-auth.d.ts +102 -3
- package/dist/auth/managed-email.d.ts +29 -0
- package/dist/auth/managed-sign-in-notifications.d.ts +4 -0
- package/dist/auth/new-signups-gate.d.ts +23 -0
- package/dist/auth/organization-user-setup.d.ts +80 -0
- package/dist/auth/signup-funnel-metrics.d.ts +44 -0
- package/dist/browser-controller-authority.d.ts +1 -61
- package/dist/browser-ephemeral.d.ts +5 -0
- package/dist/browser-state-authority.d.ts +1 -27
- package/dist/chunk-6TZ5LG3O.js +123948 -0
- package/dist/chunk-6TZ5LG3O.js.map +1 -0
- package/dist/chunk-OGQDXVXY.js +18 -0
- package/dist/chunk-OGQDXVXY.js.map +1 -0
- package/dist/claude-subscription-account-conflicts.d.ts +1 -0
- package/dist/claude-subscription-account-usage.d.ts +39 -0
- package/dist/claude-subscription-oauth.d.ts +25 -0
- package/dist/claude-subscription-profile.d.ts +9 -0
- package/dist/claude-subscription-usage.d.ts +44 -0
- package/dist/claude-workspace-connection.d.ts +15 -0
- package/dist/codemode.d.ts +32 -1
- package/dist/company-brain-okf.d.ts +24 -0
- package/dist/connected-machine-computer-access.d.ts +11 -0
- package/dist/connection-ownership.d.ts +54 -0
- package/dist/controller-data-plane.d.ts +32 -0
- package/dist/editable-artifact-office-import.d.ts +7 -4
- package/dist/editable-artifact-source-session.d.ts +3 -0
- package/dist/editable-artifact-websocket.d.ts +10 -18
- package/dist/editable-artifact-workspace-files.d.ts +1 -1
- package/dist/fatal-process-boundary.d.ts +25 -0
- package/dist/github-access.d.ts +31 -2
- package/dist/http/acting-person.d.ts +24 -0
- package/dist/http/api-contract-compat.d.ts +19 -0
- package/dist/http/api-error.d.ts +27 -1
- package/dist/http/cors.d.ts +17 -0
- package/dist/http/deprecation.d.ts +42 -0
- package/dist/http/interaction-control-error.d.ts +13 -0
- package/dist/http/keyed-admission.d.ts +12 -0
- package/dist/http/local-browser-boundary.d.ts +72 -0
- package/dist/http/metrics-listener.d.ts +22 -0
- package/dist/http/path-identifier.d.ts +17 -0
- package/dist/http/registered-route-label.d.ts +16 -0
- package/dist/http/request-body.d.ts +27 -0
- package/dist/http/request-source.d.ts +52 -0
- package/dist/http/sse.d.ts +17 -6
- package/dist/http/unmatched-route.d.ts +18 -0
- package/dist/http/user-content.d.ts +36 -0
- package/dist/http/websocket-upgrade-dispatch.d.ts +18 -0
- package/dist/index.d.ts +10 -3
- package/dist/index.js +1129 -166
- package/dist/index.js.map +1 -1
- package/dist/integration-endpoint-test.d.ts +20 -0
- package/dist/integrations/api-integrations.d.ts +2 -2
- package/dist/integrations/atlassian.d.ts +29 -3
- package/dist/integrations/connect-authority.d.ts +1 -0
- package/dist/integrations/connect-callback-return.d.ts +6 -0
- package/dist/integrations/fiken.d.ts +16 -2
- package/dist/integrations/github-app-connect.d.ts +47 -0
- package/dist/integrations/github-installation-proof.d.ts +3 -0
- package/dist/integrations/github-lens-connect.d.ts +116 -0
- package/dist/integrations/github-skill-source.d.ts +1 -5
- package/dist/integrations/google-drive.d.ts +25 -0
- package/dist/integrations/integration-commit-authority.d.ts +13 -0
- package/dist/integrations/marketplace-plugin.d.ts +2691 -0
- package/dist/integrations/oauth-client.d.ts +178 -18
- package/dist/integrations/oauth-profiles.d.ts +222 -0
- package/dist/integrations/oauth-return-path.d.ts +1 -0
- package/dist/integrations/personal-github-repositories.d.ts +84 -0
- package/dist/integrations/personal-github.d.ts +42 -0
- package/dist/integrations/pr-review-provider.d.ts +22 -0
- package/dist/integrations/provider-oauth.d.ts +19 -1
- package/dist/integrations/slack-app-home.d.ts +24 -0
- package/dist/integrations/slack-bot.d.ts +367 -12
- package/dist/integrations/slack-file-upload-flow.d.ts +62 -0
- package/dist/integrations/slack-install.d.ts +20 -0
- package/dist/integrations/slack-interactions.d.ts +113 -10
- package/dist/integrations/slack-mrkdwn.d.ts +41 -0
- package/dist/integrations/slack-routing.d.ts +120 -0
- package/dist/integrations/slack-session-defaults.d.ts +58 -0
- package/dist/integrations/slack-task-file-upload.d.ts +36 -0
- package/dist/integrations/social-oauth.d.ts +7 -0
- package/dist/interaction-agent-access.d.ts +15 -0
- package/dist/interaction-frame-proxy.d.ts +68 -0
- package/dist/interaction-metrics.d.ts +2 -0
- package/dist/mcp/action-catalog-types.d.ts +9 -0
- package/dist/mcp/action-catalog.gen.d.ts +2 -0
- package/dist/mcp/capability-account-readiness.d.ts +7 -0
- package/dist/mcp/child-read-evidence.d.ts +9 -0
- package/dist/mcp/command-read.d.ts +24 -0
- package/dist/mcp/company-profile-agent-admin.d.ts +44 -0
- package/dist/mcp/contract-input.d.ts +11 -0
- package/dist/mcp/documents.d.ts +7 -5
- package/dist/mcp/files.d.ts +2 -2
- package/dist/mcp/first-party-tool-permissions.d.ts +650 -0
- package/dist/mcp/knowledge-entries.d.ts +5 -0
- package/dist/mcp/orchestration-failure-diagnostic.d.ts +43 -0
- package/dist/mcp/receipts.d.ts +9 -0
- package/dist/mcp/request-abort.d.ts +19 -0
- package/dist/mcp/scheduled-task-input.d.ts +1189 -0
- package/dist/mcp/scheduled-task-view.d.ts +22 -4
- package/dist/mcp/server.d.ts +255 -3
- package/dist/mcp/session-event-view.d.ts +94 -0
- package/dist/mcp/session-view.d.ts +66 -2
- package/dist/mcp/session-wait.d.ts +174 -0
- package/dist/mcp-oauth.d.ts +52 -0
- package/dist/model-catalog.d.ts +11 -31
- package/dist/observability.d.ts +3 -1
- package/dist/organization-mcp.d.ts +52 -0
- package/dist/organization-recovery-notifications.d.ts +41 -0
- package/dist/routes/analytics-consent.d.ts +26 -0
- package/dist/routes/api-integrations.d.ts +25 -0
- package/dist/routes/api-keys.d.ts +17 -0
- package/dist/routes/artifact-catalog.d.ts +6 -0
- package/dist/routes/automations.d.ts +19 -0
- package/dist/routes/billing.d.ts +49 -0
- package/dist/routes/browser-sessions.d.ts +71 -3
- package/dist/routes/claude-subscription-accounts.d.ts +3 -0
- package/dist/routes/claude-subscription-oauth.d.ts +3 -0
- package/dist/routes/client-errors.d.ts +146 -0
- package/dist/routes/codex.d.ts +76 -2
- package/dist/routes/company-brain.d.ts +3 -0
- package/dist/routes/company-profile.d.ts +2 -1
- package/dist/routes/computer-sessions.d.ts +14 -0
- package/dist/routes/connect.d.ts +5 -0
- package/dist/routes/editable-artifacts.d.ts +2 -0
- package/dist/routes/external-identity-links.d.ts +6 -0
- package/dist/routes/feedback.d.ts +3 -0
- package/dist/routes/files.d.ts +4 -0
- package/dist/routes/github-browser-pages.d.ts +12 -0
- package/dist/routes/github.d.ts +2 -0
- package/dist/routes/insights-response-cache.d.ts +10 -0
- package/dist/routes/insights-usage.d.ts +12 -0
- package/dist/routes/insights.d.ts +30 -1
- package/dist/routes/knowledge.d.ts +3 -0
- package/dist/routes/managed-auth-session-sets.d.ts +26 -0
- package/dist/routes/managed-onboarding.d.ts +29 -0
- package/dist/routes/managed-sign-in-methods.d.ts +6 -0
- package/dist/routes/model-connection-access.d.ts +3 -0
- package/dist/routes/organization-integration-policy.d.ts +3 -0
- package/dist/routes/organization-integrations.d.ts +6 -0
- package/dist/routes/organization-mcp-connections.d.ts +3 -0
- package/dist/routes/organization-model-providers.d.ts +3 -0
- package/dist/routes/organization-recovery.d.ts +19 -0
- package/dist/routes/organization-service-accounts.d.ts +3 -0
- package/dist/routes/organization-sessions.d.ts +26 -0
- package/dist/routes/personal-github-git-broker.d.ts +29 -0
- package/dist/routes/personal-github.d.ts +3 -0
- package/dist/routes/pr-review-github.d.ts +3 -0
- package/dist/routes/pr-review.d.ts +3 -0
- package/dist/routes/session-artifact-associations.d.ts +4 -0
- package/dist/routes/session-history-imports.d.ts +9 -0
- package/dist/routes/sessions.d.ts +44 -6
- package/dist/routes/skill-content.d.ts +4 -0
- package/dist/routes/skill-install-authority.d.ts +6 -0
- package/dist/routes/subscription-account-pools.d.ts +34 -0
- package/dist/routes/subscription-pool-access.d.ts +16 -0
- package/dist/routes/supergrok.d.ts +6 -1
- package/dist/routes/usage-allowances.d.ts +12 -0
- package/dist/routes/user-resource-authorities.d.ts +3 -0
- package/dist/routes/workspace-artifacts.d.ts +2 -1
- package/dist/routes/workspace-capture.d.ts +10 -2
- package/dist/routes/workspace-integrations.d.ts +35 -0
- package/dist/routes/workspace-learning.d.ts +3 -0
- package/dist/routes/workspace-model-providers.d.ts +4 -0
- package/dist/routes/workspaces.d.ts +29 -0
- package/dist/sandbox/auth-callout.d.ts +8 -0
- package/dist/sandbox/channel-a.d.ts +19 -1
- package/dist/sandbox/connection-authority.d.ts +3 -0
- package/dist/sandbox/enrollment.d.ts +9 -1
- package/dist/sandbox/machines.d.ts +5 -2
- package/dist/sandbox/metrics-ingestion.d.ts +28 -20
- package/dist/sandbox/rematerialize.d.ts +2 -0
- package/dist/sandbox/session-attach-variable-sets.d.ts +30 -0
- package/dist/sandbox/viewer.d.ts +26 -1
- package/dist/sandbox-file-artifacts.d.ts +50 -0
- package/dist/scheduled-task-deletion.d.ts +13 -0
- package/dist/session-create-observability.d.ts +5 -0
- package/dist/session-event-view-MPS2OZD3.js +292 -0
- package/dist/session-event-view-MPS2OZD3.js.map +1 -0
- package/dist/site-session-origin.d.ts +13 -0
- package/dist/site-uploads.d.ts +41 -0
- package/dist/slack-reaction-files.d.ts +1 -1
- package/dist/temporal-schedule-cleanup.d.ts +1 -0
- package/dist/temporal-schedule-sync.d.ts +26 -0
- package/dist/transcription/providers/xai-subscription.d.ts +1 -0
- package/dist/transcription/service.d.ts +2 -1
- package/dist/work-discovery-observability.d.ts +33 -0
- package/dist/workspace-artifact-content.d.ts +28 -0
- package/dist/workspace-artifact-provenance.d.ts +7 -0
- package/dist/workspace-delete-observability.d.ts +10 -0
- package/dist/workspace-deletion.d.ts +6 -0
- package/dist/workspace-state-projection.d.ts +3 -5
- package/dist/workspace-tool-gateway-observability.d.ts +17 -0
- package/dist/workspace-tool-gateway.d.ts +123 -0
- package/dist/workspace-webhook-dispatch.d.ts +26 -0
- package/package.json +23 -19
- package/src/access-grant-rls.ts +75 -0
- package/src/admission-trace.ts +14 -0
- package/src/api-websocket.ts +23 -0
- package/src/app.ts +1974 -256
- package/src/artifact-catalog-cursor.ts +60 -0
- package/src/auth/managed-auth-attempt-context.ts +70 -0
- package/src/auth/managed-auth-rate-limits.ts +252 -0
- package/src/auth/managed-auth-session-adapter.ts +222 -0
- package/src/auth/managed-auth.ts +536 -60
- package/src/auth/managed-email.ts +174 -0
- package/src/auth/managed-sign-in-notifications.ts +81 -0
- package/src/auth/new-signups-gate.ts +49 -0
- package/src/auth/organization-user-setup.ts +307 -0
- package/src/auth/signup-funnel-metrics.ts +164 -0
- package/src/browser-controller-authority.ts +1 -186
- package/src/browser-ephemeral.ts +39 -0
- package/src/browser-state-authority.ts +1 -236
- package/src/claude-subscription-account-conflicts.ts +12 -0
- package/src/claude-subscription-account-usage.ts +48 -0
- package/src/claude-subscription-oauth.ts +362 -0
- package/src/claude-subscription-profile.ts +73 -0
- package/src/claude-subscription-usage.ts +97 -0
- package/src/claude-workspace-connection.ts +82 -0
- package/src/codemode.ts +194 -30
- package/src/codex-realtime.ts +8 -2
- package/src/company-brain-okf.ts +340 -0
- package/src/connected-machine-computer-access.ts +33 -0
- package/src/connection-ownership.ts +199 -0
- package/src/controller-data-plane.ts +81 -0
- package/src/editable-artifact-native-kernel.ts +26 -15
- package/src/editable-artifact-office-import.ts +77 -9
- package/src/editable-artifact-production.ts +23 -7
- package/src/editable-artifact-source-session.ts +123 -0
- package/src/editable-artifact-websocket.ts +27 -21
- package/src/editable-artifact-workspace-files.ts +31 -6
- package/src/fatal-process-boundary.ts +246 -0
- package/src/gateway-realtime.ts +1 -1
- package/src/github-access.ts +147 -5
- package/src/http/acting-person.ts +71 -0
- package/src/http/api-contract-compat.ts +49 -0
- package/src/http/api-error.ts +115 -1
- package/src/http/auth.ts +48 -0
- package/src/http/cors.ts +67 -0
- package/src/http/deprecation.ts +118 -0
- package/src/http/interaction-control-error.ts +164 -0
- package/src/http/keyed-admission.ts +29 -0
- package/src/http/local-browser-boundary.ts +343 -0
- package/src/http/metrics-listener.ts +57 -0
- package/src/http/path-identifier.ts +54 -0
- package/src/http/registered-route-label.ts +34 -0
- package/src/http/request-body.ts +127 -0
- package/src/http/request-source.ts +178 -0
- package/src/http/sse.ts +486 -132
- package/src/http/unmatched-route.ts +60 -0
- package/src/http/user-content.ts +158 -0
- package/src/http/websocket-upgrade-dispatch.ts +29 -0
- package/src/index.ts +157 -52
- package/src/integration-endpoint-test.ts +291 -0
- package/src/integrations/api-integrations.ts +98 -11
- package/src/integrations/atlassian.ts +474 -136
- package/src/integrations/connect-authority.ts +2 -0
- package/src/integrations/connect-callback-return.ts +86 -0
- package/src/integrations/fiken.ts +354 -64
- package/src/integrations/github-app-connect.ts +435 -0
- package/src/integrations/github-installation-proof.ts +60 -0
- package/src/integrations/github-lens-connect.ts +76 -0
- package/src/integrations/github-skill-source.ts +2 -142
- package/src/integrations/google-drive.ts +820 -222
- package/src/integrations/integration-commit-authority.ts +69 -0
- package/src/integrations/marketplace-plugin.ts +48 -0
- package/src/integrations/oauth-client.ts +1356 -459
- package/src/integrations/oauth-profiles.ts +537 -0
- package/src/integrations/oauth-return-path.ts +18 -0
- package/src/integrations/personal-github-repositories.ts +674 -0
- package/src/integrations/personal-github.ts +825 -0
- package/src/integrations/pr-review-provider.ts +246 -0
- package/src/integrations/provider-oauth.ts +250 -22
- package/src/integrations/slack-app-home.ts +300 -0
- package/src/integrations/slack-bot.ts +963 -239
- package/src/integrations/slack-file-upload-flow.ts +147 -0
- package/src/integrations/slack-install.ts +83 -0
- package/src/integrations/slack-interactions.ts +3170 -474
- package/src/integrations/slack-mrkdwn.ts +193 -0
- package/src/integrations/slack-routing.ts +337 -0
- package/src/integrations/slack-session-defaults.ts +224 -0
- package/src/integrations/slack-task-file-upload.ts +323 -0
- package/src/integrations/social-oauth.ts +213 -15
- package/src/interaction-agent-access.ts +48 -0
- package/src/interaction-frame-proxy.ts +409 -0
- package/src/interaction-metrics.ts +30 -0
- package/src/mcp/action-catalog-types.ts +9 -0
- package/src/mcp/action-catalog.gen.ts +6696 -0
- package/src/mcp/capability-account-readiness.ts +17 -0
- package/src/mcp/child-read-evidence.ts +19 -0
- package/src/mcp/command-read.ts +109 -0
- package/src/mcp/company-profile-agent-admin.ts +198 -0
- package/src/mcp/contract-input.ts +141 -0
- package/src/mcp/documents.ts +65 -377
- package/src/mcp/editable-artifacts.ts +12 -6
- package/src/mcp/files.ts +80 -38
- package/src/mcp/first-party-tool-permissions.ts +305 -0
- package/src/mcp/knowledge-entries.ts +322 -0
- package/src/mcp/orchestration-failure-diagnostic.ts +43 -0
- package/src/mcp/receipts.ts +34 -0
- package/src/mcp/request-abort.ts +44 -0
- package/src/mcp/scheduled-task-input.ts +76 -0
- package/src/mcp/scheduled-task-view.ts +44 -3
- package/src/mcp/server.ts +3030 -1267
- package/src/mcp/session-event-view.ts +383 -0
- package/src/mcp/session-view.ts +84 -3
- package/src/mcp/session-wait.ts +665 -0
- package/src/mcp-oauth.ts +1123 -0
- package/src/model-catalog.ts +91 -333
- package/src/observability.ts +15 -1
- package/src/organization-mcp.ts +397 -0
- package/src/organization-recovery-notifications.ts +103 -0
- package/src/routes/analytics-consent.ts +123 -0
- package/src/routes/api-integrations.ts +364 -74
- package/src/routes/api-keys.ts +336 -10
- package/src/routes/artifact-catalog.ts +279 -0
- package/src/routes/automations.ts +768 -0
- package/src/routes/billing.ts +461 -46
- package/src/routes/browser-sessions.ts +839 -131
- package/src/routes/canonical-human-identities.ts +90 -14
- package/src/routes/capabilities.ts +107 -6
- package/src/routes/channels.ts +20 -3
- package/src/routes/claude-subscription-accounts.ts +334 -0
- package/src/routes/claude-subscription-oauth.ts +142 -0
- package/src/routes/client-errors.ts +362 -0
- package/src/routes/codex.ts +833 -116
- package/src/routes/company-brain.ts +322 -0
- package/src/routes/company-profile.ts +65 -1
- package/src/routes/computer-sessions.ts +557 -106
- package/src/routes/connect.ts +1938 -0
- package/src/routes/connections.ts +1040 -349
- package/src/routes/documents.ts +488 -287
- package/src/routes/editable-artifacts.ts +79 -9
- package/src/routes/enrollments.ts +135 -39
- package/src/routes/environments.ts +154 -58
- package/src/routes/external-identity-links.ts +201 -0
- package/src/routes/feedback.ts +113 -0
- package/src/routes/files.ts +337 -39
- package/src/routes/github-browser-pages.ts +162 -0
- package/src/routes/github.ts +446 -144
- package/src/routes/insights-response-cache.ts +83 -0
- package/src/routes/insights-usage.ts +345 -0
- package/src/routes/insights.ts +153 -19
- package/src/routes/install.ts +133 -38
- package/src/routes/integration-facets.ts +107 -33
- package/src/routes/interaction-resources.ts +30 -3
- package/src/routes/knowledge.ts +488 -0
- package/src/routes/machines.ts +313 -15
- package/src/routes/managed-auth-session-sets.ts +1218 -0
- package/src/routes/managed-onboarding.ts +337 -0
- package/src/routes/managed-sign-in-methods.ts +433 -0
- package/src/routes/memory-slack-publications.ts +3 -2
- package/src/routes/model-connection-access.ts +208 -0
- package/src/routes/organization-integration-policy.ts +116 -0
- package/src/routes/organization-integrations.ts +326 -0
- package/src/routes/organization-mcp-connections.ts +178 -0
- package/src/routes/organization-memberships.ts +1019 -27
- package/src/routes/organization-model-providers.ts +293 -0
- package/src/routes/organization-recovery.ts +325 -0
- package/src/routes/organization-service-accounts.ts +128 -0
- package/src/routes/organization-sessions.ts +350 -0
- package/src/routes/personal-github-git-broker.ts +785 -0
- package/src/routes/personal-github.ts +370 -0
- package/src/routes/plugins.ts +251 -22
- package/src/routes/pr-review-github.ts +881 -0
- package/src/routes/pr-review.ts +622 -0
- package/src/routes/preference-registry.ts +12 -93
- package/src/routes/rigs.ts +167 -50
- package/src/routes/scheduled-tasks.ts +346 -41
- package/src/routes/session-artifact-associations.ts +87 -0
- package/src/routes/session-history-imports.ts +280 -0
- package/src/routes/sessions.ts +2306 -264
- package/src/routes/skill-content.ts +297 -0
- package/src/routes/skill-install-authority.ts +64 -0
- package/src/routes/skills.ts +62 -9
- package/src/routes/social.ts +46 -18
- package/src/routes/subscription-account-pools.ts +436 -0
- package/src/routes/subscription-pool-access.ts +126 -0
- package/src/routes/supergrok.ts +219 -303
- package/src/routes/transcription-recordings.ts +38 -3
- package/src/routes/transcriptions.ts +4 -0
- package/src/routes/usage-allowances.ts +378 -0
- package/src/routes/user-resource-authorities.ts +138 -0
- package/src/routes/video-generation.ts +14 -10
- package/src/routes/workspace-artifacts.ts +275 -69
- package/src/routes/workspace-capture.ts +21 -0
- package/src/routes/workspace-integrations.ts +576 -0
- package/src/routes/workspace-learning.ts +159 -0
- package/src/routes/workspace-model-providers.ts +148 -0
- package/src/routes/workspace-state.ts +38 -25
- package/src/routes/workspaces.ts +1004 -127
- package/src/sandbox/auth-callout.ts +96 -19
- package/src/sandbox/channel-a.ts +323 -24
- package/src/sandbox/connection-authority.ts +3 -0
- package/src/sandbox/enrollment.ts +81 -6
- package/src/sandbox/machines.ts +218 -76
- package/src/sandbox/metrics-ingestion.ts +411 -62
- package/src/sandbox/rematerialize.ts +121 -19
- package/src/sandbox/session-attach-variable-sets.ts +131 -0
- package/src/sandbox/viewer.ts +280 -25
- package/src/sandbox-file-artifacts.ts +413 -0
- package/src/scheduled-task-deletion.ts +138 -0
- package/src/session-create-observability.ts +46 -0
- package/src/session-realtime-context.ts +6 -3
- package/src/site-session-origin.ts +48 -0
- package/src/site-uploads.ts +224 -0
- package/src/slack-reaction-files.ts +16 -5
- package/src/temporal-schedule-cleanup.ts +13 -0
- package/src/temporal-schedule-sync.ts +74 -0
- package/src/transcription/providers/codex-subscription.ts +2 -0
- package/src/transcription/providers/openai.ts +1 -0
- package/src/transcription/providers/xai-subscription.ts +49 -3
- package/src/transcription/segmenter.ts +8 -2
- package/src/transcription/service.ts +43 -6
- package/src/work-discovery-observability.ts +121 -0
- package/src/workspace-artifact-content.ts +144 -0
- package/src/workspace-artifact-provenance.ts +104 -0
- package/src/workspace-delete-observability.ts +75 -0
- package/src/workspace-deletion.ts +73 -0
- package/src/workspace-state-projection.ts +18 -148
- package/src/workspace-tool-gateway-observability.ts +100 -0
- package/src/workspace-tool-gateway.ts +870 -0
- package/src/workspace-webhook-dispatch.ts +190 -0
- package/src/xai-subscription-auth.ts +9 -2
- package/dist/chunk-2PQMSRCB.js +0 -49759
- package/dist/chunk-2PQMSRCB.js.map +0 -1
- package/dist/routes/packs.d.ts +0 -3
- package/src/routes/packs.ts +0 -729
package/src/app.ts
CHANGED
|
@@ -1,71 +1,200 @@
|
|
|
1
|
+
import { registerClaudeSubscriptionAccountRoutes } from "./routes/claude-subscription-accounts";
|
|
2
|
+
import { registerWorkspaceModelProviderRoutes } from "./routes/workspace-model-providers";
|
|
3
|
+
import { registerClaudeSubscriptionOAuthRoutes } from "./routes/claude-subscription-oauth";
|
|
4
|
+
import { registerConnectCallbackReturns } from "./integrations/connect-callback-return";
|
|
5
|
+
import { registerFeedbackRoutes } from "./routes/feedback";
|
|
6
|
+
import { registerWorkspaceIntegrationRoutes } from "./routes/workspace-integrations";
|
|
7
|
+
import { registerOrganizationIntegrationRoutes } from "./routes/organization-integrations";
|
|
1
8
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
9
|
+
CLIENT_ERRORS_PATH,
|
|
10
|
+
isClientErrorReportRequest,
|
|
11
|
+
registerClientErrorRoutes,
|
|
12
|
+
} from "./routes/client-errors";
|
|
13
|
+
import {
|
|
14
|
+
ANALYTICS_CONSENT_PATH,
|
|
15
|
+
isAnalyticsConsentReportRequest,
|
|
16
|
+
registerAnalyticsConsentRoutes,
|
|
17
|
+
} from "./routes/analytics-consent";
|
|
18
|
+
import { codemodeSessionRequest } from "./codemode";
|
|
19
|
+
import { SiteSessionPathError, OrganizationIntegrationDeniedError } from "@opengeni/contracts";
|
|
20
|
+
import { registerModelConnectionAccessRoutes } from "./routes/model-connection-access";
|
|
21
|
+
import {
|
|
22
|
+
codeSearchDeploymentPolicy,
|
|
23
|
+
agentConfigDeploymentPolicy,
|
|
4
24
|
configuredAllowedReasoningEfforts,
|
|
5
|
-
configuredModels,
|
|
6
25
|
resolveFirstPartyMcpToolPolicy,
|
|
7
|
-
|
|
8
|
-
|
|
26
|
+
resolveVoiceInputProviderRegistry,
|
|
27
|
+
UnsupportedLatencyModeError,
|
|
28
|
+
type Settings,
|
|
9
29
|
} from "@opengeni/config";
|
|
10
30
|
import {
|
|
31
|
+
AGENT_CAPABILITY_IDS,
|
|
11
32
|
ClientConfig,
|
|
12
33
|
CodemodeCallRequest,
|
|
13
34
|
ErrorEnvelope,
|
|
14
35
|
OPENGENI_API_CONTRACT_HEADER,
|
|
15
36
|
OPENGENI_API_CONTRACT_REVISION,
|
|
16
37
|
OPENGENI_CORRELATION_HEADER,
|
|
38
|
+
resolveAgentToolFamilies,
|
|
17
39
|
resolveWorkspaceMemoryEnabled,
|
|
18
40
|
resolveWorkspaceMemoryPromptMode,
|
|
19
41
|
VOICE_INPUT_ACCEPTED_MIME_TYPES,
|
|
20
42
|
TRANSCRIPTION_RECORDING_PROVIDER_SEGMENT_SECONDS,
|
|
43
|
+
ToolGatewayApprovalRequest,
|
|
44
|
+
ToolGatewayCallRequest,
|
|
21
45
|
type AccessGrant,
|
|
46
|
+
type ClientAgentConfig,
|
|
22
47
|
type ErrorCode,
|
|
48
|
+
type FirstPartyMcpToolName,
|
|
49
|
+
type Session,
|
|
23
50
|
} from "@opengeni/contracts";
|
|
24
51
|
import {
|
|
25
52
|
createDocumentServices,
|
|
26
|
-
|
|
53
|
+
getDocumentForIndexing,
|
|
27
54
|
indexDocumentNow,
|
|
28
55
|
type DocumentServices,
|
|
29
56
|
} from "@opengeni/documents";
|
|
30
57
|
import {
|
|
31
|
-
CodemodeCallBudgetExceededError,
|
|
32
58
|
CodemodeOperationConflictError,
|
|
33
59
|
CodemodeOperationNotExecutableError,
|
|
34
60
|
CodemodePayloadTooLargeError,
|
|
35
61
|
CodemodeToolApprovalRequiredError,
|
|
36
62
|
CodemodeToolNotInCatalogError,
|
|
63
|
+
ConnectAttemptConflictError,
|
|
64
|
+
ConnectAttemptNotFoundError,
|
|
65
|
+
configureChildLifecycleNotices,
|
|
66
|
+
configureCodeSearchDeploymentPolicy,
|
|
67
|
+
configureWorkspaceControlRequestLockTimeoutMs,
|
|
37
68
|
dbSql,
|
|
69
|
+
getManagedAuthSessionSetSnapshot,
|
|
38
70
|
getWorkspace,
|
|
71
|
+
reapManagedAuthIsolatedSessions,
|
|
72
|
+
reapExpiredManagedAuthSessionSets,
|
|
73
|
+
resolveSessionMemoryAgentScope,
|
|
39
74
|
rlsContextForWorkspace,
|
|
40
|
-
withSessionRlsActorContext,
|
|
41
75
|
} from "@opengeni/db";
|
|
42
|
-
import {
|
|
76
|
+
import { requireSessionEventDurableFanoutCapability } from "@opengeni/events";
|
|
77
|
+
import { githubAppBotIdentityWarnings } from "@opengeni/github";
|
|
78
|
+
import {
|
|
79
|
+
createObservability,
|
|
80
|
+
failureDiagnostic,
|
|
81
|
+
withTraceContext,
|
|
82
|
+
withMcpTelemetry,
|
|
83
|
+
} from "@opengeni/observability";
|
|
43
84
|
import { createObjectStorage } from "@opengeni/storage";
|
|
44
85
|
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
45
|
-
import {
|
|
86
|
+
import { SUPPORTED_PROTOCOL_VERSIONS } from "@modelcontextprotocol/sdk/types.js";
|
|
87
|
+
import { handleMcpRequestWithClientAbort } from "./mcp/request-abort";
|
|
88
|
+
import { Hono, type Context } from "hono";
|
|
46
89
|
import { bodyLimit } from "hono/body-limit";
|
|
47
90
|
import { compress } from "hono/compress";
|
|
48
91
|
import { cors } from "hono/cors";
|
|
92
|
+
import { getCookie } from "hono/cookie";
|
|
49
93
|
import { HTTPException } from "hono/http-exception";
|
|
50
|
-
import {
|
|
94
|
+
import {
|
|
95
|
+
ApiHttpError,
|
|
96
|
+
agentConfigHttpError,
|
|
97
|
+
allowanceExhaustedHttpError,
|
|
98
|
+
modelUnavailableHttpError,
|
|
99
|
+
scheduledTaskTargetAccessHttpError,
|
|
100
|
+
workspaceControlBusyHttpError,
|
|
101
|
+
} from "./http/api-error";
|
|
102
|
+
import {
|
|
103
|
+
isRequestBodyValidationError,
|
|
104
|
+
requestBodyValidationHttpError,
|
|
105
|
+
tagRequestJsonParseErrors,
|
|
106
|
+
} from "./http/request-body";
|
|
107
|
+
import { invalidPathIdentifierHttpError } from "./http/path-identifier";
|
|
108
|
+
import { replaceTrustedClientAddressHeader } from "./http/request-source";
|
|
109
|
+
import { unmatchedRoute } from "./http/unmatched-route";
|
|
51
110
|
import type { ContentfulStatusCode } from "hono/utils/http-status";
|
|
111
|
+
import { bearerApiContractHeaderCompatibility } from "./http/api-contract-compat";
|
|
112
|
+
import { deprecationHeadersMiddleware } from "./http/deprecation";
|
|
113
|
+
import {
|
|
114
|
+
boundedRegisteredRouteLabel,
|
|
115
|
+
registeredHandlerRoutePath,
|
|
116
|
+
} from "./http/registered-route-label";
|
|
52
117
|
import type { ApiRouteDeps, AppDependencies } from "@opengeni/core";
|
|
53
118
|
import {
|
|
54
119
|
CodexCompactionV2ProviderLockedError,
|
|
120
|
+
ManagedAuthActorLeaseOutcomeUnknownError,
|
|
55
121
|
hasPermission,
|
|
56
122
|
requireAccessGrant,
|
|
57
|
-
|
|
123
|
+
requireAccessGrantAuthorization,
|
|
124
|
+
accountScopedApiKeyWorkspaceAuthority,
|
|
125
|
+
requireAccessContext,
|
|
58
126
|
requirePermission,
|
|
127
|
+
releaseManagedAuthRequestActorLease,
|
|
128
|
+
resolveCatalogSettings,
|
|
129
|
+
resolveWorkspaceCatalogSettings,
|
|
130
|
+
resolveCallerWorkspaceModelSelections,
|
|
131
|
+
resolveDefaultSessionModelForSelections,
|
|
132
|
+
selectDefaultSessionModel,
|
|
133
|
+
isWorkspaceModelAdmissible,
|
|
134
|
+
resolveWorkspaceModelSelection,
|
|
135
|
+
validateManagedAuthRequestActorLease,
|
|
59
136
|
requireSessionAuthorization,
|
|
60
137
|
SessionAuthorizationDeniedError,
|
|
138
|
+
withSessionAuthorizationReadReuse,
|
|
61
139
|
SessionAuthorizationUnavailableError,
|
|
140
|
+
createUserPresenceRecorder,
|
|
141
|
+
registerProductUsageMetricBaselines,
|
|
62
142
|
} from "@opengeni/core";
|
|
63
|
-
import {
|
|
143
|
+
import { createManagedAuthNewSignupsGate } from "./auth/new-signups-gate";
|
|
144
|
+
import {
|
|
145
|
+
createManagedAuth,
|
|
146
|
+
isolatedManagedAuthOAuthCallbackRequest,
|
|
147
|
+
managedAuthNewSignupsPausedResponse,
|
|
148
|
+
managedAuthOAuthReturnMatches,
|
|
149
|
+
resolveManagedAuthOAuthAttempt,
|
|
150
|
+
} from "./auth/managed-auth";
|
|
151
|
+
import {
|
|
152
|
+
adoptManagedAuthSession,
|
|
153
|
+
MANAGED_AUTH_SESSION_SET_COOKIE,
|
|
154
|
+
ManagedAuthActorChangeError,
|
|
155
|
+
managedAuthCsrfHash,
|
|
156
|
+
managedAuthDerivedUuid,
|
|
157
|
+
managedAuthSecretRequestDigest,
|
|
158
|
+
managedAuthSha256,
|
|
159
|
+
} from "@opengeni/core/managed-auth-session-sets";
|
|
160
|
+
import { createBetterAuthSessionAdapter } from "./auth/managed-auth-session-adapter";
|
|
161
|
+
import {
|
|
162
|
+
currentManagedAuthCreatedSessionId,
|
|
163
|
+
runManagedAuthAttempt,
|
|
164
|
+
runManagedAuthDiscardedProviderSession,
|
|
165
|
+
runManagedAuthProvider,
|
|
166
|
+
} from "./auth/managed-auth-attempt-context";
|
|
167
|
+
import { createManagedEmailTransport } from "./auth/managed-email";
|
|
168
|
+
import { startManagedSignInNotificationDelivery } from "./auth/managed-sign-in-notifications";
|
|
169
|
+
import {
|
|
170
|
+
assertManagedEmailTransportMetadata,
|
|
171
|
+
assertOrganizationUserSetupQueryTransportConfigured,
|
|
172
|
+
} from "./auth/organization-user-setup";
|
|
64
173
|
import { createApiSandboxClient, makeResumeBoxById } from "./sandbox/access";
|
|
65
|
-
import { requireLimit } from "@opengeni/core";
|
|
66
174
|
import { buildOpenGeniMcpServer } from "./mcp/server";
|
|
175
|
+
import {
|
|
176
|
+
buildWorkspaceToolGatewayMcpServer,
|
|
177
|
+
approveWorkspaceToolGatewayCall,
|
|
178
|
+
callWorkspaceToolGateway,
|
|
179
|
+
grantUsesAttemptScopedMcp,
|
|
180
|
+
prepareMcpOAuthWorkspaceToolGateway,
|
|
181
|
+
prepareWorkspaceToolGateway,
|
|
182
|
+
workspaceToolGatewayDeclarations,
|
|
183
|
+
} from "./workspace-tool-gateway";
|
|
184
|
+
import {
|
|
185
|
+
isMcpOAuthResourcePath,
|
|
186
|
+
mcpOAuthAuthenticateHeader,
|
|
187
|
+
mcpOAuthBearerToken,
|
|
188
|
+
ORGANIZATION_MCP_PATH,
|
|
189
|
+
registerMcpOAuthRoutes,
|
|
190
|
+
resolveMcpOAuthRouteAccess,
|
|
191
|
+
resolveOrganizationMcpOAuthAccess,
|
|
192
|
+
} from "./mcp-oauth";
|
|
193
|
+
import { buildOrganizationMcpServer, type OrganizationMcpCaller } from "./organization-mcp";
|
|
67
194
|
import {
|
|
68
195
|
CodemodeAuthorityError,
|
|
196
|
+
CodemodeCatalogNotReadyError,
|
|
197
|
+
CodemodeCatalogStaleError,
|
|
69
198
|
isCodemodeGrant,
|
|
70
199
|
readCodemodeOperation,
|
|
71
200
|
requireActiveCodemodeCatalog,
|
|
@@ -77,49 +206,97 @@ import {
|
|
|
77
206
|
BrowserControlTransportError,
|
|
78
207
|
} from "@opengeni/runtime/sandbox";
|
|
79
208
|
import { requireAccessKey } from "./http/auth";
|
|
209
|
+
import {
|
|
210
|
+
publicListenerServesMetrics,
|
|
211
|
+
registerPrometheusMetricsRoute,
|
|
212
|
+
} from "./http/metrics-listener";
|
|
80
213
|
import { allowedCorsOrigin } from "./http/cors";
|
|
214
|
+
import {
|
|
215
|
+
createLocalBrowserBoundary,
|
|
216
|
+
localBrowserRequestHost,
|
|
217
|
+
markLocalInternalDispatch,
|
|
218
|
+
} from "./http/local-browser-boundary";
|
|
219
|
+
import { withAccessGrantSessionRlsContext } from "./access-grant-rls";
|
|
81
220
|
import { registerCapabilityRoutes } from "./routes/capabilities";
|
|
82
221
|
import { registerCatalogAssetRoutes } from "./routes/catalog-assets";
|
|
83
222
|
import { registerCodexRoutes } from "./routes/codex";
|
|
223
|
+
import { registerOrganizationModelProviderRoutes } from "./routes/organization-model-providers";
|
|
224
|
+
import { registerOrganizationIntegrationPolicyRoutes } from "./routes/organization-integration-policy";
|
|
84
225
|
import { registerSuperGrokRoutes } from "./routes/supergrok";
|
|
85
226
|
import { registerConnectionRoutes } from "./routes/connections";
|
|
227
|
+
import { registerConnectRoutes } from "./routes/connect";
|
|
228
|
+
|
|
229
|
+
import { registerExternalIdentityLinkRoutes } from "./routes/external-identity-links";
|
|
86
230
|
import { registerDocumentRoutes } from "./routes/documents";
|
|
231
|
+
import { registerKnowledgeRoutes } from "./routes/knowledge";
|
|
87
232
|
import { registerEnrollmentRoutes } from "./routes/enrollments";
|
|
88
233
|
import { registerMachineRoutes } from "./routes/machines";
|
|
89
234
|
import { registerMemorySlackPublicationRoutes } from "./routes/memory-slack-publications";
|
|
90
235
|
import { registerEnvironmentRoutes } from "./routes/environments";
|
|
91
236
|
import { registerFileRoutes } from "./routes/files";
|
|
92
237
|
import { registerApiKeyRoutes } from "./routes/api-keys";
|
|
238
|
+
import { registerOrganizationMcpConnectionRoutes } from "./routes/organization-mcp-connections";
|
|
239
|
+
import { registerOrganizationServiceAccountRoutes } from "./routes/organization-service-accounts";
|
|
93
240
|
import { registerBillingRoutes } from "./routes/billing";
|
|
94
241
|
import { registerBrowserIdentityRoutes } from "./routes/browser-identities";
|
|
95
242
|
import { registerBrowserSessionRoutes } from "./routes/browser-sessions";
|
|
96
243
|
import { registerComputerSessionRoutes } from "./routes/computer-sessions";
|
|
97
244
|
import { registerGitHubRoutes } from "./routes/github";
|
|
245
|
+
import { registerPersonalGitHubRoutes } from "./routes/personal-github";
|
|
246
|
+
import {
|
|
247
|
+
isPersonalGitHubGitBrokerRequest,
|
|
248
|
+
registerPersonalGitHubGitBrokerRoutes,
|
|
249
|
+
} from "./routes/personal-github-git-broker";
|
|
98
250
|
import { registerInstallRoutes } from "./routes/install";
|
|
99
251
|
import { registerApiIntegrationRoutes } from "./routes/api-integrations";
|
|
100
252
|
import { registerIntegrationFacetRoutes } from "./routes/integration-facets";
|
|
101
253
|
import { registerInteractionResourceRoutes } from "./routes/interaction-resources";
|
|
102
|
-
import {
|
|
254
|
+
import { registerPrReviewRoutes } from "./routes/pr-review";
|
|
255
|
+
import { registerAutomationRoutes } from "./routes/automations";
|
|
103
256
|
import { registerPluginRoutes } from "./routes/plugins";
|
|
104
257
|
import { registerSkillRoutes } from "./routes/skills";
|
|
105
258
|
import { registerChannelRoutes } from "./routes/channels";
|
|
106
259
|
import { registerRigRoutes } from "./routes/rigs";
|
|
107
260
|
import { registerScheduledTaskRoutes } from "./routes/scheduled-tasks";
|
|
108
261
|
import { registerSessionRoutes } from "./routes/sessions";
|
|
262
|
+
import {
|
|
263
|
+
archivedSessionImportErrorResponse,
|
|
264
|
+
isSessionHistoryImportRequest,
|
|
265
|
+
registerSessionHistoryImportRoutes,
|
|
266
|
+
} from "./routes/session-history-imports";
|
|
109
267
|
import { registerSocialRoutes } from "./routes/social";
|
|
110
268
|
import { registerWorkspaceRoutes } from "./routes/workspaces";
|
|
269
|
+
import { registerUsageAllowanceRoutes } from "./routes/usage-allowances";
|
|
111
270
|
import { registerWorkspaceInstructionPolicyRoutes } from "./routes/workspace-instruction-policies";
|
|
271
|
+
import { registerWorkspaceLearningRoutes } from "./routes/workspace-learning";
|
|
112
272
|
import { registerCompanyProfileRoutes } from "./routes/company-profile";
|
|
273
|
+
import { registerCompanyBrainRoutes } from "./routes/company-brain";
|
|
113
274
|
import { registerSlackTaskPolicyRoutes } from "./routes/slack-task-policy";
|
|
114
275
|
import { registerWorkspaceStateRoutes } from "./routes/workspace-state";
|
|
115
276
|
import { registerWorkspaceArtifactRoutes } from "./routes/workspace-artifacts";
|
|
277
|
+
import { registerArtifactCatalogRoutes } from "./routes/artifact-catalog";
|
|
116
278
|
import { registerPreferenceRegistryRoutes } from "./routes/preference-registry";
|
|
117
279
|
import { registerInsightsRoutes } from "./routes/insights";
|
|
280
|
+
import { registerInsightsUsageRoutes } from "./routes/insights-usage";
|
|
118
281
|
import { registerTranscriptionRoutes } from "./routes/transcriptions";
|
|
119
282
|
import { registerEditableArtifactRoutes } from "./routes/editable-artifacts";
|
|
283
|
+
import { registerSessionArtifactAssociationRoutes } from "./routes/session-artifact-associations";
|
|
120
284
|
import { registerVideoGenerationRoutes } from "./routes/video-generation";
|
|
121
285
|
import { registerCanonicalHumanIdentityRoutes } from "./routes/canonical-human-identities";
|
|
122
286
|
import { registerOrganizationMembershipRoutes } from "./routes/organization-memberships";
|
|
287
|
+
import { registerOrganizationSessionRoutes } from "./routes/organization-sessions";
|
|
288
|
+
import { registerOrganizationRecoveryRoutes } from "./routes/organization-recovery";
|
|
289
|
+
import { registerManagedOnboardingRoutes } from "./routes/managed-onboarding";
|
|
290
|
+
import {
|
|
291
|
+
registerManagedSignInMethodRoutes,
|
|
292
|
+
handleManagedSignInConnectCallback,
|
|
293
|
+
} from "./routes/managed-sign-in-methods";
|
|
294
|
+
import {
|
|
295
|
+
registerManagedAuthSessionSetRoutes,
|
|
296
|
+
requireManagedAuthProviderRouteAllowed,
|
|
297
|
+
scrubManagedAuthProviderResponse,
|
|
298
|
+
} from "./routes/managed-auth-session-sets";
|
|
299
|
+
import { registerUserResourceAuthorityRoutes } from "./routes/user-resource-authorities";
|
|
123
300
|
import { projectClientModel } from "./model-catalog";
|
|
124
301
|
import { createTranscriptionService } from "./transcription/service";
|
|
125
302
|
import { createFfmpegTranscriptionSegmenter } from "./transcription/segmenter";
|
|
@@ -149,6 +326,8 @@ export { workflowIdForSession } from "@opengeni/core";
|
|
|
149
326
|
export { replaySessionEvents, sseSessionStream, sseWorkspaceControlStream } from "./http/sse";
|
|
150
327
|
|
|
151
328
|
export const API_MAX_REQUEST_BODY_BYTES = 32 * 1024 * 1024;
|
|
329
|
+
const managedAuthReaperDatabases = new WeakSet<object>();
|
|
330
|
+
const MANAGED_AUTH_REAPER_INTERVAL_MS = 60_000;
|
|
152
331
|
|
|
153
332
|
/** Effective Hono bodyLimit — API JSON ceiling or voice multipart + multipart overhead. */
|
|
154
333
|
export function apiRequestBodyLimitBytes(settings: {
|
|
@@ -167,11 +346,58 @@ export function createApp(deps: AppDependencies): Hono {
|
|
|
167
346
|
return createAppComposition(deps).app;
|
|
168
347
|
}
|
|
169
348
|
|
|
349
|
+
export async function resolveWorkspaceMcpRouteDeps(
|
|
350
|
+
routeDeps: ApiRouteDeps,
|
|
351
|
+
grant: AccessGrant,
|
|
352
|
+
): Promise<ApiRouteDeps> {
|
|
353
|
+
const catalogSourceSettings = routeDeps.catalogSourceSettings ?? routeDeps.settings;
|
|
354
|
+
const resolvedCatalog = await resolveWorkspaceCatalogSettings(
|
|
355
|
+
routeDeps.db,
|
|
356
|
+
catalogSourceSettings,
|
|
357
|
+
{
|
|
358
|
+
accountId: grant.accountId,
|
|
359
|
+
workspaceId: grant.workspaceId,
|
|
360
|
+
},
|
|
361
|
+
);
|
|
362
|
+
return { ...routeDeps, catalogSourceSettings, settings: resolvedCatalog.settings };
|
|
363
|
+
}
|
|
364
|
+
|
|
170
365
|
export function createAppComposition(deps: AppDependencies): {
|
|
171
366
|
app: Hono;
|
|
172
367
|
routeDeps: ApiRouteDeps;
|
|
173
368
|
} {
|
|
174
|
-
|
|
369
|
+
assertOrganizationUserSetupQueryTransportConfigured(deps.settings);
|
|
370
|
+
// The request-scoped workspace control-prefix budget is validated once by
|
|
371
|
+
// @opengeni/config at boot; install it for every request-scoped db command
|
|
372
|
+
// (Send/Steer/control/queue/settings/delete) built by this app.
|
|
373
|
+
configureWorkspaceControlRequestLockTimeoutMs(deps.settings.workspaceControlLockTimeoutMs);
|
|
374
|
+
// Child lifecycle notice producers (human-input/approval resolutions, direct
|
|
375
|
+
// Pause) run inside API-originated db commands; install the boot-validated
|
|
376
|
+
// rollout flag once for this process.
|
|
377
|
+
configureChildLifecycleNotices({ enabled: deps.settings.childLifecycleNoticesEnabled });
|
|
378
|
+
// Sessions created by this process freeze their code_search decision from
|
|
379
|
+
// the boot-parsed deployment policy.
|
|
380
|
+
configureCodeSearchDeploymentPolicy(codeSearchDeploymentPolicy(deps.settings));
|
|
381
|
+
const managedEmailTransport =
|
|
382
|
+
deps.managedEmailTransport ?? createManagedEmailTransport(deps.settings);
|
|
383
|
+
assertManagedEmailTransportMetadata(managedEmailTransport);
|
|
384
|
+
const observability =
|
|
385
|
+
deps.observability ?? createObservability(deps.settings, { component: "api" });
|
|
386
|
+
const managedAuthNewSignupsGate = createManagedAuthNewSignupsGate({
|
|
387
|
+
db: deps.db,
|
|
388
|
+
settings: deps.settings,
|
|
389
|
+
observability,
|
|
390
|
+
});
|
|
391
|
+
const managedAuth =
|
|
392
|
+
deps.managedAuth ??
|
|
393
|
+
createManagedAuth(deps.settings, deps.db, managedEmailTransport, {
|
|
394
|
+
observability,
|
|
395
|
+
newSignupsGate: managedAuthNewSignupsGate,
|
|
396
|
+
});
|
|
397
|
+
const managedAuthSessionAdapter =
|
|
398
|
+
deps.managedAuthSessionAdapter ??
|
|
399
|
+
(managedAuth ? createBetterAuthSessionAdapter(managedAuth, deps.db) : null);
|
|
400
|
+
if (managedAuth) startManagedSignInNotificationDelivery(deps.db, managedEmailTransport);
|
|
175
401
|
const objectStorage =
|
|
176
402
|
deps.objectStorage === undefined ? createObjectStorage(deps.settings) : deps.objectStorage;
|
|
177
403
|
let documentServices: DocumentServices | null = deps.documentServices ?? null;
|
|
@@ -204,9 +430,12 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
204
430
|
if (context.accountId !== accountId) {
|
|
205
431
|
throw new Error("document account/workspace authority mismatch");
|
|
206
432
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
433
|
+
// This is a metadata-only authority-tuple fence. Workspace and
|
|
434
|
+
// organization documents intentionally have no authority subject, so a
|
|
435
|
+
// viewer-scoped public read cannot supply the immutable human needed for
|
|
436
|
+
// Drive ACL evaluation. The byte boundary below reconstructs that
|
|
437
|
+
// subject from the stored document through indexDocumentNow.
|
|
438
|
+
const claimedDocument = await getDocumentForIndexing(deps.db, workspaceId, documentId);
|
|
210
439
|
if (
|
|
211
440
|
!claimedDocument ||
|
|
212
441
|
claimedDocument.authorityKind !== authorityKind ||
|
|
@@ -221,16 +450,6 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
221
450
|
workspaceId,
|
|
222
451
|
documentId,
|
|
223
452
|
getDocumentServices(),
|
|
224
|
-
{
|
|
225
|
-
beforeEmbed: async ({ chunkCount }) => {
|
|
226
|
-
await requireLimit(routeDeps, {
|
|
227
|
-
accountId,
|
|
228
|
-
workspaceId,
|
|
229
|
-
action: "document:index",
|
|
230
|
-
quantity: chunkCount,
|
|
231
|
-
});
|
|
232
|
-
},
|
|
233
|
-
},
|
|
234
453
|
{ viewerSubjectId: authoritySubjectId },
|
|
235
454
|
);
|
|
236
455
|
if (
|
|
@@ -249,8 +468,24 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
249
468
|
// concrete for routes; it throws SandboxResumeError when sandboxBackend=none.
|
|
250
469
|
const sandboxClient = deps.sandboxClient ?? createApiSandboxClient(deps.settings);
|
|
251
470
|
const resumeBoxById = deps.resumeBoxById ?? makeResumeBoxById(sandboxClient);
|
|
252
|
-
|
|
253
|
-
|
|
471
|
+
if (
|
|
472
|
+
managedAuth &&
|
|
473
|
+
deps.settings.managedAuthSessionSetMode !== "legacy" &&
|
|
474
|
+
!managedAuthReaperDatabases.has(deps.db as object)
|
|
475
|
+
) {
|
|
476
|
+
managedAuthReaperDatabases.add(deps.db as object);
|
|
477
|
+
const timer = setInterval(() => {
|
|
478
|
+
void Promise.all([
|
|
479
|
+
reapManagedAuthIsolatedSessions(deps.db, 100),
|
|
480
|
+
reapExpiredManagedAuthSessionSets(deps.db, 100),
|
|
481
|
+
]).catch((error) => {
|
|
482
|
+
observability.error("Managed isolated-auth orphan reap failed", {
|
|
483
|
+
errorClass: error instanceof Error ? error.name : "UnknownError",
|
|
484
|
+
});
|
|
485
|
+
});
|
|
486
|
+
}, MANAGED_AUTH_REAPER_INTERVAL_MS);
|
|
487
|
+
(timer as ReturnType<typeof setInterval> & { unref?: () => void }).unref?.();
|
|
488
|
+
}
|
|
254
489
|
const transcription =
|
|
255
490
|
deps.transcription === undefined
|
|
256
491
|
? createTranscriptionService({
|
|
@@ -265,12 +500,26 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
265
500
|
ffmpegPath: deps.settings.voiceInputFfmpegPath,
|
|
266
501
|
})
|
|
267
502
|
: deps.transcriptionSegmenter;
|
|
503
|
+
registerProductUsageMetricBaselines(observability);
|
|
504
|
+
// Server-side presence counts managed people, so it exists only where
|
|
505
|
+
// canonical managed browser sessions exist. Writes are batched off the
|
|
506
|
+
// request path; see packages/core/src/user-presence.ts.
|
|
507
|
+
const userPresence =
|
|
508
|
+
deps.userPresence !== undefined
|
|
509
|
+
? deps.userPresence
|
|
510
|
+
: deps.settings.productAccessMode === "managed"
|
|
511
|
+
? createUserPresenceRecorder({ db: deps.db, observability })
|
|
512
|
+
: null;
|
|
268
513
|
const routeDeps: ApiRouteDeps = {
|
|
269
514
|
...deps,
|
|
515
|
+
resolveCatalogSettings: () => resolveCatalogSettings(deps.db, deps.settings),
|
|
270
516
|
observability,
|
|
517
|
+
userPresence,
|
|
271
518
|
githubStateSecret:
|
|
272
519
|
deps.githubStateSecret ?? deps.settings.githubAppManifestStateSecret ?? crypto.randomUUID(),
|
|
273
520
|
managedAuth,
|
|
521
|
+
managedAuthSessionAdapter,
|
|
522
|
+
managedEmailTransport,
|
|
274
523
|
objectStorage,
|
|
275
524
|
documentIndexer,
|
|
276
525
|
getDocumentServices,
|
|
@@ -280,16 +529,107 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
280
529
|
resumeBoxById,
|
|
281
530
|
};
|
|
282
531
|
const app = new Hono();
|
|
283
|
-
|
|
532
|
+
// Body/auth middleware may replace c.req.raw. The Hono context remains the
|
|
533
|
+
// same for the response, diagnostic and completion log of this request.
|
|
534
|
+
const correlationIds = new WeakMap<Context, string>();
|
|
535
|
+
const loggedHttpFailures = new WeakSet<Context>();
|
|
536
|
+
const logHttpFailure = (c: Context, status: number, code: ErrorCode, error?: unknown) => {
|
|
537
|
+
if (loggedHttpFailures.has(c)) return;
|
|
538
|
+
loggedHttpFailures.add(c);
|
|
539
|
+
const correlationId = correlationIds.get(c) ?? crypto.randomUUID();
|
|
540
|
+
const route = routeLabel(new URL(c.req.url).pathname, registeredHandlerRoutePath(c));
|
|
541
|
+
const input =
|
|
542
|
+
error === undefined
|
|
543
|
+
? null
|
|
544
|
+
: {
|
|
545
|
+
code: "http_request_failed" as const,
|
|
546
|
+
stage: "http.request" as const,
|
|
547
|
+
error,
|
|
548
|
+
};
|
|
549
|
+
const diagnostic = input ? failureDiagnostic(input, deps.settings.deploymentRevision) : null;
|
|
550
|
+
// Diagnostics and logging are observers, never a new response failure.
|
|
551
|
+
try {
|
|
552
|
+
if (input && diagnostic) {
|
|
553
|
+
observability.recordFailureDiagnostic({ ...input, diagnosticId: diagnostic.diagnosticId });
|
|
554
|
+
}
|
|
555
|
+
} catch {
|
|
556
|
+
/* Preserve the original error response if the diagnostic sink fails. */
|
|
557
|
+
}
|
|
558
|
+
try {
|
|
559
|
+
observability.error("HTTP request failed", {
|
|
560
|
+
method: c.req.method,
|
|
561
|
+
route,
|
|
562
|
+
status,
|
|
563
|
+
correlationId,
|
|
564
|
+
errorCode: code,
|
|
565
|
+
errorClass: "HttpOperationError",
|
|
566
|
+
origin: "api",
|
|
567
|
+
reasonKind: diagnostic?.causes[0]?.kind ?? "Response",
|
|
568
|
+
...(diagnostic ? { diagnosticId: diagnostic.diagnosticId } : {}),
|
|
569
|
+
});
|
|
570
|
+
observability.incrementCounter({
|
|
571
|
+
name: "opengeni_http_errors_total",
|
|
572
|
+
help: "Total OpenGeni HTTP request failures by bounded route, status, and stable code.",
|
|
573
|
+
labels: { route, status: String(status), code },
|
|
574
|
+
});
|
|
575
|
+
} catch {
|
|
576
|
+
/* Preserve the original error response if the logger fails. */
|
|
577
|
+
}
|
|
578
|
+
};
|
|
284
579
|
|
|
285
580
|
app.use("*", async (c, next) => {
|
|
286
581
|
const correlationId =
|
|
287
582
|
boundedCorrelationId(c.req.header(OPENGENI_CORRELATION_HEADER)) ?? crypto.randomUUID();
|
|
288
|
-
correlationIds.set(c
|
|
583
|
+
correlationIds.set(c, correlationId);
|
|
289
584
|
c.header(OPENGENI_CORRELATION_HEADER, correlationId);
|
|
290
585
|
await next();
|
|
291
586
|
});
|
|
292
587
|
|
|
588
|
+
// Unauthenticated local mode: admit only requests addressed to this computer
|
|
589
|
+
// and, when a browser sent them, from this stack's web app (see
|
|
590
|
+
// http/local-browser-boundary.ts). Runs before CORS so a refused preflight
|
|
591
|
+
// carries no CORS grant, and logs each distinct refused Host or Origin once
|
|
592
|
+
// because the browser shows only a generic CORS error. Null outside local
|
|
593
|
+
// development.
|
|
594
|
+
const localBrowserBoundary = createLocalBrowserBoundary(deps.settings, {
|
|
595
|
+
warn: (message, attributes) => observability.warn(message, attributes),
|
|
596
|
+
});
|
|
597
|
+
if (localBrowserBoundary) {
|
|
598
|
+
app.use("*", async (c, next) => {
|
|
599
|
+
const rejection = localBrowserBoundary.rejection(c.req.raw);
|
|
600
|
+
if (rejection) {
|
|
601
|
+
throw new ApiHttpError(rejection.status, {
|
|
602
|
+
code: "forbidden",
|
|
603
|
+
message: rejection.message,
|
|
604
|
+
retryable: false,
|
|
605
|
+
details: { code: rejection.code },
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
await next();
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// Better Auth keys its rate limits and session addresses on a request
|
|
613
|
+
// header. Drop any caller-supplied copy everywhere and stamp the trusted
|
|
614
|
+
// source address on managed-auth routes before any route derives a Better
|
|
615
|
+
// Auth request from this one.
|
|
616
|
+
app.use("*", async (c, next) => {
|
|
617
|
+
replaceTrustedClientAddressHeader(c, deps.settings, c.req.path.startsWith("/v1/auth/"));
|
|
618
|
+
await next();
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
app.use("*", async (c, next) => {
|
|
622
|
+
const oauthToken = mcpOAuthBearerToken(c.req.raw);
|
|
623
|
+
if (
|
|
624
|
+
oauthToken &&
|
|
625
|
+
(!deps.settings.mcpOauthEnabled || !isMcpOAuthResourcePath(new URL(c.req.url).pathname))
|
|
626
|
+
) {
|
|
627
|
+
c.header("www-authenticate", 'Bearer error="invalid_token"');
|
|
628
|
+
return c.json({ error: "invalid_token" }, 401);
|
|
629
|
+
}
|
|
630
|
+
await next();
|
|
631
|
+
});
|
|
632
|
+
|
|
293
633
|
const corsHeaders = {
|
|
294
634
|
allowHeaders: [
|
|
295
635
|
"Accept",
|
|
@@ -298,14 +638,39 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
298
638
|
"Range",
|
|
299
639
|
"X-OpenGeni-Access-Key",
|
|
300
640
|
"X-OpenGeni-Api-Contract",
|
|
641
|
+
"X-OpenGeni-Actor-Epoch",
|
|
301
642
|
"X-OpenGeni-Correlation-Id",
|
|
643
|
+
"X-OpenGeni-Session-Csrf",
|
|
644
|
+
"X-OpenGeni-Site-Id",
|
|
645
|
+
"X-OpenGeni-Site-Version",
|
|
302
646
|
"X-OpenGeni-Subject",
|
|
647
|
+
"X-OpenGeni-User-Activity",
|
|
303
648
|
],
|
|
304
649
|
exposeHeaders: [
|
|
305
650
|
"Accept-Ranges",
|
|
306
651
|
"Content-Range",
|
|
652
|
+
"Deprecation",
|
|
653
|
+
"Link",
|
|
654
|
+
"Sunset",
|
|
307
655
|
"X-OpenGeni-Api-Contract",
|
|
656
|
+
"X-OpenGeni-Actor-Epoch",
|
|
657
|
+
"X-OpenGeni-Actor-State",
|
|
308
658
|
"X-OpenGeni-Correlation-Id",
|
|
659
|
+
"X-OpenGeni-Covered-First",
|
|
660
|
+
"X-OpenGeni-Covered-Last",
|
|
661
|
+
"X-OpenGeni-Event-Direction",
|
|
662
|
+
"X-OpenGeni-Event-Mode",
|
|
663
|
+
"X-OpenGeni-Event-Result",
|
|
664
|
+
"X-OpenGeni-Event-Result-Mode",
|
|
665
|
+
"X-OpenGeni-Forensic-Exact",
|
|
666
|
+
"X-OpenGeni-Has-More",
|
|
667
|
+
"X-OpenGeni-Next-After",
|
|
668
|
+
"X-OpenGeni-Next-Before",
|
|
669
|
+
"X-OpenGeni-Page-Bytes",
|
|
670
|
+
"X-OpenGeni-Page-Max-Bytes",
|
|
671
|
+
"X-OpenGeni-Page-Truncated",
|
|
672
|
+
"X-OpenGeni-Payload-Mode",
|
|
673
|
+
"X-OpenGeni-Truncated-By",
|
|
309
674
|
],
|
|
310
675
|
};
|
|
311
676
|
const publicApiCors = cors({
|
|
@@ -320,8 +685,25 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
320
685
|
allowedCorsOrigin(deps.settings.corsAllowOriginRegex, origin) ? origin : null,
|
|
321
686
|
});
|
|
322
687
|
|
|
688
|
+
const localCors = localBrowserBoundary
|
|
689
|
+
? cors({
|
|
690
|
+
...corsHeaders,
|
|
691
|
+
credentials: true,
|
|
692
|
+
origin: (origin, c) =>
|
|
693
|
+
localBrowserBoundary.originAllowed(origin, localBrowserRequestHost(c.req.raw))
|
|
694
|
+
? origin
|
|
695
|
+
: null,
|
|
696
|
+
})
|
|
697
|
+
: null;
|
|
698
|
+
|
|
323
699
|
app.use("*", (c, next) => {
|
|
324
700
|
const origin = c.req.header("origin");
|
|
701
|
+
if (localCors) {
|
|
702
|
+
// The boundary above already refused every other origin. Local mode
|
|
703
|
+
// never answers with wildcard CORS: a request without credentials acts
|
|
704
|
+
// as the local user, so any site could otherwise read its responses.
|
|
705
|
+
return origin ? localCors(c, next) : next();
|
|
706
|
+
}
|
|
325
707
|
const middleware =
|
|
326
708
|
origin && allowedCorsOrigin(deps.settings.corsAllowOriginRegex, origin)
|
|
327
709
|
? credentialedCors
|
|
@@ -329,14 +711,32 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
329
711
|
return middleware(c, next);
|
|
330
712
|
});
|
|
331
713
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
714
|
+
const standardRequestBodyLimit = bodyLimit({
|
|
715
|
+
maxSize: apiRequestBodyLimitBytes(deps.settings),
|
|
716
|
+
onError: (c) =>
|
|
717
|
+
c.json({ code: "PAYLOAD_TOO_LARGE", message: "Request body is too large." }, 413),
|
|
718
|
+
});
|
|
719
|
+
app.use("*", async (c, next) => {
|
|
720
|
+
// Git packfiles must stay streaming and can legitimately exceed the JSON
|
|
721
|
+
// request ceiling. The exact closed broker routes apply their own method,
|
|
722
|
+
// content-type, authority, and idle-deadline checks.
|
|
723
|
+
const pathname = new URL(c.req.url).pathname;
|
|
724
|
+
if (isPersonalGitHubGitBrokerRequest(c.req.method, pathname)) {
|
|
725
|
+
await next();
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
// The anonymous web beacons enforce their own small limits on the
|
|
729
|
+
// streamed body; the generic ceiling would buffer far more first.
|
|
730
|
+
if (
|
|
731
|
+
isClientErrorReportRequest(c.req.method, pathname) ||
|
|
732
|
+
isAnalyticsConsentReportRequest(c.req.method, pathname) ||
|
|
733
|
+
isSessionHistoryImportRequest(c.req.method, pathname)
|
|
734
|
+
) {
|
|
735
|
+
await next();
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
return await standardRequestBodyLimit(c, next);
|
|
739
|
+
});
|
|
340
740
|
|
|
341
741
|
// Large catalog, capture, and session-list responses are on the browser's
|
|
342
742
|
// critical path. Compress JSON at the API boundary while leaving SSE and
|
|
@@ -352,90 +752,146 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
352
752
|
}
|
|
353
753
|
});
|
|
354
754
|
|
|
755
|
+
// Public-route deprecations (docs/design/api-compatibility-policy.md) are
|
|
756
|
+
// advertised on every response of the affected route, errors included.
|
|
757
|
+
app.use("/v1/*", deprecationHeadersMiddleware());
|
|
758
|
+
// A pinned SDK must not be told about a revision it is built to reject; see
|
|
759
|
+
// the policy's contract-header rule. Registered before the contract fence so
|
|
760
|
+
// it observes the header that fence sets.
|
|
761
|
+
app.use("/v1/*", bearerApiContractHeaderCompatibility());
|
|
762
|
+
|
|
355
763
|
app.use("*", async (c, next) => {
|
|
356
764
|
const url = new URL(c.req.url);
|
|
357
|
-
const route = routeLabel(url.pathname);
|
|
358
|
-
const correlationId = correlationIds.get(c
|
|
765
|
+
const route = routeLabel(url.pathname, registeredHandlerRoutePath(c));
|
|
766
|
+
const correlationId = correlationIds.get(c) ?? crypto.randomUUID();
|
|
359
767
|
const start = performance.now();
|
|
360
|
-
const span = observability.startSpan(
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
768
|
+
const span = observability.startSpan(
|
|
769
|
+
`HTTP ${c.req.method} ${route}`,
|
|
770
|
+
{
|
|
771
|
+
"http.request.method": c.req.method,
|
|
772
|
+
"opengeni.route": route,
|
|
773
|
+
},
|
|
774
|
+
{ parent: null },
|
|
775
|
+
);
|
|
776
|
+
return await withMcpTelemetry(observability, span.traceId, () =>
|
|
777
|
+
withTraceContext(span, async () => {
|
|
778
|
+
try {
|
|
779
|
+
await next();
|
|
780
|
+
const status = c.res.status || 200;
|
|
781
|
+
// Hono normally handles exceptions in onError before next() resolves.
|
|
782
|
+
// Also cover a handler returning a 5xx without throwing.
|
|
783
|
+
if (status >= 500) logHttpFailure(c, status, errorCodeForStatus(status), c.error);
|
|
784
|
+
const durationSeconds = (performance.now() - start) / 1000;
|
|
785
|
+
observability.recordHttpRequest({
|
|
786
|
+
method: c.req.method,
|
|
787
|
+
route,
|
|
788
|
+
status,
|
|
789
|
+
durationSeconds,
|
|
790
|
+
});
|
|
791
|
+
span.end({
|
|
792
|
+
attributes: {
|
|
793
|
+
"http.response.status_code": status,
|
|
794
|
+
"opengeni.duration_ms": Math.round(durationSeconds * 1000),
|
|
795
|
+
},
|
|
796
|
+
...(c.error ? { error: c.error } : {}),
|
|
797
|
+
});
|
|
798
|
+
observability.info("HTTP request completed", {
|
|
799
|
+
method: c.req.method,
|
|
800
|
+
route,
|
|
801
|
+
status,
|
|
802
|
+
durationMs: Math.round(durationSeconds * 1000),
|
|
803
|
+
traceId: span.traceId,
|
|
804
|
+
spanId: span.spanId,
|
|
805
|
+
correlationId,
|
|
806
|
+
});
|
|
807
|
+
} catch (error) {
|
|
808
|
+
const status = httpStatusForError(error);
|
|
809
|
+
const errorCode = errorCodeForStatus(status);
|
|
810
|
+
const durationSeconds = (performance.now() - start) / 1000;
|
|
811
|
+
observability.recordHttpRequest({
|
|
812
|
+
method: c.req.method,
|
|
813
|
+
route,
|
|
814
|
+
status,
|
|
815
|
+
durationSeconds,
|
|
816
|
+
});
|
|
817
|
+
span.end({
|
|
818
|
+
attributes: {
|
|
819
|
+
"http.response.status_code": status,
|
|
820
|
+
"opengeni.duration_ms": Math.round(durationSeconds * 1000),
|
|
821
|
+
},
|
|
822
|
+
error,
|
|
823
|
+
});
|
|
824
|
+
logHttpFailure(c, status, errorCode, error);
|
|
825
|
+
throw error;
|
|
826
|
+
}
|
|
827
|
+
}),
|
|
828
|
+
);
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
const accessKeyBoundary = requireAccessKey(deps.settings);
|
|
832
|
+
app.use("*", async (c, next) => {
|
|
833
|
+
// Git's credential helper supplies the exact encrypted broker bearer in
|
|
834
|
+
// Authorization. It cannot also supply the deployment access key, and the
|
|
835
|
+
// route performs its own attempt-bound authorization before every use.
|
|
836
|
+
if (isPersonalGitHubGitBrokerRequest(c.req.method, new URL(c.req.url).pathname)) {
|
|
365
837
|
await next();
|
|
366
|
-
|
|
367
|
-
const durationSeconds = (performance.now() - start) / 1000;
|
|
368
|
-
observability.recordHttpRequest({
|
|
369
|
-
method: c.req.method,
|
|
370
|
-
route,
|
|
371
|
-
status,
|
|
372
|
-
durationSeconds,
|
|
373
|
-
});
|
|
374
|
-
span.end({
|
|
375
|
-
attributes: {
|
|
376
|
-
"http.response.status_code": status,
|
|
377
|
-
"opengeni.duration_ms": Math.round(durationSeconds * 1000),
|
|
378
|
-
},
|
|
379
|
-
});
|
|
380
|
-
observability.info("HTTP request completed", {
|
|
381
|
-
method: c.req.method,
|
|
382
|
-
route,
|
|
383
|
-
status,
|
|
384
|
-
durationMs: Math.round(durationSeconds * 1000),
|
|
385
|
-
traceId: span.traceId,
|
|
386
|
-
spanId: span.spanId,
|
|
387
|
-
correlationId,
|
|
388
|
-
});
|
|
389
|
-
} catch (error) {
|
|
390
|
-
const status = httpStatusForError(error);
|
|
391
|
-
const errorCode = errorCodeForStatus(status);
|
|
392
|
-
const durationSeconds = (performance.now() - start) / 1000;
|
|
393
|
-
observability.recordHttpRequest({
|
|
394
|
-
method: c.req.method,
|
|
395
|
-
route,
|
|
396
|
-
status,
|
|
397
|
-
durationSeconds,
|
|
398
|
-
});
|
|
399
|
-
observability.incrementCounter({
|
|
400
|
-
name: "opengeni_http_errors_total",
|
|
401
|
-
help: "Total OpenGeni HTTP request failures by bounded route, status, and stable code.",
|
|
402
|
-
labels: { route, status: String(status), code: errorCode },
|
|
403
|
-
});
|
|
404
|
-
span.end({
|
|
405
|
-
attributes: {
|
|
406
|
-
"http.response.status_code": status,
|
|
407
|
-
"opengeni.duration_ms": Math.round(durationSeconds * 1000),
|
|
408
|
-
},
|
|
409
|
-
error,
|
|
410
|
-
});
|
|
411
|
-
observability.error("HTTP request failed", {
|
|
412
|
-
method: c.req.method,
|
|
413
|
-
route,
|
|
414
|
-
status,
|
|
415
|
-
durationMs: Math.round(durationSeconds * 1000),
|
|
416
|
-
traceId: span.traceId,
|
|
417
|
-
spanId: span.spanId,
|
|
418
|
-
correlationId,
|
|
419
|
-
errorCode,
|
|
420
|
-
errorClass: "HttpOperationError",
|
|
421
|
-
});
|
|
422
|
-
throw error;
|
|
838
|
+
return;
|
|
423
839
|
}
|
|
840
|
+
return await accessKeyBoundary(c, next);
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
// Malformed JSON in a client request body is a 400 wherever a route reads it.
|
|
844
|
+
app.use("/v1/*", async (c, next) => {
|
|
845
|
+
tagRequestJsonParseErrors(c);
|
|
846
|
+
await next();
|
|
424
847
|
});
|
|
425
848
|
|
|
426
|
-
|
|
849
|
+
// A request no registered handler answers is a 404 (or a 405 when the path
|
|
850
|
+
// exists for other methods) before any authentication, authorization, or
|
|
851
|
+
// contract middleware can turn it into a misleading 401/409/503. This runs
|
|
852
|
+
// after the deployment perimeter so an unauthenticated caller of a
|
|
853
|
+
// key-protected deployment learns nothing new.
|
|
854
|
+
app.use("/v1/*", async (c, next) => {
|
|
855
|
+
const unmatched = unmatchedRoute(app, c);
|
|
856
|
+
if (!unmatched) {
|
|
857
|
+
await next();
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
const requestId = correlationIds.get(c) ?? crypto.randomUUID();
|
|
861
|
+
c.header(OPENGENI_API_CONTRACT_HEADER, OPENGENI_API_CONTRACT_REVISION);
|
|
862
|
+
if (unmatched.status === 405) c.header("allow", unmatched.allow.join(", "));
|
|
863
|
+
return c.json(
|
|
864
|
+
ErrorEnvelope.parse({
|
|
865
|
+
error: {
|
|
866
|
+
status: unmatched.status,
|
|
867
|
+
code: "not_found",
|
|
868
|
+
message:
|
|
869
|
+
unmatched.status === 405
|
|
870
|
+
? `Method ${c.req.method} is not supported for this resource. Allowed: ${unmatched.allow.join(", ")}.`
|
|
871
|
+
: "Resource not found.",
|
|
872
|
+
retryable: false,
|
|
873
|
+
requestId,
|
|
874
|
+
},
|
|
875
|
+
}),
|
|
876
|
+
unmatched.status,
|
|
877
|
+
);
|
|
878
|
+
});
|
|
427
879
|
|
|
428
880
|
app.use("/v1/*", async (c, next) => {
|
|
429
881
|
c.header(OPENGENI_API_CONTRACT_HEADER, OPENGENI_API_CONTRACT_REVISION);
|
|
430
882
|
if (
|
|
431
883
|
deps.settings.environment !== "test" &&
|
|
432
|
-
|
|
433
|
-
|
|
884
|
+
apiContractAdmission({
|
|
885
|
+
method: c.req.method,
|
|
886
|
+
pathname: new URL(c.req.url).pathname,
|
|
887
|
+
authorization: c.req.header("authorization"),
|
|
888
|
+
claimedRevision: c.req.header(OPENGENI_API_CONTRACT_HEADER),
|
|
889
|
+
}) === "reject"
|
|
434
890
|
) {
|
|
435
891
|
return c.json(
|
|
436
892
|
{
|
|
437
893
|
code: "API_CONTRACT_CHANGED",
|
|
438
|
-
message: "
|
|
894
|
+
message: "Opengeni updated. Reload this client before changing state.",
|
|
439
895
|
apiContractRevision: OPENGENI_API_CONTRACT_REVISION,
|
|
440
896
|
},
|
|
441
897
|
409,
|
|
@@ -444,19 +900,255 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
444
900
|
await next();
|
|
445
901
|
});
|
|
446
902
|
|
|
903
|
+
app.use("/v1/*", async (c, next) => {
|
|
904
|
+
try {
|
|
905
|
+
await next();
|
|
906
|
+
try {
|
|
907
|
+
await validateManagedAuthRequestActorLease(c.req.raw);
|
|
908
|
+
} catch (error) {
|
|
909
|
+
if (error instanceof ManagedAuthActorChangeError) {
|
|
910
|
+
c.header("x-opengeni-actor-state", "changed");
|
|
911
|
+
throw new HTTPException(409, { message: error.code, cause: error });
|
|
912
|
+
}
|
|
913
|
+
if (error instanceof ManagedAuthActorLeaseOutcomeUnknownError) {
|
|
914
|
+
throw new ApiHttpError(503, {
|
|
915
|
+
code: "upstream_unavailable",
|
|
916
|
+
message: error.code,
|
|
917
|
+
retryable: true,
|
|
918
|
+
outcomeUnknown: true,
|
|
919
|
+
details: { managedAuthCode: error.code },
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
throw error;
|
|
923
|
+
}
|
|
924
|
+
} finally {
|
|
925
|
+
await releaseManagedAuthRequestActorLease(c.req.raw).catch((error) => {
|
|
926
|
+
observability.error("Managed actor mutation lease release failed", {
|
|
927
|
+
errorClass: error instanceof Error ? error.name : "UnknownError",
|
|
928
|
+
});
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
});
|
|
932
|
+
|
|
933
|
+
// These product-owned auth routes must be registered before Better Auth's
|
|
934
|
+
// wildcard handler or the provider returns its own 404 first.
|
|
935
|
+
registerManagedOnboardingRoutes(app, routeDeps);
|
|
936
|
+
registerManagedAuthSessionSetRoutes(app, routeDeps);
|
|
937
|
+
registerManagedSignInMethodRoutes(app, routeDeps);
|
|
447
938
|
if (managedAuth) {
|
|
448
|
-
app.on(["GET", "POST"], "/v1/auth/*", (c) =>
|
|
939
|
+
app.on(["GET", "POST"], "/v1/auth/*", async (c) => {
|
|
940
|
+
const pathname = new URL(c.req.url).pathname;
|
|
941
|
+
const oauthCallbackProvider = managedAuthOAuthCallbackProvider(pathname);
|
|
942
|
+
if (
|
|
943
|
+
pathname === "/v1/auth/sign-up/email" &&
|
|
944
|
+
c.req.method === "POST" &&
|
|
945
|
+
!(await managedAuthNewSignupsGate.signupsOpen())
|
|
946
|
+
) {
|
|
947
|
+
// Launch-load safety switch: refuse before Better Auth hashes the
|
|
948
|
+
// password or sends mail. Better Auth's own disableSignUp flags and
|
|
949
|
+
// the user-create hook remain the backstop for every other path.
|
|
950
|
+
return managedAuthNewSignupsPausedResponse();
|
|
951
|
+
}
|
|
952
|
+
if (pathname === "/v1/auth/sign-in/social" && c.req.method === "POST") {
|
|
953
|
+
const body = await c.req.raw
|
|
954
|
+
.clone()
|
|
955
|
+
.json()
|
|
956
|
+
.catch(() => null);
|
|
957
|
+
if (
|
|
958
|
+
!body ||
|
|
959
|
+
!["google", "github"].includes(body.provider) ||
|
|
960
|
+
Object.prototype.hasOwnProperty.call(body, "idToken")
|
|
961
|
+
) {
|
|
962
|
+
return c.json(
|
|
963
|
+
{
|
|
964
|
+
code: "SIGN_IN_METHOD_OAUTH_REDIRECT_REQUIRED",
|
|
965
|
+
message: "Use the browser OAuth sign-in redirect",
|
|
966
|
+
},
|
|
967
|
+
403,
|
|
968
|
+
);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
if (oauthCallbackProvider) {
|
|
972
|
+
const connectResponse = await handleManagedSignInConnectCallback(
|
|
973
|
+
c,
|
|
974
|
+
routeDeps,
|
|
975
|
+
oauthCallbackProvider,
|
|
976
|
+
);
|
|
977
|
+
if (connectResponse) return connectResponse;
|
|
978
|
+
}
|
|
979
|
+
if (
|
|
980
|
+
new Set([
|
|
981
|
+
"link-social",
|
|
982
|
+
"unlink-account",
|
|
983
|
+
"list-accounts",
|
|
984
|
+
"set-password",
|
|
985
|
+
"change-password",
|
|
986
|
+
"change-email",
|
|
987
|
+
"update-user",
|
|
988
|
+
"delete-user",
|
|
989
|
+
"get-access-token",
|
|
990
|
+
"refresh-token",
|
|
991
|
+
"account-info",
|
|
992
|
+
]).has(pathname.slice("/v1/auth/".length))
|
|
993
|
+
) {
|
|
994
|
+
return c.json(
|
|
995
|
+
{
|
|
996
|
+
code: "SIGN_IN_METHOD_PRODUCT_ROUTE_REQUIRED",
|
|
997
|
+
message: "Use personal sign-in method settings",
|
|
998
|
+
},
|
|
999
|
+
403,
|
|
1000
|
+
);
|
|
1001
|
+
}
|
|
1002
|
+
if (deps.settings.managedAuthSessionSetMode === "legacy") {
|
|
1003
|
+
return oauthCallbackProvider
|
|
1004
|
+
? await runManagedAuthProvider(
|
|
1005
|
+
oauthCallbackProvider,
|
|
1006
|
+
async () => await managedAuth.handler(c.req.raw),
|
|
1007
|
+
)
|
|
1008
|
+
: await managedAuth.handler(c.req.raw);
|
|
1009
|
+
}
|
|
1010
|
+
requireManagedAuthProviderRouteAllowed(c.req.method, pathname);
|
|
1011
|
+
// Provider authentication/recovery is isolated from whichever actor the
|
|
1012
|
+
// browser currently renders. Selected-session capabilities are all
|
|
1013
|
+
// product-owned above this wildcard and generation/epoch fenced.
|
|
1014
|
+
const authority = getCookie(c, MANAGED_AUTH_SESSION_SET_COOKIE);
|
|
1015
|
+
let providerResponse: Response;
|
|
1016
|
+
let preserveCookieNames: readonly string[] | undefined;
|
|
1017
|
+
if (oauthCallbackProvider) {
|
|
1018
|
+
const attempt = await resolveManagedAuthOAuthAttempt(
|
|
1019
|
+
managedAuth,
|
|
1020
|
+
c.req.raw,
|
|
1021
|
+
oauthCallbackProvider,
|
|
1022
|
+
deps.settings.publicBaseUrl!,
|
|
1023
|
+
);
|
|
1024
|
+
if (!attempt || !authority || attempt.authorityHash !== managedAuthSha256(authority)) {
|
|
1025
|
+
throw new HTTPException(409, { message: "provider_route_blocked" });
|
|
1026
|
+
}
|
|
1027
|
+
const isolated = await isolatedManagedAuthOAuthCallbackRequest(managedAuth, c.req.raw);
|
|
1028
|
+
preserveCookieNames = [isolated.stateCookieName];
|
|
1029
|
+
const handled = await runManagedAuthAttempt(
|
|
1030
|
+
attempt.transactionId,
|
|
1031
|
+
oauthCallbackProvider,
|
|
1032
|
+
async () => {
|
|
1033
|
+
const response = await managedAuth.handler(isolated.request);
|
|
1034
|
+
return {
|
|
1035
|
+
response,
|
|
1036
|
+
authSessionId: currentManagedAuthCreatedSessionId(),
|
|
1037
|
+
};
|
|
1038
|
+
},
|
|
1039
|
+
);
|
|
1040
|
+
providerResponse = handled.response;
|
|
1041
|
+
if (
|
|
1042
|
+
!handled.authSessionId &&
|
|
1043
|
+
managedAuthOAuthReturnMatches(
|
|
1044
|
+
providerResponse.headers.get("location") ?? "",
|
|
1045
|
+
new URL(deps.settings.publicBaseUrl!).origin,
|
|
1046
|
+
attempt.transactionId,
|
|
1047
|
+
"complete",
|
|
1048
|
+
)
|
|
1049
|
+
) {
|
|
1050
|
+
const location = new URL("/account-auth", deps.settings.publicBaseUrl!);
|
|
1051
|
+
location.searchParams.set("transaction", attempt.transactionId);
|
|
1052
|
+
location.searchParams.set("social", "error");
|
|
1053
|
+
providerResponse = Response.redirect(location, 302);
|
|
1054
|
+
} else if (handled.authSessionId) {
|
|
1055
|
+
try {
|
|
1056
|
+
await adoptManagedAuthSession({
|
|
1057
|
+
db: deps.db,
|
|
1058
|
+
adapter: managedAuthSessionAdapter!,
|
|
1059
|
+
authority,
|
|
1060
|
+
authorityHash: attempt.authorityHash,
|
|
1061
|
+
csrfHash: managedAuthCsrfHash(authority),
|
|
1062
|
+
operationId: managedAuthDerivedUuid(
|
|
1063
|
+
"opengeni:managed-auth:social-completion",
|
|
1064
|
+
`${attempt.transactionId}:${attempt.provider}`,
|
|
1065
|
+
),
|
|
1066
|
+
requestDigest: managedAuthSecretRequestDigest(deps.settings.betterAuthSecret!, {
|
|
1067
|
+
operation: "social_completion",
|
|
1068
|
+
transactionId: attempt.transactionId,
|
|
1069
|
+
provider: attempt.provider,
|
|
1070
|
+
expectedGeneration: attempt.expectedGeneration,
|
|
1071
|
+
expectedActorEpoch: attempt.expectedActorEpoch,
|
|
1072
|
+
}),
|
|
1073
|
+
expectedGeneration: attempt.expectedGeneration,
|
|
1074
|
+
expectedActorEpoch: attempt.expectedActorEpoch,
|
|
1075
|
+
transactionId: attempt.transactionId,
|
|
1076
|
+
transactionSecretHash: attempt.transactionSecretHash,
|
|
1077
|
+
authSessionId: handled.authSessionId,
|
|
1078
|
+
mode: deps.settings.managedAuthSessionSetMode,
|
|
1079
|
+
});
|
|
1080
|
+
} catch {
|
|
1081
|
+
// adoptManagedAuthSession revokes only when durable reconciliation
|
|
1082
|
+
// proves that completion did not commit. An uncertain outcome may
|
|
1083
|
+
// already point the selected slot at this session, so the callback
|
|
1084
|
+
// must not independently revoke it.
|
|
1085
|
+
const location = new URL("/account-auth", deps.settings.publicBaseUrl!);
|
|
1086
|
+
location.searchParams.set("transaction", attempt.transactionId);
|
|
1087
|
+
location.searchParams.set("social", "error");
|
|
1088
|
+
providerResponse = Response.redirect(location, 302);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
} else {
|
|
1092
|
+
const headers = new Headers(c.req.raw.headers);
|
|
1093
|
+
headers.delete("cookie");
|
|
1094
|
+
headers.delete("authorization");
|
|
1095
|
+
headers.delete("x-forwarded-user");
|
|
1096
|
+
const providerRequest = new Request(c.req.raw, { headers });
|
|
1097
|
+
const discardProviderSession =
|
|
1098
|
+
deps.settings.managedAuthSessionSetMode === "broker" || authority !== undefined;
|
|
1099
|
+
providerResponse = discardProviderSession
|
|
1100
|
+
? await runManagedAuthDiscardedProviderSession(
|
|
1101
|
+
async () => await managedAuth.handler(providerRequest),
|
|
1102
|
+
)
|
|
1103
|
+
: await managedAuth.handler(providerRequest);
|
|
1104
|
+
}
|
|
1105
|
+
let replacementCookies: readonly string[] | undefined;
|
|
1106
|
+
if (deps.settings.managedAuthSessionSetMode === "broker") {
|
|
1107
|
+
replacementCookies = await managedAuthSessionAdapter!.createLegacySelectedSessionCookies(
|
|
1108
|
+
null,
|
|
1109
|
+
c.req.header("cookie") ?? null,
|
|
1110
|
+
);
|
|
1111
|
+
} else {
|
|
1112
|
+
if (authority !== undefined) {
|
|
1113
|
+
const snapshot = await getManagedAuthSessionSetSnapshot(deps.db, {
|
|
1114
|
+
authorityHash: managedAuthSha256(authority),
|
|
1115
|
+
mode: "dual",
|
|
1116
|
+
includeInternal: true,
|
|
1117
|
+
readOnly: true,
|
|
1118
|
+
});
|
|
1119
|
+
if (snapshot) {
|
|
1120
|
+
replacementCookies =
|
|
1121
|
+
await managedAuthSessionAdapter!.createLegacySelectedSessionCookies(
|
|
1122
|
+
snapshot.selected,
|
|
1123
|
+
c.req.header("cookie") ?? null,
|
|
1124
|
+
);
|
|
1125
|
+
} else {
|
|
1126
|
+
replacementCookies =
|
|
1127
|
+
await managedAuthSessionAdapter!.createLegacySelectedSessionCookies(
|
|
1128
|
+
null,
|
|
1129
|
+
c.req.header("cookie") ?? null,
|
|
1130
|
+
);
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
return await scrubManagedAuthProviderResponse(providerResponse, {
|
|
1135
|
+
replacementCookies,
|
|
1136
|
+
preserveCookieNames,
|
|
1137
|
+
});
|
|
1138
|
+
});
|
|
449
1139
|
}
|
|
450
1140
|
|
|
451
|
-
app.get("/healthz", (c) =>
|
|
452
|
-
|
|
1141
|
+
app.get("/healthz", (c) => {
|
|
1142
|
+
const warnings = githubAppBotIdentityWarnings(deps.settings);
|
|
1143
|
+
return c.json({
|
|
453
1144
|
service: deps.settings.serviceName,
|
|
454
1145
|
environment: deps.settings.environment,
|
|
455
1146
|
deploymentRevision: deps.settings.deploymentRevision,
|
|
456
1147
|
...(deps.settings.serverVersion ? { serverVersion: deps.settings.serverVersion } : {}),
|
|
1148
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
457
1149
|
ok: true,
|
|
458
|
-
})
|
|
459
|
-
);
|
|
1150
|
+
});
|
|
1151
|
+
});
|
|
460
1152
|
|
|
461
1153
|
app.get("/readyz", async (c) => {
|
|
462
1154
|
const result = await runReadinessChecks(readinessChecks(deps), 2_000);
|
|
@@ -469,44 +1161,146 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
469
1161
|
return c.json(result, result.ok ? 200 : 503);
|
|
470
1162
|
});
|
|
471
1163
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
);
|
|
1164
|
+
if (publicListenerServesMetrics(deps.settings)) {
|
|
1165
|
+
registerPrometheusMetricsRoute(app, observability);
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
registerMcpOAuthRoutes(app, routeDeps);
|
|
1169
|
+
|
|
1170
|
+
registerClientErrorRoutes(app, { observability, settings: deps.settings });
|
|
1171
|
+
registerAnalyticsConsentRoutes(app, { observability, settings: deps.settings });
|
|
477
1172
|
|
|
478
1173
|
app.get("/v1/config/client", async (c) => {
|
|
479
1174
|
c.header("cache-control", "no-store");
|
|
480
|
-
const
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
:
|
|
1175
|
+
const resolvedCatalog = await resolveCatalogSettings(deps.db, deps.settings);
|
|
1176
|
+
let catalogSettings = resolvedCatalog.settings;
|
|
1177
|
+
let selections = resolveWorkspaceModelSelection({
|
|
1178
|
+
settings: catalogSettings,
|
|
1179
|
+
policy: null,
|
|
1180
|
+
codexSubscriptionActive: false,
|
|
1181
|
+
});
|
|
1182
|
+
let defaultSelection = selectDefaultSessionModel({
|
|
1183
|
+
settings: catalogSettings,
|
|
1184
|
+
selections,
|
|
1185
|
+
workspaceDefaults: null,
|
|
1186
|
+
creditsAvailable: false,
|
|
1187
|
+
});
|
|
1188
|
+
let modelSelectionForbidden = false;
|
|
1189
|
+
const requestedWorkspaceId = c.req.query("workspaceId");
|
|
1190
|
+
if (requestedWorkspaceId !== undefined && requestedWorkspaceId.trim() === "") {
|
|
1191
|
+
throw new HTTPException(422, { message: "workspaceId must not be empty" });
|
|
1192
|
+
}
|
|
1193
|
+
const callerScoped =
|
|
1194
|
+
requestedWorkspaceId !== undefined ||
|
|
1195
|
+
deps.settings.productAccessMode !== "managed" ||
|
|
1196
|
+
c.req.header("authorization") !== undefined ||
|
|
1197
|
+
// Browser bootstrap precedes session-set reconciliation. A cookie alone
|
|
1198
|
+
// cannot scope this public read (broker/changed actors need an epoch).
|
|
1199
|
+
c.req.header("x-opengeni-actor-epoch") !== undefined ||
|
|
1200
|
+
c.req.header("x-opengeni-external-actor") !== undefined;
|
|
1201
|
+
if (callerScoped) {
|
|
1202
|
+
let context: Awaited<ReturnType<typeof requireAccessContext>> | null = null;
|
|
1203
|
+
try {
|
|
1204
|
+
context = await requireAccessContext(c, routeDeps);
|
|
1205
|
+
} catch (error) {
|
|
1206
|
+
// A stale browser cookie must not break the signed-out bootstrap.
|
|
1207
|
+
// Explicit workspace/bearer/external-actor requests fail closed.
|
|
1208
|
+
if (
|
|
1209
|
+
!(error instanceof HTTPException && error.status === 401) ||
|
|
1210
|
+
requestedWorkspaceId !== undefined ||
|
|
1211
|
+
c.req.header("authorization") !== undefined ||
|
|
1212
|
+
c.req.header("x-opengeni-actor-epoch") !== undefined ||
|
|
1213
|
+
c.req.header("x-opengeni-external-actor") !== undefined
|
|
1214
|
+
) {
|
|
1215
|
+
throw error;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
const workspaceId = requestedWorkspaceId ?? context?.defaultWorkspaceId;
|
|
1219
|
+
if (context && workspaceId) {
|
|
1220
|
+
const grant = await requireAccessGrant(c, routeDeps, workspaceId);
|
|
1221
|
+
await withAccessGrantSessionRlsContext(routeDeps, grant, async () => {
|
|
1222
|
+
catalogSettings = (
|
|
1223
|
+
await resolveWorkspaceCatalogSettings(deps.db, deps.settings, {
|
|
1224
|
+
accountId: grant.accountId,
|
|
1225
|
+
workspaceId,
|
|
1226
|
+
})
|
|
1227
|
+
).settings;
|
|
1228
|
+
selections = await resolveCallerWorkspaceModelSelections(
|
|
1229
|
+
deps.db,
|
|
1230
|
+
catalogSettings,
|
|
1231
|
+
{ accountId: grant.accountId, workspaceId, subjectId: grant.subjectId },
|
|
1232
|
+
{ observeAvailability: true },
|
|
1233
|
+
);
|
|
1234
|
+
if (!hasPermission(grant.permissions, "sessions:create")) {
|
|
1235
|
+
modelSelectionForbidden = true;
|
|
1236
|
+
selections = [];
|
|
1237
|
+
}
|
|
1238
|
+
const workspace = await getWorkspace(deps.db, workspaceId);
|
|
1239
|
+
defaultSelection = await resolveDefaultSessionModelForSelections(deps.db, {
|
|
1240
|
+
settings: catalogSettings,
|
|
1241
|
+
accountId: grant.accountId,
|
|
1242
|
+
workspaceSettings: workspace?.settings ?? {},
|
|
1243
|
+
selections,
|
|
1244
|
+
});
|
|
1245
|
+
});
|
|
1246
|
+
} else if (context) {
|
|
1247
|
+
// An organization key has no implicit workspace. Do not advertise
|
|
1248
|
+
// another workspace's authority; it can request an exact workspace.
|
|
1249
|
+
selections = [];
|
|
1250
|
+
modelSelectionForbidden = true;
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
const models = selections.filter(isWorkspaceModelAdmissible);
|
|
1254
|
+
const defaultModel = defaultSelection.model;
|
|
1255
|
+
const fallbackDefinition = selections.find(
|
|
1256
|
+
({ model }) => model.id === defaultModel || model.aliases.includes(defaultModel),
|
|
1257
|
+
);
|
|
1258
|
+
const legacyModelFallback =
|
|
1259
|
+
models.length === 0
|
|
1260
|
+
? {
|
|
1261
|
+
id: defaultModel,
|
|
1262
|
+
availability: {
|
|
1263
|
+
status: "unavailable" as const,
|
|
1264
|
+
selectable: false as const,
|
|
1265
|
+
reason: modelSelectionForbidden
|
|
1266
|
+
? ("policy_blocked" as const)
|
|
1267
|
+
: (fallbackDefinition?.availability.reason ?? ("unsupported" as const)),
|
|
1268
|
+
checkedAt: fallbackDefinition?.availability.checkedAt ?? null,
|
|
1269
|
+
},
|
|
1270
|
+
}
|
|
1271
|
+
: undefined;
|
|
486
1272
|
return c.json(
|
|
487
1273
|
ClientConfig.parse({
|
|
488
1274
|
deploymentRevision: deps.settings.deploymentRevision,
|
|
1275
|
+
claudeSubscriptionEnabled: deps.settings.claudeSubscriptionEnabled,
|
|
489
1276
|
apiContractRevision: OPENGENI_API_CONTRACT_REVISION,
|
|
490
1277
|
...(deps.settings.serverVersion ? { serverVersion: deps.settings.serverVersion } : {}),
|
|
491
|
-
defaultModel
|
|
492
|
-
allowedModels:
|
|
493
|
-
|
|
494
|
-
//
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
1278
|
+
defaultModel,
|
|
1279
|
+
allowedModels: models.length > 0 ? models.map(({ model }) => model.id) : [defaultModel],
|
|
1280
|
+
...(legacyModelFallback ? { legacyModelFallback } : {}),
|
|
1281
|
+
// Availability remains an observation hint, not the admission predicate.
|
|
1282
|
+
models: models.map(({ model, availability }) => ({
|
|
1283
|
+
...projectClientModel(model),
|
|
1284
|
+
availability,
|
|
1285
|
+
})),
|
|
1286
|
+
defaultReasoningEffort: defaultSelection.reasoningEffort,
|
|
499
1287
|
allowedReasoningEfforts: configuredAllowedReasoningEfforts(deps.settings),
|
|
1288
|
+
defaultSandboxBackend: deps.settings.sandboxBackend,
|
|
500
1289
|
mcpServers: deps.settings.mcpServers.map((server) => ({
|
|
501
1290
|
id: server.id,
|
|
502
1291
|
name: server.name ?? server.id,
|
|
503
1292
|
})),
|
|
504
1293
|
firstPartyMcpTools: resolveFirstPartyMcpToolPolicy(deps.settings),
|
|
1294
|
+
codeSearch: codeSearchDeploymentPolicy(deps.settings),
|
|
1295
|
+
agentConfig: clientAgentConfig(deps.settings),
|
|
505
1296
|
fileUploads: {
|
|
506
1297
|
enabled: objectStorage !== null,
|
|
507
1298
|
maxSizeBytes: objectStorage?.maxSinglePutSizeBytes ?? 5_000_000_000,
|
|
508
1299
|
},
|
|
509
1300
|
voiceInput: {
|
|
1301
|
+
providers: resolveVoiceInputProviderRegistry(deps.settings).map(
|
|
1302
|
+
(provider) => provider.id,
|
|
1303
|
+
),
|
|
510
1304
|
available: (await transcription?.available()) ?? false,
|
|
511
1305
|
maxDurationSeconds: deps.settings.voiceInputMaxDurationSeconds,
|
|
512
1306
|
maxSizeBytes: deps.settings.voiceInputMaxSizeBytes,
|
|
@@ -531,8 +1325,18 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
531
1325
|
: {}),
|
|
532
1326
|
},
|
|
533
1327
|
productAccessMode: deps.settings.productAccessMode,
|
|
534
|
-
|
|
1328
|
+
billingMode: deps.settings.billingMode,
|
|
1329
|
+
managedAuthSessionSetMode: deps.settings.managedAuthSessionSetMode,
|
|
1330
|
+
auth: clientAuthConfig(
|
|
1331
|
+
deps.settings,
|
|
1332
|
+
deps.settings.productAccessMode === "managed"
|
|
1333
|
+
? await managedAuthNewSignupsGate.signupsOpen()
|
|
1334
|
+
: true,
|
|
1335
|
+
),
|
|
1336
|
+
documentationUrl: deps.settings.documentationUrl,
|
|
535
1337
|
analytics: clientAnalyticsConfig(deps.settings),
|
|
1338
|
+
legal: clientLegalConfig(deps.settings),
|
|
1339
|
+
...(deps.settings.supportEmail ? { supportEmail: deps.settings.supportEmail } : {}),
|
|
536
1340
|
// Channel-A structured services (P4.4) ride exec/readFile/createEditor,
|
|
537
1341
|
// available on every real backend; `none` has no box so they are all off.
|
|
538
1342
|
// Per-session availability is still negotiated on /stream-capabilities.
|
|
@@ -541,6 +1345,67 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
541
1345
|
);
|
|
542
1346
|
});
|
|
543
1347
|
|
|
1348
|
+
// The organization MCP server: every public action, as a person who signed
|
|
1349
|
+
// in (capped by their connection's access setting) or as an organization
|
|
1350
|
+
// API key. Each action runs the real route in this process.
|
|
1351
|
+
app.all(ORGANIZATION_MCP_PATH, async (c) => {
|
|
1352
|
+
let boundedRequest: Request;
|
|
1353
|
+
try {
|
|
1354
|
+
boundedRequest = await boundedMcpRequest(c.req.raw);
|
|
1355
|
+
} catch (error) {
|
|
1356
|
+
if (error instanceof McpPayloadTooLargeError) {
|
|
1357
|
+
throw new HTTPException(413, { message: "MCP request body exceeds the safety limit" });
|
|
1358
|
+
}
|
|
1359
|
+
throw error;
|
|
1360
|
+
}
|
|
1361
|
+
const challenge = () => {
|
|
1362
|
+
if (deps.settings.mcpOauthEnabled) {
|
|
1363
|
+
c.header("www-authenticate", mcpOAuthAuthenticateHeader(routeDeps, ORGANIZATION_MCP_PATH));
|
|
1364
|
+
}
|
|
1365
|
+
};
|
|
1366
|
+
let caller: OrganizationMcpCaller;
|
|
1367
|
+
try {
|
|
1368
|
+
const connection = await resolveOrganizationMcpOAuthAccess(routeDeps, c.req.raw);
|
|
1369
|
+
if (connection) {
|
|
1370
|
+
caller = {
|
|
1371
|
+
kind: "person",
|
|
1372
|
+
accountId: connection.accountId,
|
|
1373
|
+
subjectId: connection.subjectId,
|
|
1374
|
+
access: connection.organizationAccess,
|
|
1375
|
+
};
|
|
1376
|
+
} else {
|
|
1377
|
+
const context = await requireAccessContext(c, routeDeps);
|
|
1378
|
+
const authorization = c.req.header("authorization");
|
|
1379
|
+
if (!accountScopedApiKeyWorkspaceAuthority(context) || !authorization) {
|
|
1380
|
+
throw new HTTPException(403, {
|
|
1381
|
+
message: "Connect with Opengeni sign-in or an organization API key.",
|
|
1382
|
+
});
|
|
1383
|
+
}
|
|
1384
|
+
caller = {
|
|
1385
|
+
kind: "key",
|
|
1386
|
+
authorization,
|
|
1387
|
+
accessKey: c.req.header("x-opengeni-access-key") ?? null,
|
|
1388
|
+
};
|
|
1389
|
+
}
|
|
1390
|
+
} catch (error) {
|
|
1391
|
+
if (error instanceof HTTPException && error.status === 401) challenge();
|
|
1392
|
+
throw error;
|
|
1393
|
+
}
|
|
1394
|
+
const transport = new WebStandardStreamableHTTPServerTransport({ enableJsonResponse: true });
|
|
1395
|
+
const mcp = buildOrganizationMcpServer({
|
|
1396
|
+
caller,
|
|
1397
|
+
origin: new URL(c.req.url).origin,
|
|
1398
|
+
dispatch: async (request) => await app.fetch(request, c.env),
|
|
1399
|
+
signal: c.req.raw.signal,
|
|
1400
|
+
});
|
|
1401
|
+
try {
|
|
1402
|
+
await mcp.connect(transport);
|
|
1403
|
+
return await handleMcpRequestWithClientAbort(transport, boundedRequest, c.req.raw.signal);
|
|
1404
|
+
} finally {
|
|
1405
|
+
await mcp.close().catch(() => undefined);
|
|
1406
|
+
}
|
|
1407
|
+
});
|
|
1408
|
+
|
|
544
1409
|
app.use("/v1/workspaces/:workspaceId/*", async (c, next) => {
|
|
545
1410
|
const workspaceId = c.req.param("workspaceId");
|
|
546
1411
|
if (workspaceRequestRequiresCodexAccountPrevalidation(c.req.raw)) {
|
|
@@ -559,6 +1424,27 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
559
1424
|
|
|
560
1425
|
app.all("/v1/workspaces/:workspaceId/mcp", async (c) => {
|
|
561
1426
|
const workspaceId = c.req.param("workspaceId");
|
|
1427
|
+
// This endpoint uses a fresh stateless JSON-response transport per POST;
|
|
1428
|
+
// there is no persistent server-to-client stream. Refuse GET only after
|
|
1429
|
+
// the existing OAuth/workspace/session authorization, before tool setup.
|
|
1430
|
+
const unsupportedGet = () => {
|
|
1431
|
+
const version = c.req.header("mcp-protocol-version");
|
|
1432
|
+
if (version && !SUPPORTED_PROTOCOL_VERSIONS.includes(version)) {
|
|
1433
|
+
return c.json(
|
|
1434
|
+
{
|
|
1435
|
+
jsonrpc: "2.0",
|
|
1436
|
+
id: null,
|
|
1437
|
+
error: { code: -32000, message: "Unsupported protocol version." },
|
|
1438
|
+
},
|
|
1439
|
+
400,
|
|
1440
|
+
);
|
|
1441
|
+
}
|
|
1442
|
+
return c.json(
|
|
1443
|
+
{ jsonrpc: "2.0", id: null, error: { code: -32000, message: "Method not allowed." } },
|
|
1444
|
+
405,
|
|
1445
|
+
{ allow: "POST" },
|
|
1446
|
+
);
|
|
1447
|
+
};
|
|
562
1448
|
let boundedRequest: Request;
|
|
563
1449
|
try {
|
|
564
1450
|
boundedRequest = await boundedMcpRequest(c.req.raw);
|
|
@@ -570,42 +1456,253 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
570
1456
|
}
|
|
571
1457
|
throw error;
|
|
572
1458
|
}
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
1459
|
+
let oauthAccess: Awaited<ReturnType<typeof resolveMcpOAuthRouteAccess>>;
|
|
1460
|
+
try {
|
|
1461
|
+
oauthAccess = await resolveMcpOAuthRouteAccess(routeDeps, c.req.raw, workspaceId);
|
|
1462
|
+
} catch (error) {
|
|
1463
|
+
if (error instanceof HTTPException && error.status === 401) {
|
|
1464
|
+
c.header(
|
|
1465
|
+
"www-authenticate",
|
|
1466
|
+
mcpOAuthAuthenticateHeader(routeDeps, new URL(c.req.url).pathname),
|
|
1467
|
+
);
|
|
1468
|
+
}
|
|
1469
|
+
throw error;
|
|
1470
|
+
}
|
|
1471
|
+
if (oauthAccess) {
|
|
1472
|
+
return await withAccessGrantSessionRlsContext(routeDeps, oauthAccess.grant, async () => {
|
|
1473
|
+
if (c.req.method === "GET") return unsupportedGet();
|
|
1474
|
+
const prepared = await prepareMcpOAuthWorkspaceToolGateway(
|
|
1475
|
+
routeDeps,
|
|
1476
|
+
oauthAccess.grant,
|
|
1477
|
+
oauthAccess.allowedToolIdentities,
|
|
1478
|
+
);
|
|
1479
|
+
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
1480
|
+
enableJsonResponse: true,
|
|
1481
|
+
});
|
|
1482
|
+
const mcp = buildWorkspaceToolGatewayMcpServer(
|
|
1483
|
+
prepared,
|
|
1484
|
+
oauthAccess.grant,
|
|
1485
|
+
routeDeps.observability,
|
|
1486
|
+
);
|
|
577
1487
|
try {
|
|
578
|
-
await
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
1488
|
+
await mcp.connect(transport);
|
|
1489
|
+
return await handleMcpRequestWithClientAbort(transport, boundedRequest, c.req.raw.signal);
|
|
1490
|
+
} finally {
|
|
1491
|
+
await Promise.allSettled([mcp.close(), prepared.close()]);
|
|
1492
|
+
}
|
|
1493
|
+
});
|
|
1494
|
+
}
|
|
1495
|
+
let authorization: Awaited<ReturnType<typeof requireMcpAccessGrantAuthorization>>;
|
|
1496
|
+
try {
|
|
1497
|
+
authorization = await requireMcpAccessGrantAuthorization(c, routeDeps, workspaceId);
|
|
1498
|
+
} catch (error) {
|
|
1499
|
+
if (deps.settings.mcpOauthEnabled && error instanceof HTTPException && error.status === 401) {
|
|
1500
|
+
c.header(
|
|
1501
|
+
"www-authenticate",
|
|
1502
|
+
mcpOAuthAuthenticateHeader(routeDeps, new URL(c.req.url).pathname),
|
|
1503
|
+
);
|
|
1504
|
+
}
|
|
1505
|
+
throw error;
|
|
1506
|
+
}
|
|
1507
|
+
const grant = authorization.grant;
|
|
1508
|
+
// The agent-attempt context, this route check, and a tool's own entry check
|
|
1509
|
+
// re-read the same caller session and attempt; share those reads.
|
|
1510
|
+
return await withSessionAuthorizationReadReuse((reads) =>
|
|
1511
|
+
withAccessGrantSessionRlsContext(routeDeps, grant, async () => {
|
|
1512
|
+
const boundSessionId = grant.metadata?.sessionId;
|
|
1513
|
+
if (typeof boundSessionId === "string") {
|
|
1514
|
+
try {
|
|
1515
|
+
await requireSessionAuthorization(routeDeps, grant, {
|
|
1516
|
+
sessionId: boundSessionId,
|
|
1517
|
+
operation: "session.first_party_mcp.call",
|
|
1518
|
+
surface: "first_party_mcp",
|
|
590
1519
|
});
|
|
1520
|
+
} catch (error) {
|
|
1521
|
+
if (error instanceof SessionAuthorizationDeniedError) {
|
|
1522
|
+
throw new HTTPException(404, { message: "session not found" });
|
|
1523
|
+
}
|
|
1524
|
+
if (error instanceof SessionAuthorizationUnavailableError) {
|
|
1525
|
+
throw new HTTPException(503, {
|
|
1526
|
+
message: "session authorization is unavailable",
|
|
1527
|
+
});
|
|
1528
|
+
}
|
|
1529
|
+
throw error;
|
|
591
1530
|
}
|
|
592
|
-
throw error;
|
|
593
1531
|
}
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
1532
|
+
if (c.req.method === "GET") return unsupportedGet();
|
|
1533
|
+
reads.handOffToToolDispatch();
|
|
1534
|
+
const workspace = await getWorkspace(routeDeps.db, workspaceId);
|
|
1535
|
+
const workspaceMemoryEnabled = resolveWorkspaceMemoryEnabled(workspace?.settings);
|
|
1536
|
+
const workspaceMemoryPromptMode = resolveWorkspaceMemoryPromptMode();
|
|
1537
|
+
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
1538
|
+
enableJsonResponse: true,
|
|
1539
|
+
});
|
|
1540
|
+
if (!grantUsesAttemptScopedMcp(grant)) {
|
|
1541
|
+
const prepared = await prepareWorkspaceToolGateway(routeDeps, authorization);
|
|
1542
|
+
const mcp = buildWorkspaceToolGatewayMcpServer(prepared, grant, routeDeps.observability);
|
|
1543
|
+
try {
|
|
1544
|
+
await mcp.connect(transport);
|
|
1545
|
+
return await handleMcpRequestWithClientAbort(
|
|
1546
|
+
transport,
|
|
1547
|
+
boundedRequest,
|
|
1548
|
+
c.req.raw.signal,
|
|
1549
|
+
);
|
|
1550
|
+
} finally {
|
|
1551
|
+
await Promise.allSettled([mcp.close(), prepared.close()]);
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
const mcpDeps = await resolveWorkspaceMcpRouteDeps(routeDeps, grant);
|
|
1555
|
+
// The bound session's frozen Memory selector (migration 0427) decides
|
|
1556
|
+
// which Memory tools the attempt receives and which typed layers they
|
|
1557
|
+
// read and write. A missing row resolves to no Memory tools.
|
|
1558
|
+
const sessionMemory =
|
|
1559
|
+
typeof boundSessionId === "string"
|
|
1560
|
+
? ((await resolveSessionMemoryAgentScope(
|
|
1561
|
+
routeDeps.db,
|
|
1562
|
+
workspaceId,
|
|
1563
|
+
boundSessionId,
|
|
1564
|
+
grant.metadata,
|
|
1565
|
+
)) ?? {
|
|
1566
|
+
mode: "off" as const,
|
|
1567
|
+
userSubjectId: null,
|
|
1568
|
+
rootSessionId: null,
|
|
1569
|
+
})
|
|
1570
|
+
: null;
|
|
1571
|
+
const mcp = buildOpenGeniMcpServer(mcpDeps, grant, {
|
|
1572
|
+
requestOrigin: new URL(c.req.url).origin,
|
|
1573
|
+
workspaceMemoryEnabled,
|
|
1574
|
+
workspaceMemoryPromptMode,
|
|
1575
|
+
sessionMemory,
|
|
1576
|
+
});
|
|
1577
|
+
// Bind tool handlers' `extra.signal` to the HTTP client's connection: a
|
|
1578
|
+
// worker that drops the call (Steer/Pause) aborts a blocking tool here.
|
|
1579
|
+
// Each POST builds a fresh server; close it once the JSON response is
|
|
1580
|
+
// ready, like the gateway paths above, so it can't outlive the request.
|
|
1581
|
+
try {
|
|
1582
|
+
await mcp.connect(transport);
|
|
1583
|
+
return await handleMcpRequestWithClientAbort(transport, boundedRequest, c.req.raw.signal);
|
|
1584
|
+
} finally {
|
|
1585
|
+
await mcp.close().catch(() => undefined);
|
|
1586
|
+
}
|
|
1587
|
+
}),
|
|
1588
|
+
);
|
|
1589
|
+
});
|
|
1590
|
+
|
|
1591
|
+
app.get("/v1/workspaces/:workspaceId/tools/catalog", async (c) => {
|
|
1592
|
+
const workspaceId = c.req.param("workspaceId");
|
|
1593
|
+
const authorization = await requireAccessGrantAuthorization(
|
|
1594
|
+
c,
|
|
1595
|
+
routeDeps,
|
|
1596
|
+
workspaceId,
|
|
1597
|
+
"workspace:read",
|
|
1598
|
+
);
|
|
1599
|
+
const prepared = await prepareWorkspaceToolGateway(routeDeps, authorization);
|
|
1600
|
+
try {
|
|
1601
|
+
c.header("cache-control", "no-store");
|
|
1602
|
+
return c.json(prepared.toolGatewayCatalog);
|
|
1603
|
+
} finally {
|
|
1604
|
+
await prepared.close();
|
|
1605
|
+
}
|
|
1606
|
+
});
|
|
1607
|
+
|
|
1608
|
+
app.post("/v1/workspaces/:workspaceId/tools/calls", async (c) => {
|
|
1609
|
+
const workspaceId = c.req.param("workspaceId");
|
|
1610
|
+
const authorization = await requireAccessGrantAuthorization(
|
|
1611
|
+
c,
|
|
1612
|
+
routeDeps,
|
|
1613
|
+
workspaceId,
|
|
1614
|
+
"workspace:read",
|
|
1615
|
+
);
|
|
1616
|
+
const grant = authorization.grant;
|
|
1617
|
+
const parsed = ToolGatewayCallRequest.safeParse(await c.req.json().catch(() => null));
|
|
1618
|
+
if (!parsed.success) {
|
|
1619
|
+
throw new HTTPException(400, { message: "Invalid tool gateway call" });
|
|
1620
|
+
}
|
|
1621
|
+
const prepared = await prepareWorkspaceToolGateway(routeDeps, authorization);
|
|
1622
|
+
try {
|
|
1623
|
+
return c.json(
|
|
1624
|
+
await callWorkspaceToolGateway(
|
|
1625
|
+
prepared,
|
|
1626
|
+
grant,
|
|
1627
|
+
parsed.data,
|
|
1628
|
+
routeDeps.db,
|
|
1629
|
+
undefined,
|
|
1630
|
+
routeDeps.observability,
|
|
1631
|
+
),
|
|
1632
|
+
);
|
|
1633
|
+
} finally {
|
|
1634
|
+
await prepared.close();
|
|
1635
|
+
}
|
|
1636
|
+
});
|
|
1637
|
+
|
|
1638
|
+
app.post("/v1/workspaces/:workspaceId/tools/approvals", async (c) => {
|
|
1639
|
+
const workspaceId = c.req.param("workspaceId");
|
|
1640
|
+
const authorization = await requireAccessGrantAuthorization(
|
|
1641
|
+
c,
|
|
1642
|
+
routeDeps,
|
|
1643
|
+
workspaceId,
|
|
1644
|
+
"workspace:read",
|
|
1645
|
+
);
|
|
1646
|
+
const parsed = ToolGatewayApprovalRequest.safeParse(await c.req.json().catch(() => null));
|
|
1647
|
+
if (!parsed.success) {
|
|
1648
|
+
throw new HTTPException(400, { message: "Invalid tool gateway approval" });
|
|
1649
|
+
}
|
|
1650
|
+
const prepared = await prepareWorkspaceToolGateway(routeDeps, authorization);
|
|
1651
|
+
try {
|
|
1652
|
+
return c.json(
|
|
1653
|
+
await approveWorkspaceToolGatewayCall(
|
|
1654
|
+
prepared,
|
|
1655
|
+
authorization.grant,
|
|
1656
|
+
routeDeps.db,
|
|
1657
|
+
parsed.data,
|
|
1658
|
+
undefined,
|
|
1659
|
+
routeDeps.observability,
|
|
1660
|
+
),
|
|
1661
|
+
201,
|
|
1662
|
+
);
|
|
1663
|
+
} finally {
|
|
1664
|
+
await prepared.close();
|
|
1665
|
+
}
|
|
1666
|
+
});
|
|
1667
|
+
|
|
1668
|
+
app.get("/v1/workspaces/:workspaceId/tools/declarations", async (c) => {
|
|
1669
|
+
const workspaceId = c.req.param("workspaceId");
|
|
1670
|
+
const authorization = await requireAccessGrantAuthorization(
|
|
1671
|
+
c,
|
|
1672
|
+
routeDeps,
|
|
1673
|
+
workspaceId,
|
|
1674
|
+
"workspace:read",
|
|
1675
|
+
);
|
|
1676
|
+
const prepared = await prepareWorkspaceToolGateway(routeDeps, authorization);
|
|
1677
|
+
try {
|
|
1678
|
+
c.header("cache-control", "no-store");
|
|
1679
|
+
return c.json(workspaceToolGatewayDeclarations(prepared));
|
|
1680
|
+
} finally {
|
|
1681
|
+
await prepared.close();
|
|
1682
|
+
}
|
|
1683
|
+
});
|
|
1684
|
+
|
|
1685
|
+
app.all("/v1/workspaces/:workspaceId/codemode/sdk/*", async (c) => {
|
|
1686
|
+
const workspaceId = c.req.param("workspaceId");
|
|
1687
|
+
const grant = await requireAccessGrant(c, routeDeps, workspaceId);
|
|
1688
|
+
const url = new URL(c.req.url);
|
|
1689
|
+
const prefix = `/v1/workspaces/${workspaceId}/codemode/sdk`;
|
|
1690
|
+
let forwarded: Request;
|
|
1691
|
+
try {
|
|
1692
|
+
const path = url.pathname.slice(prefix.length) + url.search;
|
|
1693
|
+
forwarded = await codemodeSessionRequest(routeDeps, grant, c.req.raw, path, (session) => {
|
|
1694
|
+
assertConfiguredCodemodeSessionProxyPath(session, path, c.req.method);
|
|
1695
|
+
const proxyTools = configuredCodemodeSessionProxyTools(routeDeps.settings, session);
|
|
1696
|
+
return proxyTools === null
|
|
1697
|
+
? routeDeps.settings
|
|
1698
|
+
: { ...routeDeps.settings, allowedFirstPartyMcpTools: proxyTools };
|
|
605
1699
|
});
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
}
|
|
1700
|
+
} catch (error) {
|
|
1701
|
+
throw codemodeHttpError(error);
|
|
1702
|
+
}
|
|
1703
|
+
// The forwarded request drops the caller's Host and names a non-sandbox
|
|
1704
|
+
// path; the request it was built from already passed the local boundary.
|
|
1705
|
+
return app.fetch(markLocalInternalDispatch(forwarded));
|
|
609
1706
|
});
|
|
610
1707
|
|
|
611
1708
|
app.get("/v1/workspaces/:workspaceId/codemode/catalog", async (c) => {
|
|
@@ -661,27 +1758,42 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
661
1758
|
}
|
|
662
1759
|
});
|
|
663
1760
|
|
|
1761
|
+
registerConnectCallbackReturns(app, routeDeps);
|
|
664
1762
|
registerFileRoutes(app, routeDeps);
|
|
1763
|
+
registerSessionArtifactAssociationRoutes(app, routeDeps);
|
|
665
1764
|
registerApiKeyRoutes(app, routeDeps);
|
|
1765
|
+
registerOrganizationMcpConnectionRoutes(app, routeDeps);
|
|
1766
|
+
registerOrganizationServiceAccountRoutes(app, routeDeps);
|
|
666
1767
|
registerBillingRoutes(app, routeDeps);
|
|
667
1768
|
registerBrowserIdentityRoutes(app, routeDeps);
|
|
668
1769
|
registerBrowserSessionRoutes(app, routeDeps);
|
|
669
1770
|
registerComputerSessionRoutes(app, routeDeps);
|
|
670
1771
|
registerDocumentRoutes(app, routeDeps);
|
|
1772
|
+
registerKnowledgeRoutes(app, routeDeps);
|
|
671
1773
|
registerGitHubRoutes(app, routeDeps);
|
|
672
1774
|
registerInstallRoutes(app, routeDeps);
|
|
673
1775
|
registerInteractionResourceRoutes(app, routeDeps);
|
|
674
1776
|
registerWorkspaceRoutes(app, routeDeps);
|
|
1777
|
+
registerUsageAllowanceRoutes(app, routeDeps);
|
|
675
1778
|
registerInsightsRoutes(app, routeDeps);
|
|
1779
|
+
registerInsightsUsageRoutes(app, routeDeps);
|
|
676
1780
|
registerWorkspaceInstructionPolicyRoutes(app, routeDeps);
|
|
1781
|
+
registerWorkspaceLearningRoutes(app, routeDeps);
|
|
677
1782
|
registerCompanyProfileRoutes(app, routeDeps);
|
|
1783
|
+
registerCompanyBrainRoutes(app, routeDeps);
|
|
678
1784
|
registerSlackTaskPolicyRoutes(app, routeDeps);
|
|
679
1785
|
registerWorkspaceStateRoutes(app, routeDeps);
|
|
680
1786
|
registerMemorySlackPublicationRoutes(app, routeDeps);
|
|
681
1787
|
registerWorkspaceArtifactRoutes(app, routeDeps);
|
|
1788
|
+
registerArtifactCatalogRoutes(app, routeDeps);
|
|
682
1789
|
registerPreferenceRegistryRoutes(app, routeDeps);
|
|
683
1790
|
registerSocialRoutes(app, routeDeps);
|
|
1791
|
+
registerPersonalGitHubRoutes(app, routeDeps);
|
|
1792
|
+
registerPersonalGitHubGitBrokerRoutes(app, routeDeps);
|
|
684
1793
|
registerConnectionRoutes(app, routeDeps);
|
|
1794
|
+
registerConnectRoutes(app, routeDeps);
|
|
1795
|
+
|
|
1796
|
+
registerExternalIdentityLinkRoutes(app, routeDeps);
|
|
685
1797
|
registerCapabilityRoutes(app, routeDeps);
|
|
686
1798
|
registerApiIntegrationRoutes(app, routeDeps);
|
|
687
1799
|
registerIntegrationFacetRoutes(app, routeDeps);
|
|
@@ -691,23 +1803,37 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
691
1803
|
registerEnvironmentRoutes(app, routeDeps);
|
|
692
1804
|
registerChannelRoutes(app, routeDeps);
|
|
693
1805
|
registerRigRoutes(app, routeDeps);
|
|
694
|
-
|
|
1806
|
+
registerAutomationRoutes(app, routeDeps);
|
|
1807
|
+
registerPrReviewRoutes(app, routeDeps);
|
|
695
1808
|
registerPluginRoutes(app, routeDeps);
|
|
696
1809
|
registerSkillRoutes(app, routeDeps);
|
|
697
1810
|
registerSessionRoutes(app, routeDeps);
|
|
1811
|
+
registerSessionHistoryImportRoutes(app, routeDeps);
|
|
1812
|
+
registerFeedbackRoutes(app, routeDeps);
|
|
1813
|
+
registerWorkspaceIntegrationRoutes(app, routeDeps);
|
|
1814
|
+
registerOrganizationIntegrationRoutes(app, routeDeps);
|
|
698
1815
|
registerScheduledTaskRoutes(app, routeDeps);
|
|
699
1816
|
registerCodexRoutes(app, routeDeps);
|
|
1817
|
+
registerOrganizationModelProviderRoutes(app, routeDeps);
|
|
1818
|
+
registerWorkspaceModelProviderRoutes(app, routeDeps);
|
|
1819
|
+
registerClaudeSubscriptionOAuthRoutes(app, routeDeps);
|
|
1820
|
+
registerOrganizationIntegrationPolicyRoutes(app, routeDeps);
|
|
1821
|
+
registerModelConnectionAccessRoutes(app, routeDeps);
|
|
700
1822
|
registerSuperGrokRoutes(app, routeDeps);
|
|
1823
|
+
registerClaudeSubscriptionAccountRoutes(app, routeDeps);
|
|
701
1824
|
registerTranscriptionRoutes(app, routeDeps);
|
|
702
1825
|
registerEditableArtifactRoutes(app, routeDeps);
|
|
703
1826
|
registerVideoGenerationRoutes(app, routeDeps);
|
|
704
1827
|
registerCanonicalHumanIdentityRoutes(app, routeDeps);
|
|
705
1828
|
registerOrganizationMembershipRoutes(app, routeDeps);
|
|
1829
|
+
registerOrganizationSessionRoutes(app, routeDeps);
|
|
1830
|
+
registerOrganizationRecoveryRoutes(app, routeDeps);
|
|
1831
|
+
registerUserResourceAuthorityRoutes(app, routeDeps);
|
|
706
1832
|
registerSlackInteractionRoutes(app, routeDeps);
|
|
707
1833
|
|
|
708
1834
|
app.notFound((c) => {
|
|
709
1835
|
if (!new URL(c.req.url).pathname.startsWith("/v1/")) return c.text("Not Found", 404);
|
|
710
|
-
const requestId = correlationIds.get(c
|
|
1836
|
+
const requestId = correlationIds.get(c) ?? crypto.randomUUID();
|
|
711
1837
|
return c.json(
|
|
712
1838
|
ErrorEnvelope.parse({
|
|
713
1839
|
error: {
|
|
@@ -722,18 +1848,49 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
722
1848
|
);
|
|
723
1849
|
});
|
|
724
1850
|
|
|
725
|
-
app.onError((
|
|
1851
|
+
app.onError((rawError, c) => {
|
|
1852
|
+
const requestId = correlationIds.get(c) ?? crypto.randomUUID();
|
|
1853
|
+
c.header(OPENGENI_CORRELATION_HEADER, requestId);
|
|
1854
|
+
if (new URL(c.req.url).pathname.startsWith("/v1/")) {
|
|
1855
|
+
c.header(OPENGENI_API_CONTRACT_HEADER, OPENGENI_API_CONTRACT_REVISION);
|
|
1856
|
+
}
|
|
1857
|
+
// Archives remain read-only through the existing Send/Steer/control paths.
|
|
1858
|
+
const importErrorResponse = archivedSessionImportErrorResponse(c, rawError);
|
|
1859
|
+
if (importErrorResponse) return importErrorResponse;
|
|
1860
|
+
// One central mapping for every Send/Steer/control route: a bounded
|
|
1861
|
+
// control-prefix wait that expired is a known, retryable, not-applied 503.
|
|
1862
|
+
const error =
|
|
1863
|
+
rawError instanceof OrganizationIntegrationDeniedError
|
|
1864
|
+
? new HTTPException(403, { message: rawError.message })
|
|
1865
|
+
: (allowanceExhaustedHttpError(rawError) ??
|
|
1866
|
+
workspaceControlBusyHttpError(rawError) ??
|
|
1867
|
+
scheduledTaskTargetAccessHttpError(rawError) ??
|
|
1868
|
+
agentConfigHttpError(rawError) ??
|
|
1869
|
+
modelUnavailableHttpError(rawError) ??
|
|
1870
|
+
(rawError instanceof UnsupportedLatencyModeError
|
|
1871
|
+
? new ApiHttpError(422, {
|
|
1872
|
+
code: "validation_failed",
|
|
1873
|
+
message: rawError.message,
|
|
1874
|
+
retryable: false,
|
|
1875
|
+
outcomeUnknown: false,
|
|
1876
|
+
details: {
|
|
1877
|
+
code: rawError.code,
|
|
1878
|
+
modelId: rawError.modelId,
|
|
1879
|
+
latencyMode: rawError.latencyMode,
|
|
1880
|
+
allowedLatencyModes: [...rawError.allowedLatencyModes],
|
|
1881
|
+
},
|
|
1882
|
+
})
|
|
1883
|
+
: null) ??
|
|
1884
|
+
requestBodyValidationHttpError(rawError) ??
|
|
1885
|
+
invalidPathIdentifierHttpError(rawError, new URL(c.req.url).pathname) ??
|
|
1886
|
+
rawError);
|
|
726
1887
|
const compactionLock = codexCompactionV2ProviderLockedError(error);
|
|
727
1888
|
const apiError = error instanceof ApiHttpError ? error : null;
|
|
728
1889
|
const status = compactionLock ? 422 : httpStatusForError(error);
|
|
729
1890
|
const code: ErrorCode = compactionLock
|
|
730
1891
|
? compactionLock.code
|
|
731
1892
|
: (apiError?.code ?? errorCodeForStatus(status));
|
|
732
|
-
|
|
733
|
-
c.header(OPENGENI_CORRELATION_HEADER, requestId);
|
|
734
|
-
if (new URL(c.req.url).pathname.startsWith("/v1/")) {
|
|
735
|
-
c.header(OPENGENI_API_CONTRACT_HEADER, OPENGENI_API_CONTRACT_REVISION);
|
|
736
|
-
}
|
|
1893
|
+
if (status >= 500) logHttpFailure(c, status, code, rawError);
|
|
737
1894
|
const envelope = ErrorEnvelope.parse({
|
|
738
1895
|
error: {
|
|
739
1896
|
status,
|
|
@@ -744,7 +1901,9 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
744
1901
|
? (boundedPublicMessage(apiError.message) ?? "Request failed.")
|
|
745
1902
|
: publicErrorMessage(error, status),
|
|
746
1903
|
retryable: apiError?.retryable ?? retryableHttpStatus(status),
|
|
747
|
-
...(mutationOutcomeUnknown(error, c.req.method)
|
|
1904
|
+
...((apiError?.outcomeUnknown ?? mutationOutcomeUnknown(error, c.req.method))
|
|
1905
|
+
? { outcomeUnknown: true }
|
|
1906
|
+
: {}),
|
|
748
1907
|
requestId,
|
|
749
1908
|
...(apiError?.details ? { details: apiError.details } : {}),
|
|
750
1909
|
},
|
|
@@ -755,6 +1914,11 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
755
1914
|
return { app, routeDeps };
|
|
756
1915
|
}
|
|
757
1916
|
|
|
1917
|
+
function managedAuthOAuthCallbackProvider(pathname: string): "google" | "github" | null {
|
|
1918
|
+
const match = pathname.match(/^\/v1\/auth\/callback\/(google|github)$/u);
|
|
1919
|
+
return match?.[1] === "google" || match?.[1] === "github" ? match[1] : null;
|
|
1920
|
+
}
|
|
1921
|
+
|
|
758
1922
|
function mutationOutcomeUnknown(error: unknown, method: string): boolean {
|
|
759
1923
|
if (method === "GET" || method === "HEAD" || method === "OPTIONS") return false;
|
|
760
1924
|
const cause = error instanceof HTTPException ? error.cause : error;
|
|
@@ -781,7 +1945,41 @@ const publicWorkspaceBrowserRoutePatterns = [
|
|
|
781
1945
|
] as const;
|
|
782
1946
|
|
|
783
1947
|
export function workspaceActorContextExempt(method: string, pathname: string): boolean {
|
|
784
|
-
|
|
1948
|
+
// The account-scoped provisioning route shares the workspace collection prefix.
|
|
1949
|
+
// Hono's trailing wildcard also matches it, but "external" is not a workspace UUID;
|
|
1950
|
+
// the route performs its own organization API-key authorization.
|
|
1951
|
+
if (method === "PUT" && pathname === "/v1/workspaces/external") return true;
|
|
1952
|
+
// Import adapters apply their own exact create/control gates and request-local
|
|
1953
|
+
// RLS in core. Tenant mirrors first resolve the organization-local mapping;
|
|
1954
|
+
// "external" itself is not a native workspace id.
|
|
1955
|
+
if (isSessionHistoryImportRequest(method, pathname)) return true;
|
|
1956
|
+
// Organization budget authority is independent of ordinary workspace
|
|
1957
|
+
// membership; these routes apply their own exact account/workspace gate.
|
|
1958
|
+
if (
|
|
1959
|
+
(["GET", "PUT", "DELETE"].includes(method) &&
|
|
1960
|
+
/^\/v1\/workspaces\/[^/]+\/allowance$/.test(pathname)) ||
|
|
1961
|
+
(method === "GET" && /^\/v1\/workspaces\/[^/]+\/allowance\/state$/.test(pathname)) ||
|
|
1962
|
+
(method === "POST" && /^\/v1\/workspaces\/[^/]+\/allowance\/grants$/.test(pathname))
|
|
1963
|
+
)
|
|
1964
|
+
return true;
|
|
1965
|
+
// Allowance tenant mirrors resolve the workspace by exact organization-local
|
|
1966
|
+
// external identity before applying the ordinary workspace authorization.
|
|
1967
|
+
if (
|
|
1968
|
+
(["GET", "PUT", "DELETE"].includes(method) &&
|
|
1969
|
+
/^\/v1\/workspaces\/external\/[^/]+\/[^/]+\/allowance$/.test(pathname)) ||
|
|
1970
|
+
(method === "GET" &&
|
|
1971
|
+
/^\/v1\/workspaces\/external\/[^/]+\/[^/]+\/allowance\/state$/.test(pathname)) ||
|
|
1972
|
+
(method === "POST" &&
|
|
1973
|
+
/^\/v1\/workspaces\/external\/[^/]+\/[^/]+\/allowance\/grants$/.test(pathname)) ||
|
|
1974
|
+
(method === "GET" &&
|
|
1975
|
+
/^\/v1\/workspaces\/external\/[^/]+\/[^/]+\/usage(?:\/me)?$/.test(pathname)) ||
|
|
1976
|
+
(method === "PUT" &&
|
|
1977
|
+
/^\/v1\/workspaces\/external\/[^/]+\/[^/]+\/members\/(?:[^/]+|external\/[^/]+\/[^/]+)\/allowance$/.test(
|
|
1978
|
+
pathname,
|
|
1979
|
+
))
|
|
1980
|
+
)
|
|
1981
|
+
return true;
|
|
1982
|
+
if (/^\/v1\/workspaces\/[^/]+\/mcp(?:\/(?:docs|files))?$/.test(pathname)) return true;
|
|
785
1983
|
if (
|
|
786
1984
|
method === "GET" &&
|
|
787
1985
|
publicWorkspaceBrowserRoutePatterns.some((route) => route.test(pathname))
|
|
@@ -810,46 +2008,15 @@ async function validateCodexAccountTarget(request: Request): Promise<void> {
|
|
|
810
2008
|
}
|
|
811
2009
|
}
|
|
812
2010
|
|
|
813
|
-
async function
|
|
814
|
-
deps: ApiRouteDeps,
|
|
815
|
-
grant: AccessGrant,
|
|
816
|
-
fn: () => Promise<T>,
|
|
817
|
-
): Promise<T> {
|
|
818
|
-
if (grant.principalKind !== "agent_attempt") {
|
|
819
|
-
return await withSessionRlsActorContext({ subjectId: grant.subjectId }, fn);
|
|
820
|
-
}
|
|
821
|
-
const callerSessionId = grant.metadata?.sessionId;
|
|
822
|
-
if (typeof callerSessionId !== "string") {
|
|
823
|
-
throw new HTTPException(403, { message: "agent attempt authority is invalid" });
|
|
824
|
-
}
|
|
825
|
-
try {
|
|
826
|
-
const actor = await requireLiveAgentAttemptAuthorization(deps.db, grant, callerSessionId);
|
|
827
|
-
return await withSessionRlsActorContext(
|
|
828
|
-
{
|
|
829
|
-
subjectId: actor.subjectId,
|
|
830
|
-
initiatingHumanSubjectId: actor.initiatingHumanSubjectId,
|
|
831
|
-
},
|
|
832
|
-
fn,
|
|
833
|
-
);
|
|
834
|
-
} catch (error) {
|
|
835
|
-
if (error instanceof SessionAuthorizationDeniedError) {
|
|
836
|
-
throw new HTTPException(403, {
|
|
837
|
-
message: "agent attempt authority is invalid",
|
|
838
|
-
cause: error,
|
|
839
|
-
});
|
|
840
|
-
}
|
|
841
|
-
throw error;
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
async function requireMcpAccessGrant(
|
|
2011
|
+
async function requireMcpAccessGrantAuthorization(
|
|
846
2012
|
c: Parameters<typeof requireAccessGrant>[0],
|
|
847
2013
|
deps: ApiRouteDeps,
|
|
848
2014
|
workspaceId: string,
|
|
849
|
-
): Promise<
|
|
850
|
-
const
|
|
2015
|
+
): Promise<Awaited<ReturnType<typeof requireAccessGrantAuthorization>>> {
|
|
2016
|
+
const authorization = await requireAccessGrantAuthorization(c, deps, workspaceId);
|
|
2017
|
+
const grant = authorization.grant;
|
|
851
2018
|
if (hasPermission(grant.permissions, "workspace:read")) {
|
|
852
|
-
return
|
|
2019
|
+
return authorization;
|
|
853
2020
|
}
|
|
854
2021
|
if (isCodemodeGrant(grant)) {
|
|
855
2022
|
requirePermission(grant, "workspace:read");
|
|
@@ -859,15 +2026,28 @@ async function requireMcpAccessGrant(
|
|
|
859
2026
|
// authorization seam runs immediately after this gate, and tool registration
|
|
860
2027
|
// still exposes only capabilities permitted by the delegated grant.
|
|
861
2028
|
if (grant.metadata?.delegated === true && typeof grant.metadata.sessionId === "string") {
|
|
862
|
-
return
|
|
2029
|
+
return authorization;
|
|
863
2030
|
}
|
|
864
2031
|
requirePermission(grant, "workspace:read");
|
|
865
|
-
return
|
|
2032
|
+
return authorization;
|
|
866
2033
|
}
|
|
867
2034
|
|
|
868
|
-
function clientAuthConfig(settings: AppDependencies["settings"]) {
|
|
2035
|
+
function clientAuthConfig(settings: AppDependencies["settings"], newSignupsEnabled: boolean) {
|
|
869
2036
|
if (settings.productAccessMode === "managed") {
|
|
870
|
-
return {
|
|
2037
|
+
return {
|
|
2038
|
+
mode: "managedSession" as const,
|
|
2039
|
+
session: "cookie" as const,
|
|
2040
|
+
emailVerificationRequired: settings.environment !== "local",
|
|
2041
|
+
newSignupsEnabled,
|
|
2042
|
+
socialProviders: [
|
|
2043
|
+
...(settings.managedAuthGoogleClientId && settings.managedAuthGoogleClientSecret
|
|
2044
|
+
? (["google"] as const)
|
|
2045
|
+
: []),
|
|
2046
|
+
...(settings.managedAuthGithubClientId && settings.managedAuthGithubClientSecret
|
|
2047
|
+
? (["github"] as const)
|
|
2048
|
+
: []),
|
|
2049
|
+
],
|
|
2050
|
+
};
|
|
871
2051
|
}
|
|
872
2052
|
if (settings.productAccessMode === "configured") {
|
|
873
2053
|
return {
|
|
@@ -885,7 +2065,143 @@ function clientAuthConfig(settings: AppDependencies["settings"]) {
|
|
|
885
2065
|
return { mode: "none" as const };
|
|
886
2066
|
}
|
|
887
2067
|
|
|
2068
|
+
/** Configured SDK credentials cannot regain tools from widened legacy columns. */
|
|
2069
|
+
export function configuredCodemodeSessionProxyTools(
|
|
2070
|
+
settings: Pick<Settings, "defaultFirstPartyMcpTools" | "allowedFirstPartyMcpTools">,
|
|
2071
|
+
session: Pick<Session, "agent">,
|
|
2072
|
+
): FirstPartyMcpToolName[] | null {
|
|
2073
|
+
if (!session.agent) return null;
|
|
2074
|
+
const families = resolveAgentToolFamilies(session.agent);
|
|
2075
|
+
return resolveFirstPartyMcpToolPolicy(settings).allowed.filter((tool) =>
|
|
2076
|
+
families.allowsFirstPartyTool(tool),
|
|
2077
|
+
);
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
/**
|
|
2081
|
+
* Permissions span capabilities: project_create grants sessions:create,
|
|
2082
|
+
* and runtime titling grants sessions:control. Fence the endpoint family
|
|
2083
|
+
* too, without changing null-config routing or ordinary REST authorization.
|
|
2084
|
+
*/
|
|
2085
|
+
export function assertConfiguredCodemodeSessionProxyPath(
|
|
2086
|
+
session: Pick<Session, "id" | "agent">,
|
|
2087
|
+
path: string,
|
|
2088
|
+
method: string,
|
|
2089
|
+
): void {
|
|
2090
|
+
if (!session.agent) return;
|
|
2091
|
+
const families = resolveAgentToolFamilies(session.agent);
|
|
2092
|
+
const pathname = new URL(path, "http://codemode.invalid").pathname;
|
|
2093
|
+
const segments = pathname.split("/").filter(Boolean);
|
|
2094
|
+
const verb = method.toUpperCase();
|
|
2095
|
+
const read = verb === "GET" || verb === "HEAD";
|
|
2096
|
+
const allows = (tool: FirstPartyMcpToolName) => families.allowsFirstPartyTool(tool);
|
|
2097
|
+
let allowed = false;
|
|
2098
|
+
if (pathname === "/v1/config/client") {
|
|
2099
|
+
allowed = read;
|
|
2100
|
+
} else if (segments[0] === "v1" && segments[1] === "workspaces" && segments[2] === "site-host") {
|
|
2101
|
+
const [surface, targetSessionId, operation] = segments.slice(3);
|
|
2102
|
+
if (!surface) {
|
|
2103
|
+
allowed = read;
|
|
2104
|
+
} else if (surface === "sessions") {
|
|
2105
|
+
const ownSession = targetSessionId === session.id;
|
|
2106
|
+
const targetAllowed = ownSession || families.subagents;
|
|
2107
|
+
if (!targetSessionId) {
|
|
2108
|
+
allowed = families.subagents;
|
|
2109
|
+
} else if (ownSession && !operation && read) {
|
|
2110
|
+
allowed = true;
|
|
2111
|
+
} else if (ownSession && !operation && verb === "PATCH") {
|
|
2112
|
+
allowed = allows("set_session_title");
|
|
2113
|
+
} else if (ownSession && operation === "background-commands") {
|
|
2114
|
+
allowed = read && allows("command_read");
|
|
2115
|
+
} else if (operation === "goal") {
|
|
2116
|
+
allowed = allows("goal_set") && targetAllowed;
|
|
2117
|
+
} else if (operation === "browser" || operation === "computer") {
|
|
2118
|
+
allowed = allows("browser_open") && targetAllowed;
|
|
2119
|
+
} else if (operation === "human-input-requests") {
|
|
2120
|
+
allowed = families.humanInput && targetAllowed;
|
|
2121
|
+
} else if (operation === "artifacts") {
|
|
2122
|
+
allowed = allows("artifacts_list") && targetAllowed;
|
|
2123
|
+
} else if (ownSession && ["fs", "git", "terminal", "workspace"].includes(operation ?? "")) {
|
|
2124
|
+
// Resource-derived sandbox mechanics are not platform capabilities.
|
|
2125
|
+
allowed = true;
|
|
2126
|
+
} else if (
|
|
2127
|
+
[
|
|
2128
|
+
"agent",
|
|
2129
|
+
"tool-policy",
|
|
2130
|
+
"mcp-servers",
|
|
2131
|
+
"mcp-credentials",
|
|
2132
|
+
"variable-sets",
|
|
2133
|
+
"project",
|
|
2134
|
+
].includes(operation ?? "")
|
|
2135
|
+
) {
|
|
2136
|
+
allowed = allows("project_get") && targetAllowed;
|
|
2137
|
+
} else {
|
|
2138
|
+
allowed = families.subagents;
|
|
2139
|
+
}
|
|
2140
|
+
} else if (surface === "files") {
|
|
2141
|
+
allowed = families.allowsMcpServer("files");
|
|
2142
|
+
} else if (surface === "skills") {
|
|
2143
|
+
allowed = families.skills === "manage" || (families.skills === "read" && read);
|
|
2144
|
+
} else if (surface === "human-input-requests") {
|
|
2145
|
+
allowed = families.humanInput;
|
|
2146
|
+
} else {
|
|
2147
|
+
const owners: Record<string, FirstPartyMcpToolName> = {
|
|
2148
|
+
"model-catalog": "sessions_list",
|
|
2149
|
+
"realtime-model-catalog": "sessions_list",
|
|
2150
|
+
"model-policy": "sessions_list",
|
|
2151
|
+
"gateway-custom-models": "sessions_list",
|
|
2152
|
+
"openrouter-custom-models": "sessions_list",
|
|
2153
|
+
"new-session-draft": "session_create",
|
|
2154
|
+
"session-tenancy": "session_create",
|
|
2155
|
+
"session-message-search": "session_events",
|
|
2156
|
+
"agent-topology": "sessions_list",
|
|
2157
|
+
projects: "project_get",
|
|
2158
|
+
sandboxes: "sandboxes_list",
|
|
2159
|
+
machines: "sandboxes_list",
|
|
2160
|
+
rigs: "rig_list",
|
|
2161
|
+
environments: "environment_list",
|
|
2162
|
+
"variable-sets": "variable_set_list",
|
|
2163
|
+
capabilities: "capability_catalog_search",
|
|
2164
|
+
catalog: "capability_catalog_search",
|
|
2165
|
+
members: "project_get",
|
|
2166
|
+
"member-candidates": "project_get",
|
|
2167
|
+
"control-events": "project_get",
|
|
2168
|
+
"browser-identities": "browser_identity",
|
|
2169
|
+
"browser-sessions": "browser_open",
|
|
2170
|
+
"computer-sessions": "computer_open",
|
|
2171
|
+
"attached-browsers": "browser_open",
|
|
2172
|
+
documents: "knowledge_search",
|
|
2173
|
+
knowledge: "knowledge_search",
|
|
2174
|
+
memory: "knowledge_search",
|
|
2175
|
+
"instruction-policy": "instruction_policy_get",
|
|
2176
|
+
preferences: "preference_registry_get",
|
|
2177
|
+
"company-profile": "company_profile_propose",
|
|
2178
|
+
artifacts: "artifacts_list",
|
|
2179
|
+
"scheduled-tasks": "scheduled_tasks_list",
|
|
2180
|
+
automations: "scheduled_tasks_list",
|
|
2181
|
+
connections: "github_connect_link",
|
|
2182
|
+
integrations: "github_connect_link",
|
|
2183
|
+
github: "github_connect_link",
|
|
2184
|
+
social: "social_connections_list",
|
|
2185
|
+
mcp: "github_connect_link",
|
|
2186
|
+
};
|
|
2187
|
+
const tool = owners[surface];
|
|
2188
|
+
allowed = tool !== undefined && allows(tool);
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
if (!allowed) {
|
|
2192
|
+
throw new HTTPException(403, {
|
|
2193
|
+
message: "Agent configuration does not allow this Codemode SDK operation",
|
|
2194
|
+
});
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
|
|
888
2198
|
function codemodeHttpError(error: unknown): HTTPException {
|
|
2199
|
+
if (error instanceof SiteSessionPathError) {
|
|
2200
|
+
// The proxied Site/SDK surface is an explicit allowlist; a route outside
|
|
2201
|
+
// it (tool policy, visibility, forks, Steer, control, ...) does not exist
|
|
2202
|
+
// for this caller rather than being a server fault.
|
|
2203
|
+
return new HTTPException(404, { message: error.message, cause: error });
|
|
2204
|
+
}
|
|
889
2205
|
if (error instanceof SessionAuthorizationDeniedError) {
|
|
890
2206
|
return new HTTPException(404, {
|
|
891
2207
|
message: "session not found",
|
|
@@ -898,18 +2214,39 @@ function codemodeHttpError(error: unknown): HTTPException {
|
|
|
898
2214
|
cause: error,
|
|
899
2215
|
});
|
|
900
2216
|
}
|
|
901
|
-
if (
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
2217
|
+
if (error instanceof CodemodeCatalogNotReadyError) {
|
|
2218
|
+
return new ApiHttpError(409, {
|
|
2219
|
+
code: "conflict",
|
|
2220
|
+
message: error.message,
|
|
2221
|
+
retryable: true,
|
|
2222
|
+
outcomeUnknown: false,
|
|
2223
|
+
details: { code: error.code },
|
|
2224
|
+
});
|
|
2225
|
+
}
|
|
2226
|
+
if (error instanceof CodemodeCatalogStaleError) {
|
|
2227
|
+
return new ApiHttpError(409, {
|
|
2228
|
+
code: "conflict",
|
|
2229
|
+
message: error.message,
|
|
2230
|
+
retryable: true,
|
|
2231
|
+
outcomeUnknown: false,
|
|
2232
|
+
details: { code: error.code },
|
|
2233
|
+
});
|
|
2234
|
+
}
|
|
2235
|
+
if (error instanceof CodemodeAuthorityError) {
|
|
2236
|
+
return new ApiHttpError(error.reason === "invalid_grant" ? 403 : 409, {
|
|
2237
|
+
code: error.reason === "invalid_grant" ? "forbidden" : "conflict",
|
|
2238
|
+
message: error.message,
|
|
2239
|
+
retryable: false,
|
|
2240
|
+
outcomeUnknown: false,
|
|
2241
|
+
details: { code: error.code },
|
|
2242
|
+
});
|
|
2243
|
+
}
|
|
2244
|
+
if (error instanceof CodemodeOperationNotExecutableError) {
|
|
905
2245
|
return new HTTPException(409, { message: error.message, cause: error });
|
|
906
2246
|
}
|
|
907
2247
|
if (error instanceof CodemodeOperationConflictError) {
|
|
908
2248
|
return new HTTPException(409, { message: error.message, cause: error });
|
|
909
2249
|
}
|
|
910
|
-
if (error instanceof CodemodeCallBudgetExceededError) {
|
|
911
|
-
return new HTTPException(429, { message: error.message, cause: error });
|
|
912
|
-
}
|
|
913
2250
|
if (error instanceof CodemodeToolNotInCatalogError) {
|
|
914
2251
|
return new HTTPException(404, { message: error.message, cause: error });
|
|
915
2252
|
}
|
|
@@ -952,6 +2289,15 @@ function clientAnalyticsConfig(settings: AppDependencies["settings"]) {
|
|
|
952
2289
|
};
|
|
953
2290
|
}
|
|
954
2291
|
|
|
2292
|
+
function clientLegalConfig(settings: AppDependencies["settings"]) {
|
|
2293
|
+
return {
|
|
2294
|
+
...(settings.legalPrivacyPolicyUrl ? { privacyPolicyUrl: settings.legalPrivacyPolicyUrl } : {}),
|
|
2295
|
+
...(settings.legalTermsOfServiceUrl
|
|
2296
|
+
? { termsOfServiceUrl: settings.legalTermsOfServiceUrl }
|
|
2297
|
+
: {}),
|
|
2298
|
+
};
|
|
2299
|
+
}
|
|
2300
|
+
|
|
955
2301
|
function structuredServicesHint(backend: string): {
|
|
956
2302
|
fileSystem: boolean;
|
|
957
2303
|
git: boolean;
|
|
@@ -975,9 +2321,17 @@ function codexCompactionV2ProviderLockedError(
|
|
|
975
2321
|
}
|
|
976
2322
|
|
|
977
2323
|
export function httpStatusForError(error: unknown): number {
|
|
2324
|
+
if (error instanceof ConnectAttemptConflictError) return 409;
|
|
2325
|
+
if (error instanceof ConnectAttemptNotFoundError) return 404;
|
|
978
2326
|
if (codexCompactionV2ProviderLockedError(error)) {
|
|
979
2327
|
return 422;
|
|
980
2328
|
}
|
|
2329
|
+
if (workspaceControlBusyHttpError(error)) {
|
|
2330
|
+
return 503;
|
|
2331
|
+
}
|
|
2332
|
+
if (isRequestBodyValidationError(error)) {
|
|
2333
|
+
return 400;
|
|
2334
|
+
}
|
|
981
2335
|
if (error instanceof HTTPException) {
|
|
982
2336
|
return error.status;
|
|
983
2337
|
}
|
|
@@ -991,7 +2345,7 @@ export function errorCodeForStatus(status: number): ErrorCode {
|
|
|
991
2345
|
if (status === 401) return "unauthenticated";
|
|
992
2346
|
if (status === 402) return "payment_required";
|
|
993
2347
|
if (status === 403) return "forbidden";
|
|
994
|
-
if (status === 404) return "not_found";
|
|
2348
|
+
if (status === 404 || status === 405) return "not_found";
|
|
995
2349
|
if (status === 409) return "conflict";
|
|
996
2350
|
if (status === 413 || status === 422 || status === 400) return "validation_failed";
|
|
997
2351
|
if (status === 429) return "limit_exceeded";
|
|
@@ -1004,11 +2358,14 @@ function retryableHttpStatus(status: number): boolean {
|
|
|
1004
2358
|
}
|
|
1005
2359
|
|
|
1006
2360
|
function publicErrorMessage(error: unknown, status: number): string {
|
|
2361
|
+
if (error instanceof ConnectAttemptConflictError)
|
|
2362
|
+
return "Connection setup changed or is still in progress. Reload its current status before retrying.";
|
|
2363
|
+
if (error instanceof ConnectAttemptNotFoundError) return "Connection setup not found.";
|
|
1007
2364
|
if (status === 502 || status === 503 || status === 504) {
|
|
1008
|
-
return "
|
|
2365
|
+
return "Opengeni is temporarily unavailable — retry.";
|
|
1009
2366
|
}
|
|
1010
2367
|
if (status >= 500) {
|
|
1011
|
-
return "
|
|
2368
|
+
return "Opengeni could not complete the request.";
|
|
1012
2369
|
}
|
|
1013
2370
|
if (error instanceof HTTPException) {
|
|
1014
2371
|
return boundedPublicMessage(error.message) ?? "Request failed.";
|
|
@@ -1038,19 +2395,26 @@ type ReadinessChecks = Record<ReadinessCheckName, ReadinessCheck>;
|
|
|
1038
2395
|
type ReadinessCheckResult = { ok: boolean; error?: string };
|
|
1039
2396
|
|
|
1040
2397
|
function readinessChecks(deps: AppDependencies): ReadinessChecks {
|
|
2398
|
+
const configuredNatsCheck = deps.readinessChecks?.nats;
|
|
1041
2399
|
return {
|
|
1042
|
-
db:
|
|
1043
|
-
deps.readinessChecks?.db
|
|
1044
|
-
|
|
2400
|
+
db: async () => {
|
|
2401
|
+
if (deps.readinessChecks?.db) {
|
|
2402
|
+
await deps.readinessChecks.db();
|
|
2403
|
+
} else {
|
|
1045
2404
|
await deps.db.execute(dbSql`select 1`);
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
2405
|
+
}
|
|
2406
|
+
await resolveCatalogSettings(deps.db, deps.settings);
|
|
2407
|
+
},
|
|
2408
|
+
nats: async () => {
|
|
2409
|
+
requireSessionEventDurableFanoutCapability(deps.bus);
|
|
2410
|
+
if (configuredNatsCheck) {
|
|
2411
|
+
await configuredNatsCheck();
|
|
2412
|
+
} else {
|
|
1050
2413
|
if (deps.bus.isConnected && !deps.bus.isConnected()) {
|
|
1051
2414
|
throw new Error("NATS is not connected");
|
|
1052
2415
|
}
|
|
1053
|
-
}
|
|
2416
|
+
}
|
|
2417
|
+
},
|
|
1054
2418
|
temporal:
|
|
1055
2419
|
deps.readinessChecks?.temporal ??
|
|
1056
2420
|
deps.workflowClient.check ??
|
|
@@ -1115,8 +2479,37 @@ const routeLabelPatterns: Array<{
|
|
|
1115
2479
|
pattern: RegExp;
|
|
1116
2480
|
label: string | ((match: RegExpMatchArray) => string);
|
|
1117
2481
|
}> = [
|
|
2482
|
+
{
|
|
2483
|
+
pattern: /^\/\.well-known\/oauth-authorization-server$/,
|
|
2484
|
+
label: "/.well-known/oauth-authorization-server",
|
|
2485
|
+
},
|
|
2486
|
+
{
|
|
2487
|
+
pattern:
|
|
2488
|
+
/^\/\.well-known\/oauth-protected-resource\/v1\/workspaces\/[^/]+\/mcp(?:\/(docs|files))?$/,
|
|
2489
|
+
label: (match) =>
|
|
2490
|
+
`/.well-known/oauth-protected-resource/v1/workspaces/:workspaceId/mcp${match[1] ? `/${match[1]}` : ""}`,
|
|
2491
|
+
},
|
|
2492
|
+
{
|
|
2493
|
+
pattern: /^\/oauth\/(register|authorize|token)$/,
|
|
2494
|
+
label: (match) => `/oauth/${match[1]}`,
|
|
2495
|
+
},
|
|
1118
2496
|
{ pattern: /^\/healthz$/, label: "/healthz" },
|
|
1119
2497
|
{ pattern: /^\/readyz$/, label: "/readyz" },
|
|
2498
|
+
// Better Auth answers these behind one `/v1/auth/*` registration, so the
|
|
2499
|
+
// provider endpoints need an explicit closed set to stay distinguishable.
|
|
2500
|
+
{
|
|
2501
|
+
pattern:
|
|
2502
|
+
/^\/v1\/auth\/(sign-up\/email|sign-in\/email|sign-in\/social|sign-out|send-verification-email|verify-email|request-password-reset|reset-password|error|ok)$/,
|
|
2503
|
+
label: (match) => `/v1/auth/${match[1]}`,
|
|
2504
|
+
},
|
|
2505
|
+
{ pattern: /^\/v1\/auth\/reset-password\/[^/]+$/, label: "/v1/auth/reset-password/:token" },
|
|
2506
|
+
{
|
|
2507
|
+
pattern: /^\/v1\/auth\/callback\/([^/]+)$/,
|
|
2508
|
+
label: (match) =>
|
|
2509
|
+
match[1] === "google" || match[1] === "github"
|
|
2510
|
+
? `/v1/auth/callback/${match[1]}`
|
|
2511
|
+
: "/v1/auth/callback/:providerId",
|
|
2512
|
+
},
|
|
1120
2513
|
{ pattern: /^\/traffic-readyz$/, label: "/traffic-readyz" },
|
|
1121
2514
|
{
|
|
1122
2515
|
pattern: /^\/v1\/workspaces\/[^/]+\/codex\/connect\/start$/,
|
|
@@ -1130,6 +2523,22 @@ const routeLabelPatterns: Array<{
|
|
|
1130
2523
|
pattern: /^\/v1\/workspaces\/[^/]+\/codex\/status$/,
|
|
1131
2524
|
label: "/v1/workspaces/:workspaceId/codex/status",
|
|
1132
2525
|
},
|
|
2526
|
+
{
|
|
2527
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/codex\/source$/,
|
|
2528
|
+
label: "/v1/workspaces/:workspaceId/codex/source",
|
|
2529
|
+
},
|
|
2530
|
+
{
|
|
2531
|
+
pattern: /^\/v1\/organizations\/[^/]+\/codex\/(accounts|settings)$/,
|
|
2532
|
+
label: (match) => `/v1/organizations/:organizationId/codex/${match[1]}`,
|
|
2533
|
+
},
|
|
2534
|
+
{
|
|
2535
|
+
pattern: /^\/v1\/organizations\/[^/]+\/codex\/connect\/(start|poll)$/,
|
|
2536
|
+
label: (match) => `/v1/organizations/:organizationId/codex/connect/${match[1]}`,
|
|
2537
|
+
},
|
|
2538
|
+
{
|
|
2539
|
+
pattern: /^\/v1\/organizations\/[^/]+\/codex\/accounts\/[^/]+(?:\/activate)?$/,
|
|
2540
|
+
label: "/v1/organizations/:organizationId/codex/accounts/:accountId",
|
|
2541
|
+
},
|
|
1133
2542
|
{
|
|
1134
2543
|
pattern: /^\/v1\/workspaces\/[^/]+\/supergrok\/connect\/(start|poll)$/,
|
|
1135
2544
|
label: (match) => `/v1/workspaces/:workspaceId/supergrok/connect/${match[1]}`,
|
|
@@ -1156,6 +2565,8 @@ const routeLabelPatterns: Array<{
|
|
|
1156
2565
|
},
|
|
1157
2566
|
{ pattern: /^\/metrics$/, label: "/metrics" },
|
|
1158
2567
|
{ pattern: /^\/v1\/config\/client$/, label: "/v1/config/client" },
|
|
2568
|
+
{ pattern: /^\/v1\/client-errors$/, label: "/v1/client-errors" },
|
|
2569
|
+
{ pattern: /^\/v1\/analytics-consent$/, label: "/v1/analytics-consent" },
|
|
1159
2570
|
{ pattern: /^\/v1\/billing$/, label: "/v1/billing" },
|
|
1160
2571
|
{ pattern: /^\/v1\/billing\/checkout$/, label: "/v1/billing/checkout" },
|
|
1161
2572
|
{ pattern: /^\/v1\/billing\/usage$/, label: "/v1/billing/usage" },
|
|
@@ -1163,11 +2574,31 @@ const routeLabelPatterns: Array<{
|
|
|
1163
2574
|
pattern: /^\/v1\/workspaces\/[^/]+\/insights$/,
|
|
1164
2575
|
label: "/v1/workspaces/:workspaceId/insights",
|
|
1165
2576
|
},
|
|
2577
|
+
{
|
|
2578
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/insights\/usage$/,
|
|
2579
|
+
label: "/v1/workspaces/:workspaceId/insights/usage",
|
|
2580
|
+
},
|
|
2581
|
+
{
|
|
2582
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/insights\/calls$/,
|
|
2583
|
+
label: "/v1/workspaces/:workspaceId/insights/calls",
|
|
2584
|
+
},
|
|
2585
|
+
{
|
|
2586
|
+
pattern: /^\/v1\/organizations\/[^/]+\/insights\/usage$/,
|
|
2587
|
+
label: "/v1/organizations/:accountId/insights/usage",
|
|
2588
|
+
},
|
|
2589
|
+
{
|
|
2590
|
+
pattern: /^\/v1\/organizations\/[^/]+\/insights\/calls$/,
|
|
2591
|
+
label: "/v1/organizations/:accountId/insights/calls",
|
|
2592
|
+
},
|
|
1166
2593
|
{
|
|
1167
2594
|
pattern: /^\/v1\/billing\/entitlements$/,
|
|
1168
2595
|
label: "/v1/billing/entitlements",
|
|
1169
2596
|
},
|
|
1170
2597
|
{ pattern: /^\/v1\/webhooks\/stripe$/, label: "/v1/webhooks/stripe" },
|
|
2598
|
+
{
|
|
2599
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/pr-review\/(registrations|repositories)(?:\/[^/]+)?$/,
|
|
2600
|
+
label: (match) => `/v1/workspaces/:workspaceId/pr-review/${match[1]}`,
|
|
2601
|
+
},
|
|
1171
2602
|
{
|
|
1172
2603
|
pattern: /^\/v1\/workspaces\/[^/]+\/mcp$/,
|
|
1173
2604
|
label: "/v1/workspaces/:workspaceId/mcp",
|
|
@@ -1180,6 +2611,22 @@ const routeLabelPatterns: Array<{
|
|
|
1180
2611
|
pattern: /^\/v1\/workspaces\/[^/]+\/mcp\/files$/,
|
|
1181
2612
|
label: "/v1/workspaces/:workspaceId/mcp/files",
|
|
1182
2613
|
},
|
|
2614
|
+
{
|
|
2615
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/tools\/catalog$/,
|
|
2616
|
+
label: "/v1/workspaces/:workspaceId/tools/catalog",
|
|
2617
|
+
},
|
|
2618
|
+
{
|
|
2619
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/tools\/calls$/,
|
|
2620
|
+
label: "/v1/workspaces/:workspaceId/tools/calls",
|
|
2621
|
+
},
|
|
2622
|
+
{
|
|
2623
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/tools\/approvals$/,
|
|
2624
|
+
label: "/v1/workspaces/:workspaceId/tools/approvals",
|
|
2625
|
+
},
|
|
2626
|
+
{
|
|
2627
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/tools\/declarations$/,
|
|
2628
|
+
label: "/v1/workspaces/:workspaceId/tools/declarations",
|
|
2629
|
+
},
|
|
1183
2630
|
{
|
|
1184
2631
|
pattern: /^\/v1\/workspaces\/[^/]+\/default-rig$/,
|
|
1185
2632
|
label: "/v1/workspaces/:workspaceId/default-rig",
|
|
@@ -1188,6 +2635,18 @@ const routeLabelPatterns: Array<{
|
|
|
1188
2635
|
pattern: /^\/v1\/workspaces\/[^/]+\/sessions$/,
|
|
1189
2636
|
label: "/v1/workspaces/:workspaceId/sessions",
|
|
1190
2637
|
},
|
|
2638
|
+
{
|
|
2639
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/session-imports$/,
|
|
2640
|
+
label: "/v1/workspaces/:workspaceId/session-imports",
|
|
2641
|
+
},
|
|
2642
|
+
{
|
|
2643
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/session-imports\/[^/]+\/events$/,
|
|
2644
|
+
label: "/v1/workspaces/:workspaceId/session-imports/:importId/events",
|
|
2645
|
+
},
|
|
2646
|
+
{
|
|
2647
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/session-message-search$/,
|
|
2648
|
+
label: "/v1/workspaces/:workspaceId/session-message-search",
|
|
2649
|
+
},
|
|
1191
2650
|
{
|
|
1192
2651
|
pattern: /^\/v1\/workspaces\/[^/]+\/control-events\/stream$/,
|
|
1193
2652
|
label: "/v1/workspaces/:workspaceId/control-events/stream",
|
|
@@ -1285,6 +2744,11 @@ const routeLabelPatterns: Array<{
|
|
|
1285
2744
|
label:
|
|
1286
2745
|
"/v1/workspaces/:workspaceId/computer-sessions/:computerSessionId/targets/:targetId/observation",
|
|
1287
2746
|
},
|
|
2747
|
+
{
|
|
2748
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/computer-sessions\/[^/]+\/targets\/[^/]+\/screenshot$/,
|
|
2749
|
+
label:
|
|
2750
|
+
"/v1/workspaces/:workspaceId/computer-sessions/:computerSessionId/targets/:targetId/screenshot",
|
|
2751
|
+
},
|
|
1288
2752
|
{
|
|
1289
2753
|
pattern: /^\/v1\/workspaces\/[^/]+\/computer-sessions\/[^/]+\/operations\/[^/]+$/,
|
|
1290
2754
|
label:
|
|
@@ -1348,6 +2812,10 @@ const routeLabelPatterns: Array<{
|
|
|
1348
2812
|
pattern: /^\/v1\/workspaces\/[^/]+\/inference-control$/,
|
|
1349
2813
|
label: "/v1/workspaces/:workspaceId/inference-control",
|
|
1350
2814
|
},
|
|
2815
|
+
{
|
|
2816
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/pause-timer$/,
|
|
2817
|
+
label: "/v1/workspaces/:workspaceId/pause-timer",
|
|
2818
|
+
},
|
|
1351
2819
|
{
|
|
1352
2820
|
pattern:
|
|
1353
2821
|
/^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/fs\/(list|list-batch|read|write|delete|move|mkdir)$/,
|
|
@@ -1369,6 +2837,10 @@ const routeLabelPatterns: Array<{
|
|
|
1369
2837
|
pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/events\/stream$/,
|
|
1370
2838
|
label: "/v1/workspaces/:workspaceId/sessions/:id/events/stream",
|
|
1371
2839
|
},
|
|
2840
|
+
{
|
|
2841
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/(visibility|forks)$/,
|
|
2842
|
+
label: (match) => `/v1/workspaces/:workspaceId/sessions/:id/${match[1]}`,
|
|
2843
|
+
},
|
|
1372
2844
|
{
|
|
1373
2845
|
pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/lineage$/,
|
|
1374
2846
|
label: "/v1/workspaces/:workspaceId/sessions/:id/lineage",
|
|
@@ -1377,6 +2849,10 @@ const routeLabelPatterns: Array<{
|
|
|
1377
2849
|
pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/events$/,
|
|
1378
2850
|
label: "/v1/workspaces/:workspaceId/sessions/:id/events",
|
|
1379
2851
|
},
|
|
2852
|
+
{
|
|
2853
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/turns$/,
|
|
2854
|
+
label: "/v1/workspaces/:workspaceId/sessions/:id/turns",
|
|
2855
|
+
},
|
|
1380
2856
|
{
|
|
1381
2857
|
pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/queue\/[^/]+\/(move|edit|steer|delete)$/,
|
|
1382
2858
|
label: "/v1/workspaces/:workspaceId/sessions/:id/queue/:turnId/:action",
|
|
@@ -1385,6 +2861,10 @@ const routeLabelPatterns: Array<{
|
|
|
1385
2861
|
pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/queue$/,
|
|
1386
2862
|
label: "/v1/workspaces/:workspaceId/sessions/:id/queue",
|
|
1387
2863
|
},
|
|
2864
|
+
{
|
|
2865
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/composer-draft\/submit$/,
|
|
2866
|
+
label: "/v1/workspaces/:workspaceId/sessions/:id/composer-draft/submit",
|
|
2867
|
+
},
|
|
1388
2868
|
{
|
|
1389
2869
|
pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/composer-draft$/,
|
|
1390
2870
|
label: "/v1/workspaces/:workspaceId/sessions/:id/composer-draft",
|
|
@@ -1449,6 +2929,84 @@ const routeLabelPatterns: Array<{
|
|
|
1449
2929
|
pattern: /^\/v1\/workspaces\/[^/]+\/api-keys\/[^/]+$/,
|
|
1450
2930
|
label: "/v1/workspaces/:workspaceId/api-keys/:id",
|
|
1451
2931
|
},
|
|
2932
|
+
{
|
|
2933
|
+
pattern: /^\/v1\/organizations\/[^/]+\/api-keys$/,
|
|
2934
|
+
label: "/v1/organizations/:organizationId/api-keys",
|
|
2935
|
+
},
|
|
2936
|
+
{
|
|
2937
|
+
pattern: /^\/v1\/organizations\/[^/]+\/api-keys\/[^/]+$/,
|
|
2938
|
+
label: "/v1/organizations/:organizationId/api-keys/:id",
|
|
2939
|
+
},
|
|
2940
|
+
{
|
|
2941
|
+
pattern: /^\/v1\/workspaces\/external$/,
|
|
2942
|
+
label: "/v1/workspaces/external",
|
|
2943
|
+
},
|
|
2944
|
+
{
|
|
2945
|
+
pattern: /^\/v1\/workspaces\/external\/[^/]+\/[^/]+\/session-imports$/,
|
|
2946
|
+
label: "/v1/workspaces/external/:source/:externalId/session-imports",
|
|
2947
|
+
},
|
|
2948
|
+
{
|
|
2949
|
+
pattern: /^\/v1\/workspaces\/external\/[^/]+\/[^/]+\/session-imports\/[^/]+\/events$/,
|
|
2950
|
+
label: "/v1/workspaces/external/:source/:externalId/session-imports/:importId/events",
|
|
2951
|
+
},
|
|
2952
|
+
{
|
|
2953
|
+
pattern: /^\/v1\/workspaces\/external\/[^/]+\/[^/]+\/allowance\/grants$/,
|
|
2954
|
+
label: "/v1/workspaces/external/:source/:externalId/allowance/grants",
|
|
2955
|
+
},
|
|
2956
|
+
{
|
|
2957
|
+
pattern: /^\/v1\/workspaces\/external\/[^/]+\/[^/]+\/allowance$/,
|
|
2958
|
+
label: "/v1/workspaces/external/:source/:externalId/allowance",
|
|
2959
|
+
},
|
|
2960
|
+
{
|
|
2961
|
+
pattern: /^\/v1\/workspaces\/external\/[^/]+\/[^/]+\/allowance\/state$/,
|
|
2962
|
+
label: "/v1/workspaces/external/:source/:externalId/allowance/state",
|
|
2963
|
+
},
|
|
2964
|
+
{
|
|
2965
|
+
pattern: /^\/v1\/workspaces\/external\/[^/]+\/[^/]+\/usage\/me$/,
|
|
2966
|
+
label: "/v1/workspaces/external/:source/:externalId/usage/me",
|
|
2967
|
+
},
|
|
2968
|
+
{
|
|
2969
|
+
pattern: /^\/v1\/workspaces\/external\/[^/]+\/[^/]+\/usage$/,
|
|
2970
|
+
label: "/v1/workspaces/external/:source/:externalId/usage",
|
|
2971
|
+
},
|
|
2972
|
+
{
|
|
2973
|
+
pattern:
|
|
2974
|
+
/^\/v1\/workspaces\/external\/[^/]+\/[^/]+\/members\/external\/[^/]+\/[^/]+\/allowance$/,
|
|
2975
|
+
label:
|
|
2976
|
+
"/v1/workspaces/external/:source/:externalId/members/external/:memberSource/:memberExternalId/allowance",
|
|
2977
|
+
},
|
|
2978
|
+
{
|
|
2979
|
+
pattern: /^\/v1\/workspaces\/external\/[^/]+\/[^/]+\/members\/[^/]+\/allowance$/,
|
|
2980
|
+
label: "/v1/workspaces/external/:source/:externalId/members/:subjectId/allowance",
|
|
2981
|
+
},
|
|
2982
|
+
{
|
|
2983
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/allowance\/grants$/,
|
|
2984
|
+
label: "/v1/workspaces/:workspaceId/allowance/grants",
|
|
2985
|
+
},
|
|
2986
|
+
{
|
|
2987
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/allowance$/,
|
|
2988
|
+
label: "/v1/workspaces/:workspaceId/allowance",
|
|
2989
|
+
},
|
|
2990
|
+
{
|
|
2991
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/allowance\/state$/,
|
|
2992
|
+
label: "/v1/workspaces/:workspaceId/allowance/state",
|
|
2993
|
+
},
|
|
2994
|
+
{
|
|
2995
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/usage\/me$/,
|
|
2996
|
+
label: "/v1/workspaces/:workspaceId/usage/me",
|
|
2997
|
+
},
|
|
2998
|
+
{
|
|
2999
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/usage$/,
|
|
3000
|
+
label: "/v1/workspaces/:workspaceId/usage",
|
|
3001
|
+
},
|
|
3002
|
+
{
|
|
3003
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/members\/external\/[^/]+\/[^/]+\/allowance$/,
|
|
3004
|
+
label: "/v1/workspaces/:workspaceId/members/external/:source/:externalId/allowance",
|
|
3005
|
+
},
|
|
3006
|
+
{
|
|
3007
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/members\/[^/]+\/allowance$/,
|
|
3008
|
+
label: "/v1/workspaces/:workspaceId/members/:subjectId/allowance",
|
|
3009
|
+
},
|
|
1452
3010
|
{
|
|
1453
3011
|
pattern: /^\/v1\/workspaces\/[^/]+\/scheduled-tasks$/,
|
|
1454
3012
|
label: "/v1/workspaces/:workspaceId/scheduled-tasks",
|
|
@@ -1513,6 +3071,10 @@ const routeLabelPatterns: Array<{
|
|
|
1513
3071
|
pattern: /^\/v1\/workspaces\/[^/]+\/github\/app$/,
|
|
1514
3072
|
label: "/v1/workspaces/:workspaceId/github/app",
|
|
1515
3073
|
},
|
|
3074
|
+
{
|
|
3075
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/github\/action-policies$/,
|
|
3076
|
+
label: "/v1/workspaces/:workspaceId/github/action-policies",
|
|
3077
|
+
},
|
|
1516
3078
|
{
|
|
1517
3079
|
pattern: /^\/v1\/workspaces\/[^/]+\/github\/repositories$/,
|
|
1518
3080
|
label: "/v1/workspaces/:workspaceId/github/repositories",
|
|
@@ -1545,6 +3107,22 @@ const routeLabelPatterns: Array<{
|
|
|
1545
3107
|
pattern: /^\/v1\/workspaces\/[^/]+\/github\/app-manifest$/,
|
|
1546
3108
|
label: "/v1/workspaces/:workspaceId/github/app-manifest",
|
|
1547
3109
|
},
|
|
3110
|
+
{
|
|
3111
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/pr-review\/github$/,
|
|
3112
|
+
label: "/v1/workspaces/:workspaceId/pr-review/github",
|
|
3113
|
+
},
|
|
3114
|
+
{
|
|
3115
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/pr-review\/github\/connect$/,
|
|
3116
|
+
label: "/v1/workspaces/:workspaceId/pr-review/github/connect",
|
|
3117
|
+
},
|
|
3118
|
+
{
|
|
3119
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/pr-review\/github\/installations\/select$/,
|
|
3120
|
+
label: "/v1/workspaces/:workspaceId/pr-review/github/installations/select",
|
|
3121
|
+
},
|
|
3122
|
+
{
|
|
3123
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/pr-review\/github\/installations\/[^/]+\/configure$/,
|
|
3124
|
+
label: "/v1/workspaces/:workspaceId/pr-review/github/installations/:installationId/configure",
|
|
3125
|
+
},
|
|
1548
3126
|
{
|
|
1549
3127
|
pattern: /^\/v1\/workspaces\/[^/]+\/capabilities$/,
|
|
1550
3128
|
label: "/v1/workspaces/:workspaceId/capabilities",
|
|
@@ -1647,26 +3225,7 @@ const routeLabelPatterns: Array<{
|
|
|
1647
3225
|
pattern: /^\/v1\/workspaces\/[^/]+\/environments\/[^/]+$/,
|
|
1648
3226
|
label: "/v1/workspaces/:workspaceId/environments/:id",
|
|
1649
3227
|
},
|
|
1650
|
-
|
|
1651
|
-
pattern: /^\/v1\/workspaces\/[^/]+\/packs$/,
|
|
1652
|
-
label: "/v1/workspaces/:workspaceId/packs",
|
|
1653
|
-
},
|
|
1654
|
-
{
|
|
1655
|
-
pattern: /^\/v1\/workspaces\/[^/]+\/packs\/installations$/,
|
|
1656
|
-
label: "/v1/workspaces/:workspaceId/packs/installations",
|
|
1657
|
-
},
|
|
1658
|
-
{
|
|
1659
|
-
pattern: /^\/v1\/workspaces\/[^/]+\/packs\/marketing-social-daily-analysis\/scheduled-tasks$/,
|
|
1660
|
-
label: "/v1/workspaces/:workspaceId/packs/marketing-social-daily-analysis/scheduled-tasks",
|
|
1661
|
-
},
|
|
1662
|
-
{
|
|
1663
|
-
pattern: /^\/v1\/workspaces\/[^/]+\/packs\/[^/]+\/enable$/,
|
|
1664
|
-
label: "/v1/workspaces/:workspaceId/packs/:id/enable",
|
|
1665
|
-
},
|
|
1666
|
-
{
|
|
1667
|
-
pattern: /^\/v1\/workspaces\/[^/]+\/packs\/[^/]+$/,
|
|
1668
|
-
label: "/v1/workspaces/:workspaceId/packs/:id",
|
|
1669
|
-
},
|
|
3228
|
+
|
|
1670
3229
|
{
|
|
1671
3230
|
pattern: /^\/v1\/workspaces\/[^/]+\/plugins\/preview$/,
|
|
1672
3231
|
label: "/v1/workspaces/:workspaceId/plugins/preview",
|
|
@@ -1703,6 +3262,26 @@ const routeLabelPatterns: Array<{
|
|
|
1703
3262
|
pattern: /^\/v1\/workspaces\/[^/]+\/connections\/oauth\/start$/,
|
|
1704
3263
|
label: "/v1/workspaces/:workspaceId/connections/oauth/start",
|
|
1705
3264
|
},
|
|
3265
|
+
{
|
|
3266
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/connections\/github$/,
|
|
3267
|
+
label: "/v1/workspaces/:workspaceId/connections/github",
|
|
3268
|
+
},
|
|
3269
|
+
{
|
|
3270
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/connections\/github\/oauth\/start$/,
|
|
3271
|
+
label: "/v1/workspaces/:workspaceId/connections/github/oauth/start",
|
|
3272
|
+
},
|
|
3273
|
+
{
|
|
3274
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/connections\/[^/]+\/github\/reconnect$/,
|
|
3275
|
+
label: "/v1/workspaces/:workspaceId/connections/:connectionId/github/reconnect",
|
|
3276
|
+
},
|
|
3277
|
+
{
|
|
3278
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/connections\/[^/]+\/github\/repositories\/verify$/,
|
|
3279
|
+
label: "/v1/workspaces/:workspaceId/connections/:connectionId/github/repositories/verify",
|
|
3280
|
+
},
|
|
3281
|
+
{
|
|
3282
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/connections\/[^/]+\/github\/repositories$/,
|
|
3283
|
+
label: "/v1/workspaces/:workspaceId/connections/:connectionId/github/repositories",
|
|
3284
|
+
},
|
|
1706
3285
|
{
|
|
1707
3286
|
pattern: /^\/v1\/workspaces\/[^/]+\/integrations\/oauth\/start$/,
|
|
1708
3287
|
label: "/v1/workspaces/:workspaceId/integrations/oauth/start",
|
|
@@ -1727,6 +3306,18 @@ const routeLabelPatterns: Array<{
|
|
|
1727
3306
|
pattern: /^\/v1\/workspaces\/[^/]+\/connections\/[^/]+$/,
|
|
1728
3307
|
label: "/v1/workspaces/:workspaceId/connections/:connectionId",
|
|
1729
3308
|
},
|
|
3309
|
+
{
|
|
3310
|
+
pattern: /^\/v1\/git\/personal\/[^/]+\/info\/refs$/,
|
|
3311
|
+
label: "/v1/git/personal/:routeId/info/refs",
|
|
3312
|
+
},
|
|
3313
|
+
{
|
|
3314
|
+
pattern: /^\/v1\/git\/personal\/[^/]+\/git-upload-pack$/,
|
|
3315
|
+
label: "/v1/git/personal/:routeId/git-upload-pack",
|
|
3316
|
+
},
|
|
3317
|
+
{
|
|
3318
|
+
pattern: /^\/v1\/git\/personal\/[^/]+\/git-receive-pack$/,
|
|
3319
|
+
label: "/v1/git/personal/:routeId/git-receive-pack",
|
|
3320
|
+
},
|
|
1730
3321
|
{ pattern: /^\/v1\/catalog-assets\/.+$/, label: "/v1/catalog-assets/*" },
|
|
1731
3322
|
{
|
|
1732
3323
|
pattern: /^\/v1\/integrations\/oauth\/callback$/,
|
|
@@ -1736,6 +3327,10 @@ const routeLabelPatterns: Array<{
|
|
|
1736
3327
|
pattern: /^\/v1\/integrations\/provider-oauth\/callback$/,
|
|
1737
3328
|
label: "/v1/integrations/provider-oauth/callback",
|
|
1738
3329
|
},
|
|
3330
|
+
{
|
|
3331
|
+
pattern: /^\/v1\/integrations\/github-personal\/oauth\/callback$/,
|
|
3332
|
+
label: "/v1/integrations/github-personal/oauth/callback",
|
|
3333
|
+
},
|
|
1739
3334
|
{
|
|
1740
3335
|
pattern: /^\/v1\/integrations\/google-drive\/callback$/,
|
|
1741
3336
|
label: "/v1/integrations/google-drive/callback",
|
|
@@ -1776,6 +3371,14 @@ const routeLabelPatterns: Array<{
|
|
|
1776
3371
|
pattern: /^\/v1\/workspaces\/[^/]+\/machines\/[^/]+\/metrics\/series$/,
|
|
1777
3372
|
label: "/v1/workspaces/:workspaceId/machines/:enrollmentId/metrics/series",
|
|
1778
3373
|
},
|
|
3374
|
+
{
|
|
3375
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/machines\/[^/]+\/operation-policy$/,
|
|
3376
|
+
label: "/v1/workspaces/:workspaceId/machines/:enrollmentId/operation-policy",
|
|
3377
|
+
},
|
|
3378
|
+
{
|
|
3379
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/machines\/[^/]+\/update$/,
|
|
3380
|
+
label: "/v1/workspaces/:workspaceId/machines/:enrollmentId/update",
|
|
3381
|
+
},
|
|
1779
3382
|
{
|
|
1780
3383
|
pattern: /^\/v1\/workspaces\/[^/]+\/machines$/,
|
|
1781
3384
|
label: "/v1/workspaces/:workspaceId/machines",
|
|
@@ -1793,18 +3396,105 @@ const routeLabelPatterns: Array<{
|
|
|
1793
3396
|
pattern: /^\/v1\/github\/oauth\/callback$/,
|
|
1794
3397
|
label: "/v1/github/oauth/callback",
|
|
1795
3398
|
},
|
|
3399
|
+
{ pattern: /^\/v1\/pr-review\/github\/setup$/, label: "/v1/pr-review/github/setup" },
|
|
3400
|
+
{
|
|
3401
|
+
pattern: /^\/v1\/pr-review\/github\/install\/callback$/,
|
|
3402
|
+
label: "/v1/pr-review/github/install/callback",
|
|
3403
|
+
},
|
|
3404
|
+
{
|
|
3405
|
+
pattern: /^\/v1\/pr-review\/github\/oauth\/callback$/,
|
|
3406
|
+
label: "/v1/pr-review/github/oauth/callback",
|
|
3407
|
+
},
|
|
3408
|
+
{
|
|
3409
|
+
pattern: /^\/v1\/webhooks\/pr-review\/github$/,
|
|
3410
|
+
label: "/v1/webhooks/pr-review/github",
|
|
3411
|
+
},
|
|
3412
|
+
{
|
|
3413
|
+
pattern: /^\/v1\/workspaces\/[^/]+$/,
|
|
3414
|
+
label: "/v1/workspaces/:workspaceId",
|
|
3415
|
+
},
|
|
1796
3416
|
];
|
|
1797
3417
|
|
|
1798
|
-
|
|
3418
|
+
/**
|
|
3419
|
+
* Bounded route label for metrics, spans, and request logs. Explicit patterns
|
|
3420
|
+
* keep established label spellings stable; any other request answered by a
|
|
3421
|
+
* registered handler uses that handler's code-owned path template (see
|
|
3422
|
+
* `registeredHandlerRoutePath`). Only unregistered paths fall into `unknown`.
|
|
3423
|
+
*/
|
|
3424
|
+
export function routeLabel(pathname: string, registeredRoutePath?: string | null): string {
|
|
3425
|
+
if (/^\/v1\/workspaces\/[^/]+\/transcriptions$/.test(pathname))
|
|
3426
|
+
return "/v1/workspaces/:workspaceId/transcriptions";
|
|
3427
|
+
const transcription = pathname.match(
|
|
3428
|
+
/^\/v1\/workspaces\/[^/]+\/transcription-recordings(?:\/[^/]+(\/(?:finalize|process-next)|\/chunks\/\d+)?)?$/,
|
|
3429
|
+
);
|
|
3430
|
+
if (transcription) {
|
|
3431
|
+
const base = "/v1/workspaces/:workspaceId/transcription-recordings";
|
|
3432
|
+
if (pathname.split("/").length === 5) return base;
|
|
3433
|
+
return (
|
|
3434
|
+
base +
|
|
3435
|
+
"/:recordingId" +
|
|
3436
|
+
(transcription[1]?.startsWith("/chunks/") ? "/chunks/:chunkNumber" : (transcription[1] ?? ""))
|
|
3437
|
+
);
|
|
3438
|
+
}
|
|
1799
3439
|
for (const candidate of routeLabelPatterns) {
|
|
1800
3440
|
const match = pathname.match(candidate.pattern);
|
|
1801
3441
|
if (match) {
|
|
1802
3442
|
return typeof candidate.label === "string" ? candidate.label : candidate.label(match);
|
|
1803
3443
|
}
|
|
1804
3444
|
}
|
|
3445
|
+
const registered = boundedRegisteredRouteLabel(registeredRoutePath);
|
|
3446
|
+
if (registered) return registered;
|
|
1805
3447
|
return pathname.startsWith("/v1/") ? "/v1/unknown" : "/unknown";
|
|
1806
3448
|
}
|
|
1807
3449
|
|
|
3450
|
+
/**
|
|
3451
|
+
* API contract revisions no longer admitted from ANY caller, including bearer
|
|
3452
|
+
* integrations that would otherwise be accepted across revisions. Add a
|
|
3453
|
+
* revision here only for a truly breaking wire change (which also requires a
|
|
3454
|
+
* major release-train change); ordinary revision bumps stay additive.
|
|
3455
|
+
*/
|
|
3456
|
+
export const REFUSED_API_CONTRACT_REVISIONS: ReadonlySet<string> = new Set<string>([]);
|
|
3457
|
+
|
|
3458
|
+
const BEARER_AUTHORIZATION = /^bearer\s+\S/i;
|
|
3459
|
+
|
|
3460
|
+
/**
|
|
3461
|
+
* The rollout fence for state-changing product calls.
|
|
3462
|
+
*
|
|
3463
|
+
* The exact-revision check exists to stop a stale first-party browser tab
|
|
3464
|
+
* (cookie session, or unauthenticated local mode) from writing with an old
|
|
3465
|
+
* request shape after a deployment; it answers 409 so the page reloads onto
|
|
3466
|
+
* the matching bundle. That tab never sends `Authorization`, so this is not a
|
|
3467
|
+
* header a stale bundle can choose to bypass it with.
|
|
3468
|
+
*
|
|
3469
|
+
* Bearer-authenticated callers (organization/workspace API keys, delegated
|
|
3470
|
+
* tokens, the deployment key sent as a bearer) are integrations pinned to an
|
|
3471
|
+
* SDK version: reloading cannot upgrade them, and the API is additive within a
|
|
3472
|
+
* major release train. They are admitted with an older revision or with no
|
|
3473
|
+
* revision claim at all. Only a revision listed in
|
|
3474
|
+
* `REFUSED_API_CONTRACT_REVISIONS` is refused for them.
|
|
3475
|
+
*
|
|
3476
|
+
* This is a compatibility fence, not an authorization boundary: every route
|
|
3477
|
+
* still authenticates and authorizes the request independently.
|
|
3478
|
+
*/
|
|
3479
|
+
export function apiContractAdmission(
|
|
3480
|
+
input: {
|
|
3481
|
+
method: string;
|
|
3482
|
+
pathname: string;
|
|
3483
|
+
authorization: string | undefined;
|
|
3484
|
+
claimedRevision: string | undefined;
|
|
3485
|
+
},
|
|
3486
|
+
refusedRevisions: ReadonlySet<string> = REFUSED_API_CONTRACT_REVISIONS,
|
|
3487
|
+
): "admit" | "reject" {
|
|
3488
|
+
if (!isApiContractProtectedMutation(input.method, input.pathname)) return "admit";
|
|
3489
|
+
if (input.claimedRevision === OPENGENI_API_CONTRACT_REVISION) return "admit";
|
|
3490
|
+
if (input.claimedRevision !== undefined && refusedRevisions.has(input.claimedRevision)) {
|
|
3491
|
+
return "reject";
|
|
3492
|
+
}
|
|
3493
|
+
return input.authorization !== undefined && BEARER_AUTHORIZATION.test(input.authorization)
|
|
3494
|
+
? "admit"
|
|
3495
|
+
: "reject";
|
|
3496
|
+
}
|
|
3497
|
+
|
|
1808
3498
|
/**
|
|
1809
3499
|
* State-changing OpenGeni HTTP calls must never cross an incompatible rollout
|
|
1810
3500
|
* boundary. Standard third-party protocols and externally initiated callbacks
|
|
@@ -1817,19 +3507,47 @@ export function isApiContractProtectedMutation(method: string, pathname: string)
|
|
|
1817
3507
|
if (!pathname.startsWith("/v1/")) {
|
|
1818
3508
|
return false;
|
|
1819
3509
|
}
|
|
3510
|
+
if (
|
|
3511
|
+
pathname === "/v1/auth/organization-onboarding" ||
|
|
3512
|
+
pathname === "/v1/auth/organization-setup"
|
|
3513
|
+
) {
|
|
3514
|
+
return true;
|
|
3515
|
+
}
|
|
1820
3516
|
if (
|
|
1821
3517
|
pathname.startsWith("/v1/auth/") ||
|
|
1822
3518
|
pathname.startsWith("/v1/webhooks/") ||
|
|
1823
3519
|
pathname.startsWith("/v1/integrations/oauth/") ||
|
|
3520
|
+
isPersonalGitHubGitBrokerRequest(method, pathname) ||
|
|
1824
3521
|
pathname === "/v1/integrations/slack/events" ||
|
|
1825
3522
|
pathname === "/v1/integrations/slack/commands" ||
|
|
1826
3523
|
pathname === "/v1/integrations/slack/interactions" ||
|
|
1827
3524
|
pathname.startsWith("/v1/github/") ||
|
|
3525
|
+
// A stale tab must still report the error that follows a rollout.
|
|
3526
|
+
pathname === CLIENT_ERRORS_PATH ||
|
|
3527
|
+
pathname === ANALYTICS_CONSENT_PATH ||
|
|
1828
3528
|
pathname === "/v1/enrollments/device/start" ||
|
|
1829
3529
|
pathname === "/v1/enrollments/device/poll" ||
|
|
1830
|
-
pathname === "/v1/enrollments/token/exchange"
|
|
3530
|
+
pathname === "/v1/enrollments/token/exchange" ||
|
|
3531
|
+
// The runner authenticates this protocol with its install-key proof, not
|
|
3532
|
+
// a browser cookie or the product-client revision header.
|
|
3533
|
+
pathname === "/v1/enrollments/renew"
|
|
1831
3534
|
) {
|
|
1832
3535
|
return false;
|
|
1833
3536
|
}
|
|
1834
|
-
|
|
3537
|
+
const segments = pathname.split("/");
|
|
3538
|
+
return !segments.includes("mcp") && !segments.includes("codemode");
|
|
3539
|
+
}
|
|
3540
|
+
|
|
3541
|
+
/** Client-safe agent-configuration projection. */
|
|
3542
|
+
function clientAgentConfig(settings: Settings): ClientAgentConfig {
|
|
3543
|
+
const policy = agentConfigDeploymentPolicy(settings);
|
|
3544
|
+
return {
|
|
3545
|
+
// Deprecated: agent configuration is always on. Kept for client compatibility.
|
|
3546
|
+
enabled: true,
|
|
3547
|
+
defaultForNewSessions: true,
|
|
3548
|
+
capabilities: AGENT_CAPABILITY_IDS.map((id) => {
|
|
3549
|
+
const reason = policy.unavailable[id];
|
|
3550
|
+
return reason === undefined ? { id, available: true } : { id, available: false, reason };
|
|
3551
|
+
}),
|
|
3552
|
+
};
|
|
1835
3553
|
}
|