@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,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared agent query execution logic.
|
|
3
|
+
*
|
|
4
|
+
* Used by both the synchronous JSON endpoint (POST /api/v1/query) and the
|
|
5
|
+
* Slack bot routes to run the Atlas agent to completion and extract
|
|
6
|
+
* structured results from the tool calls.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { runAgent } from "@atlas/api/lib/agent";
|
|
10
|
+
import { createLogger, withRequestContext } from "@atlas/api/lib/logger";
|
|
11
|
+
|
|
12
|
+
const log = createLogger("agent-query");
|
|
13
|
+
|
|
14
|
+
export interface PendingAction {
|
|
15
|
+
id: string;
|
|
16
|
+
type: string;
|
|
17
|
+
target: string;
|
|
18
|
+
summary: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface AgentQueryResult {
|
|
22
|
+
answer: string;
|
|
23
|
+
sql: string[];
|
|
24
|
+
data: { columns: string[]; rows: Record<string, unknown>[] }[];
|
|
25
|
+
steps: number;
|
|
26
|
+
usage: { totalTokens: number };
|
|
27
|
+
pendingActions?: PendingAction[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Run the Atlas agent on a single question and return structured results.
|
|
32
|
+
*
|
|
33
|
+
* Creates a UIMessage from the question, optionally loads Salesforce tools,
|
|
34
|
+
* invokes the agent loop, and extracts SQL queries, data, and the final
|
|
35
|
+
* answer from tool results.
|
|
36
|
+
*/
|
|
37
|
+
export async function executeAgentQuery(
|
|
38
|
+
question: string,
|
|
39
|
+
requestId?: string,
|
|
40
|
+
options?: { priorMessages?: Array<{ role: "user" | "assistant"; content: string }> },
|
|
41
|
+
): Promise<AgentQueryResult> {
|
|
42
|
+
const id = requestId ?? crypto.randomUUID();
|
|
43
|
+
|
|
44
|
+
return withRequestContext({ requestId: id }, async () => {
|
|
45
|
+
const priorUIMessages = (options?.priorMessages ?? []).map((m, i) => ({
|
|
46
|
+
id: `${id}-prior-${i}`,
|
|
47
|
+
role: m.role as "user" | "assistant",
|
|
48
|
+
parts: [{ type: "text" as const, text: m.content }],
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
const messages = [
|
|
52
|
+
...priorUIMessages,
|
|
53
|
+
{
|
|
54
|
+
id,
|
|
55
|
+
role: "user" as const,
|
|
56
|
+
parts: [{ type: "text" as const, text: question }],
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
// Optionally include Salesforce tools and actions
|
|
61
|
+
let toolRegistry;
|
|
62
|
+
const includeActions = process.env.ATLAS_ACTIONS_ENABLED === "true";
|
|
63
|
+
let includeSalesforce = false;
|
|
64
|
+
try {
|
|
65
|
+
const { listSalesforceSources } = await import(
|
|
66
|
+
"@atlas/api/lib/db/salesforce"
|
|
67
|
+
);
|
|
68
|
+
includeSalesforce = listSalesforceSources().length > 0;
|
|
69
|
+
} catch (err: unknown) {
|
|
70
|
+
const code = (err as { code?: string })?.code;
|
|
71
|
+
const isModuleNotFound =
|
|
72
|
+
code === "MODULE_NOT_FOUND" || code === "ERR_MODULE_NOT_FOUND";
|
|
73
|
+
if (!isModuleNotFound) {
|
|
74
|
+
log.error(
|
|
75
|
+
{ err: err instanceof Error ? err : new Error(String(err)) },
|
|
76
|
+
"Failed to initialize Salesforce tool registry — falling back to default tools",
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (includeSalesforce || includeActions) {
|
|
81
|
+
try {
|
|
82
|
+
const { buildRegistry } = await import(
|
|
83
|
+
"@atlas/api/lib/tools/registry"
|
|
84
|
+
);
|
|
85
|
+
toolRegistry = await buildRegistry({ includeSalesforce, includeActions });
|
|
86
|
+
} catch (err) {
|
|
87
|
+
log.error(
|
|
88
|
+
{ err: err instanceof Error ? err : new Error(String(err)) },
|
|
89
|
+
"Failed to build tool registry — falling back to default tools",
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const result = await runAgent({
|
|
95
|
+
messages,
|
|
96
|
+
...(toolRegistry && { tools: toolRegistry }),
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const [text, steps, totalUsage] = await Promise.all([
|
|
100
|
+
result.text,
|
|
101
|
+
result.steps,
|
|
102
|
+
result.totalUsage,
|
|
103
|
+
]);
|
|
104
|
+
|
|
105
|
+
// Collect SQL queries and their data from tool results
|
|
106
|
+
const sqlQueries: string[] = [];
|
|
107
|
+
const dataResults: { columns: string[]; rows: Record<string, unknown>[] }[] = [];
|
|
108
|
+
const pendingActions: PendingAction[] = [];
|
|
109
|
+
const answer = text;
|
|
110
|
+
|
|
111
|
+
for (const step of steps) {
|
|
112
|
+
// No tool results in text-only steps
|
|
113
|
+
if (!step.toolResults) continue;
|
|
114
|
+
for (const tr of step.toolResults) {
|
|
115
|
+
if (tr.toolName === "executeSQL" && tr.output) {
|
|
116
|
+
const r = tr.output as {
|
|
117
|
+
success?: boolean;
|
|
118
|
+
columns?: string[];
|
|
119
|
+
rows?: Record<string, unknown>[];
|
|
120
|
+
};
|
|
121
|
+
const inp = tr.input as { sql?: string };
|
|
122
|
+
if (inp.sql) {
|
|
123
|
+
sqlQueries.push(inp.sql);
|
|
124
|
+
}
|
|
125
|
+
if (r.success && r.columns && r.rows) {
|
|
126
|
+
dataResults.push({ columns: r.columns, rows: r.rows });
|
|
127
|
+
} else if (r.success) {
|
|
128
|
+
log.warn(
|
|
129
|
+
{ requestId: id, toolName: "executeSQL", hasColumns: !!r.columns, hasRows: !!r.rows },
|
|
130
|
+
"executeSQL returned success but missing columns or rows",
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// Detect pending action approvals from any action tool
|
|
135
|
+
if (tr.output && typeof tr.output === "object") {
|
|
136
|
+
const out = tr.output as Record<string, unknown>;
|
|
137
|
+
if (out.status === "pending_approval") {
|
|
138
|
+
if (typeof out.actionId !== "string" || !out.actionId) {
|
|
139
|
+
log.warn(
|
|
140
|
+
{ toolName: tr.toolName, outputKeys: Object.keys(out) },
|
|
141
|
+
"Tool returned pending_approval but missing or invalid actionId — skipping",
|
|
142
|
+
);
|
|
143
|
+
} else {
|
|
144
|
+
const actionType = typeof (tr.input as Record<string, unknown>)?.actionType === "string"
|
|
145
|
+
? (tr.input as Record<string, unknown>).actionType as string
|
|
146
|
+
: tr.toolName;
|
|
147
|
+
pendingActions.push({
|
|
148
|
+
id: out.actionId,
|
|
149
|
+
type: actionType,
|
|
150
|
+
target: typeof out.target === "string" ? out.target : "",
|
|
151
|
+
summary: typeof out.summary === "string" ? out.summary : "",
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (!answer && dataResults.length > 0) {
|
|
160
|
+
log.warn(
|
|
161
|
+
{ requestId: id, steps: steps.length, sqlCount: sqlQueries.length },
|
|
162
|
+
"Agent produced data but no text answer — model may have hit step limit before responding",
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
answer,
|
|
168
|
+
sql: sqlQueries,
|
|
169
|
+
data: dataResults,
|
|
170
|
+
steps: steps.length,
|
|
171
|
+
usage: {
|
|
172
|
+
totalTokens:
|
|
173
|
+
(totalUsage?.inputTokens ?? 0) + (totalUsage?.outputTokens ?? 0),
|
|
174
|
+
},
|
|
175
|
+
...(pendingActions.length > 0 && { pendingActions }),
|
|
176
|
+
};
|
|
177
|
+
});
|
|
178
|
+
}
|
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Atlas agent.
|
|
3
|
+
*
|
|
4
|
+
* Runs a single-agent loop driven by a ToolRegistry (default: explore,
|
|
5
|
+
* executeSQL). The loop runs until the step limit (25) is reached or
|
|
6
|
+
* the model stops issuing tool calls.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
convertToModelMessages,
|
|
11
|
+
stepCountIs,
|
|
12
|
+
streamText,
|
|
13
|
+
type ModelMessage,
|
|
14
|
+
type SystemModelMessage,
|
|
15
|
+
type UIMessage,
|
|
16
|
+
} from "ai";
|
|
17
|
+
import { getModel, getProviderType, type ProviderType } from "./providers";
|
|
18
|
+
import { defaultRegistry, type ToolRegistry } from "./tools/registry";
|
|
19
|
+
import { getContextFragments, getDialectHints } from "./plugins/tools";
|
|
20
|
+
import { connections, detectDBType, type ConnectionMetadata, type DBType } from "./db/connection";
|
|
21
|
+
import { getCrossSourceJoins, type CrossSourceJoin } from "./semantic";
|
|
22
|
+
import { getSemanticIndex } from "./semantic-index";
|
|
23
|
+
import { createLogger } from "./logger";
|
|
24
|
+
import { trace, SpanStatusCode } from "@opentelemetry/api";
|
|
25
|
+
|
|
26
|
+
const log = createLogger("agent");
|
|
27
|
+
const tracer = trace.getTracer("atlas");
|
|
28
|
+
|
|
29
|
+
const SYSTEM_PROMPT_PREFIX = `You are Atlas, an expert data analyst AI. You answer questions about data by exploring a semantic layer, writing SQL, and interpreting results.
|
|
30
|
+
|
|
31
|
+
## Your Workflow
|
|
32
|
+
|
|
33
|
+
Follow these steps for every question:
|
|
34
|
+
|
|
35
|
+
### 1. Understand the Question
|
|
36
|
+
Parse what the user is really asking. Check the Ambiguous Terms section below if the question uses terms that could have multiple meanings.`;
|
|
37
|
+
|
|
38
|
+
const SYSTEM_PROMPT_SUFFIX = `## Rules
|
|
39
|
+
- Use the Semantic Layer Reference below to identify tables and columns — write SQL directly when the reference has enough detail
|
|
40
|
+
- Use the explore tool only when you need information not in the reference (e.g., sample values, complex join SQL, query pattern SQL)
|
|
41
|
+
- NEVER guess table or column names — verify them against the reference or via explore
|
|
42
|
+
- NEVER modify data — only SELECT queries are allowed
|
|
43
|
+
- If you cannot answer a question with the available data, say so clearly
|
|
44
|
+
- Be concise but thorough in your interpretations
|
|
45
|
+
|
|
46
|
+
## Follow-up Questions
|
|
47
|
+
When the user asks a follow-up question:
|
|
48
|
+
- Reference previous query results — don't re-explore the semantic layer if you already know the schema
|
|
49
|
+
- However, if the follow-up involves a different table or entity than the previous query, check the reference or re-explore the relevant entity schema
|
|
50
|
+
- Build on prior SQL — reuse CTEs, table aliases, and filters from earlier queries when relevant
|
|
51
|
+
- If the user says "break that down by X" or "now filter to Y", modify the previous query rather than starting from scratch
|
|
52
|
+
- Refer back to specific numbers from your previous analysis when interpreting new results
|
|
53
|
+
|
|
54
|
+
## Ambiguous Terms
|
|
55
|
+
Before writing SQL, check if the user's question contains terms from the glossary that need clarification:
|
|
56
|
+
- If a term has status "ambiguous", ASK the user to clarify which meaning they intend before proceeding
|
|
57
|
+
- If a term has a "disambiguation" field (even if status is "defined"), follow its guidance — it may tell you to ask a clarifying question
|
|
58
|
+
- Example: if the glossary lists multiple possible_mappings for a term like "size", ask which meaning the user intends
|
|
59
|
+
- Only ask ONE clarifying question at a time — don't barrage the user
|
|
60
|
+
- If the glossary provides a default interpretation, mention it: "By 'revenue' I'll use companies.revenue (annual company revenue). Would you prefer subscription MRR from accounts.monthly_value?"
|
|
61
|
+
|
|
62
|
+
## Error Recovery
|
|
63
|
+
When a SQL query fails, read the error carefully before retrying:
|
|
64
|
+
- **Column not found** — The error often suggests the correct name (e.g., "column 'revnue' does not exist — did you mean 'revenue'?"). Go back to the entity schema to verify the exact column name.
|
|
65
|
+
- **Table not found** — Re-read catalog.yml to find the correct table name. The table may use a different name than you expected.
|
|
66
|
+
- **Syntax error** — Check the error position hint. Common issues: missing commas, unmatched parentheses, incorrect JOIN syntax.
|
|
67
|
+
- **Type mismatch** — You may need to CAST a column (e.g., CAST(value AS numeric)). Check the column type in the entity schema.
|
|
68
|
+
- **Timeout** — Simplify the query: remove unnecessary JOINs, add WHERE filters to reduce the dataset, or break into smaller queries.
|
|
69
|
+
- Never retry the exact same SQL. Always fix the identified issue first.
|
|
70
|
+
- Max 2 retries per question — if the query still fails, explain the issue to the user.`;
|
|
71
|
+
|
|
72
|
+
const MYSQL_DIALECT_GUIDE = `
|
|
73
|
+
|
|
74
|
+
## SQL Dialect: MySQL
|
|
75
|
+
This database uses MySQL. Key differences from PostgreSQL:
|
|
76
|
+
- Use \`YEAR(col)\` and \`MONTH(col)\` (preferred) or \`EXTRACT(YEAR FROM col)\` — both work
|
|
77
|
+
- Use \`DATE_FORMAT(col, '%Y-%m')\` instead of \`TO_CHAR(col, 'YYYY-MM')\`
|
|
78
|
+
- Use \`IFNULL(col, default)\` or \`COALESCE(col, default)\` — both work
|
|
79
|
+
- Use backtick quoting for identifiers: \`\\\`column\\\`\` instead of \`"column"\`
|
|
80
|
+
- Use \`CONCAT(a, b)\` for string concatenation — \`||\` is logical OR in MySQL
|
|
81
|
+
- No \`ILIKE\` — use \`WHERE col COLLATE utf8mb4_bin LIKE 'pattern'\` for case-sensitive matching
|
|
82
|
+
- \`GROUP_CONCAT(col SEPARATOR ', ')\` instead of \`STRING_AGG(col, ', ')\`
|
|
83
|
+
- No \`::type\` casting — use \`CAST(x AS SIGNED)\`, \`CAST(x AS DECIMAL)\`
|
|
84
|
+
- \`LIMIT offset, count\` or \`LIMIT count OFFSET offset\` — both forms work
|
|
85
|
+
- \`COALESCE\`, \`CASE\`, \`NULLIF\`, \`COUNT\`, \`SUM\`, \`AVG\`, \`MIN\`, \`MAX\` work identically`;
|
|
86
|
+
|
|
87
|
+
const CLICKHOUSE_DIALECT_GUIDE = `
|
|
88
|
+
|
|
89
|
+
## SQL Dialect: ClickHouse
|
|
90
|
+
This database uses ClickHouse. Key differences from PostgreSQL:
|
|
91
|
+
- Use \`toYear(col)\` and \`toMonth(col)\` instead of \`EXTRACT(YEAR FROM col)\`
|
|
92
|
+
- Use \`formatDateTime(col, '%Y-%m')\` instead of \`TO_CHAR(col, 'YYYY-MM')\`
|
|
93
|
+
- Use \`toString(col)\` for explicit string casts — no \`::text\` casting
|
|
94
|
+
- Use \`toInt32(col)\`, \`toFloat64(col)\` for numeric casts — no \`::integer\` casting
|
|
95
|
+
- Use \`ifNull(col, default)\` or \`COALESCE(col, default)\` — both work
|
|
96
|
+
- \`arrayJoin(arr)\` unnests array columns into rows
|
|
97
|
+
- No \`UPDATE\` or \`DELETE\` — ClickHouse is append-only OLAP
|
|
98
|
+
- No foreign keys — joins are supported but there are no referential integrity constraints
|
|
99
|
+
- \`count()\` instead of \`COUNT(*)\` — both work but \`count()\` is idiomatic
|
|
100
|
+
- Use single quotes for strings, double quotes or backticks for identifiers
|
|
101
|
+
- \`argMax(col, ordering_col)\` / \`argMin\` for "latest value" queries
|
|
102
|
+
- Date arithmetic: \`today()\`, \`yesterday()\`, \`now()\`, \`toStartOfMonth(col)\`, \`toStartOfWeek(col)\`
|
|
103
|
+
- \`COALESCE\`, \`CASE\`, \`COUNT\`, \`SUM\`, \`AVG\`, \`MIN\`, \`MAX\` work identically
|
|
104
|
+
- Note: Atlas SQL validation uses PostgreSQL-compatible parsing. Avoid ClickHouse-only syntax (PREWHERE, LIMIT BY, WITH TOTALS) — use standard SQL equivalents instead
|
|
105
|
+
- \`EXPLAIN\`, \`SHOW\`, and \`DESCRIBE\` are not available — use the explore tool to read entity schema files instead`;
|
|
106
|
+
|
|
107
|
+
const SOQL_DIALECT_GUIDE = `
|
|
108
|
+
|
|
109
|
+
## Query Language: Salesforce SOQL
|
|
110
|
+
This datasource uses Salesforce Object Query Language (SOQL). Key differences from SQL:
|
|
111
|
+
- **No JOINs** — use relationship queries instead:
|
|
112
|
+
- Child-to-parent: \`SELECT Account.Name FROM Contact\`
|
|
113
|
+
- Parent-to-child: \`SELECT Name, (SELECT LastName FROM Contacts) FROM Account\`
|
|
114
|
+
- **Object names** instead of table names (e.g. \`Account\`, \`Contact\`, \`Opportunity\`)
|
|
115
|
+
- **Field API names** — always use the API name (e.g. \`FirstName\`), not the label
|
|
116
|
+
- **No wildcards** — \`SELECT *\` is not supported; list fields explicitly
|
|
117
|
+
- **Date literals**: \`TODAY\`, \`YESTERDAY\`, \`LAST_WEEK\`, \`THIS_MONTH\`, \`LAST_N_DAYS:30\`, \`NEXT_N_DAYS:7\`
|
|
118
|
+
- **Date functions**: \`CALENDAR_YEAR(CloseDate)\`, \`CALENDAR_MONTH(CloseDate)\`, \`DAY_IN_MONTH(CloseDate)\`
|
|
119
|
+
- **Aggregate functions**: \`COUNT()\`, \`COUNT(Id)\`, \`SUM(Amount)\`, \`AVG(Amount)\`, \`MIN(Amount)\`, \`MAX(Amount)\`
|
|
120
|
+
- **GROUP BY** works similarly to SQL: \`SELECT StageName, COUNT(Id) FROM Opportunity GROUP BY StageName\`
|
|
121
|
+
- **HAVING** clause is supported for filtering aggregates
|
|
122
|
+
- **LIMIT** and \`OFFSET\` are supported
|
|
123
|
+
- **No subqueries in FROM** — subqueries only in WHERE (semi-joins): \`WHERE AccountId IN (SELECT Id FROM Account WHERE ...)\`
|
|
124
|
+
- **No UNION, INTERSECT, EXCEPT**
|
|
125
|
+
- Use the \`querySalesforce\` tool (not \`executeSQL\`) for all Salesforce queries`;
|
|
126
|
+
|
|
127
|
+
const SNOWFLAKE_DIALECT_GUIDE = `
|
|
128
|
+
|
|
129
|
+
## SQL Dialect: Snowflake
|
|
130
|
+
This database uses Snowflake. Key differences from PostgreSQL:
|
|
131
|
+
- \`ILIKE\` works for case-insensitive matching (same as PostgreSQL)
|
|
132
|
+
- \`::type\` casting works (e.g. \`col::VARCHAR\`), plus \`TRY_CAST(col AS type)\` for safe casting that returns NULL on failure
|
|
133
|
+
- Identifiers are case-insensitive by default; double-quoted identifiers are case-sensitive
|
|
134
|
+
- Use \`FLATTEN()\` / \`LATERAL FLATTEN(input => col)\` to unnest semi-structured (VARIANT/ARRAY/OBJECT) data
|
|
135
|
+
- \`QUALIFY\` clause filters window function results directly (e.g. \`QUALIFY ROW_NUMBER() OVER (...) = 1\`)
|
|
136
|
+
- \`DATE_TRUNC('month', col)\` syntax (same as PostgreSQL)
|
|
137
|
+
- No \`LIMIT offset, count\` — use \`LIMIT count OFFSET offset\`
|
|
138
|
+
- Use \`LISTAGG(col, ', ')\` instead of \`STRING_AGG(col, ', ')\` or \`ARRAY_AGG(col)\` for aggregation
|
|
139
|
+
- \`COALESCE\`, \`CASE\`, \`NULLIF\`, \`COUNT\`, \`SUM\`, \`AVG\`, \`MIN\`, \`MAX\` work identically`;
|
|
140
|
+
|
|
141
|
+
const DUCKDB_DIALECT_GUIDE = `
|
|
142
|
+
|
|
143
|
+
## SQL Dialect: DuckDB
|
|
144
|
+
This database uses DuckDB, an in-process analytical engine. DuckDB's SQL is PostgreSQL-compatible with extensions:
|
|
145
|
+
- \`::type\` casting works (e.g. \`col::INTEGER\`, \`col::VARCHAR\`) — same as PostgreSQL
|
|
146
|
+
- \`ILIKE\` works for case-insensitive matching (same as PostgreSQL)
|
|
147
|
+
- \`EXTRACT(YEAR FROM col)\`, \`DATE_TRUNC('month', col)\`, \`TO_CHAR(col, 'YYYY-MM')\` — same as PostgreSQL
|
|
148
|
+
- \`STRING_AGG(col, ', ')\` works (same as PostgreSQL)
|
|
149
|
+
- \`PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY col)\` works for percentile calculations
|
|
150
|
+
- \`LIST_AGG\`, \`ARRAY_AGG\`, \`LIST()\` for array aggregation
|
|
151
|
+
- \`UNNEST(list_col)\` to expand list/array columns into rows
|
|
152
|
+
- Note: Atlas SQL validation uses PostgreSQL-compatible parsing. Avoid DuckDB-only syntax (QUALIFY, EXCLUDE, REPLACE, STRUCT_PACK, COLUMNS(*), COLUMNS(regex), STRUCT literals with curly braces) — use standard SQL equivalents instead
|
|
153
|
+
- \`COALESCE\`, \`CASE\`, \`NULLIF\`, \`COUNT\`, \`SUM\`, \`AVG\`, \`MIN\`, \`MAX\` work identically`;
|
|
154
|
+
|
|
155
|
+
function dialectName(dbType: DBType): string {
|
|
156
|
+
switch (dbType) {
|
|
157
|
+
case "postgres": return "PostgreSQL";
|
|
158
|
+
case "mysql": return "MySQL";
|
|
159
|
+
case "clickhouse": return "ClickHouse";
|
|
160
|
+
case "snowflake": return "Snowflake";
|
|
161
|
+
case "duckdb": return "DuckDB";
|
|
162
|
+
case "salesforce": return "Salesforce (SOQL)";
|
|
163
|
+
default: { const _: never = dbType; return _; }
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function buildMultiSourceSection(
|
|
168
|
+
sources: ConnectionMetadata[],
|
|
169
|
+
): string {
|
|
170
|
+
const lines = sources.map((s) => {
|
|
171
|
+
const dialect = dialectName(s.dbType);
|
|
172
|
+
const desc = s.description ? ` — ${s.description}` : "";
|
|
173
|
+
const healthNote = s.health?.status === "unhealthy"
|
|
174
|
+
? " (**UNAVAILABLE** — skip queries to this source)"
|
|
175
|
+
: s.health?.status === "degraded"
|
|
176
|
+
? " (currently degraded — queries may fail)"
|
|
177
|
+
: "";
|
|
178
|
+
return `- **${s.id}** (${dialect})${desc}${healthNote}`;
|
|
179
|
+
});
|
|
180
|
+
let section = `## Available Data Sources
|
|
181
|
+
|
|
182
|
+
This environment has ${sources.length} database connections. Use the \`connectionId\` parameter in executeSQL to target the correct database.
|
|
183
|
+
|
|
184
|
+
${lines.join("\n")}
|
|
185
|
+
|
|
186
|
+
**Important:**
|
|
187
|
+
- Always specify \`connectionId\` when querying a non-default source
|
|
188
|
+
- Check entity YAML files for the \`connection\` field to see which tables belong to which source
|
|
189
|
+
- Tables are scoped to their connection — a table on "warehouse" cannot be queried via "default"
|
|
190
|
+
|
|
191
|
+
**Semantic layer navigation:**
|
|
192
|
+
- Default connection entities are in \`entities/\` at the root
|
|
193
|
+
- Other sources have their own subdirectory: \`{connectionId}/entities/\`
|
|
194
|
+
- Start by running \`ls\` to see all available source directories
|
|
195
|
+
- Each source may also have its own \`metrics/\` and \`glossary.yml\``;
|
|
196
|
+
|
|
197
|
+
// Surface cross-source relationships in the system prompt so the agent
|
|
198
|
+
// knows upfront which tables span sources and avoids impossible cross-DB JOINs.
|
|
199
|
+
let crossJoins: readonly CrossSourceJoin[];
|
|
200
|
+
try {
|
|
201
|
+
crossJoins = getCrossSourceJoins();
|
|
202
|
+
} catch (err) {
|
|
203
|
+
log.warn({ err: err instanceof Error ? err.message : String(err) }, "Failed to load cross-source joins — continuing without hints");
|
|
204
|
+
crossJoins = [];
|
|
205
|
+
}
|
|
206
|
+
if (crossJoins.length > 0) {
|
|
207
|
+
const joinLines = crossJoins.map((j) => {
|
|
208
|
+
const desc = j.description ? `${j.description} ` : "";
|
|
209
|
+
return `- **${j.fromSource}.${j.fromTable}** → **${j.toSource}.${j.toTable}**: ${desc}(${j.relationship}, on: ${j.on})`;
|
|
210
|
+
});
|
|
211
|
+
section += `\n\n## Cross-Source Relationships\n\n${joinLines.join("\n")}\n\nCross-source joins cannot be done in a single SQL query. Query each source separately and combine results in your analysis.`;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return section;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Collect Salesforce source metadata via dynamic import to avoid a hard
|
|
219
|
+
* dependency on the salesforce module (it may not be installed).
|
|
220
|
+
*/
|
|
221
|
+
function getSfSourceMeta(): ConnectionMetadata[] {
|
|
222
|
+
try {
|
|
223
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
224
|
+
const { describeSalesforceSources } = require("./db/salesforce") as {
|
|
225
|
+
describeSalesforceSources: () => ConnectionMetadata[];
|
|
226
|
+
};
|
|
227
|
+
return describeSalesforceSources();
|
|
228
|
+
} catch (err) {
|
|
229
|
+
// jsforce not installed — expected, don't log
|
|
230
|
+
if (err instanceof Error && err.message.includes("Cannot find module")) return [];
|
|
231
|
+
log.warn({ err: err instanceof Error ? err.message : String(err) }, "Failed to describe Salesforce sources");
|
|
232
|
+
return [];
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function appendDialectHints(prompt: string): string {
|
|
237
|
+
const hints = getDialectHints();
|
|
238
|
+
if (hints.length === 0) return prompt;
|
|
239
|
+
return prompt + "\n\n## Additional SQL Dialect Notes\n\n" + hints.map((h) => h.dialect).join("\n\n");
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function buildSystemPrompt(registry: ToolRegistry): string {
|
|
243
|
+
let base = SYSTEM_PROMPT_PREFIX + "\n\n" + registry.describe() + "\n\n" + SYSTEM_PROMPT_SUFFIX;
|
|
244
|
+
|
|
245
|
+
// Append the pre-indexed semantic layer summary
|
|
246
|
+
const semanticIndex = getSemanticIndex();
|
|
247
|
+
if (semanticIndex) {
|
|
248
|
+
base += "\n\n" + semanticIndex;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Append plugin context fragments (if any)
|
|
252
|
+
const fragments = getContextFragments();
|
|
253
|
+
if (fragments.length > 0) {
|
|
254
|
+
base += "\n\n" + fragments.join("\n\n");
|
|
255
|
+
}
|
|
256
|
+
const connectionMeta = connections.describe();
|
|
257
|
+
const sfMeta = getSfSourceMeta();
|
|
258
|
+
const meta = [...connectionMeta, ...sfMeta];
|
|
259
|
+
|
|
260
|
+
// Single-connection: identical to pre-v0.7 behavior
|
|
261
|
+
if (meta.length <= 1) {
|
|
262
|
+
let dbType: DBType;
|
|
263
|
+
try {
|
|
264
|
+
dbType = meta.length === 1
|
|
265
|
+
? meta[0].dbType
|
|
266
|
+
: detectDBType();
|
|
267
|
+
} catch (err) {
|
|
268
|
+
log.debug({ err: err instanceof Error ? err.message : String(err) }, "Could not detect DB type — omitting dialect guide");
|
|
269
|
+
return appendDialectHints(base);
|
|
270
|
+
}
|
|
271
|
+
switch (dbType) {
|
|
272
|
+
case "postgres": return appendDialectHints(base);
|
|
273
|
+
case "mysql": return appendDialectHints(base + MYSQL_DIALECT_GUIDE);
|
|
274
|
+
case "clickhouse": return appendDialectHints(base + CLICKHOUSE_DIALECT_GUIDE);
|
|
275
|
+
case "snowflake": return appendDialectHints(base + SNOWFLAKE_DIALECT_GUIDE);
|
|
276
|
+
case "duckdb": return appendDialectHints(base + DUCKDB_DIALECT_GUIDE);
|
|
277
|
+
case "salesforce": return appendDialectHints(base + SOQL_DIALECT_GUIDE);
|
|
278
|
+
default: { const _exhaustive: never = dbType; throw new Error(`Unknown: ${_exhaustive}`); }
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Multi-connection: list sources + include all relevant dialect guides
|
|
283
|
+
let prompt = base + "\n\n" + buildMultiSourceSection(meta);
|
|
284
|
+
|
|
285
|
+
const dbTypes = new Set(meta.map((m) => m.dbType));
|
|
286
|
+
if (dbTypes.has("mysql")) prompt += MYSQL_DIALECT_GUIDE;
|
|
287
|
+
if (dbTypes.has("clickhouse")) prompt += CLICKHOUSE_DIALECT_GUIDE;
|
|
288
|
+
if (dbTypes.has("snowflake")) prompt += SNOWFLAKE_DIALECT_GUIDE;
|
|
289
|
+
if (dbTypes.has("duckdb")) prompt += DUCKDB_DIALECT_GUIDE;
|
|
290
|
+
if (dbTypes.has("salesforce")) prompt += SOQL_DIALECT_GUIDE;
|
|
291
|
+
|
|
292
|
+
return appendDialectHints(prompt);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Build the system prompt with provider-appropriate cache control.
|
|
297
|
+
*
|
|
298
|
+
* The prompt body is composed from the registry's tool descriptions via
|
|
299
|
+
* `registry.describe()`, sandwiched between the standard prefix and suffix.
|
|
300
|
+
*
|
|
301
|
+
* - Anthropic / Bedrock-Anthropic: returns a SystemModelMessage with
|
|
302
|
+
* `providerOptions.anthropic.cacheControl` (~80% savings on steps 2+).
|
|
303
|
+
* - Bedrock (non-Anthropic): returns a SystemModelMessage with
|
|
304
|
+
* `providerOptions.bedrock.cachePoint`.
|
|
305
|
+
* - OpenAI / Ollama / Gateway: returns a plain string (OpenAI caches
|
|
306
|
+
* automatically for prompts >= 1024 tokens; others have no caching).
|
|
307
|
+
*/
|
|
308
|
+
export function buildSystemParam(
|
|
309
|
+
providerType: ProviderType,
|
|
310
|
+
registry: ToolRegistry = defaultRegistry,
|
|
311
|
+
): string | SystemModelMessage {
|
|
312
|
+
const content = buildSystemPrompt(registry);
|
|
313
|
+
|
|
314
|
+
switch (providerType) {
|
|
315
|
+
case "anthropic":
|
|
316
|
+
case "bedrock-anthropic":
|
|
317
|
+
return {
|
|
318
|
+
role: "system",
|
|
319
|
+
content,
|
|
320
|
+
providerOptions: {
|
|
321
|
+
anthropic: { cacheControl: { type: "ephemeral" } },
|
|
322
|
+
},
|
|
323
|
+
};
|
|
324
|
+
case "bedrock":
|
|
325
|
+
return {
|
|
326
|
+
role: "system",
|
|
327
|
+
content,
|
|
328
|
+
providerOptions: {
|
|
329
|
+
bedrock: { cachePoint: { type: "default" } },
|
|
330
|
+
},
|
|
331
|
+
};
|
|
332
|
+
case "openai":
|
|
333
|
+
case "ollama":
|
|
334
|
+
case "gateway":
|
|
335
|
+
return content;
|
|
336
|
+
default: {
|
|
337
|
+
const _exhaustive: never = providerType;
|
|
338
|
+
throw new Error(`Unknown provider type: ${_exhaustive}`);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Apply prompt caching to the last message in the conversation.
|
|
345
|
+
*
|
|
346
|
+
* This marks the last message with provider-specific cache control so that
|
|
347
|
+
* all preceding context (system prompt + earlier messages) can be cached
|
|
348
|
+
* by the LLM provider on subsequent steps.
|
|
349
|
+
*
|
|
350
|
+
* - Anthropic / Bedrock-Anthropic: `providerOptions.anthropic.cacheControl`
|
|
351
|
+
* - Bedrock (non-Anthropic): `providerOptions.bedrock.cachePoint`
|
|
352
|
+
* - OpenAI / Ollama / Gateway: no-op (OpenAI caches automatically)
|
|
353
|
+
*/
|
|
354
|
+
export function applyCacheControl(
|
|
355
|
+
messages: ModelMessage[],
|
|
356
|
+
providerType: ProviderType,
|
|
357
|
+
): ModelMessage[] {
|
|
358
|
+
if (messages.length === 0) return messages;
|
|
359
|
+
|
|
360
|
+
// Only Anthropic-family and Bedrock need explicit cache markers
|
|
361
|
+
const lastIndex = messages.length - 1;
|
|
362
|
+
|
|
363
|
+
switch (providerType) {
|
|
364
|
+
case "anthropic":
|
|
365
|
+
case "bedrock-anthropic": {
|
|
366
|
+
return messages.map((message, index) => {
|
|
367
|
+
if (index !== lastIndex) return message;
|
|
368
|
+
return {
|
|
369
|
+
...message,
|
|
370
|
+
providerOptions: {
|
|
371
|
+
...message.providerOptions,
|
|
372
|
+
anthropic: { cacheControl: { type: "ephemeral" as const } },
|
|
373
|
+
},
|
|
374
|
+
} as typeof message;
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
case "bedrock": {
|
|
378
|
+
return messages.map((message, index) => {
|
|
379
|
+
if (index !== lastIndex) return message;
|
|
380
|
+
return {
|
|
381
|
+
...message,
|
|
382
|
+
providerOptions: {
|
|
383
|
+
...message.providerOptions,
|
|
384
|
+
bedrock: { cachePoint: { type: "default" as const } },
|
|
385
|
+
},
|
|
386
|
+
} as typeof message;
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
case "openai":
|
|
390
|
+
case "ollama":
|
|
391
|
+
case "gateway":
|
|
392
|
+
return messages;
|
|
393
|
+
default: {
|
|
394
|
+
const _exhaustive: never = providerType;
|
|
395
|
+
throw new Error(`Unknown provider type: ${_exhaustive}`);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Run the Atlas agent loop.
|
|
402
|
+
*
|
|
403
|
+
* @param messages - The conversation history from the chat UI.
|
|
404
|
+
* @param tools - Optional custom {@link ToolRegistry}. Defaults to
|
|
405
|
+
* {@link defaultRegistry} (explore + executeSQL). The loop terminates
|
|
406
|
+
* when the step limit (25) is reached or the model stops issuing tool calls.
|
|
407
|
+
*/
|
|
408
|
+
export async function runAgent({
|
|
409
|
+
messages,
|
|
410
|
+
tools: toolRegistry = defaultRegistry,
|
|
411
|
+
}: {
|
|
412
|
+
messages: UIMessage[];
|
|
413
|
+
tools?: ToolRegistry;
|
|
414
|
+
}) {
|
|
415
|
+
const model = getModel();
|
|
416
|
+
const providerType = getProviderType();
|
|
417
|
+
|
|
418
|
+
const span = tracer.startSpan("atlas.agent", {
|
|
419
|
+
attributes: { provider: providerType, messageCount: messages.length },
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
let spanEnded = false;
|
|
423
|
+
function endSpan(code: SpanStatusCode, message?: string) {
|
|
424
|
+
if (spanEnded) return;
|
|
425
|
+
spanEnded = true;
|
|
426
|
+
span.setStatus({ code, ...(message && { message }) });
|
|
427
|
+
span.end();
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
let result;
|
|
431
|
+
try {
|
|
432
|
+
result = streamText({
|
|
433
|
+
model,
|
|
434
|
+
system: buildSystemParam(providerType, toolRegistry),
|
|
435
|
+
messages: await convertToModelMessages(messages),
|
|
436
|
+
tools: toolRegistry.getAll(),
|
|
437
|
+
temperature: 0.2,
|
|
438
|
+
maxOutputTokens: 4096,
|
|
439
|
+
stopWhen: stepCountIs(25),
|
|
440
|
+
// totalMs: 180s for self-hosted (full agent loop budget).
|
|
441
|
+
// On Vercel, maxDuration caps the serverless function at 60s.
|
|
442
|
+
timeout: { totalMs: 180_000, stepMs: 30_000, chunkMs: 5_000 },
|
|
443
|
+
|
|
444
|
+
onError: ({ error }) => {
|
|
445
|
+
log.error(
|
|
446
|
+
{ err: error instanceof Error ? error : new Error(String(error)) },
|
|
447
|
+
"stream error",
|
|
448
|
+
);
|
|
449
|
+
endSpan(
|
|
450
|
+
SpanStatusCode.ERROR,
|
|
451
|
+
error instanceof Error ? error.message : String(error),
|
|
452
|
+
);
|
|
453
|
+
},
|
|
454
|
+
|
|
455
|
+
prepareStep: ({ messages: stepMessages }) => {
|
|
456
|
+
return {
|
|
457
|
+
messages: applyCacheControl(stepMessages, providerType),
|
|
458
|
+
};
|
|
459
|
+
},
|
|
460
|
+
|
|
461
|
+
onStepFinish: ({ stepNumber, finishReason, usage }) => {
|
|
462
|
+
log.info(
|
|
463
|
+
{
|
|
464
|
+
step: stepNumber,
|
|
465
|
+
finishReason,
|
|
466
|
+
inputTokens: usage?.inputTokens,
|
|
467
|
+
outputTokens: usage?.outputTokens,
|
|
468
|
+
cacheRead: usage?.inputTokenDetails?.cacheReadTokens,
|
|
469
|
+
cacheWrite: usage?.inputTokenDetails?.cacheWriteTokens,
|
|
470
|
+
},
|
|
471
|
+
"step complete",
|
|
472
|
+
);
|
|
473
|
+
},
|
|
474
|
+
|
|
475
|
+
onFinish: ({ finishReason, totalUsage, steps }) => {
|
|
476
|
+
log.info(
|
|
477
|
+
{
|
|
478
|
+
finishReason,
|
|
479
|
+
totalSteps: steps.length,
|
|
480
|
+
totalInput: totalUsage?.inputTokens,
|
|
481
|
+
totalOutput: totalUsage?.outputTokens,
|
|
482
|
+
totalCacheRead: totalUsage?.inputTokenDetails?.cacheReadTokens,
|
|
483
|
+
totalCacheWrite: totalUsage?.inputTokenDetails?.cacheWriteTokens,
|
|
484
|
+
},
|
|
485
|
+
"agent finished",
|
|
486
|
+
);
|
|
487
|
+
span.setAttributes({
|
|
488
|
+
finishReason: finishReason ?? "",
|
|
489
|
+
totalSteps: steps.length,
|
|
490
|
+
totalInputTokens: totalUsage?.inputTokens ?? 0,
|
|
491
|
+
totalOutputTokens: totalUsage?.outputTokens ?? 0,
|
|
492
|
+
});
|
|
493
|
+
endSpan(SpanStatusCode.OK);
|
|
494
|
+
},
|
|
495
|
+
});
|
|
496
|
+
} catch (err) {
|
|
497
|
+
endSpan(
|
|
498
|
+
SpanStatusCode.ERROR,
|
|
499
|
+
err instanceof Error ? err.message : String(err),
|
|
500
|
+
);
|
|
501
|
+
throw err;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
return result;
|
|
505
|
+
}
|