@opengeni/api-router 0.30.3 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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-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 +1091 -162
- 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 +24 -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 +107 -9
- 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 +59 -2
- 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/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 +1967 -252
- 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 +28 -3
- 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 +717 -225
- 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 +3167 -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 -43
- 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 +780 -112
- 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 +1039 -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 +267 -33
- 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-WCXHI3FF.js +0 -49920
- package/dist/chunk-WCXHI3FF.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,25 +1,52 @@
|
|
|
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,
|
|
@@ -28,44 +55,146 @@ import {
|
|
|
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;
|
|
@@ -224,16 +450,6 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
224
450
|
workspaceId,
|
|
225
451
|
documentId,
|
|
226
452
|
getDocumentServices(),
|
|
227
|
-
{
|
|
228
|
-
beforeEmbed: async ({ chunkCount }) => {
|
|
229
|
-
await requireLimit(routeDeps, {
|
|
230
|
-
accountId,
|
|
231
|
-
workspaceId,
|
|
232
|
-
action: "document:index",
|
|
233
|
-
quantity: chunkCount,
|
|
234
|
-
});
|
|
235
|
-
},
|
|
236
|
-
},
|
|
237
453
|
{ viewerSubjectId: authoritySubjectId },
|
|
238
454
|
);
|
|
239
455
|
if (
|
|
@@ -252,8 +468,24 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
252
468
|
// concrete for routes; it throws SandboxResumeError when sandboxBackend=none.
|
|
253
469
|
const sandboxClient = deps.sandboxClient ?? createApiSandboxClient(deps.settings);
|
|
254
470
|
const resumeBoxById = deps.resumeBoxById ?? makeResumeBoxById(sandboxClient);
|
|
255
|
-
|
|
256
|
-
|
|
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
|
+
}
|
|
257
489
|
const transcription =
|
|
258
490
|
deps.transcription === undefined
|
|
259
491
|
? createTranscriptionService({
|
|
@@ -268,12 +500,26 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
268
500
|
ffmpegPath: deps.settings.voiceInputFfmpegPath,
|
|
269
501
|
})
|
|
270
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;
|
|
271
513
|
const routeDeps: ApiRouteDeps = {
|
|
272
514
|
...deps,
|
|
515
|
+
resolveCatalogSettings: () => resolveCatalogSettings(deps.db, deps.settings),
|
|
273
516
|
observability,
|
|
517
|
+
userPresence,
|
|
274
518
|
githubStateSecret:
|
|
275
519
|
deps.githubStateSecret ?? deps.settings.githubAppManifestStateSecret ?? crypto.randomUUID(),
|
|
276
520
|
managedAuth,
|
|
521
|
+
managedAuthSessionAdapter,
|
|
522
|
+
managedEmailTransport,
|
|
277
523
|
objectStorage,
|
|
278
524
|
documentIndexer,
|
|
279
525
|
getDocumentServices,
|
|
@@ -283,16 +529,107 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
283
529
|
resumeBoxById,
|
|
284
530
|
};
|
|
285
531
|
const app = new Hono();
|
|
286
|
-
|
|
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
|
+
};
|
|
287
579
|
|
|
288
580
|
app.use("*", async (c, next) => {
|
|
289
581
|
const correlationId =
|
|
290
582
|
boundedCorrelationId(c.req.header(OPENGENI_CORRELATION_HEADER)) ?? crypto.randomUUID();
|
|
291
|
-
correlationIds.set(c
|
|
583
|
+
correlationIds.set(c, correlationId);
|
|
292
584
|
c.header(OPENGENI_CORRELATION_HEADER, correlationId);
|
|
293
585
|
await next();
|
|
294
586
|
});
|
|
295
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
|
+
|
|
296
633
|
const corsHeaders = {
|
|
297
634
|
allowHeaders: [
|
|
298
635
|
"Accept",
|
|
@@ -301,14 +638,39 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
301
638
|
"Range",
|
|
302
639
|
"X-OpenGeni-Access-Key",
|
|
303
640
|
"X-OpenGeni-Api-Contract",
|
|
641
|
+
"X-OpenGeni-Actor-Epoch",
|
|
304
642
|
"X-OpenGeni-Correlation-Id",
|
|
643
|
+
"X-OpenGeni-Session-Csrf",
|
|
644
|
+
"X-OpenGeni-Site-Id",
|
|
645
|
+
"X-OpenGeni-Site-Version",
|
|
305
646
|
"X-OpenGeni-Subject",
|
|
647
|
+
"X-OpenGeni-User-Activity",
|
|
306
648
|
],
|
|
307
649
|
exposeHeaders: [
|
|
308
650
|
"Accept-Ranges",
|
|
309
651
|
"Content-Range",
|
|
652
|
+
"Deprecation",
|
|
653
|
+
"Link",
|
|
654
|
+
"Sunset",
|
|
310
655
|
"X-OpenGeni-Api-Contract",
|
|
656
|
+
"X-OpenGeni-Actor-Epoch",
|
|
657
|
+
"X-OpenGeni-Actor-State",
|
|
311
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",
|
|
312
674
|
],
|
|
313
675
|
};
|
|
314
676
|
const publicApiCors = cors({
|
|
@@ -323,8 +685,25 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
323
685
|
allowedCorsOrigin(deps.settings.corsAllowOriginRegex, origin) ? origin : null,
|
|
324
686
|
});
|
|
325
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
|
+
|
|
326
699
|
app.use("*", (c, next) => {
|
|
327
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
|
+
}
|
|
328
707
|
const middleware =
|
|
329
708
|
origin && allowedCorsOrigin(deps.settings.corsAllowOriginRegex, origin)
|
|
330
709
|
? credentialedCors
|
|
@@ -332,14 +711,32 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
332
711
|
return middleware(c, next);
|
|
333
712
|
});
|
|
334
713
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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
|
+
});
|
|
343
740
|
|
|
344
741
|
// Large catalog, capture, and session-list responses are on the browser's
|
|
345
742
|
// critical path. Compress JSON at the API boundary while leaving SSE and
|
|
@@ -355,90 +752,146 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
355
752
|
}
|
|
356
753
|
});
|
|
357
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
|
+
|
|
358
763
|
app.use("*", async (c, next) => {
|
|
359
764
|
const url = new URL(c.req.url);
|
|
360
|
-
const route = routeLabel(url.pathname);
|
|
361
|
-
const correlationId = correlationIds.get(c
|
|
765
|
+
const route = routeLabel(url.pathname, registeredHandlerRoutePath(c));
|
|
766
|
+
const correlationId = correlationIds.get(c) ?? crypto.randomUUID();
|
|
362
767
|
const start = performance.now();
|
|
363
|
-
const span = observability.startSpan(
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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)) {
|
|
368
837
|
await next();
|
|
369
|
-
|
|
370
|
-
const durationSeconds = (performance.now() - start) / 1000;
|
|
371
|
-
observability.recordHttpRequest({
|
|
372
|
-
method: c.req.method,
|
|
373
|
-
route,
|
|
374
|
-
status,
|
|
375
|
-
durationSeconds,
|
|
376
|
-
});
|
|
377
|
-
span.end({
|
|
378
|
-
attributes: {
|
|
379
|
-
"http.response.status_code": status,
|
|
380
|
-
"opengeni.duration_ms": Math.round(durationSeconds * 1000),
|
|
381
|
-
},
|
|
382
|
-
});
|
|
383
|
-
observability.info("HTTP request completed", {
|
|
384
|
-
method: c.req.method,
|
|
385
|
-
route,
|
|
386
|
-
status,
|
|
387
|
-
durationMs: Math.round(durationSeconds * 1000),
|
|
388
|
-
traceId: span.traceId,
|
|
389
|
-
spanId: span.spanId,
|
|
390
|
-
correlationId,
|
|
391
|
-
});
|
|
392
|
-
} catch (error) {
|
|
393
|
-
const status = httpStatusForError(error);
|
|
394
|
-
const errorCode = errorCodeForStatus(status);
|
|
395
|
-
const durationSeconds = (performance.now() - start) / 1000;
|
|
396
|
-
observability.recordHttpRequest({
|
|
397
|
-
method: c.req.method,
|
|
398
|
-
route,
|
|
399
|
-
status,
|
|
400
|
-
durationSeconds,
|
|
401
|
-
});
|
|
402
|
-
observability.incrementCounter({
|
|
403
|
-
name: "opengeni_http_errors_total",
|
|
404
|
-
help: "Total OpenGeni HTTP request failures by bounded route, status, and stable code.",
|
|
405
|
-
labels: { route, status: String(status), code: errorCode },
|
|
406
|
-
});
|
|
407
|
-
span.end({
|
|
408
|
-
attributes: {
|
|
409
|
-
"http.response.status_code": status,
|
|
410
|
-
"opengeni.duration_ms": Math.round(durationSeconds * 1000),
|
|
411
|
-
},
|
|
412
|
-
error,
|
|
413
|
-
});
|
|
414
|
-
observability.error("HTTP request failed", {
|
|
415
|
-
method: c.req.method,
|
|
416
|
-
route,
|
|
417
|
-
status,
|
|
418
|
-
durationMs: Math.round(durationSeconds * 1000),
|
|
419
|
-
traceId: span.traceId,
|
|
420
|
-
spanId: span.spanId,
|
|
421
|
-
correlationId,
|
|
422
|
-
errorCode,
|
|
423
|
-
errorClass: "HttpOperationError",
|
|
424
|
-
});
|
|
425
|
-
throw error;
|
|
838
|
+
return;
|
|
426
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();
|
|
427
847
|
});
|
|
428
848
|
|
|
429
|
-
|
|
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
|
+
});
|
|
430
879
|
|
|
431
880
|
app.use("/v1/*", async (c, next) => {
|
|
432
881
|
c.header(OPENGENI_API_CONTRACT_HEADER, OPENGENI_API_CONTRACT_REVISION);
|
|
433
882
|
if (
|
|
434
883
|
deps.settings.environment !== "test" &&
|
|
435
|
-
|
|
436
|
-
|
|
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"
|
|
437
890
|
) {
|
|
438
891
|
return c.json(
|
|
439
892
|
{
|
|
440
893
|
code: "API_CONTRACT_CHANGED",
|
|
441
|
-
message: "
|
|
894
|
+
message: "Opengeni updated. Reload this client before changing state.",
|
|
442
895
|
apiContractRevision: OPENGENI_API_CONTRACT_REVISION,
|
|
443
896
|
},
|
|
444
897
|
409,
|
|
@@ -447,19 +900,255 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
447
900
|
await next();
|
|
448
901
|
});
|
|
449
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);
|
|
450
938
|
if (managedAuth) {
|
|
451
|
-
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
|
+
});
|
|
452
1139
|
}
|
|
453
1140
|
|
|
454
|
-
app.get("/healthz", (c) =>
|
|
455
|
-
|
|
1141
|
+
app.get("/healthz", (c) => {
|
|
1142
|
+
const warnings = githubAppBotIdentityWarnings(deps.settings);
|
|
1143
|
+
return c.json({
|
|
456
1144
|
service: deps.settings.serviceName,
|
|
457
1145
|
environment: deps.settings.environment,
|
|
458
1146
|
deploymentRevision: deps.settings.deploymentRevision,
|
|
459
1147
|
...(deps.settings.serverVersion ? { serverVersion: deps.settings.serverVersion } : {}),
|
|
1148
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
460
1149
|
ok: true,
|
|
461
|
-
})
|
|
462
|
-
);
|
|
1150
|
+
});
|
|
1151
|
+
});
|
|
463
1152
|
|
|
464
1153
|
app.get("/readyz", async (c) => {
|
|
465
1154
|
const result = await runReadinessChecks(readinessChecks(deps), 2_000);
|
|
@@ -472,44 +1161,146 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
472
1161
|
return c.json(result, result.ok ? 200 : 503);
|
|
473
1162
|
});
|
|
474
1163
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
);
|
|
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 });
|
|
480
1172
|
|
|
481
1173
|
app.get("/v1/config/client", async (c) => {
|
|
482
1174
|
c.header("cache-control", "no-store");
|
|
483
|
-
const
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
:
|
|
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;
|
|
489
1272
|
return c.json(
|
|
490
1273
|
ClientConfig.parse({
|
|
491
1274
|
deploymentRevision: deps.settings.deploymentRevision,
|
|
1275
|
+
claudeSubscriptionEnabled: deps.settings.claudeSubscriptionEnabled,
|
|
492
1276
|
apiContractRevision: OPENGENI_API_CONTRACT_REVISION,
|
|
493
1277
|
...(deps.settings.serverVersion ? { serverVersion: deps.settings.serverVersion } : {}),
|
|
494
|
-
defaultModel
|
|
495
|
-
allowedModels:
|
|
496
|
-
|
|
497
|
-
//
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
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,
|
|
502
1287
|
allowedReasoningEfforts: configuredAllowedReasoningEfforts(deps.settings),
|
|
1288
|
+
defaultSandboxBackend: deps.settings.sandboxBackend,
|
|
503
1289
|
mcpServers: deps.settings.mcpServers.map((server) => ({
|
|
504
1290
|
id: server.id,
|
|
505
1291
|
name: server.name ?? server.id,
|
|
506
1292
|
})),
|
|
507
1293
|
firstPartyMcpTools: resolveFirstPartyMcpToolPolicy(deps.settings),
|
|
1294
|
+
codeSearch: codeSearchDeploymentPolicy(deps.settings),
|
|
1295
|
+
agentConfig: clientAgentConfig(deps.settings),
|
|
508
1296
|
fileUploads: {
|
|
509
1297
|
enabled: objectStorage !== null,
|
|
510
1298
|
maxSizeBytes: objectStorage?.maxSinglePutSizeBytes ?? 5_000_000_000,
|
|
511
1299
|
},
|
|
512
1300
|
voiceInput: {
|
|
1301
|
+
providers: resolveVoiceInputProviderRegistry(deps.settings).map(
|
|
1302
|
+
(provider) => provider.id,
|
|
1303
|
+
),
|
|
513
1304
|
available: (await transcription?.available()) ?? false,
|
|
514
1305
|
maxDurationSeconds: deps.settings.voiceInputMaxDurationSeconds,
|
|
515
1306
|
maxSizeBytes: deps.settings.voiceInputMaxSizeBytes,
|
|
@@ -534,8 +1325,18 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
534
1325
|
: {}),
|
|
535
1326
|
},
|
|
536
1327
|
productAccessMode: deps.settings.productAccessMode,
|
|
537
|
-
|
|
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,
|
|
538
1337
|
analytics: clientAnalyticsConfig(deps.settings),
|
|
1338
|
+
legal: clientLegalConfig(deps.settings),
|
|
1339
|
+
...(deps.settings.supportEmail ? { supportEmail: deps.settings.supportEmail } : {}),
|
|
539
1340
|
// Channel-A structured services (P4.4) ride exec/readFile/createEditor,
|
|
540
1341
|
// available on every real backend; `none` has no box so they are all off.
|
|
541
1342
|
// Per-session availability is still negotiated on /stream-capabilities.
|
|
@@ -544,6 +1345,67 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
544
1345
|
);
|
|
545
1346
|
});
|
|
546
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
|
+
|
|
547
1409
|
app.use("/v1/workspaces/:workspaceId/*", async (c, next) => {
|
|
548
1410
|
const workspaceId = c.req.param("workspaceId");
|
|
549
1411
|
if (workspaceRequestRequiresCodexAccountPrevalidation(c.req.raw)) {
|
|
@@ -562,6 +1424,27 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
562
1424
|
|
|
563
1425
|
app.all("/v1/workspaces/:workspaceId/mcp", async (c) => {
|
|
564
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
|
+
};
|
|
565
1448
|
let boundedRequest: Request;
|
|
566
1449
|
try {
|
|
567
1450
|
boundedRequest = await boundedMcpRequest(c.req.raw);
|
|
@@ -573,42 +1456,253 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
573
1456
|
}
|
|
574
1457
|
throw error;
|
|
575
1458
|
}
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
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
|
+
);
|
|
580
1487
|
try {
|
|
581
|
-
await
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
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",
|
|
593
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;
|
|
594
1530
|
}
|
|
595
|
-
throw error;
|
|
596
1531
|
}
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
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 };
|
|
608
1699
|
});
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
}
|
|
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));
|
|
612
1706
|
});
|
|
613
1707
|
|
|
614
1708
|
app.get("/v1/workspaces/:workspaceId/codemode/catalog", async (c) => {
|
|
@@ -664,27 +1758,42 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
664
1758
|
}
|
|
665
1759
|
});
|
|
666
1760
|
|
|
1761
|
+
registerConnectCallbackReturns(app, routeDeps);
|
|
667
1762
|
registerFileRoutes(app, routeDeps);
|
|
1763
|
+
registerSessionArtifactAssociationRoutes(app, routeDeps);
|
|
668
1764
|
registerApiKeyRoutes(app, routeDeps);
|
|
1765
|
+
registerOrganizationMcpConnectionRoutes(app, routeDeps);
|
|
1766
|
+
registerOrganizationServiceAccountRoutes(app, routeDeps);
|
|
669
1767
|
registerBillingRoutes(app, routeDeps);
|
|
670
1768
|
registerBrowserIdentityRoutes(app, routeDeps);
|
|
671
1769
|
registerBrowserSessionRoutes(app, routeDeps);
|
|
672
1770
|
registerComputerSessionRoutes(app, routeDeps);
|
|
673
1771
|
registerDocumentRoutes(app, routeDeps);
|
|
1772
|
+
registerKnowledgeRoutes(app, routeDeps);
|
|
674
1773
|
registerGitHubRoutes(app, routeDeps);
|
|
675
1774
|
registerInstallRoutes(app, routeDeps);
|
|
676
1775
|
registerInteractionResourceRoutes(app, routeDeps);
|
|
677
1776
|
registerWorkspaceRoutes(app, routeDeps);
|
|
1777
|
+
registerUsageAllowanceRoutes(app, routeDeps);
|
|
678
1778
|
registerInsightsRoutes(app, routeDeps);
|
|
1779
|
+
registerInsightsUsageRoutes(app, routeDeps);
|
|
679
1780
|
registerWorkspaceInstructionPolicyRoutes(app, routeDeps);
|
|
1781
|
+
registerWorkspaceLearningRoutes(app, routeDeps);
|
|
680
1782
|
registerCompanyProfileRoutes(app, routeDeps);
|
|
1783
|
+
registerCompanyBrainRoutes(app, routeDeps);
|
|
681
1784
|
registerSlackTaskPolicyRoutes(app, routeDeps);
|
|
682
1785
|
registerWorkspaceStateRoutes(app, routeDeps);
|
|
683
1786
|
registerMemorySlackPublicationRoutes(app, routeDeps);
|
|
684
1787
|
registerWorkspaceArtifactRoutes(app, routeDeps);
|
|
1788
|
+
registerArtifactCatalogRoutes(app, routeDeps);
|
|
685
1789
|
registerPreferenceRegistryRoutes(app, routeDeps);
|
|
686
1790
|
registerSocialRoutes(app, routeDeps);
|
|
1791
|
+
registerPersonalGitHubRoutes(app, routeDeps);
|
|
1792
|
+
registerPersonalGitHubGitBrokerRoutes(app, routeDeps);
|
|
687
1793
|
registerConnectionRoutes(app, routeDeps);
|
|
1794
|
+
registerConnectRoutes(app, routeDeps);
|
|
1795
|
+
|
|
1796
|
+
registerExternalIdentityLinkRoutes(app, routeDeps);
|
|
688
1797
|
registerCapabilityRoutes(app, routeDeps);
|
|
689
1798
|
registerApiIntegrationRoutes(app, routeDeps);
|
|
690
1799
|
registerIntegrationFacetRoutes(app, routeDeps);
|
|
@@ -694,23 +1803,37 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
694
1803
|
registerEnvironmentRoutes(app, routeDeps);
|
|
695
1804
|
registerChannelRoutes(app, routeDeps);
|
|
696
1805
|
registerRigRoutes(app, routeDeps);
|
|
697
|
-
|
|
1806
|
+
registerAutomationRoutes(app, routeDeps);
|
|
1807
|
+
registerPrReviewRoutes(app, routeDeps);
|
|
698
1808
|
registerPluginRoutes(app, routeDeps);
|
|
699
1809
|
registerSkillRoutes(app, routeDeps);
|
|
700
1810
|
registerSessionRoutes(app, routeDeps);
|
|
1811
|
+
registerSessionHistoryImportRoutes(app, routeDeps);
|
|
1812
|
+
registerFeedbackRoutes(app, routeDeps);
|
|
1813
|
+
registerWorkspaceIntegrationRoutes(app, routeDeps);
|
|
1814
|
+
registerOrganizationIntegrationRoutes(app, routeDeps);
|
|
701
1815
|
registerScheduledTaskRoutes(app, routeDeps);
|
|
702
1816
|
registerCodexRoutes(app, routeDeps);
|
|
1817
|
+
registerOrganizationModelProviderRoutes(app, routeDeps);
|
|
1818
|
+
registerWorkspaceModelProviderRoutes(app, routeDeps);
|
|
1819
|
+
registerClaudeSubscriptionOAuthRoutes(app, routeDeps);
|
|
1820
|
+
registerOrganizationIntegrationPolicyRoutes(app, routeDeps);
|
|
1821
|
+
registerModelConnectionAccessRoutes(app, routeDeps);
|
|
703
1822
|
registerSuperGrokRoutes(app, routeDeps);
|
|
1823
|
+
registerClaudeSubscriptionAccountRoutes(app, routeDeps);
|
|
704
1824
|
registerTranscriptionRoutes(app, routeDeps);
|
|
705
1825
|
registerEditableArtifactRoutes(app, routeDeps);
|
|
706
1826
|
registerVideoGenerationRoutes(app, routeDeps);
|
|
707
1827
|
registerCanonicalHumanIdentityRoutes(app, routeDeps);
|
|
708
1828
|
registerOrganizationMembershipRoutes(app, routeDeps);
|
|
1829
|
+
registerOrganizationSessionRoutes(app, routeDeps);
|
|
1830
|
+
registerOrganizationRecoveryRoutes(app, routeDeps);
|
|
1831
|
+
registerUserResourceAuthorityRoutes(app, routeDeps);
|
|
709
1832
|
registerSlackInteractionRoutes(app, routeDeps);
|
|
710
1833
|
|
|
711
1834
|
app.notFound((c) => {
|
|
712
1835
|
if (!new URL(c.req.url).pathname.startsWith("/v1/")) return c.text("Not Found", 404);
|
|
713
|
-
const requestId = correlationIds.get(c
|
|
1836
|
+
const requestId = correlationIds.get(c) ?? crypto.randomUUID();
|
|
714
1837
|
return c.json(
|
|
715
1838
|
ErrorEnvelope.parse({
|
|
716
1839
|
error: {
|
|
@@ -725,18 +1848,49 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
725
1848
|
);
|
|
726
1849
|
});
|
|
727
1850
|
|
|
728
|
-
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);
|
|
729
1887
|
const compactionLock = codexCompactionV2ProviderLockedError(error);
|
|
730
1888
|
const apiError = error instanceof ApiHttpError ? error : null;
|
|
731
1889
|
const status = compactionLock ? 422 : httpStatusForError(error);
|
|
732
1890
|
const code: ErrorCode = compactionLock
|
|
733
1891
|
? compactionLock.code
|
|
734
1892
|
: (apiError?.code ?? errorCodeForStatus(status));
|
|
735
|
-
|
|
736
|
-
c.header(OPENGENI_CORRELATION_HEADER, requestId);
|
|
737
|
-
if (new URL(c.req.url).pathname.startsWith("/v1/")) {
|
|
738
|
-
c.header(OPENGENI_API_CONTRACT_HEADER, OPENGENI_API_CONTRACT_REVISION);
|
|
739
|
-
}
|
|
1893
|
+
if (status >= 500) logHttpFailure(c, status, code, rawError);
|
|
740
1894
|
const envelope = ErrorEnvelope.parse({
|
|
741
1895
|
error: {
|
|
742
1896
|
status,
|
|
@@ -747,7 +1901,9 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
747
1901
|
? (boundedPublicMessage(apiError.message) ?? "Request failed.")
|
|
748
1902
|
: publicErrorMessage(error, status),
|
|
749
1903
|
retryable: apiError?.retryable ?? retryableHttpStatus(status),
|
|
750
|
-
...(mutationOutcomeUnknown(error, c.req.method)
|
|
1904
|
+
...((apiError?.outcomeUnknown ?? mutationOutcomeUnknown(error, c.req.method))
|
|
1905
|
+
? { outcomeUnknown: true }
|
|
1906
|
+
: {}),
|
|
751
1907
|
requestId,
|
|
752
1908
|
...(apiError?.details ? { details: apiError.details } : {}),
|
|
753
1909
|
},
|
|
@@ -758,6 +1914,11 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
758
1914
|
return { app, routeDeps };
|
|
759
1915
|
}
|
|
760
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
|
+
|
|
761
1922
|
function mutationOutcomeUnknown(error: unknown, method: string): boolean {
|
|
762
1923
|
if (method === "GET" || method === "HEAD" || method === "OPTIONS") return false;
|
|
763
1924
|
const cause = error instanceof HTTPException ? error.cause : error;
|
|
@@ -784,7 +1945,41 @@ const publicWorkspaceBrowserRoutePatterns = [
|
|
|
784
1945
|
] as const;
|
|
785
1946
|
|
|
786
1947
|
export function workspaceActorContextExempt(method: string, pathname: string): boolean {
|
|
787
|
-
|
|
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;
|
|
788
1983
|
if (
|
|
789
1984
|
method === "GET" &&
|
|
790
1985
|
publicWorkspaceBrowserRoutePatterns.some((route) => route.test(pathname))
|
|
@@ -813,46 +2008,15 @@ async function validateCodexAccountTarget(request: Request): Promise<void> {
|
|
|
813
2008
|
}
|
|
814
2009
|
}
|
|
815
2010
|
|
|
816
|
-
async function
|
|
817
|
-
deps: ApiRouteDeps,
|
|
818
|
-
grant: AccessGrant,
|
|
819
|
-
fn: () => Promise<T>,
|
|
820
|
-
): Promise<T> {
|
|
821
|
-
if (grant.principalKind !== "agent_attempt") {
|
|
822
|
-
return await withSessionRlsActorContext({ subjectId: grant.subjectId }, fn);
|
|
823
|
-
}
|
|
824
|
-
const callerSessionId = grant.metadata?.sessionId;
|
|
825
|
-
if (typeof callerSessionId !== "string") {
|
|
826
|
-
throw new HTTPException(403, { message: "agent attempt authority is invalid" });
|
|
827
|
-
}
|
|
828
|
-
try {
|
|
829
|
-
const actor = await requireLiveAgentAttemptAuthorization(deps.db, grant, callerSessionId);
|
|
830
|
-
return await withSessionRlsActorContext(
|
|
831
|
-
{
|
|
832
|
-
subjectId: actor.subjectId,
|
|
833
|
-
initiatingHumanSubjectId: actor.initiatingHumanSubjectId,
|
|
834
|
-
},
|
|
835
|
-
fn,
|
|
836
|
-
);
|
|
837
|
-
} catch (error) {
|
|
838
|
-
if (error instanceof SessionAuthorizationDeniedError) {
|
|
839
|
-
throw new HTTPException(403, {
|
|
840
|
-
message: "agent attempt authority is invalid",
|
|
841
|
-
cause: error,
|
|
842
|
-
});
|
|
843
|
-
}
|
|
844
|
-
throw error;
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
async function requireMcpAccessGrant(
|
|
2011
|
+
async function requireMcpAccessGrantAuthorization(
|
|
849
2012
|
c: Parameters<typeof requireAccessGrant>[0],
|
|
850
2013
|
deps: ApiRouteDeps,
|
|
851
2014
|
workspaceId: string,
|
|
852
|
-
): Promise<
|
|
853
|
-
const
|
|
2015
|
+
): Promise<Awaited<ReturnType<typeof requireAccessGrantAuthorization>>> {
|
|
2016
|
+
const authorization = await requireAccessGrantAuthorization(c, deps, workspaceId);
|
|
2017
|
+
const grant = authorization.grant;
|
|
854
2018
|
if (hasPermission(grant.permissions, "workspace:read")) {
|
|
855
|
-
return
|
|
2019
|
+
return authorization;
|
|
856
2020
|
}
|
|
857
2021
|
if (isCodemodeGrant(grant)) {
|
|
858
2022
|
requirePermission(grant, "workspace:read");
|
|
@@ -862,15 +2026,28 @@ async function requireMcpAccessGrant(
|
|
|
862
2026
|
// authorization seam runs immediately after this gate, and tool registration
|
|
863
2027
|
// still exposes only capabilities permitted by the delegated grant.
|
|
864
2028
|
if (grant.metadata?.delegated === true && typeof grant.metadata.sessionId === "string") {
|
|
865
|
-
return
|
|
2029
|
+
return authorization;
|
|
866
2030
|
}
|
|
867
2031
|
requirePermission(grant, "workspace:read");
|
|
868
|
-
return
|
|
2032
|
+
return authorization;
|
|
869
2033
|
}
|
|
870
2034
|
|
|
871
|
-
function clientAuthConfig(settings: AppDependencies["settings"]) {
|
|
2035
|
+
function clientAuthConfig(settings: AppDependencies["settings"], newSignupsEnabled: boolean) {
|
|
872
2036
|
if (settings.productAccessMode === "managed") {
|
|
873
|
-
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
|
+
};
|
|
874
2051
|
}
|
|
875
2052
|
if (settings.productAccessMode === "configured") {
|
|
876
2053
|
return {
|
|
@@ -888,7 +2065,143 @@ function clientAuthConfig(settings: AppDependencies["settings"]) {
|
|
|
888
2065
|
return { mode: "none" as const };
|
|
889
2066
|
}
|
|
890
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
|
+
|
|
891
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
|
+
}
|
|
892
2205
|
if (error instanceof SessionAuthorizationDeniedError) {
|
|
893
2206
|
return new HTTPException(404, {
|
|
894
2207
|
message: "session not found",
|
|
@@ -901,18 +2214,39 @@ function codemodeHttpError(error: unknown): HTTPException {
|
|
|
901
2214
|
cause: error,
|
|
902
2215
|
});
|
|
903
2216
|
}
|
|
904
|
-
if (
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
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) {
|
|
908
2245
|
return new HTTPException(409, { message: error.message, cause: error });
|
|
909
2246
|
}
|
|
910
2247
|
if (error instanceof CodemodeOperationConflictError) {
|
|
911
2248
|
return new HTTPException(409, { message: error.message, cause: error });
|
|
912
2249
|
}
|
|
913
|
-
if (error instanceof CodemodeCallBudgetExceededError) {
|
|
914
|
-
return new HTTPException(429, { message: error.message, cause: error });
|
|
915
|
-
}
|
|
916
2250
|
if (error instanceof CodemodeToolNotInCatalogError) {
|
|
917
2251
|
return new HTTPException(404, { message: error.message, cause: error });
|
|
918
2252
|
}
|
|
@@ -955,6 +2289,15 @@ function clientAnalyticsConfig(settings: AppDependencies["settings"]) {
|
|
|
955
2289
|
};
|
|
956
2290
|
}
|
|
957
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
|
+
|
|
958
2301
|
function structuredServicesHint(backend: string): {
|
|
959
2302
|
fileSystem: boolean;
|
|
960
2303
|
git: boolean;
|
|
@@ -978,9 +2321,17 @@ function codexCompactionV2ProviderLockedError(
|
|
|
978
2321
|
}
|
|
979
2322
|
|
|
980
2323
|
export function httpStatusForError(error: unknown): number {
|
|
2324
|
+
if (error instanceof ConnectAttemptConflictError) return 409;
|
|
2325
|
+
if (error instanceof ConnectAttemptNotFoundError) return 404;
|
|
981
2326
|
if (codexCompactionV2ProviderLockedError(error)) {
|
|
982
2327
|
return 422;
|
|
983
2328
|
}
|
|
2329
|
+
if (workspaceControlBusyHttpError(error)) {
|
|
2330
|
+
return 503;
|
|
2331
|
+
}
|
|
2332
|
+
if (isRequestBodyValidationError(error)) {
|
|
2333
|
+
return 400;
|
|
2334
|
+
}
|
|
984
2335
|
if (error instanceof HTTPException) {
|
|
985
2336
|
return error.status;
|
|
986
2337
|
}
|
|
@@ -994,7 +2345,7 @@ export function errorCodeForStatus(status: number): ErrorCode {
|
|
|
994
2345
|
if (status === 401) return "unauthenticated";
|
|
995
2346
|
if (status === 402) return "payment_required";
|
|
996
2347
|
if (status === 403) return "forbidden";
|
|
997
|
-
if (status === 404) return "not_found";
|
|
2348
|
+
if (status === 404 || status === 405) return "not_found";
|
|
998
2349
|
if (status === 409) return "conflict";
|
|
999
2350
|
if (status === 413 || status === 422 || status === 400) return "validation_failed";
|
|
1000
2351
|
if (status === 429) return "limit_exceeded";
|
|
@@ -1007,11 +2358,14 @@ function retryableHttpStatus(status: number): boolean {
|
|
|
1007
2358
|
}
|
|
1008
2359
|
|
|
1009
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.";
|
|
1010
2364
|
if (status === 502 || status === 503 || status === 504) {
|
|
1011
|
-
return "
|
|
2365
|
+
return "Opengeni is temporarily unavailable — retry.";
|
|
1012
2366
|
}
|
|
1013
2367
|
if (status >= 500) {
|
|
1014
|
-
return "
|
|
2368
|
+
return "Opengeni could not complete the request.";
|
|
1015
2369
|
}
|
|
1016
2370
|
if (error instanceof HTTPException) {
|
|
1017
2371
|
return boundedPublicMessage(error.message) ?? "Request failed.";
|
|
@@ -1041,19 +2395,26 @@ type ReadinessChecks = Record<ReadinessCheckName, ReadinessCheck>;
|
|
|
1041
2395
|
type ReadinessCheckResult = { ok: boolean; error?: string };
|
|
1042
2396
|
|
|
1043
2397
|
function readinessChecks(deps: AppDependencies): ReadinessChecks {
|
|
2398
|
+
const configuredNatsCheck = deps.readinessChecks?.nats;
|
|
1044
2399
|
return {
|
|
1045
|
-
db:
|
|
1046
|
-
deps.readinessChecks?.db
|
|
1047
|
-
|
|
2400
|
+
db: async () => {
|
|
2401
|
+
if (deps.readinessChecks?.db) {
|
|
2402
|
+
await deps.readinessChecks.db();
|
|
2403
|
+
} else {
|
|
1048
2404
|
await deps.db.execute(dbSql`select 1`);
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
2405
|
+
}
|
|
2406
|
+
await resolveCatalogSettings(deps.db, deps.settings);
|
|
2407
|
+
},
|
|
2408
|
+
nats: async () => {
|
|
2409
|
+
requireSessionEventDurableFanoutCapability(deps.bus);
|
|
2410
|
+
if (configuredNatsCheck) {
|
|
2411
|
+
await configuredNatsCheck();
|
|
2412
|
+
} else {
|
|
1053
2413
|
if (deps.bus.isConnected && !deps.bus.isConnected()) {
|
|
1054
2414
|
throw new Error("NATS is not connected");
|
|
1055
2415
|
}
|
|
1056
|
-
}
|
|
2416
|
+
}
|
|
2417
|
+
},
|
|
1057
2418
|
temporal:
|
|
1058
2419
|
deps.readinessChecks?.temporal ??
|
|
1059
2420
|
deps.workflowClient.check ??
|
|
@@ -1118,8 +2479,37 @@ const routeLabelPatterns: Array<{
|
|
|
1118
2479
|
pattern: RegExp;
|
|
1119
2480
|
label: string | ((match: RegExpMatchArray) => string);
|
|
1120
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
|
+
},
|
|
1121
2496
|
{ pattern: /^\/healthz$/, label: "/healthz" },
|
|
1122
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
|
+
},
|
|
1123
2513
|
{ pattern: /^\/traffic-readyz$/, label: "/traffic-readyz" },
|
|
1124
2514
|
{
|
|
1125
2515
|
pattern: /^\/v1\/workspaces\/[^/]+\/codex\/connect\/start$/,
|
|
@@ -1133,6 +2523,22 @@ const routeLabelPatterns: Array<{
|
|
|
1133
2523
|
pattern: /^\/v1\/workspaces\/[^/]+\/codex\/status$/,
|
|
1134
2524
|
label: "/v1/workspaces/:workspaceId/codex/status",
|
|
1135
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
|
+
},
|
|
1136
2542
|
{
|
|
1137
2543
|
pattern: /^\/v1\/workspaces\/[^/]+\/supergrok\/connect\/(start|poll)$/,
|
|
1138
2544
|
label: (match) => `/v1/workspaces/:workspaceId/supergrok/connect/${match[1]}`,
|
|
@@ -1159,6 +2565,8 @@ const routeLabelPatterns: Array<{
|
|
|
1159
2565
|
},
|
|
1160
2566
|
{ pattern: /^\/metrics$/, label: "/metrics" },
|
|
1161
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" },
|
|
1162
2570
|
{ pattern: /^\/v1\/billing$/, label: "/v1/billing" },
|
|
1163
2571
|
{ pattern: /^\/v1\/billing\/checkout$/, label: "/v1/billing/checkout" },
|
|
1164
2572
|
{ pattern: /^\/v1\/billing\/usage$/, label: "/v1/billing/usage" },
|
|
@@ -1166,11 +2574,31 @@ const routeLabelPatterns: Array<{
|
|
|
1166
2574
|
pattern: /^\/v1\/workspaces\/[^/]+\/insights$/,
|
|
1167
2575
|
label: "/v1/workspaces/:workspaceId/insights",
|
|
1168
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
|
+
},
|
|
1169
2593
|
{
|
|
1170
2594
|
pattern: /^\/v1\/billing\/entitlements$/,
|
|
1171
2595
|
label: "/v1/billing/entitlements",
|
|
1172
2596
|
},
|
|
1173
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
|
+
},
|
|
1174
2602
|
{
|
|
1175
2603
|
pattern: /^\/v1\/workspaces\/[^/]+\/mcp$/,
|
|
1176
2604
|
label: "/v1/workspaces/:workspaceId/mcp",
|
|
@@ -1183,6 +2611,22 @@ const routeLabelPatterns: Array<{
|
|
|
1183
2611
|
pattern: /^\/v1\/workspaces\/[^/]+\/mcp\/files$/,
|
|
1184
2612
|
label: "/v1/workspaces/:workspaceId/mcp/files",
|
|
1185
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
|
+
},
|
|
1186
2630
|
{
|
|
1187
2631
|
pattern: /^\/v1\/workspaces\/[^/]+\/default-rig$/,
|
|
1188
2632
|
label: "/v1/workspaces/:workspaceId/default-rig",
|
|
@@ -1191,6 +2635,18 @@ const routeLabelPatterns: Array<{
|
|
|
1191
2635
|
pattern: /^\/v1\/workspaces\/[^/]+\/sessions$/,
|
|
1192
2636
|
label: "/v1/workspaces/:workspaceId/sessions",
|
|
1193
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
|
+
},
|
|
1194
2650
|
{
|
|
1195
2651
|
pattern: /^\/v1\/workspaces\/[^/]+\/control-events\/stream$/,
|
|
1196
2652
|
label: "/v1/workspaces/:workspaceId/control-events/stream",
|
|
@@ -1288,6 +2744,11 @@ const routeLabelPatterns: Array<{
|
|
|
1288
2744
|
label:
|
|
1289
2745
|
"/v1/workspaces/:workspaceId/computer-sessions/:computerSessionId/targets/:targetId/observation",
|
|
1290
2746
|
},
|
|
2747
|
+
{
|
|
2748
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/computer-sessions\/[^/]+\/targets\/[^/]+\/screenshot$/,
|
|
2749
|
+
label:
|
|
2750
|
+
"/v1/workspaces/:workspaceId/computer-sessions/:computerSessionId/targets/:targetId/screenshot",
|
|
2751
|
+
},
|
|
1291
2752
|
{
|
|
1292
2753
|
pattern: /^\/v1\/workspaces\/[^/]+\/computer-sessions\/[^/]+\/operations\/[^/]+$/,
|
|
1293
2754
|
label:
|
|
@@ -1351,6 +2812,10 @@ const routeLabelPatterns: Array<{
|
|
|
1351
2812
|
pattern: /^\/v1\/workspaces\/[^/]+\/inference-control$/,
|
|
1352
2813
|
label: "/v1/workspaces/:workspaceId/inference-control",
|
|
1353
2814
|
},
|
|
2815
|
+
{
|
|
2816
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/pause-timer$/,
|
|
2817
|
+
label: "/v1/workspaces/:workspaceId/pause-timer",
|
|
2818
|
+
},
|
|
1354
2819
|
{
|
|
1355
2820
|
pattern:
|
|
1356
2821
|
/^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/fs\/(list|list-batch|read|write|delete|move|mkdir)$/,
|
|
@@ -1372,6 +2837,10 @@ const routeLabelPatterns: Array<{
|
|
|
1372
2837
|
pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/events\/stream$/,
|
|
1373
2838
|
label: "/v1/workspaces/:workspaceId/sessions/:id/events/stream",
|
|
1374
2839
|
},
|
|
2840
|
+
{
|
|
2841
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/(visibility|forks)$/,
|
|
2842
|
+
label: (match) => `/v1/workspaces/:workspaceId/sessions/:id/${match[1]}`,
|
|
2843
|
+
},
|
|
1375
2844
|
{
|
|
1376
2845
|
pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/lineage$/,
|
|
1377
2846
|
label: "/v1/workspaces/:workspaceId/sessions/:id/lineage",
|
|
@@ -1380,6 +2849,10 @@ const routeLabelPatterns: Array<{
|
|
|
1380
2849
|
pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/events$/,
|
|
1381
2850
|
label: "/v1/workspaces/:workspaceId/sessions/:id/events",
|
|
1382
2851
|
},
|
|
2852
|
+
{
|
|
2853
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/turns$/,
|
|
2854
|
+
label: "/v1/workspaces/:workspaceId/sessions/:id/turns",
|
|
2855
|
+
},
|
|
1383
2856
|
{
|
|
1384
2857
|
pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/queue\/[^/]+\/(move|edit|steer|delete)$/,
|
|
1385
2858
|
label: "/v1/workspaces/:workspaceId/sessions/:id/queue/:turnId/:action",
|
|
@@ -1388,6 +2861,10 @@ const routeLabelPatterns: Array<{
|
|
|
1388
2861
|
pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/queue$/,
|
|
1389
2862
|
label: "/v1/workspaces/:workspaceId/sessions/:id/queue",
|
|
1390
2863
|
},
|
|
2864
|
+
{
|
|
2865
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/composer-draft\/submit$/,
|
|
2866
|
+
label: "/v1/workspaces/:workspaceId/sessions/:id/composer-draft/submit",
|
|
2867
|
+
},
|
|
1391
2868
|
{
|
|
1392
2869
|
pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/composer-draft$/,
|
|
1393
2870
|
label: "/v1/workspaces/:workspaceId/sessions/:id/composer-draft",
|
|
@@ -1452,6 +2929,84 @@ const routeLabelPatterns: Array<{
|
|
|
1452
2929
|
pattern: /^\/v1\/workspaces\/[^/]+\/api-keys\/[^/]+$/,
|
|
1453
2930
|
label: "/v1/workspaces/:workspaceId/api-keys/:id",
|
|
1454
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
|
+
},
|
|
1455
3010
|
{
|
|
1456
3011
|
pattern: /^\/v1\/workspaces\/[^/]+\/scheduled-tasks$/,
|
|
1457
3012
|
label: "/v1/workspaces/:workspaceId/scheduled-tasks",
|
|
@@ -1516,6 +3071,10 @@ const routeLabelPatterns: Array<{
|
|
|
1516
3071
|
pattern: /^\/v1\/workspaces\/[^/]+\/github\/app$/,
|
|
1517
3072
|
label: "/v1/workspaces/:workspaceId/github/app",
|
|
1518
3073
|
},
|
|
3074
|
+
{
|
|
3075
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/github\/action-policies$/,
|
|
3076
|
+
label: "/v1/workspaces/:workspaceId/github/action-policies",
|
|
3077
|
+
},
|
|
1519
3078
|
{
|
|
1520
3079
|
pattern: /^\/v1\/workspaces\/[^/]+\/github\/repositories$/,
|
|
1521
3080
|
label: "/v1/workspaces/:workspaceId/github/repositories",
|
|
@@ -1548,6 +3107,22 @@ const routeLabelPatterns: Array<{
|
|
|
1548
3107
|
pattern: /^\/v1\/workspaces\/[^/]+\/github\/app-manifest$/,
|
|
1549
3108
|
label: "/v1/workspaces/:workspaceId/github/app-manifest",
|
|
1550
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
|
+
},
|
|
1551
3126
|
{
|
|
1552
3127
|
pattern: /^\/v1\/workspaces\/[^/]+\/capabilities$/,
|
|
1553
3128
|
label: "/v1/workspaces/:workspaceId/capabilities",
|
|
@@ -1650,26 +3225,7 @@ const routeLabelPatterns: Array<{
|
|
|
1650
3225
|
pattern: /^\/v1\/workspaces\/[^/]+\/environments\/[^/]+$/,
|
|
1651
3226
|
label: "/v1/workspaces/:workspaceId/environments/:id",
|
|
1652
3227
|
},
|
|
1653
|
-
|
|
1654
|
-
pattern: /^\/v1\/workspaces\/[^/]+\/packs$/,
|
|
1655
|
-
label: "/v1/workspaces/:workspaceId/packs",
|
|
1656
|
-
},
|
|
1657
|
-
{
|
|
1658
|
-
pattern: /^\/v1\/workspaces\/[^/]+\/packs\/installations$/,
|
|
1659
|
-
label: "/v1/workspaces/:workspaceId/packs/installations",
|
|
1660
|
-
},
|
|
1661
|
-
{
|
|
1662
|
-
pattern: /^\/v1\/workspaces\/[^/]+\/packs\/marketing-social-daily-analysis\/scheduled-tasks$/,
|
|
1663
|
-
label: "/v1/workspaces/:workspaceId/packs/marketing-social-daily-analysis/scheduled-tasks",
|
|
1664
|
-
},
|
|
1665
|
-
{
|
|
1666
|
-
pattern: /^\/v1\/workspaces\/[^/]+\/packs\/[^/]+\/enable$/,
|
|
1667
|
-
label: "/v1/workspaces/:workspaceId/packs/:id/enable",
|
|
1668
|
-
},
|
|
1669
|
-
{
|
|
1670
|
-
pattern: /^\/v1\/workspaces\/[^/]+\/packs\/[^/]+$/,
|
|
1671
|
-
label: "/v1/workspaces/:workspaceId/packs/:id",
|
|
1672
|
-
},
|
|
3228
|
+
|
|
1673
3229
|
{
|
|
1674
3230
|
pattern: /^\/v1\/workspaces\/[^/]+\/plugins\/preview$/,
|
|
1675
3231
|
label: "/v1/workspaces/:workspaceId/plugins/preview",
|
|
@@ -1706,6 +3262,26 @@ const routeLabelPatterns: Array<{
|
|
|
1706
3262
|
pattern: /^\/v1\/workspaces\/[^/]+\/connections\/oauth\/start$/,
|
|
1707
3263
|
label: "/v1/workspaces/:workspaceId/connections/oauth/start",
|
|
1708
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
|
+
},
|
|
1709
3285
|
{
|
|
1710
3286
|
pattern: /^\/v1\/workspaces\/[^/]+\/integrations\/oauth\/start$/,
|
|
1711
3287
|
label: "/v1/workspaces/:workspaceId/integrations/oauth/start",
|
|
@@ -1730,6 +3306,18 @@ const routeLabelPatterns: Array<{
|
|
|
1730
3306
|
pattern: /^\/v1\/workspaces\/[^/]+\/connections\/[^/]+$/,
|
|
1731
3307
|
label: "/v1/workspaces/:workspaceId/connections/:connectionId",
|
|
1732
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
|
+
},
|
|
1733
3321
|
{ pattern: /^\/v1\/catalog-assets\/.+$/, label: "/v1/catalog-assets/*" },
|
|
1734
3322
|
{
|
|
1735
3323
|
pattern: /^\/v1\/integrations\/oauth\/callback$/,
|
|
@@ -1739,6 +3327,10 @@ const routeLabelPatterns: Array<{
|
|
|
1739
3327
|
pattern: /^\/v1\/integrations\/provider-oauth\/callback$/,
|
|
1740
3328
|
label: "/v1/integrations/provider-oauth/callback",
|
|
1741
3329
|
},
|
|
3330
|
+
{
|
|
3331
|
+
pattern: /^\/v1\/integrations\/github-personal\/oauth\/callback$/,
|
|
3332
|
+
label: "/v1/integrations/github-personal/oauth/callback",
|
|
3333
|
+
},
|
|
1742
3334
|
{
|
|
1743
3335
|
pattern: /^\/v1\/integrations\/google-drive\/callback$/,
|
|
1744
3336
|
label: "/v1/integrations/google-drive/callback",
|
|
@@ -1779,6 +3371,14 @@ const routeLabelPatterns: Array<{
|
|
|
1779
3371
|
pattern: /^\/v1\/workspaces\/[^/]+\/machines\/[^/]+\/metrics\/series$/,
|
|
1780
3372
|
label: "/v1/workspaces/:workspaceId/machines/:enrollmentId/metrics/series",
|
|
1781
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
|
+
},
|
|
1782
3382
|
{
|
|
1783
3383
|
pattern: /^\/v1\/workspaces\/[^/]+\/machines$/,
|
|
1784
3384
|
label: "/v1/workspaces/:workspaceId/machines",
|
|
@@ -1796,18 +3396,105 @@ const routeLabelPatterns: Array<{
|
|
|
1796
3396
|
pattern: /^\/v1\/github\/oauth\/callback$/,
|
|
1797
3397
|
label: "/v1/github/oauth/callback",
|
|
1798
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
|
+
},
|
|
1799
3416
|
];
|
|
1800
3417
|
|
|
1801
|
-
|
|
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
|
+
}
|
|
1802
3439
|
for (const candidate of routeLabelPatterns) {
|
|
1803
3440
|
const match = pathname.match(candidate.pattern);
|
|
1804
3441
|
if (match) {
|
|
1805
3442
|
return typeof candidate.label === "string" ? candidate.label : candidate.label(match);
|
|
1806
3443
|
}
|
|
1807
3444
|
}
|
|
3445
|
+
const registered = boundedRegisteredRouteLabel(registeredRoutePath);
|
|
3446
|
+
if (registered) return registered;
|
|
1808
3447
|
return pathname.startsWith("/v1/") ? "/v1/unknown" : "/unknown";
|
|
1809
3448
|
}
|
|
1810
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
|
+
|
|
1811
3498
|
/**
|
|
1812
3499
|
* State-changing OpenGeni HTTP calls must never cross an incompatible rollout
|
|
1813
3500
|
* boundary. Standard third-party protocols and externally initiated callbacks
|
|
@@ -1820,19 +3507,47 @@ export function isApiContractProtectedMutation(method: string, pathname: string)
|
|
|
1820
3507
|
if (!pathname.startsWith("/v1/")) {
|
|
1821
3508
|
return false;
|
|
1822
3509
|
}
|
|
3510
|
+
if (
|
|
3511
|
+
pathname === "/v1/auth/organization-onboarding" ||
|
|
3512
|
+
pathname === "/v1/auth/organization-setup"
|
|
3513
|
+
) {
|
|
3514
|
+
return true;
|
|
3515
|
+
}
|
|
1823
3516
|
if (
|
|
1824
3517
|
pathname.startsWith("/v1/auth/") ||
|
|
1825
3518
|
pathname.startsWith("/v1/webhooks/") ||
|
|
1826
3519
|
pathname.startsWith("/v1/integrations/oauth/") ||
|
|
3520
|
+
isPersonalGitHubGitBrokerRequest(method, pathname) ||
|
|
1827
3521
|
pathname === "/v1/integrations/slack/events" ||
|
|
1828
3522
|
pathname === "/v1/integrations/slack/commands" ||
|
|
1829
3523
|
pathname === "/v1/integrations/slack/interactions" ||
|
|
1830
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 ||
|
|
1831
3528
|
pathname === "/v1/enrollments/device/start" ||
|
|
1832
3529
|
pathname === "/v1/enrollments/device/poll" ||
|
|
1833
|
-
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"
|
|
1834
3534
|
) {
|
|
1835
3535
|
return false;
|
|
1836
3536
|
}
|
|
1837
|
-
|
|
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
|
+
};
|
|
1838
3553
|
}
|