@useatlas/create 0.0.1
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/README.md +231 -0
- package/index.ts +829 -0
- package/package.json +38 -0
- package/templates/docker/.env.example +67 -0
- package/templates/docker/Dockerfile +52 -0
- package/templates/docker/bin/__tests__/benchmark.test.ts +598 -0
- package/templates/docker/bin/__tests__/duckdb-ingest.test.ts +171 -0
- package/templates/docker/bin/__tests__/eval.test.ts +434 -0
- package/templates/docker/bin/__tests__/matview-partition.test.ts +615 -0
- package/templates/docker/bin/__tests__/multi-source.test.ts +113 -0
- package/templates/docker/bin/__tests__/plugin-cli.test.ts +322 -0
- package/templates/docker/bin/__tests__/profiler-heuristics.test.ts +608 -0
- package/templates/docker/bin/__tests__/query.test.ts +240 -0
- package/templates/docker/bin/__tests__/schema-drift.test.ts +542 -0
- package/templates/docker/bin/__tests__/view-yaml-generation.test.ts +146 -0
- package/templates/docker/bin/atlas.ts +5044 -0
- package/templates/docker/bin/benchmark.ts +695 -0
- package/templates/docker/bin/enrich.ts +559 -0
- package/templates/docker/bin/eval.ts +770 -0
- package/templates/docker/bin/smoke.ts +438 -0
- package/templates/docker/data/.gitkeep +0 -0
- package/templates/docker/data/cybersec.sql +1961 -0
- package/templates/docker/data/demo-semantic/catalog.yml +40 -0
- package/templates/docker/data/demo-semantic/entities/accounts.yml +170 -0
- package/templates/docker/data/demo-semantic/entities/companies.yml +207 -0
- package/templates/docker/data/demo-semantic/entities/people.yml +145 -0
- package/templates/docker/data/demo-semantic/glossary.yml +22 -0
- package/templates/docker/data/demo-semantic/metrics/accounts.yml +38 -0
- package/templates/docker/data/demo-semantic/metrics/companies.yml +89 -0
- package/templates/docker/data/demo.sql +373 -0
- package/templates/docker/data/ecommerce.sql +1690 -0
- package/templates/docker/data/init-demo-db.sql +8 -0
- package/templates/docker/docker-compose.yml +34 -0
- package/templates/docker/docs/deploy.md +390 -0
- package/templates/docker/eslint.config.mjs +18 -0
- package/templates/docker/gitignore +5 -0
- package/templates/docker/next.config.ts +9 -0
- package/templates/docker/package.json +59 -0
- package/templates/docker/postcss.config.mjs +8 -0
- package/templates/docker/public/.gitkeep +0 -0
- package/templates/docker/public/favicon.svg +4 -0
- package/templates/docker/railway.json +13 -0
- package/templates/docker/render.yaml +34 -0
- package/templates/docker/semantic/catalog.yml +5 -0
- package/templates/docker/semantic/entities/.gitkeep +0 -0
- package/templates/docker/semantic/glossary.yml +6 -0
- package/templates/docker/semantic/metrics/.gitkeep +0 -0
- package/templates/docker/sidecar/Dockerfile +28 -0
- package/templates/docker/sidecar/railway.json +14 -0
- package/templates/docker/sidecar/server.ts +188 -0
- package/templates/docker/src/api/__tests__/actions.test.ts +683 -0
- package/templates/docker/src/api/__tests__/admin.test.ts +820 -0
- package/templates/docker/src/api/__tests__/auth.test.ts +165 -0
- package/templates/docker/src/api/__tests__/chat.test.ts +376 -0
- package/templates/docker/src/api/__tests__/conversations.test.ts +555 -0
- package/templates/docker/src/api/__tests__/cors.test.ts +135 -0
- package/templates/docker/src/api/__tests__/health-plugin.test.ts +169 -0
- package/templates/docker/src/api/__tests__/health.test.ts +261 -0
- package/templates/docker/src/api/__tests__/query.test.ts +891 -0
- package/templates/docker/src/api/__tests__/scheduled-tasks.test.ts +601 -0
- package/templates/docker/src/api/__tests__/slack.test.ts +847 -0
- package/templates/docker/src/api/index.ts +117 -0
- package/templates/docker/src/api/routes/actions.ts +274 -0
- package/templates/docker/src/api/routes/admin.ts +757 -0
- package/templates/docker/src/api/routes/auth.ts +48 -0
- package/templates/docker/src/api/routes/chat.ts +465 -0
- package/templates/docker/src/api/routes/conversations.ts +266 -0
- package/templates/docker/src/api/routes/health.ts +287 -0
- package/templates/docker/src/api/routes/openapi.ts +390 -0
- package/templates/docker/src/api/routes/query.ts +318 -0
- package/templates/docker/src/api/routes/scheduled-tasks.ts +467 -0
- package/templates/docker/src/api/routes/slack.ts +611 -0
- package/templates/docker/src/api/server.ts +226 -0
- package/templates/docker/src/app/api/[...route]/route.ts +33 -0
- package/templates/docker/src/app/error.tsx +24 -0
- package/templates/docker/src/app/globals.css +126 -0
- package/templates/docker/src/app/layout.tsx +19 -0
- package/templates/docker/src/app/page.tsx +14 -0
- package/templates/docker/src/global.d.ts +1 -0
- package/templates/docker/src/lib/__tests__/agent-cache.test.ts +437 -0
- package/templates/docker/src/lib/__tests__/agent-dialect.test.ts +114 -0
- package/templates/docker/src/lib/__tests__/agent-health-annotations.test.ts +164 -0
- package/templates/docker/src/lib/__tests__/agent-integration.test.ts +514 -0
- package/templates/docker/src/lib/__tests__/config-actions.test.ts +166 -0
- package/templates/docker/src/lib/__tests__/config.test.ts +1063 -0
- package/templates/docker/src/lib/__tests__/conversations.test.ts +589 -0
- package/templates/docker/src/lib/__tests__/errors.test.ts +256 -0
- package/templates/docker/src/lib/__tests__/logger.test.ts +200 -0
- package/templates/docker/src/lib/__tests__/providers.test.ts +99 -0
- package/templates/docker/src/lib/__tests__/rls.test.ts +435 -0
- package/templates/docker/src/lib/__tests__/scheduled-task-types.test.ts +124 -0
- package/templates/docker/src/lib/__tests__/scheduled-tasks.test.ts +550 -0
- package/templates/docker/src/lib/__tests__/semantic-index.test.ts +547 -0
- package/templates/docker/src/lib/__tests__/semantic-multisource.test.ts +544 -0
- package/templates/docker/src/lib/__tests__/semantic.test.ts +363 -0
- package/templates/docker/src/lib/__tests__/startup-actions.test.ts +452 -0
- package/templates/docker/src/lib/__tests__/startup.test.ts +465 -0
- package/templates/docker/src/lib/__tests__/tracing.test.ts +28 -0
- package/templates/docker/src/lib/action-types.ts +95 -0
- package/templates/docker/src/lib/agent-query.ts +178 -0
- package/templates/docker/src/lib/agent.ts +505 -0
- package/templates/docker/src/lib/api-url.ts +2 -0
- package/templates/docker/src/lib/auth/__tests__/audit.test.ts +418 -0
- package/templates/docker/src/lib/auth/__tests__/byot-integration.test.ts +222 -0
- package/templates/docker/src/lib/auth/__tests__/byot.test.ts +366 -0
- package/templates/docker/src/lib/auth/__tests__/detect.test.ts +190 -0
- package/templates/docker/src/lib/auth/__tests__/managed.test.ts +173 -0
- package/templates/docker/src/lib/auth/__tests__/middleware.test.ts +456 -0
- package/templates/docker/src/lib/auth/__tests__/migrate.test.ts +201 -0
- package/templates/docker/src/lib/auth/__tests__/permissions.test.ts +225 -0
- package/templates/docker/src/lib/auth/__tests__/server.test.ts +34 -0
- package/templates/docker/src/lib/auth/__tests__/simple-key.test.ts +176 -0
- package/templates/docker/src/lib/auth/__tests__/types.test.ts +44 -0
- package/templates/docker/src/lib/auth/audit.ts +89 -0
- package/templates/docker/src/lib/auth/byot.ts +158 -0
- package/templates/docker/src/lib/auth/client.ts +35 -0
- package/templates/docker/src/lib/auth/detect.ts +83 -0
- package/templates/docker/src/lib/auth/managed.ts +73 -0
- package/templates/docker/src/lib/auth/middleware.ts +208 -0
- package/templates/docker/src/lib/auth/migrate.ts +111 -0
- package/templates/docker/src/lib/auth/permissions.ts +156 -0
- package/templates/docker/src/lib/auth/server.ts +142 -0
- package/templates/docker/src/lib/auth/simple-key.ts +92 -0
- package/templates/docker/src/lib/auth/types.ts +49 -0
- package/templates/docker/src/lib/config.ts +704 -0
- package/templates/docker/src/lib/conversation-types.ts +29 -0
- package/templates/docker/src/lib/conversations.ts +270 -0
- package/templates/docker/src/lib/db/__tests__/connection.test.ts +69 -0
- package/templates/docker/src/lib/db/__tests__/duckdb.test.ts +141 -0
- package/templates/docker/src/lib/db/__tests__/internal.test.ts +387 -0
- package/templates/docker/src/lib/db/__tests__/registry-health.test.ts +207 -0
- package/templates/docker/src/lib/db/__tests__/registry-pool-limits.test.ts +156 -0
- package/templates/docker/src/lib/db/__tests__/registry.test.ts +595 -0
- package/templates/docker/src/lib/db/__tests__/salesforce.test.ts +339 -0
- package/templates/docker/src/lib/db/__tests__/snowflake.test.ts +217 -0
- package/templates/docker/src/lib/db/__tests__/source-rate-limit.test.ts +130 -0
- package/templates/docker/src/lib/db/connection.ts +753 -0
- package/templates/docker/src/lib/db/duckdb.ts +122 -0
- package/templates/docker/src/lib/db/internal.ts +273 -0
- package/templates/docker/src/lib/db/salesforce.ts +342 -0
- package/templates/docker/src/lib/db/source-rate-limit.ts +191 -0
- package/templates/docker/src/lib/errors.ts +154 -0
- package/templates/docker/src/lib/logger.ts +98 -0
- package/templates/docker/src/lib/plugins/__tests__/hooks-integration.test.ts +202 -0
- package/templates/docker/src/lib/plugins/__tests__/hooks.test.ts +529 -0
- package/templates/docker/src/lib/plugins/__tests__/migrate.test.ts +521 -0
- package/templates/docker/src/lib/plugins/__tests__/registry.test.ts +346 -0
- package/templates/docker/src/lib/plugins/__tests__/tools.test.ts +49 -0
- package/templates/docker/src/lib/plugins/__tests__/wiring.test.ts +585 -0
- package/templates/docker/src/lib/plugins/hooks.ts +162 -0
- package/templates/docker/src/lib/plugins/index.ts +9 -0
- package/templates/docker/src/lib/plugins/migrate.ts +309 -0
- package/templates/docker/src/lib/plugins/registry.ts +231 -0
- package/templates/docker/src/lib/plugins/tools.ts +39 -0
- package/templates/docker/src/lib/plugins/wiring.ts +291 -0
- package/templates/docker/src/lib/providers.ts +102 -0
- package/templates/docker/src/lib/rls.ts +321 -0
- package/templates/docker/src/lib/scheduled-task-types.ts +132 -0
- package/templates/docker/src/lib/scheduled-tasks.ts +475 -0
- package/templates/docker/src/lib/scheduler/__tests__/delivery.test.ts +192 -0
- package/templates/docker/src/lib/scheduler/__tests__/engine.test.ts +248 -0
- package/templates/docker/src/lib/scheduler/__tests__/format-email.test.ts +96 -0
- package/templates/docker/src/lib/scheduler/__tests__/format-slack.test.ts +78 -0
- package/templates/docker/src/lib/scheduler/__tests__/format-webhook.test.ts +78 -0
- package/templates/docker/src/lib/scheduler/delivery.ts +248 -0
- package/templates/docker/src/lib/scheduler/engine.ts +317 -0
- package/templates/docker/src/lib/scheduler/executor.ts +73 -0
- package/templates/docker/src/lib/scheduler/format-email.ts +109 -0
- package/templates/docker/src/lib/scheduler/format-slack.ts +35 -0
- package/templates/docker/src/lib/scheduler/format-webhook.ts +37 -0
- package/templates/docker/src/lib/scheduler/index.ts +7 -0
- package/templates/docker/src/lib/security.ts +11 -0
- package/templates/docker/src/lib/semantic-index.ts +503 -0
- package/templates/docker/src/lib/semantic.ts +387 -0
- package/templates/docker/src/lib/sidecar-types.ts +16 -0
- package/templates/docker/src/lib/slack/__tests__/api.test.ts +160 -0
- package/templates/docker/src/lib/slack/__tests__/format.test.ts +237 -0
- package/templates/docker/src/lib/slack/__tests__/store.test.ts +188 -0
- package/templates/docker/src/lib/slack/__tests__/threads.test.ts +112 -0
- package/templates/docker/src/lib/slack/__tests__/verify.test.ts +111 -0
- package/templates/docker/src/lib/slack/api.ts +102 -0
- package/templates/docker/src/lib/slack/format.ts +209 -0
- package/templates/docker/src/lib/slack/store.ts +107 -0
- package/templates/docker/src/lib/slack/threads.ts +64 -0
- package/templates/docker/src/lib/slack/verify.ts +71 -0
- package/templates/docker/src/lib/startup.ts +730 -0
- package/templates/docker/src/lib/tools/__tests__/action-permissions.test.ts +594 -0
- package/templates/docker/src/lib/tools/__tests__/custom-validation.test.ts +238 -0
- package/templates/docker/src/lib/tools/__tests__/explore-backend.test.ts +267 -0
- package/templates/docker/src/lib/tools/__tests__/explore-nsjail.test.ts +492 -0
- package/templates/docker/src/lib/tools/__tests__/explore-plugin.test.ts +374 -0
- package/templates/docker/src/lib/tools/__tests__/explore-sdk-compat.test.ts +82 -0
- package/templates/docker/src/lib/tools/__tests__/explore-sidecar.test.ts +208 -0
- package/templates/docker/src/lib/tools/__tests__/registry-actions.test.ts +144 -0
- package/templates/docker/src/lib/tools/__tests__/registry.test.ts +235 -0
- package/templates/docker/src/lib/tools/__tests__/salesforce-tool.test.ts +154 -0
- package/templates/docker/src/lib/tools/__tests__/soql-validation.test.ts +303 -0
- package/templates/docker/src/lib/tools/__tests__/sql-audit.test.ts +225 -0
- package/templates/docker/src/lib/tools/__tests__/sql-connection-whitelist.test.ts +98 -0
- package/templates/docker/src/lib/tools/__tests__/sql-duckdb.test.ts +233 -0
- package/templates/docker/src/lib/tools/__tests__/sql-ratelimit.test.ts +225 -0
- package/templates/docker/src/lib/tools/__tests__/sql.test.ts +1012 -0
- package/templates/docker/src/lib/tools/actions/__tests__/audit.test.ts +211 -0
- package/templates/docker/src/lib/tools/actions/__tests__/email.test.ts +378 -0
- package/templates/docker/src/lib/tools/actions/__tests__/handler.test.ts +681 -0
- package/templates/docker/src/lib/tools/actions/__tests__/jira.test.ts +427 -0
- package/templates/docker/src/lib/tools/actions/audit.ts +47 -0
- package/templates/docker/src/lib/tools/actions/email.ts +191 -0
- package/templates/docker/src/lib/tools/actions/handler.ts +591 -0
- package/templates/docker/src/lib/tools/actions/index.ts +23 -0
- package/templates/docker/src/lib/tools/actions/jira.ts +220 -0
- package/templates/docker/src/lib/tools/explore-nsjail.ts +343 -0
- package/templates/docker/src/lib/tools/explore-sandbox.ts +264 -0
- package/templates/docker/src/lib/tools/explore-sidecar.ts +163 -0
- package/templates/docker/src/lib/tools/explore.ts +379 -0
- package/templates/docker/src/lib/tools/registry.ts +221 -0
- package/templates/docker/src/lib/tools/salesforce.ts +138 -0
- package/templates/docker/src/lib/tools/soql-validation.ts +172 -0
- package/templates/docker/src/lib/tools/sql.ts +680 -0
- package/templates/docker/src/lib/tracing.ts +40 -0
- package/templates/docker/src/lib/utils.ts +6 -0
- package/templates/docker/src/test-setup.ts +38 -0
- package/templates/docker/src/types/vercel-sandbox.d.ts +54 -0
- package/templates/docker/src/ui/components/actions/action-approval-card.tsx +295 -0
- package/templates/docker/src/ui/components/actions/action-status-badge.tsx +50 -0
- package/templates/docker/src/ui/components/admin/admin-layout.tsx +26 -0
- package/templates/docker/src/ui/components/admin/admin-sidebar.tsx +96 -0
- package/templates/docker/src/ui/components/admin/empty-state.tsx +24 -0
- package/templates/docker/src/ui/components/admin/entity-detail.tsx +233 -0
- package/templates/docker/src/ui/components/admin/entity-list.tsx +96 -0
- package/templates/docker/src/ui/components/admin/error-banner.tsx +22 -0
- package/templates/docker/src/ui/components/admin/feature-disabled.tsx +44 -0
- package/templates/docker/src/ui/components/admin/health-badge.tsx +30 -0
- package/templates/docker/src/ui/components/admin/loading-state.tsx +14 -0
- package/templates/docker/src/ui/components/admin/stat-card.tsx +32 -0
- package/templates/docker/src/ui/components/atlas-chat.tsx +370 -0
- package/templates/docker/src/ui/components/chart/chart-detection.ts +261 -0
- package/templates/docker/src/ui/components/chart/result-chart.tsx +375 -0
- package/templates/docker/src/ui/components/chat/api-key-bar.tsx +66 -0
- package/templates/docker/src/ui/components/chat/copy-button.tsx +25 -0
- package/templates/docker/src/ui/components/chat/data-table.tsx +102 -0
- package/templates/docker/src/ui/components/chat/error-banner.tsx +32 -0
- package/templates/docker/src/ui/components/chat/explore-card.tsx +41 -0
- package/templates/docker/src/ui/components/chat/loading-card.tsx +10 -0
- package/templates/docker/src/ui/components/chat/managed-auth-card.tsx +116 -0
- package/templates/docker/src/ui/components/chat/markdown.tsx +72 -0
- package/templates/docker/src/ui/components/chat/sql-block.tsx +30 -0
- package/templates/docker/src/ui/components/chat/sql-result-card.tsx +144 -0
- package/templates/docker/src/ui/components/chat/starter-prompts.ts +6 -0
- package/templates/docker/src/ui/components/chat/tool-part.tsx +40 -0
- package/templates/docker/src/ui/components/chat/typing-indicator.tsx +19 -0
- package/templates/docker/src/ui/components/conversations/conversation-item.tsx +120 -0
- package/templates/docker/src/ui/components/conversations/conversation-list.tsx +66 -0
- package/templates/docker/src/ui/components/conversations/conversation-sidebar.tsx +78 -0
- package/templates/docker/src/ui/components/conversations/delete-confirmation.tsx +27 -0
- package/templates/docker/src/ui/context.tsx +78 -0
- package/templates/docker/src/ui/hooks/use-admin-fetch.ts +104 -0
- package/templates/docker/src/ui/hooks/use-conversations.ts +184 -0
- package/templates/docker/src/ui/hooks/use-dark-mode.ts +17 -0
- package/templates/docker/src/ui/lib/action-types.ts +63 -0
- package/templates/docker/src/ui/lib/helpers.ts +104 -0
- package/templates/docker/src/ui/lib/types.ts +145 -0
- package/templates/docker/tsconfig.json +41 -0
- package/templates/docker/vercel.json +3 -0
- package/templates/nextjs-standalone/.env.example +68 -0
- package/templates/nextjs-standalone/bin/__tests__/benchmark.test.ts +598 -0
- package/templates/nextjs-standalone/bin/__tests__/duckdb-ingest.test.ts +171 -0
- package/templates/nextjs-standalone/bin/__tests__/eval.test.ts +434 -0
- package/templates/nextjs-standalone/bin/__tests__/matview-partition.test.ts +615 -0
- package/templates/nextjs-standalone/bin/__tests__/multi-source.test.ts +113 -0
- package/templates/nextjs-standalone/bin/__tests__/plugin-cli.test.ts +322 -0
- package/templates/nextjs-standalone/bin/__tests__/profiler-heuristics.test.ts +608 -0
- package/templates/nextjs-standalone/bin/__tests__/query.test.ts +240 -0
- package/templates/nextjs-standalone/bin/__tests__/schema-drift.test.ts +542 -0
- package/templates/nextjs-standalone/bin/__tests__/view-yaml-generation.test.ts +146 -0
- package/templates/nextjs-standalone/bin/atlas.ts +5044 -0
- package/templates/nextjs-standalone/bin/benchmark.ts +695 -0
- package/templates/nextjs-standalone/bin/enrich.ts +559 -0
- package/templates/nextjs-standalone/bin/eval.ts +770 -0
- package/templates/nextjs-standalone/bin/smoke.ts +438 -0
- package/templates/nextjs-standalone/data/.gitkeep +0 -0
- package/templates/nextjs-standalone/data/cybersec.sql +1961 -0
- package/templates/nextjs-standalone/data/demo-semantic/catalog.yml +40 -0
- package/templates/nextjs-standalone/data/demo-semantic/entities/accounts.yml +170 -0
- package/templates/nextjs-standalone/data/demo-semantic/entities/companies.yml +207 -0
- package/templates/nextjs-standalone/data/demo-semantic/entities/people.yml +145 -0
- package/templates/nextjs-standalone/data/demo-semantic/glossary.yml +22 -0
- package/templates/nextjs-standalone/data/demo-semantic/metrics/accounts.yml +38 -0
- package/templates/nextjs-standalone/data/demo-semantic/metrics/companies.yml +89 -0
- package/templates/nextjs-standalone/data/demo.sql +373 -0
- package/templates/nextjs-standalone/data/ecommerce.sql +1690 -0
- package/templates/nextjs-standalone/data/init-demo-db.sql +8 -0
- package/templates/nextjs-standalone/docs/deploy.md +390 -0
- package/templates/nextjs-standalone/eslint.config.mjs +18 -0
- package/templates/nextjs-standalone/gitignore +5 -0
- package/templates/nextjs-standalone/next.config.ts +10 -0
- package/templates/nextjs-standalone/package.json +63 -0
- package/templates/nextjs-standalone/postcss.config.mjs +8 -0
- package/templates/nextjs-standalone/semantic/catalog.yml +5 -0
- package/templates/nextjs-standalone/semantic/entities/.gitkeep +0 -0
- package/templates/nextjs-standalone/semantic/glossary.yml +6 -0
- package/templates/nextjs-standalone/semantic/metrics/.gitkeep +0 -0
- package/templates/nextjs-standalone/src/api/__tests__/actions.test.ts +683 -0
- package/templates/nextjs-standalone/src/api/__tests__/admin.test.ts +820 -0
- package/templates/nextjs-standalone/src/api/__tests__/auth.test.ts +165 -0
- package/templates/nextjs-standalone/src/api/__tests__/chat.test.ts +376 -0
- package/templates/nextjs-standalone/src/api/__tests__/conversations.test.ts +555 -0
- package/templates/nextjs-standalone/src/api/__tests__/cors.test.ts +135 -0
- package/templates/nextjs-standalone/src/api/__tests__/health-plugin.test.ts +169 -0
- package/templates/nextjs-standalone/src/api/__tests__/health.test.ts +261 -0
- package/templates/nextjs-standalone/src/api/__tests__/query.test.ts +891 -0
- package/templates/nextjs-standalone/src/api/__tests__/scheduled-tasks.test.ts +601 -0
- package/templates/nextjs-standalone/src/api/__tests__/slack.test.ts +847 -0
- package/templates/nextjs-standalone/src/api/index.ts +117 -0
- package/templates/nextjs-standalone/src/api/routes/actions.ts +274 -0
- package/templates/nextjs-standalone/src/api/routes/admin.ts +757 -0
- package/templates/nextjs-standalone/src/api/routes/auth.ts +48 -0
- package/templates/nextjs-standalone/src/api/routes/chat.ts +465 -0
- package/templates/nextjs-standalone/src/api/routes/conversations.ts +266 -0
- package/templates/nextjs-standalone/src/api/routes/health.ts +287 -0
- package/templates/nextjs-standalone/src/api/routes/openapi.ts +390 -0
- package/templates/nextjs-standalone/src/api/routes/query.ts +318 -0
- package/templates/nextjs-standalone/src/api/routes/scheduled-tasks.ts +467 -0
- package/templates/nextjs-standalone/src/api/routes/slack.ts +611 -0
- package/templates/nextjs-standalone/src/api/server.ts +226 -0
- package/templates/nextjs-standalone/src/app/api/[...route]/route.ts +33 -0
- package/templates/nextjs-standalone/src/app/error.tsx +24 -0
- package/templates/nextjs-standalone/src/app/global-error.tsx +68 -0
- package/templates/nextjs-standalone/src/app/globals.css +126 -0
- package/templates/nextjs-standalone/src/app/layout.tsx +19 -0
- package/templates/nextjs-standalone/src/app/page.tsx +14 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-cache.test.ts +437 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-dialect.test.ts +114 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-health-annotations.test.ts +164 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-integration.test.ts +514 -0
- package/templates/nextjs-standalone/src/lib/__tests__/config-actions.test.ts +166 -0
- package/templates/nextjs-standalone/src/lib/__tests__/config.test.ts +1063 -0
- package/templates/nextjs-standalone/src/lib/__tests__/conversations.test.ts +589 -0
- package/templates/nextjs-standalone/src/lib/__tests__/errors.test.ts +256 -0
- package/templates/nextjs-standalone/src/lib/__tests__/logger.test.ts +200 -0
- package/templates/nextjs-standalone/src/lib/__tests__/providers.test.ts +99 -0
- package/templates/nextjs-standalone/src/lib/__tests__/rls.test.ts +435 -0
- package/templates/nextjs-standalone/src/lib/__tests__/scheduled-task-types.test.ts +124 -0
- package/templates/nextjs-standalone/src/lib/__tests__/scheduled-tasks.test.ts +550 -0
- package/templates/nextjs-standalone/src/lib/__tests__/semantic-index.test.ts +547 -0
- package/templates/nextjs-standalone/src/lib/__tests__/semantic-multisource.test.ts +544 -0
- package/templates/nextjs-standalone/src/lib/__tests__/semantic.test.ts +363 -0
- package/templates/nextjs-standalone/src/lib/__tests__/startup-actions.test.ts +452 -0
- package/templates/nextjs-standalone/src/lib/__tests__/startup.test.ts +465 -0
- package/templates/nextjs-standalone/src/lib/__tests__/tracing.test.ts +28 -0
- package/templates/nextjs-standalone/src/lib/action-types.ts +95 -0
- package/templates/nextjs-standalone/src/lib/agent-query.ts +178 -0
- package/templates/nextjs-standalone/src/lib/agent.ts +505 -0
- package/templates/nextjs-standalone/src/lib/api-url.ts +3 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/audit.test.ts +418 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/byot-integration.test.ts +222 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/byot.test.ts +366 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/detect.test.ts +190 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/managed.test.ts +173 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/middleware.test.ts +456 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/migrate.test.ts +201 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/permissions.test.ts +225 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/server.test.ts +34 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/simple-key.test.ts +176 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/types.test.ts +44 -0
- package/templates/nextjs-standalone/src/lib/auth/audit.ts +89 -0
- package/templates/nextjs-standalone/src/lib/auth/byot.ts +158 -0
- package/templates/nextjs-standalone/src/lib/auth/client.ts +23 -0
- package/templates/nextjs-standalone/src/lib/auth/detect.ts +83 -0
- package/templates/nextjs-standalone/src/lib/auth/managed.ts +73 -0
- package/templates/nextjs-standalone/src/lib/auth/middleware.ts +208 -0
- package/templates/nextjs-standalone/src/lib/auth/migrate.ts +111 -0
- package/templates/nextjs-standalone/src/lib/auth/permissions.ts +156 -0
- package/templates/nextjs-standalone/src/lib/auth/server.ts +142 -0
- package/templates/nextjs-standalone/src/lib/auth/simple-key.ts +92 -0
- package/templates/nextjs-standalone/src/lib/auth/types.ts +49 -0
- package/templates/nextjs-standalone/src/lib/config.ts +704 -0
- package/templates/nextjs-standalone/src/lib/conversation-types.ts +29 -0
- package/templates/nextjs-standalone/src/lib/conversations.ts +270 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/connection.test.ts +69 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/duckdb.test.ts +141 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/internal.test.ts +387 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/registry-health.test.ts +207 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/registry-pool-limits.test.ts +156 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/registry.test.ts +595 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/salesforce.test.ts +339 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/snowflake.test.ts +217 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/source-rate-limit.test.ts +130 -0
- package/templates/nextjs-standalone/src/lib/db/connection.ts +753 -0
- package/templates/nextjs-standalone/src/lib/db/duckdb.ts +122 -0
- package/templates/nextjs-standalone/src/lib/db/internal.ts +273 -0
- package/templates/nextjs-standalone/src/lib/db/salesforce.ts +342 -0
- package/templates/nextjs-standalone/src/lib/db/source-rate-limit.ts +191 -0
- package/templates/nextjs-standalone/src/lib/errors.ts +154 -0
- package/templates/nextjs-standalone/src/lib/logger.ts +98 -0
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/hooks-integration.test.ts +202 -0
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/hooks.test.ts +529 -0
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/migrate.test.ts +521 -0
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/registry.test.ts +346 -0
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/tools.test.ts +49 -0
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/wiring.test.ts +585 -0
- package/templates/nextjs-standalone/src/lib/plugins/hooks.ts +162 -0
- package/templates/nextjs-standalone/src/lib/plugins/index.ts +9 -0
- package/templates/nextjs-standalone/src/lib/plugins/migrate.ts +309 -0
- package/templates/nextjs-standalone/src/lib/plugins/registry.ts +231 -0
- package/templates/nextjs-standalone/src/lib/plugins/tools.ts +39 -0
- package/templates/nextjs-standalone/src/lib/plugins/wiring.ts +291 -0
- package/templates/nextjs-standalone/src/lib/providers.ts +102 -0
- package/templates/nextjs-standalone/src/lib/rls.ts +321 -0
- package/templates/nextjs-standalone/src/lib/scheduled-task-types.ts +132 -0
- package/templates/nextjs-standalone/src/lib/scheduled-tasks.ts +475 -0
- package/templates/nextjs-standalone/src/lib/scheduler/__tests__/delivery.test.ts +192 -0
- package/templates/nextjs-standalone/src/lib/scheduler/__tests__/engine.test.ts +248 -0
- package/templates/nextjs-standalone/src/lib/scheduler/__tests__/format-email.test.ts +96 -0
- package/templates/nextjs-standalone/src/lib/scheduler/__tests__/format-slack.test.ts +78 -0
- package/templates/nextjs-standalone/src/lib/scheduler/__tests__/format-webhook.test.ts +78 -0
- package/templates/nextjs-standalone/src/lib/scheduler/delivery.ts +248 -0
- package/templates/nextjs-standalone/src/lib/scheduler/engine.ts +317 -0
- package/templates/nextjs-standalone/src/lib/scheduler/executor.ts +73 -0
- package/templates/nextjs-standalone/src/lib/scheduler/format-email.ts +109 -0
- package/templates/nextjs-standalone/src/lib/scheduler/format-slack.ts +35 -0
- package/templates/nextjs-standalone/src/lib/scheduler/format-webhook.ts +37 -0
- package/templates/nextjs-standalone/src/lib/scheduler/index.ts +7 -0
- package/templates/nextjs-standalone/src/lib/security.ts +11 -0
- package/templates/nextjs-standalone/src/lib/semantic-index.ts +503 -0
- package/templates/nextjs-standalone/src/lib/semantic.ts +387 -0
- package/templates/nextjs-standalone/src/lib/sidecar-types.ts +16 -0
- package/templates/nextjs-standalone/src/lib/slack/__tests__/api.test.ts +160 -0
- package/templates/nextjs-standalone/src/lib/slack/__tests__/format.test.ts +237 -0
- package/templates/nextjs-standalone/src/lib/slack/__tests__/store.test.ts +188 -0
- package/templates/nextjs-standalone/src/lib/slack/__tests__/threads.test.ts +112 -0
- package/templates/nextjs-standalone/src/lib/slack/__tests__/verify.test.ts +111 -0
- package/templates/nextjs-standalone/src/lib/slack/api.ts +102 -0
- package/templates/nextjs-standalone/src/lib/slack/format.ts +209 -0
- package/templates/nextjs-standalone/src/lib/slack/store.ts +107 -0
- package/templates/nextjs-standalone/src/lib/slack/threads.ts +64 -0
- package/templates/nextjs-standalone/src/lib/slack/verify.ts +71 -0
- package/templates/nextjs-standalone/src/lib/startup.ts +730 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/action-permissions.test.ts +594 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/custom-validation.test.ts +238 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-backend.test.ts +267 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-nsjail.test.ts +492 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-plugin.test.ts +374 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-sdk-compat.test.ts +82 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-sidecar.test.ts +208 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/registry-actions.test.ts +144 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/registry.test.ts +235 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/salesforce-tool.test.ts +154 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/soql-validation.test.ts +303 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-audit.test.ts +225 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-connection-whitelist.test.ts +98 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-duckdb.test.ts +233 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-ratelimit.test.ts +225 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql.test.ts +1012 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/__tests__/audit.test.ts +211 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/__tests__/email.test.ts +378 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/__tests__/handler.test.ts +681 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/__tests__/jira.test.ts +427 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/audit.ts +47 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/email.ts +191 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/handler.ts +591 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/index.ts +23 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/jira.ts +220 -0
- package/templates/nextjs-standalone/src/lib/tools/explore-nsjail.ts +343 -0
- package/templates/nextjs-standalone/src/lib/tools/explore-sandbox.ts +264 -0
- package/templates/nextjs-standalone/src/lib/tools/explore-sidecar.ts +163 -0
- package/templates/nextjs-standalone/src/lib/tools/explore.ts +379 -0
- package/templates/nextjs-standalone/src/lib/tools/registry.ts +221 -0
- package/templates/nextjs-standalone/src/lib/tools/salesforce.ts +138 -0
- package/templates/nextjs-standalone/src/lib/tools/soql-validation.ts +172 -0
- package/templates/nextjs-standalone/src/lib/tools/sql.ts +680 -0
- package/templates/nextjs-standalone/src/lib/tracing.ts +40 -0
- package/templates/nextjs-standalone/src/lib/utils.ts +6 -0
- package/templates/nextjs-standalone/src/test-setup.ts +38 -0
- package/templates/nextjs-standalone/src/ui/components/actions/action-approval-card.tsx +295 -0
- package/templates/nextjs-standalone/src/ui/components/actions/action-status-badge.tsx +50 -0
- package/templates/nextjs-standalone/src/ui/components/admin/admin-layout.tsx +26 -0
- package/templates/nextjs-standalone/src/ui/components/admin/admin-sidebar.tsx +96 -0
- package/templates/nextjs-standalone/src/ui/components/admin/empty-state.tsx +24 -0
- package/templates/nextjs-standalone/src/ui/components/admin/entity-detail.tsx +233 -0
- package/templates/nextjs-standalone/src/ui/components/admin/entity-list.tsx +96 -0
- package/templates/nextjs-standalone/src/ui/components/admin/error-banner.tsx +22 -0
- package/templates/nextjs-standalone/src/ui/components/admin/feature-disabled.tsx +44 -0
- package/templates/nextjs-standalone/src/ui/components/admin/health-badge.tsx +30 -0
- package/templates/nextjs-standalone/src/ui/components/admin/loading-state.tsx +14 -0
- package/templates/nextjs-standalone/src/ui/components/admin/stat-card.tsx +32 -0
- package/templates/nextjs-standalone/src/ui/components/atlas-chat.tsx +370 -0
- package/templates/nextjs-standalone/src/ui/components/chart/chart-detection.ts +261 -0
- package/templates/nextjs-standalone/src/ui/components/chart/result-chart.tsx +375 -0
- package/templates/nextjs-standalone/src/ui/components/chat/api-key-bar.tsx +66 -0
- package/templates/nextjs-standalone/src/ui/components/chat/copy-button.tsx +25 -0
- package/templates/nextjs-standalone/src/ui/components/chat/data-table.tsx +102 -0
- package/templates/nextjs-standalone/src/ui/components/chat/error-banner.tsx +32 -0
- package/templates/nextjs-standalone/src/ui/components/chat/explore-card.tsx +41 -0
- package/templates/nextjs-standalone/src/ui/components/chat/loading-card.tsx +10 -0
- package/templates/nextjs-standalone/src/ui/components/chat/managed-auth-card.tsx +116 -0
- package/templates/nextjs-standalone/src/ui/components/chat/markdown.tsx +72 -0
- package/templates/nextjs-standalone/src/ui/components/chat/sql-block.tsx +30 -0
- package/templates/nextjs-standalone/src/ui/components/chat/sql-result-card.tsx +144 -0
- package/templates/nextjs-standalone/src/ui/components/chat/starter-prompts.ts +6 -0
- package/templates/nextjs-standalone/src/ui/components/chat/tool-part.tsx +40 -0
- package/templates/nextjs-standalone/src/ui/components/chat/typing-indicator.tsx +19 -0
- package/templates/nextjs-standalone/src/ui/components/conversations/conversation-item.tsx +120 -0
- package/templates/nextjs-standalone/src/ui/components/conversations/conversation-list.tsx +66 -0
- package/templates/nextjs-standalone/src/ui/components/conversations/conversation-sidebar.tsx +78 -0
- package/templates/nextjs-standalone/src/ui/components/conversations/delete-confirmation.tsx +27 -0
- package/templates/nextjs-standalone/src/ui/context.tsx +78 -0
- package/templates/nextjs-standalone/src/ui/hooks/use-admin-fetch.ts +104 -0
- package/templates/nextjs-standalone/src/ui/hooks/use-conversations.ts +184 -0
- package/templates/nextjs-standalone/src/ui/hooks/use-dark-mode.ts +17 -0
- package/templates/nextjs-standalone/src/ui/lib/action-types.ts +63 -0
- package/templates/nextjs-standalone/src/ui/lib/helpers.ts +104 -0
- package/templates/nextjs-standalone/src/ui/lib/types.ts +145 -0
- package/templates/nextjs-standalone/tsconfig.json +32 -0
- package/templates/nextjs-standalone/vercel.json +4 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Salesforce DataSource adapter via jsforce.
|
|
3
|
+
*
|
|
4
|
+
* Salesforce uses SOQL, not SQL, so it cannot be a DBConnection (which has
|
|
5
|
+
* `query(sql)`). Instead, it exposes a separate DataSource interface and
|
|
6
|
+
* a separate registry.
|
|
7
|
+
*
|
|
8
|
+
* Connection URL format: salesforce://user:pass@login.salesforce.com?token=TOKEN
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { QueryResult, ConnectionMetadata } from "@atlas/api/lib/db/connection";
|
|
12
|
+
import { createLogger } from "@atlas/api/lib/logger";
|
|
13
|
+
|
|
14
|
+
const log = createLogger("salesforce");
|
|
15
|
+
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Types
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
export interface SObjectInfo {
|
|
21
|
+
name: string;
|
|
22
|
+
label: string;
|
|
23
|
+
queryable: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface SObjectField {
|
|
27
|
+
name: string;
|
|
28
|
+
type: string;
|
|
29
|
+
label: string;
|
|
30
|
+
picklistValues: { value: string; label: string; active: boolean }[];
|
|
31
|
+
referenceTo: string[];
|
|
32
|
+
nillable: boolean;
|
|
33
|
+
length: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface SObjectDescribe {
|
|
37
|
+
name: string;
|
|
38
|
+
label: string;
|
|
39
|
+
fields: SObjectField[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface SalesforceConfig {
|
|
43
|
+
loginUrl: string;
|
|
44
|
+
username: string;
|
|
45
|
+
password: string;
|
|
46
|
+
securityToken?: string;
|
|
47
|
+
clientId?: string;
|
|
48
|
+
clientSecret?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface SalesforceDataSource {
|
|
52
|
+
query(soql: string, timeoutMs?: number): Promise<QueryResult>;
|
|
53
|
+
describe(objectName: string): Promise<SObjectDescribe>;
|
|
54
|
+
listObjects(): Promise<SObjectInfo[]>;
|
|
55
|
+
close(): Promise<void>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
// URL parser
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Parse a Salesforce connection URL into SalesforceConfig.
|
|
64
|
+
*
|
|
65
|
+
* Format: salesforce://user:pass@login.salesforce.com?token=TOKEN
|
|
66
|
+
*
|
|
67
|
+
* - hostname → loginUrl (default `login.salesforce.com`)
|
|
68
|
+
* - URL username/password → credentials
|
|
69
|
+
* - Query params: `token` (security token), `clientId`, `clientSecret`
|
|
70
|
+
*/
|
|
71
|
+
export function parseSalesforceURL(url: string): SalesforceConfig {
|
|
72
|
+
const parsed = new URL(url);
|
|
73
|
+
if (parsed.protocol !== "salesforce:") {
|
|
74
|
+
throw new Error(
|
|
75
|
+
`Invalid Salesforce URL: expected salesforce:// scheme, got "${parsed.protocol}"`,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const username = decodeURIComponent(parsed.username);
|
|
80
|
+
if (!username) {
|
|
81
|
+
throw new Error("Invalid Salesforce URL: missing username.");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const password = decodeURIComponent(parsed.password);
|
|
85
|
+
if (!password) {
|
|
86
|
+
throw new Error("Invalid Salesforce URL: missing password.");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const hostname = parsed.hostname || "login.salesforce.com";
|
|
90
|
+
const loginUrl = `https://${hostname}`;
|
|
91
|
+
|
|
92
|
+
const securityToken = parsed.searchParams.get("token") ?? undefined;
|
|
93
|
+
const clientId = parsed.searchParams.get("clientId") ?? undefined;
|
|
94
|
+
const clientSecret = parsed.searchParams.get("clientSecret") ?? undefined;
|
|
95
|
+
|
|
96
|
+
return { loginUrl, username, password, securityToken, clientId, clientSecret };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
// DataSource factory
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Create a Salesforce DataSource backed by jsforce.
|
|
105
|
+
*/
|
|
106
|
+
export function createSalesforceDataSource(
|
|
107
|
+
config: SalesforceConfig,
|
|
108
|
+
): SalesforceDataSource {
|
|
109
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
110
|
+
let jsforce: any;
|
|
111
|
+
try {
|
|
112
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
113
|
+
jsforce = require("jsforce");
|
|
114
|
+
} catch {
|
|
115
|
+
throw new Error(
|
|
116
|
+
"Salesforce support requires the jsforce package. Install it with: bun add jsforce",
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const connOpts: Record<string, unknown> = {
|
|
121
|
+
loginUrl: config.loginUrl,
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
if (config.clientId && config.clientSecret) {
|
|
125
|
+
connOpts.oauth2 = {
|
|
126
|
+
clientId: config.clientId,
|
|
127
|
+
clientSecret: config.clientSecret,
|
|
128
|
+
loginUrl: config.loginUrl,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
133
|
+
const conn = new jsforce.Connection(connOpts) as any;
|
|
134
|
+
|
|
135
|
+
let loginPromise: Promise<void> | null = null;
|
|
136
|
+
|
|
137
|
+
async function ensureLoggedIn(): Promise<void> {
|
|
138
|
+
if (loginPromise) return loginPromise;
|
|
139
|
+
loginPromise = (async () => {
|
|
140
|
+
const loginPassword = config.securityToken
|
|
141
|
+
? config.password + config.securityToken
|
|
142
|
+
: config.password;
|
|
143
|
+
try {
|
|
144
|
+
await conn.login(config.username, loginPassword);
|
|
145
|
+
} catch (err) {
|
|
146
|
+
loginPromise = null;
|
|
147
|
+
log.error(
|
|
148
|
+
{
|
|
149
|
+
err: err instanceof Error ? err : new Error(String(err)),
|
|
150
|
+
loginUrl: config.loginUrl,
|
|
151
|
+
username: config.username,
|
|
152
|
+
},
|
|
153
|
+
"Salesforce login failed",
|
|
154
|
+
);
|
|
155
|
+
throw err;
|
|
156
|
+
}
|
|
157
|
+
log.info("Salesforce login successful");
|
|
158
|
+
})();
|
|
159
|
+
return loginPromise;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function isSessionExpiredError(err: unknown): boolean {
|
|
163
|
+
if (!(err instanceof Error)) return false;
|
|
164
|
+
const msg = err.message;
|
|
165
|
+
return (
|
|
166
|
+
msg.includes("INVALID_SESSION_ID") ||
|
|
167
|
+
msg.includes("Session expired") ||
|
|
168
|
+
msg.includes("session has expired")
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function withSessionRetry<T>(fn: () => Promise<T>): Promise<T> {
|
|
173
|
+
try {
|
|
174
|
+
return await fn();
|
|
175
|
+
} catch (err) {
|
|
176
|
+
if (isSessionExpiredError(err)) {
|
|
177
|
+
log.warn("Salesforce session expired — re-authenticating and retrying");
|
|
178
|
+
loginPromise = null;
|
|
179
|
+
await ensureLoggedIn();
|
|
180
|
+
return await fn();
|
|
181
|
+
}
|
|
182
|
+
throw err;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
async query(soql: string, timeoutMs = 30000): Promise<QueryResult> {
|
|
188
|
+
return withSessionRetry(async () => {
|
|
189
|
+
await ensureLoggedIn();
|
|
190
|
+
|
|
191
|
+
let timeoutId: ReturnType<typeof setTimeout>;
|
|
192
|
+
const result = await Promise.race([
|
|
193
|
+
conn.query(soql),
|
|
194
|
+
new Promise<never>((_, reject) => {
|
|
195
|
+
timeoutId = setTimeout(
|
|
196
|
+
() => reject(new Error("Salesforce query timed out")),
|
|
197
|
+
timeoutMs,
|
|
198
|
+
);
|
|
199
|
+
}),
|
|
200
|
+
]).finally(() => clearTimeout(timeoutId!));
|
|
201
|
+
|
|
202
|
+
const records = (result.records ?? []) as Record<string, unknown>[];
|
|
203
|
+
|
|
204
|
+
if (records.length === 0) {
|
|
205
|
+
return { columns: [], rows: [] };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Extract columns from first record, filtering out the `attributes` metadata key
|
|
209
|
+
const columns = Object.keys(records[0]).filter(
|
|
210
|
+
(k) => k !== "attributes",
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
const rows = records.map((record) => {
|
|
214
|
+
const row: Record<string, unknown> = {};
|
|
215
|
+
for (const col of columns) {
|
|
216
|
+
row[col] = record[col];
|
|
217
|
+
}
|
|
218
|
+
return row;
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
return { columns, rows };
|
|
222
|
+
});
|
|
223
|
+
},
|
|
224
|
+
|
|
225
|
+
async describe(objectName: string): Promise<SObjectDescribe> {
|
|
226
|
+
return withSessionRetry(async () => {
|
|
227
|
+
await ensureLoggedIn();
|
|
228
|
+
const desc = await conn.describe(objectName);
|
|
229
|
+
return {
|
|
230
|
+
name: desc.name,
|
|
231
|
+
label: desc.label,
|
|
232
|
+
fields: (desc.fields ?? []).map(
|
|
233
|
+
(f: Record<string, unknown>) => ({
|
|
234
|
+
name: f.name as string,
|
|
235
|
+
type: f.type as string,
|
|
236
|
+
label: f.label as string,
|
|
237
|
+
picklistValues: Array.isArray(f.picklistValues)
|
|
238
|
+
? f.picklistValues.map(
|
|
239
|
+
(pv: Record<string, unknown>) => ({
|
|
240
|
+
value: pv.value as string,
|
|
241
|
+
label: pv.label as string,
|
|
242
|
+
active: pv.active as boolean,
|
|
243
|
+
}),
|
|
244
|
+
)
|
|
245
|
+
: [],
|
|
246
|
+
referenceTo: Array.isArray(f.referenceTo)
|
|
247
|
+
? (f.referenceTo as string[])
|
|
248
|
+
: [],
|
|
249
|
+
nillable: f.nillable as boolean,
|
|
250
|
+
length: (f.length as number) ?? 0,
|
|
251
|
+
}),
|
|
252
|
+
),
|
|
253
|
+
};
|
|
254
|
+
});
|
|
255
|
+
},
|
|
256
|
+
|
|
257
|
+
async listObjects(): Promise<SObjectInfo[]> {
|
|
258
|
+
return withSessionRetry(async () => {
|
|
259
|
+
await ensureLoggedIn();
|
|
260
|
+
const result = await conn.describeGlobal();
|
|
261
|
+
return (result.sobjects ?? [])
|
|
262
|
+
.filter((obj: Record<string, unknown>) => obj.queryable === true)
|
|
263
|
+
.map((obj: Record<string, unknown>) => ({
|
|
264
|
+
name: obj.name as string,
|
|
265
|
+
label: obj.label as string,
|
|
266
|
+
queryable: true,
|
|
267
|
+
}));
|
|
268
|
+
});
|
|
269
|
+
},
|
|
270
|
+
|
|
271
|
+
async close(): Promise<void> {
|
|
272
|
+
if (loginPromise) {
|
|
273
|
+
try {
|
|
274
|
+
await loginPromise;
|
|
275
|
+
await conn.logout();
|
|
276
|
+
} catch (err) {
|
|
277
|
+
log.warn(
|
|
278
|
+
{ err: err instanceof Error ? err.message : String(err) },
|
|
279
|
+
"Failed to logout from Salesforce",
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
loginPromise = null;
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// ---------------------------------------------------------------------------
|
|
289
|
+
// Source registry (parallel to ConnectionRegistry)
|
|
290
|
+
// ---------------------------------------------------------------------------
|
|
291
|
+
|
|
292
|
+
const _sources = new Map<string, SalesforceDataSource>();
|
|
293
|
+
|
|
294
|
+
export function registerSalesforceSource(
|
|
295
|
+
id: string,
|
|
296
|
+
config: SalesforceConfig,
|
|
297
|
+
): void {
|
|
298
|
+
const existing = _sources.get(id);
|
|
299
|
+
const newSource = createSalesforceDataSource(config);
|
|
300
|
+
_sources.set(id, newSource);
|
|
301
|
+
if (existing) {
|
|
302
|
+
existing.close().catch((err) => {
|
|
303
|
+
log.warn(
|
|
304
|
+
{ err: err instanceof Error ? err.message : String(err), sourceId: id },
|
|
305
|
+
"Failed to close previous Salesforce source during re-registration",
|
|
306
|
+
);
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export function getSalesforceSource(id: string): SalesforceDataSource {
|
|
312
|
+
const source = _sources.get(id);
|
|
313
|
+
if (!source) {
|
|
314
|
+
throw new Error(`Salesforce source "${id}" is not registered.`);
|
|
315
|
+
}
|
|
316
|
+
return source;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export function listSalesforceSources(): string[] {
|
|
320
|
+
return Array.from(_sources.keys());
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/** Return ConnectionMetadata for each registered Salesforce source. */
|
|
324
|
+
export function describeSalesforceSources(): ConnectionMetadata[] {
|
|
325
|
+
return Array.from(_sources.keys()).map((id) => ({
|
|
326
|
+
id,
|
|
327
|
+
dbType: "salesforce" as const,
|
|
328
|
+
}));
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/** Test helper — clears all registered sources. */
|
|
332
|
+
export function _resetSalesforceSources(): void {
|
|
333
|
+
for (const [id, source] of _sources.entries()) {
|
|
334
|
+
source.close().catch((err) => {
|
|
335
|
+
log.warn(
|
|
336
|
+
{ err: err instanceof Error ? err.message : String(err), sourceId: id },
|
|
337
|
+
"Failed to close Salesforce source during registry reset",
|
|
338
|
+
);
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
_sources.clear();
|
|
342
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-source rate limiting for multi-database deployments.
|
|
3
|
+
*
|
|
4
|
+
* Enforces QPM (queries per minute) and concurrency limits per datasource.
|
|
5
|
+
* Uses a sliding-window pattern for QPM tracking.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createLogger } from "@atlas/api/lib/logger";
|
|
9
|
+
|
|
10
|
+
const log = createLogger("source-rate-limit");
|
|
11
|
+
|
|
12
|
+
const WINDOW_MS = 60_000;
|
|
13
|
+
|
|
14
|
+
export interface SourceRateLimit {
|
|
15
|
+
readonly queriesPerMinute: number;
|
|
16
|
+
readonly concurrency: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const DEFAULT_LIMIT: SourceRateLimit = {
|
|
20
|
+
queriesPerMinute: 60,
|
|
21
|
+
concurrency: 5,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/** Per-source config overrides. */
|
|
25
|
+
const limits = new Map<string, SourceRateLimit>();
|
|
26
|
+
|
|
27
|
+
/** Per-source sliding window of request timestamps. */
|
|
28
|
+
const windows = new Map<string, number[]>();
|
|
29
|
+
|
|
30
|
+
/** Per-source current concurrency count. */
|
|
31
|
+
const concurrency = new Map<string, number>();
|
|
32
|
+
|
|
33
|
+
/** Register a custom rate limit for a datasource. */
|
|
34
|
+
export function registerSourceRateLimit(id: string, limit: SourceRateLimit): void {
|
|
35
|
+
if (!Number.isInteger(limit.queriesPerMinute) || limit.queriesPerMinute < 1) {
|
|
36
|
+
throw new Error(`queriesPerMinute must be a positive integer, got ${limit.queriesPerMinute}`);
|
|
37
|
+
}
|
|
38
|
+
if (!Number.isInteger(limit.concurrency) || limit.concurrency < 1) {
|
|
39
|
+
throw new Error(`concurrency must be a positive integer, got ${limit.concurrency}`);
|
|
40
|
+
}
|
|
41
|
+
limits.set(id, limit);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Remove a custom rate limit for a datasource. */
|
|
45
|
+
export function clearSourceRateLimit(id: string): void {
|
|
46
|
+
limits.delete(id);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function getLimit(id: string): SourceRateLimit {
|
|
50
|
+
return limits.get(id) ?? DEFAULT_LIMIT;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function checkSourceRateLimit(sourceId: string): {
|
|
54
|
+
allowed: boolean;
|
|
55
|
+
reason?: string;
|
|
56
|
+
retryAfterMs?: number;
|
|
57
|
+
} {
|
|
58
|
+
const limit = getLimit(sourceId);
|
|
59
|
+
|
|
60
|
+
// Check concurrency first
|
|
61
|
+
const current = concurrency.get(sourceId) ?? 0;
|
|
62
|
+
if (current >= limit.concurrency) {
|
|
63
|
+
return {
|
|
64
|
+
allowed: false,
|
|
65
|
+
reason: `Source "${sourceId}" concurrency limit reached (${limit.concurrency})`,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Check QPM
|
|
70
|
+
const now = Date.now();
|
|
71
|
+
const cutoff = now - WINDOW_MS;
|
|
72
|
+
|
|
73
|
+
let timestamps = windows.get(sourceId);
|
|
74
|
+
if (!timestamps) {
|
|
75
|
+
timestamps = [];
|
|
76
|
+
windows.set(sourceId, timestamps);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Evict stale entries
|
|
80
|
+
const firstValid = timestamps.findIndex((t) => t > cutoff);
|
|
81
|
+
if (firstValid > 0) timestamps.splice(0, firstValid);
|
|
82
|
+
else if (firstValid === -1) timestamps.length = 0;
|
|
83
|
+
|
|
84
|
+
if (timestamps.length >= limit.queriesPerMinute) {
|
|
85
|
+
const retryAfterMs = Math.max(1, timestamps[0] + WINDOW_MS - now);
|
|
86
|
+
return {
|
|
87
|
+
allowed: false,
|
|
88
|
+
reason: `Source "${sourceId}" QPM limit reached (${limit.queriesPerMinute}/min)`,
|
|
89
|
+
retryAfterMs,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
timestamps.push(now);
|
|
94
|
+
return { allowed: true };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Atomic check-and-acquire: checks both QPM and concurrency limits, and if
|
|
99
|
+
* allowed, atomically records the QPM timestamp AND increments the concurrency
|
|
100
|
+
* counter. This prevents the TOCTOU race where two concurrent requests both
|
|
101
|
+
* pass the concurrency check before either increments.
|
|
102
|
+
*
|
|
103
|
+
* Callers MUST call `decrementSourceConcurrency()` in a `finally` block after
|
|
104
|
+
* the query completes (success or failure).
|
|
105
|
+
*/
|
|
106
|
+
export function acquireSourceSlot(sourceId: string): {
|
|
107
|
+
acquired: boolean;
|
|
108
|
+
reason?: string;
|
|
109
|
+
retryAfterMs?: number;
|
|
110
|
+
} {
|
|
111
|
+
const limit = getLimit(sourceId);
|
|
112
|
+
|
|
113
|
+
// Check concurrency first
|
|
114
|
+
const current = concurrency.get(sourceId) ?? 0;
|
|
115
|
+
if (current >= limit.concurrency) {
|
|
116
|
+
return {
|
|
117
|
+
acquired: false,
|
|
118
|
+
reason: `Source "${sourceId}" concurrency limit reached (${limit.concurrency})`,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Check QPM
|
|
123
|
+
const now = Date.now();
|
|
124
|
+
const cutoff = now - WINDOW_MS;
|
|
125
|
+
|
|
126
|
+
let timestamps = windows.get(sourceId);
|
|
127
|
+
if (!timestamps) {
|
|
128
|
+
timestamps = [];
|
|
129
|
+
windows.set(sourceId, timestamps);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Evict stale entries
|
|
133
|
+
const firstValid = timestamps.findIndex((t) => t > cutoff);
|
|
134
|
+
if (firstValid > 0) timestamps.splice(0, firstValid);
|
|
135
|
+
else if (firstValid === -1) timestamps.length = 0;
|
|
136
|
+
|
|
137
|
+
if (timestamps.length >= limit.queriesPerMinute) {
|
|
138
|
+
const retryAfterMs = Math.max(1, timestamps[0] + WINDOW_MS - now);
|
|
139
|
+
return {
|
|
140
|
+
acquired: false,
|
|
141
|
+
reason: `Source "${sourceId}" QPM limit reached (${limit.queriesPerMinute}/min)`,
|
|
142
|
+
retryAfterMs,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Atomically record QPM timestamp AND increment concurrency
|
|
147
|
+
timestamps.push(now);
|
|
148
|
+
concurrency.set(sourceId, current + 1);
|
|
149
|
+
return { acquired: true };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Increment concurrency counter before query execution. */
|
|
153
|
+
export function incrementSourceConcurrency(sourceId: string): void {
|
|
154
|
+
concurrency.set(sourceId, (concurrency.get(sourceId) ?? 0) + 1);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Decrement concurrency counter after query execution. */
|
|
158
|
+
export function decrementSourceConcurrency(sourceId: string): void {
|
|
159
|
+
const current = concurrency.get(sourceId) ?? 0;
|
|
160
|
+
concurrency.set(sourceId, Math.max(0, current - 1));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Periodic cleanup — evict stale QPM windows. */
|
|
164
|
+
const cleanupInterval = setInterval(() => {
|
|
165
|
+
try {
|
|
166
|
+
const cutoff = Date.now() - WINDOW_MS;
|
|
167
|
+
for (const [key, timestamps] of windows) {
|
|
168
|
+
if (timestamps.length === 0 || timestamps[timestamps.length - 1] <= cutoff) {
|
|
169
|
+
windows.delete(key);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
} catch (err) {
|
|
173
|
+
log.error(
|
|
174
|
+
{ err: err instanceof Error ? err : new Error(String(err)) },
|
|
175
|
+
"Source rate limit cleanup failed",
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
}, WINDOW_MS);
|
|
179
|
+
cleanupInterval.unref();
|
|
180
|
+
|
|
181
|
+
/** Stop the periodic cleanup timer. For tests. */
|
|
182
|
+
export function _stopSourceRateLimitCleanup(): void {
|
|
183
|
+
clearInterval(cleanupInterval);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Reset all state. For tests. */
|
|
187
|
+
export function _resetSourceRateLimits(): void {
|
|
188
|
+
limits.clear();
|
|
189
|
+
windows.clear();
|
|
190
|
+
concurrency.clear();
|
|
191
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side error parsing for Atlas chat errors.
|
|
3
|
+
*
|
|
4
|
+
* The server returns JSON error bodies with { error, message, retryAfterSeconds? }.
|
|
5
|
+
* This module parses those into user-friendly `ChatErrorInfo` objects.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { AuthMode } from "@atlas/api/lib/auth/types";
|
|
9
|
+
|
|
10
|
+
/* ------------------------------------------------------------------ */
|
|
11
|
+
/* ChatErrorCode — all server error codes */
|
|
12
|
+
/* ------------------------------------------------------------------ */
|
|
13
|
+
|
|
14
|
+
export const CHAT_ERROR_CODES = [
|
|
15
|
+
"auth_error",
|
|
16
|
+
"rate_limited",
|
|
17
|
+
"configuration_error",
|
|
18
|
+
"no_datasource",
|
|
19
|
+
"invalid_request",
|
|
20
|
+
"provider_model_not_found",
|
|
21
|
+
"provider_auth_error",
|
|
22
|
+
"provider_rate_limit",
|
|
23
|
+
"provider_timeout",
|
|
24
|
+
"provider_unreachable",
|
|
25
|
+
"provider_error",
|
|
26
|
+
"internal_error",
|
|
27
|
+
] as const;
|
|
28
|
+
|
|
29
|
+
/** Union of all error codes the server can return in the `error` field. */
|
|
30
|
+
export type ChatErrorCode = (typeof CHAT_ERROR_CODES)[number];
|
|
31
|
+
|
|
32
|
+
/* ------------------------------------------------------------------ */
|
|
33
|
+
/* ChatErrorInfo */
|
|
34
|
+
/* ------------------------------------------------------------------ */
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Structured error info extracted from a chat error response.
|
|
38
|
+
*
|
|
39
|
+
* - `title` — Primary user-facing message (always present).
|
|
40
|
+
* - `detail` — Optional secondary message with extra context.
|
|
41
|
+
* - `retryAfterSeconds` — Seconds to wait before retrying (rate_limited only).
|
|
42
|
+
* Clamped to [0, 300].
|
|
43
|
+
* - `code` — The server error code, if the response was valid JSON with a known code.
|
|
44
|
+
*/
|
|
45
|
+
export interface ChatErrorInfo {
|
|
46
|
+
title: string;
|
|
47
|
+
detail?: string;
|
|
48
|
+
retryAfterSeconds?: number;
|
|
49
|
+
code?: ChatErrorCode;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* ------------------------------------------------------------------ */
|
|
53
|
+
/* authErrorMessage */
|
|
54
|
+
/* ------------------------------------------------------------------ */
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Map an auth mode to a user-friendly error message.
|
|
58
|
+
*
|
|
59
|
+
* Different auth modes require different guidance:
|
|
60
|
+
* - `simple-key`: the user needs to check or re-enter their API key.
|
|
61
|
+
* - `managed`: the session likely expired; a fresh sign-in is needed.
|
|
62
|
+
* - `byot`: the external token may have expired or been revoked.
|
|
63
|
+
* - `none`: auth should not fail in this mode; a generic message is shown.
|
|
64
|
+
*/
|
|
65
|
+
export function authErrorMessage(authMode: AuthMode): string {
|
|
66
|
+
switch (authMode) {
|
|
67
|
+
case "simple-key":
|
|
68
|
+
return "Invalid or missing API key. Check your key and try again.";
|
|
69
|
+
case "managed":
|
|
70
|
+
return "Your session has expired. Please sign in again.";
|
|
71
|
+
case "byot":
|
|
72
|
+
return "Authentication failed. Your token may have expired.";
|
|
73
|
+
case "none":
|
|
74
|
+
return "An unexpected authentication error occurred. Please refresh the page.";
|
|
75
|
+
default: {
|
|
76
|
+
const _exhaustive: never = authMode;
|
|
77
|
+
return `Authentication failed (unknown mode: ${_exhaustive}).`;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* ------------------------------------------------------------------ */
|
|
83
|
+
/* parseChatError */
|
|
84
|
+
/* ------------------------------------------------------------------ */
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Parse an AI SDK chat error into a user-friendly `ChatErrorInfo`.
|
|
88
|
+
*
|
|
89
|
+
* Expects `error.message` to contain a JSON string with `{ error, message, retryAfterSeconds? }`.
|
|
90
|
+
* Falls back to a generic message when the body is not valid JSON (e.g. network failures,
|
|
91
|
+
* HTML error pages, or unexpected formats).
|
|
92
|
+
*/
|
|
93
|
+
export function parseChatError(error: Error, authMode: AuthMode): ChatErrorInfo {
|
|
94
|
+
let parsed: Record<string, unknown>;
|
|
95
|
+
try {
|
|
96
|
+
parsed = JSON.parse(error.message);
|
|
97
|
+
} catch {
|
|
98
|
+
return { title: "Something went wrong. Please try again." };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const code = typeof parsed.error === "string" ? parsed.error : undefined;
|
|
102
|
+
const serverMessage = typeof parsed.message === "string" ? parsed.message : undefined;
|
|
103
|
+
|
|
104
|
+
switch (code) {
|
|
105
|
+
case "auth_error":
|
|
106
|
+
return { title: authErrorMessage(authMode), code };
|
|
107
|
+
|
|
108
|
+
case "rate_limited": {
|
|
109
|
+
const raw = typeof parsed.retryAfterSeconds === "number" ? parsed.retryAfterSeconds : undefined;
|
|
110
|
+
const clamped = raw !== undefined ? Math.max(0, Math.min(raw, 300)) : undefined;
|
|
111
|
+
return {
|
|
112
|
+
title: "Too many requests.",
|
|
113
|
+
detail: clamped !== undefined
|
|
114
|
+
? `Try again in ${clamped} seconds.`
|
|
115
|
+
: "Please wait before trying again.",
|
|
116
|
+
retryAfterSeconds: clamped,
|
|
117
|
+
code,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
case "configuration_error":
|
|
122
|
+
return { title: "Atlas is not fully configured.", detail: serverMessage, code };
|
|
123
|
+
|
|
124
|
+
case "no_datasource":
|
|
125
|
+
return { title: "No data source configured.", detail: serverMessage, code };
|
|
126
|
+
|
|
127
|
+
case "invalid_request":
|
|
128
|
+
return { title: "Invalid request.", detail: serverMessage, code };
|
|
129
|
+
|
|
130
|
+
case "provider_model_not_found":
|
|
131
|
+
return { title: "The configured AI model was not found.", detail: serverMessage, code };
|
|
132
|
+
|
|
133
|
+
case "provider_auth_error":
|
|
134
|
+
return { title: "The AI provider could not authenticate.", detail: serverMessage, code };
|
|
135
|
+
|
|
136
|
+
case "provider_rate_limit":
|
|
137
|
+
return { title: "The AI provider is rate limiting requests.", detail: serverMessage, code };
|
|
138
|
+
|
|
139
|
+
case "provider_timeout":
|
|
140
|
+
return { title: "The AI provider timed out.", detail: serverMessage, code };
|
|
141
|
+
|
|
142
|
+
case "provider_unreachable":
|
|
143
|
+
return { title: "Could not reach the AI provider.", detail: serverMessage, code };
|
|
144
|
+
|
|
145
|
+
case "provider_error":
|
|
146
|
+
return { title: "The AI provider returned an error.", detail: serverMessage, code };
|
|
147
|
+
|
|
148
|
+
case "internal_error":
|
|
149
|
+
return { title: serverMessage ?? "An unexpected error occurred.", code };
|
|
150
|
+
|
|
151
|
+
default:
|
|
152
|
+
return { title: serverMessage ?? "Something went wrong. Please try again." };
|
|
153
|
+
}
|
|
154
|
+
}
|