@opengeni/api-router 0.30.3 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-grant-rls.d.ts +3 -0
- package/dist/admission-trace.d.ts +8 -0
- package/dist/api-websocket.d.ts +18 -0
- package/dist/app.d.ts +50 -2
- package/dist/app.js +12 -1
- package/dist/artifact-catalog-cursor.d.ts +27 -0
- package/dist/auth/managed-auth-attempt-context.d.ts +10 -0
- package/dist/auth/managed-auth-rate-limits.d.ts +167 -0
- package/dist/auth/managed-auth-session-adapter.d.ts +6 -0
- package/dist/auth/managed-auth.d.ts +102 -3
- package/dist/auth/managed-email.d.ts +29 -0
- package/dist/auth/managed-sign-in-notifications.d.ts +4 -0
- package/dist/auth/new-signups-gate.d.ts +23 -0
- package/dist/auth/organization-user-setup.d.ts +80 -0
- package/dist/auth/signup-funnel-metrics.d.ts +44 -0
- package/dist/browser-controller-authority.d.ts +1 -61
- package/dist/browser-ephemeral.d.ts +5 -0
- package/dist/browser-state-authority.d.ts +1 -27
- package/dist/chunk-6TZ5LG3O.js +123948 -0
- package/dist/chunk-6TZ5LG3O.js.map +1 -0
- package/dist/chunk-OGQDXVXY.js +18 -0
- package/dist/chunk-OGQDXVXY.js.map +1 -0
- package/dist/claude-subscription-account-conflicts.d.ts +1 -0
- package/dist/claude-subscription-account-usage.d.ts +39 -0
- package/dist/claude-subscription-oauth.d.ts +25 -0
- package/dist/claude-subscription-profile.d.ts +9 -0
- package/dist/claude-subscription-usage.d.ts +44 -0
- package/dist/claude-workspace-connection.d.ts +15 -0
- package/dist/codemode.d.ts +32 -1
- package/dist/company-brain-okf.d.ts +24 -0
- package/dist/connected-machine-computer-access.d.ts +11 -0
- package/dist/connection-ownership.d.ts +54 -0
- package/dist/controller-data-plane.d.ts +32 -0
- package/dist/editable-artifact-source-session.d.ts +3 -0
- package/dist/editable-artifact-websocket.d.ts +10 -18
- package/dist/editable-artifact-workspace-files.d.ts +1 -1
- package/dist/fatal-process-boundary.d.ts +25 -0
- package/dist/github-access.d.ts +31 -2
- package/dist/http/acting-person.d.ts +24 -0
- package/dist/http/api-contract-compat.d.ts +19 -0
- package/dist/http/api-error.d.ts +27 -1
- package/dist/http/cors.d.ts +17 -0
- package/dist/http/deprecation.d.ts +42 -0
- package/dist/http/interaction-control-error.d.ts +13 -0
- package/dist/http/keyed-admission.d.ts +12 -0
- package/dist/http/local-browser-boundary.d.ts +72 -0
- package/dist/http/metrics-listener.d.ts +22 -0
- package/dist/http/path-identifier.d.ts +17 -0
- package/dist/http/registered-route-label.d.ts +16 -0
- package/dist/http/request-body.d.ts +27 -0
- package/dist/http/request-source.d.ts +52 -0
- package/dist/http/sse.d.ts +17 -6
- package/dist/http/unmatched-route.d.ts +18 -0
- package/dist/http/user-content.d.ts +36 -0
- package/dist/http/websocket-upgrade-dispatch.d.ts +18 -0
- package/dist/index.d.ts +10 -3
- package/dist/index.js +1091 -162
- package/dist/index.js.map +1 -1
- package/dist/integration-endpoint-test.d.ts +20 -0
- package/dist/integrations/api-integrations.d.ts +2 -2
- package/dist/integrations/atlassian.d.ts +29 -3
- package/dist/integrations/connect-authority.d.ts +1 -0
- package/dist/integrations/connect-callback-return.d.ts +6 -0
- package/dist/integrations/fiken.d.ts +16 -2
- package/dist/integrations/github-app-connect.d.ts +47 -0
- package/dist/integrations/github-installation-proof.d.ts +3 -0
- package/dist/integrations/github-lens-connect.d.ts +116 -0
- package/dist/integrations/github-skill-source.d.ts +1 -5
- package/dist/integrations/google-drive.d.ts +24 -0
- package/dist/integrations/integration-commit-authority.d.ts +13 -0
- package/dist/integrations/marketplace-plugin.d.ts +2691 -0
- package/dist/integrations/oauth-client.d.ts +178 -18
- package/dist/integrations/oauth-profiles.d.ts +222 -0
- package/dist/integrations/oauth-return-path.d.ts +1 -0
- package/dist/integrations/personal-github-repositories.d.ts +84 -0
- package/dist/integrations/personal-github.d.ts +42 -0
- package/dist/integrations/pr-review-provider.d.ts +22 -0
- package/dist/integrations/provider-oauth.d.ts +19 -1
- package/dist/integrations/slack-app-home.d.ts +24 -0
- package/dist/integrations/slack-bot.d.ts +367 -12
- package/dist/integrations/slack-file-upload-flow.d.ts +62 -0
- package/dist/integrations/slack-install.d.ts +20 -0
- package/dist/integrations/slack-interactions.d.ts +107 -9
- package/dist/integrations/slack-mrkdwn.d.ts +41 -0
- package/dist/integrations/slack-routing.d.ts +120 -0
- package/dist/integrations/slack-session-defaults.d.ts +58 -0
- package/dist/integrations/slack-task-file-upload.d.ts +36 -0
- package/dist/integrations/social-oauth.d.ts +7 -0
- package/dist/interaction-agent-access.d.ts +15 -0
- package/dist/interaction-frame-proxy.d.ts +68 -0
- package/dist/interaction-metrics.d.ts +2 -0
- package/dist/mcp/action-catalog-types.d.ts +9 -0
- package/dist/mcp/action-catalog.gen.d.ts +2 -0
- package/dist/mcp/capability-account-readiness.d.ts +7 -0
- package/dist/mcp/child-read-evidence.d.ts +9 -0
- package/dist/mcp/command-read.d.ts +24 -0
- package/dist/mcp/company-profile-agent-admin.d.ts +44 -0
- package/dist/mcp/contract-input.d.ts +11 -0
- package/dist/mcp/documents.d.ts +7 -5
- package/dist/mcp/files.d.ts +2 -2
- package/dist/mcp/first-party-tool-permissions.d.ts +650 -0
- package/dist/mcp/knowledge-entries.d.ts +5 -0
- package/dist/mcp/orchestration-failure-diagnostic.d.ts +43 -0
- package/dist/mcp/receipts.d.ts +9 -0
- package/dist/mcp/request-abort.d.ts +19 -0
- package/dist/mcp/scheduled-task-input.d.ts +1189 -0
- package/dist/mcp/scheduled-task-view.d.ts +22 -4
- package/dist/mcp/server.d.ts +255 -3
- package/dist/mcp/session-event-view.d.ts +94 -0
- package/dist/mcp/session-view.d.ts +66 -2
- package/dist/mcp/session-wait.d.ts +174 -0
- package/dist/mcp-oauth.d.ts +52 -0
- package/dist/model-catalog.d.ts +11 -31
- package/dist/observability.d.ts +3 -1
- package/dist/organization-mcp.d.ts +52 -0
- package/dist/organization-recovery-notifications.d.ts +41 -0
- package/dist/routes/analytics-consent.d.ts +26 -0
- package/dist/routes/api-integrations.d.ts +25 -0
- package/dist/routes/api-keys.d.ts +17 -0
- package/dist/routes/artifact-catalog.d.ts +6 -0
- package/dist/routes/automations.d.ts +19 -0
- package/dist/routes/billing.d.ts +49 -0
- package/dist/routes/browser-sessions.d.ts +59 -2
- package/dist/routes/claude-subscription-accounts.d.ts +3 -0
- package/dist/routes/claude-subscription-oauth.d.ts +3 -0
- package/dist/routes/client-errors.d.ts +146 -0
- package/dist/routes/codex.d.ts +76 -2
- package/dist/routes/company-brain.d.ts +3 -0
- package/dist/routes/company-profile.d.ts +2 -1
- package/dist/routes/computer-sessions.d.ts +14 -0
- package/dist/routes/connect.d.ts +5 -0
- package/dist/routes/editable-artifacts.d.ts +2 -0
- package/dist/routes/external-identity-links.d.ts +6 -0
- package/dist/routes/feedback.d.ts +3 -0
- package/dist/routes/github-browser-pages.d.ts +12 -0
- package/dist/routes/github.d.ts +2 -0
- package/dist/routes/insights-response-cache.d.ts +10 -0
- package/dist/routes/insights-usage.d.ts +12 -0
- package/dist/routes/insights.d.ts +30 -1
- package/dist/routes/knowledge.d.ts +3 -0
- package/dist/routes/managed-auth-session-sets.d.ts +26 -0
- package/dist/routes/managed-onboarding.d.ts +29 -0
- package/dist/routes/managed-sign-in-methods.d.ts +6 -0
- package/dist/routes/model-connection-access.d.ts +3 -0
- package/dist/routes/organization-integration-policy.d.ts +3 -0
- package/dist/routes/organization-integrations.d.ts +6 -0
- package/dist/routes/organization-mcp-connections.d.ts +3 -0
- package/dist/routes/organization-model-providers.d.ts +3 -0
- package/dist/routes/organization-recovery.d.ts +19 -0
- package/dist/routes/organization-service-accounts.d.ts +3 -0
- package/dist/routes/organization-sessions.d.ts +26 -0
- package/dist/routes/personal-github-git-broker.d.ts +29 -0
- package/dist/routes/personal-github.d.ts +3 -0
- package/dist/routes/pr-review-github.d.ts +3 -0
- package/dist/routes/pr-review.d.ts +3 -0
- package/dist/routes/session-artifact-associations.d.ts +4 -0
- package/dist/routes/session-history-imports.d.ts +9 -0
- package/dist/routes/sessions.d.ts +44 -6
- package/dist/routes/skill-content.d.ts +4 -0
- package/dist/routes/skill-install-authority.d.ts +6 -0
- package/dist/routes/subscription-account-pools.d.ts +34 -0
- package/dist/routes/subscription-pool-access.d.ts +16 -0
- package/dist/routes/supergrok.d.ts +6 -1
- package/dist/routes/usage-allowances.d.ts +12 -0
- package/dist/routes/user-resource-authorities.d.ts +3 -0
- package/dist/routes/workspace-artifacts.d.ts +2 -1
- package/dist/routes/workspace-capture.d.ts +10 -2
- package/dist/routes/workspace-integrations.d.ts +35 -0
- package/dist/routes/workspace-learning.d.ts +3 -0
- package/dist/routes/workspace-model-providers.d.ts +4 -0
- package/dist/routes/workspaces.d.ts +29 -0
- package/dist/sandbox/auth-callout.d.ts +8 -0
- package/dist/sandbox/channel-a.d.ts +19 -1
- package/dist/sandbox/connection-authority.d.ts +3 -0
- package/dist/sandbox/enrollment.d.ts +9 -1
- package/dist/sandbox/machines.d.ts +5 -2
- package/dist/sandbox/metrics-ingestion.d.ts +28 -20
- package/dist/sandbox/rematerialize.d.ts +2 -0
- package/dist/sandbox/session-attach-variable-sets.d.ts +30 -0
- package/dist/sandbox/viewer.d.ts +26 -1
- package/dist/sandbox-file-artifacts.d.ts +50 -0
- package/dist/scheduled-task-deletion.d.ts +13 -0
- package/dist/session-create-observability.d.ts +5 -0
- package/dist/session-event-view-MPS2OZD3.js +292 -0
- package/dist/session-event-view-MPS2OZD3.js.map +1 -0
- package/dist/site-session-origin.d.ts +13 -0
- package/dist/site-uploads.d.ts +41 -0
- package/dist/slack-reaction-files.d.ts +1 -1
- package/dist/temporal-schedule-cleanup.d.ts +1 -0
- package/dist/temporal-schedule-sync.d.ts +26 -0
- package/dist/transcription/providers/xai-subscription.d.ts +1 -0
- package/dist/transcription/service.d.ts +2 -1
- package/dist/work-discovery-observability.d.ts +33 -0
- package/dist/workspace-artifact-content.d.ts +28 -0
- package/dist/workspace-artifact-provenance.d.ts +7 -0
- package/dist/workspace-delete-observability.d.ts +10 -0
- package/dist/workspace-deletion.d.ts +6 -0
- package/dist/workspace-state-projection.d.ts +3 -5
- package/dist/workspace-tool-gateway-observability.d.ts +17 -0
- package/dist/workspace-tool-gateway.d.ts +123 -0
- package/dist/workspace-webhook-dispatch.d.ts +26 -0
- package/package.json +23 -19
- package/src/access-grant-rls.ts +75 -0
- package/src/admission-trace.ts +14 -0
- package/src/api-websocket.ts +23 -0
- package/src/app.ts +1967 -252
- package/src/artifact-catalog-cursor.ts +60 -0
- package/src/auth/managed-auth-attempt-context.ts +70 -0
- package/src/auth/managed-auth-rate-limits.ts +252 -0
- package/src/auth/managed-auth-session-adapter.ts +222 -0
- package/src/auth/managed-auth.ts +536 -60
- package/src/auth/managed-email.ts +174 -0
- package/src/auth/managed-sign-in-notifications.ts +81 -0
- package/src/auth/new-signups-gate.ts +49 -0
- package/src/auth/organization-user-setup.ts +307 -0
- package/src/auth/signup-funnel-metrics.ts +164 -0
- package/src/browser-controller-authority.ts +1 -186
- package/src/browser-ephemeral.ts +39 -0
- package/src/browser-state-authority.ts +1 -236
- package/src/claude-subscription-account-conflicts.ts +12 -0
- package/src/claude-subscription-account-usage.ts +48 -0
- package/src/claude-subscription-oauth.ts +362 -0
- package/src/claude-subscription-profile.ts +73 -0
- package/src/claude-subscription-usage.ts +97 -0
- package/src/claude-workspace-connection.ts +82 -0
- package/src/codemode.ts +194 -30
- package/src/codex-realtime.ts +8 -2
- package/src/company-brain-okf.ts +340 -0
- package/src/connected-machine-computer-access.ts +33 -0
- package/src/connection-ownership.ts +199 -0
- package/src/controller-data-plane.ts +81 -0
- package/src/editable-artifact-native-kernel.ts +26 -15
- package/src/editable-artifact-office-import.ts +28 -3
- package/src/editable-artifact-production.ts +23 -7
- package/src/editable-artifact-source-session.ts +123 -0
- package/src/editable-artifact-websocket.ts +27 -21
- package/src/editable-artifact-workspace-files.ts +31 -6
- package/src/fatal-process-boundary.ts +246 -0
- package/src/gateway-realtime.ts +1 -1
- package/src/github-access.ts +147 -5
- package/src/http/acting-person.ts +71 -0
- package/src/http/api-contract-compat.ts +49 -0
- package/src/http/api-error.ts +115 -1
- package/src/http/auth.ts +48 -0
- package/src/http/cors.ts +67 -0
- package/src/http/deprecation.ts +118 -0
- package/src/http/interaction-control-error.ts +164 -0
- package/src/http/keyed-admission.ts +29 -0
- package/src/http/local-browser-boundary.ts +343 -0
- package/src/http/metrics-listener.ts +57 -0
- package/src/http/path-identifier.ts +54 -0
- package/src/http/registered-route-label.ts +34 -0
- package/src/http/request-body.ts +127 -0
- package/src/http/request-source.ts +178 -0
- package/src/http/sse.ts +486 -132
- package/src/http/unmatched-route.ts +60 -0
- package/src/http/user-content.ts +158 -0
- package/src/http/websocket-upgrade-dispatch.ts +29 -0
- package/src/index.ts +157 -52
- package/src/integration-endpoint-test.ts +291 -0
- package/src/integrations/api-integrations.ts +98 -11
- package/src/integrations/atlassian.ts +474 -136
- package/src/integrations/connect-authority.ts +2 -0
- package/src/integrations/connect-callback-return.ts +86 -0
- package/src/integrations/fiken.ts +354 -64
- package/src/integrations/github-app-connect.ts +435 -0
- package/src/integrations/github-installation-proof.ts +60 -0
- package/src/integrations/github-lens-connect.ts +76 -0
- package/src/integrations/github-skill-source.ts +2 -142
- package/src/integrations/google-drive.ts +717 -225
- package/src/integrations/integration-commit-authority.ts +69 -0
- package/src/integrations/marketplace-plugin.ts +48 -0
- package/src/integrations/oauth-client.ts +1356 -459
- package/src/integrations/oauth-profiles.ts +537 -0
- package/src/integrations/oauth-return-path.ts +18 -0
- package/src/integrations/personal-github-repositories.ts +674 -0
- package/src/integrations/personal-github.ts +825 -0
- package/src/integrations/pr-review-provider.ts +246 -0
- package/src/integrations/provider-oauth.ts +250 -22
- package/src/integrations/slack-app-home.ts +300 -0
- package/src/integrations/slack-bot.ts +963 -239
- package/src/integrations/slack-file-upload-flow.ts +147 -0
- package/src/integrations/slack-install.ts +83 -0
- package/src/integrations/slack-interactions.ts +3167 -474
- package/src/integrations/slack-mrkdwn.ts +193 -0
- package/src/integrations/slack-routing.ts +337 -0
- package/src/integrations/slack-session-defaults.ts +224 -0
- package/src/integrations/slack-task-file-upload.ts +323 -0
- package/src/integrations/social-oauth.ts +213 -15
- package/src/interaction-agent-access.ts +48 -0
- package/src/interaction-frame-proxy.ts +409 -0
- package/src/interaction-metrics.ts +30 -0
- package/src/mcp/action-catalog-types.ts +9 -0
- package/src/mcp/action-catalog.gen.ts +6696 -0
- package/src/mcp/capability-account-readiness.ts +17 -0
- package/src/mcp/child-read-evidence.ts +19 -0
- package/src/mcp/command-read.ts +109 -0
- package/src/mcp/company-profile-agent-admin.ts +198 -0
- package/src/mcp/contract-input.ts +141 -0
- package/src/mcp/documents.ts +65 -377
- package/src/mcp/editable-artifacts.ts +12 -6
- package/src/mcp/files.ts +80 -43
- package/src/mcp/first-party-tool-permissions.ts +305 -0
- package/src/mcp/knowledge-entries.ts +322 -0
- package/src/mcp/orchestration-failure-diagnostic.ts +43 -0
- package/src/mcp/receipts.ts +34 -0
- package/src/mcp/request-abort.ts +44 -0
- package/src/mcp/scheduled-task-input.ts +76 -0
- package/src/mcp/scheduled-task-view.ts +44 -3
- package/src/mcp/server.ts +3030 -1267
- package/src/mcp/session-event-view.ts +383 -0
- package/src/mcp/session-view.ts +84 -3
- package/src/mcp/session-wait.ts +665 -0
- package/src/mcp-oauth.ts +1123 -0
- package/src/model-catalog.ts +91 -333
- package/src/observability.ts +15 -1
- package/src/organization-mcp.ts +397 -0
- package/src/organization-recovery-notifications.ts +103 -0
- package/src/routes/analytics-consent.ts +123 -0
- package/src/routes/api-integrations.ts +364 -74
- package/src/routes/api-keys.ts +336 -10
- package/src/routes/artifact-catalog.ts +279 -0
- package/src/routes/automations.ts +768 -0
- package/src/routes/billing.ts +461 -46
- package/src/routes/browser-sessions.ts +780 -112
- package/src/routes/canonical-human-identities.ts +90 -14
- package/src/routes/capabilities.ts +107 -6
- package/src/routes/channels.ts +20 -3
- package/src/routes/claude-subscription-accounts.ts +334 -0
- package/src/routes/claude-subscription-oauth.ts +142 -0
- package/src/routes/client-errors.ts +362 -0
- package/src/routes/codex.ts +833 -116
- package/src/routes/company-brain.ts +322 -0
- package/src/routes/company-profile.ts +65 -1
- package/src/routes/computer-sessions.ts +557 -106
- package/src/routes/connect.ts +1938 -0
- package/src/routes/connections.ts +1039 -349
- package/src/routes/documents.ts +488 -287
- package/src/routes/editable-artifacts.ts +79 -9
- package/src/routes/enrollments.ts +135 -39
- package/src/routes/environments.ts +154 -58
- package/src/routes/external-identity-links.ts +201 -0
- package/src/routes/feedback.ts +113 -0
- package/src/routes/files.ts +267 -33
- package/src/routes/github-browser-pages.ts +162 -0
- package/src/routes/github.ts +446 -144
- package/src/routes/insights-response-cache.ts +83 -0
- package/src/routes/insights-usage.ts +345 -0
- package/src/routes/insights.ts +153 -19
- package/src/routes/install.ts +133 -38
- package/src/routes/integration-facets.ts +107 -33
- package/src/routes/interaction-resources.ts +30 -3
- package/src/routes/knowledge.ts +488 -0
- package/src/routes/machines.ts +313 -15
- package/src/routes/managed-auth-session-sets.ts +1218 -0
- package/src/routes/managed-onboarding.ts +337 -0
- package/src/routes/managed-sign-in-methods.ts +433 -0
- package/src/routes/memory-slack-publications.ts +3 -2
- package/src/routes/model-connection-access.ts +208 -0
- package/src/routes/organization-integration-policy.ts +116 -0
- package/src/routes/organization-integrations.ts +326 -0
- package/src/routes/organization-mcp-connections.ts +178 -0
- package/src/routes/organization-memberships.ts +1019 -27
- package/src/routes/organization-model-providers.ts +293 -0
- package/src/routes/organization-recovery.ts +325 -0
- package/src/routes/organization-service-accounts.ts +128 -0
- package/src/routes/organization-sessions.ts +350 -0
- package/src/routes/personal-github-git-broker.ts +785 -0
- package/src/routes/personal-github.ts +370 -0
- package/src/routes/plugins.ts +251 -22
- package/src/routes/pr-review-github.ts +881 -0
- package/src/routes/pr-review.ts +622 -0
- package/src/routes/preference-registry.ts +12 -93
- package/src/routes/rigs.ts +167 -50
- package/src/routes/scheduled-tasks.ts +346 -41
- package/src/routes/session-artifact-associations.ts +87 -0
- package/src/routes/session-history-imports.ts +280 -0
- package/src/routes/sessions.ts +2306 -264
- package/src/routes/skill-content.ts +297 -0
- package/src/routes/skill-install-authority.ts +64 -0
- package/src/routes/skills.ts +62 -9
- package/src/routes/social.ts +46 -18
- package/src/routes/subscription-account-pools.ts +436 -0
- package/src/routes/subscription-pool-access.ts +126 -0
- package/src/routes/supergrok.ts +219 -303
- package/src/routes/transcription-recordings.ts +38 -3
- package/src/routes/transcriptions.ts +4 -0
- package/src/routes/usage-allowances.ts +378 -0
- package/src/routes/user-resource-authorities.ts +138 -0
- package/src/routes/video-generation.ts +14 -10
- package/src/routes/workspace-artifacts.ts +275 -69
- package/src/routes/workspace-capture.ts +21 -0
- package/src/routes/workspace-integrations.ts +576 -0
- package/src/routes/workspace-learning.ts +159 -0
- package/src/routes/workspace-model-providers.ts +148 -0
- package/src/routes/workspace-state.ts +38 -25
- package/src/routes/workspaces.ts +1004 -127
- package/src/sandbox/auth-callout.ts +96 -19
- package/src/sandbox/channel-a.ts +323 -24
- package/src/sandbox/connection-authority.ts +3 -0
- package/src/sandbox/enrollment.ts +81 -6
- package/src/sandbox/machines.ts +218 -76
- package/src/sandbox/metrics-ingestion.ts +411 -62
- package/src/sandbox/rematerialize.ts +121 -19
- package/src/sandbox/session-attach-variable-sets.ts +131 -0
- package/src/sandbox/viewer.ts +280 -25
- package/src/sandbox-file-artifacts.ts +413 -0
- package/src/scheduled-task-deletion.ts +138 -0
- package/src/session-create-observability.ts +46 -0
- package/src/session-realtime-context.ts +6 -3
- package/src/site-session-origin.ts +48 -0
- package/src/site-uploads.ts +224 -0
- package/src/slack-reaction-files.ts +16 -5
- package/src/temporal-schedule-cleanup.ts +13 -0
- package/src/temporal-schedule-sync.ts +74 -0
- package/src/transcription/providers/codex-subscription.ts +2 -0
- package/src/transcription/providers/openai.ts +1 -0
- package/src/transcription/providers/xai-subscription.ts +49 -3
- package/src/transcription/segmenter.ts +8 -2
- package/src/transcription/service.ts +43 -6
- package/src/work-discovery-observability.ts +121 -0
- package/src/workspace-artifact-content.ts +144 -0
- package/src/workspace-artifact-provenance.ts +104 -0
- package/src/workspace-delete-observability.ts +75 -0
- package/src/workspace-deletion.ts +73 -0
- package/src/workspace-state-projection.ts +18 -148
- package/src/workspace-tool-gateway-observability.ts +100 -0
- package/src/workspace-tool-gateway.ts +870 -0
- package/src/workspace-webhook-dispatch.ts +190 -0
- package/src/xai-subscription-auth.ts +9 -2
- package/dist/chunk-WCXHI3FF.js +0 -49920
- package/dist/chunk-WCXHI3FF.js.map +0 -1
- package/dist/routes/packs.d.ts +0 -3
- package/src/routes/packs.ts +0 -729
package/src/routes/billing.ts
CHANGED
|
@@ -1,17 +1,28 @@
|
|
|
1
1
|
import {
|
|
2
|
+
BillingCheckoutStatus,
|
|
3
|
+
CreateBillingPortalRequest,
|
|
4
|
+
CreateBillingPortalResponse,
|
|
2
5
|
CreateCheckoutRequest,
|
|
3
6
|
CreateCheckoutResponse,
|
|
7
|
+
OrganizationUsageQuery,
|
|
8
|
+
OrganizationUsageWorkspacePageQuery,
|
|
4
9
|
type AccessContext,
|
|
5
10
|
type Permission,
|
|
6
11
|
} from "@opengeni/contracts";
|
|
12
|
+
import { OrganizationModelUsageQuery } from "@opengeni/contracts/organization-model-usage";
|
|
7
13
|
import { configuredEntitlements } from "@opengeni/config";
|
|
8
14
|
import {
|
|
9
15
|
applyCreditLedgerEntry,
|
|
10
16
|
getBillingBalance,
|
|
11
17
|
getBillingCustomer,
|
|
18
|
+
getCreditLedgerEntry,
|
|
12
19
|
hasCreditLedgerEntry,
|
|
13
20
|
isStripeWebhookProcessed,
|
|
14
21
|
listUsageEvents,
|
|
22
|
+
getOrganizationModelUsage,
|
|
23
|
+
getOrganizationUsageSummary,
|
|
24
|
+
getOrganizationUsageWorkspacePage,
|
|
25
|
+
withSessionRlsActorContext,
|
|
15
26
|
getManagedAccount,
|
|
16
27
|
markStripeWebhookProcessed,
|
|
17
28
|
recordStripeWebhookEvent,
|
|
@@ -22,6 +33,7 @@ import { HTTPException } from "hono/http-exception";
|
|
|
22
33
|
import Stripe from "stripe";
|
|
23
34
|
import { requireAccessContext } from "@opengeni/core";
|
|
24
35
|
import type { ApiRouteDeps } from "@opengeni/core";
|
|
36
|
+
import { withAccessGrantSessionRlsContext } from "../access-grant-rls";
|
|
25
37
|
|
|
26
38
|
export function registerBillingRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
27
39
|
app.get("/v1/billing", async (c) => {
|
|
@@ -55,6 +67,46 @@ export function registerBillingRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
55
67
|
});
|
|
56
68
|
});
|
|
57
69
|
|
|
70
|
+
app.get("/v1/billing/usage-summary", async (c) => {
|
|
71
|
+
const context = await requireAccessContext(c, deps);
|
|
72
|
+
const accountId = requireSelectedAccount(context, c.req.query("accountId"), "billing:read");
|
|
73
|
+
const parsed = OrganizationUsageQuery.safeParse(c.req.query());
|
|
74
|
+
if (!parsed.success) {
|
|
75
|
+
throw new HTTPException(400, {
|
|
76
|
+
message: parsed.error.issues[0]?.message ?? "invalid usage query",
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return await withBillingUsageActor(deps, context, accountId, async () =>
|
|
80
|
+
c.json(await getOrganizationUsageSummary(deps.db, { accountId, ...parsed.data })),
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
app.get("/v1/billing/usage-workspaces", async (c) => {
|
|
85
|
+
const context = await requireAccessContext(c, deps);
|
|
86
|
+
const accountId = requireSelectedAccount(context, c.req.query("accountId"), "billing:read");
|
|
87
|
+
const parsed = OrganizationUsageWorkspacePageQuery.safeParse(c.req.query());
|
|
88
|
+
if (!parsed.success)
|
|
89
|
+
throw new HTTPException(400, {
|
|
90
|
+
message: parsed.error.issues[0]?.message ?? "invalid usage page query",
|
|
91
|
+
});
|
|
92
|
+
return await withBillingUsageActor(deps, context, accountId, async () =>
|
|
93
|
+
c.json(await getOrganizationUsageWorkspacePage(deps.db, { accountId, ...parsed.data })),
|
|
94
|
+
);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
app.get("/v1/billing/usage-models", async (c) => {
|
|
98
|
+
const context = await requireAccessContext(c, deps);
|
|
99
|
+
const accountId = requireSelectedAccount(context, c.req.query("accountId"), "billing:read");
|
|
100
|
+
const parsed = OrganizationModelUsageQuery.safeParse(c.req.query());
|
|
101
|
+
if (!parsed.success)
|
|
102
|
+
throw new HTTPException(400, {
|
|
103
|
+
message: parsed.error.issues[0]?.message ?? "invalid model usage query",
|
|
104
|
+
});
|
|
105
|
+
return await withBillingUsageActor(deps, context, accountId, async () =>
|
|
106
|
+
c.json(await getOrganizationModelUsage(deps.db, { accountId, ...parsed.data })),
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
|
|
58
110
|
app.get("/v1/billing/entitlements", async (c) => {
|
|
59
111
|
const context = await requireAccessContext(c, deps);
|
|
60
112
|
const accountId = requireSelectedAccount(context, c.req.query("accountId"), "billing:read");
|
|
@@ -78,31 +130,154 @@ export function registerBillingRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
78
130
|
}
|
|
79
131
|
const body = parsed.data;
|
|
80
132
|
const accountId = requireSelectedAccount(context, body.accountId, "billing:manage");
|
|
81
|
-
const amountCents = usdToCents(body.amountUsd);
|
|
82
|
-
const amountMicros = centsToMicros(amountCents);
|
|
83
133
|
const stripe = stripeClient(deps);
|
|
134
|
+
const promotion = body.promotionCode
|
|
135
|
+
? await resolveCheckoutPromotionCode(stripe, body.promotionCode)
|
|
136
|
+
: null;
|
|
137
|
+
const amountCents =
|
|
138
|
+
body.amountUsd !== undefined
|
|
139
|
+
? usdToCents(body.amountUsd)
|
|
140
|
+
: (promotion?.amountOffCents ?? null);
|
|
141
|
+
if (amountCents === null) {
|
|
142
|
+
throw new HTTPException(422, {
|
|
143
|
+
message: promotion
|
|
144
|
+
? "Choose how many credits to buy with this code."
|
|
145
|
+
: "amountUsd is required",
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
if (amountCents < 500 || amountCents > 1_000_000) {
|
|
149
|
+
throw new HTTPException(422, {
|
|
150
|
+
message: "Credits must be between $5 and $10,000.",
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
const amountMicros = centsToMicros(amountCents);
|
|
84
154
|
const customerId = await getOrCreateStripeCustomer(deps, stripe, context, accountId);
|
|
85
155
|
const idempotencyKey = `checkout:${accountId}:${amountMicros}:${crypto.randomUUID()}`;
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
156
|
+
let session: Stripe.Checkout.Session;
|
|
157
|
+
try {
|
|
158
|
+
session = await stripe.checkout.sessions.create(
|
|
159
|
+
stripeCheckoutSessionCreateParams({
|
|
160
|
+
accountId,
|
|
161
|
+
customerId,
|
|
162
|
+
amountCents,
|
|
163
|
+
amountMicros,
|
|
164
|
+
creditsProductId: deps.settings.stripeCreditsProductId,
|
|
165
|
+
publicBaseUrl: deps.settings.publicBaseUrl,
|
|
166
|
+
webBaseUrl: deps.settings.webBaseUrl,
|
|
167
|
+
successUrl: body.successUrl,
|
|
168
|
+
cancelUrl: body.cancelUrl,
|
|
169
|
+
idempotencyKey,
|
|
170
|
+
...(promotion
|
|
171
|
+
? {
|
|
172
|
+
promotionCodeId: promotion.id,
|
|
173
|
+
fullyDiscounted:
|
|
174
|
+
promotion.percentOff === 100 || (promotion.amountOffCents ?? 0) >= amountCents,
|
|
175
|
+
}
|
|
176
|
+
: {}),
|
|
177
|
+
}),
|
|
178
|
+
{ idempotencyKey },
|
|
179
|
+
);
|
|
180
|
+
} catch (error) {
|
|
181
|
+
// Stripe refuses a code whose restrictions this purchase doesn't meet
|
|
182
|
+
// (first purchase only, a minimum amount, another customer's code).
|
|
183
|
+
if (promotion && error instanceof Stripe.errors.StripeInvalidRequestError) {
|
|
184
|
+
throw new HTTPException(422, { message: "This code can't be used for this purchase." });
|
|
185
|
+
}
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
if (!session.url) {
|
|
189
|
+
throw new HTTPException(502, { message: "Stripe did not return a checkout URL" });
|
|
190
|
+
}
|
|
191
|
+
return c.json(
|
|
192
|
+
CreateCheckoutResponse.parse({
|
|
193
|
+
checkoutSessionId: session.id,
|
|
194
|
+
url: session.url,
|
|
195
|
+
amountUsd: amountCents / 100,
|
|
196
|
+
}),
|
|
197
|
+
);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Where one checkout stands, for the page the customer returns to. Credits
|
|
201
|
+
// normally post from the webhook; a completed checkout whose webhook has not
|
|
202
|
+
// arrived yet is settled here from Stripe's own record of the session, under
|
|
203
|
+
// the same ledger idempotency key, so it grants at most once.
|
|
204
|
+
app.get("/v1/billing/checkout/:checkoutSessionId", async (c) => {
|
|
205
|
+
if (deps.settings.billingMode !== "stripe") {
|
|
206
|
+
throw new HTTPException(404, { message: "stripe billing is not enabled" });
|
|
207
|
+
}
|
|
208
|
+
const context = await requireAccessContext(c, deps);
|
|
209
|
+
const accountId = requireSelectedAccount(context, c.req.query("accountId"), "billing:read");
|
|
210
|
+
const checkoutSessionId = c.req.param("checkoutSessionId");
|
|
211
|
+
if (!/^cs_[A-Za-z0-9_]{1,250}$/.test(checkoutSessionId)) {
|
|
212
|
+
throw new HTTPException(404, { message: "checkout not found" });
|
|
213
|
+
}
|
|
214
|
+
const stripe = stripeClient(deps);
|
|
215
|
+
let session: Stripe.Checkout.Session;
|
|
216
|
+
try {
|
|
217
|
+
session = await stripe.checkout.sessions.retrieve(checkoutSessionId);
|
|
218
|
+
} catch (error) {
|
|
219
|
+
if (error instanceof Stripe.errors.StripeInvalidRequestError) {
|
|
220
|
+
throw new HTTPException(404, { message: "checkout not found" });
|
|
221
|
+
}
|
|
222
|
+
throw error;
|
|
223
|
+
}
|
|
224
|
+
if (session.metadata?.opengeni_account_id !== accountId) {
|
|
225
|
+
throw new HTTPException(404, { message: "checkout not found" });
|
|
226
|
+
}
|
|
227
|
+
const credit = creditMetadata(session.metadata, `Stripe checkout session ${session.id}`);
|
|
228
|
+
let entry = await getCreditLedgerEntry(deps.db, accountId, credit.idempotencyKey);
|
|
229
|
+
if (!entry && session.status === "complete") {
|
|
230
|
+
await grantCheckoutSessionCredits(deps, session, {
|
|
231
|
+
stripeEventId: null,
|
|
232
|
+
livemode: session.livemode,
|
|
233
|
+
});
|
|
234
|
+
entry = await getCreditLedgerEntry(deps.db, accountId, credit.idempotencyKey);
|
|
235
|
+
}
|
|
236
|
+
return c.json(
|
|
237
|
+
BillingCheckoutStatus.parse({
|
|
238
|
+
checkoutSessionId: session.id,
|
|
239
|
+
status: session.status ?? "open",
|
|
240
|
+
credit: {
|
|
241
|
+
state: entry ? "granted" : "pending",
|
|
242
|
+
amountMicros: entry?.amountMicros ?? credit.amountMicros,
|
|
243
|
+
currency: "usd",
|
|
244
|
+
free: entry
|
|
245
|
+
? entry.sourceType === "stripe_checkout_coupon"
|
|
246
|
+
: isFreeCouponCheckout(session),
|
|
247
|
+
},
|
|
248
|
+
balance: entry ? await getBillingBalance(deps.db, accountId) : null,
|
|
249
|
+
}),
|
|
250
|
+
);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
app.post("/v1/billing/portal", async (c) => {
|
|
254
|
+
if (deps.settings.billingMode !== "stripe") {
|
|
255
|
+
throw new HTTPException(404, { message: "stripe billing is not enabled" });
|
|
256
|
+
}
|
|
257
|
+
const context = await requireAccessContext(c, deps);
|
|
258
|
+
const parsed = CreateBillingPortalRequest.safeParse(await c.req.json());
|
|
259
|
+
if (!parsed.success) {
|
|
260
|
+
throw new HTTPException(400, {
|
|
261
|
+
message: parsed.error.issues[0]?.message ?? "invalid billing portal request",
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
const accountId = requireSelectedAccount(context, parsed.data.accountId, "billing:manage");
|
|
265
|
+
const stripe = stripeClient(deps);
|
|
266
|
+
const customerId = await getOrCreateStripeCustomer(deps, stripe, context, accountId);
|
|
267
|
+
const session = await stripe.billingPortal.sessions.create(
|
|
268
|
+
stripeBillingPortalSessionCreateParams({
|
|
89
269
|
customerId,
|
|
90
|
-
amountCents,
|
|
91
|
-
amountMicros,
|
|
92
|
-
creditsProductId: deps.settings.stripeCreditsProductId,
|
|
93
270
|
publicBaseUrl: deps.settings.publicBaseUrl,
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
idempotencyKey,
|
|
271
|
+
webBaseUrl: deps.settings.webBaseUrl,
|
|
272
|
+
returnUrl: parsed.data.returnUrl,
|
|
97
273
|
}),
|
|
98
|
-
{ idempotencyKey },
|
|
99
274
|
);
|
|
100
275
|
if (!session.url) {
|
|
101
|
-
throw new HTTPException(502, { message: "Stripe did not return a
|
|
276
|
+
throw new HTTPException(502, { message: "Stripe did not return a billing portal URL" });
|
|
102
277
|
}
|
|
103
278
|
return c.json(
|
|
104
|
-
|
|
105
|
-
|
|
279
|
+
CreateBillingPortalResponse.parse({
|
|
280
|
+
portalSessionId: session.id,
|
|
106
281
|
url: session.url,
|
|
107
282
|
}),
|
|
108
283
|
);
|
|
@@ -159,24 +334,39 @@ export function stripeCheckoutSessionCreateParams(input: {
|
|
|
159
334
|
amountMicros: number;
|
|
160
335
|
creditsProductId?: string | undefined;
|
|
161
336
|
publicBaseUrl?: string | undefined;
|
|
337
|
+
webBaseUrl?: string | undefined;
|
|
162
338
|
successUrl?: string | undefined;
|
|
163
339
|
cancelUrl?: string | undefined;
|
|
164
340
|
idempotencyKey: string;
|
|
341
|
+
/** Apply this promotion code up front instead of letting Stripe ask for one. */
|
|
342
|
+
promotionCodeId?: string | undefined;
|
|
343
|
+
/**
|
|
344
|
+
* The applied code covers the whole package, so the total is $0. Nothing is
|
|
345
|
+
* taxable, so Checkout skips tax and with it the billing address: the
|
|
346
|
+
* customer only confirms.
|
|
347
|
+
*/
|
|
348
|
+
fullyDiscounted?: boolean | undefined;
|
|
165
349
|
}): Stripe.Checkout.SessionCreateParams {
|
|
166
350
|
const successUrl = checkoutReturnUrl(
|
|
167
351
|
input.publicBaseUrl,
|
|
352
|
+
input.webBaseUrl,
|
|
168
353
|
input.successUrl,
|
|
169
354
|
"/billing?checkout=success",
|
|
170
355
|
"successUrl",
|
|
171
356
|
);
|
|
172
357
|
const cancelUrl = checkoutReturnUrl(
|
|
173
358
|
input.publicBaseUrl,
|
|
359
|
+
input.webBaseUrl,
|
|
174
360
|
input.cancelUrl,
|
|
175
361
|
"/billing?checkout=cancelled",
|
|
176
362
|
"cancelUrl",
|
|
177
363
|
);
|
|
178
364
|
return {
|
|
179
365
|
mode: "payment",
|
|
366
|
+
// Stripe takes either a code field on its page or one discount up front.
|
|
367
|
+
...(input.promotionCodeId
|
|
368
|
+
? { discounts: [{ promotion_code: input.promotionCodeId }] }
|
|
369
|
+
: { allow_promotion_codes: true }),
|
|
180
370
|
customer: input.customerId,
|
|
181
371
|
customer_update: {
|
|
182
372
|
address: "auto",
|
|
@@ -184,7 +374,7 @@ export function stripeCheckoutSessionCreateParams(input: {
|
|
|
184
374
|
},
|
|
185
375
|
success_url: successUrl,
|
|
186
376
|
cancel_url: cancelUrl,
|
|
187
|
-
automatic_tax: { enabled:
|
|
377
|
+
automatic_tax: { enabled: !(input.promotionCodeId && input.fullyDiscounted) },
|
|
188
378
|
billing_address_collection: "auto",
|
|
189
379
|
line_items: [
|
|
190
380
|
{
|
|
@@ -196,7 +386,7 @@ export function stripeCheckoutSessionCreateParams(input: {
|
|
|
196
386
|
? { product: input.creditsProductId }
|
|
197
387
|
: {
|
|
198
388
|
product_data: {
|
|
199
|
-
name: "
|
|
389
|
+
name: "Opengeni credits",
|
|
200
390
|
metadata: {
|
|
201
391
|
app: "opengeni",
|
|
202
392
|
billing_model: "prepaid_credits",
|
|
@@ -211,6 +401,7 @@ export function stripeCheckoutSessionCreateParams(input: {
|
|
|
211
401
|
opengeni_credit_amount_usd: (input.amountCents / 100).toFixed(2),
|
|
212
402
|
opengeni_credit_micros: String(input.amountMicros),
|
|
213
403
|
opengeni_credit_idempotency_key: input.idempotencyKey,
|
|
404
|
+
opengeni_credit_coupon_v1: "1",
|
|
214
405
|
},
|
|
215
406
|
payment_intent_data: {
|
|
216
407
|
metadata: {
|
|
@@ -218,30 +409,65 @@ export function stripeCheckoutSessionCreateParams(input: {
|
|
|
218
409
|
opengeni_credit_amount_usd: (input.amountCents / 100).toFixed(2),
|
|
219
410
|
opengeni_credit_micros: String(input.amountMicros),
|
|
220
411
|
opengeni_credit_idempotency_key: input.idempotencyKey,
|
|
412
|
+
opengeni_credit_coupon_v1: "1",
|
|
413
|
+
},
|
|
414
|
+
},
|
|
415
|
+
invoice_creation: {
|
|
416
|
+
enabled: true,
|
|
417
|
+
invoice_data: {
|
|
418
|
+
metadata: {
|
|
419
|
+
opengeni_account_id: input.accountId,
|
|
420
|
+
opengeni_credit_amount_usd: (input.amountCents / 100).toFixed(2),
|
|
421
|
+
opengeni_credit_micros: String(input.amountMicros),
|
|
422
|
+
opengeni_credit_idempotency_key: input.idempotencyKey,
|
|
423
|
+
opengeni_credit_coupon_v1: "1",
|
|
424
|
+
},
|
|
221
425
|
},
|
|
222
426
|
},
|
|
223
427
|
};
|
|
224
428
|
}
|
|
225
429
|
|
|
430
|
+
export function stripeBillingPortalSessionCreateParams(input: {
|
|
431
|
+
customerId: string;
|
|
432
|
+
publicBaseUrl?: string | undefined;
|
|
433
|
+
webBaseUrl?: string | undefined;
|
|
434
|
+
returnUrl?: string | undefined;
|
|
435
|
+
}): Stripe.BillingPortal.SessionCreateParams {
|
|
436
|
+
return {
|
|
437
|
+
customer: input.customerId,
|
|
438
|
+
return_url: checkoutReturnUrl(
|
|
439
|
+
input.publicBaseUrl,
|
|
440
|
+
input.webBaseUrl,
|
|
441
|
+
input.returnUrl,
|
|
442
|
+
"/billing",
|
|
443
|
+
"returnUrl",
|
|
444
|
+
),
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
|
|
226
448
|
function checkoutReturnUrl(
|
|
227
449
|
publicBaseUrl: string | undefined,
|
|
450
|
+
webBaseUrl: string | undefined,
|
|
228
451
|
candidate: string | undefined,
|
|
229
452
|
fallbackPath: string,
|
|
230
453
|
field: string,
|
|
231
454
|
): string {
|
|
232
455
|
if (!publicBaseUrl) {
|
|
233
456
|
throw new HTTPException(500, {
|
|
234
|
-
message: "OPENGENI_PUBLIC_BASE_URL is required for Stripe
|
|
457
|
+
message: "OPENGENI_PUBLIC_BASE_URL is required for Stripe redirects",
|
|
235
458
|
});
|
|
236
459
|
}
|
|
237
|
-
const base = new URL(publicBaseUrl);
|
|
460
|
+
const base = new URL(webBaseUrl ?? publicBaseUrl);
|
|
238
461
|
const fallback = new URL(fallbackPath, base).toString();
|
|
239
462
|
if (!candidate) {
|
|
240
463
|
return fallback;
|
|
241
464
|
}
|
|
242
465
|
const parsed = new URL(candidate);
|
|
243
|
-
|
|
244
|
-
|
|
466
|
+
const allowedOrigins = new Set([new URL(publicBaseUrl).origin, base.origin]);
|
|
467
|
+
if (!allowedOrigins.has(parsed.origin)) {
|
|
468
|
+
throw new HTTPException(400, {
|
|
469
|
+
message: `${field} must use the Opengeni public${allowedOrigins.size > 1 ? " or web" : ""} origin`,
|
|
470
|
+
});
|
|
245
471
|
}
|
|
246
472
|
return parsed.toString();
|
|
247
473
|
}
|
|
@@ -253,7 +479,12 @@ async function handleStripeWebhookEvent(
|
|
|
253
479
|
): Promise<void> {
|
|
254
480
|
switch (event.type) {
|
|
255
481
|
case "checkout.session.completed":
|
|
256
|
-
|
|
482
|
+
case "checkout.session.async_payment_succeeded":
|
|
483
|
+
await handleCheckoutSessionPayment(deps, event);
|
|
484
|
+
return;
|
|
485
|
+
case "checkout.session.async_payment_failed":
|
|
486
|
+
// A delayed payment method failed. Completion reported it `unpaid`, so
|
|
487
|
+
// no credit was granted and there is nothing to reverse.
|
|
257
488
|
return;
|
|
258
489
|
case "checkout.session.expired":
|
|
259
490
|
case "payment_intent.succeeded":
|
|
@@ -289,42 +520,171 @@ async function handleStripeWebhookEvent(
|
|
|
289
520
|
}
|
|
290
521
|
}
|
|
291
522
|
|
|
292
|
-
|
|
523
|
+
export type StripeCheckoutCreditDecision =
|
|
524
|
+
| { action: "grant"; credit: CheckoutCreditMetadata }
|
|
525
|
+
| { action: "ignore"; reason: "foreign_checkout" | "not_payment_mode" | "payment_not_paid" };
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Decides whether a Checkout Session webhook grants OpenGeni credits.
|
|
529
|
+
*
|
|
530
|
+
* Sessions created outside OpenGeni on the same Stripe account carry no
|
|
531
|
+
* `opengeni_` metadata and are acknowledged without effect. An OpenGeni
|
|
532
|
+
* session with malformed credit metadata still throws so the failure stays
|
|
533
|
+
* visible. Credits are granted once Stripe reports the payment `paid` or a
|
|
534
|
+
* completed checkout fully covered by a coupon:
|
|
535
|
+
* on `checkout.session.completed` for immediate methods, or on
|
|
536
|
+
* `checkout.session.async_payment_succeeded` for delayed methods, whose
|
|
537
|
+
* completion event arrives `unpaid`. Both events carry the same session
|
|
538
|
+
* metadata, so the ledger idempotency key grants at most once.
|
|
539
|
+
*/
|
|
540
|
+
export function stripeCheckoutCreditDecision(
|
|
541
|
+
session: Stripe.Checkout.Session,
|
|
542
|
+
): StripeCheckoutCreditDecision {
|
|
543
|
+
if (!Object.keys(session.metadata ?? {}).some((key) => key.startsWith("opengeni_"))) {
|
|
544
|
+
return { action: "ignore", reason: "foreign_checkout" };
|
|
545
|
+
}
|
|
546
|
+
if (session.mode !== "payment") {
|
|
547
|
+
return { action: "ignore", reason: "not_payment_mode" };
|
|
548
|
+
}
|
|
549
|
+
if (session.payment_status !== "paid" && !isFreeCouponCheckout(session)) {
|
|
550
|
+
return { action: "ignore", reason: "payment_not_paid" };
|
|
551
|
+
}
|
|
552
|
+
const credit = creditMetadata(session.metadata, `Stripe checkout session ${session.id}`);
|
|
553
|
+
if (
|
|
554
|
+
session.metadata?.opengeni_credit_coupon_v1 === "1" &&
|
|
555
|
+
(session.currency !== "usd" ||
|
|
556
|
+
session.amount_subtotal !== credit.amountMicros / 10_000 ||
|
|
557
|
+
typeof session.amount_total !== "number" ||
|
|
558
|
+
!Number.isSafeInteger(session.amount_total) ||
|
|
559
|
+
session.amount_total < 0)
|
|
560
|
+
) {
|
|
561
|
+
throw new Error(`Stripe checkout session ${session.id} has invalid credit package totals`);
|
|
562
|
+
}
|
|
563
|
+
return { action: "grant", credit };
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function isFreeCouponCheckout(session: Stripe.Checkout.Session): boolean {
|
|
567
|
+
return (
|
|
568
|
+
(session.payment_status === "paid" || session.payment_status === "no_payment_required") &&
|
|
569
|
+
session.status === "complete" &&
|
|
570
|
+
session.amount_total === 0 &&
|
|
571
|
+
typeof session.amount_subtotal === "number" &&
|
|
572
|
+
session.amount_subtotal > 0 &&
|
|
573
|
+
(session.total_details?.amount_discount ?? 0) >= session.amount_subtotal
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
async function handleCheckoutSessionPayment(
|
|
293
578
|
deps: ApiRouteDeps,
|
|
294
579
|
event: Stripe.Event,
|
|
295
580
|
): Promise<void> {
|
|
296
|
-
|
|
297
|
-
|
|
581
|
+
await grantCheckoutSessionCredits(deps, event.data.object as Stripe.Checkout.Session, {
|
|
582
|
+
stripeEventId: event.id,
|
|
583
|
+
stripeEventType: event.type,
|
|
584
|
+
livemode: event.livemode,
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Grants a paid (or fully coupon-covered) OpenGeni checkout's credits once.
|
|
590
|
+
* The webhook calls this with its event; the checkout status read calls it
|
|
591
|
+
* with the session it retrieved from Stripe when the webhook is late.
|
|
592
|
+
*/
|
|
593
|
+
async function grantCheckoutSessionCredits(
|
|
594
|
+
deps: ApiRouteDeps,
|
|
595
|
+
session: Stripe.Checkout.Session,
|
|
596
|
+
source: { stripeEventId: string | null; stripeEventType?: string; livemode: boolean },
|
|
597
|
+
): Promise<void> {
|
|
598
|
+
const decision = stripeCheckoutCreditDecision(session);
|
|
599
|
+
if (decision.action === "ignore") {
|
|
600
|
+
if (decision.reason === "foreign_checkout") {
|
|
601
|
+
console.info("[api] stripe webhook ignored a checkout session not created by OpenGeni", {
|
|
602
|
+
stripeEventType: source.stripeEventType ?? null,
|
|
603
|
+
livemode: source.livemode,
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
const credit = decision.credit;
|
|
609
|
+
const freeCouponCheckout = isFreeCouponCheckout(session);
|
|
610
|
+
if (!(await getManagedAccount(deps.db, credit.accountId))) {
|
|
611
|
+
// Another OpenGeni deployment sharing the Stripe account (or an account
|
|
612
|
+
// removed before a delayed payment settled). Retrying cannot succeed, so
|
|
613
|
+
// acknowledge instead of failing the delivery for days.
|
|
614
|
+
console.info(
|
|
615
|
+
"[api] stripe webhook ignored a checkout session for an account not in this deployment",
|
|
616
|
+
{
|
|
617
|
+
stripeEventType: source.stripeEventType ?? null,
|
|
618
|
+
livemode: source.livemode,
|
|
619
|
+
},
|
|
620
|
+
);
|
|
298
621
|
return;
|
|
299
622
|
}
|
|
300
|
-
const credit = creditMetadata(session.metadata, `Stripe checkout session ${session.id}`);
|
|
301
623
|
const customerId = typeof session.customer === "string" ? session.customer : session.customer?.id;
|
|
302
624
|
if (customerId) {
|
|
303
625
|
await upsertBillingCustomer(deps.db, {
|
|
304
626
|
accountId: credit.accountId,
|
|
305
|
-
provider:
|
|
627
|
+
provider: source.livemode ? "stripe:live" : "stripe:test",
|
|
306
628
|
providerCustomerId: customerId,
|
|
307
629
|
email: session.customer_details?.email ?? session.customer_email ?? null,
|
|
308
630
|
});
|
|
309
631
|
}
|
|
632
|
+
if (await hasCreditLedgerEntry(deps.db, credit.accountId, credit.idempotencyKey)) {
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
310
635
|
await applyCreditLedgerEntry(deps.db, {
|
|
311
636
|
accountId: credit.accountId,
|
|
312
|
-
type: "credit_topup",
|
|
637
|
+
type: freeCouponCheckout ? "grant" : "credit_topup",
|
|
313
638
|
amountMicros: credit.amountMicros,
|
|
314
|
-
sourceType: "stripe_checkout_session",
|
|
639
|
+
sourceType: freeCouponCheckout ? "stripe_checkout_coupon" : "stripe_checkout_session",
|
|
315
640
|
sourceId: session.id,
|
|
316
641
|
idempotencyKey: credit.idempotencyKey,
|
|
317
642
|
metadata: {
|
|
318
|
-
stripeEventId:
|
|
643
|
+
stripeEventId: source.stripeEventId,
|
|
319
644
|
stripePaymentIntentId:
|
|
320
645
|
typeof session.payment_intent === "string"
|
|
321
646
|
? session.payment_intent
|
|
322
647
|
: (session.payment_intent?.id ?? null),
|
|
323
648
|
stripePackageId: credit.packageId,
|
|
324
649
|
stripeCreditAmountUsd: credit.amountUsd,
|
|
650
|
+
...(session.metadata?.opengeni_credit_coupon_v1 === "1"
|
|
651
|
+
? {
|
|
652
|
+
stripeAmountTotalCents: session.amount_total,
|
|
653
|
+
stripeDiscountCents: session.total_details?.amount_discount ?? 0,
|
|
654
|
+
}
|
|
655
|
+
: {}),
|
|
325
656
|
},
|
|
326
657
|
});
|
|
327
|
-
recordCreditMicrosMetric(deps, "topup", credit.amountMicros);
|
|
658
|
+
recordCreditMicrosMetric(deps, freeCouponCheckout ? "grant" : "topup", credit.amountMicros);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* Looks up a customer-typed promotion code. Stripe matches codes without
|
|
663
|
+
* regard to case. A fixed USD amount off sets the credits a checkout buys.
|
|
664
|
+
*/
|
|
665
|
+
async function resolveCheckoutPromotionCode(
|
|
666
|
+
stripe: Stripe,
|
|
667
|
+
code: string,
|
|
668
|
+
): Promise<{ id: string; amountOffCents: number | null; percentOff: number | null }> {
|
|
669
|
+
const listed = await stripe.promotionCodes.list({
|
|
670
|
+
code,
|
|
671
|
+
active: true,
|
|
672
|
+
limit: 1,
|
|
673
|
+
expand: ["data.promotion.coupon"],
|
|
674
|
+
});
|
|
675
|
+
const promotionCode = listed.data[0];
|
|
676
|
+
const coupon =
|
|
677
|
+
promotionCode && typeof promotionCode.promotion?.coupon === "object"
|
|
678
|
+
? promotionCode.promotion.coupon
|
|
679
|
+
: null;
|
|
680
|
+
if (!promotionCode || !coupon?.valid) {
|
|
681
|
+
throw new HTTPException(422, { message: "That code isn't valid or has expired." });
|
|
682
|
+
}
|
|
683
|
+
const amountOffCents =
|
|
684
|
+
coupon.amount_off && coupon.currency === "usd"
|
|
685
|
+
? coupon.amount_off
|
|
686
|
+
: (coupon.currency_options?.usd?.amount_off ?? null);
|
|
687
|
+
return { id: promotionCode.id, amountOffCents, percentOff: coupon.percent_off ?? null };
|
|
328
688
|
}
|
|
329
689
|
|
|
330
690
|
async function mirrorPaymentIntentCustomer(deps: ApiRouteDeps, event: Stripe.Event): Promise<void> {
|
|
@@ -377,19 +737,21 @@ async function applyRefundDebit(
|
|
|
377
737
|
if (await hasCreditLedgerEntry(deps.db, accountId, idempotencyKey)) {
|
|
378
738
|
return;
|
|
379
739
|
}
|
|
740
|
+
const debitMicros = await creditAdjustmentMicros(deps, metadata, refund.amount);
|
|
380
741
|
await applyCreditLedgerEntry(deps.db, {
|
|
381
742
|
accountId,
|
|
382
743
|
type: "credit_refund",
|
|
383
|
-
amountMicros: -
|
|
744
|
+
amountMicros: -debitMicros,
|
|
384
745
|
sourceType: "stripe_refund",
|
|
385
746
|
sourceId: refund.id,
|
|
386
747
|
idempotencyKey,
|
|
387
748
|
metadata: {
|
|
388
749
|
stripeRefundId: refund.id,
|
|
389
750
|
stripePaymentIntentId: paymentIntentId(refund.payment_intent),
|
|
751
|
+
stripeRefundAmountCents: refund.amount,
|
|
390
752
|
},
|
|
391
753
|
});
|
|
392
|
-
recordCreditMicrosMetric(deps, "refund",
|
|
754
|
+
recordCreditMicrosMetric(deps, "refund", debitMicros);
|
|
393
755
|
}
|
|
394
756
|
|
|
395
757
|
async function holdDisputedCredits(
|
|
@@ -403,10 +765,11 @@ async function holdDisputedCredits(
|
|
|
403
765
|
if (!accountId) {
|
|
404
766
|
return;
|
|
405
767
|
}
|
|
768
|
+
const debitMicros = await creditAdjustmentMicros(deps, metadata, dispute.amount);
|
|
406
769
|
await applyCreditLedgerEntry(deps.db, {
|
|
407
770
|
accountId,
|
|
408
771
|
type: "credit_dispute_hold",
|
|
409
|
-
amountMicros: -
|
|
772
|
+
amountMicros: -debitMicros,
|
|
410
773
|
sourceType: "stripe_dispute",
|
|
411
774
|
sourceId: dispute.id,
|
|
412
775
|
idempotencyKey: `stripe:dispute_hold:${dispute.id}`,
|
|
@@ -432,10 +795,11 @@ async function releaseDisputedCredits(
|
|
|
432
795
|
if (!(await hasCreditLedgerEntry(deps.db, accountId, holdIdempotencyKey))) {
|
|
433
796
|
return;
|
|
434
797
|
}
|
|
798
|
+
const debitMicros = await creditAdjustmentMicros(deps, metadata, dispute.amount);
|
|
435
799
|
await applyCreditLedgerEntry(deps.db, {
|
|
436
800
|
accountId,
|
|
437
801
|
type: "credit_dispute_release",
|
|
438
|
-
amountMicros:
|
|
802
|
+
amountMicros: debitMicros,
|
|
439
803
|
sourceType: "stripe_dispute",
|
|
440
804
|
sourceId: dispute.id,
|
|
441
805
|
idempotencyKey: `stripe:dispute_release:${dispute.id}`,
|
|
@@ -443,6 +807,34 @@ async function releaseDisputedCredits(
|
|
|
443
807
|
});
|
|
444
808
|
}
|
|
445
809
|
|
|
810
|
+
async function creditAdjustmentMicros(
|
|
811
|
+
deps: ApiRouteDeps,
|
|
812
|
+
metadata: Stripe.Metadata | null,
|
|
813
|
+
amountCents: number,
|
|
814
|
+
): Promise<number> {
|
|
815
|
+
if (metadata?.opengeni_credit_coupon_v1 !== "1") return centsToMicros(amountCents);
|
|
816
|
+
const credit = creditMetadata(metadata, "Stripe adjustment");
|
|
817
|
+
const entry = await getCreditLedgerEntry(deps.db, credit.accountId, credit.idempotencyKey);
|
|
818
|
+
const paidCents = entry?.metadata.stripeAmountTotalCents;
|
|
819
|
+
if (
|
|
820
|
+
entry?.sourceType !== "stripe_checkout_session" ||
|
|
821
|
+
entry.amountMicros !== credit.amountMicros ||
|
|
822
|
+
typeof paidCents !== "number" ||
|
|
823
|
+
!Number.isSafeInteger(paidCents) ||
|
|
824
|
+
paidCents <= 0 ||
|
|
825
|
+
!Number.isSafeInteger(amountCents) ||
|
|
826
|
+
amountCents <= 0
|
|
827
|
+
) {
|
|
828
|
+
throw new Error("Stripe credit adjustment is missing a settled checkout");
|
|
829
|
+
}
|
|
830
|
+
const proportionalMicros =
|
|
831
|
+
(BigInt(entry.amountMicros) * BigInt(amountCents) + BigInt(Math.floor(paidCents / 2))) /
|
|
832
|
+
BigInt(paidCents);
|
|
833
|
+
return Number(
|
|
834
|
+
proportionalMicros > BigInt(entry.amountMicros) ? entry.amountMicros : proportionalMicros,
|
|
835
|
+
);
|
|
836
|
+
}
|
|
837
|
+
|
|
446
838
|
async function mirrorCustomer(
|
|
447
839
|
deps: ApiRouteDeps,
|
|
448
840
|
event: Stripe.Event,
|
|
@@ -480,18 +872,20 @@ async function metadataForRefund(
|
|
|
480
872
|
stripe: Stripe,
|
|
481
873
|
refund: Stripe.Refund,
|
|
482
874
|
): Promise<Stripe.Metadata | null> {
|
|
483
|
-
if (
|
|
875
|
+
if (refund.metadata?.opengeni_credit_idempotency_key) {
|
|
484
876
|
return refund.metadata;
|
|
485
877
|
}
|
|
486
878
|
const paymentIntent = paymentIntentId(refund.payment_intent);
|
|
487
|
-
return paymentIntent
|
|
879
|
+
return paymentIntent
|
|
880
|
+
? (await stripe.paymentIntents.retrieve(paymentIntent)).metadata
|
|
881
|
+
: refund.metadata;
|
|
488
882
|
}
|
|
489
883
|
|
|
490
884
|
async function metadataForDispute(
|
|
491
885
|
stripe: Stripe,
|
|
492
886
|
dispute: Stripe.Dispute,
|
|
493
887
|
): Promise<Stripe.Metadata | null> {
|
|
494
|
-
if (
|
|
888
|
+
if (dispute.metadata?.opengeni_credit_idempotency_key) {
|
|
495
889
|
return dispute.metadata;
|
|
496
890
|
}
|
|
497
891
|
const paymentIntent = paymentIntentId(
|
|
@@ -503,7 +897,7 @@ async function metadataForDispute(
|
|
|
503
897
|
}
|
|
504
898
|
const chargeId = typeof dispute.charge === "string" ? dispute.charge : dispute.charge?.id;
|
|
505
899
|
if (!chargeId) {
|
|
506
|
-
return
|
|
900
|
+
return dispute.metadata;
|
|
507
901
|
}
|
|
508
902
|
const charge = await stripe.charges.retrieve(chargeId);
|
|
509
903
|
const chargePaymentIntent = paymentIntentId(charge.payment_intent);
|
|
@@ -512,21 +906,23 @@ async function metadataForDispute(
|
|
|
512
906
|
: charge.metadata;
|
|
513
907
|
}
|
|
514
908
|
|
|
515
|
-
|
|
516
|
-
metadata: Stripe.Metadata | null | undefined,
|
|
517
|
-
label: string,
|
|
518
|
-
): {
|
|
909
|
+
export type CheckoutCreditMetadata = {
|
|
519
910
|
accountId: string;
|
|
520
911
|
amountMicros: number;
|
|
521
912
|
idempotencyKey: string;
|
|
522
913
|
amountUsd?: string;
|
|
523
914
|
packageId?: string;
|
|
524
|
-
}
|
|
915
|
+
};
|
|
916
|
+
|
|
917
|
+
function creditMetadata(
|
|
918
|
+
metadata: Stripe.Metadata | null | undefined,
|
|
919
|
+
label: string,
|
|
920
|
+
): CheckoutCreditMetadata {
|
|
525
921
|
const accountId = metadata?.opengeni_account_id;
|
|
526
922
|
const amountMicros = Number(metadata?.opengeni_credit_micros);
|
|
527
923
|
const idempotencyKey = metadata?.opengeni_credit_idempotency_key;
|
|
528
924
|
if (!accountId || !Number.isSafeInteger(amountMicros) || amountMicros <= 0 || !idempotencyKey) {
|
|
529
|
-
throw new Error(`${label} is missing
|
|
925
|
+
throw new Error(`${label} is missing Opengeni credit metadata`);
|
|
530
926
|
}
|
|
531
927
|
return {
|
|
532
928
|
accountId,
|
|
@@ -582,7 +978,26 @@ export function stripeCustomerProvider(
|
|
|
582
978
|
: "stripe:test";
|
|
583
979
|
}
|
|
584
980
|
|
|
585
|
-
|
|
981
|
+
/** Billing routes do not traverse the workspace actor middleware. Always bind
|
|
982
|
+
* these reads explicitly; only a revalidated live attempt may supply a human
|
|
983
|
+
* initiator. Neither query parameters nor serviceInitiator claims are proof. */
|
|
984
|
+
export async function withBillingUsageActor<T>(
|
|
985
|
+
deps: ApiRouteDeps,
|
|
986
|
+
context: AccessContext,
|
|
987
|
+
accountId: string,
|
|
988
|
+
read: () => Promise<T>,
|
|
989
|
+
): Promise<T> {
|
|
990
|
+
const attemptGrant = context.workspaceGrants.find(
|
|
991
|
+
(grant) =>
|
|
992
|
+
grant.accountId === accountId &&
|
|
993
|
+
grant.subjectId === context.subjectId &&
|
|
994
|
+
grant.principalKind === "agent_attempt",
|
|
995
|
+
);
|
|
996
|
+
if (attemptGrant) return await withAccessGrantSessionRlsContext(deps, attemptGrant, read);
|
|
997
|
+
return await withSessionRlsActorContext({ subjectId: context.subjectId }, read);
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
export function requireSelectedAccount(
|
|
586
1001
|
context: AccessContext,
|
|
587
1002
|
requested: string | undefined,
|
|
588
1003
|
permission: Permission,
|