@opengeni/api-router 3.0.1 → 3.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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-OGQDXVXY.js +18 -0
- package/dist/chunk-OGQDXVXY.js.map +1 -0
- package/dist/{chunk-AJ62T5QN.js → chunk-OT56OY4V.js} +80503 -39869
- package/dist/chunk-OT56OY4V.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 +165 -55
- 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
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { ErrorCode, Permission } from "@opengeni/contracts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Bounded, content-free classification of a rejected (4xx/5xx) API response,
|
|
6
|
+
* so a refused request can be diagnosed from logs and counted per route without
|
|
7
|
+
* copying the response message, identifiers, or user content into telemetry.
|
|
8
|
+
*
|
|
9
|
+
* - `code` is the public `ErrorEnvelope.code` (a closed enum).
|
|
10
|
+
* - `reason` is `permission:<name>` when the refusal names a missing permission
|
|
11
|
+
* from the closed permission catalog, else the envelope's typed
|
|
12
|
+
* `details.code` when it is a short identifier, else `control:<code>` for a
|
|
13
|
+
* Connected Machine control failure, else `unclassified`.
|
|
14
|
+
* - `fingerprint` is a one-way hash of the message with identifiers and
|
|
15
|
+
* numbers normalized away. It groups identical refusals in logs; resolve it
|
|
16
|
+
* back to source text with `bun run scripts/resolve-rejection-fingerprint.ts`.
|
|
17
|
+
* It never becomes a metric label.
|
|
18
|
+
*/
|
|
19
|
+
export type HttpRejectionFacts = {
|
|
20
|
+
code: string;
|
|
21
|
+
reason: string;
|
|
22
|
+
fingerprint?: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const ERROR_CODES: ReadonlySet<string> = new Set(ErrorCode.options);
|
|
26
|
+
const PERMISSIONS: ReadonlySet<string> = new Set(Permission.options);
|
|
27
|
+
const DETAIL_CODE = /^[A-Za-z][A-Za-z0-9_.-]{0,63}$/;
|
|
28
|
+
const MISSING_PERMISSION = /^missing (?:literal )?permission: ([a-z][a-z0-9-]*:[a-z][a-z0-9-]*)/;
|
|
29
|
+
const CONTROL_CODE = /^[a-z][a-z0-9_]{0,31}$/;
|
|
30
|
+
const MAX_ENVELOPE_BYTES = 32 * 1024;
|
|
31
|
+
|
|
32
|
+
export const UNCLASSIFIED_REJECTION_REASON = "unclassified";
|
|
33
|
+
|
|
34
|
+
export function rejectionFingerprint(message: string): string {
|
|
35
|
+
return `m_${createHash("sha256").update(normalizeRejectionMessage(message)).digest("hex").slice(0, 10)}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Identifiers, quoted values, and numbers never distinguish two fingerprints. */
|
|
39
|
+
export function normalizeRejectionMessage(message: string): string {
|
|
40
|
+
return message
|
|
41
|
+
.replace(/[\u0000-\u001f\u007f]+/g, " ")
|
|
42
|
+
.replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, "<id>")
|
|
43
|
+
.replace(/"[^"]*"|'[^']*'|`[^`]*`/g, "<q>")
|
|
44
|
+
.replace(/[0-9]+/g, "0")
|
|
45
|
+
.replace(/\s+/g, " ")
|
|
46
|
+
.trim();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function rejectionReasonFromMessage(message: string | undefined): string | null {
|
|
50
|
+
if (!message) return null;
|
|
51
|
+
const permission = MISSING_PERMISSION.exec(message)?.[1];
|
|
52
|
+
return permission && PERMISSIONS.has(permission) ? `permission:${permission}` : null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Classify a parsed `ErrorEnvelope`-shaped body. Unknown shapes still get a code. */
|
|
56
|
+
export function httpRejectionFactsFromEnvelope(
|
|
57
|
+
envelope: unknown,
|
|
58
|
+
fallbackCode: string,
|
|
59
|
+
): HttpRejectionFacts {
|
|
60
|
+
const error =
|
|
61
|
+
envelope && typeof envelope === "object" && "error" in envelope
|
|
62
|
+
? (envelope as { error?: unknown }).error
|
|
63
|
+
: undefined;
|
|
64
|
+
const record = error && typeof error === "object" ? (error as Record<string, unknown>) : {};
|
|
65
|
+
const code =
|
|
66
|
+
typeof record.code === "string" && ERROR_CODES.has(record.code) ? record.code : fallbackCode;
|
|
67
|
+
const message = typeof record.message === "string" ? record.message : undefined;
|
|
68
|
+
const details =
|
|
69
|
+
record.details && typeof record.details === "object"
|
|
70
|
+
? (record.details as Record<string, unknown>)
|
|
71
|
+
: undefined;
|
|
72
|
+
const detailCode =
|
|
73
|
+
typeof details?.code === "string" && DETAIL_CODE.test(details.code) ? details.code : null;
|
|
74
|
+
const controlCode =
|
|
75
|
+
typeof details?.controlFailureCode === "string" && CONTROL_CODE.test(details.controlFailureCode)
|
|
76
|
+
? `control:${details.controlFailureCode}`
|
|
77
|
+
: null;
|
|
78
|
+
return {
|
|
79
|
+
code,
|
|
80
|
+
reason:
|
|
81
|
+
rejectionReasonFromMessage(message) ??
|
|
82
|
+
detailCode ??
|
|
83
|
+
controlCode ??
|
|
84
|
+
UNCLASSIFIED_REJECTION_REASON,
|
|
85
|
+
...(message ? { fingerprint: rejectionFingerprint(message) } : {}),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Classify a thrown error that escaped the central error handler. */
|
|
90
|
+
export function httpRejectionFactsFromError(
|
|
91
|
+
error: unknown,
|
|
92
|
+
fallbackCode: string,
|
|
93
|
+
): HttpRejectionFacts {
|
|
94
|
+
const message = error instanceof Error ? error.message : undefined;
|
|
95
|
+
return {
|
|
96
|
+
code: fallbackCode,
|
|
97
|
+
reason: rejectionReasonFromMessage(message) ?? UNCLASSIFIED_REJECTION_REASON,
|
|
98
|
+
...(message ? { fingerprint: rejectionFingerprint(message) } : {}),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Read a small JSON error body without consuming the response that is sent to
|
|
104
|
+
* the client. Streams, non-JSON bodies, and large bodies are never read.
|
|
105
|
+
*/
|
|
106
|
+
export async function readRejectionEnvelope(response: Response): Promise<unknown> {
|
|
107
|
+
if (!response.body) return undefined;
|
|
108
|
+
if (!/^application\/json(?:;|$)/i.test(response.headers.get("content-type") ?? "")) {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
const length = Number(response.headers.get("content-length") ?? "0");
|
|
112
|
+
if (Number.isFinite(length) && length > MAX_ENVELOPE_BYTES) return undefined;
|
|
113
|
+
try {
|
|
114
|
+
const text = await response.clone().text();
|
|
115
|
+
if (text.length > MAX_ENVELOPE_BYTES) return undefined;
|
|
116
|
+
return JSON.parse(text) as unknown;
|
|
117
|
+
} catch {
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type { Context } from "hono";
|
|
2
|
+
import type { z } from "zod";
|
|
3
|
+
import { ZodError } from "zod";
|
|
4
|
+
|
|
5
|
+
import { ApiHttpError } from "./api-error";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Validation failures produced by parsing a client-supplied request body.
|
|
9
|
+
*
|
|
10
|
+
* The same `ZodError` instance is rethrown so route-local handlers that
|
|
11
|
+
* already map `instanceof ZodError` keep their exact behavior; only the
|
|
12
|
+
* central `app.onError` consults this set to answer a client error instead of
|
|
13
|
+
* a 500. A ZodError that is NOT registered here (for example, a server-side
|
|
14
|
+
* response projection that no longer matches its contract) remains a genuine
|
|
15
|
+
* server fault.
|
|
16
|
+
*/
|
|
17
|
+
const requestBodyValidationErrors = new WeakSet<ZodError>();
|
|
18
|
+
/** Malformed-JSON failures raised by reading a client request body. */
|
|
19
|
+
const requestBodyJsonErrors = new WeakSet<SyntaxError>();
|
|
20
|
+
|
|
21
|
+
const MAX_REPORTED_ISSUES = 10;
|
|
22
|
+
|
|
23
|
+
/** Parse an already-read request body against its public request schema. */
|
|
24
|
+
export function parseRequestBody<Schema extends z.ZodType>(
|
|
25
|
+
schema: Schema,
|
|
26
|
+
body: unknown,
|
|
27
|
+
): z.output<Schema> {
|
|
28
|
+
const parsed = schema.safeParse(body);
|
|
29
|
+
if (parsed.success) return parsed.data;
|
|
30
|
+
requestBodyValidationErrors.add(parsed.error);
|
|
31
|
+
throw parsed.error;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Read the request body as JSON. Malformed JSON is a client error (400), never
|
|
36
|
+
* an unhandled `SyntaxError` 500. Other failures (body limit, aborted stream)
|
|
37
|
+
* keep their own mapping.
|
|
38
|
+
*/
|
|
39
|
+
export async function readRequestJson(c: Context): Promise<unknown> {
|
|
40
|
+
try {
|
|
41
|
+
return await c.req.json();
|
|
42
|
+
} catch (error) {
|
|
43
|
+
if (error instanceof SyntaxError) {
|
|
44
|
+
throw new ApiHttpError(400, {
|
|
45
|
+
code: "validation_failed",
|
|
46
|
+
message: "Request body must be valid JSON.",
|
|
47
|
+
retryable: false,
|
|
48
|
+
details: { code: "invalid_json" },
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Read and validate a JSON request body in one step. */
|
|
56
|
+
export async function parseRequestJson<Schema extends z.ZodType>(
|
|
57
|
+
c: Context,
|
|
58
|
+
schema: Schema,
|
|
59
|
+
): Promise<z.output<Schema>> {
|
|
60
|
+
return parseRequestBody(schema, await readRequestJson(c));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Tag malformed-JSON failures of `c.req.json()` for this request. The same
|
|
65
|
+
* `SyntaxError` instance is rethrown so routes that already catch it keep
|
|
66
|
+
* their own mapping; an uncaught one becomes a 400 in `app.onError` instead of
|
|
67
|
+
* a 500. A `SyntaxError` from parsing stored or upstream data is never tagged.
|
|
68
|
+
*/
|
|
69
|
+
export function tagRequestJsonParseErrors(c: Context): void {
|
|
70
|
+
const original = c.req.json.bind(c.req);
|
|
71
|
+
const tagged = async () => {
|
|
72
|
+
try {
|
|
73
|
+
return await original();
|
|
74
|
+
} catch (error) {
|
|
75
|
+
if (error instanceof SyntaxError) requestBodyJsonErrors.add(error);
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
(c.req as { json: typeof tagged }).json = tagged;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function isRequestBodyValidationError(error: unknown): error is ZodError | SyntaxError {
|
|
83
|
+
return requestBodyInputError(error) !== null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Walk a bounded cause chain for a tagged client-input failure. */
|
|
87
|
+
function requestBodyInputError(error: unknown): ZodError | SyntaxError | null {
|
|
88
|
+
let current: unknown = error;
|
|
89
|
+
for (let depth = 0; depth < 5 && current instanceof Error; depth += 1) {
|
|
90
|
+
if (current instanceof ZodError && requestBodyValidationErrors.has(current)) return current;
|
|
91
|
+
if (current instanceof SyntaxError && requestBodyJsonErrors.has(current)) return current;
|
|
92
|
+
current = current.cause;
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Public 400 for a registered request-body validation failure, naming every
|
|
99
|
+
* offending field (bounded) so API callers can correct the request.
|
|
100
|
+
*/
|
|
101
|
+
export function requestBodyValidationHttpError(error: unknown): ApiHttpError | null {
|
|
102
|
+
const inputError = requestBodyInputError(error);
|
|
103
|
+
if (!inputError) return null;
|
|
104
|
+
if (inputError instanceof SyntaxError) {
|
|
105
|
+
return new ApiHttpError(400, {
|
|
106
|
+
code: "validation_failed",
|
|
107
|
+
message: "Request body must be valid JSON.",
|
|
108
|
+
retryable: false,
|
|
109
|
+
details: { code: "invalid_json" },
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
const zodError = inputError;
|
|
113
|
+
const issues = zodError.issues.slice(0, MAX_REPORTED_ISSUES).map((issue) => ({
|
|
114
|
+
path: issue.path.map(String).join("."),
|
|
115
|
+
message: issue.message,
|
|
116
|
+
}));
|
|
117
|
+
const summary = issues.map((issue) => `${issue.path || "request"}: ${issue.message}`).join("; ");
|
|
118
|
+
const omittedIssueCount = Math.max(0, zodError.issues.length - issues.length);
|
|
119
|
+
return new ApiHttpError(400, {
|
|
120
|
+
code: "validation_failed",
|
|
121
|
+
message: `Invalid request body: ${summary}${
|
|
122
|
+
omittedIssueCount > 0 ? `; and ${omittedIssueCount} more` : ""
|
|
123
|
+
}`,
|
|
124
|
+
retryable: false,
|
|
125
|
+
details: { code: "invalid_request_body", issues, omittedIssueCount },
|
|
126
|
+
});
|
|
127
|
+
}
|
|
@@ -1,11 +1,28 @@
|
|
|
1
|
+
import { BlockList, isIP } from "node:net";
|
|
2
|
+
import { trustedProxyCidrEntries, type Settings } from "@opengeni/config";
|
|
1
3
|
import type { Context } from "hono";
|
|
2
4
|
|
|
3
5
|
const TRANSPORT_PEER_ADDRESS_BINDING = "opengeniTransportPeerAddress";
|
|
4
6
|
|
|
7
|
+
/** Returned when the runtime supplied no transport peer (for example an
|
|
8
|
+
* in-process test request). Callers treat it as one shared bucket. */
|
|
9
|
+
export const UNKNOWN_REQUEST_SOURCE_ADDRESS = "unknown";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* App-owned request header carrying the server-resolved client address into
|
|
13
|
+
* Better Auth, which reads client addresses only from request headers. It is
|
|
14
|
+
* removed from every inbound request and re-stamped on managed-auth routes, so
|
|
15
|
+
* a caller-supplied value never reaches the auth rate limiter or session IP.
|
|
16
|
+
*/
|
|
17
|
+
export const TRUSTED_CLIENT_ADDRESS_HEADER = "x-opengeni-trusted-client-address";
|
|
18
|
+
|
|
5
19
|
type ApiRequestBindings = {
|
|
6
20
|
[TRANSPORT_PEER_ADDRESS_BINDING]?: string | null;
|
|
7
21
|
};
|
|
8
22
|
|
|
23
|
+
/** The deployment's declared proxy chain (`OPENGENI_API_TRUSTED_PROXY_*`). */
|
|
24
|
+
export type RequestSourceTrust = Pick<Settings, "apiTrustedProxyHops" | "apiTrustedProxyCidrs">;
|
|
25
|
+
|
|
9
26
|
export function apiRequestBindingsForTransportPeer(
|
|
10
27
|
address: string | null | undefined,
|
|
11
28
|
): ApiRequestBindings {
|
|
@@ -15,23 +32,147 @@ export function apiRequestBindingsForTransportPeer(
|
|
|
15
32
|
/**
|
|
16
33
|
* Resolve a quota/audit source from the server-owned transport peer. Forwarded
|
|
17
34
|
* values are considered only when the operator declares an exact trusted proxy
|
|
18
|
-
* hop count
|
|
19
|
-
*
|
|
35
|
+
* hop count (`OPENGENI_API_TRUSTED_PROXY_HOPS`) and, when
|
|
36
|
+
* `OPENGENI_API_TRUSTED_PROXY_CIDRS` is set, only for a transport peer inside
|
|
37
|
+
* those ranges. Each trusted proxy must append the address of the peer that
|
|
38
|
+
* connected to it, or overwrite the header with the original client address;
|
|
39
|
+
* the chain is then walked from the server side so caller-prepended values
|
|
40
|
+
* cannot replace the address observed by the trusted edge. A missing, short,
|
|
41
|
+
* or malformed chain falls back to the transport peer.
|
|
20
42
|
*/
|
|
21
|
-
export function trustedRequestSourceAddress(c: Context,
|
|
43
|
+
export function trustedRequestSourceAddress(c: Context, trust: RequestSourceTrust): string {
|
|
22
44
|
const bindings = c.env as ApiRequestBindings | undefined;
|
|
23
|
-
const peer =
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
.
|
|
28
|
-
|
|
29
|
-
|
|
45
|
+
const peer =
|
|
46
|
+
normalizedAddress(bindings?.[TRANSPORT_PEER_ADDRESS_BINDING]) ?? UNKNOWN_REQUEST_SOURCE_ADDRESS;
|
|
47
|
+
const trustedProxyHops = trust.apiTrustedProxyHops;
|
|
48
|
+
if (
|
|
49
|
+
!Number.isInteger(trustedProxyHops) ||
|
|
50
|
+
trustedProxyHops <= 0 ||
|
|
51
|
+
peer === UNKNOWN_REQUEST_SOURCE_ADDRESS ||
|
|
52
|
+
!peerIsTrustedProxy(peer, trust.apiTrustedProxyCidrs)
|
|
53
|
+
) {
|
|
54
|
+
return peer;
|
|
55
|
+
}
|
|
56
|
+
const header = c.req.header("x-forwarded-for");
|
|
57
|
+
if (!header) return peer;
|
|
58
|
+
// Entries are never filtered: dropping a malformed value would shift the
|
|
59
|
+
// server-side position and could promote a caller-prepended value.
|
|
60
|
+
const forwarded = header.split(",");
|
|
30
61
|
const sourceIndex = forwarded.length - trustedProxyHops;
|
|
31
|
-
|
|
62
|
+
if (sourceIndex < 0) return peer;
|
|
63
|
+
return normalizedAddress(forwarded[sourceIndex]) ?? peer;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Rate-limit key for a trusted source address. An IPv6 address keys on its
|
|
68
|
+
* /64, the block one subscriber normally holds and the granularity Better
|
|
69
|
+
* Auth applies to the same stamped address, so rotating addresses inside one
|
|
70
|
+
* /64 cannot mint fresh buckets. An IPv4-mapped IPv6 address (how a
|
|
71
|
+
* dual-stack listener reports an IPv4 peer) keys on the embedded IPv4 address
|
|
72
|
+
* instead of collapsing every IPv4 client into one /64. Anything else,
|
|
73
|
+
* including {@link UNKNOWN_REQUEST_SOURCE_ADDRESS}, is returned unchanged.
|
|
74
|
+
*/
|
|
75
|
+
export function requestSourceRateLimitKey(address: string): string {
|
|
76
|
+
if (isIP(address) !== 6) return address;
|
|
77
|
+
const groups = ipv6Groups(address);
|
|
78
|
+
if (!groups) return address;
|
|
79
|
+
if (groups.slice(0, 5).every((group) => group === 0) && groups[5] === 0xffff) {
|
|
80
|
+
const high = groups[6]!;
|
|
81
|
+
const low = groups[7]!;
|
|
82
|
+
return `${high >> 8}.${high & 0xff}.${low >> 8}.${low & 0xff}`;
|
|
83
|
+
}
|
|
84
|
+
return `${groups
|
|
85
|
+
.slice(0, 4)
|
|
86
|
+
.map((group) => group.toString(16))
|
|
87
|
+
.join(":")}::/64`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** {@link trustedRequestSourceAddress} as an application rate-limit key. */
|
|
91
|
+
export function trustedRequestSourceRateLimitKey(c: Context, trust: RequestSourceTrust): string {
|
|
92
|
+
return requestSourceRateLimitKey(trustedRequestSourceAddress(c, trust));
|
|
32
93
|
}
|
|
33
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Replace any caller-supplied {@link TRUSTED_CLIENT_ADDRESS_HEADER}. When
|
|
97
|
+
* `stamp` is true the header is set to the trusted source address, so every
|
|
98
|
+
* Better Auth request derived from this request (direct handler calls,
|
|
99
|
+
* isolated provider requests, and `auth.api.*` calls built from its headers)
|
|
100
|
+
* keys rate limits and session records on the real client.
|
|
101
|
+
*/
|
|
102
|
+
export function replaceTrustedClientAddressHeader(
|
|
103
|
+
c: Context,
|
|
104
|
+
trust: RequestSourceTrust,
|
|
105
|
+
stamp: boolean,
|
|
106
|
+
): void {
|
|
107
|
+
const headers = c.req.raw.headers;
|
|
108
|
+
headers.delete(TRUSTED_CLIENT_ADDRESS_HEADER);
|
|
109
|
+
if (!stamp) return;
|
|
110
|
+
const address = trustedRequestSourceAddress(c, trust);
|
|
111
|
+
if (address !== UNKNOWN_REQUEST_SOURCE_ADDRESS) {
|
|
112
|
+
headers.set(TRUSTED_CLIENT_ADDRESS_HEADER, address);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const trustedProxyRanges = new Map<string, BlockList | null>();
|
|
117
|
+
|
|
118
|
+
function peerIsTrustedProxy(peer: string, cidrs: string): boolean {
|
|
119
|
+
let ranges = trustedProxyRanges.get(cidrs);
|
|
120
|
+
if (ranges === undefined) {
|
|
121
|
+
// Settings validation already rejected malformed entries at boot.
|
|
122
|
+
const entries = trustedProxyCidrEntries(cidrs);
|
|
123
|
+
ranges = entries.length === 0 ? null : new BlockList();
|
|
124
|
+
for (const entry of entries) ranges?.addSubnet(entry.address, entry.prefix, entry.family);
|
|
125
|
+
trustedProxyRanges.set(cidrs, ranges);
|
|
126
|
+
}
|
|
127
|
+
if (!ranges) return true;
|
|
128
|
+
return ranges.check(peer, isIP(peer) === 6 ? "ipv6" : "ipv4");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Canonical IP text, or null. Accepts the `ipv4:port` and `[ipv6]:port` forms
|
|
133
|
+
* some load balancers write into X-Forwarded-For, and drops IPv6 zone ids.
|
|
134
|
+
*/
|
|
34
135
|
function normalizedAddress(value: string | null | undefined): string | null {
|
|
35
|
-
|
|
36
|
-
|
|
136
|
+
let candidate = value?.trim();
|
|
137
|
+
if (!candidate || candidate.length > 64) return null;
|
|
138
|
+
const bracketed = /^\[([^\]]+)\](?::\d{1,5})?$/u.exec(candidate);
|
|
139
|
+
if (bracketed) {
|
|
140
|
+
candidate = bracketed[1]!;
|
|
141
|
+
} else {
|
|
142
|
+
const ipv4WithPort = /^(\d{1,3}(?:\.\d{1,3}){3}):\d{1,5}$/u.exec(candidate);
|
|
143
|
+
if (ipv4WithPort) candidate = ipv4WithPort[1]!;
|
|
144
|
+
}
|
|
145
|
+
const zone = candidate.indexOf("%");
|
|
146
|
+
if (zone >= 0) candidate = candidate.slice(0, zone);
|
|
147
|
+
const family = isIP(candidate);
|
|
148
|
+
if (family === 4) return candidate;
|
|
149
|
+
if (family === 6) return candidate.toLowerCase();
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** The eight 16-bit groups of a valid IPv6 address, or null. */
|
|
154
|
+
function ipv6Groups(address: string): number[] | null {
|
|
155
|
+
let text = address;
|
|
156
|
+
const tail: number[] = [];
|
|
157
|
+
const lastColon = text.lastIndexOf(":");
|
|
158
|
+
const last = text.slice(lastColon + 1);
|
|
159
|
+
if (last.includes(".")) {
|
|
160
|
+
const octets = last.split(".").map(Number);
|
|
161
|
+
if (octets.length !== 4 || octets.some((octet) => !Number.isInteger(octet))) return null;
|
|
162
|
+
tail.push((octets[0]! << 8) | octets[1]!, (octets[2]! << 8) | octets[3]!);
|
|
163
|
+
text = text.slice(0, lastColon + 1);
|
|
164
|
+
if (!text.endsWith("::")) text = text.slice(0, -1);
|
|
165
|
+
}
|
|
166
|
+
const halves = text.split("::");
|
|
167
|
+
if (halves.length > 2) return null;
|
|
168
|
+
const parse = (part: string): number[] =>
|
|
169
|
+
part ? part.split(":").map((group) => Number.parseInt(group, 16)) : [];
|
|
170
|
+
const head = parse(halves[0]!);
|
|
171
|
+
const rest = halves.length === 2 ? parse(halves[1]!) : [];
|
|
172
|
+
const fill = halves.length === 2 ? 8 - head.length - rest.length - tail.length : 0;
|
|
173
|
+
if (fill < 0) return null;
|
|
174
|
+
const groups = [...head, ...Array.from({ length: fill }, () => 0), ...rest, ...tail];
|
|
175
|
+
if (groups.length !== 8) return null;
|
|
176
|
+
if (groups.some((group) => !Number.isInteger(group) || group < 0 || group > 0xffff)) return null;
|
|
177
|
+
return groups;
|
|
37
178
|
}
|
package/src/http/sse.ts
CHANGED
|
@@ -5,8 +5,9 @@ import {
|
|
|
5
5
|
} from "@opengeni/contracts";
|
|
6
6
|
import {
|
|
7
7
|
getWorkspaceInteractionRevisionState,
|
|
8
|
-
|
|
8
|
+
listSessionEventPage,
|
|
9
9
|
listWorkspaceControlEvents,
|
|
10
|
+
readSessionEventStorageGapEnd,
|
|
10
11
|
type Database,
|
|
11
12
|
} from "@opengeni/db";
|
|
12
13
|
import {
|
|
@@ -21,6 +22,7 @@ import type { Observability } from "@opengeni/observability";
|
|
|
21
22
|
import { MANAGED_AUTH_ACTOR_EPOCH_HEADER } from "@opengeni/core/managed-auth-session-sets";
|
|
22
23
|
|
|
23
24
|
const SESSION_REPLAY_PAGE_SIZE = 100;
|
|
25
|
+
const SESSION_REPLAY_PAGE_MAX_BYTES = 8 * 1024 * 1024;
|
|
24
26
|
const WORKSPACE_CONTROL_REPLAY_PAGE_SIZE = 100;
|
|
25
27
|
export const SSE_QUEUED_FRAME_MAX_COUNT = 1;
|
|
26
28
|
export const SSE_WRITE_STALL_TIMEOUT_MS = 30_000;
|
|
@@ -149,12 +151,12 @@ export function createByteBoundedSseStream(
|
|
|
149
151
|
return {
|
|
150
152
|
stream,
|
|
151
153
|
write: async (frame) => {
|
|
152
|
-
const chunk = encoder.encode(frame);
|
|
153
154
|
for (;;) {
|
|
154
155
|
if (stopped) return false;
|
|
155
156
|
const desired = controller.desiredSize;
|
|
156
157
|
if (desired === null) return false;
|
|
157
158
|
if (desired >= 1 && queuedFrames === 0) {
|
|
159
|
+
const chunk = encoder.encode(frame);
|
|
158
160
|
controller.enqueue(chunk);
|
|
159
161
|
queuedFrames = 1;
|
|
160
162
|
queuedBytes = chunk.byteLength;
|
|
@@ -267,6 +269,32 @@ export function createLatestWinsDelivery<T extends { sequence: number }>(
|
|
|
267
269
|
};
|
|
268
270
|
}
|
|
269
271
|
|
|
272
|
+
/**
|
|
273
|
+
* Durable coverage for each projected frame. Storage may omit sequences on
|
|
274
|
+
* purpose (an archived session keeps only its readable timeline), and clients
|
|
275
|
+
* treat an uncovered jump as a gap to backfill. Within a page the next stored
|
|
276
|
+
* row bounds the gap; after the last row, `gapEnd` (when given) reads the
|
|
277
|
+
* race-free bound from storage.
|
|
278
|
+
*/
|
|
279
|
+
export async function coverStorageGaps(
|
|
280
|
+
projection: { events: SessionEvent[]; coveredThroughBySequence: ReadonlyMap<number, number> },
|
|
281
|
+
gapEnd: ((through: number) => Promise<number>) | null,
|
|
282
|
+
): Promise<Map<number, number>> {
|
|
283
|
+
const coverage = new Map<number, number>();
|
|
284
|
+
const events = projection.events;
|
|
285
|
+
for (const [index, event] of events.entries()) {
|
|
286
|
+
const own = projection.coveredThroughBySequence.get(event.sequence) ?? event.sequence;
|
|
287
|
+
const next = events[index + 1];
|
|
288
|
+
coverage.set(event.sequence, next ? Math.max(own, next.sequence - 1) : own);
|
|
289
|
+
}
|
|
290
|
+
const last = events.at(-1);
|
|
291
|
+
if (last && gapEnd) {
|
|
292
|
+
const own = coverage.get(last.sequence)!;
|
|
293
|
+
coverage.set(last.sequence, Math.max(own, await gapEnd(own)));
|
|
294
|
+
}
|
|
295
|
+
return coverage;
|
|
296
|
+
}
|
|
297
|
+
|
|
270
298
|
export async function sseSessionStream(
|
|
271
299
|
db: Database,
|
|
272
300
|
bus: EventBus,
|
|
@@ -277,18 +305,19 @@ export async function sseSessionStream(
|
|
|
277
305
|
options: SessionSseDeliveryOptions = {},
|
|
278
306
|
): Promise<Response> {
|
|
279
307
|
if (isHttp1BrowserBatch(options)) {
|
|
280
|
-
const events = await
|
|
308
|
+
const { events } = await listSessionEventPage(db, workspaceId, sessionId, {
|
|
281
309
|
after,
|
|
282
310
|
limit: SESSION_REPLAY_PAGE_SIZE,
|
|
311
|
+
maxBytes: options.finiteResponseMaxBytes ?? HTTP1_BROWSER_SSE_BATCH_MAX_BYTES,
|
|
283
312
|
});
|
|
284
313
|
await options.reauthorize?.();
|
|
285
314
|
const compactProjection = coalesceSessionEventDeltasWithCoverage(events);
|
|
315
|
+
const coverage = await coverStorageGaps(compactProjection, async (through) =>
|
|
316
|
+
readSessionEventStorageGapEnd(db, workspaceId, sessionId, through),
|
|
317
|
+
);
|
|
286
318
|
return finiteSseBatchResponse(
|
|
287
319
|
compactProjection.events.map((event) =>
|
|
288
|
-
formatSessionEventSse(
|
|
289
|
-
event,
|
|
290
|
-
compactProjection.coveredThroughBySequence.get(event.sequence) ?? event.sequence,
|
|
291
|
-
),
|
|
320
|
+
formatSessionEventSse(event, coverage.get(event.sequence) ?? event.sequence),
|
|
292
321
|
),
|
|
293
322
|
options,
|
|
294
323
|
);
|
|
@@ -353,10 +382,12 @@ export async function sseSessionStream(
|
|
|
353
382
|
targetSequence === undefined
|
|
354
383
|
? SESSION_REPLAY_PAGE_SIZE
|
|
355
384
|
: Math.min(SESSION_REPLAY_PAGE_SIZE, targetSequence - lastSent);
|
|
356
|
-
const
|
|
385
|
+
const replayPage = await listSessionEventPage(db, workspaceId, sessionId, {
|
|
357
386
|
after: lastSent,
|
|
358
387
|
limit,
|
|
388
|
+
maxBytes: SESSION_REPLAY_PAGE_MAX_BYTES,
|
|
359
389
|
});
|
|
390
|
+
const page = replayPage.events;
|
|
360
391
|
const eligible =
|
|
361
392
|
targetSequence === undefined
|
|
362
393
|
? page
|
|
@@ -372,9 +403,16 @@ export async function sseSessionStream(
|
|
|
372
403
|
// a long answer cannot create thousands of React renders and starve
|
|
373
404
|
// command acknowledgements behind its own token stream.
|
|
374
405
|
const compactProjection = coalesceSessionEventDeltasWithCoverage(eligible);
|
|
406
|
+
// Live delivery stops exactly at a published sequence; replay pages also
|
|
407
|
+
// cover what storage intentionally omits after their last event.
|
|
408
|
+
const coverage = await coverStorageGaps(
|
|
409
|
+
compactProjection,
|
|
410
|
+
targetSequence === undefined
|
|
411
|
+
? async (through) => readSessionEventStorageGapEnd(db, workspaceId, sessionId, through)
|
|
412
|
+
: null,
|
|
413
|
+
);
|
|
375
414
|
for (const projected of compactProjection.events) {
|
|
376
|
-
const coveredThrough =
|
|
377
|
-
compactProjection.coveredThroughBySequence.get(projected.sequence) ?? projected.sequence;
|
|
415
|
+
const coveredThrough = coverage.get(projected.sequence) ?? projected.sequence;
|
|
378
416
|
await writeFrame(formatSessionEventSse(projected, coveredThrough));
|
|
379
417
|
lastSent = coveredThrough;
|
|
380
418
|
}
|
|
@@ -382,8 +420,10 @@ export async function sseSessionStream(
|
|
|
382
420
|
throw new Error(`Session event replay made no progress after sequence ${lastSent}`);
|
|
383
421
|
}
|
|
384
422
|
if (targetSequence !== undefined && lastSent >= targetSequence) return;
|
|
423
|
+
if (targetSequence === undefined && !replayPage.hasMore) return;
|
|
385
424
|
// A byte-selected page may contain fewer rows than requested, especially
|
|
386
|
-
// when one large message travels alone.
|
|
425
|
+
// when one large message travels alone. Use explicit continuation rather
|
|
426
|
+
// than page length; live publication covers appends after the snapshot.
|
|
387
427
|
}
|
|
388
428
|
};
|
|
389
429
|
let durableDeliveryTail = Promise.resolve();
|
|
@@ -448,17 +488,28 @@ export async function sseSessionStream(
|
|
|
448
488
|
stopReconnectObservation = durableFanout.subscribeRecovery(scheduleReconnectReconciliation);
|
|
449
489
|
|
|
450
490
|
void (async () => {
|
|
451
|
-
const release = await bus.subscribe(
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
491
|
+
const release = await bus.subscribe(
|
|
492
|
+
workspaceId,
|
|
493
|
+
sessionId,
|
|
494
|
+
(events) => {
|
|
495
|
+
if (bootstrapping) {
|
|
496
|
+
for (const event of events) {
|
|
497
|
+
if (!newestBuffered || event.sequence > newestBuffered.sequence) {
|
|
498
|
+
newestBuffered = event;
|
|
499
|
+
}
|
|
456
500
|
}
|
|
501
|
+
} else {
|
|
502
|
+
delivery?.publish(events);
|
|
457
503
|
}
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
// Live fanout stopped while this stream still held it. Heartbeats alone
|
|
507
|
+
// would keep a silently stale timeline open; fail retryably so the
|
|
508
|
+
// client reconnects and replays from the durable Postgres cursor.
|
|
509
|
+
onTerminated: (error) =>
|
|
510
|
+
fail(retryableSseFailure("session live fanout subscription ended", error)),
|
|
511
|
+
},
|
|
512
|
+
);
|
|
462
513
|
if (channel.stopped()) {
|
|
463
514
|
release();
|
|
464
515
|
return;
|
|
@@ -632,13 +683,22 @@ export async function sseWorkspaceControlStream(
|
|
|
632
683
|
delivery = createLatestWinsDelivery(send, fail);
|
|
633
684
|
|
|
634
685
|
void (async () => {
|
|
635
|
-
const release = await bus.subscribeWorkspaceControl(
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
686
|
+
const release = await bus.subscribeWorkspaceControl(
|
|
687
|
+
workspaceId,
|
|
688
|
+
(event) => {
|
|
689
|
+
if (bootstrapping) {
|
|
690
|
+
if (!newestBuffered || event.sequence > newestBuffered.sequence) newestBuffered = event;
|
|
691
|
+
} else {
|
|
692
|
+
delivery?.publish([event]);
|
|
693
|
+
}
|
|
694
|
+
},
|
|
695
|
+
{
|
|
696
|
+
// Same contract as session SSE: a dead live subscription must reconnect
|
|
697
|
+
// and replay the durable control cursor, not idle on heartbeats.
|
|
698
|
+
onTerminated: (error) =>
|
|
699
|
+
fail(retryableSseFailure("workspace control live subscription ended", error)),
|
|
700
|
+
},
|
|
701
|
+
);
|
|
642
702
|
if (channel.stopped()) {
|
|
643
703
|
release();
|
|
644
704
|
return;
|
|
@@ -678,7 +738,7 @@ export type WorkspaceInteractionSseOptions = SseDeliveryOptions & {
|
|
|
678
738
|
|
|
679
739
|
/**
|
|
680
740
|
* One HTTP connection for the two workspace-wide invalidation domains used by
|
|
681
|
-
* every visible
|
|
741
|
+
* every visible Opengeni surface. Keeping these as separate HTTP/1 streams
|
|
682
742
|
* consumes all six per-origin browser connections with only two windows and
|
|
683
743
|
* starves ordinary mutations/terminal grants. The durable cursors remain
|
|
684
744
|
* independent; this function only multiplexes their already-bounded SSE frames.
|