@opengeni/api-router 3.0.1 → 3.0.2-canary.38894290271001
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 +8 -1
- package/dist/admission-trace.d.ts +8 -0
- package/dist/app.d.ts +50 -3
- package/dist/app.js +10 -1
- package/dist/artifact-catalog-cursor.d.ts +1 -0
- package/dist/attached-browser-viewer.d.ts +11 -0
- package/dist/auth/managed-auth-attempt-context.d.ts +2 -0
- package/dist/auth/managed-auth-rate-limits.d.ts +167 -0
- package/dist/auth/managed-auth-session-adapter.d.ts +9 -1
- package/dist/auth/managed-auth.d.ts +72 -5
- package/dist/auth/managed-sign-in-notifications.d.ts +4 -0
- package/dist/auth/new-signups-gate.d.ts +23 -0
- package/dist/auth/signup-funnel-metrics.d.ts +44 -0
- package/dist/azure-live.d.ts +28 -0
- package/dist/browser-action-fences.d.ts +13 -0
- package/dist/browser-controller-authority.d.ts +1 -61
- package/dist/browser-ephemeral.d.ts +5 -0
- package/dist/browser-lost-runtime-end.d.ts +24 -0
- package/dist/browser-state-authority.d.ts +1 -27
- package/dist/{chunk-AJ62T5QN.js → chunk-GBUJ2DYZ.js} +80507 -39878
- package/dist/chunk-GBUJ2DYZ.js.map +1 -0
- package/dist/chunk-OGQDXVXY.js +18 -0
- package/dist/chunk-OGQDXVXY.js.map +1 -0
- package/dist/claude-default-models.d.ts +19 -0
- package/dist/claude-subscription-core.d.ts +50 -0
- package/dist/claude-subscription-oauth.d.ts +36 -0
- package/dist/claude-subscription-profile.d.ts +9 -0
- package/dist/claude-subscription-usage.d.ts +6 -0
- package/dist/claude-workspace-connection.d.ts +15 -0
- package/dist/codemode.d.ts +40 -5
- package/dist/codex-realtime.d.ts +29 -3
- package/dist/codex-session-pointers.d.ts +19 -0
- package/dist/connection-ownership.d.ts +6 -3
- package/dist/controller-data-plane.d.ts +16 -0
- package/dist/credit-promotion-snapshot.d.ts +6 -0
- package/dist/editable-artifact-source-session.d.ts +3 -0
- package/dist/editable-artifact-websocket.d.ts +5 -3
- package/dist/fatal-process-boundary.d.ts +9 -1
- package/dist/github-access.d.ts +7 -1
- package/dist/github-browser-flow.d.ts +1 -1
- package/dist/http/acting-person.d.ts +36 -0
- package/dist/http/api-contract-compat.d.ts +19 -0
- package/dist/http/api-error.d.ts +50 -1
- package/dist/http/auth.d.ts +8 -1
- package/dist/http/cors.d.ts +12 -0
- package/dist/http/deprecation.d.ts +42 -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/rejection-telemetry.d.ts +34 -0
- package/dist/http/request-body.d.ts +27 -0
- package/dist/http/request-source.d.ts +41 -3
- package/dist/http/sse.d.ts +12 -1
- package/dist/http/unmatched-route.d.ts +18 -0
- package/dist/http/user-content.d.ts +36 -0
- package/dist/http/variable-sets-removed.d.ts +41 -0
- package/dist/http/websocket-upgrade-dispatch.d.ts +18 -0
- package/dist/index.js +1279 -108
- package/dist/index.js.map +1 -1
- package/dist/integration-connect-metrics.d.ts +35 -0
- package/dist/integration-endpoint-test.d.ts +20 -0
- package/dist/integrations/api-integrations.d.ts +2 -2
- package/dist/integrations/atlassian.d.ts +8 -3
- package/dist/integrations/fiken.d.ts +3 -1
- package/dist/integrations/github-lens-connect.d.ts +37 -66
- package/dist/integrations/google-drive.d.ts +2 -0
- package/dist/integrations/integration-commit-authority.d.ts +13 -0
- package/dist/integrations/marketplace-plugin.d.ts +43 -43
- package/dist/integrations/oauth-client.d.ts +171 -12
- package/dist/integrations/oauth-profiles.d.ts +42 -13
- package/dist/integrations/onepassword-vault-provider.d.ts +9 -0
- package/dist/integrations/slack-bot.d.ts +285 -7
- package/dist/integrations/slack-file-upload-flow.d.ts +62 -0
- package/dist/integrations/slack-interactions.d.ts +89 -10
- package/dist/integrations/slack-mrkdwn.d.ts +41 -0
- package/dist/integrations/slack-routing.d.ts +1 -1
- package/dist/integrations/slack-session-defaults.d.ts +58 -0
- package/dist/integrations/slack-task-file-upload.d.ts +36 -0
- package/dist/integrations/slack-tool-review.d.ts +5 -0
- package/dist/interaction-agent-access.d.ts +13 -8
- package/dist/interaction-agent-version.d.ts +21 -0
- package/dist/interaction-holder-heartbeat.d.ts +5 -0
- package/dist/interaction-metrics.d.ts +27 -2
- package/dist/interaction-route-stages.d.ts +25 -0
- package/dist/interaction-secrets-vault.d.ts +20 -0
- package/dist/interaction-secrets.d.ts +103 -0
- package/dist/mcp/action-catalog-types.d.ts +14 -0
- package/dist/mcp/action-catalog.gen.d.ts +2 -0
- package/dist/mcp/action-descriptions.d.ts +4 -0
- package/dist/mcp/action-schema-imports.gen.d.ts +1 -0
- package/dist/mcp/capability-account-readiness.d.ts +7 -0
- package/dist/mcp/child-read-evidence.d.ts +9 -0
- package/dist/mcp/company-profile-agent-admin.d.ts +12 -0
- package/dist/mcp/contract-input.d.ts +13 -0
- package/dist/mcp/documents.d.ts +2 -0
- package/dist/mcp/first-party-tool-permissions.d.ts +3 -606
- package/dist/mcp/notification-tools.d.ts +26 -0
- package/dist/mcp/orchestration-failure-diagnostic.d.ts +43 -0
- package/dist/mcp/scheduled-task-input.d.ts +1309 -0
- package/dist/mcp/scheduled-task-view.d.ts +217 -3
- package/dist/mcp/secret-tools.d.ts +14 -0
- package/dist/mcp/server.d.ts +108 -5
- package/dist/mcp/session-admin-access-tools.d.ts +33 -0
- package/dist/mcp/session-event-errors.d.ts +28 -0
- package/dist/mcp/session-event-view.d.ts +62 -0
- package/dist/mcp/session-view.d.ts +15 -0
- package/dist/mcp/session-wait.d.ts +18 -0
- package/dist/mcp/site-agent-clamp-tools.d.ts +38 -0
- package/dist/mcp/site-data-tools.d.ts +22 -0
- package/dist/mcp/site-publication-tools.d.ts +19 -0
- package/dist/mcp/site-sql-tools.d.ts +27 -0
- package/dist/mcp-oauth.d.ts +37 -4
- package/dist/model-calls/chat-completions.d.ts +87 -0
- package/dist/model-calls/pending-usage-settlements.d.ts +57 -0
- package/dist/model-calls/service.d.ts +36 -0
- package/dist/model-catalog.d.ts +9 -3
- package/dist/native-push-dispatch.d.ts +69 -0
- package/dist/observability.d.ts +3 -1
- package/dist/organization-mcp.d.ts +82 -0
- package/dist/prepared-mcp-actor.d.ts +77 -0
- package/dist/prepared-mcp-permissions.d.ts +8 -0
- package/dist/routes/analytics-consent.d.ts +26 -0
- package/dist/routes/api-integrations.d.ts +11 -0
- package/dist/routes/api-keys.d.ts +7 -7
- package/dist/routes/automations.d.ts +2 -0
- package/dist/routes/billing.d.ts +59 -0
- package/dist/routes/browser-sessions.d.ts +65 -2
- package/dist/routes/chat-completions.d.ts +11 -0
- package/dist/routes/claude-core-usage.d.ts +20 -0
- 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 +182 -0
- package/dist/routes/codex-core.d.ts +401 -0
- package/dist/routes/codex.d.ts +64 -2
- package/dist/routes/computer-sessions.d.ts +6 -1
- package/dist/routes/desktop-distribution.d.ts +32 -0
- package/dist/routes/github-browser-fonts.gen.d.ts +2 -0
- package/dist/routes/github-browser-pages.d.ts +27 -0
- package/dist/routes/inbox.d.ts +13 -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 +28 -0
- package/dist/routes/interaction-placement-message.d.ts +7 -0
- package/dist/routes/managed-sign-in-methods.d.ts +6 -0
- package/dist/routes/model-account-choice.d.ts +13 -0
- package/dist/routes/model-account-settings.d.ts +19 -0
- package/dist/routes/native-app-auth.d.ts +12 -0
- package/dist/routes/organization-integration-policy.d.ts +8 -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-defaults.d.ts +9 -0
- package/dist/routes/organization-service-accounts.d.ts +3 -0
- package/dist/routes/personal-api-keys.d.ts +3 -0
- package/dist/routes/session-admin-access.d.ts +3 -0
- package/dist/routes/session-artifact-associations.d.ts +4 -0
- package/dist/routes/session-history-imports.d.ts +20 -0
- package/dist/routes/sessions.d.ts +38 -2
- package/dist/routes/site-data.d.ts +18 -0
- package/dist/routes/site-publishing.d.ts +5 -0
- package/dist/routes/site-runtime.d.ts +31 -0
- package/dist/routes/site-sql.d.ts +16 -0
- package/dist/routes/slack-bot-access.d.ts +3 -0
- package/dist/routes/subscription-account-pools.d.ts +21 -0
- package/dist/routes/subscription-core-http.d.ts +5 -0
- package/dist/routes/subscription-pool-access.d.ts +37 -0
- package/dist/routes/subscription-pool-core.d.ts +153 -0
- package/dist/routes/supergrok.d.ts +6 -6
- package/dist/routes/usage-allowances.d.ts +12 -0
- package/dist/routes/variable-sets-removed.d.ts +2 -0
- package/dist/routes/vault-providers.d.ts +34 -0
- package/dist/routes/vaults.d.ts +3 -0
- package/dist/routes/workspace-integrations.d.ts +35 -0
- package/dist/routes/workspace-model-providers.d.ts +4 -0
- package/dist/routes/workspaces.d.ts +11 -2
- package/dist/sandbox/agent-update.d.ts +64 -0
- package/dist/sandbox/auth-callout.d.ts +8 -0
- package/dist/sandbox/channel-a.d.ts +15 -1
- package/dist/sandbox/enrollment.d.ts +26 -4
- package/dist/sandbox/machine-audience.d.ts +29 -0
- package/dist/sandbox/machines.d.ts +15 -2
- package/dist/sandbox/metrics-ingestion.d.ts +6 -2
- package/dist/sandbox/screen-control.d.ts +85 -0
- package/dist/sandbox/viewer.d.ts +5 -1
- package/dist/sandbox-file-artifacts.d.ts +17 -1
- package/dist/scheduled-task-blockers.d.ts +39 -0
- package/dist/scheduled-task-deletion.d.ts +3 -4
- package/dist/scheduled-task-lifecycle.d.ts +14 -0
- package/dist/secret-answers.d.ts +40 -0
- package/dist/session-create-observability.d.ts +5 -0
- package/dist/session-event-view-36GO3IL7.js +528 -0
- package/dist/session-event-view-36GO3IL7.js.map +1 -0
- package/dist/signed-state-secret.d.ts +18 -0
- package/dist/site-agent-guard.d.ts +53 -0
- package/dist/site-data/operations.d.ts +103 -0
- package/dist/site-data/service.d.ts +203 -0
- package/dist/site-frame-guard.d.ts +32 -0
- package/dist/site-publishing/agent-data-tools.d.ts +84 -0
- package/dist/site-publishing/agent-model.d.ts +58 -0
- package/dist/site-publishing/agent-session.d.ts +16 -0
- package/dist/site-publishing/content-scanner.d.ts +31 -0
- package/dist/site-publishing/edge-sync.d.ts +48 -0
- package/dist/site-publishing/gateway-signature.d.ts +56 -0
- package/dist/site-publishing/limits.d.ts +63 -0
- package/dist/site-publishing/page-tool-authority.d.ts +30 -0
- package/dist/site-publishing/platform-usage.d.ts +113 -0
- package/dist/site-publishing/platform.d.ts +21 -0
- package/dist/site-publishing/readiness.d.ts +62 -0
- package/dist/site-publishing/runtime.d.ts +179 -0
- package/dist/site-publishing/service.d.ts +264 -0
- package/dist/site-publishing/session-events.d.ts +22 -0
- package/dist/site-publishing/session-views.d.ts +36 -0
- package/dist/site-publishing/viewer.d.ts +40 -0
- package/dist/site-session-origin.d.ts +31 -6
- package/dist/site-sql/format.d.ts +47 -0
- package/dist/site-sql/gateway.d.ts +50 -0
- package/dist/site-sql/service.d.ts +213 -0
- package/dist/temporal-schedule-sync.d.ts +26 -0
- package/dist/transcription/billing-refusal.d.ts +8 -0
- package/dist/transcription/normalize.d.ts +23 -0
- package/dist/transcription/providers/azure-mai.d.ts +12 -0
- package/dist/transcription/providers/azure-openai.d.ts +4 -0
- package/dist/transcription/providers/openai.d.ts +16 -0
- package/dist/transcription/providers/subscription-core.d.ts +39 -0
- package/dist/transcription/service.d.ts +10 -1
- package/dist/transcription/wav-duration.d.ts +8 -0
- package/dist/workspace-tool-gateway-attestations.d.ts +37 -0
- package/dist/workspace-tool-gateway-observability.d.ts +16 -0
- package/dist/workspace-tool-gateway-secrets.d.ts +27 -0
- package/dist/workspace-tool-gateway.d.ts +102 -6
- package/dist/workspace-tool-target-fence.d.ts +116 -0
- package/dist/workspace-tool-target.d.ts +225 -0
- package/dist/workspace-webhook-dispatch.d.ts +26 -0
- package/dist/xai-subscription-core.d.ts +60 -0
- package/package.json +25 -20
- package/src/access-grant-rls.ts +64 -14
- package/src/admission-trace.ts +14 -0
- package/src/app.ts +1684 -252
- package/src/artifact-catalog-cursor.ts +1 -0
- package/src/attached-browser-viewer.ts +26 -0
- package/src/auth/managed-auth-attempt-context.ts +9 -0
- package/src/auth/managed-auth-rate-limits.ts +252 -0
- package/src/auth/managed-auth-session-adapter.ts +64 -29
- package/src/auth/managed-auth.ts +303 -35
- package/src/auth/managed-email.ts +18 -1
- 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 +3 -3
- package/src/auth/signup-funnel-metrics.ts +164 -0
- package/src/azure-live.ts +103 -0
- package/src/browser-action-fences.ts +45 -0
- package/src/browser-controller-authority.ts +1 -186
- package/src/browser-ephemeral.ts +39 -0
- package/src/browser-lost-runtime-end.ts +71 -0
- package/src/browser-state-authority.ts +1 -236
- package/src/claude-default-models.ts +37 -0
- package/src/claude-subscription-core.ts +154 -0
- package/src/claude-subscription-oauth.ts +352 -0
- package/src/claude-subscription-profile.ts +73 -0
- package/src/claude-subscription-usage.ts +51 -0
- package/src/claude-workspace-connection.ts +82 -0
- package/src/codemode.ts +236 -39
- package/src/codex-realtime.ts +181 -53
- package/src/codex-session-pointers.ts +80 -0
- package/src/company-brain-okf.ts +1 -1
- package/src/connected-machine-computer-access.ts +2 -1
- package/src/connection-ownership.ts +19 -13
- package/src/controller-data-plane.ts +34 -0
- package/src/credit-promotion-snapshot.ts +36 -0
- package/src/editable-artifact-production.ts +10 -0
- package/src/editable-artifact-source-session.ts +123 -0
- package/src/editable-artifact-websocket.ts +16 -3
- package/src/fatal-process-boundary.ts +67 -3
- package/src/gateway-realtime.ts +12 -8
- package/src/github-access.ts +31 -5
- package/src/github-browser-flow.ts +1 -1
- package/src/http/acting-person.ts +105 -0
- package/src/http/api-contract-compat.ts +49 -0
- package/src/http/api-error.ts +210 -2
- package/src/http/auth.ts +68 -4
- package/src/http/cors.ts +37 -5
- package/src/http/deprecation.ts +118 -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/rejection-telemetry.ts +120 -0
- package/src/http/request-body.ts +127 -0
- package/src/http/request-source.ts +154 -13
- package/src/http/sse.ts +88 -28
- package/src/http/unmatched-route.ts +60 -0
- package/src/http/user-content.ts +158 -0
- package/src/http/variable-sets-removed.ts +122 -0
- package/src/http/websocket-upgrade-dispatch.ts +29 -0
- package/src/index.ts +107 -57
- package/src/integration-connect-metrics.ts +228 -0
- package/src/integration-endpoint-test.ts +291 -0
- package/src/integrations/api-integrations.ts +81 -7
- package/src/integrations/atlassian.ts +202 -83
- package/src/integrations/fiken.ts +66 -38
- package/src/integrations/github-app-connect.ts +64 -18
- package/src/integrations/github-lens-connect.ts +8 -29
- package/src/integrations/google-drive.ts +448 -152
- package/src/integrations/integration-commit-authority.ts +69 -0
- package/src/integrations/marketplace-plugin.ts +1 -1
- package/src/integrations/oauth-client.ts +733 -193
- package/src/integrations/oauth-profiles.ts +116 -45
- package/src/integrations/onepassword-vault-provider.ts +16 -0
- package/src/integrations/personal-github-repositories.ts +1 -1
- package/src/integrations/personal-github.ts +61 -35
- package/src/integrations/provider-oauth.ts +67 -85
- package/src/integrations/slack-app-home.ts +5 -5
- package/src/integrations/slack-bot.ts +893 -201
- package/src/integrations/slack-file-upload-flow.ts +147 -0
- package/src/integrations/slack-install.ts +7 -7
- package/src/integrations/slack-interactions.ts +1589 -299
- package/src/integrations/slack-mrkdwn.ts +193 -0
- package/src/integrations/slack-routing.ts +2 -2
- package/src/integrations/slack-session-defaults.ts +224 -0
- package/src/integrations/slack-task-file-upload.ts +323 -0
- package/src/integrations/slack-tool-review.ts +65 -0
- package/src/integrations/social-oauth.ts +90 -58
- package/src/interaction-agent-access.ts +40 -20
- package/src/interaction-agent-version.ts +85 -0
- package/src/interaction-holder-heartbeat.ts +24 -8
- package/src/interaction-metrics.ts +164 -2
- package/src/interaction-route-stages.ts +94 -0
- package/src/interaction-secrets-vault.ts +213 -0
- package/src/interaction-secrets.ts +426 -0
- package/src/mcp/action-catalog-types.ts +14 -0
- package/src/mcp/action-catalog.gen.ts +8014 -0
- package/src/mcp/action-descriptions.ts +299 -0
- package/src/mcp/action-schema-imports.gen.ts +304 -0
- package/src/mcp/capability-account-readiness.ts +17 -0
- package/src/mcp/child-read-evidence.ts +19 -0
- package/src/mcp/company-profile-agent-admin.ts +13 -0
- package/src/mcp/contract-input.ts +165 -0
- package/src/mcp/documents.ts +9 -2
- package/src/mcp/editable-artifacts.ts +29 -11
- package/src/mcp/files.ts +5 -1
- package/src/mcp/first-party-tool-permissions.ts +7 -285
- package/src/mcp/knowledge-entries.ts +108 -34
- package/src/mcp/notification-tools.ts +480 -0
- package/src/mcp/orchestration-failure-diagnostic.ts +43 -0
- package/src/mcp/scheduled-task-input.ts +89 -0
- package/src/mcp/scheduled-task-view.ts +63 -3
- package/src/mcp/secret-tools.ts +185 -0
- package/src/mcp/server.ts +2213 -1116
- package/src/mcp/session-admin-access-tools.ts +163 -0
- package/src/mcp/session-event-errors.ts +123 -0
- package/src/mcp/session-event-view.ts +395 -13
- package/src/mcp/session-view.ts +3 -0
- package/src/mcp/session-wait.ts +67 -7
- package/src/mcp/site-agent-clamp-tools.ts +140 -0
- package/src/mcp/site-data-tools.ts +339 -0
- package/src/mcp/site-publication-tools.ts +201 -0
- package/src/mcp/site-sql-tools.ts +427 -0
- package/src/mcp-oauth.ts +635 -45
- package/src/model-calls/chat-completions.ts +352 -0
- package/src/model-calls/pending-usage-settlements.ts +125 -0
- package/src/model-calls/service.ts +694 -0
- package/src/model-catalog.ts +68 -17
- package/src/native-push-dispatch.ts +457 -0
- package/src/observability.ts +15 -1
- package/src/organization-mcp.ts +485 -0
- package/src/prepared-mcp-actor.ts +154 -0
- package/src/prepared-mcp-permissions.ts +38 -0
- package/src/routes/analytics-consent.ts +123 -0
- package/src/routes/api-integrations.ts +268 -9
- package/src/routes/api-keys.ts +164 -25
- package/src/routes/artifact-catalog.ts +111 -0
- package/src/routes/automations.ts +163 -90
- package/src/routes/billing.ts +535 -59
- package/src/routes/browser-sessions.ts +966 -342
- package/src/routes/canonical-human-identities.ts +61 -0
- package/src/routes/capabilities.ts +112 -5
- package/src/routes/channels.ts +4 -3
- package/src/routes/chat-completions.ts +235 -0
- package/src/routes/claude-core-usage.ts +147 -0
- package/src/routes/claude-subscription-accounts.ts +233 -0
- package/src/routes/claude-subscription-oauth.ts +175 -0
- package/src/routes/client-errors.ts +394 -0
- package/src/routes/codex-core.ts +687 -0
- package/src/routes/codex.ts +1277 -1119
- package/src/routes/company-brain.ts +4 -0
- package/src/routes/computer-sessions.ts +319 -85
- package/src/routes/connect.ts +277 -175
- package/src/routes/connections.ts +690 -363
- package/src/routes/desktop-distribution.ts +217 -0
- package/src/routes/documents.ts +13 -7
- package/src/routes/editable-artifacts.ts +74 -4
- package/src/routes/enrollments.ts +110 -16
- package/src/routes/external-identity-links.ts +13 -12
- package/src/routes/files.ts +41 -16
- package/src/routes/github-browser-fonts.gen.ts +12 -0
- package/src/routes/github-browser-pages.ts +258 -0
- package/src/routes/github.ts +200 -75
- package/src/routes/inbox.ts +353 -0
- package/src/routes/insights-response-cache.ts +83 -0
- package/src/routes/insights-usage.ts +345 -0
- package/src/routes/insights.ts +103 -8
- package/src/routes/install.ts +51 -10
- package/src/routes/integration-facets.ts +70 -28
- package/src/routes/interaction-placement-message.ts +12 -0
- package/src/routes/knowledge.ts +136 -27
- package/src/routes/machines.ts +189 -135
- package/src/routes/managed-auth-session-sets.ts +43 -10
- package/src/routes/managed-onboarding.ts +39 -19
- package/src/routes/managed-sign-in-methods.ts +433 -0
- package/src/routes/memory-slack-publications.ts +3 -2
- package/src/routes/model-account-choice.ts +194 -0
- package/src/routes/model-account-settings.ts +235 -0
- package/src/routes/model-connection-access.ts +210 -48
- package/src/routes/native-app-auth.ts +296 -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 +183 -0
- package/src/routes/organization-memberships.ts +205 -19
- package/src/routes/organization-model-defaults.ts +110 -0
- package/src/routes/organization-model-providers.ts +74 -3
- package/src/routes/organization-service-accounts.ts +128 -0
- package/src/routes/organization-sessions.ts +21 -5
- package/src/routes/personal-api-keys.ts +184 -0
- package/src/routes/personal-github-git-broker.ts +2 -2
- package/src/routes/personal-github.ts +9 -4
- package/src/routes/plugins.ts +46 -7
- package/src/routes/pr-review-github.ts +105 -118
- package/src/routes/pr-review.ts +88 -82
- package/src/routes/preference-registry.ts +18 -0
- package/src/routes/rigs.ts +48 -13
- package/src/routes/scheduled-tasks.ts +347 -27
- package/src/routes/session-admin-access.ts +271 -0
- package/src/routes/session-artifact-associations.ts +164 -0
- package/src/routes/session-history-imports.ts +305 -0
- package/src/routes/sessions.ts +1682 -363
- package/src/routes/site-data.ts +340 -0
- package/src/routes/site-publishing.ts +584 -0
- package/src/routes/site-runtime.ts +1944 -0
- package/src/routes/site-sql.ts +263 -0
- package/src/routes/skill-content.ts +72 -7
- package/src/routes/skills.ts +5 -4
- package/src/routes/slack-bot-access.ts +108 -0
- package/src/routes/social.ts +27 -15
- package/src/routes/subscription-account-pools.ts +209 -0
- package/src/routes/subscription-core-http.ts +18 -0
- package/src/routes/subscription-pool-access.ts +180 -0
- package/src/routes/subscription-pool-core.ts +552 -0
- package/src/routes/supergrok.ts +174 -655
- package/src/routes/transcription-recordings.ts +71 -4
- package/src/routes/transcriptions.ts +26 -2
- package/src/routes/usage-allowances.ts +381 -0
- package/src/routes/variable-sets-removed.ts +19 -0
- package/src/routes/vault-providers.ts +782 -0
- package/src/routes/vaults.ts +421 -0
- package/src/routes/video-generation.ts +30 -12
- package/src/routes/workspace-artifacts.ts +165 -53
- package/src/routes/workspace-integrations.ts +576 -0
- package/src/routes/workspace-model-providers.ts +148 -0
- package/src/routes/workspace-state.ts +4 -0
- package/src/routes/workspaces.ts +625 -117
- package/src/sandbox/agent-update.ts +315 -0
- package/src/sandbox/auth-callout.ts +104 -21
- package/src/sandbox/channel-a.ts +150 -49
- package/src/sandbox/enrollment.ts +131 -22
- package/src/sandbox/machine-audience.ts +192 -0
- package/src/sandbox/machines.ts +75 -31
- package/src/sandbox/metrics-ingestion.ts +133 -2
- package/src/sandbox/rematerialize.ts +51 -21
- package/src/sandbox/screen-control.ts +289 -0
- package/src/sandbox/viewer.ts +33 -58
- package/src/sandbox-file-artifacts.ts +53 -27
- package/src/scheduled-task-blockers.ts +293 -0
- package/src/scheduled-task-deletion.ts +13 -3
- package/src/scheduled-task-lifecycle.ts +84 -0
- package/src/secret-answers.ts +119 -0
- package/src/session-create-observability.ts +46 -0
- package/src/session-realtime-context.ts +26 -4
- package/src/signed-state-secret.ts +58 -0
- package/src/site-agent-guard.ts +261 -0
- package/src/site-data/operations.ts +777 -0
- package/src/site-data/service.ts +1043 -0
- package/src/site-frame-guard.ts +107 -0
- package/src/site-publishing/agent-data-tools.ts +250 -0
- package/src/site-publishing/agent-model.ts +127 -0
- package/src/site-publishing/agent-session.ts +148 -0
- package/src/site-publishing/content-scanner.ts +93 -0
- package/src/site-publishing/edge-sync.ts +148 -0
- package/src/site-publishing/gateway-signature.ts +198 -0
- package/src/site-publishing/limits.ts +315 -0
- package/src/site-publishing/page-tool-authority.ts +111 -0
- package/src/site-publishing/platform-usage.ts +479 -0
- package/src/site-publishing/platform.ts +98 -0
- package/src/site-publishing/readiness.ts +330 -0
- package/src/site-publishing/runtime.ts +831 -0
- package/src/site-publishing/service.ts +1282 -0
- package/src/site-publishing/session-events.ts +262 -0
- package/src/site-publishing/session-views.ts +262 -0
- package/src/site-publishing/viewer.ts +75 -0
- package/src/site-session-origin.ts +91 -6
- package/src/site-sql/format.ts +134 -0
- package/src/site-sql/gateway.ts +168 -0
- package/src/site-sql/service.ts +1636 -0
- package/src/site-uploads.ts +27 -5
- package/src/temporal-schedule-sync.ts +74 -0
- package/src/transcription/billing-refusal.ts +14 -0
- package/src/transcription/normalize.ts +77 -0
- package/src/transcription/providers/azure-mai.ts +98 -0
- package/src/transcription/providers/azure-openai.ts +16 -7
- package/src/transcription/providers/codex-subscription.ts +190 -67
- package/src/transcription/providers/openai.ts +79 -5
- package/src/transcription/providers/subscription-core.ts +125 -0
- package/src/transcription/providers/xai-subscription.ts +81 -83
- package/src/transcription/segmenter.ts +7 -1
- package/src/transcription/service.ts +239 -10
- package/src/transcription/wav-duration.ts +39 -0
- package/src/workspace-deletion.ts +25 -1
- package/src/workspace-tool-gateway-attestations.ts +133 -0
- package/src/workspace-tool-gateway-observability.ts +64 -0
- package/src/workspace-tool-gateway-secrets.ts +104 -0
- package/src/workspace-tool-gateway.ts +921 -103
- package/src/workspace-tool-target-fence.ts +380 -0
- package/src/workspace-tool-target.ts +554 -0
- package/src/workspace-webhook-dispatch.ts +225 -0
- package/src/xai-realtime.ts +85 -81
- package/src/xai-subscription-core.ts +172 -0
- package/dist/browser-auth-broker.d.ts +0 -20
- package/dist/chunk-AJ62T5QN.js.map +0 -1
- package/dist/connection-authority-owner.d.ts +0 -59
- package/dist/routes/connection-authorities.d.ts +0 -3
- package/dist/routes/environments.d.ts +0 -5
- package/dist/routes/host-mcp-bindings.d.ts +0 -5
- package/dist/routes/host-mcp-resolvers.d.ts +0 -3
- package/dist/routes/packs.d.ts +0 -3
- package/dist/session-event-view-MU3HMNV3.js +0 -264
- package/dist/session-event-view-MU3HMNV3.js.map +0 -1
- package/dist/xai-subscription-auth.d.ts +0 -23
- package/src/browser-auth-broker.ts +0 -155
- package/src/connection-authority-owner.ts +0 -61
- package/src/routes/connection-authorities.ts +0 -139
- package/src/routes/environments.ts +0 -388
- package/src/routes/host-mcp-bindings.ts +0 -122
- package/src/routes/host-mcp-resolvers.ts +0 -41
- package/src/routes/packs.ts +0 -784
- package/src/xai-subscription-auth.ts +0 -139
package/src/app.ts
CHANGED
|
@@ -1,33 +1,92 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
|
|
3
|
+
import { registerClaudeSubscriptionAccountRoutes } from "./routes/claude-subscription-accounts";
|
|
4
|
+
import { isPreparedMcpConnectPath } from "./prepared-mcp-permissions";
|
|
5
|
+
import { registerWorkspaceModelProviderRoutes } from "./routes/workspace-model-providers";
|
|
6
|
+
import { registerClaudeSubscriptionOAuthRoutes } from "./routes/claude-subscription-oauth";
|
|
1
7
|
import { registerConnectCallbackReturns } from "./integrations/connect-callback-return";
|
|
2
8
|
import { registerFeedbackRoutes } from "./routes/feedback";
|
|
9
|
+
import { registerInboxRoutes } from "./routes/inbox";
|
|
10
|
+
import { registerWorkspaceIntegrationRoutes } from "./routes/workspace-integrations";
|
|
11
|
+
import { registerOrganizationIntegrationRoutes } from "./routes/organization-integrations";
|
|
12
|
+
import {
|
|
13
|
+
resolveWorkspaceToolTarget,
|
|
14
|
+
invokeWorkspaceToolTarget,
|
|
15
|
+
approveWorkspaceToolTarget,
|
|
16
|
+
resolveWorkspaceToolManifest,
|
|
17
|
+
} from "./workspace-tool-target";
|
|
18
|
+
import type { TargetAuthorityFence } from "./workspace-tool-target-fence";
|
|
19
|
+
import { recordWorkspaceToolTargetOperation } from "./workspace-tool-gateway-observability";
|
|
20
|
+
import {
|
|
21
|
+
CLIENT_ERRORS_PATH,
|
|
22
|
+
isClientErrorReportRequest,
|
|
23
|
+
registerClientErrorRoutes,
|
|
24
|
+
} from "./routes/client-errors";
|
|
25
|
+
import {
|
|
26
|
+
ANALYTICS_CONSENT_PATH,
|
|
27
|
+
isAnalyticsConsentReportRequest,
|
|
28
|
+
registerAnalyticsConsentRoutes,
|
|
29
|
+
} from "./routes/analytics-consent";
|
|
3
30
|
import { codemodeSessionRequest } from "./codemode";
|
|
4
|
-
import {
|
|
31
|
+
import { siteClampedAgentGuard } from "./site-agent-guard";
|
|
32
|
+
import { siteFrameGuard } from "./site-frame-guard";
|
|
33
|
+
import { SiteDataUnsupportedError } from "@opengeni/contracts/site-session-http";
|
|
34
|
+
import {
|
|
35
|
+
SiteSessionPathError,
|
|
36
|
+
OrganizationIntegrationDeniedError,
|
|
37
|
+
normalizeOrganizationAccessPolicy,
|
|
38
|
+
} from "@opengeni/contracts";
|
|
39
|
+
import {
|
|
40
|
+
httpRejectionFactsFromEnvelope,
|
|
41
|
+
httpRejectionFactsFromError,
|
|
42
|
+
readRejectionEnvelope,
|
|
43
|
+
type HttpRejectionFacts,
|
|
44
|
+
} from "./http/rejection-telemetry";
|
|
45
|
+
import { observeInteractionRouteOutcome } from "./interaction-metrics";
|
|
46
|
+
import {
|
|
47
|
+
isOAuthCallbackRoute,
|
|
48
|
+
observeConnectAttemptTransition,
|
|
49
|
+
observeOAuthCallback,
|
|
50
|
+
} from "./integration-connect-metrics";
|
|
5
51
|
import { registerModelConnectionAccessRoutes } from "./routes/model-connection-access";
|
|
6
52
|
import {
|
|
7
|
-
|
|
8
|
-
|
|
53
|
+
codeSearchDeploymentPolicy,
|
|
54
|
+
agentConfigDeploymentPolicy,
|
|
55
|
+
canonicalPublicOrigin,
|
|
9
56
|
configuredAllowedReasoningEfforts,
|
|
10
|
-
|
|
57
|
+
environmentsEncryptionKeyBytes,
|
|
11
58
|
resolveFirstPartyMcpToolPolicy,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
59
|
+
UnsupportedLatencyModeError,
|
|
60
|
+
voiceInputPricingIssues,
|
|
61
|
+
type Settings,
|
|
62
|
+
type FirstPartyMcpToolPolicySettings,
|
|
15
63
|
} from "@opengeni/config";
|
|
16
64
|
import {
|
|
65
|
+
AGENT_CAPABILITY_IDS,
|
|
17
66
|
ClientConfig,
|
|
18
67
|
CodemodeCallRequest,
|
|
19
68
|
ErrorEnvelope,
|
|
20
69
|
OPENGENI_API_CONTRACT_HEADER,
|
|
21
70
|
OPENGENI_API_CONTRACT_REVISION,
|
|
22
71
|
OPENGENI_CORRELATION_HEADER,
|
|
72
|
+
resolveAgentToolFamilies,
|
|
23
73
|
resolveWorkspaceMemoryEnabled,
|
|
24
74
|
resolveWorkspaceMemoryPromptMode,
|
|
25
75
|
VOICE_INPUT_ACCEPTED_MIME_TYPES,
|
|
76
|
+
WorkspaceVoiceInputSettings,
|
|
26
77
|
TRANSCRIPTION_RECORDING_PROVIDER_SEGMENT_SECONDS,
|
|
27
78
|
ToolGatewayApprovalRequest,
|
|
28
79
|
ToolGatewayCallRequest,
|
|
80
|
+
ToolGatewayResolveRequest,
|
|
81
|
+
ToolGatewayInvokeRequest,
|
|
82
|
+
ToolGatewayTargetApprovalRequest,
|
|
83
|
+
ToolGatewayManifestRequest,
|
|
29
84
|
type AccessGrant,
|
|
85
|
+
type ClientAgentConfig,
|
|
86
|
+
type ClientSitesConfig,
|
|
30
87
|
type ErrorCode,
|
|
88
|
+
type FirstPartyMcpToolName,
|
|
89
|
+
type Session,
|
|
31
90
|
} from "@opengeni/contracts";
|
|
32
91
|
import {
|
|
33
92
|
createDocumentServices,
|
|
@@ -39,53 +98,106 @@ import {
|
|
|
39
98
|
CodemodeOperationConflictError,
|
|
40
99
|
CodemodeOperationNotExecutableError,
|
|
41
100
|
CodemodePayloadTooLargeError,
|
|
101
|
+
CodemodeOperationLimitError,
|
|
42
102
|
CodemodeToolApprovalRequiredError,
|
|
43
103
|
CodemodeToolNotInCatalogError,
|
|
44
104
|
ConnectAttemptConflictError,
|
|
45
105
|
ConnectAttemptNotFoundError,
|
|
46
106
|
configureChildLifecycleNotices,
|
|
107
|
+
configureCodeSearchDeploymentPolicy,
|
|
47
108
|
configureWorkspaceControlRequestLockTimeoutMs,
|
|
109
|
+
setConnectAttemptTransitionObserver,
|
|
48
110
|
dbSql,
|
|
49
111
|
getManagedAuthSessionSetSnapshot,
|
|
50
112
|
getWorkspace,
|
|
51
113
|
reapManagedAuthIsolatedSessions,
|
|
52
114
|
reapExpiredManagedAuthSessionSets,
|
|
53
115
|
resolveSessionMemoryAgentScope,
|
|
116
|
+
resolveSessionAdminAuthority,
|
|
54
117
|
rlsContextForWorkspace,
|
|
55
118
|
} from "@opengeni/db";
|
|
56
119
|
import { requireSessionEventDurableFanoutCapability } from "@opengeni/events";
|
|
57
120
|
import { githubAppBotIdentityWarnings } from "@opengeni/github";
|
|
58
|
-
import {
|
|
121
|
+
import {
|
|
122
|
+
createObservability,
|
|
123
|
+
errorCauseAttributes,
|
|
124
|
+
failureDiagnostic,
|
|
125
|
+
withTraceContext,
|
|
126
|
+
withMcpTelemetry,
|
|
127
|
+
} from "@opengeni/observability";
|
|
59
128
|
import { createObjectStorage } from "@opengeni/storage";
|
|
60
129
|
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
130
|
+
import { SUPPORTED_PROTOCOL_VERSIONS } from "@modelcontextprotocol/sdk/types.js";
|
|
61
131
|
import { handleMcpRequestWithClientAbort } from "./mcp/request-abort";
|
|
62
|
-
import { Hono } from "hono";
|
|
132
|
+
import { Hono, type Context } from "hono";
|
|
63
133
|
import { bodyLimit } from "hono/body-limit";
|
|
64
134
|
import { compress } from "hono/compress";
|
|
65
135
|
import { cors } from "hono/cors";
|
|
66
136
|
import { getCookie } from "hono/cookie";
|
|
67
137
|
import { HTTPException } from "hono/http-exception";
|
|
68
|
-
import {
|
|
138
|
+
import {
|
|
139
|
+
ApiHttpError,
|
|
140
|
+
agentConfigHttpError,
|
|
141
|
+
allowanceExhaustedHttpError,
|
|
142
|
+
DATABASE_CONTENTION_ERROR_DETAIL_CODE,
|
|
143
|
+
DATABASE_UNAVAILABLE_ERROR_DETAIL_CODE,
|
|
144
|
+
databaseContentionHttpError,
|
|
145
|
+
databaseUnavailableHttpError,
|
|
146
|
+
modelUnavailableHttpError,
|
|
147
|
+
scheduledTaskTargetAccessHttpError,
|
|
148
|
+
sessionUsageLimitHttpError,
|
|
149
|
+
workspaceControlBusyHttpError,
|
|
150
|
+
} from "./http/api-error";
|
|
151
|
+
import {
|
|
152
|
+
isRequestBodyValidationError,
|
|
153
|
+
requestBodyValidationHttpError,
|
|
154
|
+
tagRequestJsonParseErrors,
|
|
155
|
+
} from "./http/request-body";
|
|
156
|
+
import { invalidPathIdentifierHttpError } from "./http/path-identifier";
|
|
157
|
+
import { replaceTrustedClientAddressHeader } from "./http/request-source";
|
|
158
|
+
import { unmatchedRoute } from "./http/unmatched-route";
|
|
69
159
|
import type { ContentfulStatusCode } from "hono/utils/http-status";
|
|
160
|
+
import { bearerApiContractHeaderCompatibility } from "./http/api-contract-compat";
|
|
161
|
+
import { deprecationHeadersMiddleware } from "./http/deprecation";
|
|
162
|
+
import {
|
|
163
|
+
boundedRegisteredRouteLabel,
|
|
164
|
+
registeredHandlerRoutePath,
|
|
165
|
+
} from "./http/registered-route-label";
|
|
70
166
|
import type { ApiRouteDeps, AppDependencies } from "@opengeni/core";
|
|
167
|
+
import { sessionUsageLimitsProducerEnabled } from "@opengeni/core";
|
|
71
168
|
import {
|
|
72
169
|
CodexCompactionV2ProviderLockedError,
|
|
73
170
|
ManagedAuthActorLeaseOutcomeUnknownError,
|
|
74
171
|
hasPermission,
|
|
75
172
|
requireAccessGrant,
|
|
76
173
|
requireAccessGrantAuthorization,
|
|
174
|
+
requireFreshAccessGrant,
|
|
175
|
+
accountScopedApiKeyWorkspaceAuthority,
|
|
176
|
+
requireAccessContext,
|
|
77
177
|
requirePermission,
|
|
78
178
|
releaseManagedAuthRequestActorLease,
|
|
79
179
|
resolveCatalogSettings,
|
|
80
180
|
resolveWorkspaceCatalogSettings,
|
|
181
|
+
resolveCallerWorkspaceModelSelections,
|
|
182
|
+
resolveDefaultSessionModelForSelections,
|
|
183
|
+
selectDefaultSessionModel,
|
|
184
|
+
isWorkspaceModelAdmissible,
|
|
185
|
+
resolveWorkspaceModelSelection,
|
|
81
186
|
validateManagedAuthRequestActorLease,
|
|
82
187
|
requireSessionAuthorization,
|
|
83
188
|
SessionAuthorizationDeniedError,
|
|
189
|
+
withSessionAuthorizationReadReuse,
|
|
84
190
|
SessionAuthorizationUnavailableError,
|
|
191
|
+
createUserPresenceRecorder,
|
|
192
|
+
registerProductUsageMetricBaselines,
|
|
193
|
+
stampPersonalApiKeyRequest,
|
|
194
|
+
verifiedDelegatedHumanAuthorizationForRequest,
|
|
85
195
|
} from "@opengeni/core";
|
|
196
|
+
import { createManagedAuthNewSignupsGate } from "./auth/new-signups-gate";
|
|
86
197
|
import {
|
|
87
198
|
createManagedAuth,
|
|
88
199
|
isolatedManagedAuthOAuthCallbackRequest,
|
|
200
|
+
managedAuthNewSignupsPausedResponse,
|
|
89
201
|
managedAuthOAuthReturnMatches,
|
|
90
202
|
resolveManagedAuthOAuthAttempt,
|
|
91
203
|
} from "./auth/managed-auth";
|
|
@@ -106,6 +218,7 @@ import {
|
|
|
106
218
|
runManagedAuthProvider,
|
|
107
219
|
} from "./auth/managed-auth-attempt-context";
|
|
108
220
|
import { createManagedEmailTransport } from "./auth/managed-email";
|
|
221
|
+
import { startManagedSignInNotificationDelivery } from "./auth/managed-sign-in-notifications";
|
|
109
222
|
import {
|
|
110
223
|
assertManagedEmailTransportMetadata,
|
|
111
224
|
assertOrganizationUserSetupQueryTransportConfigured,
|
|
@@ -115,19 +228,26 @@ import { buildOpenGeniMcpServer } from "./mcp/server";
|
|
|
115
228
|
import {
|
|
116
229
|
buildWorkspaceToolGatewayMcpServer,
|
|
117
230
|
approveWorkspaceToolGatewayCall,
|
|
118
|
-
|
|
231
|
+
callWorkspaceToolGatewayForCaller,
|
|
119
232
|
grantUsesAttemptScopedMcp,
|
|
233
|
+
prepareAttestedWorkspaceToolGateway,
|
|
120
234
|
prepareMcpOAuthWorkspaceToolGateway,
|
|
121
235
|
prepareWorkspaceToolGateway,
|
|
122
236
|
workspaceToolGatewayDeclarations,
|
|
237
|
+
workspaceToolGatewayServerTiming,
|
|
123
238
|
} from "./workspace-tool-gateway";
|
|
239
|
+
import { createWorkspaceToolGatewayCatalogAttestations } from "./workspace-tool-gateway-attestations";
|
|
240
|
+
import { recordWorkspaceToolGatewayPreparation } from "./workspace-tool-gateway-observability";
|
|
124
241
|
import {
|
|
125
242
|
isMcpOAuthResourcePath,
|
|
126
243
|
mcpOAuthAuthenticateHeader,
|
|
127
244
|
mcpOAuthBearerToken,
|
|
245
|
+
ORGANIZATION_MCP_PATH,
|
|
128
246
|
registerMcpOAuthRoutes,
|
|
129
247
|
resolveMcpOAuthRouteAccess,
|
|
248
|
+
resolveOrganizationMcpOAuthAccess,
|
|
130
249
|
} from "./mcp-oauth";
|
|
250
|
+
import { buildOrganizationMcpServer, type OrganizationMcpCaller } from "./organization-mcp";
|
|
131
251
|
import {
|
|
132
252
|
CodemodeAuthorityError,
|
|
133
253
|
CodemodeCatalogNotReadyError,
|
|
@@ -143,42 +263,65 @@ import {
|
|
|
143
263
|
BrowserControlTransportError,
|
|
144
264
|
} from "@opengeni/runtime/sandbox";
|
|
145
265
|
import { requireAccessKey } from "./http/auth";
|
|
266
|
+
import {
|
|
267
|
+
publicListenerServesMetrics,
|
|
268
|
+
registerPrometheusMetricsRoute,
|
|
269
|
+
} from "./http/metrics-listener";
|
|
146
270
|
import { allowedCorsOrigin } from "./http/cors";
|
|
271
|
+
import {
|
|
272
|
+
createLocalBrowserBoundary,
|
|
273
|
+
localBrowserRequestHost,
|
|
274
|
+
markLocalInternalDispatch,
|
|
275
|
+
} from "./http/local-browser-boundary";
|
|
147
276
|
import { withAccessGrantSessionRlsContext } from "./access-grant-rls";
|
|
148
277
|
import { registerCapabilityRoutes } from "./routes/capabilities";
|
|
149
278
|
import { registerCatalogAssetRoutes } from "./routes/catalog-assets";
|
|
150
279
|
import { registerCodexRoutes } from "./routes/codex";
|
|
151
280
|
import { registerOrganizationModelProviderRoutes } from "./routes/organization-model-providers";
|
|
281
|
+
import { registerOrganizationModelDefaultsRoutes } from "./routes/organization-model-defaults";
|
|
282
|
+
import {
|
|
283
|
+
registerModelAccountSettingsRoutes,
|
|
284
|
+
subscriptionSettingsLockedHttpError,
|
|
285
|
+
} from "./routes/model-account-settings";
|
|
286
|
+
import { registerModelAccountChoiceRoutes } from "./routes/model-account-choice";
|
|
287
|
+
import { registerSessionAdminAccessRoutes } from "./routes/session-admin-access";
|
|
288
|
+
import { registerOrganizationIntegrationPolicyRoutes } from "./routes/organization-integration-policy";
|
|
152
289
|
import { registerSuperGrokRoutes } from "./routes/supergrok";
|
|
153
290
|
import { registerConnectionRoutes } from "./routes/connections";
|
|
291
|
+
import { registerVaultProviderRoutes, vaultProviderRegistryFor } from "./routes/vault-providers";
|
|
154
292
|
import { registerConnectRoutes } from "./routes/connect";
|
|
155
|
-
|
|
156
|
-
import { registerHostMcpResolverRoutes } from "./routes/host-mcp-resolvers";
|
|
293
|
+
|
|
157
294
|
import { registerExternalIdentityLinkRoutes } from "./routes/external-identity-links";
|
|
158
295
|
import { registerDocumentRoutes } from "./routes/documents";
|
|
159
296
|
import { registerKnowledgeRoutes } from "./routes/knowledge";
|
|
160
297
|
import { registerEnrollmentRoutes } from "./routes/enrollments";
|
|
161
298
|
import { registerMachineRoutes } from "./routes/machines";
|
|
162
299
|
import { registerMemorySlackPublicationRoutes } from "./routes/memory-slack-publications";
|
|
163
|
-
import {
|
|
300
|
+
import { registerVariableSetsRemovedRoutes } from "./routes/variable-sets-removed";
|
|
301
|
+
import { variableSetsRemovedErrorResponse } from "./http/variable-sets-removed";
|
|
302
|
+
import { registerVaultRoutes } from "./routes/vaults";
|
|
164
303
|
import { registerFileRoutes } from "./routes/files";
|
|
165
304
|
import { registerApiKeyRoutes } from "./routes/api-keys";
|
|
305
|
+
import { registerOrganizationMcpConnectionRoutes } from "./routes/organization-mcp-connections";
|
|
306
|
+
import { registerPersonalApiKeyRoutes } from "./routes/personal-api-keys";
|
|
307
|
+
import { registerOrganizationServiceAccountRoutes } from "./routes/organization-service-accounts";
|
|
166
308
|
import { registerBillingRoutes } from "./routes/billing";
|
|
167
309
|
import { registerBrowserIdentityRoutes } from "./routes/browser-identities";
|
|
168
310
|
import { registerBrowserSessionRoutes } from "./routes/browser-sessions";
|
|
169
311
|
import { registerComputerSessionRoutes } from "./routes/computer-sessions";
|
|
312
|
+
import { createVaultInteractionSecretsPort } from "./interaction-secrets-vault";
|
|
170
313
|
import { registerGitHubRoutes } from "./routes/github";
|
|
171
314
|
import { registerPersonalGitHubRoutes } from "./routes/personal-github";
|
|
172
315
|
import {
|
|
173
316
|
isPersonalGitHubGitBrokerRequest,
|
|
174
317
|
registerPersonalGitHubGitBrokerRoutes,
|
|
175
318
|
} from "./routes/personal-github-git-broker";
|
|
319
|
+
import { registerDesktopDistributionRoutes } from "./routes/desktop-distribution";
|
|
176
320
|
import { registerInstallRoutes } from "./routes/install";
|
|
177
321
|
import { registerApiIntegrationRoutes } from "./routes/api-integrations";
|
|
178
322
|
import { registerIntegrationFacetRoutes } from "./routes/integration-facets";
|
|
179
323
|
import { registerInteractionResourceRoutes } from "./routes/interaction-resources";
|
|
180
324
|
import { registerPrReviewRoutes } from "./routes/pr-review";
|
|
181
|
-
import { registerPackRoutes } from "./routes/packs";
|
|
182
325
|
import { registerAutomationRoutes } from "./routes/automations";
|
|
183
326
|
import { registerPluginRoutes } from "./routes/plugins";
|
|
184
327
|
import { registerSkillRoutes } from "./routes/skills";
|
|
@@ -186,37 +329,58 @@ import { registerChannelRoutes } from "./routes/channels";
|
|
|
186
329
|
import { registerRigRoutes } from "./routes/rigs";
|
|
187
330
|
import { registerScheduledTaskRoutes } from "./routes/scheduled-tasks";
|
|
188
331
|
import { registerSessionRoutes } from "./routes/sessions";
|
|
332
|
+
import {
|
|
333
|
+
archivedSessionImportErrorResponse,
|
|
334
|
+
isSessionHistoryImportRequest,
|
|
335
|
+
registerSessionHistoryImportRoutes,
|
|
336
|
+
} from "./routes/session-history-imports";
|
|
189
337
|
import { registerSocialRoutes } from "./routes/social";
|
|
190
338
|
import { registerWorkspaceRoutes } from "./routes/workspaces";
|
|
339
|
+
import { registerUsageAllowanceRoutes } from "./routes/usage-allowances";
|
|
191
340
|
import { registerWorkspaceInstructionPolicyRoutes } from "./routes/workspace-instruction-policies";
|
|
192
341
|
import { registerWorkspaceLearningRoutes } from "./routes/workspace-learning";
|
|
193
342
|
import { registerCompanyProfileRoutes } from "./routes/company-profile";
|
|
194
343
|
import { registerCompanyBrainRoutes } from "./routes/company-brain";
|
|
195
344
|
import { registerSlackTaskPolicyRoutes } from "./routes/slack-task-policy";
|
|
345
|
+
import { registerSlackBotAccessRoutes } from "./routes/slack-bot-access";
|
|
196
346
|
import { registerWorkspaceStateRoutes } from "./routes/workspace-state";
|
|
197
347
|
import { registerWorkspaceArtifactRoutes } from "./routes/workspace-artifacts";
|
|
348
|
+
import { isSiteDataFileUploadRequest, registerSiteDataRoutes } from "./routes/site-data";
|
|
349
|
+
import { registerSitePublishingRoutes } from "./routes/site-publishing";
|
|
350
|
+
import { configuredSitesDomain } from "./site-publishing/service";
|
|
351
|
+
import { registerSiteRuntimeRoutes } from "./routes/site-runtime";
|
|
198
352
|
import { registerArtifactCatalogRoutes } from "./routes/artifact-catalog";
|
|
353
|
+
import { registerSiteSqlRoutes } from "./routes/site-sql";
|
|
199
354
|
import { registerPreferenceRegistryRoutes } from "./routes/preference-registry";
|
|
200
355
|
import { registerInsightsRoutes } from "./routes/insights";
|
|
356
|
+
import { registerInsightsUsageRoutes } from "./routes/insights-usage";
|
|
201
357
|
import { registerTranscriptionRoutes } from "./routes/transcriptions";
|
|
358
|
+
import { registerChatCompletionRoutes } from "./routes/chat-completions";
|
|
359
|
+
import { createModelCallService } from "./model-calls/service";
|
|
202
360
|
import { registerEditableArtifactRoutes } from "./routes/editable-artifacts";
|
|
361
|
+
import { registerSessionArtifactAssociationRoutes } from "./routes/session-artifact-associations";
|
|
203
362
|
import { registerVideoGenerationRoutes } from "./routes/video-generation";
|
|
204
363
|
import { registerCanonicalHumanIdentityRoutes } from "./routes/canonical-human-identities";
|
|
205
364
|
import { registerOrganizationMembershipRoutes } from "./routes/organization-memberships";
|
|
206
365
|
import { registerOrganizationSessionRoutes } from "./routes/organization-sessions";
|
|
207
366
|
import { registerOrganizationRecoveryRoutes } from "./routes/organization-recovery";
|
|
208
367
|
import { registerManagedOnboardingRoutes } from "./routes/managed-onboarding";
|
|
368
|
+
import {
|
|
369
|
+
registerManagedSignInMethodRoutes,
|
|
370
|
+
handleManagedSignInConnectCallback,
|
|
371
|
+
} from "./routes/managed-sign-in-methods";
|
|
372
|
+
import { registerNativeAppAuthRoutes } from "./routes/native-app-auth";
|
|
209
373
|
import {
|
|
210
374
|
registerManagedAuthSessionSetRoutes,
|
|
211
375
|
requireManagedAuthProviderRouteAllowed,
|
|
212
376
|
scrubManagedAuthProviderResponse,
|
|
213
377
|
} from "./routes/managed-auth-session-sets";
|
|
214
378
|
import { registerUserResourceAuthorityRoutes } from "./routes/user-resource-authorities";
|
|
215
|
-
import { registerConnectionAuthorityRoutes } from "./routes/connection-authorities";
|
|
216
379
|
import { projectClientModel } from "./model-catalog";
|
|
217
380
|
import { createTranscriptionService } from "./transcription/service";
|
|
218
381
|
import { createFfmpegTranscriptionSegmenter } from "./transcription/segmenter";
|
|
219
382
|
import { registerSlackInteractionRoutes } from "./integrations/slack-interactions";
|
|
383
|
+
import { resolveSignedStateSecret } from "./signed-state-secret";
|
|
220
384
|
|
|
221
385
|
export { allowedCorsOrigin } from "./http/cors";
|
|
222
386
|
|
|
@@ -278,6 +442,25 @@ export async function resolveWorkspaceMcpRouteDeps(
|
|
|
278
442
|
return { ...routeDeps, catalogSourceSettings, settings: resolvedCatalog.settings };
|
|
279
443
|
}
|
|
280
444
|
|
|
445
|
+
/**
|
|
446
|
+
* Signed browser state must verify on every API replica; see
|
|
447
|
+
* {@link resolveSignedStateSecret}. A per-process key is announced once so a
|
|
448
|
+
* multi-replica operator knows why such round trips fail.
|
|
449
|
+
*/
|
|
450
|
+
function signedStateSecret(
|
|
451
|
+
settings: AppDependencies["settings"],
|
|
452
|
+
observability: Pick<ReturnType<typeof createObservability>, "warn">,
|
|
453
|
+
): string {
|
|
454
|
+
const resolved = resolveSignedStateSecret(settings);
|
|
455
|
+
if (resolved.source === "process_random" && !["local", "test"].includes(settings.environment)) {
|
|
456
|
+
observability.warn(
|
|
457
|
+
"Signed browser state uses a per-process key; set OPENGENI_GITHUB_APP_MANIFEST_STATE_SECRET or OPENGENI_INTEGRATIONS_STATE_SECRET before running more than one API replica",
|
|
458
|
+
{ source: resolved.source },
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
return resolved.secret;
|
|
462
|
+
}
|
|
463
|
+
|
|
281
464
|
export function createAppComposition(deps: AppDependencies): {
|
|
282
465
|
app: Hono;
|
|
283
466
|
routeDeps: ApiRouteDeps;
|
|
@@ -291,14 +474,34 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
291
474
|
// Pause) run inside API-originated db commands; install the boot-validated
|
|
292
475
|
// rollout flag once for this process.
|
|
293
476
|
configureChildLifecycleNotices({ enabled: deps.settings.childLifecycleNoticesEnabled });
|
|
477
|
+
// Sessions created by this process freeze their code_search decision from
|
|
478
|
+
// the boot-parsed deployment policy.
|
|
479
|
+
configureCodeSearchDeploymentPolicy(codeSearchDeploymentPolicy(deps.settings));
|
|
294
480
|
const managedEmailTransport =
|
|
295
481
|
deps.managedEmailTransport ?? createManagedEmailTransport(deps.settings);
|
|
296
482
|
assertManagedEmailTransportMetadata(managedEmailTransport);
|
|
483
|
+
const observability =
|
|
484
|
+
deps.observability ?? createObservability(deps.settings, { component: "api" });
|
|
485
|
+
// Connect attempt state lives in the db layer; count its committed
|
|
486
|
+
// transitions with bounded provider/state labels (integration-connect-metrics).
|
|
487
|
+
setConnectAttemptTransitionObserver((transition) =>
|
|
488
|
+
observeConnectAttemptTransition(observability, transition),
|
|
489
|
+
);
|
|
490
|
+
const managedAuthNewSignupsGate = createManagedAuthNewSignupsGate({
|
|
491
|
+
db: deps.db,
|
|
492
|
+
settings: deps.settings,
|
|
493
|
+
observability,
|
|
494
|
+
});
|
|
297
495
|
const managedAuth =
|
|
298
|
-
deps.managedAuth ??
|
|
496
|
+
deps.managedAuth ??
|
|
497
|
+
createManagedAuth(deps.settings, deps.db, managedEmailTransport, {
|
|
498
|
+
observability,
|
|
499
|
+
newSignupsGate: managedAuthNewSignupsGate,
|
|
500
|
+
});
|
|
299
501
|
const managedAuthSessionAdapter =
|
|
300
502
|
deps.managedAuthSessionAdapter ??
|
|
301
503
|
(managedAuth ? createBetterAuthSessionAdapter(managedAuth, deps.db) : null);
|
|
504
|
+
if (managedAuth) startManagedSignInNotificationDelivery(deps.db, managedEmailTransport);
|
|
302
505
|
const objectStorage =
|
|
303
506
|
deps.objectStorage === undefined ? createObjectStorage(deps.settings) : deps.objectStorage;
|
|
304
507
|
let documentServices: DocumentServices | null = deps.documentServices ?? null;
|
|
@@ -369,8 +572,6 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
369
572
|
// concrete for routes; it throws SandboxResumeError when sandboxBackend=none.
|
|
370
573
|
const sandboxClient = deps.sandboxClient ?? createApiSandboxClient(deps.settings);
|
|
371
574
|
const resumeBoxById = deps.resumeBoxById ?? makeResumeBoxById(sandboxClient);
|
|
372
|
-
const observability =
|
|
373
|
-
deps.observability ?? createObservability(deps.settings, { component: "api" });
|
|
374
575
|
if (
|
|
375
576
|
managedAuth &&
|
|
376
577
|
deps.settings.managedAuthSessionSetMode !== "legacy" &&
|
|
@@ -389,12 +590,27 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
389
590
|
}, MANAGED_AUTH_REAPER_INTERVAL_MS);
|
|
390
591
|
(timer as ReturnType<typeof setInterval> & { unref?: () => void }).unref?.();
|
|
391
592
|
}
|
|
593
|
+
if (deps.transcription === undefined) {
|
|
594
|
+
// Voice pricing problems never fail boot (workers share this config); the
|
|
595
|
+
// affected provider is withheld and the operator sees exactly why, once.
|
|
596
|
+
for (const issue of voiceInputPricingIssues(deps.settings)) {
|
|
597
|
+
observability.error(
|
|
598
|
+
`Voice input provider ${issue.providerId} is unavailable: ${issue.message}`,
|
|
599
|
+
{
|
|
600
|
+
providerId: issue.providerId,
|
|
601
|
+
env: issue.env,
|
|
602
|
+
reason: issue.reason,
|
|
603
|
+
},
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
392
607
|
const transcription =
|
|
393
608
|
deps.transcription === undefined
|
|
394
609
|
? createTranscriptionService({
|
|
395
610
|
settings: deps.settings,
|
|
396
611
|
db: deps.db,
|
|
397
612
|
...(deps.codexFetch ? { codexFetch: deps.codexFetch } : {}),
|
|
613
|
+
log: (message, attributes) => observability.error(message, attributes),
|
|
398
614
|
})
|
|
399
615
|
: deps.transcription;
|
|
400
616
|
const transcriptionSegmenter =
|
|
@@ -403,12 +619,36 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
403
619
|
ffmpegPath: deps.settings.voiceInputFfmpegPath,
|
|
404
620
|
})
|
|
405
621
|
: deps.transcriptionSegmenter;
|
|
622
|
+
const modelCalls =
|
|
623
|
+
deps.modelCalls === undefined
|
|
624
|
+
? createModelCallService({
|
|
625
|
+
db: deps.db,
|
|
626
|
+
settings: deps.settings,
|
|
627
|
+
...(deps.catalogSourceSettings
|
|
628
|
+
? { catalogSourceSettings: deps.catalogSourceSettings }
|
|
629
|
+
: {}),
|
|
630
|
+
entitlements: deps.entitlements ?? null,
|
|
631
|
+
...(deps.codexFetch ? { codexFetch: deps.codexFetch } : {}),
|
|
632
|
+
log: (message, attributes) => observability.error(message, attributes),
|
|
633
|
+
usageLimitMetrics: observability,
|
|
634
|
+
})
|
|
635
|
+
: deps.modelCalls;
|
|
636
|
+
registerProductUsageMetricBaselines(observability);
|
|
637
|
+
// Server-side presence counts managed people, so it exists only where
|
|
638
|
+
// canonical managed browser sessions exist. Writes are batched off the
|
|
639
|
+
// request path; see packages/core/src/user-presence.ts.
|
|
640
|
+
const userPresence =
|
|
641
|
+
deps.userPresence !== undefined
|
|
642
|
+
? deps.userPresence
|
|
643
|
+
: deps.settings.productAccessMode === "managed"
|
|
644
|
+
? createUserPresenceRecorder({ db: deps.db, observability })
|
|
645
|
+
: null;
|
|
406
646
|
const routeDeps: ApiRouteDeps = {
|
|
407
647
|
...deps,
|
|
408
648
|
resolveCatalogSettings: () => resolveCatalogSettings(deps.db, deps.settings),
|
|
409
649
|
observability,
|
|
410
|
-
|
|
411
|
-
|
|
650
|
+
userPresence,
|
|
651
|
+
githubStateSecret: deps.githubStateSecret ?? signedStateSecret(deps.settings, observability),
|
|
412
652
|
managedAuth,
|
|
413
653
|
managedAuthSessionAdapter,
|
|
414
654
|
managedEmailTransport,
|
|
@@ -417,20 +657,101 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
417
657
|
getDocumentServices,
|
|
418
658
|
transcription,
|
|
419
659
|
transcriptionSegmenter,
|
|
660
|
+
modelCalls,
|
|
420
661
|
...(sandboxClient ? { sandboxClient } : {}),
|
|
421
662
|
resumeBoxById,
|
|
422
663
|
};
|
|
423
664
|
const app = new Hono();
|
|
424
|
-
|
|
665
|
+
// Body/auth middleware may replace c.req.raw. The Hono context remains the
|
|
666
|
+
// same for the response, diagnostic and completion log of this request.
|
|
667
|
+
const correlationIds = new WeakMap<Context, string>();
|
|
668
|
+
const loggedHttpFailures = new WeakSet<Context>();
|
|
669
|
+
const logHttpFailure = (c: Context, status: number, code: ErrorCode, error?: unknown) => {
|
|
670
|
+
if (loggedHttpFailures.has(c)) return;
|
|
671
|
+
loggedHttpFailures.add(c);
|
|
672
|
+
const correlationId = correlationIds.get(c) ?? crypto.randomUUID();
|
|
673
|
+
const route = routeLabel(new URL(c.req.url).pathname, registeredHandlerRoutePath(c));
|
|
674
|
+
const input =
|
|
675
|
+
error === undefined
|
|
676
|
+
? null
|
|
677
|
+
: {
|
|
678
|
+
code: "http_request_failed" as const,
|
|
679
|
+
stage: "http.request" as const,
|
|
680
|
+
error,
|
|
681
|
+
};
|
|
682
|
+
const diagnostic = input ? failureDiagnostic(input, deps.settings.deploymentRevision) : null;
|
|
683
|
+
// Diagnostics and logging are observers, never a new response failure.
|
|
684
|
+
try {
|
|
685
|
+
if (input && diagnostic) {
|
|
686
|
+
observability.recordFailureDiagnostic({ ...input, diagnosticId: diagnostic.diagnosticId });
|
|
687
|
+
}
|
|
688
|
+
} catch {
|
|
689
|
+
/* Preserve the original error response if the diagnostic sink fails. */
|
|
690
|
+
}
|
|
691
|
+
try {
|
|
692
|
+
observability.error("HTTP request failed", {
|
|
693
|
+
method: c.req.method,
|
|
694
|
+
route,
|
|
695
|
+
status,
|
|
696
|
+
correlationId,
|
|
697
|
+
errorCode: code,
|
|
698
|
+
errorClass: "HttpOperationError",
|
|
699
|
+
origin: "api",
|
|
700
|
+
reasonKind: diagnostic?.causes[0]?.kind ?? "Response",
|
|
701
|
+
...(diagnostic ? { diagnosticId: diagnostic.diagnosticId } : {}),
|
|
702
|
+
...(error === undefined ? {} : errorCauseAttributes(error)),
|
|
703
|
+
});
|
|
704
|
+
observability.incrementCounter({
|
|
705
|
+
name: "opengeni_http_errors_total",
|
|
706
|
+
help: "Total Opengeni HTTP request failures by bounded route, status, and stable code.",
|
|
707
|
+
labels: { route, status: String(status), code },
|
|
708
|
+
});
|
|
709
|
+
} catch {
|
|
710
|
+
/* Preserve the original error response if the logger fails. */
|
|
711
|
+
}
|
|
712
|
+
};
|
|
425
713
|
|
|
426
714
|
app.use("*", async (c, next) => {
|
|
427
715
|
const correlationId =
|
|
428
716
|
boundedCorrelationId(c.req.header(OPENGENI_CORRELATION_HEADER)) ?? crypto.randomUUID();
|
|
429
|
-
correlationIds.set(c
|
|
717
|
+
correlationIds.set(c, correlationId);
|
|
430
718
|
c.header(OPENGENI_CORRELATION_HEADER, correlationId);
|
|
431
719
|
await next();
|
|
432
720
|
});
|
|
433
721
|
|
|
722
|
+
// Unauthenticated local mode: admit only requests addressed to this computer
|
|
723
|
+
// and, when a browser sent them, from this stack's web app (see
|
|
724
|
+
// http/local-browser-boundary.ts). Runs before CORS so a refused preflight
|
|
725
|
+
// carries no CORS grant, and logs each distinct refused Host or Origin once
|
|
726
|
+
// because the browser shows only a generic CORS error. Null outside local
|
|
727
|
+
// development.
|
|
728
|
+
const localBrowserBoundary = createLocalBrowserBoundary(deps.settings, {
|
|
729
|
+
warn: (message, attributes) => observability.warn(message, attributes),
|
|
730
|
+
});
|
|
731
|
+
if (localBrowserBoundary) {
|
|
732
|
+
app.use("*", async (c, next) => {
|
|
733
|
+
const rejection = localBrowserBoundary.rejection(c.req.raw);
|
|
734
|
+
if (rejection) {
|
|
735
|
+
throw new ApiHttpError(rejection.status, {
|
|
736
|
+
code: "forbidden",
|
|
737
|
+
message: rejection.message,
|
|
738
|
+
retryable: false,
|
|
739
|
+
details: { code: rejection.code },
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
await next();
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// Better Auth keys its rate limits and session addresses on a request
|
|
747
|
+
// header. Drop any caller-supplied copy everywhere and stamp the trusted
|
|
748
|
+
// source address on managed-auth routes before any route derives a Better
|
|
749
|
+
// Auth request from this one.
|
|
750
|
+
app.use("*", async (c, next) => {
|
|
751
|
+
replaceTrustedClientAddressHeader(c, deps.settings, c.req.path.startsWith("/v1/auth/"));
|
|
752
|
+
await next();
|
|
753
|
+
});
|
|
754
|
+
|
|
434
755
|
app.use("*", async (c, next) => {
|
|
435
756
|
const oauthToken = mcpOAuthBearerToken(c.req.raw);
|
|
436
757
|
if (
|
|
@@ -452,15 +773,28 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
452
773
|
"X-OpenGeni-Access-Key",
|
|
453
774
|
"X-OpenGeni-Api-Contract",
|
|
454
775
|
"X-OpenGeni-Actor-Epoch",
|
|
776
|
+
// Resumable voice-input chunk integrity/timing (cross-origin embeds).
|
|
777
|
+
"X-OpenGeni-Chunk-Duration-Milliseconds",
|
|
778
|
+
"X-OpenGeni-Chunk-Sha256",
|
|
779
|
+
"X-OpenGeni-Chunk-Start-Milliseconds",
|
|
455
780
|
"X-OpenGeni-Correlation-Id",
|
|
456
781
|
"X-OpenGeni-Session-Csrf",
|
|
782
|
+
// Session scope for a session proxy; the API itself ignores it.
|
|
783
|
+
"X-OpenGeni-Session-Id",
|
|
784
|
+
// Set by Site bridges on every bridged request (Site Data frame clamp).
|
|
785
|
+
"X-OpenGeni-Site-Frame",
|
|
786
|
+
"X-OpenGeni-Site-Frame-Id",
|
|
457
787
|
"X-OpenGeni-Site-Id",
|
|
458
788
|
"X-OpenGeni-Site-Version",
|
|
459
789
|
"X-OpenGeni-Subject",
|
|
790
|
+
"X-OpenGeni-User-Activity",
|
|
460
791
|
],
|
|
461
792
|
exposeHeaders: [
|
|
462
793
|
"Accept-Ranges",
|
|
463
794
|
"Content-Range",
|
|
795
|
+
"Deprecation",
|
|
796
|
+
"Link",
|
|
797
|
+
"Sunset",
|
|
464
798
|
"X-OpenGeni-Api-Contract",
|
|
465
799
|
"X-OpenGeni-Actor-Epoch",
|
|
466
800
|
"X-OpenGeni-Actor-State",
|
|
@@ -471,6 +805,8 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
471
805
|
"X-OpenGeni-Event-Mode",
|
|
472
806
|
"X-OpenGeni-Event-Result",
|
|
473
807
|
"X-OpenGeni-Event-Result-Mode",
|
|
808
|
+
"X-OpenGeni-Export-Next-Cursor",
|
|
809
|
+
"X-OpenGeni-Export-Truncated",
|
|
474
810
|
"X-OpenGeni-Forensic-Exact",
|
|
475
811
|
"X-OpenGeni-Has-More",
|
|
476
812
|
"X-OpenGeni-Next-After",
|
|
@@ -494,8 +830,25 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
494
830
|
allowedCorsOrigin(deps.settings.corsAllowOriginRegex, origin) ? origin : null,
|
|
495
831
|
});
|
|
496
832
|
|
|
833
|
+
const localCors = localBrowserBoundary
|
|
834
|
+
? cors({
|
|
835
|
+
...corsHeaders,
|
|
836
|
+
credentials: true,
|
|
837
|
+
origin: (origin, c) =>
|
|
838
|
+
localBrowserBoundary.originAllowed(origin, localBrowserRequestHost(c.req.raw))
|
|
839
|
+
? origin
|
|
840
|
+
: null,
|
|
841
|
+
})
|
|
842
|
+
: null;
|
|
843
|
+
|
|
497
844
|
app.use("*", (c, next) => {
|
|
498
845
|
const origin = c.req.header("origin");
|
|
846
|
+
if (localCors) {
|
|
847
|
+
// The boundary above already refused every other origin. Local mode
|
|
848
|
+
// never answers with wildcard CORS: a request without credentials acts
|
|
849
|
+
// as the local user, so any site could otherwise read its responses.
|
|
850
|
+
return origin ? localCors(c, next) : next();
|
|
851
|
+
}
|
|
499
852
|
const middleware =
|
|
500
853
|
origin && allowedCorsOrigin(deps.settings.corsAllowOriginRegex, origin)
|
|
501
854
|
? credentialedCors
|
|
@@ -512,7 +865,19 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
512
865
|
// Git packfiles must stay streaming and can legitimately exceed the JSON
|
|
513
866
|
// request ceiling. The exact closed broker routes apply their own method,
|
|
514
867
|
// content-type, authority, and idle-deadline checks.
|
|
515
|
-
|
|
868
|
+
const pathname = new URL(c.req.url).pathname;
|
|
869
|
+
if (isPersonalGitHubGitBrokerRequest(c.req.method, pathname)) {
|
|
870
|
+
await next();
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
// The anonymous web beacons and Site Data uploads enforce their own limits
|
|
874
|
+
// on the streamed body; the generic ceiling would buffer far more first.
|
|
875
|
+
if (
|
|
876
|
+
isClientErrorReportRequest(c.req.method, pathname) ||
|
|
877
|
+
isAnalyticsConsentReportRequest(c.req.method, pathname) ||
|
|
878
|
+
isSessionHistoryImportRequest(c.req.method, pathname) ||
|
|
879
|
+
isSiteDataFileUploadRequest(c.req.method, pathname)
|
|
880
|
+
) {
|
|
516
881
|
await next();
|
|
517
882
|
return;
|
|
518
883
|
}
|
|
@@ -533,75 +898,126 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
533
898
|
}
|
|
534
899
|
});
|
|
535
900
|
|
|
901
|
+
// Public-route deprecations (docs/design/api-compatibility-policy.md) are
|
|
902
|
+
// advertised on every response of the affected route, errors included.
|
|
903
|
+
app.use("/v1/*", deprecationHeadersMiddleware());
|
|
904
|
+
// A pinned SDK must not be told about a revision it is built to reject; see
|
|
905
|
+
// the policy's contract-header rule. Registered before the contract fence so
|
|
906
|
+
// it observes the header that fence sets.
|
|
907
|
+
app.use("/v1/*", bearerApiContractHeaderCompatibility());
|
|
908
|
+
|
|
536
909
|
app.use("*", async (c, next) => {
|
|
537
910
|
const url = new URL(c.req.url);
|
|
538
|
-
const route = routeLabel(url.pathname);
|
|
539
|
-
const correlationId = correlationIds.get(c
|
|
911
|
+
const route = routeLabel(url.pathname, registeredHandlerRoutePath(c));
|
|
912
|
+
const correlationId = correlationIds.get(c) ?? crypto.randomUUID();
|
|
540
913
|
const start = performance.now();
|
|
541
|
-
const span = observability.startSpan(
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
914
|
+
const span = observability.startSpan(
|
|
915
|
+
`HTTP ${c.req.method} ${route}`,
|
|
916
|
+
{
|
|
917
|
+
"http.request.method": c.req.method,
|
|
918
|
+
"opengeni.route": route,
|
|
919
|
+
},
|
|
920
|
+
{ parent: null },
|
|
921
|
+
);
|
|
922
|
+
return await withMcpTelemetry(observability, span.traceId, () =>
|
|
923
|
+
withTraceContext(span, async () => {
|
|
924
|
+
try {
|
|
925
|
+
await next();
|
|
926
|
+
const status = c.res.status || 200;
|
|
927
|
+
// Hono normally handles exceptions in onError before next() resolves.
|
|
928
|
+
// Also cover a handler returning a 5xx without throwing.
|
|
929
|
+
if (status >= 500) logHttpFailure(c, status, errorCodeForStatus(status), c.error);
|
|
930
|
+
const durationSeconds = (performance.now() - start) / 1000;
|
|
931
|
+
observability.recordHttpRequest({
|
|
932
|
+
method: c.req.method,
|
|
933
|
+
route,
|
|
934
|
+
status,
|
|
935
|
+
durationSeconds,
|
|
936
|
+
});
|
|
937
|
+
span.end({
|
|
938
|
+
attributes: {
|
|
939
|
+
"http.response.status_code": status,
|
|
940
|
+
"opengeni.duration_ms": Math.round(durationSeconds * 1000),
|
|
941
|
+
},
|
|
942
|
+
...(c.error ? { error: c.error } : {}),
|
|
943
|
+
});
|
|
944
|
+
const rejection =
|
|
945
|
+
status >= 400
|
|
946
|
+
? httpRejectionFactsFromEnvelope(
|
|
947
|
+
await readRejectionEnvelope(c.res),
|
|
948
|
+
errorCodeForStatus(status),
|
|
949
|
+
)
|
|
950
|
+
: null;
|
|
951
|
+
observeInteractionRouteOutcome(observability, {
|
|
952
|
+
method: c.req.method,
|
|
953
|
+
route,
|
|
954
|
+
status,
|
|
955
|
+
durationMs: durationSeconds * 1000,
|
|
956
|
+
rejectionReason: rejection?.reason,
|
|
957
|
+
});
|
|
958
|
+
if (isOAuthCallbackRoute(route)) {
|
|
959
|
+
observeOAuthCallback(observability, {
|
|
960
|
+
route,
|
|
961
|
+
status,
|
|
962
|
+
location: c.res.headers.get("location"),
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
if (rejection) {
|
|
966
|
+
observability.recordHttpRejection({
|
|
967
|
+
method: c.req.method,
|
|
968
|
+
route,
|
|
969
|
+
status,
|
|
970
|
+
code: rejection.code,
|
|
971
|
+
reason: rejection.reason,
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
observability.info("HTTP request completed", {
|
|
975
|
+
method: c.req.method,
|
|
976
|
+
route,
|
|
977
|
+
status,
|
|
978
|
+
durationMs: Math.round(durationSeconds * 1000),
|
|
979
|
+
traceId: span.traceId,
|
|
980
|
+
spanId: span.spanId,
|
|
981
|
+
correlationId,
|
|
982
|
+
...(rejection ? rejectionLogAttributes(rejection) : {}),
|
|
983
|
+
});
|
|
984
|
+
} catch (error) {
|
|
985
|
+
const status = httpStatusForError(error);
|
|
986
|
+
const errorCode = errorCodeForStatus(status);
|
|
987
|
+
const durationSeconds = (performance.now() - start) / 1000;
|
|
988
|
+
observability.recordHttpRequest({
|
|
989
|
+
method: c.req.method,
|
|
990
|
+
route,
|
|
991
|
+
status,
|
|
992
|
+
durationSeconds,
|
|
993
|
+
});
|
|
994
|
+
const rejection = httpRejectionFactsFromError(error, errorCode);
|
|
995
|
+
observeInteractionRouteOutcome(observability, {
|
|
996
|
+
method: c.req.method,
|
|
997
|
+
route,
|
|
998
|
+
status,
|
|
999
|
+
durationMs: durationSeconds * 1000,
|
|
1000
|
+
rejectionReason: rejection.reason,
|
|
1001
|
+
});
|
|
1002
|
+
observability.recordHttpRejection({
|
|
1003
|
+
method: c.req.method,
|
|
1004
|
+
route,
|
|
1005
|
+
status,
|
|
1006
|
+
code: rejection.code,
|
|
1007
|
+
reason: rejection.reason,
|
|
1008
|
+
});
|
|
1009
|
+
span.end({
|
|
1010
|
+
attributes: {
|
|
1011
|
+
"http.response.status_code": status,
|
|
1012
|
+
"opengeni.duration_ms": Math.round(durationSeconds * 1000),
|
|
1013
|
+
},
|
|
1014
|
+
error,
|
|
1015
|
+
});
|
|
1016
|
+
logHttpFailure(c, status, errorCode, error);
|
|
1017
|
+
throw error;
|
|
1018
|
+
}
|
|
1019
|
+
}),
|
|
1020
|
+
);
|
|
605
1021
|
});
|
|
606
1022
|
|
|
607
1023
|
const accessKeyBoundary = requireAccessKey(deps.settings);
|
|
@@ -616,17 +1032,66 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
616
1032
|
return await accessKeyBoundary(c, next);
|
|
617
1033
|
});
|
|
618
1034
|
|
|
1035
|
+
// Malformed JSON in a client request body is a 400 wherever a route reads it.
|
|
1036
|
+
app.use("/v1/*", async (c, next) => {
|
|
1037
|
+
tagRequestJsonParseErrors(c);
|
|
1038
|
+
await next();
|
|
1039
|
+
});
|
|
1040
|
+
|
|
1041
|
+
// A request no registered handler answers is a 404 (or a 405 when the path
|
|
1042
|
+
// exists for other methods) before any authentication, authorization, or
|
|
1043
|
+
// contract middleware can turn it into a misleading 401/409/503. This runs
|
|
1044
|
+
// after the deployment perimeter so an unauthenticated caller of a
|
|
1045
|
+
// key-protected deployment learns nothing new.
|
|
1046
|
+
app.use("/v1/*", async (c, next) => {
|
|
1047
|
+
const unmatched = unmatchedRoute(app, c);
|
|
1048
|
+
if (!unmatched) {
|
|
1049
|
+
await next();
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
1052
|
+
const requestId = correlationIds.get(c) ?? crypto.randomUUID();
|
|
1053
|
+
c.header(OPENGENI_API_CONTRACT_HEADER, OPENGENI_API_CONTRACT_REVISION);
|
|
1054
|
+
if (unmatched.status === 405) c.header("allow", unmatched.allow.join(", "));
|
|
1055
|
+
return c.json(
|
|
1056
|
+
ErrorEnvelope.parse({
|
|
1057
|
+
error: {
|
|
1058
|
+
status: unmatched.status,
|
|
1059
|
+
code: "not_found",
|
|
1060
|
+
message:
|
|
1061
|
+
unmatched.status === 405
|
|
1062
|
+
? `Method ${c.req.method} is not supported for this resource. Allowed: ${unmatched.allow.join(", ")}.`
|
|
1063
|
+
: "Resource not found.",
|
|
1064
|
+
retryable: false,
|
|
1065
|
+
requestId,
|
|
1066
|
+
},
|
|
1067
|
+
}),
|
|
1068
|
+
unmatched.status,
|
|
1069
|
+
);
|
|
1070
|
+
});
|
|
1071
|
+
|
|
1072
|
+
// A personal API key acts as its owner. Verify it and stamp this exact raw
|
|
1073
|
+
// Request before any route resolves access; the body limit above may have
|
|
1074
|
+
// rebuilt the Request, so this runs after it.
|
|
1075
|
+
app.use("/v1/*", async (c, next) => {
|
|
1076
|
+
await stampPersonalApiKeyRequest(c.req.raw, deps);
|
|
1077
|
+
await next();
|
|
1078
|
+
});
|
|
1079
|
+
|
|
619
1080
|
app.use("/v1/*", async (c, next) => {
|
|
620
1081
|
c.header(OPENGENI_API_CONTRACT_HEADER, OPENGENI_API_CONTRACT_REVISION);
|
|
621
1082
|
if (
|
|
622
1083
|
deps.settings.environment !== "test" &&
|
|
623
|
-
|
|
624
|
-
|
|
1084
|
+
apiContractAdmission({
|
|
1085
|
+
method: c.req.method,
|
|
1086
|
+
pathname: new URL(c.req.url).pathname,
|
|
1087
|
+
authorization: c.req.header("authorization"),
|
|
1088
|
+
claimedRevision: c.req.header(OPENGENI_API_CONTRACT_HEADER),
|
|
1089
|
+
}) === "reject"
|
|
625
1090
|
) {
|
|
626
1091
|
return c.json(
|
|
627
1092
|
{
|
|
628
1093
|
code: "API_CONTRACT_CHANGED",
|
|
629
|
-
message: "
|
|
1094
|
+
message: "Opengeni updated. Reload this client before changing state.",
|
|
630
1095
|
apiContractRevision: OPENGENI_API_CONTRACT_REVISION,
|
|
631
1096
|
},
|
|
632
1097
|
409,
|
|
@@ -669,10 +1134,72 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
669
1134
|
// wildcard handler or the provider returns its own 404 first.
|
|
670
1135
|
registerManagedOnboardingRoutes(app, routeDeps);
|
|
671
1136
|
registerManagedAuthSessionSetRoutes(app, routeDeps);
|
|
1137
|
+
registerManagedSignInMethodRoutes(app, routeDeps);
|
|
1138
|
+
registerNativeAppAuthRoutes(app, routeDeps);
|
|
672
1139
|
if (managedAuth) {
|
|
673
1140
|
app.on(["GET", "POST"], "/v1/auth/*", async (c) => {
|
|
674
1141
|
const pathname = new URL(c.req.url).pathname;
|
|
675
1142
|
const oauthCallbackProvider = managedAuthOAuthCallbackProvider(pathname);
|
|
1143
|
+
if (
|
|
1144
|
+
pathname === "/v1/auth/sign-up/email" &&
|
|
1145
|
+
c.req.method === "POST" &&
|
|
1146
|
+
!(await managedAuthNewSignupsGate.signupsOpen())
|
|
1147
|
+
) {
|
|
1148
|
+
// Launch-load safety switch: refuse before Better Auth hashes the
|
|
1149
|
+
// password or sends mail. Better Auth's own disableSignUp flags and
|
|
1150
|
+
// the user-create hook remain the backstop for every other path.
|
|
1151
|
+
return managedAuthNewSignupsPausedResponse();
|
|
1152
|
+
}
|
|
1153
|
+
if (pathname === "/v1/auth/sign-in/social" && c.req.method === "POST") {
|
|
1154
|
+
const body = await c.req.raw
|
|
1155
|
+
.clone()
|
|
1156
|
+
.json()
|
|
1157
|
+
.catch(() => null);
|
|
1158
|
+
if (
|
|
1159
|
+
!body ||
|
|
1160
|
+
!["google", "github"].includes(body.provider) ||
|
|
1161
|
+
Object.prototype.hasOwnProperty.call(body, "idToken")
|
|
1162
|
+
) {
|
|
1163
|
+
return c.json(
|
|
1164
|
+
{
|
|
1165
|
+
code: "SIGN_IN_METHOD_OAUTH_REDIRECT_REQUIRED",
|
|
1166
|
+
message: "Use the browser OAuth sign-in redirect",
|
|
1167
|
+
},
|
|
1168
|
+
403,
|
|
1169
|
+
);
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
if (oauthCallbackProvider) {
|
|
1173
|
+
const connectResponse = await handleManagedSignInConnectCallback(
|
|
1174
|
+
c,
|
|
1175
|
+
routeDeps,
|
|
1176
|
+
oauthCallbackProvider,
|
|
1177
|
+
);
|
|
1178
|
+
if (connectResponse) return connectResponse;
|
|
1179
|
+
}
|
|
1180
|
+
if (
|
|
1181
|
+
new Set([
|
|
1182
|
+
"link-social",
|
|
1183
|
+
"unlink-account",
|
|
1184
|
+
"list-accounts",
|
|
1185
|
+
"set-password",
|
|
1186
|
+
"change-password",
|
|
1187
|
+
"change-email",
|
|
1188
|
+
"update-user",
|
|
1189
|
+
"delete-user",
|
|
1190
|
+
"get-access-token",
|
|
1191
|
+
"refresh-token",
|
|
1192
|
+
"account-info",
|
|
1193
|
+
]).has(pathname.slice("/v1/auth/".length))
|
|
1194
|
+
) {
|
|
1195
|
+
return c.json(
|
|
1196
|
+
{
|
|
1197
|
+
code: "SIGN_IN_METHOD_PRODUCT_ROUTE_REQUIRED",
|
|
1198
|
+
message: "Use personal sign-in method settings",
|
|
1199
|
+
},
|
|
1200
|
+
403,
|
|
1201
|
+
);
|
|
1202
|
+
}
|
|
676
1203
|
if (deps.settings.managedAuthSessionSetMode === "legacy") {
|
|
677
1204
|
return oauthCallbackProvider
|
|
678
1205
|
? await runManagedAuthProvider(
|
|
@@ -835,37 +1362,151 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
835
1362
|
return c.json(result, result.ok ? 200 : 503);
|
|
836
1363
|
});
|
|
837
1364
|
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
}),
|
|
842
|
-
);
|
|
1365
|
+
if (publicListenerServesMetrics(deps.settings)) {
|
|
1366
|
+
registerPrometheusMetricsRoute(app, observability);
|
|
1367
|
+
}
|
|
843
1368
|
|
|
844
1369
|
registerMcpOAuthRoutes(app, routeDeps);
|
|
845
1370
|
|
|
1371
|
+
registerClientErrorRoutes(app, { observability, settings: deps.settings });
|
|
1372
|
+
registerAnalyticsConsentRoutes(app, { observability, settings: deps.settings });
|
|
1373
|
+
|
|
846
1374
|
app.get("/v1/config/client", async (c) => {
|
|
847
1375
|
c.header("cache-control", "no-store");
|
|
848
1376
|
const resolvedCatalog = await resolveCatalogSettings(deps.db, deps.settings);
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
:
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
1377
|
+
let catalogSettings = resolvedCatalog.settings;
|
|
1378
|
+
let selections = resolveWorkspaceModelSelection({
|
|
1379
|
+
settings: catalogSettings,
|
|
1380
|
+
policy: null,
|
|
1381
|
+
codexSubscriptionActive: false,
|
|
1382
|
+
});
|
|
1383
|
+
let defaultSelection = selectDefaultSessionModel({
|
|
1384
|
+
settings: catalogSettings,
|
|
1385
|
+
selections,
|
|
1386
|
+
workspaceDefaults: null,
|
|
1387
|
+
creditsAvailable: false,
|
|
1388
|
+
});
|
|
1389
|
+
let modelSelectionForbidden = false;
|
|
1390
|
+
let voiceInputAvailable = false;
|
|
1391
|
+
let voiceInputProviders: string[] = [];
|
|
1392
|
+
const requestedWorkspaceId = c.req.query("workspaceId");
|
|
1393
|
+
if (requestedWorkspaceId !== undefined && requestedWorkspaceId.trim() === "") {
|
|
1394
|
+
throw new HTTPException(422, { message: "workspaceId must not be empty" });
|
|
1395
|
+
}
|
|
1396
|
+
const callerScoped =
|
|
1397
|
+
requestedWorkspaceId !== undefined ||
|
|
1398
|
+
deps.settings.productAccessMode !== "managed" ||
|
|
1399
|
+
c.req.header("authorization") !== undefined ||
|
|
1400
|
+
// Browser bootstrap precedes session-set reconciliation. A cookie alone
|
|
1401
|
+
// cannot scope this public read (broker/changed actors need an epoch).
|
|
1402
|
+
c.req.header("x-opengeni-actor-epoch") !== undefined ||
|
|
1403
|
+
c.req.header("x-opengeni-external-actor") !== undefined;
|
|
1404
|
+
if (callerScoped) {
|
|
1405
|
+
let context: Awaited<ReturnType<typeof requireAccessContext>> | null = null;
|
|
1406
|
+
try {
|
|
1407
|
+
context = await requireAccessContext(c, routeDeps);
|
|
1408
|
+
} catch (error) {
|
|
1409
|
+
// A stale browser cookie must not break the signed-out bootstrap.
|
|
1410
|
+
// Explicit workspace/bearer/external-actor requests fail closed.
|
|
1411
|
+
if (
|
|
1412
|
+
!(error instanceof HTTPException && error.status === 401) ||
|
|
1413
|
+
requestedWorkspaceId !== undefined ||
|
|
1414
|
+
c.req.header("authorization") !== undefined ||
|
|
1415
|
+
c.req.header("x-opengeni-actor-epoch") !== undefined ||
|
|
1416
|
+
c.req.header("x-opengeni-external-actor") !== undefined
|
|
1417
|
+
) {
|
|
1418
|
+
throw error;
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
const workspaceId = requestedWorkspaceId ?? context?.defaultWorkspaceId;
|
|
1422
|
+
if (context && workspaceId) {
|
|
1423
|
+
const grant = await requireAccessGrant(c, routeDeps, workspaceId);
|
|
1424
|
+
await withAccessGrantSessionRlsContext(routeDeps, grant, async () => {
|
|
1425
|
+
catalogSettings = (
|
|
1426
|
+
await resolveWorkspaceCatalogSettings(deps.db, deps.settings, {
|
|
1427
|
+
accountId: grant.accountId,
|
|
1428
|
+
workspaceId,
|
|
1429
|
+
})
|
|
1430
|
+
).settings;
|
|
1431
|
+
selections = await resolveCallerWorkspaceModelSelections(
|
|
1432
|
+
deps.db,
|
|
1433
|
+
catalogSettings,
|
|
1434
|
+
{ accountId: grant.accountId, workspaceId, subjectId: grant.subjectId },
|
|
1435
|
+
{ observeAvailability: true },
|
|
1436
|
+
);
|
|
1437
|
+
if (!hasPermission(grant.permissions, "sessions:create")) {
|
|
1438
|
+
modelSelectionForbidden = true;
|
|
1439
|
+
selections = [];
|
|
1440
|
+
}
|
|
1441
|
+
const workspace = await getWorkspace(deps.db, workspaceId);
|
|
1442
|
+
const voicePreferences = WorkspaceVoiceInputSettings.safeParse(
|
|
1443
|
+
workspace?.settings.voiceInput,
|
|
1444
|
+
).data;
|
|
1445
|
+
const voiceContext = {
|
|
1446
|
+
workspaceId,
|
|
1447
|
+
subjectId: grant.subjectId,
|
|
1448
|
+
preferredProvider: voicePreferences?.preferredProvider,
|
|
1449
|
+
fallbackEnabled: voicePreferences?.fallbackEnabled,
|
|
1450
|
+
};
|
|
1451
|
+
if (hasPermission(grant.permissions, "sessions:create") && transcription) {
|
|
1452
|
+
// `providers` lists every ready provider (the workspace picker);
|
|
1453
|
+
// `available` honours the workspace preference and fallback, so
|
|
1454
|
+
// the composer never offers a mic every request would refuse.
|
|
1455
|
+
voiceInputProviders = (await transcription.availableProviderIds?.(voiceContext)) ?? [];
|
|
1456
|
+
voiceInputAvailable =
|
|
1457
|
+
voiceInputProviders.length > 0 &&
|
|
1458
|
+
(await Promise.resolve(transcription.available(voiceContext)).catch(() => false));
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
defaultSelection = await resolveDefaultSessionModelForSelections(deps.db, {
|
|
1462
|
+
settings: catalogSettings,
|
|
1463
|
+
accountId: grant.accountId,
|
|
1464
|
+
workspaceSettings: workspace?.settings ?? {},
|
|
1465
|
+
selections,
|
|
1466
|
+
});
|
|
1467
|
+
});
|
|
1468
|
+
} else if (context) {
|
|
1469
|
+
// An organization key has no implicit workspace. Do not advertise
|
|
1470
|
+
// another workspace's authority; it can request an exact workspace.
|
|
1471
|
+
selections = [];
|
|
1472
|
+
modelSelectionForbidden = true;
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
const models = selections.filter(isWorkspaceModelAdmissible);
|
|
1476
|
+
const defaultModel = defaultSelection.model;
|
|
1477
|
+
const fallbackDefinition = selections.find(
|
|
1478
|
+
({ model }) => model.id === defaultModel || model.aliases.includes(defaultModel),
|
|
1479
|
+
);
|
|
1480
|
+
const legacyModelFallback =
|
|
1481
|
+
models.length === 0
|
|
1482
|
+
? {
|
|
1483
|
+
id: defaultModel,
|
|
1484
|
+
availability: {
|
|
1485
|
+
status: "unavailable" as const,
|
|
1486
|
+
selectable: false as const,
|
|
1487
|
+
reason: modelSelectionForbidden
|
|
1488
|
+
? ("policy_blocked" as const)
|
|
1489
|
+
: (fallbackDefinition?.availability.reason ?? ("unsupported" as const)),
|
|
1490
|
+
checkedAt: fallbackDefinition?.availability.checkedAt ?? null,
|
|
1491
|
+
},
|
|
1492
|
+
}
|
|
1493
|
+
: undefined;
|
|
856
1494
|
return c.json(
|
|
857
1495
|
ClientConfig.parse({
|
|
858
1496
|
deploymentRevision: deps.settings.deploymentRevision,
|
|
1497
|
+
claudeSubscriptionEnabled: deps.settings.claudeSubscriptionEnabled,
|
|
1498
|
+
connectedMachines: deps.settings.sandboxSelfhostedEnabled,
|
|
859
1499
|
apiContractRevision: OPENGENI_API_CONTRACT_REVISION,
|
|
860
1500
|
...(deps.settings.serverVersion ? { serverVersion: deps.settings.serverVersion } : {}),
|
|
861
|
-
defaultModel
|
|
862
|
-
allowedModels:
|
|
863
|
-
|
|
864
|
-
//
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
1501
|
+
defaultModel,
|
|
1502
|
+
allowedModels: models.length > 0 ? models.map(({ model }) => model.id) : [defaultModel],
|
|
1503
|
+
...(legacyModelFallback ? { legacyModelFallback } : {}),
|
|
1504
|
+
// Availability remains an observation hint, not the admission predicate.
|
|
1505
|
+
models: models.map(({ model, availability }) => ({
|
|
1506
|
+
...projectClientModel(model),
|
|
1507
|
+
availability,
|
|
1508
|
+
})),
|
|
1509
|
+
defaultReasoningEffort: defaultSelection.reasoningEffort,
|
|
869
1510
|
allowedReasoningEfforts: configuredAllowedReasoningEfforts(deps.settings),
|
|
870
1511
|
defaultSandboxBackend: deps.settings.sandboxBackend,
|
|
871
1512
|
mcpServers: deps.settings.mcpServers.map((server) => ({
|
|
@@ -873,15 +1514,29 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
873
1514
|
name: server.name ?? server.id,
|
|
874
1515
|
})),
|
|
875
1516
|
firstPartyMcpTools: resolveFirstPartyMcpToolPolicy(deps.settings),
|
|
1517
|
+
codeSearch: codeSearchDeploymentPolicy(deps.settings),
|
|
1518
|
+
// Archiving needs object storage; without it the worker never archives.
|
|
1519
|
+
...(deps.settings.sessionArchiveEnabled && objectStorage
|
|
1520
|
+
? {
|
|
1521
|
+
sessionArchive: {
|
|
1522
|
+
enabled: true as const,
|
|
1523
|
+
idleDays: deps.settings.sessionArchiveIdleDays,
|
|
1524
|
+
},
|
|
1525
|
+
}
|
|
1526
|
+
: {}),
|
|
1527
|
+
agentConfig: clientAgentConfig(deps.settings),
|
|
1528
|
+
// Producer capability for per-task usage limits. Clients only offer a
|
|
1529
|
+
// new limit when enabled; existing limits stay visible and removable.
|
|
1530
|
+
sessionUsageLimits: {
|
|
1531
|
+
enabled: sessionUsageLimitsProducerEnabled(deps.settings),
|
|
1532
|
+
},
|
|
876
1533
|
fileUploads: {
|
|
877
1534
|
enabled: objectStorage !== null,
|
|
878
1535
|
maxSizeBytes: objectStorage?.maxSinglePutSizeBytes ?? 5_000_000_000,
|
|
879
1536
|
},
|
|
880
1537
|
voiceInput: {
|
|
881
|
-
providers:
|
|
882
|
-
|
|
883
|
-
),
|
|
884
|
-
available: (await transcription?.available()) ?? false,
|
|
1538
|
+
providers: voiceInputProviders,
|
|
1539
|
+
available: voiceInputAvailable,
|
|
885
1540
|
maxDurationSeconds: deps.settings.voiceInputMaxDurationSeconds,
|
|
886
1541
|
maxSizeBytes: deps.settings.voiceInputMaxSizeBytes,
|
|
887
1542
|
acceptedMimeTypes: [...VOICE_INPUT_ACCEPTED_MIME_TYPES],
|
|
@@ -889,7 +1544,7 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
889
1544
|
objectStorage &&
|
|
890
1545
|
transcription &&
|
|
891
1546
|
transcriptionSegmenter &&
|
|
892
|
-
|
|
1547
|
+
voiceInputAvailable &&
|
|
893
1548
|
(await transcriptionSegmenter.available())
|
|
894
1549
|
? {
|
|
895
1550
|
resumable: {
|
|
@@ -907,16 +1562,126 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
907
1562
|
productAccessMode: deps.settings.productAccessMode,
|
|
908
1563
|
billingMode: deps.settings.billingMode,
|
|
909
1564
|
managedAuthSessionSetMode: deps.settings.managedAuthSessionSetMode,
|
|
910
|
-
auth: clientAuthConfig(
|
|
1565
|
+
auth: clientAuthConfig(
|
|
1566
|
+
deps.settings,
|
|
1567
|
+
deps.settings.productAccessMode === "managed"
|
|
1568
|
+
? await managedAuthNewSignupsGate.signupsOpen()
|
|
1569
|
+
: true,
|
|
1570
|
+
),
|
|
1571
|
+
documentationUrl: deps.settings.documentationUrl,
|
|
911
1572
|
analytics: clientAnalyticsConfig(deps.settings),
|
|
1573
|
+
legal: clientLegalConfig(deps.settings),
|
|
1574
|
+
...(deps.settings.supportEmail ? { supportEmail: deps.settings.supportEmail } : {}),
|
|
912
1575
|
// Channel-A structured services (P4.4) ride exec/readFile/createEditor,
|
|
913
1576
|
// available on every real backend; `none` has no box so they are all off.
|
|
914
1577
|
// Per-session availability is still negotiated on /stream-capabilities.
|
|
915
1578
|
structuredServices: structuredServicesHint(deps.settings.sandboxBackend),
|
|
1579
|
+
sites: clientSitesConfig(deps.settings),
|
|
916
1580
|
}),
|
|
917
1581
|
);
|
|
918
1582
|
});
|
|
919
1583
|
|
|
1584
|
+
// The organization MCP server: every public action, as a person who signed
|
|
1585
|
+
// in (capped by their connection's access setting) or as an organization
|
|
1586
|
+
// API key. Each action runs the real route in this process.
|
|
1587
|
+
app.all(ORGANIZATION_MCP_PATH, async (c) => {
|
|
1588
|
+
let boundedRequest: Request;
|
|
1589
|
+
try {
|
|
1590
|
+
boundedRequest = await boundedMcpRequest(c.req.raw);
|
|
1591
|
+
} catch (error) {
|
|
1592
|
+
if (error instanceof McpPayloadTooLargeError) {
|
|
1593
|
+
throw new HTTPException(413, { message: "MCP request body exceeds the safety limit" });
|
|
1594
|
+
}
|
|
1595
|
+
throw error;
|
|
1596
|
+
}
|
|
1597
|
+
const challenge = () => {
|
|
1598
|
+
if (deps.settings.mcpOauthEnabled) {
|
|
1599
|
+
c.header("www-authenticate", mcpOAuthAuthenticateHeader(routeDeps, ORGANIZATION_MCP_PATH));
|
|
1600
|
+
}
|
|
1601
|
+
};
|
|
1602
|
+
let caller: OrganizationMcpCaller;
|
|
1603
|
+
try {
|
|
1604
|
+
const connection = await resolveOrganizationMcpOAuthAccess(routeDeps, c.req.raw);
|
|
1605
|
+
const personalKey = connection
|
|
1606
|
+
? null
|
|
1607
|
+
: verifiedDelegatedHumanAuthorizationForRequest(c.req.raw);
|
|
1608
|
+
if (connection) {
|
|
1609
|
+
caller = {
|
|
1610
|
+
kind: "person",
|
|
1611
|
+
accountId: connection.accountId,
|
|
1612
|
+
subjectId: connection.subjectId,
|
|
1613
|
+
access: connection.organizationAccess,
|
|
1614
|
+
};
|
|
1615
|
+
} else if (personalKey) {
|
|
1616
|
+
// A personal API key: the person who created it, with its access.
|
|
1617
|
+
// Each action rechecks their live access, exactly as for sign-in.
|
|
1618
|
+
await requireAccessContext(c, routeDeps);
|
|
1619
|
+
caller = {
|
|
1620
|
+
kind: "person",
|
|
1621
|
+
accountId: personalKey.organizationId,
|
|
1622
|
+
subjectId: personalKey.subjectId,
|
|
1623
|
+
access: normalizeOrganizationAccessPolicy({
|
|
1624
|
+
preset: "custom",
|
|
1625
|
+
permissions: [...personalKey.permissions],
|
|
1626
|
+
workspaceScope: personalKey.workspaceScope,
|
|
1627
|
+
}),
|
|
1628
|
+
};
|
|
1629
|
+
} else {
|
|
1630
|
+
const context = await requireAccessContext(c, routeDeps);
|
|
1631
|
+
const authorization = c.req.header("authorization");
|
|
1632
|
+
if (!accountScopedApiKeyWorkspaceAuthority(context) || !authorization) {
|
|
1633
|
+
throw new HTTPException(403, {
|
|
1634
|
+
message:
|
|
1635
|
+
"Connect with Opengeni sign-in, a personal API key or an organization API key.",
|
|
1636
|
+
});
|
|
1637
|
+
}
|
|
1638
|
+
caller = {
|
|
1639
|
+
kind: "key",
|
|
1640
|
+
authorization,
|
|
1641
|
+
accessKey: c.req.header("x-opengeni-access-key") ?? null,
|
|
1642
|
+
};
|
|
1643
|
+
}
|
|
1644
|
+
} catch (error) {
|
|
1645
|
+
if (error instanceof HTTPException && error.status === 401) challenge();
|
|
1646
|
+
throw error;
|
|
1647
|
+
}
|
|
1648
|
+
// Stateless JSON-response transport: there is no server-to-client stream.
|
|
1649
|
+
// Answering GET with an empty 200 made clients (Claude) reconnect every
|
|
1650
|
+
// second; refuse it after authorization, as the workspace endpoint does.
|
|
1651
|
+
if (c.req.method === "GET") {
|
|
1652
|
+
const version = c.req.header("mcp-protocol-version");
|
|
1653
|
+
if (version && !SUPPORTED_PROTOCOL_VERSIONS.includes(version)) {
|
|
1654
|
+
return c.json(
|
|
1655
|
+
{
|
|
1656
|
+
jsonrpc: "2.0",
|
|
1657
|
+
id: null,
|
|
1658
|
+
error: { code: -32000, message: "Unsupported protocol version." },
|
|
1659
|
+
},
|
|
1660
|
+
400,
|
|
1661
|
+
);
|
|
1662
|
+
}
|
|
1663
|
+
return c.json(
|
|
1664
|
+
{ jsonrpc: "2.0", id: null, error: { code: -32000, message: "Method not allowed." } },
|
|
1665
|
+
405,
|
|
1666
|
+
{ allow: "POST" },
|
|
1667
|
+
);
|
|
1668
|
+
}
|
|
1669
|
+
const transport = new WebStandardStreamableHTTPServerTransport({ enableJsonResponse: true });
|
|
1670
|
+
const mcp = buildOrganizationMcpServer({
|
|
1671
|
+
caller,
|
|
1672
|
+
origin: new URL(c.req.url).origin,
|
|
1673
|
+
publicOrigin: canonicalPublicOrigin(deps.settings.publicBaseUrl),
|
|
1674
|
+
dispatch: async (request) => await app.fetch(request, c.env),
|
|
1675
|
+
signal: c.req.raw.signal,
|
|
1676
|
+
});
|
|
1677
|
+
try {
|
|
1678
|
+
await mcp.connect(transport);
|
|
1679
|
+
return await handleMcpRequestWithClientAbort(transport, boundedRequest, c.req.raw.signal);
|
|
1680
|
+
} finally {
|
|
1681
|
+
await mcp.close().catch(() => undefined);
|
|
1682
|
+
}
|
|
1683
|
+
});
|
|
1684
|
+
|
|
920
1685
|
app.use("/v1/workspaces/:workspaceId/*", async (c, next) => {
|
|
921
1686
|
const workspaceId = c.req.param("workspaceId");
|
|
922
1687
|
if (workspaceRequestRequiresCodexAccountPrevalidation(c.req.raw)) {
|
|
@@ -929,12 +1694,47 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
929
1694
|
await next();
|
|
930
1695
|
return;
|
|
931
1696
|
}
|
|
932
|
-
|
|
1697
|
+
// Match Hono's decoded routing path before first-use membership and RLS.
|
|
1698
|
+
const permission =
|
|
1699
|
+
c.req.method === "PUT" &&
|
|
1700
|
+
/^\/v1\/workspaces\/[^/]+\/artifact-catalog\/[^/]+\/[^/]+\/pin$/.test(c.req.path)
|
|
1701
|
+
? "artifacts:publish"
|
|
1702
|
+
: undefined;
|
|
1703
|
+
const grant = await requireAccessGrant(c, routeDeps, workspaceId, permission);
|
|
933
1704
|
await withAccessGrantSessionRlsContext(routeDeps, grant, next);
|
|
934
1705
|
});
|
|
935
1706
|
|
|
1707
|
+
// Requests a host forwarded from a Site frame: SITE_OPERATIONS only, only
|
|
1708
|
+
// the frame's own sessions, and the validated Site origin as provenance.
|
|
1709
|
+
app.use("/v1/workspaces/:workspaceId/*", siteFrameGuard(routeDeps));
|
|
1710
|
+
// Agents started from a Site page do not learn or widen access: their
|
|
1711
|
+
// workspace writes are held to an allowlist (their tools, conversations,
|
|
1712
|
+
// files and content); everything else is refused.
|
|
1713
|
+
app.use("/v1/workspaces/:workspaceId/*", siteClampedAgentGuard(routeDeps));
|
|
1714
|
+
|
|
936
1715
|
app.all("/v1/workspaces/:workspaceId/mcp", async (c) => {
|
|
937
1716
|
const workspaceId = c.req.param("workspaceId");
|
|
1717
|
+
// This endpoint uses a fresh stateless JSON-response transport per POST;
|
|
1718
|
+
// there is no persistent server-to-client stream. Refuse GET only after
|
|
1719
|
+
// the existing OAuth/workspace/session authorization, before tool setup.
|
|
1720
|
+
const unsupportedGet = () => {
|
|
1721
|
+
const version = c.req.header("mcp-protocol-version");
|
|
1722
|
+
if (version && !SUPPORTED_PROTOCOL_VERSIONS.includes(version)) {
|
|
1723
|
+
return c.json(
|
|
1724
|
+
{
|
|
1725
|
+
jsonrpc: "2.0",
|
|
1726
|
+
id: null,
|
|
1727
|
+
error: { code: -32000, message: "Unsupported protocol version." },
|
|
1728
|
+
},
|
|
1729
|
+
400,
|
|
1730
|
+
);
|
|
1731
|
+
}
|
|
1732
|
+
return c.json(
|
|
1733
|
+
{ jsonrpc: "2.0", id: null, error: { code: -32000, message: "Method not allowed." } },
|
|
1734
|
+
405,
|
|
1735
|
+
{ allow: "POST" },
|
|
1736
|
+
);
|
|
1737
|
+
};
|
|
938
1738
|
let boundedRequest: Request;
|
|
939
1739
|
try {
|
|
940
1740
|
boundedRequest = await boundedMcpRequest(c.req.raw);
|
|
@@ -960,6 +1760,7 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
960
1760
|
}
|
|
961
1761
|
if (oauthAccess) {
|
|
962
1762
|
return await withAccessGrantSessionRlsContext(routeDeps, oauthAccess.grant, async () => {
|
|
1763
|
+
if (c.req.method === "GET") return unsupportedGet();
|
|
963
1764
|
const prepared = await prepareMcpOAuthWorkspaceToolGateway(
|
|
964
1765
|
routeDeps,
|
|
965
1766
|
oauthAccess.grant,
|
|
@@ -994,73 +1795,203 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
994
1795
|
throw error;
|
|
995
1796
|
}
|
|
996
1797
|
const grant = authorization.grant;
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
message: "session authorization is unavailable",
|
|
1798
|
+
// Admin actions dispatch back into this API as a person. Capture the
|
|
1799
|
+
// context now, before this request takes on the agent's database and
|
|
1800
|
+
// billing scope, so none of it carries into those actions.
|
|
1801
|
+
const outsideAgentContext = AsyncLocalStorage.snapshot();
|
|
1802
|
+
// The agent-attempt context, this route check, and a tool's own entry check
|
|
1803
|
+
// re-read the same caller session and attempt; share those reads.
|
|
1804
|
+
return await withSessionAuthorizationReadReuse((reads) =>
|
|
1805
|
+
withAccessGrantSessionRlsContext(routeDeps, grant, async () => {
|
|
1806
|
+
const boundSessionId = grant.metadata?.sessionId;
|
|
1807
|
+
if (typeof boundSessionId === "string") {
|
|
1808
|
+
try {
|
|
1809
|
+
await requireSessionAuthorization(routeDeps, grant, {
|
|
1810
|
+
sessionId: boundSessionId,
|
|
1811
|
+
operation: "session.first_party_mcp.call",
|
|
1812
|
+
surface: "first_party_mcp",
|
|
1013
1813
|
});
|
|
1814
|
+
} catch (error) {
|
|
1815
|
+
if (error instanceof SessionAuthorizationDeniedError) {
|
|
1816
|
+
throw new HTTPException(404, { message: "session not found" });
|
|
1817
|
+
}
|
|
1818
|
+
if (error instanceof SessionAuthorizationUnavailableError) {
|
|
1819
|
+
throw new HTTPException(503, {
|
|
1820
|
+
message: "session authorization is unavailable",
|
|
1821
|
+
});
|
|
1822
|
+
}
|
|
1823
|
+
throw error;
|
|
1014
1824
|
}
|
|
1015
|
-
throw error;
|
|
1016
1825
|
}
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1826
|
+
if (c.req.method === "GET") return unsupportedGet();
|
|
1827
|
+
reads.handOffToToolDispatch();
|
|
1828
|
+
const workspace = await getWorkspace(routeDeps.db, workspaceId);
|
|
1829
|
+
const workspaceMemoryEnabled = resolveWorkspaceMemoryEnabled(workspace?.settings);
|
|
1830
|
+
const workspaceMemoryPromptMode = resolveWorkspaceMemoryPromptMode();
|
|
1831
|
+
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
1832
|
+
enableJsonResponse: true,
|
|
1833
|
+
});
|
|
1834
|
+
if (!grantUsesAttemptScopedMcp(grant)) {
|
|
1835
|
+
const prepared = await prepareWorkspaceToolGateway(routeDeps, authorization);
|
|
1836
|
+
const mcp = buildWorkspaceToolGatewayMcpServer(prepared, grant, routeDeps.observability);
|
|
1837
|
+
try {
|
|
1838
|
+
await mcp.connect(transport);
|
|
1839
|
+
return await handleMcpRequestWithClientAbort(
|
|
1840
|
+
transport,
|
|
1841
|
+
boundedRequest,
|
|
1842
|
+
c.req.raw.signal,
|
|
1843
|
+
);
|
|
1844
|
+
} finally {
|
|
1845
|
+
await Promise.allSettled([mcp.close(), prepared.close()]);
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
const mcpDeps = await resolveWorkspaceMcpRouteDeps(routeDeps, grant);
|
|
1849
|
+
// The bound session's frozen Memory selector (migration 0427) decides
|
|
1850
|
+
// which Memory tools the attempt receives and which typed layers they
|
|
1851
|
+
// read and write. A missing row resolves to no Memory tools.
|
|
1852
|
+
const sessionMemory =
|
|
1853
|
+
typeof boundSessionId === "string"
|
|
1854
|
+
? ((await resolveSessionMemoryAgentScope(
|
|
1855
|
+
routeDeps.db,
|
|
1856
|
+
workspaceId,
|
|
1857
|
+
boundSessionId,
|
|
1858
|
+
grant.metadata,
|
|
1859
|
+
)) ?? {
|
|
1860
|
+
mode: "off" as const,
|
|
1861
|
+
userSubjectId: null,
|
|
1862
|
+
rootSessionId: null,
|
|
1863
|
+
})
|
|
1864
|
+
: null;
|
|
1865
|
+
// Checked live: allowed by the organization, given to this session,
|
|
1866
|
+
// and the person who gave it is still an owner or admin.
|
|
1867
|
+
const sessionAdminAccess =
|
|
1868
|
+
typeof boundSessionId === "string" &&
|
|
1869
|
+
(await resolveSessionAdminAuthority(routeDeps.db, {
|
|
1870
|
+
accountId: grant.accountId,
|
|
1871
|
+
workspaceId,
|
|
1872
|
+
sessionId: boundSessionId,
|
|
1873
|
+
}))
|
|
1874
|
+
? {
|
|
1875
|
+
origin: new URL(c.req.url).origin,
|
|
1876
|
+
dispatch: (request: Request) =>
|
|
1877
|
+
outsideAgentContext(async () => await app.fetch(request, c.env)),
|
|
1878
|
+
signal: c.req.raw.signal,
|
|
1879
|
+
}
|
|
1880
|
+
: null;
|
|
1881
|
+
const mcp = buildOpenGeniMcpServer(mcpDeps, grant, {
|
|
1882
|
+
requestOrigin: new URL(c.req.url).origin,
|
|
1883
|
+
workspaceMemoryEnabled,
|
|
1884
|
+
workspaceMemoryPromptMode,
|
|
1885
|
+
sessionMemory,
|
|
1886
|
+
sessionAdminAccess,
|
|
1887
|
+
});
|
|
1888
|
+
// Bind tool handlers' `extra.signal` to the HTTP client's connection: a
|
|
1889
|
+
// worker that drops the call (Steer/Pause) aborts a blocking tool here.
|
|
1890
|
+
// Each POST builds a fresh server; close it once the JSON response is
|
|
1891
|
+
// ready, like the gateway paths above, so it can't outlive the request.
|
|
1027
1892
|
try {
|
|
1028
1893
|
await mcp.connect(transport);
|
|
1029
1894
|
return await handleMcpRequestWithClientAbort(transport, boundedRequest, c.req.raw.signal);
|
|
1030
1895
|
} finally {
|
|
1031
|
-
await
|
|
1896
|
+
await mcp.close().catch(() => undefined);
|
|
1032
1897
|
}
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
// The bound session's frozen Memory selector (migration 0427) decides
|
|
1036
|
-
// which Memory tools the attempt receives and which typed layers they
|
|
1037
|
-
// read and write. A missing row resolves to no Memory tools.
|
|
1038
|
-
const sessionMemory =
|
|
1039
|
-
typeof boundSessionId === "string"
|
|
1040
|
-
? ((await resolveSessionMemoryAgentScope(
|
|
1041
|
-
routeDeps.db,
|
|
1042
|
-
workspaceId,
|
|
1043
|
-
boundSessionId,
|
|
1044
|
-
grant.metadata,
|
|
1045
|
-
)) ?? {
|
|
1046
|
-
mode: "off" as const,
|
|
1047
|
-
userSubjectId: null,
|
|
1048
|
-
rootSessionId: null,
|
|
1049
|
-
})
|
|
1050
|
-
: null;
|
|
1051
|
-
const mcp = buildOpenGeniMcpServer(mcpDeps, grant, {
|
|
1052
|
-
requestOrigin: new URL(c.req.url).origin,
|
|
1053
|
-
workspaceMemoryEnabled,
|
|
1054
|
-
workspaceMemoryPromptMode,
|
|
1055
|
-
sessionMemory,
|
|
1056
|
-
});
|
|
1057
|
-
await mcp.connect(transport);
|
|
1058
|
-
// Bind tool handlers' `extra.signal` to the HTTP client's connection: a
|
|
1059
|
-
// worker that drops the call (Steer/Pause) aborts a blocking tool here.
|
|
1060
|
-
return await handleMcpRequestWithClientAbort(transport, boundedRequest, c.req.raw.signal);
|
|
1061
|
-
});
|
|
1898
|
+
}),
|
|
1899
|
+
);
|
|
1062
1900
|
});
|
|
1063
1901
|
|
|
1902
|
+
// Known calls need no preceding full-catalog discovery or replica-local attestation.
|
|
1903
|
+
for (const operation of ["resolve", "invoke", "target-approvals", "manifest"] as const) {
|
|
1904
|
+
app.post(`/v1/workspaces/:workspaceId/tools/${operation}`, async (c) => {
|
|
1905
|
+
const workspaceId = c.req.param("workspaceId");
|
|
1906
|
+
const authorization = await requireAccessGrantAuthorization(
|
|
1907
|
+
c,
|
|
1908
|
+
routeDeps,
|
|
1909
|
+
workspaceId,
|
|
1910
|
+
"workspace:read",
|
|
1911
|
+
);
|
|
1912
|
+
let fence: TargetAuthorityFence | undefined;
|
|
1913
|
+
const options = {
|
|
1914
|
+
signal: c.req.raw.signal,
|
|
1915
|
+
// Human callers keep their fresh grant. Organization keys and external
|
|
1916
|
+
// actors are re-read by the request fence itself.
|
|
1917
|
+
reauthorize: async () => {
|
|
1918
|
+
const live = await requireFreshAccessGrant(c, routeDeps, workspaceId, "workspace:read");
|
|
1919
|
+
const original = authorization.grant;
|
|
1920
|
+
if (
|
|
1921
|
+
live.accountId !== original.accountId ||
|
|
1922
|
+
live.subjectId !== original.subjectId ||
|
|
1923
|
+
live.principalKind !== original.principalKind ||
|
|
1924
|
+
original.permissions.some((permission) => !hasPermission(live.permissions, permission))
|
|
1925
|
+
)
|
|
1926
|
+
throw new HTTPException(403, { message: "tool_access_changed" });
|
|
1927
|
+
},
|
|
1928
|
+
onFence: (created: TargetAuthorityFence) => {
|
|
1929
|
+
fence = created;
|
|
1930
|
+
},
|
|
1931
|
+
};
|
|
1932
|
+
const body: unknown = await c.req.json().catch(() => null);
|
|
1933
|
+
const schema =
|
|
1934
|
+
operation === "resolve"
|
|
1935
|
+
? ToolGatewayResolveRequest
|
|
1936
|
+
: operation === "invoke"
|
|
1937
|
+
? ToolGatewayInvokeRequest
|
|
1938
|
+
: operation === "manifest"
|
|
1939
|
+
? ToolGatewayManifestRequest
|
|
1940
|
+
: ToolGatewayTargetApprovalRequest;
|
|
1941
|
+
if (!schema.safeParse(body).success)
|
|
1942
|
+
throw new HTTPException(400, { message: "Invalid targeted tool request" });
|
|
1943
|
+
c.header("cache-control", "no-store");
|
|
1944
|
+
const started = performance.now();
|
|
1945
|
+
let outcome: "ok" | "failed" = "failed";
|
|
1946
|
+
try {
|
|
1947
|
+
const response =
|
|
1948
|
+
operation === "resolve"
|
|
1949
|
+
? await resolveWorkspaceToolTarget(
|
|
1950
|
+
routeDeps,
|
|
1951
|
+
authorization,
|
|
1952
|
+
ToolGatewayResolveRequest.parse(body),
|
|
1953
|
+
options,
|
|
1954
|
+
)
|
|
1955
|
+
: operation === "invoke"
|
|
1956
|
+
? await invokeWorkspaceToolTarget(
|
|
1957
|
+
routeDeps,
|
|
1958
|
+
authorization,
|
|
1959
|
+
ToolGatewayInvokeRequest.parse(body),
|
|
1960
|
+
options,
|
|
1961
|
+
)
|
|
1962
|
+
: operation === "manifest"
|
|
1963
|
+
? await resolveWorkspaceToolManifest(
|
|
1964
|
+
routeDeps,
|
|
1965
|
+
authorization,
|
|
1966
|
+
ToolGatewayManifestRequest.parse(body),
|
|
1967
|
+
options,
|
|
1968
|
+
)
|
|
1969
|
+
: await approveWorkspaceToolTarget(
|
|
1970
|
+
routeDeps,
|
|
1971
|
+
authorization,
|
|
1972
|
+
ToolGatewayTargetApprovalRequest.parse(body),
|
|
1973
|
+
options,
|
|
1974
|
+
);
|
|
1975
|
+
c.header(
|
|
1976
|
+
"server-timing",
|
|
1977
|
+
`gw-target;dur=${(performance.now() - started).toFixed(1)};desc="${operation}"` +
|
|
1978
|
+
(fence
|
|
1979
|
+
? `, gw-fence;dur=${fence.stats.durationMs.toFixed(1)};desc="${fence.stats.evaluations}"`
|
|
1980
|
+
: ""),
|
|
1981
|
+
);
|
|
1982
|
+
outcome = "ok";
|
|
1983
|
+
return c.json(response, operation === "target-approvals" ? 201 : 200);
|
|
1984
|
+
} finally {
|
|
1985
|
+
recordWorkspaceToolTargetOperation(routeDeps.observability, {
|
|
1986
|
+
operation,
|
|
1987
|
+
outcome,
|
|
1988
|
+
durationSeconds: (performance.now() - started) / 1000,
|
|
1989
|
+
});
|
|
1990
|
+
}
|
|
1991
|
+
});
|
|
1992
|
+
}
|
|
1993
|
+
// Legacy calls retain their content-free, per-process catalog attestations.
|
|
1994
|
+
const toolGatewayCatalogAttestations = createWorkspaceToolGatewayCatalogAttestations();
|
|
1064
1995
|
app.get("/v1/workspaces/:workspaceId/tools/catalog", async (c) => {
|
|
1065
1996
|
const workspaceId = c.req.param("workspaceId");
|
|
1066
1997
|
const authorization = await requireAccessGrantAuthorization(
|
|
@@ -1069,9 +2000,22 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
1069
2000
|
workspaceId,
|
|
1070
2001
|
"workspace:read",
|
|
1071
2002
|
);
|
|
1072
|
-
const
|
|
2003
|
+
const startedAt = performance.now();
|
|
2004
|
+
const prepared = await prepareAttestedWorkspaceToolGateway(
|
|
2005
|
+
routeDeps,
|
|
2006
|
+
authorization,
|
|
2007
|
+
toolGatewayCatalogAttestations,
|
|
2008
|
+
);
|
|
1073
2009
|
try {
|
|
2010
|
+
const prepareMs = performance.now() - startedAt;
|
|
2011
|
+
recordWorkspaceToolGatewayPreparation(routeDeps.observability, {
|
|
2012
|
+
operation: "catalog",
|
|
2013
|
+
scope: "complete",
|
|
2014
|
+
attestation: "record",
|
|
2015
|
+
durationSeconds: prepareMs / 1_000,
|
|
2016
|
+
});
|
|
1074
2017
|
c.header("cache-control", "no-store");
|
|
2018
|
+
c.header("server-timing", workspaceToolGatewayServerTiming({ prepareMs, scope: "complete" }));
|
|
1075
2019
|
return c.json(prepared.toolGatewayCatalog);
|
|
1076
2020
|
} finally {
|
|
1077
2021
|
await prepared.close();
|
|
@@ -1086,26 +2030,28 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
1086
2030
|
workspaceId,
|
|
1087
2031
|
"workspace:read",
|
|
1088
2032
|
);
|
|
1089
|
-
const grant = authorization.grant;
|
|
1090
2033
|
const parsed = ToolGatewayCallRequest.safeParse(await c.req.json().catch(() => null));
|
|
1091
2034
|
if (!parsed.success) {
|
|
1092
2035
|
throw new HTTPException(400, { message: "Invalid tool gateway call" });
|
|
1093
2036
|
}
|
|
1094
|
-
const
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
2037
|
+
const { response, timing } = await callWorkspaceToolGatewayForCaller(
|
|
2038
|
+
routeDeps,
|
|
2039
|
+
authorization,
|
|
2040
|
+
parsed.data,
|
|
2041
|
+
{
|
|
2042
|
+
attestations: toolGatewayCatalogAttestations,
|
|
2043
|
+
...(routeDeps.observability ? { observability: routeDeps.observability } : {}),
|
|
2044
|
+
onPreparation: (preparation) =>
|
|
2045
|
+
recordWorkspaceToolGatewayPreparation(routeDeps.observability, {
|
|
2046
|
+
operation: "call",
|
|
2047
|
+
scope: preparation.scope,
|
|
2048
|
+
attestation: preparation.attestation,
|
|
2049
|
+
durationSeconds: preparation.prepareMs / 1_000,
|
|
2050
|
+
}),
|
|
2051
|
+
},
|
|
2052
|
+
);
|
|
2053
|
+
c.header("server-timing", workspaceToolGatewayServerTiming(timing));
|
|
2054
|
+
return c.json(response);
|
|
1109
2055
|
});
|
|
1110
2056
|
|
|
1111
2057
|
app.post("/v1/workspaces/:workspaceId/tools/approvals", async (c) => {
|
|
@@ -1146,7 +2092,11 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
1146
2092
|
workspaceId,
|
|
1147
2093
|
"workspace:read",
|
|
1148
2094
|
);
|
|
1149
|
-
const prepared = await
|
|
2095
|
+
const prepared = await prepareAttestedWorkspaceToolGateway(
|
|
2096
|
+
routeDeps,
|
|
2097
|
+
authorization,
|
|
2098
|
+
toolGatewayCatalogAttestations,
|
|
2099
|
+
);
|
|
1150
2100
|
try {
|
|
1151
2101
|
c.header("cache-control", "no-store");
|
|
1152
2102
|
return c.json(workspaceToolGatewayDeclarations(prepared));
|
|
@@ -1162,16 +2112,20 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
1162
2112
|
const prefix = `/v1/workspaces/${workspaceId}/codemode/sdk`;
|
|
1163
2113
|
let forwarded: Request;
|
|
1164
2114
|
try {
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
2115
|
+
const path = url.pathname.slice(prefix.length) + url.search;
|
|
2116
|
+
forwarded = await codemodeSessionRequest(routeDeps, grant, c.req.raw, path, (session) => {
|
|
2117
|
+
assertConfiguredCodemodeSessionProxyPath(session, path, c.req.method);
|
|
2118
|
+
const proxyTools = configuredCodemodeSessionProxyTools(routeDeps.settings, session);
|
|
2119
|
+
return proxyTools === null
|
|
2120
|
+
? routeDeps.settings
|
|
2121
|
+
: { ...routeDeps.settings, allowedFirstPartyMcpTools: proxyTools };
|
|
2122
|
+
});
|
|
1171
2123
|
} catch (error) {
|
|
1172
2124
|
throw codemodeHttpError(error);
|
|
1173
2125
|
}
|
|
1174
|
-
|
|
2126
|
+
// The forwarded request drops the caller's Host and names a non-sandbox
|
|
2127
|
+
// path; the request it was built from already passed the local boundary.
|
|
2128
|
+
return app.fetch(markLocalInternalDispatch(forwarded));
|
|
1175
2129
|
});
|
|
1176
2130
|
|
|
1177
2131
|
app.get("/v1/workspaces/:workspaceId/codemode/catalog", async (c) => {
|
|
@@ -1198,7 +2152,12 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
1198
2152
|
throw new HTTPException(400, { message: "Invalid Codemode call" });
|
|
1199
2153
|
}
|
|
1200
2154
|
try {
|
|
1201
|
-
const submission = await submitAndDispatchCodemodeCall(
|
|
2155
|
+
const submission = await submitAndDispatchCodemodeCall(
|
|
2156
|
+
routeDeps,
|
|
2157
|
+
grant,
|
|
2158
|
+
parsed.data,
|
|
2159
|
+
c.req.raw.signal,
|
|
2160
|
+
);
|
|
1202
2161
|
const terminal = ["completed", "failed", "outcome_unknown", "cancelled"].includes(
|
|
1203
2162
|
submission.operation.state,
|
|
1204
2163
|
);
|
|
@@ -1215,7 +2174,9 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
1215
2174
|
throw new HTTPException(403, { message: "Codemode access denied" });
|
|
1216
2175
|
}
|
|
1217
2176
|
try {
|
|
1218
|
-
const operation = await readCodemodeOperation(routeDeps, grant, c.req.param("operationId")
|
|
2177
|
+
const operation = await readCodemodeOperation(routeDeps, grant, c.req.param("operationId"), {
|
|
2178
|
+
durableApproval: c.req.header("x-opengeni-codemode-capabilities") === "durable-approval-v1",
|
|
2179
|
+
});
|
|
1219
2180
|
if (!operation)
|
|
1220
2181
|
throw new HTTPException(404, {
|
|
1221
2182
|
message: "Codemode operation not found",
|
|
@@ -1229,35 +2190,53 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
1229
2190
|
|
|
1230
2191
|
registerConnectCallbackReturns(app, routeDeps);
|
|
1231
2192
|
registerFileRoutes(app, routeDeps);
|
|
2193
|
+
registerSessionArtifactAssociationRoutes(app, routeDeps);
|
|
1232
2194
|
registerApiKeyRoutes(app, routeDeps);
|
|
2195
|
+
registerOrganizationMcpConnectionRoutes(app, routeDeps);
|
|
2196
|
+
registerPersonalApiKeyRoutes(app, routeDeps);
|
|
2197
|
+
registerOrganizationServiceAccountRoutes(app, routeDeps);
|
|
1233
2198
|
registerBillingRoutes(app, routeDeps);
|
|
1234
2199
|
registerBrowserIdentityRoutes(app, routeDeps);
|
|
1235
|
-
|
|
1236
|
-
|
|
2200
|
+
const interactionSecrets = createVaultInteractionSecretsPort({
|
|
2201
|
+
db: routeDeps.db,
|
|
2202
|
+
encryptionKey: environmentsEncryptionKeyBytes(routeDeps.settings) ?? null,
|
|
2203
|
+
providersFor: (workspaceId, subjectId) =>
|
|
2204
|
+
vaultProviderRegistryFor(routeDeps, { workspaceId, subjectId }),
|
|
2205
|
+
});
|
|
2206
|
+
registerBrowserSessionRoutes(app, routeDeps, { interactionSecrets });
|
|
2207
|
+
registerComputerSessionRoutes(app, routeDeps, { interactionSecrets });
|
|
1237
2208
|
registerDocumentRoutes(app, routeDeps);
|
|
1238
2209
|
registerKnowledgeRoutes(app, routeDeps);
|
|
1239
2210
|
registerGitHubRoutes(app, routeDeps);
|
|
1240
2211
|
registerInstallRoutes(app, routeDeps);
|
|
2212
|
+
registerDesktopDistributionRoutes(app, routeDeps);
|
|
1241
2213
|
registerInteractionResourceRoutes(app, routeDeps);
|
|
1242
2214
|
registerWorkspaceRoutes(app, routeDeps);
|
|
2215
|
+
registerUsageAllowanceRoutes(app, routeDeps);
|
|
1243
2216
|
registerInsightsRoutes(app, routeDeps);
|
|
2217
|
+
registerInsightsUsageRoutes(app, routeDeps);
|
|
1244
2218
|
registerWorkspaceInstructionPolicyRoutes(app, routeDeps);
|
|
1245
2219
|
registerWorkspaceLearningRoutes(app, routeDeps);
|
|
1246
2220
|
registerCompanyProfileRoutes(app, routeDeps);
|
|
1247
2221
|
registerCompanyBrainRoutes(app, routeDeps);
|
|
1248
2222
|
registerSlackTaskPolicyRoutes(app, routeDeps);
|
|
2223
|
+
registerSlackBotAccessRoutes(app, routeDeps);
|
|
1249
2224
|
registerWorkspaceStateRoutes(app, routeDeps);
|
|
1250
2225
|
registerMemorySlackPublicationRoutes(app, routeDeps);
|
|
1251
2226
|
registerWorkspaceArtifactRoutes(app, routeDeps);
|
|
2227
|
+
registerSiteDataRoutes(app, routeDeps);
|
|
2228
|
+
registerSitePublishingRoutes(app, routeDeps);
|
|
2229
|
+
registerSiteRuntimeRoutes(app, routeDeps);
|
|
1252
2230
|
registerArtifactCatalogRoutes(app, routeDeps);
|
|
2231
|
+
registerSiteSqlRoutes(app, routeDeps);
|
|
1253
2232
|
registerPreferenceRegistryRoutes(app, routeDeps);
|
|
1254
2233
|
registerSocialRoutes(app, routeDeps);
|
|
1255
2234
|
registerPersonalGitHubRoutes(app, routeDeps);
|
|
1256
2235
|
registerPersonalGitHubGitBrokerRoutes(app, routeDeps);
|
|
1257
2236
|
registerConnectionRoutes(app, routeDeps);
|
|
2237
|
+
registerVaultProviderRoutes(app, routeDeps);
|
|
1258
2238
|
registerConnectRoutes(app, routeDeps);
|
|
1259
|
-
|
|
1260
|
-
registerHostMcpResolverRoutes(app, routeDeps);
|
|
2239
|
+
|
|
1261
2240
|
registerExternalIdentityLinkRoutes(app, routeDeps);
|
|
1262
2241
|
registerCapabilityRoutes(app, routeDeps);
|
|
1263
2242
|
registerApiIntegrationRoutes(app, routeDeps);
|
|
@@ -1265,22 +2244,38 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
1265
2244
|
registerCatalogAssetRoutes(app, routeDeps);
|
|
1266
2245
|
registerEnrollmentRoutes(app, routeDeps);
|
|
1267
2246
|
registerMachineRoutes(app, routeDeps);
|
|
1268
|
-
|
|
2247
|
+
registerVariableSetsRemovedRoutes(app);
|
|
2248
|
+
registerVaultRoutes(app, routeDeps);
|
|
1269
2249
|
registerChannelRoutes(app, routeDeps);
|
|
1270
2250
|
registerRigRoutes(app, routeDeps);
|
|
1271
|
-
registerPackRoutes(app, routeDeps);
|
|
1272
2251
|
registerAutomationRoutes(app, routeDeps);
|
|
1273
2252
|
registerPrReviewRoutes(app, routeDeps);
|
|
1274
2253
|
registerPluginRoutes(app, routeDeps);
|
|
1275
2254
|
registerSkillRoutes(app, routeDeps);
|
|
2255
|
+
// The inbox's per-session mute routes call the session seam themselves, so
|
|
2256
|
+
// they register before the session module's fail-closed middleware, which
|
|
2257
|
+
// only classifies the session module's own paths.
|
|
2258
|
+
registerInboxRoutes(app, routeDeps);
|
|
1276
2259
|
registerSessionRoutes(app, routeDeps);
|
|
2260
|
+
registerSessionHistoryImportRoutes(app, routeDeps);
|
|
1277
2261
|
registerFeedbackRoutes(app, routeDeps);
|
|
2262
|
+
registerWorkspaceIntegrationRoutes(app, routeDeps);
|
|
2263
|
+
registerOrganizationIntegrationRoutes(app, routeDeps);
|
|
1278
2264
|
registerScheduledTaskRoutes(app, routeDeps);
|
|
1279
2265
|
registerCodexRoutes(app, routeDeps);
|
|
1280
2266
|
registerOrganizationModelProviderRoutes(app, routeDeps);
|
|
2267
|
+
registerOrganizationModelDefaultsRoutes(app, routeDeps);
|
|
2268
|
+
registerModelAccountSettingsRoutes(app, routeDeps);
|
|
2269
|
+
registerModelAccountChoiceRoutes(app, routeDeps);
|
|
2270
|
+
registerSessionAdminAccessRoutes(app, routeDeps);
|
|
2271
|
+
registerWorkspaceModelProviderRoutes(app, routeDeps);
|
|
2272
|
+
registerClaudeSubscriptionOAuthRoutes(app, routeDeps);
|
|
2273
|
+
registerOrganizationIntegrationPolicyRoutes(app, routeDeps);
|
|
1281
2274
|
registerModelConnectionAccessRoutes(app, routeDeps);
|
|
1282
2275
|
registerSuperGrokRoutes(app, routeDeps);
|
|
2276
|
+
registerClaudeSubscriptionAccountRoutes(app, routeDeps);
|
|
1283
2277
|
registerTranscriptionRoutes(app, routeDeps);
|
|
2278
|
+
registerChatCompletionRoutes(app, { ...routeDeps, modelCalls });
|
|
1284
2279
|
registerEditableArtifactRoutes(app, routeDeps);
|
|
1285
2280
|
registerVideoGenerationRoutes(app, routeDeps);
|
|
1286
2281
|
registerCanonicalHumanIdentityRoutes(app, routeDeps);
|
|
@@ -1288,12 +2283,11 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
1288
2283
|
registerOrganizationSessionRoutes(app, routeDeps);
|
|
1289
2284
|
registerOrganizationRecoveryRoutes(app, routeDeps);
|
|
1290
2285
|
registerUserResourceAuthorityRoutes(app, routeDeps);
|
|
1291
|
-
registerConnectionAuthorityRoutes(app, routeDeps);
|
|
1292
2286
|
registerSlackInteractionRoutes(app, routeDeps);
|
|
1293
2287
|
|
|
1294
2288
|
app.notFound((c) => {
|
|
1295
2289
|
if (!new URL(c.req.url).pathname.startsWith("/v1/")) return c.text("Not Found", 404);
|
|
1296
|
-
const requestId = correlationIds.get(c
|
|
2290
|
+
const requestId = correlationIds.get(c) ?? crypto.randomUUID();
|
|
1297
2291
|
return c.json(
|
|
1298
2292
|
ErrorEnvelope.parse({
|
|
1299
2293
|
error: {
|
|
@@ -1309,19 +2303,59 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
1309
2303
|
});
|
|
1310
2304
|
|
|
1311
2305
|
app.onError((rawError, c) => {
|
|
2306
|
+
const requestId = correlationIds.get(c) ?? crypto.randomUUID();
|
|
2307
|
+
c.header(OPENGENI_CORRELATION_HEADER, requestId);
|
|
2308
|
+
if (new URL(c.req.url).pathname.startsWith("/v1/")) {
|
|
2309
|
+
c.header(OPENGENI_API_CONTRACT_HEADER, OPENGENI_API_CONTRACT_REVISION);
|
|
2310
|
+
}
|
|
2311
|
+
// Archives remain read-only through the existing Send/Steer/control paths.
|
|
2312
|
+
const importErrorResponse = archivedSessionImportErrorResponse(c, rawError);
|
|
2313
|
+
if (importErrorResponse) return importErrorResponse;
|
|
2314
|
+
const removedResponse = variableSetsRemovedErrorResponse(c, rawError);
|
|
2315
|
+
if (removedResponse) return removedResponse;
|
|
1312
2316
|
// One central mapping for every Send/Steer/control route: a bounded
|
|
1313
2317
|
// control-prefix wait that expired is a known, retryable, not-applied 503.
|
|
1314
|
-
const error =
|
|
2318
|
+
const error =
|
|
2319
|
+
rawError instanceof OrganizationIntegrationDeniedError
|
|
2320
|
+
? new HTTPException(403, { message: rawError.message })
|
|
2321
|
+
: (sessionUsageLimitHttpError(rawError) ??
|
|
2322
|
+
allowanceExhaustedHttpError(rawError) ??
|
|
2323
|
+
subscriptionSettingsLockedHttpError(rawError) ??
|
|
2324
|
+
workspaceControlBusyHttpError(rawError) ??
|
|
2325
|
+
scheduledTaskTargetAccessHttpError(rawError) ??
|
|
2326
|
+
agentConfigHttpError(rawError) ??
|
|
2327
|
+
modelUnavailableHttpError(rawError) ??
|
|
2328
|
+
(rawError instanceof UnsupportedLatencyModeError
|
|
2329
|
+
? new ApiHttpError(422, {
|
|
2330
|
+
code: "validation_failed",
|
|
2331
|
+
message: rawError.message,
|
|
2332
|
+
retryable: false,
|
|
2333
|
+
outcomeUnknown: false,
|
|
2334
|
+
details: {
|
|
2335
|
+
code: rawError.code,
|
|
2336
|
+
modelId: rawError.modelId,
|
|
2337
|
+
latencyMode: rawError.latencyMode,
|
|
2338
|
+
allowedLatencyModes: [...rawError.allowedLatencyModes],
|
|
2339
|
+
},
|
|
2340
|
+
})
|
|
2341
|
+
: null) ??
|
|
2342
|
+
requestBodyValidationHttpError(rawError) ??
|
|
2343
|
+
invalidPathIdentifierHttpError(rawError, new URL(c.req.url).pathname) ??
|
|
2344
|
+
databaseUnavailableHttpError(rawError, c.req.method) ??
|
|
2345
|
+
databaseContentionHttpError(rawError, c.req.method) ??
|
|
2346
|
+
rawError);
|
|
1315
2347
|
const compactionLock = codexCompactionV2ProviderLockedError(error);
|
|
1316
2348
|
const apiError = error instanceof ApiHttpError ? error : null;
|
|
1317
2349
|
const status = compactionLock ? 422 : httpStatusForError(error);
|
|
1318
2350
|
const code: ErrorCode = compactionLock
|
|
1319
2351
|
? compactionLock.code
|
|
1320
2352
|
: (apiError?.code ?? errorCodeForStatus(status));
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
2353
|
+
if (status >= 500) logHttpFailure(c, status, code, rawError);
|
|
2354
|
+
if (
|
|
2355
|
+
apiError?.details?.code === DATABASE_UNAVAILABLE_ERROR_DETAIL_CODE ||
|
|
2356
|
+
apiError?.details?.code === DATABASE_CONTENTION_ERROR_DETAIL_CODE
|
|
2357
|
+
) {
|
|
2358
|
+
c.header("retry-after", "1");
|
|
1325
2359
|
}
|
|
1326
2360
|
const envelope = ErrorEnvelope.parse({
|
|
1327
2361
|
error: {
|
|
@@ -1346,6 +2380,14 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
1346
2380
|
return { app, routeDeps };
|
|
1347
2381
|
}
|
|
1348
2382
|
|
|
2383
|
+
function rejectionLogAttributes(rejection: HttpRejectionFacts) {
|
|
2384
|
+
return {
|
|
2385
|
+
rejectionCode: rejection.code,
|
|
2386
|
+
rejectionReason: rejection.reason,
|
|
2387
|
+
...(rejection.fingerprint ? { rejectionFingerprint: rejection.fingerprint } : {}),
|
|
2388
|
+
};
|
|
2389
|
+
}
|
|
2390
|
+
|
|
1349
2391
|
function managedAuthOAuthCallbackProvider(pathname: string): "google" | "github" | null {
|
|
1350
2392
|
const match = pathname.match(/^\/v1\/auth\/callback\/(google|github)$/u);
|
|
1351
2393
|
return match?.[1] === "google" || match?.[1] === "github" ? match[1] : null;
|
|
@@ -1381,6 +2423,36 @@ export function workspaceActorContextExempt(method: string, pathname: string): b
|
|
|
1381
2423
|
// Hono's trailing wildcard also matches it, but "external" is not a workspace UUID;
|
|
1382
2424
|
// the route performs its own organization API-key authorization.
|
|
1383
2425
|
if (method === "PUT" && pathname === "/v1/workspaces/external") return true;
|
|
2426
|
+
// Import adapters apply their own exact create/control gates and request-local
|
|
2427
|
+
// RLS in core. Tenant mirrors first resolve the organization-local mapping;
|
|
2428
|
+
// "external" itself is not a native workspace id.
|
|
2429
|
+
if (isSessionHistoryImportRequest(method, pathname)) return true;
|
|
2430
|
+
// Organization budget authority is independent of ordinary workspace
|
|
2431
|
+
// membership; these routes apply their own exact account/workspace gate.
|
|
2432
|
+
if (
|
|
2433
|
+
(["GET", "PUT", "DELETE"].includes(method) &&
|
|
2434
|
+
/^\/v1\/workspaces\/[^/]+\/allowance$/.test(pathname)) ||
|
|
2435
|
+
(method === "GET" && /^\/v1\/workspaces\/[^/]+\/allowance\/state$/.test(pathname)) ||
|
|
2436
|
+
(method === "POST" && /^\/v1\/workspaces\/[^/]+\/allowance\/grants$/.test(pathname))
|
|
2437
|
+
)
|
|
2438
|
+
return true;
|
|
2439
|
+
// Allowance tenant mirrors resolve the workspace by exact organization-local
|
|
2440
|
+
// external identity before applying the ordinary workspace authorization.
|
|
2441
|
+
if (
|
|
2442
|
+
(["GET", "PUT", "DELETE"].includes(method) &&
|
|
2443
|
+
/^\/v1\/workspaces\/external\/[^/]+\/[^/]+\/allowance$/.test(pathname)) ||
|
|
2444
|
+
(method === "GET" &&
|
|
2445
|
+
/^\/v1\/workspaces\/external\/[^/]+\/[^/]+\/allowance\/state$/.test(pathname)) ||
|
|
2446
|
+
(method === "POST" &&
|
|
2447
|
+
/^\/v1\/workspaces\/external\/[^/]+\/[^/]+\/allowance\/grants$/.test(pathname)) ||
|
|
2448
|
+
(method === "GET" &&
|
|
2449
|
+
/^\/v1\/workspaces\/external\/[^/]+\/[^/]+\/usage(?:\/me)?$/.test(pathname)) ||
|
|
2450
|
+
(method === "PUT" &&
|
|
2451
|
+
/^\/v1\/workspaces\/external\/[^/]+\/[^/]+\/members\/(?:[^/]+|external\/[^/]+\/[^/]+)\/allowance$/.test(
|
|
2452
|
+
pathname,
|
|
2453
|
+
))
|
|
2454
|
+
)
|
|
2455
|
+
return true;
|
|
1384
2456
|
if (/^\/v1\/workspaces\/[^/]+\/mcp(?:\/(?:docs|files))?$/.test(pathname)) return true;
|
|
1385
2457
|
if (
|
|
1386
2458
|
method === "GET" &&
|
|
@@ -1434,12 +2506,13 @@ async function requireMcpAccessGrantAuthorization(
|
|
|
1434
2506
|
return authorization;
|
|
1435
2507
|
}
|
|
1436
2508
|
|
|
1437
|
-
function clientAuthConfig(settings: AppDependencies["settings"]) {
|
|
2509
|
+
function clientAuthConfig(settings: AppDependencies["settings"], newSignupsEnabled: boolean) {
|
|
1438
2510
|
if (settings.productAccessMode === "managed") {
|
|
1439
2511
|
return {
|
|
1440
2512
|
mode: "managedSession" as const,
|
|
1441
2513
|
session: "cookie" as const,
|
|
1442
2514
|
emailVerificationRequired: settings.environment !== "local",
|
|
2515
|
+
newSignupsEnabled,
|
|
1443
2516
|
socialProviders: [
|
|
1444
2517
|
...(settings.managedAuthGoogleClientId && settings.managedAuthGoogleClientSecret
|
|
1445
2518
|
? (["google"] as const)
|
|
@@ -1466,11 +2539,151 @@ function clientAuthConfig(settings: AppDependencies["settings"]) {
|
|
|
1466
2539
|
return { mode: "none" as const };
|
|
1467
2540
|
}
|
|
1468
2541
|
|
|
2542
|
+
/** Configured SDK credentials cannot regain tools from widened legacy columns. */
|
|
2543
|
+
export function configuredCodemodeSessionProxyTools(
|
|
2544
|
+
settings: FirstPartyMcpToolPolicySettings,
|
|
2545
|
+
session: Pick<Session, "agent">,
|
|
2546
|
+
): FirstPartyMcpToolName[] | null {
|
|
2547
|
+
if (!session.agent) return null;
|
|
2548
|
+
const families = resolveAgentToolFamilies(session.agent);
|
|
2549
|
+
return resolveFirstPartyMcpToolPolicy(settings).allowed.filter((tool) =>
|
|
2550
|
+
families.allowsFirstPartyTool(tool),
|
|
2551
|
+
);
|
|
2552
|
+
}
|
|
2553
|
+
|
|
2554
|
+
/**
|
|
2555
|
+
* Permissions span capabilities: project_create grants sessions:create,
|
|
2556
|
+
* and runtime titling grants sessions:control. Fence the endpoint family
|
|
2557
|
+
* too, without changing null-config routing or ordinary REST authorization.
|
|
2558
|
+
*/
|
|
2559
|
+
export function assertConfiguredCodemodeSessionProxyPath(
|
|
2560
|
+
session: Pick<Session, "id" | "agent">,
|
|
2561
|
+
path: string,
|
|
2562
|
+
method: string,
|
|
2563
|
+
): void {
|
|
2564
|
+
if (!session.agent) return;
|
|
2565
|
+
const families = resolveAgentToolFamilies(session.agent);
|
|
2566
|
+
const pathname = new URL(path, "http://codemode.invalid").pathname;
|
|
2567
|
+
const segments = pathname.split("/").filter(Boolean);
|
|
2568
|
+
const verb = method.toUpperCase();
|
|
2569
|
+
const read = verb === "GET" || verb === "HEAD";
|
|
2570
|
+
const allows = (tool: FirstPartyMcpToolName) => families.allowsFirstPartyTool(tool);
|
|
2571
|
+
let allowed = false;
|
|
2572
|
+
if (pathname === "/v1/config/client") {
|
|
2573
|
+
allowed = read;
|
|
2574
|
+
} else if (segments[0] === "v1" && segments[1] === "workspaces" && segments[2] === "site-host") {
|
|
2575
|
+
const [surface, targetSessionId, operation] = segments.slice(3);
|
|
2576
|
+
if (!surface) {
|
|
2577
|
+
allowed = read;
|
|
2578
|
+
} else if (surface === "sessions") {
|
|
2579
|
+
const ownSession = targetSessionId === session.id;
|
|
2580
|
+
const targetAllowed = ownSession || families.subagents;
|
|
2581
|
+
if (!targetSessionId) {
|
|
2582
|
+
allowed = families.subagents;
|
|
2583
|
+
} else if (ownSession && !operation && read) {
|
|
2584
|
+
allowed = true;
|
|
2585
|
+
} else if (ownSession && !operation && verb === "PATCH") {
|
|
2586
|
+
allowed = allows("set_session_title");
|
|
2587
|
+
} else if (ownSession && operation === "background-commands") {
|
|
2588
|
+
allowed = read && allows("command_read");
|
|
2589
|
+
} else if (operation === "goal") {
|
|
2590
|
+
allowed = allows("goal_set") && targetAllowed;
|
|
2591
|
+
} else if (operation === "browser" || operation === "computer") {
|
|
2592
|
+
allowed = allows("browser_open") && targetAllowed;
|
|
2593
|
+
} else if (operation === "human-input-requests") {
|
|
2594
|
+
allowed = families.humanInput && targetAllowed;
|
|
2595
|
+
} else if (operation === "artifacts") {
|
|
2596
|
+
allowed = allows("artifacts_list") && targetAllowed;
|
|
2597
|
+
} else if (ownSession && ["fs", "git", "terminal", "workspace"].includes(operation ?? "")) {
|
|
2598
|
+
// Resource-derived sandbox mechanics are not platform capabilities.
|
|
2599
|
+
allowed = true;
|
|
2600
|
+
} else if (
|
|
2601
|
+
[
|
|
2602
|
+
"agent",
|
|
2603
|
+
"tool-policy",
|
|
2604
|
+
"mcp-servers",
|
|
2605
|
+
"mcp-credentials",
|
|
2606
|
+
"variable-sets",
|
|
2607
|
+
"project",
|
|
2608
|
+
].includes(operation ?? "")
|
|
2609
|
+
) {
|
|
2610
|
+
allowed = allows("project_get") && targetAllowed;
|
|
2611
|
+
} else {
|
|
2612
|
+
allowed = families.subagents;
|
|
2613
|
+
}
|
|
2614
|
+
} else if (surface === "connect") {
|
|
2615
|
+
allowed = allows("custom_mcp_setup_request") && isPreparedMcpConnectPath(pathname, verb);
|
|
2616
|
+
} else if (surface === "files") {
|
|
2617
|
+
allowed = families.allowsMcpServer("files");
|
|
2618
|
+
} else if (surface === "skills") {
|
|
2619
|
+
allowed = families.skills === "manage" || (families.skills === "read" && read);
|
|
2620
|
+
} else if (surface === "human-input-requests") {
|
|
2621
|
+
allowed = families.humanInput;
|
|
2622
|
+
} else {
|
|
2623
|
+
const owners: Record<string, FirstPartyMcpToolName> = {
|
|
2624
|
+
"model-catalog": "sessions_list",
|
|
2625
|
+
"realtime-model-catalog": "sessions_list",
|
|
2626
|
+
"model-policy": "sessions_list",
|
|
2627
|
+
"gateway-custom-models": "sessions_list",
|
|
2628
|
+
"openrouter-custom-models": "sessions_list",
|
|
2629
|
+
"opper-custom-models": "sessions_list",
|
|
2630
|
+
"new-session-draft": "session_create",
|
|
2631
|
+
"session-tenancy": "session_create",
|
|
2632
|
+
"session-message-search": "session_events",
|
|
2633
|
+
"agent-topology": "sessions_list",
|
|
2634
|
+
projects: "project_get",
|
|
2635
|
+
sandboxes: "sandboxes_list",
|
|
2636
|
+
machines: "sandboxes_list",
|
|
2637
|
+
rigs: "rig_list",
|
|
2638
|
+
environments: "environment_list",
|
|
2639
|
+
"variable-sets": "variable_set_list",
|
|
2640
|
+
capabilities: "capability_catalog_search",
|
|
2641
|
+
catalog: "capability_catalog_search",
|
|
2642
|
+
members: "project_get",
|
|
2643
|
+
"member-candidates": "project_get",
|
|
2644
|
+
"control-events": "project_get",
|
|
2645
|
+
"browser-identities": "browser_identity",
|
|
2646
|
+
"browser-sessions": "browser_open",
|
|
2647
|
+
"computer-sessions": "computer_open",
|
|
2648
|
+
"attached-browsers": "browser_open",
|
|
2649
|
+
documents: "knowledge_search",
|
|
2650
|
+
knowledge: "knowledge_search",
|
|
2651
|
+
memory: "knowledge_search",
|
|
2652
|
+
"instruction-policy": "instruction_policy_get",
|
|
2653
|
+
preferences: "preference_registry_get",
|
|
2654
|
+
"company-profile": "company_profile_propose",
|
|
2655
|
+
artifacts: "artifacts_list",
|
|
2656
|
+
"scheduled-tasks": "scheduled_tasks_list",
|
|
2657
|
+
automations: "scheduled_tasks_list",
|
|
2658
|
+
connections: "github_connect_link",
|
|
2659
|
+
integrations: "github_connect_link",
|
|
2660
|
+
github: "github_connect_link",
|
|
2661
|
+
social: "social_connections_list",
|
|
2662
|
+
mcp: "github_connect_link",
|
|
2663
|
+
};
|
|
2664
|
+
const tool = owners[surface];
|
|
2665
|
+
allowed = tool !== undefined && allows(tool);
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
if (!allowed) {
|
|
2669
|
+
throw new HTTPException(403, {
|
|
2670
|
+
message: "Agent configuration does not allow this Codemode SDK operation",
|
|
2671
|
+
});
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2674
|
+
|
|
1469
2675
|
function codemodeHttpError(error: unknown): HTTPException {
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
2676
|
+
// The journal admission is one idempotent transaction keyed by the
|
|
2677
|
+
// caller-owned operation id and is already retried in-process; every other
|
|
2678
|
+
// step is a read. An exhausted 40P01/40001 victim therefore committed
|
|
2679
|
+
// nothing and the client may resubmit the same operation id.
|
|
2680
|
+
const contention = databaseContentionHttpError(error, "POST", { outcomeUnknown: false });
|
|
2681
|
+
if (contention) return contention;
|
|
2682
|
+
if (error instanceof SiteSessionPathError || error instanceof SiteDataUnsupportedError) {
|
|
2683
|
+
// Site frame requests are held to the SITE_OPERATIONS allowlist and the
|
|
2684
|
+
// agent's own SDK requests to its workspace; a route outside them (or Site
|
|
2685
|
+
// Data on a host without a published Site) does not exist for this
|
|
2686
|
+
// caller rather than being a server fault.
|
|
1474
2687
|
return new HTTPException(404, { message: error.message, cause: error });
|
|
1475
2688
|
}
|
|
1476
2689
|
if (error instanceof SessionAuthorizationDeniedError) {
|
|
@@ -1527,6 +2740,9 @@ function codemodeHttpError(error: unknown): HTTPException {
|
|
|
1527
2740
|
if (error instanceof CodemodePayloadTooLargeError) {
|
|
1528
2741
|
return new HTTPException(413, { message: error.message, cause: error });
|
|
1529
2742
|
}
|
|
2743
|
+
if (error instanceof CodemodeOperationLimitError) {
|
|
2744
|
+
return new HTTPException(429, { message: error.message, cause: error });
|
|
2745
|
+
}
|
|
1530
2746
|
return error instanceof HTTPException
|
|
1531
2747
|
? error
|
|
1532
2748
|
: new HTTPException(500, {
|
|
@@ -1560,6 +2776,15 @@ function clientAnalyticsConfig(settings: AppDependencies["settings"]) {
|
|
|
1560
2776
|
};
|
|
1561
2777
|
}
|
|
1562
2778
|
|
|
2779
|
+
function clientLegalConfig(settings: AppDependencies["settings"]) {
|
|
2780
|
+
return {
|
|
2781
|
+
...(settings.legalPrivacyPolicyUrl ? { privacyPolicyUrl: settings.legalPrivacyPolicyUrl } : {}),
|
|
2782
|
+
...(settings.legalTermsOfServiceUrl
|
|
2783
|
+
? { termsOfServiceUrl: settings.legalTermsOfServiceUrl }
|
|
2784
|
+
: {}),
|
|
2785
|
+
};
|
|
2786
|
+
}
|
|
2787
|
+
|
|
1563
2788
|
function structuredServicesHint(backend: string): {
|
|
1564
2789
|
fileSystem: boolean;
|
|
1565
2790
|
git: boolean;
|
|
@@ -1591,6 +2816,9 @@ export function httpStatusForError(error: unknown): number {
|
|
|
1591
2816
|
if (workspaceControlBusyHttpError(error)) {
|
|
1592
2817
|
return 503;
|
|
1593
2818
|
}
|
|
2819
|
+
if (isRequestBodyValidationError(error)) {
|
|
2820
|
+
return 400;
|
|
2821
|
+
}
|
|
1594
2822
|
if (error instanceof HTTPException) {
|
|
1595
2823
|
return error.status;
|
|
1596
2824
|
}
|
|
@@ -1604,7 +2832,7 @@ export function errorCodeForStatus(status: number): ErrorCode {
|
|
|
1604
2832
|
if (status === 401) return "unauthenticated";
|
|
1605
2833
|
if (status === 402) return "payment_required";
|
|
1606
2834
|
if (status === 403) return "forbidden";
|
|
1607
|
-
if (status === 404) return "not_found";
|
|
2835
|
+
if (status === 404 || status === 405) return "not_found";
|
|
1608
2836
|
if (status === 409) return "conflict";
|
|
1609
2837
|
if (status === 413 || status === 422 || status === 400) return "validation_failed";
|
|
1610
2838
|
if (status === 429) return "limit_exceeded";
|
|
@@ -1621,10 +2849,10 @@ function publicErrorMessage(error: unknown, status: number): string {
|
|
|
1621
2849
|
return "Connection setup changed or is still in progress. Reload its current status before retrying.";
|
|
1622
2850
|
if (error instanceof ConnectAttemptNotFoundError) return "Connection setup not found.";
|
|
1623
2851
|
if (status === 502 || status === 503 || status === 504) {
|
|
1624
|
-
return "
|
|
2852
|
+
return "Opengeni is temporarily unavailable — retry.";
|
|
1625
2853
|
}
|
|
1626
2854
|
if (status >= 500) {
|
|
1627
|
-
return "
|
|
2855
|
+
return "Opengeni could not complete the request.";
|
|
1628
2856
|
}
|
|
1629
2857
|
if (error instanceof HTTPException) {
|
|
1630
2858
|
return boundedPublicMessage(error.message) ?? "Request failed.";
|
|
@@ -1739,7 +2967,7 @@ const routeLabelPatterns: Array<{
|
|
|
1739
2967
|
label: string | ((match: RegExpMatchArray) => string);
|
|
1740
2968
|
}> = [
|
|
1741
2969
|
{
|
|
1742
|
-
pattern: /^\/\.well-known\/oauth-authorization-server
|
|
2970
|
+
pattern: /^\/\.well-known\/oauth-authorization-server(?:\/.*)?$/,
|
|
1743
2971
|
label: "/.well-known/oauth-authorization-server",
|
|
1744
2972
|
},
|
|
1745
2973
|
{
|
|
@@ -1754,6 +2982,21 @@ const routeLabelPatterns: Array<{
|
|
|
1754
2982
|
},
|
|
1755
2983
|
{ pattern: /^\/healthz$/, label: "/healthz" },
|
|
1756
2984
|
{ pattern: /^\/readyz$/, label: "/readyz" },
|
|
2985
|
+
// Better Auth answers these behind one `/v1/auth/*` registration, so the
|
|
2986
|
+
// provider endpoints need an explicit closed set to stay distinguishable.
|
|
2987
|
+
{
|
|
2988
|
+
pattern:
|
|
2989
|
+
/^\/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)$/,
|
|
2990
|
+
label: (match) => `/v1/auth/${match[1]}`,
|
|
2991
|
+
},
|
|
2992
|
+
{ pattern: /^\/v1\/auth\/reset-password\/[^/]+$/, label: "/v1/auth/reset-password/:token" },
|
|
2993
|
+
{
|
|
2994
|
+
pattern: /^\/v1\/auth\/callback\/([^/]+)$/,
|
|
2995
|
+
label: (match) =>
|
|
2996
|
+
match[1] === "google" || match[1] === "github"
|
|
2997
|
+
? `/v1/auth/callback/${match[1]}`
|
|
2998
|
+
: "/v1/auth/callback/:providerId",
|
|
2999
|
+
},
|
|
1757
3000
|
{ pattern: /^\/traffic-readyz$/, label: "/traffic-readyz" },
|
|
1758
3001
|
{
|
|
1759
3002
|
pattern: /^\/v1\/workspaces\/[^/]+\/codex\/connect\/start$/,
|
|
@@ -1780,7 +3023,8 @@ const routeLabelPatterns: Array<{
|
|
|
1780
3023
|
label: (match) => `/v1/organizations/:organizationId/codex/connect/${match[1]}`,
|
|
1781
3024
|
},
|
|
1782
3025
|
{
|
|
1783
|
-
pattern:
|
|
3026
|
+
pattern:
|
|
3027
|
+
/^\/v1\/organizations\/[^/]+\/codex\/accounts\/[^/]+(?:\/(activate|allocator|extra-credits))?$/,
|
|
1784
3028
|
label: "/v1/organizations/:organizationId/codex/accounts/:accountId",
|
|
1785
3029
|
},
|
|
1786
3030
|
{
|
|
@@ -1809,6 +3053,8 @@ const routeLabelPatterns: Array<{
|
|
|
1809
3053
|
},
|
|
1810
3054
|
{ pattern: /^\/metrics$/, label: "/metrics" },
|
|
1811
3055
|
{ pattern: /^\/v1\/config\/client$/, label: "/v1/config/client" },
|
|
3056
|
+
{ pattern: /^\/v1\/client-errors$/, label: "/v1/client-errors" },
|
|
3057
|
+
{ pattern: /^\/v1\/analytics-consent$/, label: "/v1/analytics-consent" },
|
|
1812
3058
|
{ pattern: /^\/v1\/billing$/, label: "/v1/billing" },
|
|
1813
3059
|
{ pattern: /^\/v1\/billing\/checkout$/, label: "/v1/billing/checkout" },
|
|
1814
3060
|
{ pattern: /^\/v1\/billing\/usage$/, label: "/v1/billing/usage" },
|
|
@@ -1816,6 +3062,22 @@ const routeLabelPatterns: Array<{
|
|
|
1816
3062
|
pattern: /^\/v1\/workspaces\/[^/]+\/insights$/,
|
|
1817
3063
|
label: "/v1/workspaces/:workspaceId/insights",
|
|
1818
3064
|
},
|
|
3065
|
+
{
|
|
3066
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/insights\/usage$/,
|
|
3067
|
+
label: "/v1/workspaces/:workspaceId/insights/usage",
|
|
3068
|
+
},
|
|
3069
|
+
{
|
|
3070
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/insights\/calls$/,
|
|
3071
|
+
label: "/v1/workspaces/:workspaceId/insights/calls",
|
|
3072
|
+
},
|
|
3073
|
+
{
|
|
3074
|
+
pattern: /^\/v1\/organizations\/[^/]+\/insights\/usage$/,
|
|
3075
|
+
label: "/v1/organizations/:accountId/insights/usage",
|
|
3076
|
+
},
|
|
3077
|
+
{
|
|
3078
|
+
pattern: /^\/v1\/organizations\/[^/]+\/insights\/calls$/,
|
|
3079
|
+
label: "/v1/organizations/:accountId/insights/calls",
|
|
3080
|
+
},
|
|
1819
3081
|
{
|
|
1820
3082
|
pattern: /^\/v1\/billing\/entitlements$/,
|
|
1821
3083
|
label: "/v1/billing/entitlements",
|
|
@@ -1841,6 +3103,10 @@ const routeLabelPatterns: Array<{
|
|
|
1841
3103
|
pattern: /^\/v1\/workspaces\/[^/]+\/tools\/catalog$/,
|
|
1842
3104
|
label: "/v1/workspaces/:workspaceId/tools/catalog",
|
|
1843
3105
|
},
|
|
3106
|
+
{
|
|
3107
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/tools\/(resolve|invoke|target-approvals|manifest)$/,
|
|
3108
|
+
label: (match) => `/v1/workspaces/:workspaceId/tools/${match[1]}`,
|
|
3109
|
+
},
|
|
1844
3110
|
{
|
|
1845
3111
|
pattern: /^\/v1\/workspaces\/[^/]+\/tools\/calls$/,
|
|
1846
3112
|
label: "/v1/workspaces/:workspaceId/tools/calls",
|
|
@@ -1861,6 +3127,18 @@ const routeLabelPatterns: Array<{
|
|
|
1861
3127
|
pattern: /^\/v1\/workspaces\/[^/]+\/sessions$/,
|
|
1862
3128
|
label: "/v1/workspaces/:workspaceId/sessions",
|
|
1863
3129
|
},
|
|
3130
|
+
{
|
|
3131
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/session-imports$/,
|
|
3132
|
+
label: "/v1/workspaces/:workspaceId/session-imports",
|
|
3133
|
+
},
|
|
3134
|
+
{
|
|
3135
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/session-imports\/[^/]+\/events$/,
|
|
3136
|
+
label: "/v1/workspaces/:workspaceId/session-imports/:importId/events",
|
|
3137
|
+
},
|
|
3138
|
+
{
|
|
3139
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/session-message-search$/,
|
|
3140
|
+
label: "/v1/workspaces/:workspaceId/session-message-search",
|
|
3141
|
+
},
|
|
1864
3142
|
{
|
|
1865
3143
|
pattern: /^\/v1\/workspaces\/[^/]+\/control-events\/stream$/,
|
|
1866
3144
|
label: "/v1/workspaces/:workspaceId/control-events/stream",
|
|
@@ -1914,7 +3192,7 @@ const routeLabelPatterns: Array<{
|
|
|
1914
3192
|
},
|
|
1915
3193
|
{
|
|
1916
3194
|
pattern:
|
|
1917
|
-
/^\/v1\/workspaces\/[^/]+\/browser-sessions\/[^/]+\/auth-runs\/[^/]+\/(external-auth|
|
|
3195
|
+
/^\/v1\/workspaces\/[^/]+\/browser-sessions\/[^/]+\/auth-runs\/[^/]+\/(external-auth|report|verify)$/,
|
|
1918
3196
|
label: (match) =>
|
|
1919
3197
|
`/v1/workspaces/:workspaceId/browser-sessions/:browserSessionId/auth-runs/:authRunId/${match[1]}`,
|
|
1920
3198
|
},
|
|
@@ -1970,7 +3248,7 @@ const routeLabelPatterns: Array<{
|
|
|
1970
3248
|
},
|
|
1971
3249
|
{
|
|
1972
3250
|
pattern:
|
|
1973
|
-
/^\/v1\/workspaces\/[^/]+\/computer-sessions\/[^/]+\/(actions|attachments|clipboard|end|heartbeat|targets)$/,
|
|
3251
|
+
/^\/v1\/workspaces\/[^/]+\/computer-sessions\/[^/]+\/(actions|native-calls|attachments|clipboard|end|heartbeat|targets)$/,
|
|
1974
3252
|
label: (match) =>
|
|
1975
3253
|
`/v1/workspaces/:workspaceId/computer-sessions/:computerSessionId/${match[1]}`,
|
|
1976
3254
|
},
|
|
@@ -2155,6 +3433,72 @@ const routeLabelPatterns: Array<{
|
|
|
2155
3433
|
pattern: /^\/v1\/workspaces\/external$/,
|
|
2156
3434
|
label: "/v1/workspaces/external",
|
|
2157
3435
|
},
|
|
3436
|
+
{
|
|
3437
|
+
pattern: /^\/v1\/workspaces\/external\/[^/]+\/[^/]+\/session-imports$/,
|
|
3438
|
+
label: "/v1/workspaces/external/:source/:externalId/session-imports",
|
|
3439
|
+
},
|
|
3440
|
+
{
|
|
3441
|
+
pattern: /^\/v1\/workspaces\/external\/[^/]+\/[^/]+\/session-imports\/[^/]+\/events$/,
|
|
3442
|
+
label: "/v1/workspaces/external/:source/:externalId/session-imports/:importId/events",
|
|
3443
|
+
},
|
|
3444
|
+
{
|
|
3445
|
+
pattern: /^\/v1\/workspaces\/external\/[^/]+\/[^/]+\/allowance\/grants$/,
|
|
3446
|
+
label: "/v1/workspaces/external/:source/:externalId/allowance/grants",
|
|
3447
|
+
},
|
|
3448
|
+
{
|
|
3449
|
+
pattern: /^\/v1\/workspaces\/external\/[^/]+\/[^/]+\/allowance$/,
|
|
3450
|
+
label: "/v1/workspaces/external/:source/:externalId/allowance",
|
|
3451
|
+
},
|
|
3452
|
+
{
|
|
3453
|
+
pattern: /^\/v1\/workspaces\/external\/[^/]+\/[^/]+\/allowance\/state$/,
|
|
3454
|
+
label: "/v1/workspaces/external/:source/:externalId/allowance/state",
|
|
3455
|
+
},
|
|
3456
|
+
{
|
|
3457
|
+
pattern: /^\/v1\/workspaces\/external\/[^/]+\/[^/]+\/usage\/me$/,
|
|
3458
|
+
label: "/v1/workspaces/external/:source/:externalId/usage/me",
|
|
3459
|
+
},
|
|
3460
|
+
{
|
|
3461
|
+
pattern: /^\/v1\/workspaces\/external\/[^/]+\/[^/]+\/usage$/,
|
|
3462
|
+
label: "/v1/workspaces/external/:source/:externalId/usage",
|
|
3463
|
+
},
|
|
3464
|
+
{
|
|
3465
|
+
pattern:
|
|
3466
|
+
/^\/v1\/workspaces\/external\/[^/]+\/[^/]+\/members\/external\/[^/]+\/[^/]+\/allowance$/,
|
|
3467
|
+
label:
|
|
3468
|
+
"/v1/workspaces/external/:source/:externalId/members/external/:memberSource/:memberExternalId/allowance",
|
|
3469
|
+
},
|
|
3470
|
+
{
|
|
3471
|
+
pattern: /^\/v1\/workspaces\/external\/[^/]+\/[^/]+\/members\/[^/]+\/allowance$/,
|
|
3472
|
+
label: "/v1/workspaces/external/:source/:externalId/members/:subjectId/allowance",
|
|
3473
|
+
},
|
|
3474
|
+
{
|
|
3475
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/allowance\/grants$/,
|
|
3476
|
+
label: "/v1/workspaces/:workspaceId/allowance/grants",
|
|
3477
|
+
},
|
|
3478
|
+
{
|
|
3479
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/allowance$/,
|
|
3480
|
+
label: "/v1/workspaces/:workspaceId/allowance",
|
|
3481
|
+
},
|
|
3482
|
+
{
|
|
3483
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/allowance\/state$/,
|
|
3484
|
+
label: "/v1/workspaces/:workspaceId/allowance/state",
|
|
3485
|
+
},
|
|
3486
|
+
{
|
|
3487
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/usage\/me$/,
|
|
3488
|
+
label: "/v1/workspaces/:workspaceId/usage/me",
|
|
3489
|
+
},
|
|
3490
|
+
{
|
|
3491
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/usage$/,
|
|
3492
|
+
label: "/v1/workspaces/:workspaceId/usage",
|
|
3493
|
+
},
|
|
3494
|
+
{
|
|
3495
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/members\/external\/[^/]+\/[^/]+\/allowance$/,
|
|
3496
|
+
label: "/v1/workspaces/:workspaceId/members/external/:source/:externalId/allowance",
|
|
3497
|
+
},
|
|
3498
|
+
{
|
|
3499
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/members\/[^/]+\/allowance$/,
|
|
3500
|
+
label: "/v1/workspaces/:workspaceId/members/:subjectId/allowance",
|
|
3501
|
+
},
|
|
2158
3502
|
{
|
|
2159
3503
|
pattern: /^\/v1\/workspaces\/[^/]+\/scheduled-tasks$/,
|
|
2160
3504
|
label: "/v1/workspaces/:workspaceId/scheduled-tasks",
|
|
@@ -2373,26 +3717,7 @@ const routeLabelPatterns: Array<{
|
|
|
2373
3717
|
pattern: /^\/v1\/workspaces\/[^/]+\/environments\/[^/]+$/,
|
|
2374
3718
|
label: "/v1/workspaces/:workspaceId/environments/:id",
|
|
2375
3719
|
},
|
|
2376
|
-
|
|
2377
|
-
pattern: /^\/v1\/workspaces\/[^/]+\/packs$/,
|
|
2378
|
-
label: "/v1/workspaces/:workspaceId/packs",
|
|
2379
|
-
},
|
|
2380
|
-
{
|
|
2381
|
-
pattern: /^\/v1\/workspaces\/[^/]+\/packs\/installations$/,
|
|
2382
|
-
label: "/v1/workspaces/:workspaceId/packs/installations",
|
|
2383
|
-
},
|
|
2384
|
-
{
|
|
2385
|
-
pattern: /^\/v1\/workspaces\/[^/]+\/packs\/marketing-social-daily-analysis\/scheduled-tasks$/,
|
|
2386
|
-
label: "/v1/workspaces/:workspaceId/packs/marketing-social-daily-analysis/scheduled-tasks",
|
|
2387
|
-
},
|
|
2388
|
-
{
|
|
2389
|
-
pattern: /^\/v1\/workspaces\/[^/]+\/packs\/[^/]+\/enable$/,
|
|
2390
|
-
label: "/v1/workspaces/:workspaceId/packs/:id/enable",
|
|
2391
|
-
},
|
|
2392
|
-
{
|
|
2393
|
-
pattern: /^\/v1\/workspaces\/[^/]+\/packs\/[^/]+$/,
|
|
2394
|
-
label: "/v1/workspaces/:workspaceId/packs/:id",
|
|
2395
|
-
},
|
|
3720
|
+
|
|
2396
3721
|
{
|
|
2397
3722
|
pattern: /^\/v1\/workspaces\/[^/]+\/plugins\/preview$/,
|
|
2398
3723
|
label: "/v1/workspaces/:workspaceId/plugins/preview",
|
|
@@ -2546,6 +3871,22 @@ const routeLabelPatterns: Array<{
|
|
|
2546
3871
|
pattern: /^\/v1\/workspaces\/[^/]+\/machines\/[^/]+\/update$/,
|
|
2547
3872
|
label: "/v1/workspaces/:workspaceId/machines/:enrollmentId/update",
|
|
2548
3873
|
},
|
|
3874
|
+
{
|
|
3875
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/machines\/[^/]+\/screen-control$/,
|
|
3876
|
+
label: "/v1/workspaces/:workspaceId/machines/:enrollmentId/screen-control",
|
|
3877
|
+
},
|
|
3878
|
+
{
|
|
3879
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/machines\/[^/]+\/privacy-settings$/,
|
|
3880
|
+
label: "/v1/workspaces/:workspaceId/machines/:enrollmentId/privacy-settings",
|
|
3881
|
+
},
|
|
3882
|
+
{
|
|
3883
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/machines\/[^/]+\/audience\/preview$/,
|
|
3884
|
+
label: "/v1/workspaces/:workspaceId/machines/:enrollmentId/audience/preview",
|
|
3885
|
+
},
|
|
3886
|
+
{
|
|
3887
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/machines\/[^/]+\/audience$/,
|
|
3888
|
+
label: "/v1/workspaces/:workspaceId/machines/:enrollmentId/audience",
|
|
3889
|
+
},
|
|
2549
3890
|
{
|
|
2550
3891
|
pattern: /^\/v1\/workspaces\/[^/]+\/machines$/,
|
|
2551
3892
|
label: "/v1/workspaces/:workspaceId/machines",
|
|
@@ -2582,7 +3923,13 @@ const routeLabelPatterns: Array<{
|
|
|
2582
3923
|
},
|
|
2583
3924
|
];
|
|
2584
3925
|
|
|
2585
|
-
|
|
3926
|
+
/**
|
|
3927
|
+
* Bounded route label for metrics, spans, and request logs. Explicit patterns
|
|
3928
|
+
* keep established label spellings stable; any other request answered by a
|
|
3929
|
+
* registered handler uses that handler's code-owned path template (see
|
|
3930
|
+
* `registeredHandlerRoutePath`). Only unregistered paths fall into `unknown`.
|
|
3931
|
+
*/
|
|
3932
|
+
export function routeLabel(pathname: string, registeredRoutePath?: string | null): string {
|
|
2586
3933
|
if (/^\/v1\/workspaces\/[^/]+\/transcriptions$/.test(pathname))
|
|
2587
3934
|
return "/v1/workspaces/:workspaceId/transcriptions";
|
|
2588
3935
|
const transcription = pathname.match(
|
|
@@ -2603,11 +3950,61 @@ export function routeLabel(pathname: string): string {
|
|
|
2603
3950
|
return typeof candidate.label === "string" ? candidate.label : candidate.label(match);
|
|
2604
3951
|
}
|
|
2605
3952
|
}
|
|
3953
|
+
const registered = boundedRegisteredRouteLabel(registeredRoutePath);
|
|
3954
|
+
if (registered) return registered;
|
|
2606
3955
|
return pathname.startsWith("/v1/") ? "/v1/unknown" : "/unknown";
|
|
2607
3956
|
}
|
|
2608
3957
|
|
|
2609
3958
|
/**
|
|
2610
|
-
*
|
|
3959
|
+
* API contract revisions no longer admitted from ANY caller, including bearer
|
|
3960
|
+
* integrations that would otherwise be accepted across revisions. Add a
|
|
3961
|
+
* revision here only for a truly breaking wire change (which also requires a
|
|
3962
|
+
* major release-train change); ordinary revision bumps stay additive.
|
|
3963
|
+
*/
|
|
3964
|
+
export const REFUSED_API_CONTRACT_REVISIONS: ReadonlySet<string> = new Set<string>([]);
|
|
3965
|
+
|
|
3966
|
+
const BEARER_AUTHORIZATION = /^bearer\s+\S/i;
|
|
3967
|
+
|
|
3968
|
+
/**
|
|
3969
|
+
* The rollout fence for state-changing product calls.
|
|
3970
|
+
*
|
|
3971
|
+
* The exact-revision check exists to stop a stale first-party browser tab
|
|
3972
|
+
* (cookie session, or unauthenticated local mode) from writing with an old
|
|
3973
|
+
* request shape after a deployment; it answers 409 so the page reloads onto
|
|
3974
|
+
* the matching bundle. That tab never sends `Authorization`, so this is not a
|
|
3975
|
+
* header a stale bundle can choose to bypass it with.
|
|
3976
|
+
*
|
|
3977
|
+
* Bearer-authenticated callers (organization/workspace API keys, delegated
|
|
3978
|
+
* tokens, the deployment key sent as a bearer) are integrations pinned to an
|
|
3979
|
+
* SDK version: reloading cannot upgrade them, and the API is additive within a
|
|
3980
|
+
* major release train. They are admitted with an older revision or with no
|
|
3981
|
+
* revision claim at all. Only a revision listed in
|
|
3982
|
+
* `REFUSED_API_CONTRACT_REVISIONS` is refused for them.
|
|
3983
|
+
*
|
|
3984
|
+
* This is a compatibility fence, not an authorization boundary: every route
|
|
3985
|
+
* still authenticates and authorizes the request independently.
|
|
3986
|
+
*/
|
|
3987
|
+
export function apiContractAdmission(
|
|
3988
|
+
input: {
|
|
3989
|
+
method: string;
|
|
3990
|
+
pathname: string;
|
|
3991
|
+
authorization: string | undefined;
|
|
3992
|
+
claimedRevision: string | undefined;
|
|
3993
|
+
},
|
|
3994
|
+
refusedRevisions: ReadonlySet<string> = REFUSED_API_CONTRACT_REVISIONS,
|
|
3995
|
+
): "admit" | "reject" {
|
|
3996
|
+
if (!isApiContractProtectedMutation(input.method, input.pathname)) return "admit";
|
|
3997
|
+
if (input.claimedRevision === OPENGENI_API_CONTRACT_REVISION) return "admit";
|
|
3998
|
+
if (input.claimedRevision !== undefined && refusedRevisions.has(input.claimedRevision)) {
|
|
3999
|
+
return "reject";
|
|
4000
|
+
}
|
|
4001
|
+
return input.authorization !== undefined && BEARER_AUTHORIZATION.test(input.authorization)
|
|
4002
|
+
? "admit"
|
|
4003
|
+
: "reject";
|
|
4004
|
+
}
|
|
4005
|
+
|
|
4006
|
+
/**
|
|
4007
|
+
* State-changing Opengeni HTTP calls must never cross an incompatible rollout
|
|
2611
4008
|
* boundary. Standard third-party protocols and externally initiated callbacks
|
|
2612
4009
|
* are intentionally outside this product API contract.
|
|
2613
4010
|
*/
|
|
@@ -2633,12 +4030,47 @@ export function isApiContractProtectedMutation(method: string, pathname: string)
|
|
|
2633
4030
|
pathname === "/v1/integrations/slack/commands" ||
|
|
2634
4031
|
pathname === "/v1/integrations/slack/interactions" ||
|
|
2635
4032
|
pathname.startsWith("/v1/github/") ||
|
|
4033
|
+
// A stale tab must still report the error that follows a rollout.
|
|
4034
|
+
pathname === CLIENT_ERRORS_PATH ||
|
|
4035
|
+
pathname === ANALYTICS_CONSENT_PATH ||
|
|
2636
4036
|
pathname === "/v1/enrollments/device/start" ||
|
|
2637
4037
|
pathname === "/v1/enrollments/device/poll" ||
|
|
2638
|
-
pathname === "/v1/enrollments/token/exchange"
|
|
4038
|
+
pathname === "/v1/enrollments/token/exchange" ||
|
|
4039
|
+
// The runner authenticates this protocol with its install-key proof, not
|
|
4040
|
+
// a browser cookie or the product-client revision header.
|
|
4041
|
+
pathname === "/v1/enrollments/renew" ||
|
|
4042
|
+
// The Site gateway's signed protocol carries its own signature version (v1).
|
|
4043
|
+
pathname.startsWith("/v1/site-runtime/")
|
|
2639
4044
|
) {
|
|
2640
4045
|
return false;
|
|
2641
4046
|
}
|
|
2642
4047
|
const segments = pathname.split("/");
|
|
2643
4048
|
return !segments.includes("mcp") && !segments.includes("codemode");
|
|
2644
4049
|
}
|
|
4050
|
+
|
|
4051
|
+
/** Published Sites deployment facts for the console; null while publishing is off. */
|
|
4052
|
+
function clientSitesConfig(settings: Settings): ClientSitesConfig | null {
|
|
4053
|
+
const domain = configuredSitesDomain(settings);
|
|
4054
|
+
if (!domain) return null;
|
|
4055
|
+
return {
|
|
4056
|
+
domain,
|
|
4057
|
+
publicEnabled: settings.sitesPublicEnabled,
|
|
4058
|
+
publicAgentsEnabled: settings.sitesPublicAgentsEnabled,
|
|
4059
|
+
loginOnly: settings.sitesLoginOnly,
|
|
4060
|
+
forceNoindex: settings.sitesForceNoindex,
|
|
4061
|
+
};
|
|
4062
|
+
}
|
|
4063
|
+
|
|
4064
|
+
/** Client-safe agent-configuration projection. */
|
|
4065
|
+
function clientAgentConfig(settings: Settings): ClientAgentConfig {
|
|
4066
|
+
const policy = agentConfigDeploymentPolicy(settings);
|
|
4067
|
+
return {
|
|
4068
|
+
// Deprecated: agent configuration is always on. Kept for client compatibility.
|
|
4069
|
+
enabled: true,
|
|
4070
|
+
defaultForNewSessions: true,
|
|
4071
|
+
capabilities: AGENT_CAPABILITY_IDS.map((id) => {
|
|
4072
|
+
const reason = policy.unavailable[id];
|
|
4073
|
+
return reason === undefined ? { id, available: true } : { id, available: false, reason };
|
|
4074
|
+
}),
|
|
4075
|
+
};
|
|
4076
|
+
}
|