@opengeni/api-router 0.30.1 → 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-office-import.d.ts +7 -4
- 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 +1129 -166
- 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 +25 -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 +113 -10
- 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 +71 -3
- 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/files.d.ts +4 -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 +1974 -256
- 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 +77 -9
- 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 +820 -222
- 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 +3170 -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 -38
- 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 +839 -131
- 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 +1040 -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 +337 -39
- 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-2PQMSRCB.js +0 -49759
- package/dist/chunk-2PQMSRCB.js.map +0 -1
- package/dist/routes/packs.d.ts +0 -3
- package/src/routes/packs.ts +0 -729
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { createHmac, randomBytes } from "node:crypto";
|
|
2
|
+
import { HTTPException } from "hono/http-exception";
|
|
3
|
+
|
|
4
|
+
export const INSIGHTS_RESPONSE_TTL_MS = 60_000;
|
|
5
|
+
const MAX_ENTRIES = 128,
|
|
6
|
+
MAX_BYTES = 8 * 1024 * 1024,
|
|
7
|
+
MAX_ENTRY_BYTES = 1024 * 1024;
|
|
8
|
+
|
|
9
|
+
/** Ephemeral keyed digest: raw credentials never become retained map keys or logs. */
|
|
10
|
+
export function createInsightsCredentialPartition() {
|
|
11
|
+
const secret = randomBytes(32);
|
|
12
|
+
return (authorization: string | undefined, cookie: string | undefined) =>
|
|
13
|
+
createHmac("sha256", secret)
|
|
14
|
+
.update(JSON.stringify([authorization ?? null, cookie ?? null]))
|
|
15
|
+
.digest("hex");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function createInsightsResponseCache(clock: () => number = () => Date.now()) {
|
|
19
|
+
const entries = new Map<
|
|
20
|
+
string,
|
|
21
|
+
{ json: string; fence: string; expiresAt: number; bytes: number }
|
|
22
|
+
>();
|
|
23
|
+
let bytes = 0;
|
|
24
|
+
const remove = (key: string) => {
|
|
25
|
+
const entry = entries.get(key);
|
|
26
|
+
if (entry) {
|
|
27
|
+
bytes -= entry.bytes;
|
|
28
|
+
entries.delete(key);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
return {
|
|
32
|
+
get<T>(key: string, fence: string): T | null {
|
|
33
|
+
const entry = entries.get(key);
|
|
34
|
+
if (!entry) return null;
|
|
35
|
+
if (entry.expiresAt <= clock() || entry.fence !== fence) {
|
|
36
|
+
remove(key);
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
// The TTL is never extended by a hit, and callers receive isolated objects.
|
|
40
|
+
return JSON.parse(entry.json) as T;
|
|
41
|
+
},
|
|
42
|
+
put(key: string, fence: string, value: unknown): void {
|
|
43
|
+
const json = JSON.stringify(value),
|
|
44
|
+
size = Buffer.byteLength(json);
|
|
45
|
+
if (size > MAX_ENTRY_BYTES) return;
|
|
46
|
+
for (const [existingKey, entry] of entries)
|
|
47
|
+
if (entry.expiresAt <= clock()) remove(existingKey);
|
|
48
|
+
remove(key);
|
|
49
|
+
for (const existingKey of entries.keys()) {
|
|
50
|
+
if (entries.size < MAX_ENTRIES && bytes + size <= MAX_BYTES) break;
|
|
51
|
+
remove(existingKey);
|
|
52
|
+
}
|
|
53
|
+
entries.set(key, { json, fence, expiresAt: clock() + INSIGHTS_RESPONSE_TTL_MS, bytes: size });
|
|
54
|
+
bytes += size;
|
|
55
|
+
},
|
|
56
|
+
get size() {
|
|
57
|
+
return entries.size;
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Match existing bounded cause-chain PostgreSQL timeout handling; never expose SQL. */
|
|
63
|
+
export async function insightsWithFriendlyTimeout<T>(read: () => Promise<T>): Promise<T> {
|
|
64
|
+
try {
|
|
65
|
+
return await read();
|
|
66
|
+
} catch (error) {
|
|
67
|
+
let current = error;
|
|
68
|
+
for (let depth = 0; depth < 4 && current && typeof current === "object"; depth++) {
|
|
69
|
+
const candidate = current as { code?: unknown; message?: unknown; cause?: unknown };
|
|
70
|
+
if (
|
|
71
|
+
candidate.code === "57014" ||
|
|
72
|
+
(typeof candidate.message === "string" &&
|
|
73
|
+
candidate.message.toLowerCase().includes("statement timeout"))
|
|
74
|
+
) {
|
|
75
|
+
throw new HTTPException(408, {
|
|
76
|
+
message: "This range has too much data right now. Try a shorter range.",
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
current = candidate.cause;
|
|
80
|
+
}
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import {
|
|
2
|
+
InsightsCallsResponse,
|
|
3
|
+
InsightsUsageResponse,
|
|
4
|
+
OrganizationInsightsCallsQuery,
|
|
5
|
+
OrganizationInsightsUsageQuery,
|
|
6
|
+
WorkspaceInsightsCallsQuery,
|
|
7
|
+
WorkspaceInsightsUsageQuery,
|
|
8
|
+
} from "@opengeni/contracts/insights-usage";
|
|
9
|
+
import type { AccessContext } from "@opengeni/contracts";
|
|
10
|
+
import {
|
|
11
|
+
accountScopedApiKeyWorkspaceAuthority,
|
|
12
|
+
getInsightsUsage,
|
|
13
|
+
hasPermission,
|
|
14
|
+
listInsightsCalls,
|
|
15
|
+
requireAccessContext,
|
|
16
|
+
requireAccessGrant,
|
|
17
|
+
type ApiRouteDeps,
|
|
18
|
+
type InsightsQueryScope,
|
|
19
|
+
} from "@opengeni/core";
|
|
20
|
+
import {
|
|
21
|
+
currentSessionRlsActorIdentityKey,
|
|
22
|
+
InvalidInsightsCallsCursorError,
|
|
23
|
+
readInsightsResponseCacheFence,
|
|
24
|
+
} from "@opengeni/db";
|
|
25
|
+
import type { Context, Hono } from "hono";
|
|
26
|
+
import { createHash } from "node:crypto";
|
|
27
|
+
import { HTTPException } from "hono/http-exception";
|
|
28
|
+
import { requireSelectedAccount, withBillingUsageActor } from "./billing";
|
|
29
|
+
import { createInFlightCoalescer } from "./insights";
|
|
30
|
+
import {
|
|
31
|
+
createInsightsCredentialPartition,
|
|
32
|
+
createInsightsResponseCache,
|
|
33
|
+
insightsWithFriendlyTimeout,
|
|
34
|
+
} from "./insights-response-cache";
|
|
35
|
+
|
|
36
|
+
/** Keep repeated filters, but reject repeated scalar options in the strict schema. */
|
|
37
|
+
export function insightsQueryParameters(
|
|
38
|
+
query: Record<string, string[]>,
|
|
39
|
+
): Record<string, string | string[]> {
|
|
40
|
+
return Object.fromEntries(
|
|
41
|
+
Object.entries(query).map(([key, values]) => [key, values.length === 1 ? values[0]! : values]),
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Frozen interim capabilities: a newer additive contract must not silently widen a raw query. */
|
|
46
|
+
export function insightsRawQuerySupported(
|
|
47
|
+
query: Record<string, unknown>,
|
|
48
|
+
organization: boolean,
|
|
49
|
+
calls: boolean,
|
|
50
|
+
): boolean {
|
|
51
|
+
const keys = new Set([
|
|
52
|
+
"range",
|
|
53
|
+
"provider",
|
|
54
|
+
"model",
|
|
55
|
+
"payer",
|
|
56
|
+
"projectId",
|
|
57
|
+
"person",
|
|
58
|
+
"rootSessionId",
|
|
59
|
+
"scheduleId",
|
|
60
|
+
"limit",
|
|
61
|
+
...(organization ? ["workspaceId"] : []),
|
|
62
|
+
...(calls ? ["cursor"] : ["groupBy", "seriesGroups"]),
|
|
63
|
+
]);
|
|
64
|
+
if (Object.keys(query).some((key) => !keys.has(key))) return false;
|
|
65
|
+
if (
|
|
66
|
+
query.range !== undefined &&
|
|
67
|
+
!new Set(["today", "week", "month", "30d", "90d", "ytd"]).has(String(query.range))
|
|
68
|
+
)
|
|
69
|
+
return false;
|
|
70
|
+
return (
|
|
71
|
+
query.groupBy === undefined ||
|
|
72
|
+
new Set([
|
|
73
|
+
"model",
|
|
74
|
+
"provider",
|
|
75
|
+
"payer",
|
|
76
|
+
"project",
|
|
77
|
+
"rootSession",
|
|
78
|
+
"person",
|
|
79
|
+
"schedule",
|
|
80
|
+
...(organization ? ["workspace"] : []),
|
|
81
|
+
]).has(String(query.groupBy))
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function organizationCacheAuthority(context: AccessContext, accountId: string) {
|
|
86
|
+
const key = accountScopedApiKeyWorkspaceAuthority(context);
|
|
87
|
+
return [
|
|
88
|
+
context.subjectId,
|
|
89
|
+
context.accountGrants
|
|
90
|
+
.filter((g) => g.accountId === accountId)
|
|
91
|
+
.map((g) => [g.subjectId, g.role, [...g.permissions].sort()]),
|
|
92
|
+
context.workspaceGrants
|
|
93
|
+
.filter((g) => g.accountId === accountId)
|
|
94
|
+
.map((g) => [g.workspaceId, g.subjectId, g.permissionMode, [...g.permissions].sort()])
|
|
95
|
+
.sort(),
|
|
96
|
+
key
|
|
97
|
+
? [key.accountId, key.permissionMode, [...key.permissions].sort(), key.workspaceScope]
|
|
98
|
+
: null,
|
|
99
|
+
];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function rawUsageResponse(response: Awaited<ReturnType<typeof getInsightsUsage>>) {
|
|
103
|
+
const parsed = InsightsUsageResponse.parse(response);
|
|
104
|
+
// A newer contract can supply an additive default. Absence is a capability
|
|
105
|
+
// signal, so this raw implementation must not manufacture source support.
|
|
106
|
+
if (!Object.hasOwn(response.facets, "sources")) Reflect.deleteProperty(parsed.facets, "sources");
|
|
107
|
+
return parsed;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** The actor and its canonical detail authority are part of every sharing key. */
|
|
111
|
+
export function insightsUsageCoalesceKey(
|
|
112
|
+
scope: InsightsQueryScope,
|
|
113
|
+
query: unknown,
|
|
114
|
+
actor: string | null,
|
|
115
|
+
): string {
|
|
116
|
+
return JSON.stringify([
|
|
117
|
+
scope.accountId,
|
|
118
|
+
scope.workspaceId,
|
|
119
|
+
[...(scope.detailsWorkspaceIds ?? [])].sort(),
|
|
120
|
+
scope.detailsSharedWorkspaces === true,
|
|
121
|
+
query,
|
|
122
|
+
actor,
|
|
123
|
+
]);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Reuse canonical read grants, never infer session access from billing authority. */
|
|
127
|
+
export function organizationInsightsScope(
|
|
128
|
+
context: AccessContext,
|
|
129
|
+
accountId: string,
|
|
130
|
+
): InsightsQueryScope {
|
|
131
|
+
const authority = accountScopedApiKeyWorkspaceAuthority(context);
|
|
132
|
+
if (authority?.accountId === accountId) {
|
|
133
|
+
const readable = hasPermission(
|
|
134
|
+
authority.permissions,
|
|
135
|
+
"sessions:read",
|
|
136
|
+
authority.permissionMode,
|
|
137
|
+
);
|
|
138
|
+
return {
|
|
139
|
+
accountId,
|
|
140
|
+
workspaceId: null,
|
|
141
|
+
detailsWorkspaceIds:
|
|
142
|
+
readable && authority.workspaceScope.kind === "selected"
|
|
143
|
+
? [...authority.workspaceScope.workspaceIds].sort()
|
|
144
|
+
: [],
|
|
145
|
+
detailsSharedWorkspaces: readable && authority.workspaceScope.kind === "all",
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
accountId,
|
|
150
|
+
workspaceId: null,
|
|
151
|
+
detailsWorkspaceIds: [
|
|
152
|
+
...new Set(
|
|
153
|
+
context.workspaceGrants
|
|
154
|
+
.filter(
|
|
155
|
+
(grant) =>
|
|
156
|
+
grant.accountId === accountId &&
|
|
157
|
+
grant.subjectId === context.subjectId &&
|
|
158
|
+
hasPermission(grant.permissions, "sessions:read", grant.permissionMode),
|
|
159
|
+
)
|
|
160
|
+
.map((grant) => grant.workspaceId),
|
|
161
|
+
),
|
|
162
|
+
].sort(),
|
|
163
|
+
detailsSharedWorkspaces: false,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function callsWithValidation(
|
|
168
|
+
read: () => Promise<Awaited<ReturnType<typeof listInsightsCalls>>>,
|
|
169
|
+
) {
|
|
170
|
+
try {
|
|
171
|
+
return await read();
|
|
172
|
+
} catch (error) {
|
|
173
|
+
if (error instanceof InvalidInsightsCallsCursorError)
|
|
174
|
+
throw new HTTPException(400, { message: "invalid Insights calls cursor" });
|
|
175
|
+
throw error;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function registerInsightsUsageRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
180
|
+
const usage = createInFlightCoalescer<InsightsUsageResponse>();
|
|
181
|
+
const calls = createInFlightCoalescer<InsightsCallsResponse>();
|
|
182
|
+
const cache = createInsightsResponseCache();
|
|
183
|
+
const credentialPartition = createInsightsCredentialPartition();
|
|
184
|
+
async function cached<T>(
|
|
185
|
+
c: Context,
|
|
186
|
+
kind: "usage" | "calls",
|
|
187
|
+
scope: InsightsQueryScope,
|
|
188
|
+
query: object,
|
|
189
|
+
authority: unknown,
|
|
190
|
+
coalescer: { run(key: string, work: () => Promise<T>): Promise<T> },
|
|
191
|
+
read: () => Promise<T>,
|
|
192
|
+
): Promise<T> {
|
|
193
|
+
// This runs only after this request's normal authentication and scope checks.
|
|
194
|
+
const normalized = Object.fromEntries(
|
|
195
|
+
Object.entries(query)
|
|
196
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
197
|
+
.map(([key, value]) => [key, Array.isArray(value) ? [...new Set(value)].sort() : value]),
|
|
198
|
+
);
|
|
199
|
+
const key = createHash("sha256")
|
|
200
|
+
.update(
|
|
201
|
+
JSON.stringify([
|
|
202
|
+
kind,
|
|
203
|
+
authority,
|
|
204
|
+
insightsUsageCoalesceKey(scope, normalized, currentSessionRlsActorIdentityKey()),
|
|
205
|
+
credentialPartition(c.req.header("authorization"), c.req.header("cookie")),
|
|
206
|
+
new Date().toISOString().slice(0, 10),
|
|
207
|
+
]),
|
|
208
|
+
)
|
|
209
|
+
.digest("hex");
|
|
210
|
+
const fence = await readInsightsResponseCacheFence(deps.db, scope);
|
|
211
|
+
if (fence !== null) {
|
|
212
|
+
const hit = cache.get<T>(key, fence);
|
|
213
|
+
if (hit !== null) return hit;
|
|
214
|
+
}
|
|
215
|
+
return await coalescer.run(`${key}:${fence ?? "uncached"}`, async () => {
|
|
216
|
+
const response = await insightsWithFriendlyTimeout(read);
|
|
217
|
+
// Store validated successes only, and only if metadata remained stable.
|
|
218
|
+
if (fence !== null && fence === (await readInsightsResponseCacheFence(deps.db, scope)))
|
|
219
|
+
cache.put(key, fence, response);
|
|
220
|
+
return response;
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
app.get("/v1/workspaces/:workspaceId/insights/usage", async (c) => {
|
|
225
|
+
const workspaceId = c.req.param("workspaceId");
|
|
226
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:admin");
|
|
227
|
+
const query = insightsQueryParameters(c.req.queries());
|
|
228
|
+
if (!insightsRawQuerySupported(query, false, false))
|
|
229
|
+
throw new HTTPException(400, { message: "unsupported interim Insights query" });
|
|
230
|
+
const parsed = WorkspaceInsightsUsageQuery.safeParse(query);
|
|
231
|
+
if (!parsed.success) throw new HTTPException(400, { message: "invalid Insights usage query" });
|
|
232
|
+
const scope = {
|
|
233
|
+
accountId: grant.accountId,
|
|
234
|
+
workspaceId,
|
|
235
|
+
detailsWorkspaceIds: hasPermission(grant.permissions, "sessions:read", grant.permissionMode)
|
|
236
|
+
? [workspaceId]
|
|
237
|
+
: [],
|
|
238
|
+
};
|
|
239
|
+
// The API's workspace middleware already binds/revalidates this grant's RLS actor.
|
|
240
|
+
const response = await cached(
|
|
241
|
+
c,
|
|
242
|
+
"usage",
|
|
243
|
+
scope,
|
|
244
|
+
parsed.data,
|
|
245
|
+
[grant.subjectId, grant.permissionMode, [...grant.permissions].sort()],
|
|
246
|
+
usage,
|
|
247
|
+
async () =>
|
|
248
|
+
rawUsageResponse(await getInsightsUsage(deps.db, { ...scope, query: parsed.data })),
|
|
249
|
+
);
|
|
250
|
+
c.header("cache-control", "private, no-store");
|
|
251
|
+
c.header("x-opengeni-insights-max-staleness-seconds", "60");
|
|
252
|
+
return c.json(response);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
app.get("/v1/workspaces/:workspaceId/insights/calls", async (c) => {
|
|
256
|
+
const workspaceId = c.req.param("workspaceId");
|
|
257
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:admin");
|
|
258
|
+
const query = insightsQueryParameters(c.req.queries());
|
|
259
|
+
if (!insightsRawQuerySupported(query, false, true))
|
|
260
|
+
throw new HTTPException(400, { message: "unsupported interim Insights query" });
|
|
261
|
+
const parsed = WorkspaceInsightsCallsQuery.safeParse(query);
|
|
262
|
+
if (!parsed.success) throw new HTTPException(400, { message: "invalid Insights calls query" });
|
|
263
|
+
const scope = {
|
|
264
|
+
accountId: grant.accountId,
|
|
265
|
+
workspaceId,
|
|
266
|
+
detailsWorkspaceIds: hasPermission(grant.permissions, "sessions:read", grant.permissionMode)
|
|
267
|
+
? [workspaceId]
|
|
268
|
+
: [],
|
|
269
|
+
};
|
|
270
|
+
const response = await cached(
|
|
271
|
+
c,
|
|
272
|
+
"calls",
|
|
273
|
+
scope,
|
|
274
|
+
parsed.data,
|
|
275
|
+
[grant.subjectId, grant.permissionMode, [...grant.permissions].sort()],
|
|
276
|
+
calls,
|
|
277
|
+
async () =>
|
|
278
|
+
InsightsCallsResponse.parse(
|
|
279
|
+
await callsWithValidation(() =>
|
|
280
|
+
listInsightsCalls(deps.db, { ...scope, query: parsed.data }),
|
|
281
|
+
),
|
|
282
|
+
),
|
|
283
|
+
);
|
|
284
|
+
c.header("cache-control", "private, no-store");
|
|
285
|
+
c.header("x-opengeni-insights-max-staleness-seconds", "60");
|
|
286
|
+
return c.json(response);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
app.get("/v1/organizations/:accountId/insights/usage", async (c) => {
|
|
290
|
+
const context = await requireAccessContext(c, deps);
|
|
291
|
+
const accountId = requireSelectedAccount(context, c.req.param("accountId"), "billing:read");
|
|
292
|
+
const query = insightsQueryParameters(c.req.queries());
|
|
293
|
+
if (!insightsRawQuerySupported(query, true, false))
|
|
294
|
+
throw new HTTPException(400, { message: "unsupported interim Insights query" });
|
|
295
|
+
const parsed = OrganizationInsightsUsageQuery.safeParse(query);
|
|
296
|
+
if (!parsed.success) throw new HTTPException(400, { message: "invalid Insights usage query" });
|
|
297
|
+
const scope = organizationInsightsScope(context, accountId);
|
|
298
|
+
const response = await withBillingUsageActor(deps, context, accountId, () =>
|
|
299
|
+
cached(
|
|
300
|
+
c,
|
|
301
|
+
"usage",
|
|
302
|
+
scope,
|
|
303
|
+
parsed.data,
|
|
304
|
+
organizationCacheAuthority(context, accountId),
|
|
305
|
+
usage,
|
|
306
|
+
async () =>
|
|
307
|
+
rawUsageResponse(await getInsightsUsage(deps.db, { ...scope, query: parsed.data })),
|
|
308
|
+
),
|
|
309
|
+
);
|
|
310
|
+
c.header("cache-control", "private, no-store");
|
|
311
|
+
c.header("x-opengeni-insights-max-staleness-seconds", "60");
|
|
312
|
+
return c.json(response);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
app.get("/v1/organizations/:accountId/insights/calls", async (c) => {
|
|
316
|
+
const context = await requireAccessContext(c, deps);
|
|
317
|
+
const accountId = requireSelectedAccount(context, c.req.param("accountId"), "billing:read");
|
|
318
|
+
const query = insightsQueryParameters(c.req.queries());
|
|
319
|
+
if (!insightsRawQuerySupported(query, true, true))
|
|
320
|
+
throw new HTTPException(400, { message: "unsupported interim Insights query" });
|
|
321
|
+
const parsed = OrganizationInsightsCallsQuery.safeParse(query);
|
|
322
|
+
if (!parsed.success) throw new HTTPException(400, { message: "invalid Insights calls query" });
|
|
323
|
+
const scope = organizationInsightsScope(context, accountId);
|
|
324
|
+
// Call readers intersect these canonical grants with current actor visibility.
|
|
325
|
+
const response = await withBillingUsageActor(deps, context, accountId, () =>
|
|
326
|
+
cached(
|
|
327
|
+
c,
|
|
328
|
+
"calls",
|
|
329
|
+
scope,
|
|
330
|
+
parsed.data,
|
|
331
|
+
organizationCacheAuthority(context, accountId),
|
|
332
|
+
calls,
|
|
333
|
+
async () =>
|
|
334
|
+
InsightsCallsResponse.parse(
|
|
335
|
+
await callsWithValidation(() =>
|
|
336
|
+
listInsightsCalls(deps.db, { ...scope, query: parsed.data }),
|
|
337
|
+
),
|
|
338
|
+
),
|
|
339
|
+
),
|
|
340
|
+
);
|
|
341
|
+
c.header("cache-control", "private, no-store");
|
|
342
|
+
c.header("x-opengeni-insights-max-staleness-seconds", "60");
|
|
343
|
+
return c.json(response);
|
|
344
|
+
});
|
|
345
|
+
}
|
package/src/routes/insights.ts
CHANGED
|
@@ -1,30 +1,164 @@
|
|
|
1
1
|
import { InsightsRange, WorkspaceInsightsResponse } from "@opengeni/contracts";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
getWorkspaceInsights,
|
|
4
|
+
measureInsightsPhase,
|
|
5
|
+
workspaceInsightsPhaseMetricObserver,
|
|
6
|
+
normalizeWorkspaceInsightsFilter,
|
|
7
|
+
normalizeWorkspaceInsightsSessionScope,
|
|
8
|
+
requireAccessGrant,
|
|
9
|
+
WorkspaceInsightsFilterValidationError,
|
|
10
|
+
type ApiRouteDeps,
|
|
11
|
+
type WorkspaceInsightsFilterField,
|
|
12
|
+
} from "@opengeni/core";
|
|
13
|
+
import { currentSessionRlsActorIdentityKey } from "@opengeni/db";
|
|
14
|
+
import { workspaceInsightsMetricObserver } from "@opengeni/observability";
|
|
3
15
|
import type { Hono } from "hono";
|
|
4
16
|
import { HTTPException } from "hono/http-exception";
|
|
5
17
|
|
|
18
|
+
export function normalizeWorkspaceInsightsQueryFilter(
|
|
19
|
+
value: string | null | undefined,
|
|
20
|
+
field: WorkspaceInsightsFilterField,
|
|
21
|
+
): string | null {
|
|
22
|
+
try {
|
|
23
|
+
return field === "rootSessionId" || field === "sessionId"
|
|
24
|
+
? normalizeWorkspaceInsightsSessionScope(value, field)
|
|
25
|
+
: normalizeWorkspaceInsightsFilter(value, field);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
if (error instanceof WorkspaceInsightsFilterValidationError) {
|
|
28
|
+
throw new HTTPException(400, { message: error.message });
|
|
29
|
+
}
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Share one in-flight computation between identical concurrent callers.
|
|
36
|
+
*
|
|
37
|
+
* A reload, a second tab, or a retried fetch joins the rollup already running
|
|
38
|
+
* instead of starting another multi-second aggregate on the database.
|
|
39
|
+
* Authorization and filter validation still run per request before a caller
|
|
40
|
+
* may join; the map only ever holds running work. The caller supplies the
|
|
41
|
+
* complete sharing key, which must identify everything the result depends on.
|
|
42
|
+
*/
|
|
43
|
+
export function createInFlightCoalescer<T>(): {
|
|
44
|
+
run: (key: string, work: () => Promise<T>) => Promise<T>;
|
|
45
|
+
readonly size: number;
|
|
46
|
+
} {
|
|
47
|
+
const inFlight = new Map<string, Promise<T>>();
|
|
48
|
+
return {
|
|
49
|
+
run(key, work) {
|
|
50
|
+
const pending = inFlight.get(key);
|
|
51
|
+
if (pending) return pending;
|
|
52
|
+
const started: Promise<T> = work().finally(() => {
|
|
53
|
+
if (inFlight.get(key) === started) inFlight.delete(key);
|
|
54
|
+
});
|
|
55
|
+
inFlight.set(key, started);
|
|
56
|
+
return started;
|
|
57
|
+
},
|
|
58
|
+
get size() {
|
|
59
|
+
return inFlight.size;
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Insights rows are filtered by the database RLS actor (private sessions are
|
|
66
|
+
* visible only to their owner), so two administrators of one workspace can
|
|
67
|
+
* legitimately receive different responses. The actor identity is part of
|
|
68
|
+
* the key; requests share work only when every visibility input is equal.
|
|
69
|
+
*/
|
|
70
|
+
export function workspaceInsightsCoalesceKey(input: {
|
|
71
|
+
workspaceId: string;
|
|
72
|
+
range: string;
|
|
73
|
+
provider: string | null;
|
|
74
|
+
model: string | null;
|
|
75
|
+
rootSessionId?: string | null;
|
|
76
|
+
sessionId?: string | null;
|
|
77
|
+
rlsActor: string | null;
|
|
78
|
+
}): string {
|
|
79
|
+
return JSON.stringify([
|
|
80
|
+
input.workspaceId,
|
|
81
|
+
input.range,
|
|
82
|
+
input.provider,
|
|
83
|
+
input.model,
|
|
84
|
+
input.rootSessionId ?? null,
|
|
85
|
+
input.sessionId ?? null,
|
|
86
|
+
input.rlsActor,
|
|
87
|
+
]);
|
|
88
|
+
}
|
|
89
|
+
|
|
6
90
|
export function registerInsightsRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
91
|
+
const observeRequest = workspaceInsightsMetricObserver(deps.observability);
|
|
92
|
+
const observePhase = workspaceInsightsPhaseMetricObserver(deps.observability);
|
|
93
|
+
const coalesce = createInFlightCoalescer<Awaited<ReturnType<typeof getWorkspaceInsights>>>();
|
|
7
94
|
app.get("/v1/workspaces/:workspaceId/insights", async (c) => {
|
|
8
|
-
const
|
|
9
|
-
await requireAccessGrant(c, deps, workspaceId, "workspace:admin");
|
|
10
|
-
|
|
95
|
+
const startedAtMs = performance.now();
|
|
11
96
|
const rangeRaw = c.req.query("range") ?? "week";
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
97
|
+
const providerRaw = c.req.query("provider");
|
|
98
|
+
const modelRaw = c.req.query("model");
|
|
99
|
+
const rootSessionIdRaw = c.req.query("rootSessionId");
|
|
100
|
+
const sessionIdRaw = c.req.query("sessionId");
|
|
101
|
+
let provider: string | null = null;
|
|
102
|
+
let model: string | null = null;
|
|
103
|
+
let outcome = "failed";
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
const workspaceId = c.req.param("workspaceId");
|
|
107
|
+
await measureInsightsPhase(observePhase, "auth", () =>
|
|
108
|
+
requireAccessGrant(c, deps, workspaceId, "workspace:admin"),
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
const rangeParsed = InsightsRange.safeParse(rangeRaw);
|
|
112
|
+
if (!rangeParsed.success) {
|
|
113
|
+
throw new HTTPException(400, {
|
|
114
|
+
message: "range must be one of today|week|month|ytd",
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
provider = normalizeWorkspaceInsightsQueryFilter(providerRaw, "provider");
|
|
118
|
+
model = normalizeWorkspaceInsightsQueryFilter(modelRaw, "model");
|
|
119
|
+
const rootSessionId = normalizeWorkspaceInsightsQueryFilter(
|
|
120
|
+
rootSessionIdRaw,
|
|
121
|
+
"rootSessionId",
|
|
122
|
+
);
|
|
123
|
+
const sessionId = normalizeWorkspaceInsightsQueryFilter(sessionIdRaw, "sessionId");
|
|
124
|
+
|
|
125
|
+
const response = await coalesce.run(
|
|
126
|
+
workspaceInsightsCoalesceKey({
|
|
127
|
+
workspaceId,
|
|
128
|
+
range: rangeParsed.data,
|
|
129
|
+
provider,
|
|
130
|
+
model,
|
|
131
|
+
rootSessionId,
|
|
132
|
+
sessionId,
|
|
133
|
+
rlsActor: currentSessionRlsActorIdentityKey(),
|
|
134
|
+
}),
|
|
135
|
+
() =>
|
|
136
|
+
getWorkspaceInsights(
|
|
137
|
+
deps.db,
|
|
138
|
+
deps.settings,
|
|
139
|
+
{
|
|
140
|
+
workspaceId,
|
|
141
|
+
range: rangeParsed.data,
|
|
142
|
+
provider,
|
|
143
|
+
model,
|
|
144
|
+
rootSessionId,
|
|
145
|
+
sessionId,
|
|
146
|
+
},
|
|
147
|
+
observePhase,
|
|
148
|
+
),
|
|
149
|
+
);
|
|
150
|
+
c.header("cache-control", "private, no-store");
|
|
151
|
+
const result = c.json(WorkspaceInsightsResponse.parse(response));
|
|
152
|
+
outcome = "completed";
|
|
153
|
+
return result;
|
|
154
|
+
} finally {
|
|
155
|
+
observeRequest({
|
|
156
|
+
range: rangeRaw,
|
|
157
|
+
providerFiltered: provider !== null,
|
|
158
|
+
modelFiltered: model !== null,
|
|
159
|
+
outcome,
|
|
160
|
+
durationMs: performance.now() - startedAtMs,
|
|
16
161
|
});
|
|
17
162
|
}
|
|
18
|
-
const provider = c.req.query("provider");
|
|
19
|
-
const model = c.req.query("model");
|
|
20
|
-
|
|
21
|
-
const response = await getWorkspaceInsights(deps.db, deps.settings, {
|
|
22
|
-
workspaceId,
|
|
23
|
-
range: rangeParsed.data,
|
|
24
|
-
provider: provider && provider !== "all" ? provider : null,
|
|
25
|
-
model: model && model !== "all" ? model : null,
|
|
26
|
-
});
|
|
27
|
-
c.header("cache-control", "private, no-store");
|
|
28
|
-
return c.json(WorkspaceInsightsResponse.parse(response));
|
|
29
163
|
});
|
|
30
164
|
}
|