@libredb/studio 0.9.7

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.
Files changed (572) hide show
  1. package/.claude/settings.local.json +127 -0
  2. package/.cursorrules +426 -0
  3. package/.devin/wiki.json +143 -0
  4. package/.dockerignore +80 -0
  5. package/.env.example +159 -0
  6. package/.github/ISSUE_TEMPLATE/bug_report.md +49 -0
  7. package/.github/ISSUE_TEMPLATE/feature_request.md +29 -0
  8. package/.github/PULL_REQUEST_TEMPLATE.md +57 -0
  9. package/.github/workflows/ci.yml +185 -0
  10. package/.github/workflows/codeql.yml +57 -0
  11. package/.github/workflows/docker-build-push.yml +118 -0
  12. package/.github/workflows/helm-release.yml +113 -0
  13. package/CLAUDE.md +265 -0
  14. package/CODE_OF_CONDUCT.md +124 -0
  15. package/CONTRIBUTING.md +154 -0
  16. package/Dockerfile +73 -0
  17. package/LICENSE +21 -0
  18. package/README.md +614 -0
  19. package/SECURITY.md +107 -0
  20. package/artifacthub-repo.yml +4 -0
  21. package/bun.lock +1714 -0
  22. package/bunfig.toml +3 -0
  23. package/charts/libredb-studio/.helmignore +11 -0
  24. package/charts/libredb-studio/Chart.lock +6 -0
  25. package/charts/libredb-studio/Chart.yaml +50 -0
  26. package/charts/libredb-studio/README.md +206 -0
  27. package/charts/libredb-studio/templates/NOTES.txt +59 -0
  28. package/charts/libredb-studio/templates/_helpers.tpl +135 -0
  29. package/charts/libredb-studio/templates/configmap.yaml +37 -0
  30. package/charts/libredb-studio/templates/deployment.yaml +184 -0
  31. package/charts/libredb-studio/templates/hpa.yaml +32 -0
  32. package/charts/libredb-studio/templates/ingress.yaml +41 -0
  33. package/charts/libredb-studio/templates/networkpolicy.yaml +50 -0
  34. package/charts/libredb-studio/templates/pdb.yaml +18 -0
  35. package/charts/libredb-studio/templates/pvc.yaml +23 -0
  36. package/charts/libredb-studio/templates/secret.yaml +30 -0
  37. package/charts/libredb-studio/templates/seed-configmap.yaml +11 -0
  38. package/charts/libredb-studio/templates/service.yaml +22 -0
  39. package/charts/libredb-studio/templates/serviceaccount.yaml +13 -0
  40. package/charts/libredb-studio/values.schema.json +246 -0
  41. package/charts/libredb-studio/values.yaml +286 -0
  42. package/components.json +22 -0
  43. package/conductor/code_styleguides/typescript.md +43 -0
  44. package/conductor/product-guidelines.md +43 -0
  45. package/conductor/product.md +3 -0
  46. package/conductor/setup_state.json +1 -0
  47. package/conductor/tech-stack.md +39 -0
  48. package/conductor/tracks/enhance_postgres_monitoring_20251227/metadata.json +8 -0
  49. package/conductor/tracks/enhance_postgres_monitoring_20251227/plan.md +44 -0
  50. package/conductor/tracks/enhance_postgres_monitoring_20251227/spec.md +31 -0
  51. package/conductor/tracks.md +8 -0
  52. package/conductor/workflow.md +333 -0
  53. package/database-compose.yml +55 -0
  54. package/docker/postgres-init/01-extensions.sql +10 -0
  55. package/docker/postgres-init/02-sample-data.sql +585 -0
  56. package/docker/postgres.yml +68 -0
  57. package/docker-compose.yml +38 -0
  58. package/docs/AI_PLAN.md +74 -0
  59. package/docs/API_DOCS.md +875 -0
  60. package/docs/ARCHITECTURE.md +218 -0
  61. package/docs/DATABASE_PROVIDERS.md +358 -0
  62. package/docs/FEATURES.md +116 -0
  63. package/docs/HELM_CHART.md +252 -0
  64. package/docs/LOGIN_PAGE.md +178 -0
  65. package/docs/MONACO_EDITOR_PERFORMANCE.md +315 -0
  66. package/docs/OIDC_ARCH.md +681 -0
  67. package/docs/OIDC_SETUP.md +322 -0
  68. package/docs/POSTGRES_METRICS.md +516 -0
  69. package/docs/QUERY_OPTIMIZATION.md +370 -0
  70. package/docs/SEED_CONNECTIONS.md +468 -0
  71. package/docs/SQL_ALIAS_COMPLETION.md +190 -0
  72. package/docs/STORAGE_ARCHITECTURE.md +565 -0
  73. package/docs/STORAGE_QUICK_SETUP.md +419 -0
  74. package/docs/TECHNICAL_PLAN.md +36 -0
  75. package/docs/THEMING.md +345 -0
  76. package/docs/adding-a-new-database-provider.md +642 -0
  77. package/docs/backlogs/000-PLATFORM_DATA_SYNC_DATABASE.md +360 -0
  78. package/docs/backlogs/001-INLINE_DATA_EDITING.md +118 -0
  79. package/docs/backlogs/002-DATA_IMPORT.md +215 -0
  80. package/docs/backlogs/003-QUERY_TIME_MACHINE.md +183 -0
  81. package/docs/backlogs/004-AI_DATA_STORYTELLER.md +292 -0
  82. package/docs/backlogs/005-QUERY_PLAYGROUND.md +352 -0
  83. package/docs/backlogs/006-DATA_MASKING.md +418 -0
  84. package/docs/enterprise-features.md +718 -0
  85. package/docs/kubernetes-helm-chart-artifacthub-plan.md +803 -0
  86. package/docs/medium-koyeb-article-en.md +215 -0
  87. package/docs/plans/test-plans.md +445 -0
  88. package/docs/releases/RELEASE.V0.3.0.md +22 -0
  89. package/docs/releases/RELEASE.V0.4.0.md +154 -0
  90. package/docs/releases/RELEASE.V0.5.0.md +252 -0
  91. package/docs/releases/RELEASE_v0.5.6.md +145 -0
  92. package/docs/releases/RELEASE_v0.6.1.md +303 -0
  93. package/docs/releases/RELEASE_v0.6.7.md +292 -0
  94. package/docs/releases/RELEASE_v0.7.0.md +332 -0
  95. package/docs/releases/RELEASE_v0.8.0.md +521 -0
  96. package/docs/sampledb/titanic.sql +1379 -0
  97. package/docs/superpowers/plans/2026-03-25-seed-connections.md +1362 -0
  98. package/docs/superpowers/specs/2026-03-25-seed-connections-design.md +590 -0
  99. package/e2e/admin-dashboard.spec.ts +64 -0
  100. package/e2e/connection-management.spec.ts +58 -0
  101. package/e2e/export.spec.ts +34 -0
  102. package/e2e/login.spec.ts +85 -0
  103. package/e2e/query-execution.spec.ts +35 -0
  104. package/e2e/tab-management.spec.ts +64 -0
  105. package/eslint.config.mjs +28 -0
  106. package/fly.toml +43 -0
  107. package/next.config.ts +32 -0
  108. package/package.json +130 -0
  109. package/playwright.config.ts +34 -0
  110. package/postcss.config.mjs +7 -0
  111. package/public/favicon-32x32.png +0 -0
  112. package/public/favicon.ico +0 -0
  113. package/public/file.svg +1 -0
  114. package/public/globe.svg +1 -0
  115. package/public/logo.svg +32 -0
  116. package/public/next.svg +1 -0
  117. package/public/screenshots/code-generator.png +0 -0
  118. package/public/screenshots/connection-modal.png +0 -0
  119. package/public/screenshots/data-profiler.png +0 -0
  120. package/public/screenshots/erd-diagram.png +0 -0
  121. package/public/screenshots/hero-editor.png +0 -0
  122. package/public/screenshots/nl2sql.png +0 -0
  123. package/public/vercel.svg +1 -0
  124. package/public/window.svg +1 -0
  125. package/render.yaml +58 -0
  126. package/scripts/merge-lcov.mjs +239 -0
  127. package/sonar-project.properties +16 -0
  128. package/src/app/admin/error.tsx +46 -0
  129. package/src/app/admin/page.tsx +10 -0
  130. package/src/app/api/admin/audit/route.ts +52 -0
  131. package/src/app/api/admin/fleet-health/route.ts +81 -0
  132. package/src/app/api/ai/autopilot/route.ts +105 -0
  133. package/src/app/api/ai/chat/route.ts +132 -0
  134. package/src/app/api/ai/describe-schema/route.ts +52 -0
  135. package/src/app/api/ai/explain/route.ts +86 -0
  136. package/src/app/api/ai/impact/route.ts +97 -0
  137. package/src/app/api/ai/index-advisor/route.ts +98 -0
  138. package/src/app/api/ai/nl2sql/route.ts +87 -0
  139. package/src/app/api/ai/query-safety/route.ts +87 -0
  140. package/src/app/api/auth/login/route.ts +62 -0
  141. package/src/app/api/auth/logout/route.ts +25 -0
  142. package/src/app/api/auth/me/route.ts +10 -0
  143. package/src/app/api/auth/oidc/callback/route.ts +82 -0
  144. package/src/app/api/auth/oidc/login/route.ts +43 -0
  145. package/src/app/api/connections/managed/route.ts +35 -0
  146. package/src/app/api/db/cancel/route.ts +42 -0
  147. package/src/app/api/db/disconnect/route.ts +28 -0
  148. package/src/app/api/db/health/route.ts +49 -0
  149. package/src/app/api/db/maintenance/route.ts +72 -0
  150. package/src/app/api/db/monitoring/route.ts +62 -0
  151. package/src/app/api/db/multi-query/route.ts +116 -0
  152. package/src/app/api/db/pool-stats/route.ts +37 -0
  153. package/src/app/api/db/profile/route.ts +144 -0
  154. package/src/app/api/db/provider-meta/route.ts +49 -0
  155. package/src/app/api/db/query/route.ts +50 -0
  156. package/src/app/api/db/schema/route.ts +47 -0
  157. package/src/app/api/db/schema-snapshot/route.ts +42 -0
  158. package/src/app/api/db/test-connection/route.ts +55 -0
  159. package/src/app/api/db/transaction/route.ts +111 -0
  160. package/src/app/api/storage/[collection]/route.ts +67 -0
  161. package/src/app/api/storage/config/route.ts +17 -0
  162. package/src/app/api/storage/migrate/route.ts +45 -0
  163. package/src/app/api/storage/route.ts +32 -0
  164. package/src/app/error.tsx +49 -0
  165. package/src/app/global-error.tsx +55 -0
  166. package/src/app/globals.css +146 -0
  167. package/src/app/icon.svg +42 -0
  168. package/src/app/layout.tsx +34 -0
  169. package/src/app/login/login-form.tsx +301 -0
  170. package/src/app/login/page.tsx +11 -0
  171. package/src/app/monitoring/page.tsx +8 -0
  172. package/src/app/not-found.tsx +29 -0
  173. package/src/app/page.tsx +5 -0
  174. package/src/components/AIAutopilotPanel.tsx +238 -0
  175. package/src/components/CodeGenerator.tsx +271 -0
  176. package/src/components/CommandPalette.tsx +227 -0
  177. package/src/components/ConnectionModal.tsx +759 -0
  178. package/src/components/CreateTableModal.tsx +281 -0
  179. package/src/components/DataCharts.tsx +962 -0
  180. package/src/components/DataImportModal.tsx +582 -0
  181. package/src/components/DataProfiler.tsx +335 -0
  182. package/src/components/DatabaseDocs.tsx +251 -0
  183. package/src/components/MaskingSettings.tsx +414 -0
  184. package/src/components/MobileNav.tsx +50 -0
  185. package/src/components/NL2SQLPanel.tsx +281 -0
  186. package/src/components/PivotTable.tsx +257 -0
  187. package/src/components/QueryEditor.tsx +760 -0
  188. package/src/components/QueryHistory.tsx +344 -0
  189. package/src/components/QuerySafetyDialog.tsx +290 -0
  190. package/src/components/ResultsGrid.tsx +644 -0
  191. package/src/components/SaveQueryModal.tsx +104 -0
  192. package/src/components/SavedQueries.tsx +128 -0
  193. package/src/components/SchemaDiagram.tsx +473 -0
  194. package/src/components/SchemaDiff.tsx +473 -0
  195. package/src/components/SnapshotTimeline.tsx +116 -0
  196. package/src/components/Studio.tsx +639 -0
  197. package/src/components/TestDataGenerator.tsx +261 -0
  198. package/src/components/VisualExplain.tsx +820 -0
  199. package/src/components/admin/AdminDashboard.tsx +163 -0
  200. package/src/components/admin/tabs/AuditTab.tsx +531 -0
  201. package/src/components/admin/tabs/MonitoringEmbed.tsx +11 -0
  202. package/src/components/admin/tabs/OperationsTab.tsx +646 -0
  203. package/src/components/admin/tabs/OverviewTab.tsx +1328 -0
  204. package/src/components/admin/tabs/SecurityTab.tsx +284 -0
  205. package/src/components/community-section.tsx +92 -0
  206. package/src/components/icons/db-icons.tsx +84 -0
  207. package/src/components/libredb-logo.tsx +61 -0
  208. package/src/components/monitoring/MonitoringDashboard.tsx +345 -0
  209. package/src/components/monitoring/tabs/MetricChart.tsx +82 -0
  210. package/src/components/monitoring/tabs/OverviewTab.tsx +263 -0
  211. package/src/components/monitoring/tabs/PerformanceTab.tsx +254 -0
  212. package/src/components/monitoring/tabs/PoolTab.tsx +174 -0
  213. package/src/components/monitoring/tabs/QueriesTab.tsx +287 -0
  214. package/src/components/monitoring/tabs/SessionsTab.tsx +316 -0
  215. package/src/components/monitoring/tabs/StorageTab.tsx +335 -0
  216. package/src/components/monitoring/tabs/TablesTab.tsx +300 -0
  217. package/src/components/results-grid/ResultCard.tsx +111 -0
  218. package/src/components/results-grid/RowDetailSheet.tsx +178 -0
  219. package/src/components/results-grid/StatsBar.tsx +201 -0
  220. package/src/components/results-grid/index.ts +1 -0
  221. package/src/components/results-grid/utils.ts +23 -0
  222. package/src/components/schema-explorer/ColumnList.tsx +53 -0
  223. package/src/components/schema-explorer/SchemaExplorer.tsx +182 -0
  224. package/src/components/schema-explorer/TableItem.tsx +210 -0
  225. package/src/components/schema-explorer/index.ts +1 -0
  226. package/src/components/sidebar/ConnectionItem.tsx +105 -0
  227. package/src/components/sidebar/ConnectionsList.tsx +62 -0
  228. package/src/components/sidebar/Sidebar.tsx +130 -0
  229. package/src/components/sidebar/index.ts +2 -0
  230. package/src/components/studio/BottomPanel.tsx +286 -0
  231. package/src/components/studio/QueryToolbar.tsx +180 -0
  232. package/src/components/studio/StudioDesktopHeader.tsx +114 -0
  233. package/src/components/studio/StudioMobileHeader.tsx +340 -0
  234. package/src/components/studio/StudioTabBar.tsx +82 -0
  235. package/src/components/studio/index.ts +5 -0
  236. package/src/components/ui/accordion.tsx +66 -0
  237. package/src/components/ui/alert-dialog.tsx +157 -0
  238. package/src/components/ui/alert.tsx +66 -0
  239. package/src/components/ui/aspect-ratio.tsx +11 -0
  240. package/src/components/ui/avatar.tsx +53 -0
  241. package/src/components/ui/badge.tsx +46 -0
  242. package/src/components/ui/breadcrumb.tsx +109 -0
  243. package/src/components/ui/button-group.tsx +83 -0
  244. package/src/components/ui/button.tsx +60 -0
  245. package/src/components/ui/calendar.tsx +216 -0
  246. package/src/components/ui/card.tsx +92 -0
  247. package/src/components/ui/carousel.tsx +241 -0
  248. package/src/components/ui/chart.tsx +357 -0
  249. package/src/components/ui/checkbox.tsx +32 -0
  250. package/src/components/ui/collapsible.tsx +33 -0
  251. package/src/components/ui/command.tsx +184 -0
  252. package/src/components/ui/context-menu.tsx +252 -0
  253. package/src/components/ui/dialog.tsx +143 -0
  254. package/src/components/ui/drawer.tsx +135 -0
  255. package/src/components/ui/dropdown-menu.tsx +257 -0
  256. package/src/components/ui/empty.tsx +104 -0
  257. package/src/components/ui/field.tsx +248 -0
  258. package/src/components/ui/form.tsx +167 -0
  259. package/src/components/ui/hover-card.tsx +44 -0
  260. package/src/components/ui/input-group.tsx +170 -0
  261. package/src/components/ui/input-otp.tsx +77 -0
  262. package/src/components/ui/input.tsx +21 -0
  263. package/src/components/ui/item.tsx +193 -0
  264. package/src/components/ui/kbd.tsx +28 -0
  265. package/src/components/ui/label.tsx +24 -0
  266. package/src/components/ui/menubar.tsx +276 -0
  267. package/src/components/ui/navigation-menu.tsx +168 -0
  268. package/src/components/ui/pagination.tsx +127 -0
  269. package/src/components/ui/popover.tsx +48 -0
  270. package/src/components/ui/progress.tsx +31 -0
  271. package/src/components/ui/radio-group.tsx +45 -0
  272. package/src/components/ui/resizable.tsx +56 -0
  273. package/src/components/ui/scroll-area.tsx +58 -0
  274. package/src/components/ui/select.tsx +187 -0
  275. package/src/components/ui/separator.tsx +28 -0
  276. package/src/components/ui/sheet.tsx +139 -0
  277. package/src/components/ui/sidebar.tsx +726 -0
  278. package/src/components/ui/skeleton.tsx +13 -0
  279. package/src/components/ui/slider.tsx +63 -0
  280. package/src/components/ui/sonner.tsx +40 -0
  281. package/src/components/ui/spinner.tsx +16 -0
  282. package/src/components/ui/switch.tsx +31 -0
  283. package/src/components/ui/table.tsx +116 -0
  284. package/src/components/ui/tabs.tsx +66 -0
  285. package/src/components/ui/textarea.tsx +18 -0
  286. package/src/components/ui/toggle-group.tsx +83 -0
  287. package/src/components/ui/toggle.tsx +47 -0
  288. package/src/components/ui/tooltip.tsx +61 -0
  289. package/src/exports/components.ts +15 -0
  290. package/src/exports/index.ts +4 -0
  291. package/src/exports/providers.ts +4 -0
  292. package/src/exports/types.ts +26 -0
  293. package/src/hooks/use-ai-chat.ts +182 -0
  294. package/src/hooks/use-all-connections.ts +66 -0
  295. package/src/hooks/use-api-call.ts +71 -0
  296. package/src/hooks/use-auth.ts +51 -0
  297. package/src/hooks/use-connection-form.ts +349 -0
  298. package/src/hooks/use-connection-manager.ts +169 -0
  299. package/src/hooks/use-connection-payload.ts +15 -0
  300. package/src/hooks/use-inline-editing.ts +109 -0
  301. package/src/hooks/use-mobile.ts +20 -0
  302. package/src/hooks/use-monitoring-data.ts +270 -0
  303. package/src/hooks/use-provider-metadata.ts +62 -0
  304. package/src/hooks/use-query-execution.ts +478 -0
  305. package/src/hooks/use-storage-sync.ts +259 -0
  306. package/src/hooks/use-tab-manager.ts +231 -0
  307. package/src/hooks/use-toast.ts +20 -0
  308. package/src/hooks/use-transaction-control.ts +64 -0
  309. package/src/lib/api/error-codes.ts +30 -0
  310. package/src/lib/api/errors.ts +236 -0
  311. package/src/lib/api/with-error-handler.ts +41 -0
  312. package/src/lib/audit.ts +105 -0
  313. package/src/lib/auth.ts +87 -0
  314. package/src/lib/connection-string-parser.ts +172 -0
  315. package/src/lib/data-masking.ts +385 -0
  316. package/src/lib/db/base-provider.ts +325 -0
  317. package/src/lib/db/errors.ts +317 -0
  318. package/src/lib/db/factory.ts +324 -0
  319. package/src/lib/db/index.ts +123 -0
  320. package/src/lib/db/providers/document/index.ts +6 -0
  321. package/src/lib/db/providers/document/mongodb.ts +992 -0
  322. package/src/lib/db/providers/keyvalue/redis.ts +554 -0
  323. package/src/lib/db/providers/sql/index.ts +11 -0
  324. package/src/lib/db/providers/sql/mssql.ts +1065 -0
  325. package/src/lib/db/providers/sql/mysql.ts +978 -0
  326. package/src/lib/db/providers/sql/oracle.ts +1044 -0
  327. package/src/lib/db/providers/sql/postgres.ts +1179 -0
  328. package/src/lib/db/providers/sql/sql-base.ts +174 -0
  329. package/src/lib/db/providers/sql/sqlite.ts +721 -0
  330. package/src/lib/db/types.ts +437 -0
  331. package/src/lib/db/utils/pool-manager.ts +287 -0
  332. package/src/lib/db/utils/query-limiter.ts +239 -0
  333. package/src/lib/db-ui-config.ts +86 -0
  334. package/src/lib/editor/mongodb-completions.ts +172 -0
  335. package/src/lib/editor/sql-completions.ts +280 -0
  336. package/src/lib/llm/base-provider.ts +117 -0
  337. package/src/lib/llm/factory.ts +102 -0
  338. package/src/lib/llm/index.ts +90 -0
  339. package/src/lib/llm/providers/custom.ts +181 -0
  340. package/src/lib/llm/providers/gemini.ts +126 -0
  341. package/src/lib/llm/providers/ollama.ts +154 -0
  342. package/src/lib/llm/providers/openai.ts +146 -0
  343. package/src/lib/llm/types.ts +173 -0
  344. package/src/lib/llm/utils/config.ts +187 -0
  345. package/src/lib/llm/utils/retry.ts +119 -0
  346. package/src/lib/llm/utils/streaming.ts +202 -0
  347. package/src/lib/logger.ts +127 -0
  348. package/src/lib/monitoring-thresholds.ts +44 -0
  349. package/src/lib/oidc.ts +262 -0
  350. package/src/lib/query-generators.ts +61 -0
  351. package/src/lib/schema-diff/diff-engine.ts +273 -0
  352. package/src/lib/schema-diff/migration-generator.ts +208 -0
  353. package/src/lib/schema-diff/types.ts +55 -0
  354. package/src/lib/seed/config-loader.ts +79 -0
  355. package/src/lib/seed/connection-filter.ts +49 -0
  356. package/src/lib/seed/credential-resolver.ts +62 -0
  357. package/src/lib/seed/index.ts +40 -0
  358. package/src/lib/seed/resolve-connection.ts +57 -0
  359. package/src/lib/seed/types.ts +69 -0
  360. package/src/lib/sql/alias-extractor.ts +267 -0
  361. package/src/lib/sql/index.ts +8 -0
  362. package/src/lib/sql/statement-splitter.ts +167 -0
  363. package/src/lib/sql/types.ts +40 -0
  364. package/src/lib/ssh/tunnel.ts +142 -0
  365. package/src/lib/storage/factory.ts +84 -0
  366. package/src/lib/storage/index.ts +14 -0
  367. package/src/lib/storage/local-storage.ts +99 -0
  368. package/src/lib/storage/providers/postgres.ts +225 -0
  369. package/src/lib/storage/providers/sqlite.ts +153 -0
  370. package/src/lib/storage/storage-facade.ts +272 -0
  371. package/src/lib/storage/types.ts +75 -0
  372. package/src/lib/time-series-buffer.ts +58 -0
  373. package/src/lib/types.ts +173 -0
  374. package/src/lib/utils.ts +6 -0
  375. package/src/proxy.ts +104 -0
  376. package/src/types/db-drivers.d.ts +23 -0
  377. package/src/types/html2canvas.d.ts +9 -0
  378. package/tests/api/admin/audit.test.ts +178 -0
  379. package/tests/api/admin/fleet-health.test.ts +183 -0
  380. package/tests/api/ai/autopilot.test.ts +174 -0
  381. package/tests/api/ai/chat.test.ts +250 -0
  382. package/tests/api/ai/describe-schema.test.ts +266 -0
  383. package/tests/api/ai/explain.test.ts +199 -0
  384. package/tests/api/ai/impact.test.ts +168 -0
  385. package/tests/api/ai/index-advisor.test.ts +171 -0
  386. package/tests/api/ai/nl2sql.test.ts +202 -0
  387. package/tests/api/ai/query-safety.test.ts +196 -0
  388. package/tests/api/auth/login.test.ts +170 -0
  389. package/tests/api/auth/logout.test.ts +140 -0
  390. package/tests/api/auth/me.test.ts +73 -0
  391. package/tests/api/auth/oidc-callback.test.ts +215 -0
  392. package/tests/api/auth/oidc-login.test.ts +127 -0
  393. package/tests/api/db/cancel.test.ts +198 -0
  394. package/tests/api/db/disconnect.test.ts +124 -0
  395. package/tests/api/db/health.test.ts +222 -0
  396. package/tests/api/db/maintenance.test.ts +263 -0
  397. package/tests/api/db/monitoring.test.ts +221 -0
  398. package/tests/api/db/multi-query.test.ts +316 -0
  399. package/tests/api/db/pool-stats.test.ts +135 -0
  400. package/tests/api/db/profile.test.ts +330 -0
  401. package/tests/api/db/provider-meta.test.ts +193 -0
  402. package/tests/api/db/query.test.ts +314 -0
  403. package/tests/api/db/schema-snapshot.test.ts +170 -0
  404. package/tests/api/db/schema.test.ts +191 -0
  405. package/tests/api/db/test-connection.test.ts +185 -0
  406. package/tests/api/db/transaction.test.ts +314 -0
  407. package/tests/api/proxy.test.ts +191 -0
  408. package/tests/api/seed/managed-route.test.ts +113 -0
  409. package/tests/api/storage/config.test.ts +42 -0
  410. package/tests/api/storage/storage-routes.test.ts +309 -0
  411. package/tests/components/AIAutopilotPanel.test.tsx +756 -0
  412. package/tests/components/AdminPage.test.tsx +33 -0
  413. package/tests/components/CodeGenerator.test.tsx +182 -0
  414. package/tests/components/CommandPalette.test.tsx +428 -0
  415. package/tests/components/CommunitySection.test.tsx +91 -0
  416. package/tests/components/ConnectionModal.mobile.test.tsx +284 -0
  417. package/tests/components/ConnectionModal.test.tsx +570 -0
  418. package/tests/components/CreateTableModal.test.tsx +383 -0
  419. package/tests/components/DataCharts.test.tsx +739 -0
  420. package/tests/components/DataImportModal.test.tsx +751 -0
  421. package/tests/components/DataProfiler.test.tsx +589 -0
  422. package/tests/components/DatabaseDocs.test.tsx +353 -0
  423. package/tests/components/LoginPage.test.tsx +163 -0
  424. package/tests/components/LoginPageOIDC.test.tsx +92 -0
  425. package/tests/components/MaskingSettings.test.tsx +498 -0
  426. package/tests/components/MobileNav.test.tsx +30 -0
  427. package/tests/components/MonitoringPage.test.tsx +32 -0
  428. package/tests/components/NL2SQLPanel.test.tsx +621 -0
  429. package/tests/components/Page.test.tsx +33 -0
  430. package/tests/components/PivotTable.test.tsx +350 -0
  431. package/tests/components/QueryEditor.test.tsx +1730 -0
  432. package/tests/components/QueryHistory.test.tsx +572 -0
  433. package/tests/components/QuerySafetyDialog.test.tsx +586 -0
  434. package/tests/components/ResultsGrid.test.tsx +804 -0
  435. package/tests/components/RootLayout.test.tsx +83 -0
  436. package/tests/components/SaveQueryModal.test.tsx +25 -0
  437. package/tests/components/SavedQueries.test.tsx +43 -0
  438. package/tests/components/SchemaDiagram.test.tsx +1034 -0
  439. package/tests/components/SchemaDiff.test.tsx +906 -0
  440. package/tests/components/SnapshotTimeline.test.tsx +174 -0
  441. package/tests/components/Studio.test.tsx +1030 -0
  442. package/tests/components/TestDataGenerator.test.tsx +291 -0
  443. package/tests/components/VisualExplain.test.tsx +704 -0
  444. package/tests/components/admin/AdminDashboard.test.tsx +205 -0
  445. package/tests/components/admin/AuditTab.test.tsx +220 -0
  446. package/tests/components/admin/MonitoringEmbed.test.tsx +58 -0
  447. package/tests/components/admin/OperationsTab.test.tsx +975 -0
  448. package/tests/components/admin/OverviewTab.test.tsx +254 -0
  449. package/tests/components/admin/SecurityTab.test.tsx +467 -0
  450. package/tests/components/monitoring/MetricChart.test.tsx +111 -0
  451. package/tests/components/monitoring/MonitoringDashboard.test.tsx +259 -0
  452. package/tests/components/monitoring/OverviewTab.test.tsx +78 -0
  453. package/tests/components/monitoring/PerformanceTab.test.tsx +87 -0
  454. package/tests/components/monitoring/PoolTab.test.tsx +42 -0
  455. package/tests/components/monitoring/QueriesTab.test.tsx +80 -0
  456. package/tests/components/monitoring/SessionsTab.test.tsx +154 -0
  457. package/tests/components/monitoring/StorageTab.test.tsx +127 -0
  458. package/tests/components/monitoring/TablesTab.test.tsx +153 -0
  459. package/tests/components/results-grid/ResultCard.test.tsx +105 -0
  460. package/tests/components/results-grid/RowDetailSheet.test.tsx +308 -0
  461. package/tests/components/results-grid/StatsBar.test.tsx +162 -0
  462. package/tests/components/schema-explorer/ColumnList.test.tsx +151 -0
  463. package/tests/components/schema-explorer/SchemaExplorer.test.tsx +461 -0
  464. package/tests/components/schema-explorer/TableItem.test.tsx +415 -0
  465. package/tests/components/sidebar/ConnectionItem.test.tsx +201 -0
  466. package/tests/components/sidebar/ConnectionsList.test.tsx +176 -0
  467. package/tests/components/sidebar/Sidebar.test.tsx +187 -0
  468. package/tests/components/studio/BottomPanel.test.tsx +383 -0
  469. package/tests/components/studio/QueryToolbar.test.tsx +321 -0
  470. package/tests/components/studio/StudioDesktopHeader.test.tsx +377 -0
  471. package/tests/components/studio/StudioMobileHeader.test.tsx +198 -0
  472. package/tests/components/studio/StudioTabBar.test.tsx +331 -0
  473. package/tests/fixtures/connections.ts +96 -0
  474. package/tests/fixtures/masking-configs.ts +86 -0
  475. package/tests/fixtures/query-results.ts +71 -0
  476. package/tests/fixtures/schemas.ts +64 -0
  477. package/tests/fixtures/seed-connections/invalid-config.yaml +7 -0
  478. package/tests/fixtures/seed-connections/minimal-config.yaml +8 -0
  479. package/tests/fixtures/seed-connections/mixed-credentials.yaml +23 -0
  480. package/tests/fixtures/seed-connections/multi-role-config.yaml +30 -0
  481. package/tests/fixtures/seed-connections/valid-config.json +15 -0
  482. package/tests/fixtures/seed-connections/valid-config.yaml +51 -0
  483. package/tests/helpers/mock-fetch.ts +59 -0
  484. package/tests/helpers/mock-monaco.ts +112 -0
  485. package/tests/helpers/mock-navigation.ts +28 -0
  486. package/tests/helpers/mock-next.ts +80 -0
  487. package/tests/helpers/mock-provider.ts +133 -0
  488. package/tests/helpers/mock-sonner.ts +29 -0
  489. package/tests/helpers/render-with-providers.tsx +19 -0
  490. package/tests/hooks/use-ai-chat.test.ts +600 -0
  491. package/tests/hooks/use-auth.test.ts +371 -0
  492. package/tests/hooks/use-connection-form.test.ts +743 -0
  493. package/tests/hooks/use-connection-manager.test.ts +466 -0
  494. package/tests/hooks/use-inline-editing.test.ts +321 -0
  495. package/tests/hooks/use-mobile.test.ts +177 -0
  496. package/tests/hooks/use-monitoring-data.test.ts +819 -0
  497. package/tests/hooks/use-provider-metadata.test.ts +228 -0
  498. package/tests/hooks/use-query-execution.test.ts +1212 -0
  499. package/tests/hooks/use-tab-manager.test.ts +756 -0
  500. package/tests/hooks/use-toast.test.ts +74 -0
  501. package/tests/hooks/use-transaction-control.test.ts +211 -0
  502. package/tests/integration/db/mongodb-provider.test.ts +698 -0
  503. package/tests/integration/db/mssql-provider.test.ts +840 -0
  504. package/tests/integration/db/mysql-provider.test.ts +872 -0
  505. package/tests/integration/db/oracle-provider.test.ts +843 -0
  506. package/tests/integration/db/postgres-provider.test.ts +1382 -0
  507. package/tests/integration/db/redis-provider.test.ts +526 -0
  508. package/tests/integration/db/sqlite-provider.test.ts +480 -0
  509. package/tests/integration/seed/seed-pipeline.test.ts +102 -0
  510. package/tests/isolated/factory-singleton.test.ts +150 -0
  511. package/tests/isolated/use-storage-sync.test.ts +389 -0
  512. package/tests/run-components.sh +196 -0
  513. package/tests/setup-dom.ts +58 -0
  514. package/tests/setup.ts +40 -0
  515. package/tests/unit/api-errors.test.ts +210 -0
  516. package/tests/unit/code-generator-functions.test.ts +271 -0
  517. package/tests/unit/components/column-list.test.tsx +190 -0
  518. package/tests/unit/components/data-import-modal.test.tsx +441 -0
  519. package/tests/unit/components/studio-mobile-header.test.tsx +327 -0
  520. package/tests/unit/data-charts-functions.test.ts +496 -0
  521. package/tests/unit/data-import-functions.test.ts +320 -0
  522. package/tests/unit/data-import-utils.test.ts +125 -0
  523. package/tests/unit/db/base-provider.test.ts +517 -0
  524. package/tests/unit/db/errors.test.ts +403 -0
  525. package/tests/unit/db/factory.test.ts +436 -0
  526. package/tests/unit/db/pool-manager.test.ts +440 -0
  527. package/tests/unit/db/query-limiter.test.ts +387 -0
  528. package/tests/unit/db/sql-base.test.ts +438 -0
  529. package/tests/unit/lib/api/error-codes.test.ts +39 -0
  530. package/tests/unit/lib/audit.test.ts +326 -0
  531. package/tests/unit/lib/auth.test.ts +146 -0
  532. package/tests/unit/lib/connection-string-parser.test.ts +424 -0
  533. package/tests/unit/lib/data-masking.test.ts +583 -0
  534. package/tests/unit/lib/db-icons.test.tsx +41 -0
  535. package/tests/unit/lib/monitoring-thresholds.test.ts +133 -0
  536. package/tests/unit/lib/oidc.test.ts +509 -0
  537. package/tests/unit/lib/query-generators.test.ts +127 -0
  538. package/tests/unit/lib/storage/factory.test.ts +71 -0
  539. package/tests/unit/lib/storage/local-storage.test.ts +114 -0
  540. package/tests/unit/lib/storage/providers/postgres.test.ts +312 -0
  541. package/tests/unit/lib/storage/providers/sqlite.test.ts +232 -0
  542. package/tests/unit/lib/storage/storage-facade-extended.test.ts +331 -0
  543. package/tests/unit/lib/storage/storage-facade.test.ts +184 -0
  544. package/tests/unit/lib/storage.test.ts +317 -0
  545. package/tests/unit/lib/time-series-buffer.test.ts +212 -0
  546. package/tests/unit/lib/utils.test.ts +24 -0
  547. package/tests/unit/llm/base-provider.test.ts +238 -0
  548. package/tests/unit/llm/config.test.ts +262 -0
  549. package/tests/unit/llm/custom-provider.test.ts +281 -0
  550. package/tests/unit/llm/gemini-provider.test.ts +248 -0
  551. package/tests/unit/llm/llm-factory.test.ts +155 -0
  552. package/tests/unit/llm/ollama-provider.test.ts +288 -0
  553. package/tests/unit/llm/openai-provider.test.ts +324 -0
  554. package/tests/unit/llm/retry.test.ts +180 -0
  555. package/tests/unit/llm/streaming.test.ts +355 -0
  556. package/tests/unit/logger.test.ts +198 -0
  557. package/tests/unit/mongodb-completions.test.ts +516 -0
  558. package/tests/unit/pivot-table-functions.test.ts +76 -0
  559. package/tests/unit/query-cancelled-error.test.ts +81 -0
  560. package/tests/unit/schema-diff/diff-engine.test.ts +367 -0
  561. package/tests/unit/schema-diff/migration-generator.test.ts +513 -0
  562. package/tests/unit/seed/config-loader.test.ts +73 -0
  563. package/tests/unit/seed/connection-filter.test.ts +91 -0
  564. package/tests/unit/seed/credential-resolver.test.ts +85 -0
  565. package/tests/unit/seed/index.test.ts +72 -0
  566. package/tests/unit/seed/resolve-connection.test.ts +74 -0
  567. package/tests/unit/seed/types.test.ts +129 -0
  568. package/tests/unit/sql/alias-extractor.test.ts +444 -0
  569. package/tests/unit/sql/statement-splitter.test.ts +348 -0
  570. package/tests/unit/sql-completions.test.ts +463 -0
  571. package/tests/unit/ssh-tunnel.test.ts +465 -0
  572. package/tsconfig.json +42 -0
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Custom LLM Provider
3
+ * Generic OpenAI-compatible endpoint for LiteLLM, LMStudio, vLLM, etc.
4
+ */
5
+
6
+ import { BaseLLMProvider } from '../base-provider';
7
+ import {
8
+ type LLMConfig,
9
+ type LLMStreamOptions,
10
+ LLMAuthError,
11
+ LLMRateLimitError,
12
+ LLMStreamError,
13
+ LLMConfigError,
14
+ } from '../types';
15
+ import { createStreamFromSSEResponse } from '../utils/streaming';
16
+ import { logger } from '@/lib/logger';
17
+
18
+ // ============================================================================
19
+ // Custom Provider
20
+ // ============================================================================
21
+
22
+ export class CustomProvider extends BaseLLMProvider {
23
+ private baseUrl: string;
24
+
25
+ constructor(config: LLMConfig) {
26
+ super(config);
27
+ this.validate();
28
+ this.baseUrl = this.ensureApiUrl();
29
+ }
30
+
31
+ /**
32
+ * Validate configuration - requires API URL
33
+ */
34
+ public validate(): void {
35
+ if (!this.config.apiUrl || this.config.apiUrl.trim() === '') {
36
+ throw new LLMConfigError(
37
+ 'Custom provider requires LLM_API_URL environment variable.',
38
+ 'custom'
39
+ );
40
+ }
41
+
42
+ if (!this.config.model || this.config.model.trim() === '') {
43
+ throw new LLMConfigError(
44
+ 'Model name is required for Custom provider.',
45
+ 'custom'
46
+ );
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Stream completion from custom endpoint
52
+ */
53
+ public async stream(options: LLMStreamOptions): Promise<ReadableStream<Uint8Array>> {
54
+ return this.streamWithRetry(async () => {
55
+ const model = this.getModel(options);
56
+ const messages = this.buildMessages(options);
57
+
58
+ try {
59
+ const response = await this.fetchStream(model, messages, options);
60
+ await this.validateResponse(response);
61
+
62
+ return createStreamFromSSEResponse(response, 'custom');
63
+ } catch (error) {
64
+ if (
65
+ error instanceof LLMAuthError ||
66
+ error instanceof LLMRateLimitError ||
67
+ error instanceof LLMConfigError
68
+ ) {
69
+ throw error;
70
+ }
71
+ throw this.mapError(error);
72
+ }
73
+ });
74
+ }
75
+
76
+ /**
77
+ * Build messages array in OpenAI format
78
+ */
79
+ private buildMessages(options: LLMStreamOptions): Array<{ role: string; content: string }> {
80
+ return options.messages.map((m) => ({
81
+ role: m.role,
82
+ content: m.content,
83
+ }));
84
+ }
85
+
86
+ /**
87
+ * Fetch streaming response from custom endpoint
88
+ */
89
+ private async fetchStream(
90
+ model: string,
91
+ messages: Array<{ role: string; content: string }>,
92
+ options: LLMStreamOptions
93
+ ): Promise<Response> {
94
+ const headers: Record<string, string> = {
95
+ 'Content-Type': 'application/json',
96
+ };
97
+
98
+ // Add API key if provided
99
+ if (this.config.apiKey) {
100
+ headers.Authorization = `Bearer ${this.config.apiKey}`;
101
+ }
102
+
103
+ const response = await fetch(`${this.baseUrl}/chat/completions`, {
104
+ method: 'POST',
105
+ headers,
106
+ body: JSON.stringify({
107
+ model,
108
+ messages,
109
+ stream: true,
110
+ ...(options.temperature !== undefined && { temperature: options.temperature }),
111
+ ...(options.maxTokens !== undefined && { max_tokens: options.maxTokens }),
112
+ }),
113
+ });
114
+
115
+ return response;
116
+ }
117
+
118
+ /**
119
+ * Validate response status
120
+ */
121
+ private async validateResponse(response: Response): Promise<void> {
122
+ if (response.ok) {
123
+ return;
124
+ }
125
+
126
+ let errorMessage = `HTTP ${response.status}`;
127
+
128
+ try {
129
+ const errorBody = await response.text();
130
+ const errorJson = JSON.parse(errorBody);
131
+ errorMessage = errorJson.error?.message ?? errorBody;
132
+ } catch {
133
+ logger.debug('Could not parse error response body as JSON', { provider: 'custom' });
134
+ }
135
+
136
+ if (response.status === 401 || response.status === 403) {
137
+ throw new LLMAuthError(
138
+ 'Authentication failed. Check your API key configuration.',
139
+ 'custom'
140
+ );
141
+ }
142
+
143
+ if (response.status === 429) {
144
+ throw new LLMRateLimitError(
145
+ 'Rate limit exceeded. Please try again later.',
146
+ 'custom'
147
+ );
148
+ }
149
+
150
+ throw new LLMStreamError(`Custom API error: ${errorMessage}`, 'custom');
151
+ }
152
+
153
+ /**
154
+ * Map errors to LLM error types
155
+ */
156
+ private mapError(error: unknown): Error {
157
+ if (error instanceof LLMStreamError) {
158
+ return error;
159
+ }
160
+
161
+ if (!(error instanceof Error)) {
162
+ return new LLMStreamError(String(error), 'custom');
163
+ }
164
+
165
+ const message = error.message.toLowerCase();
166
+
167
+ // Connection errors
168
+ if (
169
+ message.includes('econnrefused') ||
170
+ message.includes('fetch failed') ||
171
+ message.includes('network')
172
+ ) {
173
+ return new LLMStreamError(
174
+ `Cannot connect to custom endpoint at ${this.baseUrl}. Make sure the service is running.`,
175
+ 'custom'
176
+ );
177
+ }
178
+
179
+ return new LLMStreamError(error.message, 'custom');
180
+ }
181
+ }
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Gemini LLM Provider
3
+ * Google Gemini AI integration using @google/generative-ai SDK
4
+ */
5
+
6
+ import { GoogleGenerativeAI, type GenerateContentStreamResult } from '@google/generative-ai';
7
+ import { BaseLLMProvider } from '../base-provider';
8
+ import {
9
+ type LLMConfig,
10
+ type LLMStreamOptions,
11
+ LLMAuthError,
12
+ LLMRateLimitError,
13
+ LLMSafetyError,
14
+ LLMStreamError,
15
+ } from '../types';
16
+ import { encodeText, streamFromAsyncIterable } from '../utils/streaming';
17
+
18
+ // ============================================================================
19
+ // Gemini Provider
20
+ // ============================================================================
21
+
22
+ export class GeminiProvider extends BaseLLMProvider {
23
+ private client: GoogleGenerativeAI;
24
+
25
+ constructor(config: LLMConfig) {
26
+ super(config);
27
+ this.validate();
28
+ this.client = new GoogleGenerativeAI(this.ensureApiKey());
29
+ }
30
+
31
+ /**
32
+ * Stream completion from Gemini
33
+ */
34
+ public async stream(options: LLMStreamOptions): Promise<ReadableStream<Uint8Array>> {
35
+ return this.streamWithRetry(async () => {
36
+ const model = this.getModel(options);
37
+ const systemInstruction = this.getSystemMessage(options);
38
+ const messages = this.getNonSystemMessages(options);
39
+
40
+ // Build the prompt from messages
41
+ const prompt = messages.map((m) => m.content).join('\n\n');
42
+
43
+ try {
44
+ const generativeModel = this.client.getGenerativeModel({
45
+ model,
46
+ systemInstruction,
47
+ });
48
+
49
+ const result = await generativeModel.generateContentStream(prompt);
50
+
51
+ return this.createStreamFromResult(result);
52
+ } catch (error) {
53
+ throw this.mapError(error);
54
+ }
55
+ });
56
+ }
57
+
58
+ /**
59
+ * Create ReadableStream from Gemini stream result
60
+ */
61
+ private createStreamFromResult(
62
+ result: GenerateContentStreamResult
63
+ ): ReadableStream<Uint8Array> {
64
+ return streamFromAsyncIterable(result.stream, (chunk) => {
65
+ try {
66
+ const text = chunk.text();
67
+ return text ? encodeText(text) : null;
68
+ } catch {
69
+ // Handle safety-blocked chunks
70
+ return null;
71
+ }
72
+ });
73
+ }
74
+
75
+ /**
76
+ * Map Gemini errors to LLM error types
77
+ */
78
+ private mapError(error: unknown): Error {
79
+ if (!(error instanceof Error)) {
80
+ return new LLMStreamError(String(error), 'gemini');
81
+ }
82
+
83
+ const message = error.message.toLowerCase();
84
+
85
+ // Authentication errors
86
+ if (
87
+ message.includes('api key') ||
88
+ message.includes('invalid key') ||
89
+ message.includes('unauthorized') ||
90
+ message.includes('permission denied')
91
+ ) {
92
+ return new LLMAuthError(
93
+ 'Invalid API Key. Please check your Gemini API configuration.',
94
+ 'gemini'
95
+ );
96
+ }
97
+
98
+ // Rate limit errors
99
+ if (
100
+ message.includes('quota') ||
101
+ message.includes('rate limit') ||
102
+ message.includes('resource exhausted') ||
103
+ message.includes('429')
104
+ ) {
105
+ return new LLMRateLimitError(
106
+ 'AI usage limit reached. Please try again later or upgrade your plan.',
107
+ 'gemini'
108
+ );
109
+ }
110
+
111
+ // Safety filter errors
112
+ if (
113
+ message.includes('safety') ||
114
+ message.includes('blocked') ||
115
+ message.includes('harm')
116
+ ) {
117
+ return new LLMSafetyError(
118
+ 'The prompt was blocked by safety filters. Please modify your request.',
119
+ 'gemini'
120
+ );
121
+ }
122
+
123
+ // Generic stream error
124
+ return new LLMStreamError(error.message, 'gemini');
125
+ }
126
+ }
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Ollama LLM Provider
3
+ * Local LLM integration via OpenAI-compatible API
4
+ */
5
+
6
+ import { BaseLLMProvider } from '../base-provider';
7
+ import {
8
+ type LLMConfig,
9
+ type LLMStreamOptions,
10
+ LLMStreamError,
11
+ LLMConfigError,
12
+ } from '../types';
13
+ import { createStreamFromSSEResponse } from '../utils/streaming';
14
+ import { DEFAULT_API_URLS } from '../utils/config';
15
+ import { logger } from '@/lib/logger';
16
+
17
+ // ============================================================================
18
+ // Ollama Provider
19
+ // ============================================================================
20
+
21
+ export class OllamaProvider extends BaseLLMProvider {
22
+ private baseUrl: string;
23
+
24
+ constructor(config: LLMConfig) {
25
+ super(config);
26
+ this.baseUrl = config.apiUrl ?? DEFAULT_API_URLS.ollama;
27
+ }
28
+
29
+ /**
30
+ * Override validation - Ollama doesn't require API key
31
+ */
32
+ public validate(): void {
33
+ if (!this.config.model || this.config.model.trim() === '') {
34
+ throw new LLMConfigError(
35
+ 'Model name is required for Ollama provider.',
36
+ 'ollama'
37
+ );
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Stream completion from Ollama
43
+ */
44
+ public async stream(options: LLMStreamOptions): Promise<ReadableStream<Uint8Array>> {
45
+ return this.streamWithRetry(async () => {
46
+ const model = this.getModel(options);
47
+ const messages = this.buildMessages(options);
48
+
49
+ try {
50
+ const response = await this.fetchStream(model, messages, options);
51
+ await this.validateResponse(response);
52
+
53
+ return createStreamFromSSEResponse(response, 'ollama');
54
+ } catch (error) {
55
+ throw this.mapError(error);
56
+ }
57
+ });
58
+ }
59
+
60
+ /**
61
+ * Build messages array in OpenAI format
62
+ */
63
+ private buildMessages(options: LLMStreamOptions): Array<{ role: string; content: string }> {
64
+ return options.messages.map((m) => ({
65
+ role: m.role,
66
+ content: m.content,
67
+ }));
68
+ }
69
+
70
+ /**
71
+ * Fetch streaming response from Ollama API
72
+ */
73
+ private async fetchStream(
74
+ model: string,
75
+ messages: Array<{ role: string; content: string }>,
76
+ options: LLMStreamOptions
77
+ ): Promise<Response> {
78
+ const response = await fetch(`${this.baseUrl}/chat/completions`, {
79
+ method: 'POST',
80
+ headers: {
81
+ 'Content-Type': 'application/json',
82
+ // Ollama accepts 'ollama' or empty string as API key
83
+ Authorization: 'Bearer ollama',
84
+ },
85
+ body: JSON.stringify({
86
+ model,
87
+ messages,
88
+ stream: true,
89
+ ...(options.temperature !== undefined && { temperature: options.temperature }),
90
+ ...(options.maxTokens !== undefined && { max_tokens: options.maxTokens }),
91
+ }),
92
+ });
93
+
94
+ return response;
95
+ }
96
+
97
+ /**
98
+ * Validate response status
99
+ */
100
+ private async validateResponse(response: Response): Promise<void> {
101
+ if (response.ok) {
102
+ return;
103
+ }
104
+
105
+ let errorMessage = `HTTP ${response.status}`;
106
+
107
+ try {
108
+ const errorBody = await response.text();
109
+ const errorJson = JSON.parse(errorBody);
110
+ errorMessage = errorJson.error?.message ?? errorBody;
111
+ } catch {
112
+ logger.debug('Could not parse error response body as JSON', { provider: 'ollama' });
113
+ }
114
+
115
+ // Ollama-specific errors
116
+ if (response.status === 404) {
117
+ throw new LLMConfigError(
118
+ `Model not found. Make sure "${this.config.model}" is pulled in Ollama.`,
119
+ 'ollama'
120
+ );
121
+ }
122
+
123
+ throw new LLMStreamError(`Ollama API error: ${errorMessage}`, 'ollama');
124
+ }
125
+
126
+ /**
127
+ * Map errors to LLM error types
128
+ */
129
+ private mapError(error: unknown): Error {
130
+ if (error instanceof LLMConfigError || error instanceof LLMStreamError) {
131
+ return error;
132
+ }
133
+
134
+ if (!(error instanceof Error)) {
135
+ return new LLMStreamError(String(error), 'ollama');
136
+ }
137
+
138
+ const message = error.message.toLowerCase();
139
+
140
+ // Connection errors - Ollama not running
141
+ if (
142
+ message.includes('econnrefused') ||
143
+ message.includes('fetch failed') ||
144
+ message.includes('network')
145
+ ) {
146
+ return new LLMStreamError(
147
+ `Cannot connect to Ollama at ${this.baseUrl}. Make sure Ollama is running.`,
148
+ 'ollama'
149
+ );
150
+ }
151
+
152
+ return new LLMStreamError(error.message, 'ollama');
153
+ }
154
+ }
@@ -0,0 +1,146 @@
1
+ /**
2
+ * OpenAI LLM Provider
3
+ * OpenAI Chat Completions API with SSE streaming
4
+ */
5
+
6
+ import { BaseLLMProvider } from '../base-provider';
7
+ import {
8
+ type LLMConfig,
9
+ type LLMStreamOptions,
10
+ LLMAuthError,
11
+ LLMRateLimitError,
12
+ LLMStreamError,
13
+ } from '../types';
14
+ import { createStreamFromSSEResponse } from '../utils/streaming';
15
+ import { DEFAULT_API_URLS } from '../utils/config';
16
+ import { logger } from '@/lib/logger';
17
+
18
+ // ============================================================================
19
+ // OpenAI Provider
20
+ // ============================================================================
21
+
22
+ export class OpenAIProvider extends BaseLLMProvider {
23
+ protected baseUrl: string;
24
+
25
+ constructor(config: LLMConfig) {
26
+ super(config);
27
+ this.validate();
28
+ this.baseUrl = config.apiUrl ?? DEFAULT_API_URLS.openai;
29
+ }
30
+
31
+ /**
32
+ * Stream completion from OpenAI
33
+ */
34
+ public async stream(options: LLMStreamOptions): Promise<ReadableStream<Uint8Array>> {
35
+ return this.streamWithRetry(async () => {
36
+ const model = this.getModel(options);
37
+ const messages = this.buildMessages(options);
38
+
39
+ try {
40
+ const response = await this.fetchStream(model, messages, options);
41
+ await this.validateResponse(response);
42
+
43
+ return createStreamFromSSEResponse(response, this.name);
44
+ } catch (error) {
45
+ if (error instanceof LLMAuthError || error instanceof LLMRateLimitError) {
46
+ throw error;
47
+ }
48
+ throw this.mapError(error);
49
+ }
50
+ });
51
+ }
52
+
53
+ /**
54
+ * Build messages array in OpenAI format
55
+ */
56
+ protected buildMessages(options: LLMStreamOptions): Array<{ role: string; content: string }> {
57
+ return options.messages.map((m) => ({
58
+ role: m.role,
59
+ content: m.content,
60
+ }));
61
+ }
62
+
63
+ /**
64
+ * Fetch streaming response from OpenAI API
65
+ */
66
+ protected async fetchStream(
67
+ model: string,
68
+ messages: Array<{ role: string; content: string }>,
69
+ options: LLMStreamOptions
70
+ ): Promise<Response> {
71
+ const apiKey = this.ensureApiKey();
72
+
73
+ const response = await fetch(`${this.baseUrl}/chat/completions`, {
74
+ method: 'POST',
75
+ headers: {
76
+ 'Content-Type': 'application/json',
77
+ Authorization: `Bearer ${apiKey}`,
78
+ },
79
+ body: JSON.stringify({
80
+ model,
81
+ messages,
82
+ stream: true,
83
+ ...(options.temperature !== undefined && { temperature: options.temperature }),
84
+ ...(options.maxTokens !== undefined && { max_tokens: options.maxTokens }),
85
+ }),
86
+ });
87
+
88
+ return response;
89
+ }
90
+
91
+ /**
92
+ * Validate response status and throw appropriate errors
93
+ */
94
+ protected async validateResponse(response: Response): Promise<void> {
95
+ if (response.ok) {
96
+ return;
97
+ }
98
+
99
+ let errorMessage = `HTTP ${response.status}`;
100
+
101
+ try {
102
+ const errorBody = await response.text();
103
+ const errorJson = JSON.parse(errorBody);
104
+ errorMessage = errorJson.error?.message ?? errorBody;
105
+ } catch {
106
+ logger.debug('Could not parse error response body as JSON', { provider: 'openai' });
107
+ }
108
+
109
+ if (response.status === 401 || response.status === 403) {
110
+ throw new LLMAuthError(
111
+ 'Invalid API Key. Please check your OpenAI API configuration.',
112
+ 'openai'
113
+ );
114
+ }
115
+
116
+ if (response.status === 429) {
117
+ throw new LLMRateLimitError(
118
+ 'Rate limit exceeded. Please try again later or upgrade your plan.',
119
+ 'openai'
120
+ );
121
+ }
122
+
123
+ throw new LLMStreamError(`OpenAI API error: ${errorMessage}`, 'openai');
124
+ }
125
+
126
+ /**
127
+ * Map errors to LLM error types
128
+ */
129
+ protected mapError(error: unknown): Error {
130
+ if (!(error instanceof Error)) {
131
+ return new LLMStreamError(String(error), 'openai');
132
+ }
133
+
134
+ const message = error.message.toLowerCase();
135
+
136
+ // Network errors
137
+ if (message.includes('fetch') || message.includes('network')) {
138
+ return new LLMStreamError(
139
+ 'Network error. Please check your connection.',
140
+ 'openai'
141
+ );
142
+ }
143
+
144
+ return new LLMStreamError(error.message, 'openai');
145
+ }
146
+ }