@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,831 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { isIP } from "node:net";
|
|
3
|
+
import {
|
|
4
|
+
parseSiteHostname,
|
|
5
|
+
siteHostname,
|
|
6
|
+
SITE_NOT_AVAILABLE_FOR_VIEWER_ERROR_CODE,
|
|
7
|
+
type Permission,
|
|
8
|
+
type SiteLimitProfileKind,
|
|
9
|
+
type SiteLimits,
|
|
10
|
+
type SiteViewerCapabilities,
|
|
11
|
+
} from "@opengeni/contracts";
|
|
12
|
+
import { sessionUsageLimitsProducerEnabled, type ApiRouteDeps } from "@opengeni/core";
|
|
13
|
+
import {
|
|
14
|
+
consumeSiteRuntimeRate,
|
|
15
|
+
findSiteHostname,
|
|
16
|
+
findSiteViewerSession,
|
|
17
|
+
getPublishedSite,
|
|
18
|
+
getSession,
|
|
19
|
+
getSitePublication,
|
|
20
|
+
isSiteWorkspaceMember,
|
|
21
|
+
claimSiteRuntimeReservationSweep,
|
|
22
|
+
readSessionUsageLimit,
|
|
23
|
+
recordUsageEvent,
|
|
24
|
+
releaseSiteRuntimeConversationStart,
|
|
25
|
+
setSessionUsageLimitInTransaction,
|
|
26
|
+
siteRuntimeFundedLimitInTransaction,
|
|
27
|
+
settleSiteRuntimeConversationSpend,
|
|
28
|
+
SITE_RUNTIME_ABANDONED_START_MINUTES,
|
|
29
|
+
SITE_RUNTIME_MEMBERS_PRINCIPAL,
|
|
30
|
+
SITE_RUNTIME_SITE_PRINCIPAL,
|
|
31
|
+
withRlsContext,
|
|
32
|
+
withWorkspaceSessionActivityRls,
|
|
33
|
+
type PublishedSiteRecord,
|
|
34
|
+
type SitePublicationRecord,
|
|
35
|
+
type SitePublishingScope,
|
|
36
|
+
type SiteRuntimeBudget,
|
|
37
|
+
type SiteRuntimeBudgetLimits,
|
|
38
|
+
type SiteRuntimeConversationRecord,
|
|
39
|
+
type SiteRuntimeRateBucket,
|
|
40
|
+
type SiteRuntimeReservation,
|
|
41
|
+
} from "@opengeni/db";
|
|
42
|
+
import type { SiteGatewayFields } from "./gateway-signature";
|
|
43
|
+
import { effectiveSiteLimits, siteViewerLimits, sourceSiteLimitProfile } from "./limits";
|
|
44
|
+
import {
|
|
45
|
+
siteLimitInputs,
|
|
46
|
+
sitePublishingError,
|
|
47
|
+
siteServable,
|
|
48
|
+
siteServiceSessionRequest,
|
|
49
|
+
SITE_RUNTIME_AGENT_PERMISSIONS,
|
|
50
|
+
} from "./service";
|
|
51
|
+
import {
|
|
52
|
+
effectiveSiteAccess,
|
|
53
|
+
signedInSiteViewerKind,
|
|
54
|
+
siteGuestsAllowed,
|
|
55
|
+
SITE_VISITOR_ID,
|
|
56
|
+
type SiteRuntimeViewer,
|
|
57
|
+
} from "./viewer";
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Site-runtime: the narrow API a Site gateway calls for one published Site
|
|
61
|
+
* host, after the gateway signature passed. Everything here works for exactly
|
|
62
|
+
* one Site: the host names it, the viewer is resolved for it, and the agent
|
|
63
|
+
* principal is `site:<siteId>`.
|
|
64
|
+
*/
|
|
65
|
+
|
|
66
|
+
export type SiteRuntimeDeps = Pick<ApiRouteDeps, "db" | "settings" | "objectStorage">;
|
|
67
|
+
|
|
68
|
+
export type SiteRuntimeHost = {
|
|
69
|
+
domain: string;
|
|
70
|
+
label: string;
|
|
71
|
+
hostname: string;
|
|
72
|
+
scope: SitePublishingScope;
|
|
73
|
+
publication: SitePublicationRecord;
|
|
74
|
+
site: PublishedSiteRecord;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/** The Site behind a host, whatever its status (null when the host names no Site). */
|
|
78
|
+
export async function findSiteRuntimeHost(
|
|
79
|
+
deps: SiteRuntimeDeps,
|
|
80
|
+
domain: string,
|
|
81
|
+
host: string,
|
|
82
|
+
): Promise<SiteRuntimeHost | null> {
|
|
83
|
+
// parseSiteHostname lowercases and drops a port and one trailing dot.
|
|
84
|
+
const label = parseSiteHostname(host, domain);
|
|
85
|
+
if (!label) return null;
|
|
86
|
+
const record = await findSiteHostname(deps.db, label);
|
|
87
|
+
if (!record || record.releasedAt !== null) return null;
|
|
88
|
+
const scope = { accountId: record.accountId, workspaceId: record.workspaceId };
|
|
89
|
+
const [publication, site] = await Promise.all([
|
|
90
|
+
getSitePublication(deps.db, scope, record.siteId),
|
|
91
|
+
getPublishedSite(deps.db, scope, record.siteId),
|
|
92
|
+
]);
|
|
93
|
+
if (!publication || !site || publication.label !== label) return null;
|
|
94
|
+
return { domain, label, hostname: siteHostname(label, domain), scope, publication, site };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** The Site behind a host when it is served: 404 for no Site, 410 when not served. */
|
|
98
|
+
export async function requireServedSiteRuntimeHost(
|
|
99
|
+
deps: SiteRuntimeDeps,
|
|
100
|
+
domain: string,
|
|
101
|
+
host: string,
|
|
102
|
+
): Promise<SiteRuntimeHost & { versionId: string }> {
|
|
103
|
+
const found = await findSiteRuntimeHost(deps, domain, host);
|
|
104
|
+
if (!found)
|
|
105
|
+
throw sitePublishingError(404, "not_found", "site_not_found", "This Site does not exist");
|
|
106
|
+
const { publication, site } = found;
|
|
107
|
+
if (publication.status !== "active" || !siteServable(site) || !site.currentVersionId)
|
|
108
|
+
throw sitePublishingError(410, "not_found", "site_unavailable", "This Site is not available", {
|
|
109
|
+
status: publication.status === "suspended" ? "suspended" : "unpublished",
|
|
110
|
+
});
|
|
111
|
+
return { ...found, versionId: site.currentVersionId };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function sha256Text(value: string): string {
|
|
115
|
+
return createHash("sha256").update(value, "utf8").digest("hex");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Who is viewing: a live site-session of this Site whose person the Site
|
|
120
|
+
* still admits (membership and invitations are re-read every call), else an
|
|
121
|
+
* anonymous guest of a public Site, else sign-in is required.
|
|
122
|
+
*
|
|
123
|
+
* "View as visitor": a workspace member whose site-session previews another
|
|
124
|
+
* role is resolved as exactly that role, and only ever downgraded: `public`
|
|
125
|
+
* is an anonymous guest (refused where guests are not admitted), `invited`
|
|
126
|
+
* a signed-in person outside the workspace (refused on members-only Sites).
|
|
127
|
+
* A preview role on the session of anyone who is not a member is ignored, so
|
|
128
|
+
* it can never grant more than the person already has. `ignoreViewAs`
|
|
129
|
+
* resolves the real viewer (to start or end a preview).
|
|
130
|
+
*/
|
|
131
|
+
export async function resolveSiteRuntimeViewer(
|
|
132
|
+
deps: SiteRuntimeDeps,
|
|
133
|
+
host: SiteRuntimeHost,
|
|
134
|
+
fields: SiteGatewayFields,
|
|
135
|
+
options: { ignoreViewAs?: boolean } = {},
|
|
136
|
+
): Promise<SiteRuntimeViewer> {
|
|
137
|
+
const { scope, publication, site } = host;
|
|
138
|
+
const denied = () =>
|
|
139
|
+
sitePublishingError(
|
|
140
|
+
403,
|
|
141
|
+
"forbidden",
|
|
142
|
+
"site_access_denied",
|
|
143
|
+
"You do not have access to this Site",
|
|
144
|
+
);
|
|
145
|
+
const access = effectiveSiteAccess(publication.access, deps.settings);
|
|
146
|
+
if (fields.session) {
|
|
147
|
+
const session = await findSiteViewerSession(deps.db, scope, {
|
|
148
|
+
siteId: site.id,
|
|
149
|
+
tokenHash: sha256Text(fields.session),
|
|
150
|
+
idleSeconds: deps.settings.sitesViewerSessionIdleSeconds,
|
|
151
|
+
});
|
|
152
|
+
if (session) {
|
|
153
|
+
const member = await isSiteWorkspaceMember(deps.db, scope, session.subjectId);
|
|
154
|
+
const kind = signedInSiteViewerKind({
|
|
155
|
+
access,
|
|
156
|
+
member,
|
|
157
|
+
email: session.email,
|
|
158
|
+
invitedEmails: publication.invitedEmails,
|
|
159
|
+
});
|
|
160
|
+
if (!kind) throw denied();
|
|
161
|
+
const viewAs = !options.ignoreViewAs && kind === "member" ? (session.viewAs ?? null) : null;
|
|
162
|
+
if (viewAs === "public") {
|
|
163
|
+
if (
|
|
164
|
+
!siteGuestsAllowed(publication.access, deps.settings) ||
|
|
165
|
+
!SITE_VISITOR_ID.test(fields.visitor)
|
|
166
|
+
)
|
|
167
|
+
throw denied();
|
|
168
|
+
return {
|
|
169
|
+
kind: "guest",
|
|
170
|
+
principal: `guest:${fields.visitor}`,
|
|
171
|
+
subjectId: null,
|
|
172
|
+
name: null,
|
|
173
|
+
email: null,
|
|
174
|
+
sessionId: null,
|
|
175
|
+
viewAs,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
if (viewAs === "invited") {
|
|
179
|
+
if (access === "members") throw denied();
|
|
180
|
+
return {
|
|
181
|
+
kind: "invited",
|
|
182
|
+
principal: `user:${session.subjectId}`,
|
|
183
|
+
subjectId: session.subjectId,
|
|
184
|
+
name: session.displayName,
|
|
185
|
+
email: session.email,
|
|
186
|
+
sessionId: session.id,
|
|
187
|
+
viewAs,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
kind,
|
|
192
|
+
principal: `user:${session.subjectId}`,
|
|
193
|
+
subjectId: session.subjectId,
|
|
194
|
+
name: session.displayName,
|
|
195
|
+
email: session.email,
|
|
196
|
+
sessionId: session.id,
|
|
197
|
+
viewAs: null,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (siteGuestsAllowed(publication.access, deps.settings) && SITE_VISITOR_ID.test(fields.visitor))
|
|
202
|
+
return {
|
|
203
|
+
kind: "guest",
|
|
204
|
+
principal: `guest:${fields.visitor}`,
|
|
205
|
+
subjectId: null,
|
|
206
|
+
name: null,
|
|
207
|
+
email: null,
|
|
208
|
+
sessionId: null,
|
|
209
|
+
viewAs: null,
|
|
210
|
+
};
|
|
211
|
+
throw sitePublishingError(
|
|
212
|
+
401,
|
|
213
|
+
"unauthenticated",
|
|
214
|
+
"site_sign_in_required",
|
|
215
|
+
"Sign in to view this Site",
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** What a viewer cannot do on a Site: 403 with `details.code` `not_available_for_viewer`. */
|
|
220
|
+
export type SiteViewerFeature =
|
|
221
|
+
| "agent"
|
|
222
|
+
| "agent_model"
|
|
223
|
+
| "workspace_tools"
|
|
224
|
+
| "site_data"
|
|
225
|
+
| "site_sql";
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* The `details.code` each feature answered before `not_available_for_viewer`,
|
|
229
|
+
* retained as `details.legacyCode` for migration. Clients matching
|
|
230
|
+
* `details.code` must adopt the new code; the alias is not automatic compatibility.
|
|
231
|
+
*/
|
|
232
|
+
const SITE_VIEWER_FEATURE_LEGACY_CODES: Partial<Record<SiteViewerFeature, string>> = {
|
|
233
|
+
agent: "site_agents_unavailable",
|
|
234
|
+
workspace_tools: "site_tools_unavailable",
|
|
235
|
+
site_data: "forbidden",
|
|
236
|
+
site_sql: "forbidden",
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
export function siteNotAvailableForViewer(
|
|
240
|
+
feature: SiteViewerFeature,
|
|
241
|
+
message: string,
|
|
242
|
+
extra: Record<string, unknown> = {},
|
|
243
|
+
) {
|
|
244
|
+
const legacyCode = SITE_VIEWER_FEATURE_LEGACY_CODES[feature];
|
|
245
|
+
return sitePublishingError(403, "forbidden", SITE_NOT_AVAILABLE_FOR_VIEWER_ERROR_CODE, message, {
|
|
246
|
+
...extra,
|
|
247
|
+
feature,
|
|
248
|
+
...(legacyCode ? { legacyCode } : {}),
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** The viewer capabilities a page reads from `site.config` to hide what won't work. */
|
|
253
|
+
export function siteViewerCapabilities(
|
|
254
|
+
deps: SiteRuntimeDeps,
|
|
255
|
+
host: SiteRuntimeHost,
|
|
256
|
+
viewer: SiteRuntimeViewer,
|
|
257
|
+
limits: SiteLimits,
|
|
258
|
+
): SiteViewerCapabilities {
|
|
259
|
+
return {
|
|
260
|
+
agent: siteAgentRefusal(deps, host, viewer, limits) === null,
|
|
261
|
+
// The route intersects this with the immutable page declarations and live grant.
|
|
262
|
+
workspaceTools: viewer.kind === "member",
|
|
263
|
+
siteData: viewer.kind === "guest" ? "guest" : "viewer",
|
|
264
|
+
sqlQueryAudiences: viewer.kind === "guest" ? ["everyone"] : ["everyone", "signed_in"],
|
|
265
|
+
viewAs: viewer.viewAs ?? null,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export async function siteRuntimeLimits(
|
|
270
|
+
deps: SiteRuntimeDeps,
|
|
271
|
+
host: SiteRuntimeHost,
|
|
272
|
+
viewer?: Pick<SiteRuntimeViewer, "kind">,
|
|
273
|
+
): Promise<SiteLimits> {
|
|
274
|
+
return (await siteRuntimeLimitProfile(deps, host, viewer)).limits;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* The limits that govern a viewer and the kind of limit profile they come
|
|
279
|
+
* from: workspace members are never limited by Site limits (flood brakes
|
|
280
|
+
* only); everyone else gets the Site's limits (siteViewerLimits). Without a
|
|
281
|
+
* viewer: the Site's limits.
|
|
282
|
+
*/
|
|
283
|
+
export async function siteRuntimeLimitProfile(
|
|
284
|
+
deps: SiteRuntimeDeps,
|
|
285
|
+
host: SiteRuntimeHost,
|
|
286
|
+
viewer?: Pick<SiteRuntimeViewer, "kind">,
|
|
287
|
+
): Promise<{ limits: SiteLimits; kind: SiteLimitProfileKind }> {
|
|
288
|
+
const profiles = await siteLimitInputs(deps, host.scope, host.publication);
|
|
289
|
+
const limits = effectiveSiteLimits(deps.settings, profiles.site, profiles.workspace);
|
|
290
|
+
return {
|
|
291
|
+
limits: viewer ? siteViewerLimits(limits, viewer.kind) : limits,
|
|
292
|
+
kind: sourceSiteLimitProfile(profiles.site, profiles.workspace).kind,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** A guest's client address may carry this many visitors' worth of traffic. */
|
|
297
|
+
export const SITE_GUEST_IP_LIMIT_FACTOR = 4;
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* A viewer's per-minute bucket. Guests are also counted per signed client
|
|
301
|
+
* address (`ip:<address>`, `SITE_GUEST_IP_LIMIT_FACTOR` times the visitor
|
|
302
|
+
* limit), so minting new visitor ids does not reset their limits; both must
|
|
303
|
+
* pass.
|
|
304
|
+
*/
|
|
305
|
+
export async function enforceSiteRuntimeViewerRate(
|
|
306
|
+
deps: SiteRuntimeDeps,
|
|
307
|
+
host: SiteRuntimeHost,
|
|
308
|
+
viewer: SiteRuntimeViewer,
|
|
309
|
+
fields: Pick<SiteGatewayFields, "clientIp">,
|
|
310
|
+
bucket: SiteRuntimeRateBucket,
|
|
311
|
+
limit: number | null,
|
|
312
|
+
): Promise<void> {
|
|
313
|
+
await enforceSiteRuntimeRate(deps, host, viewer.principal, bucket, limit);
|
|
314
|
+
if (viewer.kind === "guest" && limit !== null)
|
|
315
|
+
await enforceSiteRuntimeRate(
|
|
316
|
+
deps,
|
|
317
|
+
host,
|
|
318
|
+
siteClientAddressPrincipal(fields),
|
|
319
|
+
bucket,
|
|
320
|
+
limit * SITE_GUEST_IP_LIMIT_FACTOR,
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* The rate principal of the signed client address (`ip:unknown` when the
|
|
326
|
+
* gateway sent none). IPv6 addresses count per /64 (one subscriber usually
|
|
327
|
+
* holds a whole /64, so per-address keys would be free to rotate);
|
|
328
|
+
* IPv4-mapped IPv6 counts as its IPv4 address.
|
|
329
|
+
*/
|
|
330
|
+
export function siteClientAddressPrincipal(fields: Pick<SiteGatewayFields, "clientIp">): string {
|
|
331
|
+
return `ip:${siteClientAddressKey(fields.clientIp)}`;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/** A client address as a rate key: IPv4 as is, IPv6 as its /64 prefix. */
|
|
335
|
+
export function siteClientAddressKey(raw: string): string {
|
|
336
|
+
const value = raw
|
|
337
|
+
.trim()
|
|
338
|
+
.toLowerCase()
|
|
339
|
+
.replace(/^\[(.*)\]$/u, "$1")
|
|
340
|
+
.replace(/%.*$/u, "");
|
|
341
|
+
if (!value) return "unknown";
|
|
342
|
+
const family = isIP(value);
|
|
343
|
+
if (family !== 6) return value.slice(0, 64);
|
|
344
|
+
const halves = value.split("::");
|
|
345
|
+
const groups = (part: string | undefined) => (part ? part.split(":") : []);
|
|
346
|
+
const head = groups(halves[0]);
|
|
347
|
+
const tail = halves.length === 2 ? groups(halves[1]) : [];
|
|
348
|
+
// An embedded IPv4 tail is two hextets.
|
|
349
|
+
const last = halves.length === 2 ? tail : head;
|
|
350
|
+
const dotted = last.at(-1);
|
|
351
|
+
if (dotted?.includes(".")) {
|
|
352
|
+
const octets = dotted.split(".").map(Number);
|
|
353
|
+
last.splice(
|
|
354
|
+
-1,
|
|
355
|
+
1,
|
|
356
|
+
((octets[0]! << 8) | octets[1]!).toString(16),
|
|
357
|
+
((octets[2]! << 8) | octets[3]!).toString(16),
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
const zeros = halves.length === 2 ? Math.max(0, 8 - head.length - tail.length) : 0;
|
|
361
|
+
const hextets = [...head, ...Array<string>(zeros).fill("0"), ...tail].map((group) =>
|
|
362
|
+
Number.parseInt(group, 16),
|
|
363
|
+
);
|
|
364
|
+
if (hextets.length !== 8 || hextets.some((hextet) => !Number.isInteger(hextet)))
|
|
365
|
+
return value.slice(0, 64);
|
|
366
|
+
if (hextets.slice(0, 5).every((hextet) => hextet === 0) && hextets[5] === 0xffff)
|
|
367
|
+
return [hextets[6]! >> 8, hextets[6]! & 255, hextets[7]! >> 8, hextets[7]! & 255].join(".");
|
|
368
|
+
return `${hextets
|
|
369
|
+
.slice(0, 4)
|
|
370
|
+
.map((hextet) => hextet.toString(16))
|
|
371
|
+
.join(":")}::/64`;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/** Count one operation in a per-minute bucket; 429 with Retry-After past `limit` (null: no limit). */
|
|
375
|
+
export async function enforceSiteRuntimeRate(
|
|
376
|
+
deps: SiteRuntimeDeps,
|
|
377
|
+
host: SiteRuntimeHost,
|
|
378
|
+
principal: string,
|
|
379
|
+
bucket: SiteRuntimeRateBucket,
|
|
380
|
+
limit: number | null,
|
|
381
|
+
): Promise<void> {
|
|
382
|
+
if (limit === null) return;
|
|
383
|
+
const rate = await consumeSiteRuntimeRate(deps.db, host.scope, {
|
|
384
|
+
siteId: host.site.id,
|
|
385
|
+
principal,
|
|
386
|
+
bucket,
|
|
387
|
+
limit,
|
|
388
|
+
});
|
|
389
|
+
if (!rate.allowed)
|
|
390
|
+
throw sitePublishingError(
|
|
391
|
+
429,
|
|
392
|
+
"limit_exceeded",
|
|
393
|
+
"site_rate_limited",
|
|
394
|
+
"Too many requests; try again shortly",
|
|
395
|
+
{
|
|
396
|
+
bucket,
|
|
397
|
+
retryAfterSeconds: rate.retryAfterSeconds,
|
|
398
|
+
},
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// --- agents ---------------------------------------------------------------------
|
|
403
|
+
|
|
404
|
+
export { SITE_RUNTIME_AGENT_PERMISSIONS };
|
|
405
|
+
|
|
406
|
+
/** A short display label for the viewer, recorded as the end user of the agent's work. */
|
|
407
|
+
export function siteRuntimeViewerLabel(viewer: SiteRuntimeViewer): string {
|
|
408
|
+
if (viewer.kind === "guest") return "Site guest";
|
|
409
|
+
const who = viewer.name?.trim() || viewer.email || "Site viewer";
|
|
410
|
+
return who.slice(0, 120);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/** Whether this viewer may use the Site's embedded agent at all (before budgets). */
|
|
414
|
+
export function siteAgentRefusal(
|
|
415
|
+
deps: SiteRuntimeDeps,
|
|
416
|
+
host: SiteRuntimeHost,
|
|
417
|
+
viewer: SiteRuntimeViewer,
|
|
418
|
+
limits: SiteLimits,
|
|
419
|
+
): string | null {
|
|
420
|
+
if (!host.publication.agent.enabled || !limits.agentsEnabled) return "agent_disabled";
|
|
421
|
+
// A conversation of a viewer outside the workspace is capped by its own
|
|
422
|
+
// task usage limit; without the producer that cap cannot be stored, so
|
|
423
|
+
// their Site agents stay off. Members run under the workspace's normal
|
|
424
|
+
// session limits and need no Site cap.
|
|
425
|
+
if (viewer.kind !== "member" && !sessionUsageLimitsProducerEnabled(deps.settings))
|
|
426
|
+
return "usage_limits_disabled";
|
|
427
|
+
if (viewer.kind === "guest" && !limits.guestAgentsEnabled) return "guest_agents_disabled";
|
|
428
|
+
if (viewer.kind === "user" && !deps.settings.sitesPublicAgentsEnabled)
|
|
429
|
+
return "public_agents_disabled";
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const AGENT_REFUSAL_MESSAGES: Record<string, string> = {
|
|
434
|
+
agent_disabled: "This Site's assistant is not available to you",
|
|
435
|
+
usage_limits_disabled: "This Site's assistant is not available to you",
|
|
436
|
+
guest_agents_disabled: "Sign in to use this Site's assistant",
|
|
437
|
+
public_agents_disabled: "This Site's assistant is only available to invited people",
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
export function requireSiteAgent(
|
|
441
|
+
deps: SiteRuntimeDeps,
|
|
442
|
+
host: SiteRuntimeHost,
|
|
443
|
+
viewer: SiteRuntimeViewer,
|
|
444
|
+
limits: SiteLimits,
|
|
445
|
+
): void {
|
|
446
|
+
const reason = siteAgentRefusal(deps, host, viewer, limits);
|
|
447
|
+
if (reason)
|
|
448
|
+
throw siteNotAvailableForViewer(
|
|
449
|
+
"agent",
|
|
450
|
+
AGENT_REFUSAL_MESSAGES[reason] ?? "This Site's assistant is not available to you",
|
|
451
|
+
{ reason },
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function budgetExhausted(budget: string, message: string) {
|
|
456
|
+
return sitePublishingError(429, "limit_exceeded", "site_budget_exhausted", message, { budget });
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const BUDGET_MESSAGES: Record<SiteRuntimeBudget, string> = {
|
|
460
|
+
conversations_per_visitor_per_day:
|
|
461
|
+
"You have started as many conversations as this Site allows today",
|
|
462
|
+
conversations_per_site_per_day:
|
|
463
|
+
"This Site's assistant has started as many conversations as it allows today",
|
|
464
|
+
turns_per_conversation: "This conversation has reached its message limit; start a new one",
|
|
465
|
+
agent_micros_per_conversation: "This conversation has reached its budget; start a new one",
|
|
466
|
+
agent_micros_per_visitor_per_day:
|
|
467
|
+
"You have used this Site's assistant as much as it allows today",
|
|
468
|
+
agent_micros_per_visitor_per_month:
|
|
469
|
+
"You have used this Site's assistant as much as it allows this month",
|
|
470
|
+
agent_micros_per_site_per_day: "This Site's assistant has reached its limit for today",
|
|
471
|
+
agent_micros_per_site_per_month: "This Site's assistant has reached its limit for this month",
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
/** The 429 for a refused reservation. */
|
|
475
|
+
export function siteBudgetExhausted(budget: SiteRuntimeBudget) {
|
|
476
|
+
return budgetExhausted(budget, BUDGET_MESSAGES[budget]);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/** Seconds a viewer waits before retrying a start or message refused as busy. */
|
|
480
|
+
export const SITE_BUDGET_BUSY_RETRY_SECONDS = 30;
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* The 429 when only other running conversations' reservations refuse a
|
|
484
|
+
* reservation: the budget has room once their spend settles, so it is worth
|
|
485
|
+
* retrying shortly (`site_budget_busy`, with `retryAfterSeconds`).
|
|
486
|
+
*/
|
|
487
|
+
export function siteBudgetBusy(budget: SiteRuntimeBudget) {
|
|
488
|
+
return sitePublishingError(
|
|
489
|
+
429,
|
|
490
|
+
"limit_exceeded",
|
|
491
|
+
"site_budget_busy",
|
|
492
|
+
"This Site's assistant is busy right now; try again in a moment",
|
|
493
|
+
{ budget, retryAfterSeconds: SITE_BUDGET_BUSY_RETRY_SECONDS },
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/** The 429 for a refused reservation, busy or exhausted. */
|
|
498
|
+
export function siteReservationRefused(result: Extract<SiteRuntimeReservation, { ok: false }>) {
|
|
499
|
+
return result.busy ? siteBudgetBusy(result.budget) : siteBudgetExhausted(result.budget);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* The budgets a conversation start or message is reserved against. Members
|
|
504
|
+
* reserve nothing (their caps are all null). For everyone else a
|
|
505
|
+
* conversation is bounded by the per-conversation cap, else by the smallest
|
|
506
|
+
* spend limit that applies, so a session usage limit always holds it.
|
|
507
|
+
*/
|
|
508
|
+
export function siteRuntimeBudgetLimits(
|
|
509
|
+
limits: SiteLimits,
|
|
510
|
+
settings: Pick<SiteRuntimeDeps["settings"], "sitesAgentReservationMicrosPerTurn">,
|
|
511
|
+
): SiteRuntimeBudgetLimits {
|
|
512
|
+
const spend = [
|
|
513
|
+
limits.agentMicrosPerVisitorPerDay,
|
|
514
|
+
limits.agentMicrosPerVisitorPerMonth,
|
|
515
|
+
limits.agentMicrosPerSitePerDay,
|
|
516
|
+
limits.agentMicrosPerSitePerMonth,
|
|
517
|
+
].filter((value): value is number => value !== null && value !== undefined);
|
|
518
|
+
return {
|
|
519
|
+
conversationsPerVisitorPerDay: limits.conversationsPerVisitorPerDay,
|
|
520
|
+
conversationsPerSitePerDay: limits.conversationsPerSitePerDay,
|
|
521
|
+
turnsPerConversation: limits.turnsPerConversation,
|
|
522
|
+
agentMicrosPerConversation:
|
|
523
|
+
limits.agentMicrosPerConversation ?? (spend.length > 0 ? Math.min(...spend) : null),
|
|
524
|
+
agentMicrosPerVisitorPerDay: limits.agentMicrosPerVisitorPerDay,
|
|
525
|
+
agentMicrosPerVisitorPerMonth: limits.agentMicrosPerVisitorPerMonth,
|
|
526
|
+
agentMicrosPerSitePerDay: limits.agentMicrosPerSitePerDay,
|
|
527
|
+
agentMicrosPerSitePerMonth: limits.agentMicrosPerSitePerMonth,
|
|
528
|
+
turnReservationMicros: settings.sitesAgentReservationMicrosPerTurn,
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* The Site-wide usage windows a viewer's conversations count in: members
|
|
534
|
+
* count apart (`*members`), so they never use up the Site limit.
|
|
535
|
+
*/
|
|
536
|
+
export function siteRuntimeBudgetKey(viewer: Pick<SiteRuntimeViewer, "kind">): string {
|
|
537
|
+
return viewer.kind === "member" ? SITE_RUNTIME_MEMBERS_PRINCIPAL : SITE_RUNTIME_SITE_PRINCIPAL;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/** Whether a refusal is a Site-wide budget (stale reservations of others may free it). */
|
|
541
|
+
export function siteWideBudget(result: SiteRuntimeReservation): boolean {
|
|
542
|
+
return (
|
|
543
|
+
!result.ok &&
|
|
544
|
+
(result.budget === "agent_micros_per_site_per_month" ||
|
|
545
|
+
result.budget === "agent_micros_per_site_per_day")
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
const IDLE_SESSION_STATUSES = new Set(["idle", "failed", "cancelled"]);
|
|
550
|
+
/** A start whose session has not appeared after this long failed before its create committed. */
|
|
551
|
+
const ABANDONED_START_MS = SITE_RUNTIME_ABANDONED_START_MINUTES * 60_000;
|
|
552
|
+
|
|
553
|
+
/** A conversation's session usage as read while settling. */
|
|
554
|
+
export type SiteConversationUsage = {
|
|
555
|
+
usedMicros: number;
|
|
556
|
+
limitMicros: number | null;
|
|
557
|
+
version: number;
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Bring a conversation's counted spend up to its session usage so far and
|
|
562
|
+
* release what it no longer needs reserved (all of it once the session is
|
|
563
|
+
* idle, or when `release` says so). A start whose session never appeared is
|
|
564
|
+
* released entirely. A read failure leaves the counters as they are; the
|
|
565
|
+
* session's own usage limit still caps the conversation.
|
|
566
|
+
*/
|
|
567
|
+
export async function settleSiteRuntimeSpend(
|
|
568
|
+
deps: SiteRuntimeDeps,
|
|
569
|
+
host: SiteRuntimeHost,
|
|
570
|
+
conversation: Pick<SiteRuntimeConversationRecord, "sessionId" | "admissions"> &
|
|
571
|
+
Partial<Pick<SiteRuntimeConversationRecord, "createdAt">>,
|
|
572
|
+
options: { release?: boolean } = {},
|
|
573
|
+
): Promise<SiteConversationUsage | null> {
|
|
574
|
+
const { sessionId } = conversation;
|
|
575
|
+
try {
|
|
576
|
+
const session = await getSession(deps.db, host.scope.workspaceId, sessionId);
|
|
577
|
+
if (session === null) {
|
|
578
|
+
if (
|
|
579
|
+
conversation.createdAt &&
|
|
580
|
+
Date.now() - conversation.createdAt.getTime() > ABANDONED_START_MS
|
|
581
|
+
)
|
|
582
|
+
await releaseSiteRuntimeConversationStart(deps.db, host.scope, { sessionId });
|
|
583
|
+
return null;
|
|
584
|
+
}
|
|
585
|
+
const read = await readSessionUsageLimit(deps.db, {
|
|
586
|
+
...host.scope,
|
|
587
|
+
sessionId,
|
|
588
|
+
runtime: true,
|
|
589
|
+
});
|
|
590
|
+
if (read.outcome !== "ok" || read.state.usedMicros === null) return null;
|
|
591
|
+
const usage = {
|
|
592
|
+
usedMicros: read.state.usedMicros,
|
|
593
|
+
limitMicros: read.state.limitMicros,
|
|
594
|
+
version: read.state.version,
|
|
595
|
+
};
|
|
596
|
+
await settleSiteRuntimeConversationSpend(deps.db, host.scope, {
|
|
597
|
+
sessionId,
|
|
598
|
+
usedMicros: usage.usedMicros,
|
|
599
|
+
idle: options.release === true || IDLE_SESSION_STATUSES.has(session.status),
|
|
600
|
+
expected: { admissions: conversation.admissions },
|
|
601
|
+
});
|
|
602
|
+
return usage;
|
|
603
|
+
} catch (error) {
|
|
604
|
+
console.warn("[site-runtime] could not settle conversation spend", {
|
|
605
|
+
siteId: host.site.id,
|
|
606
|
+
error: error instanceof Error ? error.message : String(error),
|
|
607
|
+
});
|
|
608
|
+
return null;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* Lift the session usage limit a Site stamped on a workspace member's
|
|
614
|
+
* conversation before members were kept out of Site limits: cleared only
|
|
615
|
+
* while it is still exactly the Site's cap (`siteCapMicros`), so a limit
|
|
616
|
+
* someone set on the session since is kept. Failure refuses admission; the
|
|
617
|
+
* caller retains its durable normalization marker and retries next time.
|
|
618
|
+
*/
|
|
619
|
+
export async function liftSiteConversationUsageLimit(
|
|
620
|
+
deps: SiteRuntimeDeps,
|
|
621
|
+
host: SiteRuntimeHost,
|
|
622
|
+
sessionId: string,
|
|
623
|
+
siteCapMicros: number,
|
|
624
|
+
usage: SiteConversationUsage | null,
|
|
625
|
+
): Promise<void> {
|
|
626
|
+
let current = usage;
|
|
627
|
+
if (current === null) {
|
|
628
|
+
const read = await readSessionUsageLimit(deps.db, {
|
|
629
|
+
...host.scope,
|
|
630
|
+
sessionId,
|
|
631
|
+
runtime: true,
|
|
632
|
+
}).catch(() => null);
|
|
633
|
+
if (read?.outcome !== "ok")
|
|
634
|
+
throw sitePublishingError(
|
|
635
|
+
503,
|
|
636
|
+
"upstream_unavailable",
|
|
637
|
+
"site_conversation_unavailable",
|
|
638
|
+
"The conversation cannot continue right now",
|
|
639
|
+
);
|
|
640
|
+
current = {
|
|
641
|
+
usedMicros: read.state.usedMicros ?? 0,
|
|
642
|
+
limitMicros: read.state.limitMicros,
|
|
643
|
+
version: read.state.version,
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
if (current.limitMicros !== siteCapMicros) return;
|
|
647
|
+
const { version } = current;
|
|
648
|
+
const result = await withWorkspaceSessionActivityRls(
|
|
649
|
+
deps.db,
|
|
650
|
+
host.scope.workspaceId,
|
|
651
|
+
async (tx) =>
|
|
652
|
+
await setSessionUsageLimitInTransaction(tx, {
|
|
653
|
+
...host.scope,
|
|
654
|
+
sessionId,
|
|
655
|
+
limitMicros: null,
|
|
656
|
+
expectedVersion: version,
|
|
657
|
+
operationId: `site-runtime-member-uncap:${sessionId}:${version}`,
|
|
658
|
+
actor: { type: "host", subjectId: `site:${host.site.id}` },
|
|
659
|
+
producerEnabled: sessionUsageLimitsProducerEnabled(deps.settings),
|
|
660
|
+
}),
|
|
661
|
+
).catch(() => null);
|
|
662
|
+
if (result?.outcome !== "ok" && result?.outcome !== "replayed")
|
|
663
|
+
throw sitePublishingError(
|
|
664
|
+
503,
|
|
665
|
+
"upstream_unavailable",
|
|
666
|
+
"site_conversation_unavailable",
|
|
667
|
+
"The conversation cannot continue right now",
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* Stamp only funded execution: cumulative spend plus the current reservation,
|
|
673
|
+
* bounded by the conversation cap. Top-ups reserve atomically before this
|
|
674
|
+
* raises the ceiling; reread under the reservation lock so overlapping
|
|
675
|
+
* admissions cannot stamp a stale larger allowance. Failure refuses work.
|
|
676
|
+
*/
|
|
677
|
+
export async function capSiteConversationUsageLimit(
|
|
678
|
+
deps: SiteRuntimeDeps,
|
|
679
|
+
host: SiteRuntimeHost,
|
|
680
|
+
sessionId: string,
|
|
681
|
+
usage: SiteConversationUsage | null,
|
|
682
|
+
): Promise<void> {
|
|
683
|
+
let current = usage;
|
|
684
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
685
|
+
if (current === null) {
|
|
686
|
+
const read = await readSessionUsageLimit(deps.db, {
|
|
687
|
+
...host.scope,
|
|
688
|
+
sessionId,
|
|
689
|
+
runtime: true,
|
|
690
|
+
}).catch(() => null);
|
|
691
|
+
current =
|
|
692
|
+
read?.outcome === "ok"
|
|
693
|
+
? {
|
|
694
|
+
usedMicros: read.state.usedMicros ?? 0,
|
|
695
|
+
limitMicros: read.state.limitMicros,
|
|
696
|
+
version: read.state.version,
|
|
697
|
+
}
|
|
698
|
+
: null;
|
|
699
|
+
if (current === null) break;
|
|
700
|
+
}
|
|
701
|
+
const { version } = current;
|
|
702
|
+
const result = await withWorkspaceSessionActivityRls(
|
|
703
|
+
deps.db,
|
|
704
|
+
host.scope.workspaceId,
|
|
705
|
+
async (tx) => {
|
|
706
|
+
const funded = await siteRuntimeFundedLimitInTransaction(tx, host.scope, sessionId);
|
|
707
|
+
if (funded === null) throw new Error("Non-member conversation has no funded ceiling");
|
|
708
|
+
return await setSessionUsageLimitInTransaction(tx, {
|
|
709
|
+
...host.scope,
|
|
710
|
+
sessionId,
|
|
711
|
+
limitMicros: funded,
|
|
712
|
+
expectedVersion: version,
|
|
713
|
+
operationId: `site-runtime-cap:${sessionId}:${version}:${funded}`,
|
|
714
|
+
actor: { type: "host", subjectId: `site:${host.site.id}` },
|
|
715
|
+
producerEnabled: sessionUsageLimitsProducerEnabled(deps.settings),
|
|
716
|
+
});
|
|
717
|
+
},
|
|
718
|
+
).catch(() => null);
|
|
719
|
+
if (result?.outcome === "ok" || result?.outcome === "replayed") return;
|
|
720
|
+
current = null;
|
|
721
|
+
}
|
|
722
|
+
throw sitePublishingError(
|
|
723
|
+
503,
|
|
724
|
+
"upstream_unavailable",
|
|
725
|
+
"site_conversation_unavailable",
|
|
726
|
+
"The conversation cannot continue right now",
|
|
727
|
+
);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/** Reservations claimed per sweep. */
|
|
731
|
+
const STALE_RESERVATION_SWEEP = 20;
|
|
732
|
+
/** After a sweep that found nothing, the Site is not swept again for this long (per process). */
|
|
733
|
+
const EMPTY_SWEEP_COOLDOWN_MS = 5_000;
|
|
734
|
+
const emptySweeps = new Map<string, number>();
|
|
735
|
+
|
|
736
|
+
/**
|
|
737
|
+
* Release reservations that conversations no longer need, so a Site-wide
|
|
738
|
+
* budget held by finished, abandoned or long-inactive conversations frees
|
|
739
|
+
* up. Claims a bounded batch of likely candidates (idle sessions, starts
|
|
740
|
+
* whose session never appeared, reservations inactive for six hours),
|
|
741
|
+
* least recently visited first and each at most every 30 seconds, and
|
|
742
|
+
* settles each. A sweep that finds nothing pauses sweeping the Site for a
|
|
743
|
+
* few seconds, so refused starts in a burst cost one cheap check at most.
|
|
744
|
+
* Returns whether any reservation was visited.
|
|
745
|
+
*/
|
|
746
|
+
export async function sweepSiteRuntimeReservations(
|
|
747
|
+
deps: SiteRuntimeDeps,
|
|
748
|
+
host: SiteRuntimeHost,
|
|
749
|
+
): Promise<boolean> {
|
|
750
|
+
const now = Date.now();
|
|
751
|
+
if ((emptySweeps.get(host.site.id) ?? 0) > now - EMPTY_SWEEP_COOLDOWN_MS) return false;
|
|
752
|
+
const candidates = await claimSiteRuntimeReservationSweep(deps.db, host.scope, {
|
|
753
|
+
siteId: host.site.id,
|
|
754
|
+
limit: STALE_RESERVATION_SWEEP,
|
|
755
|
+
});
|
|
756
|
+
if (candidates.length === 0) {
|
|
757
|
+
if (emptySweeps.size > 10_000) emptySweeps.clear();
|
|
758
|
+
emptySweeps.set(host.site.id, now);
|
|
759
|
+
return false;
|
|
760
|
+
}
|
|
761
|
+
emptySweeps.delete(host.site.id);
|
|
762
|
+
for (const candidate of candidates)
|
|
763
|
+
await settleSiteRuntimeSpend(deps, host, candidate, { release: candidate.aged });
|
|
764
|
+
return true;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
/** Usage-only metering for Site runtime events (agent spend is metered by the session itself). */
|
|
768
|
+
export async function meterSiteRuntime(
|
|
769
|
+
deps: SiteRuntimeDeps,
|
|
770
|
+
host: SiteRuntimeHost,
|
|
771
|
+
event: { type: "conversation" | "sign_in" | "report"; viewer?: SiteRuntimeViewer },
|
|
772
|
+
): Promise<void> {
|
|
773
|
+
try {
|
|
774
|
+
await withRlsContext(deps.db, host.scope, async (tx) => {
|
|
775
|
+
await recordUsageEvent(tx, {
|
|
776
|
+
accountId: host.scope.accountId,
|
|
777
|
+
workspaceId: host.scope.workspaceId,
|
|
778
|
+
subjectId: event.viewer?.subjectId ?? null,
|
|
779
|
+
eventType: `site_runtime.${event.type}`,
|
|
780
|
+
quantity: 1,
|
|
781
|
+
unit: event.type,
|
|
782
|
+
sourceResourceType: "site",
|
|
783
|
+
sourceResourceId: host.site.id,
|
|
784
|
+
idempotencyKey: `site_runtime.${event.type}:${crypto.randomUUID()}`,
|
|
785
|
+
});
|
|
786
|
+
});
|
|
787
|
+
} catch (error) {
|
|
788
|
+
console.warn("[site-runtime] could not meter usage", {
|
|
789
|
+
siteId: host.site.id,
|
|
790
|
+
type: event.type,
|
|
791
|
+
error: error instanceof Error ? error.message : String(error),
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// --- in-process dispatch ------------------------------------------------------------
|
|
797
|
+
|
|
798
|
+
/**
|
|
799
|
+
* Run an ordinary session route in this process as the Site's service
|
|
800
|
+
* principal (`site:<siteId>`), with a 60-second delegated token carrying only
|
|
801
|
+
* session permissions and the viewer as its display label. The Site and the
|
|
802
|
+
* served version ride along as the request's Site origin, so new sessions and
|
|
803
|
+
* turns record where they came from. The route authorizes as usual.
|
|
804
|
+
*/
|
|
805
|
+
export async function siteRuntimeSessionRequest(
|
|
806
|
+
deps: SiteRuntimeDeps,
|
|
807
|
+
host: SiteRuntimeHost & { versionId: string },
|
|
808
|
+
viewer: SiteRuntimeViewer,
|
|
809
|
+
input: {
|
|
810
|
+
origin: string;
|
|
811
|
+
method: string;
|
|
812
|
+
path: string;
|
|
813
|
+
body?: unknown;
|
|
814
|
+
extraPermissions?: readonly Permission[];
|
|
815
|
+
},
|
|
816
|
+
): Promise<Request> {
|
|
817
|
+
return await siteServiceSessionRequest(
|
|
818
|
+
deps,
|
|
819
|
+
host.scope,
|
|
820
|
+
{ id: host.site.id, versionId: host.versionId },
|
|
821
|
+
{ ...input, label: `${siteRuntimeViewerLabel(viewer)} on ${host.hostname}` },
|
|
822
|
+
);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
/** A forwarded response without cookies (site-runtime never sets any). */
|
|
826
|
+
export function siteRuntimeForwardedResponse(response: Response): Response {
|
|
827
|
+
const headers = new Headers(response.headers);
|
|
828
|
+
headers.delete("set-cookie");
|
|
829
|
+
headers.set("cache-control", "no-store");
|
|
830
|
+
return new Response(response.body, { status: response.status, headers });
|
|
831
|
+
}
|