@opengeni/api-router 3.0.1 → 3.0.2-canary.38894290271001
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-grant-rls.d.ts +8 -1
- package/dist/admission-trace.d.ts +8 -0
- package/dist/app.d.ts +50 -3
- package/dist/app.js +10 -1
- package/dist/artifact-catalog-cursor.d.ts +1 -0
- package/dist/attached-browser-viewer.d.ts +11 -0
- package/dist/auth/managed-auth-attempt-context.d.ts +2 -0
- package/dist/auth/managed-auth-rate-limits.d.ts +167 -0
- package/dist/auth/managed-auth-session-adapter.d.ts +9 -1
- package/dist/auth/managed-auth.d.ts +72 -5
- package/dist/auth/managed-sign-in-notifications.d.ts +4 -0
- package/dist/auth/new-signups-gate.d.ts +23 -0
- package/dist/auth/signup-funnel-metrics.d.ts +44 -0
- package/dist/azure-live.d.ts +28 -0
- package/dist/browser-action-fences.d.ts +13 -0
- package/dist/browser-controller-authority.d.ts +1 -61
- package/dist/browser-ephemeral.d.ts +5 -0
- package/dist/browser-lost-runtime-end.d.ts +24 -0
- package/dist/browser-state-authority.d.ts +1 -27
- package/dist/{chunk-AJ62T5QN.js → chunk-GBUJ2DYZ.js} +80507 -39878
- package/dist/chunk-GBUJ2DYZ.js.map +1 -0
- package/dist/chunk-OGQDXVXY.js +18 -0
- package/dist/chunk-OGQDXVXY.js.map +1 -0
- package/dist/claude-default-models.d.ts +19 -0
- package/dist/claude-subscription-core.d.ts +50 -0
- package/dist/claude-subscription-oauth.d.ts +36 -0
- package/dist/claude-subscription-profile.d.ts +9 -0
- package/dist/claude-subscription-usage.d.ts +6 -0
- package/dist/claude-workspace-connection.d.ts +15 -0
- package/dist/codemode.d.ts +40 -5
- package/dist/codex-realtime.d.ts +29 -3
- package/dist/codex-session-pointers.d.ts +19 -0
- package/dist/connection-ownership.d.ts +6 -3
- package/dist/controller-data-plane.d.ts +16 -0
- package/dist/credit-promotion-snapshot.d.ts +6 -0
- package/dist/editable-artifact-source-session.d.ts +3 -0
- package/dist/editable-artifact-websocket.d.ts +5 -3
- package/dist/fatal-process-boundary.d.ts +9 -1
- package/dist/github-access.d.ts +7 -1
- package/dist/github-browser-flow.d.ts +1 -1
- package/dist/http/acting-person.d.ts +36 -0
- package/dist/http/api-contract-compat.d.ts +19 -0
- package/dist/http/api-error.d.ts +50 -1
- package/dist/http/auth.d.ts +8 -1
- package/dist/http/cors.d.ts +12 -0
- package/dist/http/deprecation.d.ts +42 -0
- package/dist/http/keyed-admission.d.ts +12 -0
- package/dist/http/local-browser-boundary.d.ts +72 -0
- package/dist/http/metrics-listener.d.ts +22 -0
- package/dist/http/path-identifier.d.ts +17 -0
- package/dist/http/registered-route-label.d.ts +16 -0
- package/dist/http/rejection-telemetry.d.ts +34 -0
- package/dist/http/request-body.d.ts +27 -0
- package/dist/http/request-source.d.ts +41 -3
- package/dist/http/sse.d.ts +12 -1
- package/dist/http/unmatched-route.d.ts +18 -0
- package/dist/http/user-content.d.ts +36 -0
- package/dist/http/variable-sets-removed.d.ts +41 -0
- package/dist/http/websocket-upgrade-dispatch.d.ts +18 -0
- package/dist/index.js +1279 -108
- package/dist/index.js.map +1 -1
- package/dist/integration-connect-metrics.d.ts +35 -0
- package/dist/integration-endpoint-test.d.ts +20 -0
- package/dist/integrations/api-integrations.d.ts +2 -2
- package/dist/integrations/atlassian.d.ts +8 -3
- package/dist/integrations/fiken.d.ts +3 -1
- package/dist/integrations/github-lens-connect.d.ts +37 -66
- package/dist/integrations/google-drive.d.ts +2 -0
- package/dist/integrations/integration-commit-authority.d.ts +13 -0
- package/dist/integrations/marketplace-plugin.d.ts +43 -43
- package/dist/integrations/oauth-client.d.ts +171 -12
- package/dist/integrations/oauth-profiles.d.ts +42 -13
- package/dist/integrations/onepassword-vault-provider.d.ts +9 -0
- package/dist/integrations/slack-bot.d.ts +285 -7
- package/dist/integrations/slack-file-upload-flow.d.ts +62 -0
- package/dist/integrations/slack-interactions.d.ts +89 -10
- package/dist/integrations/slack-mrkdwn.d.ts +41 -0
- package/dist/integrations/slack-routing.d.ts +1 -1
- package/dist/integrations/slack-session-defaults.d.ts +58 -0
- package/dist/integrations/slack-task-file-upload.d.ts +36 -0
- package/dist/integrations/slack-tool-review.d.ts +5 -0
- package/dist/interaction-agent-access.d.ts +13 -8
- package/dist/interaction-agent-version.d.ts +21 -0
- package/dist/interaction-holder-heartbeat.d.ts +5 -0
- package/dist/interaction-metrics.d.ts +27 -2
- package/dist/interaction-route-stages.d.ts +25 -0
- package/dist/interaction-secrets-vault.d.ts +20 -0
- package/dist/interaction-secrets.d.ts +103 -0
- package/dist/mcp/action-catalog-types.d.ts +14 -0
- package/dist/mcp/action-catalog.gen.d.ts +2 -0
- package/dist/mcp/action-descriptions.d.ts +4 -0
- package/dist/mcp/action-schema-imports.gen.d.ts +1 -0
- package/dist/mcp/capability-account-readiness.d.ts +7 -0
- package/dist/mcp/child-read-evidence.d.ts +9 -0
- package/dist/mcp/company-profile-agent-admin.d.ts +12 -0
- package/dist/mcp/contract-input.d.ts +13 -0
- package/dist/mcp/documents.d.ts +2 -0
- package/dist/mcp/first-party-tool-permissions.d.ts +3 -606
- package/dist/mcp/notification-tools.d.ts +26 -0
- package/dist/mcp/orchestration-failure-diagnostic.d.ts +43 -0
- package/dist/mcp/scheduled-task-input.d.ts +1309 -0
- package/dist/mcp/scheduled-task-view.d.ts +217 -3
- package/dist/mcp/secret-tools.d.ts +14 -0
- package/dist/mcp/server.d.ts +108 -5
- package/dist/mcp/session-admin-access-tools.d.ts +33 -0
- package/dist/mcp/session-event-errors.d.ts +28 -0
- package/dist/mcp/session-event-view.d.ts +62 -0
- package/dist/mcp/session-view.d.ts +15 -0
- package/dist/mcp/session-wait.d.ts +18 -0
- package/dist/mcp/site-agent-clamp-tools.d.ts +38 -0
- package/dist/mcp/site-data-tools.d.ts +22 -0
- package/dist/mcp/site-publication-tools.d.ts +19 -0
- package/dist/mcp/site-sql-tools.d.ts +27 -0
- package/dist/mcp-oauth.d.ts +37 -4
- package/dist/model-calls/chat-completions.d.ts +87 -0
- package/dist/model-calls/pending-usage-settlements.d.ts +57 -0
- package/dist/model-calls/service.d.ts +36 -0
- package/dist/model-catalog.d.ts +9 -3
- package/dist/native-push-dispatch.d.ts +69 -0
- package/dist/observability.d.ts +3 -1
- package/dist/organization-mcp.d.ts +82 -0
- package/dist/prepared-mcp-actor.d.ts +77 -0
- package/dist/prepared-mcp-permissions.d.ts +8 -0
- package/dist/routes/analytics-consent.d.ts +26 -0
- package/dist/routes/api-integrations.d.ts +11 -0
- package/dist/routes/api-keys.d.ts +7 -7
- package/dist/routes/automations.d.ts +2 -0
- package/dist/routes/billing.d.ts +59 -0
- package/dist/routes/browser-sessions.d.ts +65 -2
- package/dist/routes/chat-completions.d.ts +11 -0
- package/dist/routes/claude-core-usage.d.ts +20 -0
- package/dist/routes/claude-subscription-accounts.d.ts +3 -0
- package/dist/routes/claude-subscription-oauth.d.ts +3 -0
- package/dist/routes/client-errors.d.ts +182 -0
- package/dist/routes/codex-core.d.ts +401 -0
- package/dist/routes/codex.d.ts +64 -2
- package/dist/routes/computer-sessions.d.ts +6 -1
- package/dist/routes/desktop-distribution.d.ts +32 -0
- package/dist/routes/github-browser-fonts.gen.d.ts +2 -0
- package/dist/routes/github-browser-pages.d.ts +27 -0
- package/dist/routes/inbox.d.ts +13 -0
- package/dist/routes/insights-response-cache.d.ts +10 -0
- package/dist/routes/insights-usage.d.ts +12 -0
- package/dist/routes/insights.d.ts +28 -0
- package/dist/routes/interaction-placement-message.d.ts +7 -0
- package/dist/routes/managed-sign-in-methods.d.ts +6 -0
- package/dist/routes/model-account-choice.d.ts +13 -0
- package/dist/routes/model-account-settings.d.ts +19 -0
- package/dist/routes/native-app-auth.d.ts +12 -0
- package/dist/routes/organization-integration-policy.d.ts +8 -0
- package/dist/routes/organization-integrations.d.ts +6 -0
- package/dist/routes/organization-mcp-connections.d.ts +3 -0
- package/dist/routes/organization-model-defaults.d.ts +9 -0
- package/dist/routes/organization-service-accounts.d.ts +3 -0
- package/dist/routes/personal-api-keys.d.ts +3 -0
- package/dist/routes/session-admin-access.d.ts +3 -0
- package/dist/routes/session-artifact-associations.d.ts +4 -0
- package/dist/routes/session-history-imports.d.ts +20 -0
- package/dist/routes/sessions.d.ts +38 -2
- package/dist/routes/site-data.d.ts +18 -0
- package/dist/routes/site-publishing.d.ts +5 -0
- package/dist/routes/site-runtime.d.ts +31 -0
- package/dist/routes/site-sql.d.ts +16 -0
- package/dist/routes/slack-bot-access.d.ts +3 -0
- package/dist/routes/subscription-account-pools.d.ts +21 -0
- package/dist/routes/subscription-core-http.d.ts +5 -0
- package/dist/routes/subscription-pool-access.d.ts +37 -0
- package/dist/routes/subscription-pool-core.d.ts +153 -0
- package/dist/routes/supergrok.d.ts +6 -6
- package/dist/routes/usage-allowances.d.ts +12 -0
- package/dist/routes/variable-sets-removed.d.ts +2 -0
- package/dist/routes/vault-providers.d.ts +34 -0
- package/dist/routes/vaults.d.ts +3 -0
- package/dist/routes/workspace-integrations.d.ts +35 -0
- package/dist/routes/workspace-model-providers.d.ts +4 -0
- package/dist/routes/workspaces.d.ts +11 -2
- package/dist/sandbox/agent-update.d.ts +64 -0
- package/dist/sandbox/auth-callout.d.ts +8 -0
- package/dist/sandbox/channel-a.d.ts +15 -1
- package/dist/sandbox/enrollment.d.ts +26 -4
- package/dist/sandbox/machine-audience.d.ts +29 -0
- package/dist/sandbox/machines.d.ts +15 -2
- package/dist/sandbox/metrics-ingestion.d.ts +6 -2
- package/dist/sandbox/screen-control.d.ts +85 -0
- package/dist/sandbox/viewer.d.ts +5 -1
- package/dist/sandbox-file-artifacts.d.ts +17 -1
- package/dist/scheduled-task-blockers.d.ts +39 -0
- package/dist/scheduled-task-deletion.d.ts +3 -4
- package/dist/scheduled-task-lifecycle.d.ts +14 -0
- package/dist/secret-answers.d.ts +40 -0
- package/dist/session-create-observability.d.ts +5 -0
- package/dist/session-event-view-36GO3IL7.js +528 -0
- package/dist/session-event-view-36GO3IL7.js.map +1 -0
- package/dist/signed-state-secret.d.ts +18 -0
- package/dist/site-agent-guard.d.ts +53 -0
- package/dist/site-data/operations.d.ts +103 -0
- package/dist/site-data/service.d.ts +203 -0
- package/dist/site-frame-guard.d.ts +32 -0
- package/dist/site-publishing/agent-data-tools.d.ts +84 -0
- package/dist/site-publishing/agent-model.d.ts +58 -0
- package/dist/site-publishing/agent-session.d.ts +16 -0
- package/dist/site-publishing/content-scanner.d.ts +31 -0
- package/dist/site-publishing/edge-sync.d.ts +48 -0
- package/dist/site-publishing/gateway-signature.d.ts +56 -0
- package/dist/site-publishing/limits.d.ts +63 -0
- package/dist/site-publishing/page-tool-authority.d.ts +30 -0
- package/dist/site-publishing/platform-usage.d.ts +113 -0
- package/dist/site-publishing/platform.d.ts +21 -0
- package/dist/site-publishing/readiness.d.ts +62 -0
- package/dist/site-publishing/runtime.d.ts +179 -0
- package/dist/site-publishing/service.d.ts +264 -0
- package/dist/site-publishing/session-events.d.ts +22 -0
- package/dist/site-publishing/session-views.d.ts +36 -0
- package/dist/site-publishing/viewer.d.ts +40 -0
- package/dist/site-session-origin.d.ts +31 -6
- package/dist/site-sql/format.d.ts +47 -0
- package/dist/site-sql/gateway.d.ts +50 -0
- package/dist/site-sql/service.d.ts +213 -0
- package/dist/temporal-schedule-sync.d.ts +26 -0
- package/dist/transcription/billing-refusal.d.ts +8 -0
- package/dist/transcription/normalize.d.ts +23 -0
- package/dist/transcription/providers/azure-mai.d.ts +12 -0
- package/dist/transcription/providers/azure-openai.d.ts +4 -0
- package/dist/transcription/providers/openai.d.ts +16 -0
- package/dist/transcription/providers/subscription-core.d.ts +39 -0
- package/dist/transcription/service.d.ts +10 -1
- package/dist/transcription/wav-duration.d.ts +8 -0
- package/dist/workspace-tool-gateway-attestations.d.ts +37 -0
- package/dist/workspace-tool-gateway-observability.d.ts +16 -0
- package/dist/workspace-tool-gateway-secrets.d.ts +27 -0
- package/dist/workspace-tool-gateway.d.ts +102 -6
- package/dist/workspace-tool-target-fence.d.ts +116 -0
- package/dist/workspace-tool-target.d.ts +225 -0
- package/dist/workspace-webhook-dispatch.d.ts +26 -0
- package/dist/xai-subscription-core.d.ts +60 -0
- package/package.json +25 -20
- package/src/access-grant-rls.ts +64 -14
- package/src/admission-trace.ts +14 -0
- package/src/app.ts +1684 -252
- package/src/artifact-catalog-cursor.ts +1 -0
- package/src/attached-browser-viewer.ts +26 -0
- package/src/auth/managed-auth-attempt-context.ts +9 -0
- package/src/auth/managed-auth-rate-limits.ts +252 -0
- package/src/auth/managed-auth-session-adapter.ts +64 -29
- package/src/auth/managed-auth.ts +303 -35
- package/src/auth/managed-email.ts +18 -1
- package/src/auth/managed-sign-in-notifications.ts +81 -0
- package/src/auth/new-signups-gate.ts +49 -0
- package/src/auth/organization-user-setup.ts +3 -3
- package/src/auth/signup-funnel-metrics.ts +164 -0
- package/src/azure-live.ts +103 -0
- package/src/browser-action-fences.ts +45 -0
- package/src/browser-controller-authority.ts +1 -186
- package/src/browser-ephemeral.ts +39 -0
- package/src/browser-lost-runtime-end.ts +71 -0
- package/src/browser-state-authority.ts +1 -236
- package/src/claude-default-models.ts +37 -0
- package/src/claude-subscription-core.ts +154 -0
- package/src/claude-subscription-oauth.ts +352 -0
- package/src/claude-subscription-profile.ts +73 -0
- package/src/claude-subscription-usage.ts +51 -0
- package/src/claude-workspace-connection.ts +82 -0
- package/src/codemode.ts +236 -39
- package/src/codex-realtime.ts +181 -53
- package/src/codex-session-pointers.ts +80 -0
- package/src/company-brain-okf.ts +1 -1
- package/src/connected-machine-computer-access.ts +2 -1
- package/src/connection-ownership.ts +19 -13
- package/src/controller-data-plane.ts +34 -0
- package/src/credit-promotion-snapshot.ts +36 -0
- package/src/editable-artifact-production.ts +10 -0
- package/src/editable-artifact-source-session.ts +123 -0
- package/src/editable-artifact-websocket.ts +16 -3
- package/src/fatal-process-boundary.ts +67 -3
- package/src/gateway-realtime.ts +12 -8
- package/src/github-access.ts +31 -5
- package/src/github-browser-flow.ts +1 -1
- package/src/http/acting-person.ts +105 -0
- package/src/http/api-contract-compat.ts +49 -0
- package/src/http/api-error.ts +210 -2
- package/src/http/auth.ts +68 -4
- package/src/http/cors.ts +37 -5
- package/src/http/deprecation.ts +118 -0
- package/src/http/keyed-admission.ts +29 -0
- package/src/http/local-browser-boundary.ts +343 -0
- package/src/http/metrics-listener.ts +57 -0
- package/src/http/path-identifier.ts +54 -0
- package/src/http/registered-route-label.ts +34 -0
- package/src/http/rejection-telemetry.ts +120 -0
- package/src/http/request-body.ts +127 -0
- package/src/http/request-source.ts +154 -13
- package/src/http/sse.ts +88 -28
- package/src/http/unmatched-route.ts +60 -0
- package/src/http/user-content.ts +158 -0
- package/src/http/variable-sets-removed.ts +122 -0
- package/src/http/websocket-upgrade-dispatch.ts +29 -0
- package/src/index.ts +107 -57
- package/src/integration-connect-metrics.ts +228 -0
- package/src/integration-endpoint-test.ts +291 -0
- package/src/integrations/api-integrations.ts +81 -7
- package/src/integrations/atlassian.ts +202 -83
- package/src/integrations/fiken.ts +66 -38
- package/src/integrations/github-app-connect.ts +64 -18
- package/src/integrations/github-lens-connect.ts +8 -29
- package/src/integrations/google-drive.ts +448 -152
- package/src/integrations/integration-commit-authority.ts +69 -0
- package/src/integrations/marketplace-plugin.ts +1 -1
- package/src/integrations/oauth-client.ts +733 -193
- package/src/integrations/oauth-profiles.ts +116 -45
- package/src/integrations/onepassword-vault-provider.ts +16 -0
- package/src/integrations/personal-github-repositories.ts +1 -1
- package/src/integrations/personal-github.ts +61 -35
- package/src/integrations/provider-oauth.ts +67 -85
- package/src/integrations/slack-app-home.ts +5 -5
- package/src/integrations/slack-bot.ts +893 -201
- package/src/integrations/slack-file-upload-flow.ts +147 -0
- package/src/integrations/slack-install.ts +7 -7
- package/src/integrations/slack-interactions.ts +1589 -299
- package/src/integrations/slack-mrkdwn.ts +193 -0
- package/src/integrations/slack-routing.ts +2 -2
- package/src/integrations/slack-session-defaults.ts +224 -0
- package/src/integrations/slack-task-file-upload.ts +323 -0
- package/src/integrations/slack-tool-review.ts +65 -0
- package/src/integrations/social-oauth.ts +90 -58
- package/src/interaction-agent-access.ts +40 -20
- package/src/interaction-agent-version.ts +85 -0
- package/src/interaction-holder-heartbeat.ts +24 -8
- package/src/interaction-metrics.ts +164 -2
- package/src/interaction-route-stages.ts +94 -0
- package/src/interaction-secrets-vault.ts +213 -0
- package/src/interaction-secrets.ts +426 -0
- package/src/mcp/action-catalog-types.ts +14 -0
- package/src/mcp/action-catalog.gen.ts +8014 -0
- package/src/mcp/action-descriptions.ts +299 -0
- package/src/mcp/action-schema-imports.gen.ts +304 -0
- package/src/mcp/capability-account-readiness.ts +17 -0
- package/src/mcp/child-read-evidence.ts +19 -0
- package/src/mcp/company-profile-agent-admin.ts +13 -0
- package/src/mcp/contract-input.ts +165 -0
- package/src/mcp/documents.ts +9 -2
- package/src/mcp/editable-artifacts.ts +29 -11
- package/src/mcp/files.ts +5 -1
- package/src/mcp/first-party-tool-permissions.ts +7 -285
- package/src/mcp/knowledge-entries.ts +108 -34
- package/src/mcp/notification-tools.ts +480 -0
- package/src/mcp/orchestration-failure-diagnostic.ts +43 -0
- package/src/mcp/scheduled-task-input.ts +89 -0
- package/src/mcp/scheduled-task-view.ts +63 -3
- package/src/mcp/secret-tools.ts +185 -0
- package/src/mcp/server.ts +2213 -1116
- package/src/mcp/session-admin-access-tools.ts +163 -0
- package/src/mcp/session-event-errors.ts +123 -0
- package/src/mcp/session-event-view.ts +395 -13
- package/src/mcp/session-view.ts +3 -0
- package/src/mcp/session-wait.ts +67 -7
- package/src/mcp/site-agent-clamp-tools.ts +140 -0
- package/src/mcp/site-data-tools.ts +339 -0
- package/src/mcp/site-publication-tools.ts +201 -0
- package/src/mcp/site-sql-tools.ts +427 -0
- package/src/mcp-oauth.ts +635 -45
- package/src/model-calls/chat-completions.ts +352 -0
- package/src/model-calls/pending-usage-settlements.ts +125 -0
- package/src/model-calls/service.ts +694 -0
- package/src/model-catalog.ts +68 -17
- package/src/native-push-dispatch.ts +457 -0
- package/src/observability.ts +15 -1
- package/src/organization-mcp.ts +485 -0
- package/src/prepared-mcp-actor.ts +154 -0
- package/src/prepared-mcp-permissions.ts +38 -0
- package/src/routes/analytics-consent.ts +123 -0
- package/src/routes/api-integrations.ts +268 -9
- package/src/routes/api-keys.ts +164 -25
- package/src/routes/artifact-catalog.ts +111 -0
- package/src/routes/automations.ts +163 -90
- package/src/routes/billing.ts +535 -59
- package/src/routes/browser-sessions.ts +966 -342
- package/src/routes/canonical-human-identities.ts +61 -0
- package/src/routes/capabilities.ts +112 -5
- package/src/routes/channels.ts +4 -3
- package/src/routes/chat-completions.ts +235 -0
- package/src/routes/claude-core-usage.ts +147 -0
- package/src/routes/claude-subscription-accounts.ts +233 -0
- package/src/routes/claude-subscription-oauth.ts +175 -0
- package/src/routes/client-errors.ts +394 -0
- package/src/routes/codex-core.ts +687 -0
- package/src/routes/codex.ts +1277 -1119
- package/src/routes/company-brain.ts +4 -0
- package/src/routes/computer-sessions.ts +319 -85
- package/src/routes/connect.ts +277 -175
- package/src/routes/connections.ts +690 -363
- package/src/routes/desktop-distribution.ts +217 -0
- package/src/routes/documents.ts +13 -7
- package/src/routes/editable-artifacts.ts +74 -4
- package/src/routes/enrollments.ts +110 -16
- package/src/routes/external-identity-links.ts +13 -12
- package/src/routes/files.ts +41 -16
- package/src/routes/github-browser-fonts.gen.ts +12 -0
- package/src/routes/github-browser-pages.ts +258 -0
- package/src/routes/github.ts +200 -75
- package/src/routes/inbox.ts +353 -0
- package/src/routes/insights-response-cache.ts +83 -0
- package/src/routes/insights-usage.ts +345 -0
- package/src/routes/insights.ts +103 -8
- package/src/routes/install.ts +51 -10
- package/src/routes/integration-facets.ts +70 -28
- package/src/routes/interaction-placement-message.ts +12 -0
- package/src/routes/knowledge.ts +136 -27
- package/src/routes/machines.ts +189 -135
- package/src/routes/managed-auth-session-sets.ts +43 -10
- package/src/routes/managed-onboarding.ts +39 -19
- package/src/routes/managed-sign-in-methods.ts +433 -0
- package/src/routes/memory-slack-publications.ts +3 -2
- package/src/routes/model-account-choice.ts +194 -0
- package/src/routes/model-account-settings.ts +235 -0
- package/src/routes/model-connection-access.ts +210 -48
- package/src/routes/native-app-auth.ts +296 -0
- package/src/routes/organization-integration-policy.ts +116 -0
- package/src/routes/organization-integrations.ts +326 -0
- package/src/routes/organization-mcp-connections.ts +183 -0
- package/src/routes/organization-memberships.ts +205 -19
- package/src/routes/organization-model-defaults.ts +110 -0
- package/src/routes/organization-model-providers.ts +74 -3
- package/src/routes/organization-service-accounts.ts +128 -0
- package/src/routes/organization-sessions.ts +21 -5
- package/src/routes/personal-api-keys.ts +184 -0
- package/src/routes/personal-github-git-broker.ts +2 -2
- package/src/routes/personal-github.ts +9 -4
- package/src/routes/plugins.ts +46 -7
- package/src/routes/pr-review-github.ts +105 -118
- package/src/routes/pr-review.ts +88 -82
- package/src/routes/preference-registry.ts +18 -0
- package/src/routes/rigs.ts +48 -13
- package/src/routes/scheduled-tasks.ts +347 -27
- package/src/routes/session-admin-access.ts +271 -0
- package/src/routes/session-artifact-associations.ts +164 -0
- package/src/routes/session-history-imports.ts +305 -0
- package/src/routes/sessions.ts +1682 -363
- package/src/routes/site-data.ts +340 -0
- package/src/routes/site-publishing.ts +584 -0
- package/src/routes/site-runtime.ts +1944 -0
- package/src/routes/site-sql.ts +263 -0
- package/src/routes/skill-content.ts +72 -7
- package/src/routes/skills.ts +5 -4
- package/src/routes/slack-bot-access.ts +108 -0
- package/src/routes/social.ts +27 -15
- package/src/routes/subscription-account-pools.ts +209 -0
- package/src/routes/subscription-core-http.ts +18 -0
- package/src/routes/subscription-pool-access.ts +180 -0
- package/src/routes/subscription-pool-core.ts +552 -0
- package/src/routes/supergrok.ts +174 -655
- package/src/routes/transcription-recordings.ts +71 -4
- package/src/routes/transcriptions.ts +26 -2
- package/src/routes/usage-allowances.ts +381 -0
- package/src/routes/variable-sets-removed.ts +19 -0
- package/src/routes/vault-providers.ts +782 -0
- package/src/routes/vaults.ts +421 -0
- package/src/routes/video-generation.ts +30 -12
- package/src/routes/workspace-artifacts.ts +165 -53
- package/src/routes/workspace-integrations.ts +576 -0
- package/src/routes/workspace-model-providers.ts +148 -0
- package/src/routes/workspace-state.ts +4 -0
- package/src/routes/workspaces.ts +625 -117
- package/src/sandbox/agent-update.ts +315 -0
- package/src/sandbox/auth-callout.ts +104 -21
- package/src/sandbox/channel-a.ts +150 -49
- package/src/sandbox/enrollment.ts +131 -22
- package/src/sandbox/machine-audience.ts +192 -0
- package/src/sandbox/machines.ts +75 -31
- package/src/sandbox/metrics-ingestion.ts +133 -2
- package/src/sandbox/rematerialize.ts +51 -21
- package/src/sandbox/screen-control.ts +289 -0
- package/src/sandbox/viewer.ts +33 -58
- package/src/sandbox-file-artifacts.ts +53 -27
- package/src/scheduled-task-blockers.ts +293 -0
- package/src/scheduled-task-deletion.ts +13 -3
- package/src/scheduled-task-lifecycle.ts +84 -0
- package/src/secret-answers.ts +119 -0
- package/src/session-create-observability.ts +46 -0
- package/src/session-realtime-context.ts +26 -4
- package/src/signed-state-secret.ts +58 -0
- package/src/site-agent-guard.ts +261 -0
- package/src/site-data/operations.ts +777 -0
- package/src/site-data/service.ts +1043 -0
- package/src/site-frame-guard.ts +107 -0
- package/src/site-publishing/agent-data-tools.ts +250 -0
- package/src/site-publishing/agent-model.ts +127 -0
- package/src/site-publishing/agent-session.ts +148 -0
- package/src/site-publishing/content-scanner.ts +93 -0
- package/src/site-publishing/edge-sync.ts +148 -0
- package/src/site-publishing/gateway-signature.ts +198 -0
- package/src/site-publishing/limits.ts +315 -0
- package/src/site-publishing/page-tool-authority.ts +111 -0
- package/src/site-publishing/platform-usage.ts +479 -0
- package/src/site-publishing/platform.ts +98 -0
- package/src/site-publishing/readiness.ts +330 -0
- package/src/site-publishing/runtime.ts +831 -0
- package/src/site-publishing/service.ts +1282 -0
- package/src/site-publishing/session-events.ts +262 -0
- package/src/site-publishing/session-views.ts +262 -0
- package/src/site-publishing/viewer.ts +75 -0
- package/src/site-session-origin.ts +91 -6
- package/src/site-sql/format.ts +134 -0
- package/src/site-sql/gateway.ts +168 -0
- package/src/site-sql/service.ts +1636 -0
- package/src/site-uploads.ts +27 -5
- package/src/temporal-schedule-sync.ts +74 -0
- package/src/transcription/billing-refusal.ts +14 -0
- package/src/transcription/normalize.ts +77 -0
- package/src/transcription/providers/azure-mai.ts +98 -0
- package/src/transcription/providers/azure-openai.ts +16 -7
- package/src/transcription/providers/codex-subscription.ts +190 -67
- package/src/transcription/providers/openai.ts +79 -5
- package/src/transcription/providers/subscription-core.ts +125 -0
- package/src/transcription/providers/xai-subscription.ts +81 -83
- package/src/transcription/segmenter.ts +7 -1
- package/src/transcription/service.ts +239 -10
- package/src/transcription/wav-duration.ts +39 -0
- package/src/workspace-deletion.ts +25 -1
- package/src/workspace-tool-gateway-attestations.ts +133 -0
- package/src/workspace-tool-gateway-observability.ts +64 -0
- package/src/workspace-tool-gateway-secrets.ts +104 -0
- package/src/workspace-tool-gateway.ts +921 -103
- package/src/workspace-tool-target-fence.ts +380 -0
- package/src/workspace-tool-target.ts +554 -0
- package/src/workspace-webhook-dispatch.ts +225 -0
- package/src/xai-realtime.ts +85 -81
- package/src/xai-subscription-core.ts +172 -0
- package/dist/browser-auth-broker.d.ts +0 -20
- package/dist/chunk-AJ62T5QN.js.map +0 -1
- package/dist/connection-authority-owner.d.ts +0 -59
- package/dist/routes/connection-authorities.d.ts +0 -3
- package/dist/routes/environments.d.ts +0 -5
- package/dist/routes/host-mcp-bindings.d.ts +0 -5
- package/dist/routes/host-mcp-resolvers.d.ts +0 -3
- package/dist/routes/packs.d.ts +0 -3
- package/dist/session-event-view-MU3HMNV3.js +0 -264
- package/dist/session-event-view-MU3HMNV3.js.map +0 -1
- package/dist/xai-subscription-auth.d.ts +0 -23
- package/src/browser-auth-broker.ts +0 -155
- package/src/connection-authority-owner.ts +0 -61
- package/src/routes/connection-authorities.ts +0 -139
- package/src/routes/environments.ts +0 -388
- package/src/routes/host-mcp-bindings.ts +0 -122
- package/src/routes/host-mcp-resolvers.ts +0 -41
- package/src/routes/packs.ts +0 -784
- package/src/xai-subscription-auth.ts +0 -139
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import type { Hono } from "hono";
|
|
4
|
+
import { HTTPException } from "hono/http-exception";
|
|
5
|
+
import type { ApiRouteDeps } from "@opengeni/core";
|
|
6
|
+
|
|
7
|
+
// Public download routes for the desktop app. Like the agent install routes,
|
|
8
|
+
// they are unauthenticated so a person can install the app before signing in:
|
|
9
|
+
//
|
|
10
|
+
// GET /desktop/install.sh the macOS and Linux installer
|
|
11
|
+
// GET /desktop/latest 302 to the current release's page
|
|
12
|
+
// GET /desktop/latest/<asset> 302 to the current release's file
|
|
13
|
+
//
|
|
14
|
+
// <asset> is a version-less release file name such as Opengeni-mac.dmg. The
|
|
15
|
+
// route redirects to Opengeni-<version>-mac.dmg on the desktop-v<version>
|
|
16
|
+
// release. GitHub's repository-wide "latest release" can't be used: agent and
|
|
17
|
+
// package releases share the repository, so "latest" is rarely the desktop app.
|
|
18
|
+
|
|
19
|
+
const INSTALL_DIR =
|
|
20
|
+
[
|
|
21
|
+
new URL("./assets/desktop-install/", import.meta.url),
|
|
22
|
+
new URL("../../../desktop/install/", import.meta.url),
|
|
23
|
+
].find((candidate) => existsSync(candidate)) ??
|
|
24
|
+
new URL("../../../desktop/install/", import.meta.url);
|
|
25
|
+
|
|
26
|
+
export const DESKTOP_INSTALLER_PATH = "/desktop/install.sh";
|
|
27
|
+
export const DESKTOP_RELEASE_PAGE_PATH = "/desktop/latest";
|
|
28
|
+
export const DESKTOP_DOWNLOAD_PREFIX = "/desktop/latest/";
|
|
29
|
+
|
|
30
|
+
// Every file a desktop release publishes for people to download, without its
|
|
31
|
+
// version. A strict list, so the redirect can't point anywhere else.
|
|
32
|
+
const RELEASE_FILE =
|
|
33
|
+
/^Opengeni-(mac\.dmg|mac\.zip|windows\.exe|windows-(?:x64|arm64)\.exe|linux-(?:x86_64|arm64)\.AppImage|linux-(?:amd64|arm64)\.deb|linux-(?:x86_64|aarch64)\.rpm)$/u;
|
|
34
|
+
|
|
35
|
+
const STABLE_VERSION = /^(\d+)\.(\d+)\.(\d+)$/u;
|
|
36
|
+
const TAG_PREFIX = "desktop-v";
|
|
37
|
+
|
|
38
|
+
/** The versioned release file for a version-less name, or null if it isn't one. */
|
|
39
|
+
export function desktopReleaseFile(asset: string, version: string): string | null {
|
|
40
|
+
if (asset === "SHA256SUMS") return asset;
|
|
41
|
+
const match = RELEASE_FILE.exec(asset);
|
|
42
|
+
return match ? `Opengeni-${version}-${match[1]}` : null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The highest stable version among desktop-v<version> tags. */
|
|
46
|
+
export function newestDesktopVersion(tags: readonly string[]): string | null {
|
|
47
|
+
let best: number[] | null = null;
|
|
48
|
+
for (const tag of tags) {
|
|
49
|
+
if (!tag.startsWith(TAG_PREFIX)) continue;
|
|
50
|
+
const match = STABLE_VERSION.exec(tag.slice(TAG_PREFIX.length));
|
|
51
|
+
if (!match) continue;
|
|
52
|
+
const version = match.slice(1).map(Number);
|
|
53
|
+
if (!best || compareVersions(version, best) > 0) best = version;
|
|
54
|
+
}
|
|
55
|
+
return best ? best.join(".") : null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function compareVersions(left: number[], right: number[]): number {
|
|
59
|
+
for (let index = 0; index < 3; index++) {
|
|
60
|
+
const difference = (left[index] ?? 0) - (right[index] ?? 0);
|
|
61
|
+
if (difference !== 0) return difference;
|
|
62
|
+
}
|
|
63
|
+
return 0;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** owner/name for a https://github.com/<owner>/<name>/releases base, else null. */
|
|
67
|
+
export function githubRepositoryOf(releasesBase: string): string | null {
|
|
68
|
+
const match = /^https:\/\/github\.com\/([\w.-]+\/[\w.-]+)\/releases\/?$/u.exec(releasesBase);
|
|
69
|
+
return match?.[1] ?? null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface DesktopVersionResolverOptions {
|
|
73
|
+
/** An operator pin. When set, it is the only version ever served. */
|
|
74
|
+
pinnedVersion?: string | undefined;
|
|
75
|
+
releasesBase: string;
|
|
76
|
+
fetchImpl?: typeof fetch;
|
|
77
|
+
now?: () => number;
|
|
78
|
+
/** How long a looked-up version is reused. */
|
|
79
|
+
freshForMs?: number;
|
|
80
|
+
/** How long to wait before looking again after a failed lookup. */
|
|
81
|
+
retryAfterMs?: number;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* The version /desktop/latest serves: the pin, or the newest desktop-v tag on
|
|
86
|
+
* the release repository. A lookup is shared by concurrent requests and reused
|
|
87
|
+
* for ten minutes, well within GitHub's unauthenticated rate limit. When GitHub
|
|
88
|
+
* is unreachable the last known version keeps serving.
|
|
89
|
+
*/
|
|
90
|
+
export function createDesktopVersionResolver(
|
|
91
|
+
options: DesktopVersionResolverOptions,
|
|
92
|
+
): () => Promise<string> {
|
|
93
|
+
const { pinnedVersion } = options;
|
|
94
|
+
if (pinnedVersion) return async () => pinnedVersion;
|
|
95
|
+
const repository = githubRepositoryOf(options.releasesBase);
|
|
96
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
97
|
+
const now = options.now ?? Date.now;
|
|
98
|
+
const freshForMs = options.freshForMs ?? 10 * 60_000;
|
|
99
|
+
const retryAfterMs = options.retryAfterMs ?? 30_000;
|
|
100
|
+
let known: string | null = null;
|
|
101
|
+
let nextLookupAt = 0;
|
|
102
|
+
let inFlight: Promise<void> | null = null;
|
|
103
|
+
|
|
104
|
+
async function lookUp(): Promise<void> {
|
|
105
|
+
try {
|
|
106
|
+
const response = await fetchImpl(
|
|
107
|
+
`https://api.github.com/repos/${repository}/git/matching-refs/tags/${TAG_PREFIX}`,
|
|
108
|
+
{
|
|
109
|
+
headers: { accept: "application/vnd.github+json", "user-agent": "opengeni-api" },
|
|
110
|
+
signal: AbortSignal.timeout(5_000),
|
|
111
|
+
},
|
|
112
|
+
);
|
|
113
|
+
if (!response.ok) throw new Error(`GitHub answered ${response.status}`);
|
|
114
|
+
const refs = (await response.json()) as Array<{ ref?: unknown }>;
|
|
115
|
+
const version = newestDesktopVersion(
|
|
116
|
+
refs.flatMap((entry) =>
|
|
117
|
+
typeof entry.ref === "string" && entry.ref.startsWith("refs/tags/")
|
|
118
|
+
? [entry.ref.slice("refs/tags/".length)]
|
|
119
|
+
: [],
|
|
120
|
+
),
|
|
121
|
+
);
|
|
122
|
+
if (version) known = version;
|
|
123
|
+
nextLookupAt = now() + (version ? freshForMs : retryAfterMs);
|
|
124
|
+
} catch {
|
|
125
|
+
nextLookupAt = now() + retryAfterMs;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return async () => {
|
|
130
|
+
if (!repository) {
|
|
131
|
+
throw new HTTPException(503, {
|
|
132
|
+
message: "Desktop downloads need OPENGENI_DESKTOP_STABLE_VERSION for this release host.",
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
if (now() >= nextLookupAt) {
|
|
136
|
+
inFlight ??= lookUp().finally(() => {
|
|
137
|
+
inFlight = null;
|
|
138
|
+
});
|
|
139
|
+
// The first lookup has nothing to fall back to; later ones refresh in
|
|
140
|
+
// the background and keep serving the version already known.
|
|
141
|
+
if (!known) await inFlight;
|
|
142
|
+
}
|
|
143
|
+
if (!known) {
|
|
144
|
+
throw new HTTPException(503, { message: "No desktop release is available yet." });
|
|
145
|
+
}
|
|
146
|
+
return known;
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
let installerBody: string | undefined;
|
|
151
|
+
|
|
152
|
+
// The installer resolves releases through this server when it knows the
|
|
153
|
+
// server's address, so a deployment's version pin applies. A copy run from the
|
|
154
|
+
// repository looks the release up on GitHub itself.
|
|
155
|
+
const BASE_URL_MARKER = 'OPENGENI_DESKTOP_DEFAULT_BASE_URL=""';
|
|
156
|
+
|
|
157
|
+
export function desktopInstallerFor(body: string, publicBaseUrl: string | undefined): string {
|
|
158
|
+
if (!publicBaseUrl || !/^https?:\/\//u.test(publicBaseUrl)) return body;
|
|
159
|
+
const base = publicBaseUrl.replace(/\/+$/u, "");
|
|
160
|
+
return body.replace(BASE_URL_MARKER, `OPENGENI_DESKTOP_DEFAULT_BASE_URL="${base}"`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function registerDesktopDistributionRoutes(
|
|
164
|
+
app: Hono,
|
|
165
|
+
deps: ApiRouteDeps,
|
|
166
|
+
resolveVersion = createDesktopVersionResolver({
|
|
167
|
+
pinnedVersion: deps.settings.desktopStableVersion,
|
|
168
|
+
releasesBase: deps.settings.agentReleasesBaseUrl.replace(/\/+$/u, ""),
|
|
169
|
+
}),
|
|
170
|
+
): void {
|
|
171
|
+
const releasesBase = deps.settings.agentReleasesBaseUrl.replace(/\/+$/u, "");
|
|
172
|
+
|
|
173
|
+
app.get(DESKTOP_INSTALLER_PATH, async (c) => {
|
|
174
|
+
installerBody ??= await readFile(new URL("install.sh", INSTALL_DIR), "utf8");
|
|
175
|
+
return c.text(desktopInstallerFor(installerBody, deps.settings.publicBaseUrl), 200, {
|
|
176
|
+
"content-type": "text/x-shellscript; charset=utf-8",
|
|
177
|
+
"cache-control": "public, max-age=300",
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// The release page, listing every platform's download.
|
|
182
|
+
app.get(DESKTOP_RELEASE_PAGE_PATH, async () => {
|
|
183
|
+
const version = await resolveVersion();
|
|
184
|
+
return new Response(null, {
|
|
185
|
+
status: 302,
|
|
186
|
+
headers: {
|
|
187
|
+
location: `${releasesBase}/tag/${TAG_PREFIX}${version}`,
|
|
188
|
+
"cache-control": "public, max-age=300",
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
app.get(`${DESKTOP_DOWNLOAD_PREFIX}:asset`, async (c) => {
|
|
194
|
+
const asset = c.req.param("asset");
|
|
195
|
+
// Validate before resolving, so a bad name never costs a lookup.
|
|
196
|
+
if (!desktopReleaseFile(asset, "0.0.0")) {
|
|
197
|
+
throw new HTTPException(404, { message: "Unknown desktop download." });
|
|
198
|
+
}
|
|
199
|
+
const version = await resolveVersion();
|
|
200
|
+
const file = desktopReleaseFile(asset, version)!;
|
|
201
|
+
return new Response(null, {
|
|
202
|
+
status: 302,
|
|
203
|
+
headers: {
|
|
204
|
+
location: `${releasesBase}/download/${TAG_PREFIX}${version}/${file}`,
|
|
205
|
+
"cache-control": "public, max-age=300",
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function isDesktopDistributionPath(path: string): boolean {
|
|
212
|
+
return (
|
|
213
|
+
path === DESKTOP_INSTALLER_PATH ||
|
|
214
|
+
path === DESKTOP_RELEASE_PAGE_PATH ||
|
|
215
|
+
path.startsWith(DESKTOP_DOWNLOAD_PREFIX)
|
|
216
|
+
);
|
|
217
|
+
}
|
package/src/routes/documents.ts
CHANGED
|
@@ -61,6 +61,8 @@ import {
|
|
|
61
61
|
prepareMcpOAuthWorkspaceToolGateway,
|
|
62
62
|
} from "../workspace-tool-gateway";
|
|
63
63
|
import { sanitizeFilename } from "./files";
|
|
64
|
+
import { userContentSignedGetUrlOptions } from "../http/user-content";
|
|
65
|
+
import { parseRequestBody, parseRequestJson } from "../http/request-body";
|
|
64
66
|
|
|
65
67
|
export function registerDocumentRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
66
68
|
const { db, objectStorage, documentIndexer, getDocumentServices } = deps;
|
|
@@ -93,7 +95,7 @@ export function registerDocumentRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
93
95
|
app.post("/v1/workspaces/:workspaceId/document-bases", async (c) => {
|
|
94
96
|
const workspaceId = c.req.param("workspaceId");
|
|
95
97
|
const grant = await requireAccessGrant(c, deps, workspaceId, "documents:manage");
|
|
96
|
-
const payload =
|
|
98
|
+
const payload = await parseRequestJson(c, CreateDocumentBaseRequest);
|
|
97
99
|
return c.json(
|
|
98
100
|
DocumentBase.parse(
|
|
99
101
|
await createDocumentBase(db, { ...payload, accountId: grant.accountId, workspaceId }),
|
|
@@ -137,7 +139,7 @@ export function registerDocumentRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
137
139
|
throw new HTTPException(403, { message: "missing permission: account:admin" });
|
|
138
140
|
}
|
|
139
141
|
const accountAdminAuthorization = requireAccountAdminAuthorizationStamp(authorization);
|
|
140
|
-
const payload =
|
|
142
|
+
const payload = await parseRequestJson(c, RunDocumentDefaultCollectionBackfillRequest);
|
|
141
143
|
try {
|
|
142
144
|
return c.json(
|
|
143
145
|
DocumentDefaultCollectionBackfill.parse(
|
|
@@ -268,7 +270,7 @@ export function registerDocumentRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
268
270
|
action: "document:index",
|
|
269
271
|
quantity: 0,
|
|
270
272
|
});
|
|
271
|
-
const payload =
|
|
273
|
+
const payload = await parseRequestJson(c, AddDocumentRequest);
|
|
272
274
|
const organizationAuthorityGranted =
|
|
273
275
|
access.accountGrant?.permissions.includes("account:admin") === true;
|
|
274
276
|
if (payload.authorityKind === "organization" && !organizationAuthorityGranted) {
|
|
@@ -338,7 +340,7 @@ export function registerDocumentRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
338
340
|
workspaceId,
|
|
339
341
|
"documents:manage",
|
|
340
342
|
);
|
|
341
|
-
const payload =
|
|
343
|
+
const payload = await parseRequestJson(c, ReclassifyDocumentAuthorityRequest);
|
|
342
344
|
const accountAdminAuthorized = hasAccountAdminAuthority(authorization);
|
|
343
345
|
const accountAdminAuthorization = accountAdminAuthorized
|
|
344
346
|
? requireAccountAdminAuthorizationStamp(authorization)
|
|
@@ -435,7 +437,10 @@ export function registerDocumentRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
435
437
|
if (file.status !== "ready") {
|
|
436
438
|
throw new HTTPException(409, { message: `file is ${file.status}` });
|
|
437
439
|
}
|
|
438
|
-
const signed = await objectStorage.createGetUrl({
|
|
440
|
+
const signed = await objectStorage.createGetUrl({
|
|
441
|
+
key: file.objectKey,
|
|
442
|
+
...userContentSignedGetUrlOptions(file.contentType, file.filename),
|
|
443
|
+
});
|
|
439
444
|
await recordAuditEvent(db, {
|
|
440
445
|
accountId: grant.accountId,
|
|
441
446
|
workspaceId,
|
|
@@ -591,7 +596,7 @@ export function registerDocumentRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
591
596
|
action: "document:index",
|
|
592
597
|
quantity: 0,
|
|
593
598
|
});
|
|
594
|
-
const payload =
|
|
599
|
+
const payload = await parseRequestJson(c, CreateKnowledgeDropRequest);
|
|
595
600
|
const personalFileOwner =
|
|
596
601
|
payload.authorityKind === "personal"
|
|
597
602
|
? (await fileOwnerContextForAccess(deps, access, "documents:manage"))
|
|
@@ -712,7 +717,7 @@ export function registerDocumentRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
712
717
|
);
|
|
713
718
|
const { grant } = authorization;
|
|
714
719
|
const organizationAuthorityGranted = hasAccountAdminAuthority(authorization);
|
|
715
|
-
const payload = MoveDocumentRequest
|
|
720
|
+
const payload = parseRequestBody(MoveDocumentRequest, await c.req.json().catch(() => ({})));
|
|
716
721
|
try {
|
|
717
722
|
const document = await getDocument(db, workspaceId, c.req.param("documentId"), {
|
|
718
723
|
viewerSubjectId: grant.subjectId,
|
|
@@ -818,6 +823,7 @@ export function registerDocumentRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
818
823
|
await requireAccessGrantAuthorization(c, deps, workspaceId, "documents:search"),
|
|
819
824
|
"documents:search",
|
|
820
825
|
),
|
|
826
|
+
settings: deps.settings,
|
|
821
827
|
},
|
|
822
828
|
);
|
|
823
829
|
await server.connect(transport);
|
|
@@ -11,6 +11,9 @@ import {
|
|
|
11
11
|
editableArtifactScope,
|
|
12
12
|
hasPermission,
|
|
13
13
|
requireAccessGrant,
|
|
14
|
+
requireAccessGrantAuthorization,
|
|
15
|
+
externalActorContinuationForAuthorization,
|
|
16
|
+
requirePermission,
|
|
14
17
|
requireSessionAuthorization,
|
|
15
18
|
SessionAuthorizationDeniedError,
|
|
16
19
|
SessionAuthorizationUnavailableError,
|
|
@@ -25,11 +28,13 @@ import {
|
|
|
25
28
|
type EditableArtifactPinnedVersion,
|
|
26
29
|
type EditableArtifactModality,
|
|
27
30
|
} from "@opengeni/core";
|
|
28
|
-
import { listEditableArtifactIdsForSession } from "@opengeni/db";
|
|
31
|
+
import { hasEditableArtifactSessionLink, listEditableArtifactIdsForSession } from "@opengeni/db";
|
|
29
32
|
import type { Context, Hono } from "hono";
|
|
30
33
|
import { z } from "zod";
|
|
31
34
|
|
|
32
35
|
import { ApiHttpError } from "../http/api-error";
|
|
36
|
+
import { USER_CONTENT_SECURITY_HEADERS } from "../http/user-content";
|
|
37
|
+
import { withAccessGrantSessionRlsContext } from "../access-grant-rls";
|
|
33
38
|
|
|
34
39
|
export const EDITABLE_ARTIFACT_HTTP_REQUEST_MAX_BYTES = 4 * 1024;
|
|
35
40
|
export const EDITABLE_ARTIFACT_EXPORT_REQUEST_MAX_BYTES = 260 * 1024;
|
|
@@ -77,6 +82,7 @@ const ListEditableArtifactsQuery = z
|
|
|
77
82
|
|
|
78
83
|
const MintEditableArtifactLiveTicketRequest = z
|
|
79
84
|
.object({
|
|
85
|
+
sourceSessionId: SessionId.optional(),
|
|
80
86
|
replicaId: ReplicaId,
|
|
81
87
|
modality: z.enum(["document", "spreadsheet", "presentation"]),
|
|
82
88
|
liveProtocolVersion: PositiveProtocolVersion,
|
|
@@ -471,7 +477,13 @@ export function registerEditableArtifactRoutes(
|
|
|
471
477
|
|
|
472
478
|
// Workspace authentication precedes path/body parsing. Exact artifact
|
|
473
479
|
// permission is intentionally enforced inside the injected application.
|
|
474
|
-
const
|
|
480
|
+
const authorization = await requireAccessGrantAuthorization(
|
|
481
|
+
context,
|
|
482
|
+
deps,
|
|
483
|
+
workspaceId,
|
|
484
|
+
"artifacts:read",
|
|
485
|
+
);
|
|
486
|
+
const grant = authorization.grant;
|
|
475
487
|
const artifactId = editableArtifactId(parseArtifactId(context.req.param("artifactId")));
|
|
476
488
|
const body = await parseBoundedTicketRequest(context);
|
|
477
489
|
const actor = editableArtifactActorForGrant(grant, body.replicaId);
|
|
@@ -479,6 +491,18 @@ export function registerEditableArtifactRoutes(
|
|
|
479
491
|
accountId: grant.accountId,
|
|
480
492
|
workspaceId,
|
|
481
493
|
});
|
|
494
|
+
if (body.sourceSessionId) {
|
|
495
|
+
requirePermission(grant, "sessions:read");
|
|
496
|
+
await withAccessGrantSessionRlsContext(deps, grant, async () => {
|
|
497
|
+
await authorizeSourceSession(deps, grant, body.sourceSessionId!);
|
|
498
|
+
if (
|
|
499
|
+
!(await hasEditableArtifactSessionLink(deps.db, scope, body.sourceSessionId!, artifactId))
|
|
500
|
+
) {
|
|
501
|
+
throw applicationHttpError("not_found");
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
const externalContinuation = externalActorContinuationForAuthorization(authorization);
|
|
482
506
|
|
|
483
507
|
let rawTicket: MintEditableArtifactLiveTicketOutput;
|
|
484
508
|
try {
|
|
@@ -494,6 +518,44 @@ export function registerEditableArtifactRoutes(
|
|
|
494
518
|
commandProtocolVersion: body.commandProtocolVersion,
|
|
495
519
|
committedTransactionProtocolVersion: body.committedTransactionProtocolVersion,
|
|
496
520
|
allowEdit: hasPermission(grant.permissions, "artifacts:publish"),
|
|
521
|
+
...(body.sourceSessionId
|
|
522
|
+
? {
|
|
523
|
+
sourceSessionAuthority: {
|
|
524
|
+
sessionId: body.sourceSessionId,
|
|
525
|
+
grant: {
|
|
526
|
+
accountId: grant.accountId,
|
|
527
|
+
workspaceId: grant.workspaceId,
|
|
528
|
+
subjectId: grant.subjectId,
|
|
529
|
+
...(grant.principalKind ? { principalKind: grant.principalKind } : {}),
|
|
530
|
+
...(grant.metadata
|
|
531
|
+
? {
|
|
532
|
+
metadata: Object.fromEntries(
|
|
533
|
+
Object.entries(grant.metadata).filter(([key]) =>
|
|
534
|
+
[
|
|
535
|
+
"externalActor",
|
|
536
|
+
"delegated",
|
|
537
|
+
"sessionId",
|
|
538
|
+
"turnId",
|
|
539
|
+
"attemptId",
|
|
540
|
+
"executionGeneration",
|
|
541
|
+
].includes(key),
|
|
542
|
+
),
|
|
543
|
+
),
|
|
544
|
+
}
|
|
545
|
+
: {}),
|
|
546
|
+
// Bind only editor capabilities, not a broad delegated wildcard.
|
|
547
|
+
permissions: [
|
|
548
|
+
"sessions:read",
|
|
549
|
+
"artifacts:read",
|
|
550
|
+
...(hasPermission(grant.permissions, "artifacts:publish")
|
|
551
|
+
? ["artifacts:publish" as const]
|
|
552
|
+
: []),
|
|
553
|
+
],
|
|
554
|
+
},
|
|
555
|
+
...(externalContinuation ? { externalContinuation } : {}),
|
|
556
|
+
},
|
|
557
|
+
}
|
|
558
|
+
: {}),
|
|
497
559
|
});
|
|
498
560
|
} catch (error) {
|
|
499
561
|
throw editableArtifactHttpError(error);
|
|
@@ -547,6 +609,14 @@ export function registerEditableArtifactRoutes(
|
|
|
547
609
|
const body = await parseBoundedMaterializationRequest(context);
|
|
548
610
|
const scope = editableArtifactScope({ accountId: grant.accountId, workspaceId });
|
|
549
611
|
const actor = editableArtifactActorForGrant(grant, body.replicaId);
|
|
612
|
+
// Without a materializer workload the job would stay queued forever.
|
|
613
|
+
if (!deps.settings.artifactMaterializerDeployed) {
|
|
614
|
+
throw new ApiHttpError(503, {
|
|
615
|
+
code: "upstream_unavailable",
|
|
616
|
+
message: "File export is not available on this deployment.",
|
|
617
|
+
retryable: false,
|
|
618
|
+
});
|
|
619
|
+
}
|
|
550
620
|
try {
|
|
551
621
|
const result = await requireEditableArtifactExports(deps).enqueueMaterialization({
|
|
552
622
|
scope,
|
|
@@ -616,7 +686,7 @@ export function registerEditableArtifactRoutes(
|
|
|
616
686
|
"content-length": String(download.byteSize),
|
|
617
687
|
"content-type": download.mimeType,
|
|
618
688
|
etag: `"${download.contentHash}"`,
|
|
619
|
-
|
|
689
|
+
...USER_CONTENT_SECURITY_HEADERS,
|
|
620
690
|
},
|
|
621
691
|
});
|
|
622
692
|
});
|
|
@@ -1067,7 +1137,7 @@ export function editableArtifactHttpError(error: unknown): unknown {
|
|
|
1067
1137
|
case "unsupported_format":
|
|
1068
1138
|
return new ApiHttpError(422, {
|
|
1069
1139
|
code: "validation_failed",
|
|
1070
|
-
message:
|
|
1140
|
+
message: error.message,
|
|
1071
1141
|
});
|
|
1072
1142
|
case "not_ready":
|
|
1073
1143
|
return new ApiHttpError(409, {
|
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
MintEnrollTokenRequest,
|
|
38
38
|
MintEnrollTokenResponse,
|
|
39
39
|
RemoveEnrollmentRequest,
|
|
40
|
+
RenewEnrollmentRequest,
|
|
40
41
|
RevokeEnrollmentResponse,
|
|
41
42
|
type EnrollmentArch,
|
|
42
43
|
type EnrollmentOs,
|
|
@@ -46,10 +47,13 @@ import {
|
|
|
46
47
|
listEnrollments,
|
|
47
48
|
MachineRemovalIdempotencyError,
|
|
48
49
|
MachineRemovalRevisionConflictError,
|
|
50
|
+
PersonalMachineConnectedElsewhereError,
|
|
49
51
|
removeEnrollment,
|
|
50
52
|
} from "@opengeni/db";
|
|
51
53
|
import type { Context, Hono } from "hono";
|
|
52
54
|
import { HTTPException } from "hono/http-exception";
|
|
55
|
+
import { trustedRequestSourceRateLimitKey } from "../http/request-source";
|
|
56
|
+
import { isPersonalConnectionOwnerPrincipal } from "../connection-ownership";
|
|
53
57
|
import {
|
|
54
58
|
requireAccessGrant,
|
|
55
59
|
requireAccessGrantAuthorization,
|
|
@@ -64,9 +68,39 @@ import {
|
|
|
64
68
|
mintEnrollToken,
|
|
65
69
|
pollDeviceEnrollment,
|
|
66
70
|
startDeviceEnrollment,
|
|
71
|
+
renewEnrollmentCredentials,
|
|
67
72
|
toLookupResponse,
|
|
68
73
|
} from "../sandbox/enrollment";
|
|
69
74
|
|
|
75
|
+
const ENROLLMENT_OUTCOMES = new Set([
|
|
76
|
+
"ok",
|
|
77
|
+
"authorized",
|
|
78
|
+
"expired",
|
|
79
|
+
"denied",
|
|
80
|
+
"disabled",
|
|
81
|
+
"invalid",
|
|
82
|
+
"used",
|
|
83
|
+
"unauthorized",
|
|
84
|
+
]);
|
|
85
|
+
|
|
86
|
+
// A personal machine reconnects only from the workspace it was first connected
|
|
87
|
+
// from. The install script and the approval page show this message verbatim.
|
|
88
|
+
const PERSONAL_MACHINE_ELSEWHERE_MESSAGE =
|
|
89
|
+
"this machine was first connected as a personal machine from another workspace; connect it again from that workspace";
|
|
90
|
+
|
|
91
|
+
/** Machine enrollment decisions that never surface as an HTTP error status. */
|
|
92
|
+
function recordEnrollment(deps: ApiRouteDeps, flow: string, outcome: string): void {
|
|
93
|
+
try {
|
|
94
|
+
deps.observability?.incrementCounter({
|
|
95
|
+
name: "opengeni_machine_enrollment_total",
|
|
96
|
+
help: "Connected Machine enrollment decisions by flow (device_start, device_poll, token_exchange, renew) and closed outcome.",
|
|
97
|
+
labels: { flow, outcome: ENROLLMENT_OUTCOMES.has(outcome) ? outcome : "other" },
|
|
98
|
+
});
|
|
99
|
+
} catch {
|
|
100
|
+
// Telemetry never changes the enrollment decision.
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
70
104
|
export function registerEnrollmentRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
71
105
|
const { settings, db } = deps;
|
|
72
106
|
|
|
@@ -84,7 +118,8 @@ export function registerEnrollmentRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
84
118
|
// poll). The relay tier owns the heavy stream rate-limiting; this
|
|
85
119
|
// is the application-tier abuse cap on the device-flow endpoints. Per-IP buckets
|
|
86
120
|
// are pruned lazily. Not a distributed limiter (one replica per bucket) — that is
|
|
87
|
-
// acceptable for a bounded,
|
|
121
|
+
// acceptable for a bounded, short-TTL flow. These routes are public even behind
|
|
122
|
+
// the deployment key, because a machine never holds that key.
|
|
88
123
|
const startLimiter = new TokenBucket({ capacity: 10, refillPerSecond: 0.5 });
|
|
89
124
|
const pollLimiter = new TokenBucket({ capacity: 60, refillPerSecond: 2 });
|
|
90
125
|
// The click-Grant approve-page lookup (authenticated, but capped against a
|
|
@@ -98,12 +133,28 @@ export function registerEnrollmentRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
98
133
|
});
|
|
99
134
|
|
|
100
135
|
function rateLimit(c: Context, limiter: TokenBucket): void {
|
|
101
|
-
const ip =
|
|
136
|
+
const ip = trustedRequestSourceRateLimitKey(c, settings);
|
|
102
137
|
if (!limiter.take(ip)) {
|
|
103
138
|
throw new HTTPException(429, { message: "too many requests; slow down" });
|
|
104
139
|
}
|
|
105
140
|
}
|
|
106
141
|
|
|
142
|
+
// Machine authentication: signed install-key proof + current enrollment grant.
|
|
143
|
+
app.post("/v1/enrollments/renew", async (c) => {
|
|
144
|
+
assertSelfhostedEnabled();
|
|
145
|
+
rateLimit(c, exchangeLimiter);
|
|
146
|
+
const parsed = RenewEnrollmentRequest.safeParse(await c.req.json().catch(() => null));
|
|
147
|
+
if (!parsed.success) throw new HTTPException(400, { message: "invalid renewal request" });
|
|
148
|
+
const credentials = await renewEnrollmentCredentials({ db, settings }, parsed.data);
|
|
149
|
+
recordEnrollment(deps, "renew", credentials ? "ok" : "unauthorized");
|
|
150
|
+
if (!credentials)
|
|
151
|
+
throw new HTTPException(401, {
|
|
152
|
+
message: "machine renewal not authorized",
|
|
153
|
+
});
|
|
154
|
+
c.header("Cache-Control", "no-store");
|
|
155
|
+
return c.json(EnrollTokenExchangeResponse.parse({ credentials }), 200);
|
|
156
|
+
});
|
|
157
|
+
|
|
107
158
|
// ── POST /enrollments/device/start (agent-side, user-unauthenticated) ───────
|
|
108
159
|
app.post("/v1/enrollments/device/start", async (c) => {
|
|
109
160
|
assertSelfhostedEnabled();
|
|
@@ -136,6 +187,7 @@ export function registerEnrollmentRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
136
187
|
verificationOrigin: settings.publicBaseUrl ?? new URL(c.req.url).origin,
|
|
137
188
|
},
|
|
138
189
|
);
|
|
190
|
+
recordEnrollment(deps, "device_start", "ok");
|
|
139
191
|
return c.json(result, 201);
|
|
140
192
|
});
|
|
141
193
|
|
|
@@ -151,6 +203,8 @@ export function registerEnrollmentRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
151
203
|
{ db, settings },
|
|
152
204
|
{ deviceCode: parsed.data.deviceCode },
|
|
153
205
|
);
|
|
206
|
+
// `pending` is the normal polling loop; count only decisions.
|
|
207
|
+
if (result.state !== "pending") recordEnrollment(deps, "device_poll", result.state);
|
|
154
208
|
return c.json(result, 200);
|
|
155
209
|
});
|
|
156
210
|
|
|
@@ -220,6 +274,7 @@ export function registerEnrollmentRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
220
274
|
canOfferDisplay: body.canOfferDisplay,
|
|
221
275
|
},
|
|
222
276
|
);
|
|
277
|
+
recordEnrollment(deps, "token_exchange", result.ok ? "ok" : result.reason);
|
|
223
278
|
if (!result.ok) {
|
|
224
279
|
if (result.reason === "disabled") {
|
|
225
280
|
// The credential plane is off for this deployment (no signing secret).
|
|
@@ -227,6 +282,30 @@ export function registerEnrollmentRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
227
282
|
message: "enrollment credential plane is not configured",
|
|
228
283
|
});
|
|
229
284
|
}
|
|
285
|
+
if (result.reason === "used") {
|
|
286
|
+
// Single-use: another machine already redeemed this token. Same 401 as
|
|
287
|
+
// an invalid token, with a message the install script prints verbatim.
|
|
288
|
+
throw new HTTPException(401, {
|
|
289
|
+
message:
|
|
290
|
+
"this connect command was already used to connect another machine; create a new one for this machine",
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
if (result.reason === "owner_unavailable") {
|
|
294
|
+
// A personal command whose creator can no longer own a machine here.
|
|
295
|
+
throw new HTTPException(403, {
|
|
296
|
+
message:
|
|
297
|
+
"the person who created this connect command can no longer connect machines to this workspace; create a new command",
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
if (result.reason === "connected_elsewhere") {
|
|
301
|
+
// Nothing changed and the command was not used up.
|
|
302
|
+
throw new HTTPException(409, { message: PERSONAL_MACHINE_ELSEWHERE_MESSAGE });
|
|
303
|
+
}
|
|
304
|
+
if (result.reason === "busy") {
|
|
305
|
+
throw new HTTPException(503, {
|
|
306
|
+
message: "workspace membership is changing; run the connect command again",
|
|
307
|
+
});
|
|
308
|
+
}
|
|
230
309
|
// An invalid / expired / wrong-typ token — the token is the auth, so 401.
|
|
231
310
|
throw new HTTPException(401, {
|
|
232
311
|
message: "invalid or expired enroll token",
|
|
@@ -287,7 +366,13 @@ export function registerEnrollmentRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
287
366
|
approvedBySubjectId: grant.subjectId,
|
|
288
367
|
approvedBySubjectLabel: grant.subjectLabel ?? null,
|
|
289
368
|
},
|
|
290
|
-
)
|
|
369
|
+
).catch((error: unknown) => {
|
|
370
|
+
if (error instanceof PersonalMachineConnectedElsewhereError) {
|
|
371
|
+
// Nothing changed; the request stays pending.
|
|
372
|
+
throw new HTTPException(409, { message: PERSONAL_MACHINE_ELSEWHERE_MESSAGE });
|
|
373
|
+
}
|
|
374
|
+
throw error;
|
|
375
|
+
});
|
|
291
376
|
if (!approved) {
|
|
292
377
|
// An unknown / expired / already-terminal user_code in this workspace.
|
|
293
378
|
throw new HTTPException(404, {
|
|
@@ -331,9 +416,22 @@ export function registerEnrollmentRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
331
416
|
// Mint a short-TTL headless enroll token. Mirrors approve (enrollments:manage,
|
|
332
417
|
// requireAccessGrant BEFORE the parse). The accountId comes from the grant. No
|
|
333
418
|
// signing secret → 503/disabled (mirror poll). The token is SECRET — never logged.
|
|
419
|
+
// `scope: "user"` signs the authenticated person as the machine's owner. Only a
|
|
420
|
+
// verified person may ask for it: someone signed in, or a credential acting as
|
|
421
|
+
// them (a personal API key, or an organization key or organization MCP client
|
|
422
|
+
// acting as that person). A key, service or agent acting as itself gets 422.
|
|
423
|
+
// The owner is always the caller, never a request field.
|
|
334
424
|
app.post("/v1/workspaces/:workspaceId/enrollments/token", async (c) => {
|
|
335
425
|
const workspaceId = c.req.param("workspaceId");
|
|
336
|
-
|
|
426
|
+
// The permission is part of the authorization, so a request that lacks it
|
|
427
|
+
// is refused before any first-use workspace membership is provisioned.
|
|
428
|
+
const authorization = await requireAccessGrantAuthorization(
|
|
429
|
+
c,
|
|
430
|
+
deps,
|
|
431
|
+
workspaceId,
|
|
432
|
+
"enrollments:manage",
|
|
433
|
+
);
|
|
434
|
+
const grant = authorization.grant;
|
|
337
435
|
assertSelfhostedEnabled();
|
|
338
436
|
// All fields are optional/defaulted, so an empty POST body is valid (default
|
|
339
437
|
// allowScreenControl=false). Coalesce a missing/empty body to {} before parse.
|
|
@@ -343,12 +441,20 @@ export function registerEnrollmentRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
343
441
|
message: "invalid mint-enroll-token request",
|
|
344
442
|
});
|
|
345
443
|
}
|
|
444
|
+
const personal = parsed.data.scope === "user";
|
|
445
|
+
if (personal && !isPersonalConnectionOwnerPrincipal(authorization)) {
|
|
446
|
+
throw new HTTPException(422, {
|
|
447
|
+
message:
|
|
448
|
+
"only a person, or a credential acting as that person, can connect a machine for themselves; keys, services and agents acting as themselves can connect workspace machines",
|
|
449
|
+
});
|
|
450
|
+
}
|
|
346
451
|
const minted = await mintEnrollToken(
|
|
347
452
|
{ db, settings },
|
|
348
453
|
{
|
|
349
454
|
accountId: grant.accountId,
|
|
350
455
|
workspaceId,
|
|
351
456
|
allowScreenControl: parsed.data.allowScreenControl,
|
|
457
|
+
...(personal ? { ownerSubjectId: grant.subjectId } : {}),
|
|
352
458
|
},
|
|
353
459
|
);
|
|
354
460
|
if (!minted) {
|
|
@@ -471,18 +577,6 @@ export function registerEnrollmentRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
471
577
|
});
|
|
472
578
|
}
|
|
473
579
|
|
|
474
|
-
// The remote client IP for the per-IP rate-limit bucket. Honors the proxy's
|
|
475
|
-
// X-Forwarded-For (the first hop) when present, falling back to a constant key when
|
|
476
|
-
// neither is available (the bucket then caps the whole edge — still a useful cap).
|
|
477
|
-
function clientIp(c: Context): string {
|
|
478
|
-
const xff = c.req.header("x-forwarded-for");
|
|
479
|
-
if (xff) {
|
|
480
|
-
const first = xff.split(",")[0]?.trim();
|
|
481
|
-
if (first) return first;
|
|
482
|
-
}
|
|
483
|
-
return c.req.header("x-real-ip")?.trim() || "unknown";
|
|
484
|
-
}
|
|
485
|
-
|
|
486
580
|
// A minimal per-key token bucket. capacity = burst; refillPerSecond = sustained
|
|
487
581
|
// rate. Buckets are created lazily and reset their tokens by elapsed time on each
|
|
488
582
|
// take, so an idle key fully refills without a background timer.
|