@deepagents/text2sql 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/index.d.ts +13 -0
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +3102 -0
  4. package/dist/index.js.map +7 -0
  5. package/dist/lib/adapters/adapter.d.ts +80 -0
  6. package/dist/lib/adapters/adapter.d.ts.map +1 -0
  7. package/dist/lib/adapters/postgres.d.ts +31 -0
  8. package/dist/lib/adapters/postgres.d.ts.map +1 -0
  9. package/dist/lib/adapters/resolveTables.spec.d.ts +2 -0
  10. package/dist/lib/adapters/resolveTables.spec.d.ts.map +1 -0
  11. package/dist/lib/adapters/sqlite.d.ts +30 -0
  12. package/dist/lib/adapters/sqlite.d.ts.map +1 -0
  13. package/dist/lib/adapters/sqlserver.d.ts +31 -0
  14. package/dist/lib/adapters/sqlserver.d.ts.map +1 -0
  15. package/dist/lib/agents/brief.agent.d.ts +16 -0
  16. package/dist/lib/agents/brief.agent.d.ts.map +1 -0
  17. package/dist/lib/agents/explainer.agent.d.ts +8 -0
  18. package/dist/lib/agents/explainer.agent.d.ts.map +1 -0
  19. package/dist/lib/agents/suggestions.agents.d.ts +16 -0
  20. package/dist/lib/agents/suggestions.agents.d.ts.map +1 -0
  21. package/dist/lib/agents/synthesizer.agent.d.ts +6 -0
  22. package/dist/lib/agents/synthesizer.agent.d.ts.map +1 -0
  23. package/dist/lib/agents/teachables.agent.d.ts +14 -0
  24. package/dist/lib/agents/teachables.agent.d.ts.map +1 -0
  25. package/dist/lib/agents/text2sql.agent.d.ts +38 -0
  26. package/dist/lib/agents/text2sql.agent.d.ts.map +1 -0
  27. package/dist/lib/history/history.d.ts +41 -0
  28. package/dist/lib/history/history.d.ts.map +1 -0
  29. package/dist/lib/history/memory.history.d.ts +5 -0
  30. package/dist/lib/history/memory.history.d.ts.map +1 -0
  31. package/dist/lib/history/sqlite.history.d.ts +15 -0
  32. package/dist/lib/history/sqlite.history.d.ts.map +1 -0
  33. package/dist/lib/memory/user-profile.d.ts +39 -0
  34. package/dist/lib/memory/user-profile.d.ts.map +1 -0
  35. package/dist/lib/prompt.d.ts +7 -0
  36. package/dist/lib/prompt.d.ts.map +1 -0
  37. package/dist/lib/sql.d.ts +52 -0
  38. package/dist/lib/sql.d.ts.map +1 -0
  39. package/dist/lib/teach/teachables.d.ts +462 -0
  40. package/dist/lib/teach/teachables.d.ts.map +1 -0
  41. package/dist/lib/teach/teachings.d.ts +4 -0
  42. package/dist/lib/teach/teachings.d.ts.map +1 -0
  43. package/dist/lib/teach/xml.d.ts +6 -0
  44. package/dist/lib/teach/xml.d.ts.map +1 -0
  45. package/package.json +38 -0
package/dist/index.js ADDED
@@ -0,0 +1,3102 @@
1
+ // packages/text2sql/src/lib/agents/text2sql.agent.ts
2
+ import { groq } from "@ai-sdk/groq";
3
+ import { tool } from "ai";
4
+ import z from "zod";
5
+ import {
6
+ agent,
7
+ stepBackPrompt,
8
+ toState
9
+ } from "@deepagents/agent";
10
+ import { scratchpad_tool } from "@deepagents/toolbox";
11
+
12
+ // packages/text2sql/src/lib/prompt.ts
13
+ import pluralize from "pluralize";
14
+ var describeTables = (introspection) => {
15
+ if (!introspection.tables.length) {
16
+ return "Schema unavailable.";
17
+ }
18
+ return introspection.tables.map((table) => {
19
+ const rowCountInfo = table.rowCount != null ? ` [rows: ${table.rowCount}${table.sizeHint ? `, size: ${table.sizeHint}` : ""}]` : "";
20
+ const columns = table.columns.map((column) => {
21
+ const annotations = [];
22
+ if (column.isPrimaryKey) {
23
+ annotations.push("PK");
24
+ }
25
+ if (column.isIndexed && !column.isPrimaryKey) {
26
+ annotations.push("Indexed");
27
+ }
28
+ if (column.kind === "LowCardinality" && column.values?.length) {
29
+ annotations.push(`LowCardinality: ${column.values.join(", ")}`);
30
+ }
31
+ if (column.stats) {
32
+ const statParts = [];
33
+ if (column.stats.min != null || column.stats.max != null) {
34
+ const minText = column.stats.min ?? "n/a";
35
+ const maxText = column.stats.max ?? "n/a";
36
+ statParts.push(`range ${minText} \u2192 ${maxText}`);
37
+ }
38
+ if (column.stats.nullFraction != null && Number.isFinite(column.stats.nullFraction)) {
39
+ const percent = Math.round(column.stats.nullFraction * 1e3) / 10;
40
+ statParts.push(`null\u2248${percent}%`);
41
+ }
42
+ if (statParts.length) {
43
+ annotations.push(statParts.join(", "));
44
+ }
45
+ }
46
+ const annotationText = annotations.length ? ` [${annotations.join(", ")}]` : "";
47
+ return ` - ${column.name} (${column.type})${annotationText}`;
48
+ }).join("\n");
49
+ const indexes = table.indexes?.length ? `
50
+ Indexes:
51
+ ${table.indexes.map((index) => {
52
+ const props = [];
53
+ if (index.primary) {
54
+ props.push("PRIMARY");
55
+ } else if (index.unique) {
56
+ props.push("UNIQUE");
57
+ }
58
+ if (index.type) {
59
+ props.push(index.type);
60
+ }
61
+ const propsText = props.length ? ` (${props.join(", ")})` : "";
62
+ const columnsText = index.columns?.length ? index.columns.join(", ") : "expression";
63
+ return ` - ${index.name}${propsText}: ${columnsText}`;
64
+ }).join("\n")}` : "";
65
+ return `- Table: ${table.name}${rowCountInfo}
66
+ Columns:
67
+ ${columns}${indexes}`;
68
+ }).join("\n\n");
69
+ };
70
+ var formatTableLabel = (tableName) => {
71
+ const base = tableName.split(".").pop() ?? tableName;
72
+ return base.replace(/_/g, " ");
73
+ };
74
+ var describeRelationships = (introspection) => {
75
+ if (!introspection.relationships.length) {
76
+ return "None detected";
77
+ }
78
+ const tableMap = new Map(introspection.tables.map((table) => [table.name, table]));
79
+ return introspection.relationships.map((relationship) => {
80
+ const sourceLabel = formatTableLabel(relationship.table);
81
+ const targetLabel = formatTableLabel(relationship.referenced_table);
82
+ const singularSource = pluralize.singular(sourceLabel);
83
+ const pluralSource = pluralize(sourceLabel);
84
+ const singularTarget = pluralize.singular(targetLabel);
85
+ const pluralTarget = pluralize(targetLabel);
86
+ const sourceTable = tableMap.get(relationship.table);
87
+ const targetTable = tableMap.get(relationship.referenced_table);
88
+ const sourceCount = sourceTable?.rowCount;
89
+ const targetCount = targetTable?.rowCount;
90
+ const ratio = sourceCount != null && targetCount != null && targetCount > 0 ? sourceCount / targetCount : null;
91
+ let cardinality = "each";
92
+ if (ratio != null) {
93
+ if (ratio > 5) {
94
+ cardinality = `many-to-one (\u2248${sourceCount} vs ${targetCount})`;
95
+ } else if (ratio < 1.2 && ratio > 0.8) {
96
+ cardinality = `roughly 1:1 (${sourceCount} vs ${targetCount})`;
97
+ } else if (ratio < 0.2) {
98
+ cardinality = `one-to-many (${sourceCount} vs ${targetCount})`;
99
+ }
100
+ }
101
+ const mappings = relationship.from.map((fromCol, idx) => {
102
+ const targetCol = relationship.to[idx] ?? relationship.to[0] ?? fromCol;
103
+ return `${relationship.table}.${fromCol} -> ${relationship.referenced_table}.${targetCol}`;
104
+ }).join(", ");
105
+ return `- ${relationship.table} (${relationship.from.join(", ")}) -> ${relationship.referenced_table} (${relationship.to.join(", ")}) [${cardinality}]`;
106
+ }).join("\n");
107
+ };
108
+ function databaseSchemaPrompt(options) {
109
+ const tablesSummary = describeTables(options.introspection);
110
+ const relationshipsSummary = describeRelationships(options.introspection);
111
+ const contextInfo = options.context || "";
112
+ const adapterInfo = options.adapterInfo;
113
+ const lines = [
114
+ adapterInfo ? `<dialect_info>${adapterInfo}</dialect_info>` : "",
115
+ contextInfo ? `<context>${contextInfo}</context>` : "",
116
+ `<tables>
117
+ ${tablesSummary}
118
+ </tables>`,
119
+ `<relationships>
120
+ ${relationshipsSummary}
121
+ </relationships>`
122
+ ];
123
+ return `<schema_context>${lines.filter(Boolean).join("\n\n")}</schema_context>`;
124
+ }
125
+
126
+ // packages/text2sql/src/lib/agents/text2sql.agent.ts
127
+ var tools = {
128
+ validate_query: tool({
129
+ description: `Validate SQL query syntax before execution. Use this to check if your SQL is valid before running db_query. This helps catch errors early and allows you to correct the query if needed.`,
130
+ inputSchema: z.object({
131
+ sql: z.string().describe("The SQL query to validate.")
132
+ }),
133
+ execute: async ({ sql }, options) => {
134
+ const state = toState(options);
135
+ const result = await state.adapter.validate(sql);
136
+ if (typeof result === "string") {
137
+ return `Validation Error: ${result}`;
138
+ }
139
+ return "Query is valid.";
140
+ }
141
+ }),
142
+ get_sample_rows: tool({
143
+ description: `Get a few sample rows from a table to understand data formatting and values. Use this when you are unsure about the content of a column (e.g. date formats, status codes, string variations).`,
144
+ inputSchema: z.object({
145
+ tableName: z.string().describe("The name of the table to sample.")
146
+ }),
147
+ execute: ({ tableName }, options) => {
148
+ tableName = tableName.replace(/[^a-zA-Z0-9_.]/g, "");
149
+ const state = toState(options);
150
+ return state.adapter.execute(`SELECT * FROM ${tableName} LIMIT 3`);
151
+ }
152
+ }),
153
+ db_query: tool({
154
+ description: `Internal tool to fetch data from the store's database. Write a SQL query to retrieve the information needed to answer the user's question. The results will be returned as data that you can then present to the user in natural language.`,
155
+ inputSchema: z.object({
156
+ reasoning: z.string().describe(
157
+ "Your reasoning for why this SQL query is relevant to the user request."
158
+ ),
159
+ sql: z.string().min(1, { message: "SQL query cannot be empty." }).refine(
160
+ (sql) => sql.trim().toUpperCase().startsWith("SELECT") || sql.trim().toUpperCase().startsWith("WITH"),
161
+ {
162
+ message: "Only read-only SELECT or WITH queries are allowed."
163
+ }
164
+ ).describe("The SQL query to execute against the database.")
165
+ }),
166
+ execute: ({ sql }, options) => {
167
+ const state = toState(options);
168
+ return state.adapter.execute(sql);
169
+ }
170
+ }),
171
+ scratchpad: scratchpad_tool
172
+ };
173
+ var getRenderingGuidance = (renderingTools) => {
174
+ const renderingToolNames = Object.keys(renderingTools ?? {}).filter(
175
+ (toolName) => toolName.startsWith("render_")
176
+ );
177
+ if (!renderingToolNames.length) {
178
+ return { constraint: void 0, section: "" };
179
+ }
180
+ return {
181
+ constraint: "**Rendering**: Use a render_* visualization tool for trend/over time/monthly requests or explicit chart asks; otherwise provide the insight in text.",
182
+ section: `
183
+ <rendering_tools>
184
+ Rendering tools available: ${renderingToolNames.join(", ")}.
185
+ Use the matching render_* tool when the user requests a chart or mentions trends/over time/monthly performance. Prefer a line chart for those time-based requests. Always include a concise text insight alongside any visualization; if no suitable render_* tool fits, deliver the insight in text only.
186
+ </rendering_tools>
187
+ `
188
+ };
189
+ };
190
+ var SQL_STEP_BACK_EXAMPLES = [
191
+ {
192
+ originalQuestion: "Who are our top 5 customers by spending?",
193
+ stepBackQuestion: "What are the SQL principles for ranking and aggregation queries?",
194
+ stepBackAnswer: "Ranking queries require: 1) Aggregation functions (SUM, COUNT, AVG) grouped by the entity to rank, 2) JOINs to connect related data across tables (e.g., Customer to Invoice), 3) ORDER BY to sort by the aggregated metric, 4) LIMIT to restrict to top N results. For customer spending, join Customer and Invoice tables, sum invoice totals, group by customer identifier, order by total descending.",
195
+ finalAnswer: "SELECT c.FirstName, c.LastName, SUM(i.Total) as total_spent FROM Customer c JOIN Invoice i ON c.CustomerId = i.CustomerId GROUP BY c.CustomerId ORDER BY total_spent DESC LIMIT 5"
196
+ },
197
+ {
198
+ originalQuestion: "Show me sales by month for 2013",
199
+ stepBackQuestion: "What are the principles of time-based grouping and aggregation in SQL?",
200
+ stepBackAnswer: "Time-based queries require: 1) Date extraction functions (e.g., DATE_TRUNC, strftime, YEAR/FORMAT) to bucket timestamps, 2) WHERE clauses to filter the date range, 3) GROUP BY the derived period, 4) Aggregations such as SUM for revenue and COUNT for transactions, 5) ORDER BY the period chronologically.",
201
+ finalAnswer: "SELECT date_trunc('month', InvoiceDate) as month, COUNT(*) as sales_count, SUM(Total) as revenue FROM Invoice WHERE EXTRACT(year FROM InvoiceDate) = 2013 GROUP BY month ORDER BY month -- replace date_trunc/EXTRACT with your dialect's month/year helpers"
202
+ },
203
+ {
204
+ originalQuestion: "What are the best-selling tracks by genre?",
205
+ stepBackQuestion: "What are the SQL principles for multi-dimensional aggregation with categories?",
206
+ stepBackAnswer: "Multi-dimensional queries require: 1) Multiple JOINs to connect entities through foreign key relationships (Genre \u2192 Track \u2192 InvoiceLine), 2) GROUP BY all categorical dimensions you want to analyze (GenreId, TrackId), 3) Aggregation at the intersection of these dimensions (COUNT of sales per track per genre), 4) Proper table aliasing for query readability, 5) Understanding the data model relationships (which tables link to which).",
207
+ finalAnswer: "SELECT g.Name as genre, t.Name as track, COUNT(*) as times_sold FROM Genre g JOIN Track t ON g.GenreId = t.GenreId JOIN InvoiceLine il ON t.TrackId = il.TrackId GROUP BY g.GenreId, t.TrackId ORDER BY times_sold DESC LIMIT 10"
208
+ }
209
+ ];
210
+ var text2sqlAgent = agent({
211
+ name: "text2sql",
212
+ model: groq("openai/gpt-oss-20b"),
213
+ prompt: (state) => {
214
+ const renderingGuidance = getRenderingGuidance(state?.renderingTools);
215
+ const constraints = [
216
+ "**Max Output Rows**: Never output more than 100 rows of raw data. Use aggregation or pagination otherwise.",
217
+ "**Validation**: You must validate your query before final execution. Follow the pattern: Draft Query \u2192 `validate_query` \u2192 Fix (if needed) \u2192 `db_query`.",
218
+ "**Data Inspection**: If you are unsure about column values (e.g. status codes, date formats), use `get_sample_rows` to inspect the data before writing the query.",
219
+ "**Tool Usage**: If you have not produced a SQL snippet, do not call `db_query`. First produce the query string, then validate.",
220
+ renderingGuidance.constraint,
221
+ "**Scratchpad**: Use the `scratchpad` tool for strategic reflection during SQL query generation."
222
+ ].filter(Boolean);
223
+ const constraintsSection = constraints.map((constraint, index) => ` ${index + 1}. ${constraint}`).join("\n");
224
+ return `
225
+ <identity>
226
+ You are an expert SQL query generator, answering business questions with accurate queries.
227
+ Your tone should be concise and business-friendly.
228
+ </identity>
229
+
230
+ ${state?.userProfile || ""}
231
+
232
+ ${databaseSchemaPrompt(state)}
233
+
234
+ ${state?.teachings || ""}
235
+
236
+ <query_reasoning_strategy>
237
+ ${stepBackPrompt("general", {
238
+ examples: SQL_STEP_BACK_EXAMPLES,
239
+ stepBackQuestionTemplate: "What are the SQL patterns, database principles, and schema relationships needed to answer this question?"
240
+ })}
241
+
242
+ Skip Step-Back only if the question is a direct \u201CSELECT * FROM \u2026\u201D or a simple aggregation with a clear target.
243
+ </query_reasoning_strategy>
244
+
245
+ <constraints>
246
+ ${constraintsSection}
247
+ </constraints>
248
+ ${renderingGuidance.section}
249
+ `;
250
+ }
251
+ });
252
+ var text2sqlOnly = text2sqlAgent.clone({
253
+ tools: {},
254
+ output: z.object({
255
+ sql: z.string().describe("The SQL query generated to answer the user question.")
256
+ }),
257
+ prompt: (state) => {
258
+ return `
259
+ <identity>
260
+ You are an expert SQL query generator, answering business questions with accurate queries.
261
+ Your tone should be concise and business-friendly.
262
+ </identity>
263
+
264
+ ${databaseSchemaPrompt(state)}
265
+
266
+ <constraints>
267
+ 1. **Output**: Provide ONLY the SQL query. Do not include markdown formatting like \`\`\`sql ... \`\`\`.
268
+ 2. **Dialect**: Use standard SQL compatible with SQLite unless specified otherwise.
269
+ </constraints>
270
+ `;
271
+ }
272
+ });
273
+ var text2sqlMonolith = text2sqlAgent.clone({
274
+ model: groq("openai/gpt-oss-20b"),
275
+ // model: openai('gpt-5.1-codex'),
276
+ tools
277
+ });
278
+
279
+ // packages/text2sql/src/lib/sql.ts
280
+ import {
281
+ InvalidToolInputError,
282
+ NoSuchToolError,
283
+ ToolCallRepairError,
284
+ tool as tool3
285
+ } from "ai";
286
+ import dedent6 from "dedent";
287
+ import { v7 } from "uuid";
288
+ import z6 from "zod";
289
+ import { generate as generate2, pipe, stream, user as user2 } from "@deepagents/agent";
290
+
291
+ // packages/text2sql/src/lib/adapters/adapter.ts
292
+ var Adapter = class {
293
+ async resolveTables(filter) {
294
+ const allTables = await this.getTables();
295
+ const relationships = await this.getRelationships();
296
+ return getTablesWithRelated(allTables, relationships, filter);
297
+ }
298
+ };
299
+ function filterTablesByName(tables, filter) {
300
+ if (!filter) return tables;
301
+ return tables.filter((table) => matchesFilter(table.name, filter));
302
+ }
303
+ function filterRelationshipsByTables(relationships, tableNames) {
304
+ if (tableNames === void 0) {
305
+ return relationships;
306
+ }
307
+ if (tableNames.size === 0) {
308
+ return [];
309
+ }
310
+ return relationships.filter(
311
+ (it) => tableNames.has(it.table) || tableNames.has(it.referenced_table)
312
+ );
313
+ }
314
+ function applyTablesFilter(tables, relationships, filter) {
315
+ if (!filter) {
316
+ return { tables, relationships };
317
+ }
318
+ const allowedNames = new Set(
319
+ getTablesWithRelated(tables, relationships, filter)
320
+ );
321
+ return {
322
+ tables: tables.filter((table) => allowedNames.has(table.name)),
323
+ relationships: filterRelationshipsByTables(relationships, allowedNames)
324
+ };
325
+ }
326
+ function matchesFilter(tableName, filter) {
327
+ if (Array.isArray(filter)) {
328
+ return filter.includes(tableName);
329
+ }
330
+ return filter.test(tableName);
331
+ }
332
+ function getTablesWithRelated(allTables, relationships, filter) {
333
+ const matchedTables = filterTablesByName(allTables, filter).map(
334
+ (it) => it.name
335
+ );
336
+ if (matchedTables.length === 0) {
337
+ return [];
338
+ }
339
+ const adjacency = /* @__PURE__ */ new Map();
340
+ for (const rel of relationships) {
341
+ if (!adjacency.has(rel.table)) {
342
+ adjacency.set(rel.table, /* @__PURE__ */ new Set());
343
+ }
344
+ if (!adjacency.has(rel.referenced_table)) {
345
+ adjacency.set(rel.referenced_table, /* @__PURE__ */ new Set());
346
+ }
347
+ adjacency.get(rel.table).add(rel.referenced_table);
348
+ adjacency.get(rel.referenced_table).add(rel.table);
349
+ }
350
+ const result = new Set(matchedTables);
351
+ const queue = [...matchedTables];
352
+ while (queue.length > 0) {
353
+ const current = queue.shift();
354
+ const neighbors = adjacency.get(current);
355
+ if (!neighbors) {
356
+ continue;
357
+ }
358
+ for (const neighbor of neighbors) {
359
+ if (!result.has(neighbor)) {
360
+ result.add(neighbor);
361
+ queue.push(neighbor);
362
+ }
363
+ }
364
+ }
365
+ return Array.from(result);
366
+ }
367
+
368
+ // packages/text2sql/src/lib/adapters/sqlite.ts
369
+ var SQL_ERROR_MAP = [
370
+ {
371
+ pattern: /^no such table: .+$/,
372
+ type: "MISSING_TABLE",
373
+ hint: "Check the database schema for the correct table name. The table you referenced does not exist."
374
+ },
375
+ {
376
+ pattern: /^no such column: .+$/,
377
+ type: "INVALID_COLUMN",
378
+ hint: "Check the table schema for correct column names. The column may not exist or is ambiguous (exists in multiple joined tables)."
379
+ },
380
+ {
381
+ pattern: /^ambiguous column name: .+$/,
382
+ type: "INVALID_COLUMN",
383
+ hint: "Check the table schema for correct column names. The column may not exist or is ambiguous (exists in multiple joined tables)."
384
+ },
385
+ {
386
+ pattern: /^near ".+": syntax error$/,
387
+ type: "SYNTAX_ERROR",
388
+ hint: "There is a SQL syntax error. Review the query structure, keywords, and punctuation."
389
+ },
390
+ {
391
+ pattern: /^no tables specified$/,
392
+ type: "SYNTAX_ERROR",
393
+ hint: "There is a SQL syntax error. Review the query structure, keywords, and punctuation."
394
+ },
395
+ {
396
+ pattern: /^attempt to write a readonly database$/,
397
+ type: "CONSTRAINT_ERROR",
398
+ hint: "A database constraint was violated. This should not happen with read-only queries."
399
+ }
400
+ ];
401
+ var LOW_CARDINALITY_LIMIT = 20;
402
+ function formatError(sql, error) {
403
+ const errorMessage = error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error occurred";
404
+ const errorInfo = SQL_ERROR_MAP.find((it) => it.pattern.test(errorMessage));
405
+ if (!errorInfo) {
406
+ return {
407
+ error: errorMessage,
408
+ error_type: "UNKNOWN_ERROR",
409
+ suggestion: "Review the query and try again",
410
+ sql_attempted: sql
411
+ };
412
+ }
413
+ return {
414
+ error: errorMessage,
415
+ error_type: errorInfo.type,
416
+ suggestion: errorInfo.hint,
417
+ sql_attempted: sql
418
+ };
419
+ }
420
+ var Sqlite = class extends Adapter {
421
+ #options;
422
+ #introspection = null;
423
+ #info = null;
424
+ constructor(options) {
425
+ super();
426
+ if (!options || typeof options.execute !== "function") {
427
+ throw new Error("Sqlite adapter requires an execute function.");
428
+ }
429
+ this.#options = options;
430
+ }
431
+ async introspect(options) {
432
+ const onProgress = options?.onProgress;
433
+ if (this.#introspection) {
434
+ return this.#introspection;
435
+ }
436
+ if (this.#options.introspect) {
437
+ this.#introspection = await this.#options.introspect();
438
+ return this.#introspection;
439
+ }
440
+ const allTables = await this.#loadTables();
441
+ const allRelationships = await this.#loadRelationships(
442
+ allTables.map((t) => t.name)
443
+ );
444
+ const { tables, relationships } = this.#applyTablesFilter(
445
+ allTables,
446
+ allRelationships
447
+ );
448
+ onProgress?.({
449
+ phase: "tables",
450
+ message: `Loaded ${tables.length} tables`,
451
+ total: tables.length
452
+ });
453
+ onProgress?.({ phase: "row_counts", message: "Counting table rows..." });
454
+ await this.#annotateRowCounts(tables, onProgress);
455
+ onProgress?.({
456
+ phase: "column_stats",
457
+ message: "Collecting column statistics..."
458
+ });
459
+ await this.#annotateColumnStats(tables, onProgress);
460
+ onProgress?.({ phase: "indexes", message: "Loading index information..." });
461
+ await this.#annotateIndexes(tables, onProgress);
462
+ onProgress?.({
463
+ phase: "low_cardinality",
464
+ message: "Identifying low cardinality columns..."
465
+ });
466
+ await this.#annotateLowCardinalityColumns(tables, onProgress);
467
+ onProgress?.({
468
+ phase: "relationships",
469
+ message: "Loading foreign key relationships..."
470
+ });
471
+ this.#introspection = { tables, relationships };
472
+ return this.#introspection;
473
+ }
474
+ async execute(sql) {
475
+ return this.#options.execute(sql);
476
+ }
477
+ async validate(sql) {
478
+ const validator = this.#options.validate ?? (async (text) => {
479
+ await this.#options.execute(`EXPLAIN ${text}`);
480
+ });
481
+ try {
482
+ return await validator(sql);
483
+ } catch (error) {
484
+ return JSON.stringify(formatError(sql, error));
485
+ }
486
+ }
487
+ async info() {
488
+ if (this.#info) {
489
+ return this.#info;
490
+ }
491
+ this.#info = await this.#resolveInfo();
492
+ return this.#info;
493
+ }
494
+ formatInfo(info) {
495
+ const lines = [`Dialect: ${info.dialect ?? "unknown"}`];
496
+ if (info.version) {
497
+ lines.push(`Version: ${info.version}`);
498
+ }
499
+ if (info.database) {
500
+ lines.push(`Database: ${info.database}`);
501
+ }
502
+ if (info.host) {
503
+ lines.push(`Host: ${info.host}`);
504
+ }
505
+ if (info.details && Object.keys(info.details).length) {
506
+ lines.push(`Details: ${JSON.stringify(info.details)}`);
507
+ }
508
+ return lines.join("\n");
509
+ }
510
+ async getTables() {
511
+ const allTables = await this.#loadTables();
512
+ const allRelationships = await this.#loadRelationships(
513
+ allTables.map((t) => t.name)
514
+ );
515
+ return this.#applyTablesFilter(allTables, allRelationships).tables;
516
+ }
517
+ async #loadTables() {
518
+ const rows = await this.#runQuery(
519
+ `SELECT name FROM sqlite_master WHERE type='table' ORDER BY name`
520
+ );
521
+ const tableNames = rows.map((row) => row.name).filter(
522
+ (name) => typeof name === "string" && !name.startsWith("sqlite_")
523
+ );
524
+ const tables = await Promise.all(
525
+ tableNames.map(async (tableName) => {
526
+ const columns = await this.#runQuery(
527
+ `PRAGMA table_info(${this.#quoteIdentifier(tableName)})`
528
+ );
529
+ return {
530
+ name: tableName,
531
+ rawName: tableName,
532
+ columns: columns.map((col) => ({
533
+ name: col.name ?? "unknown",
534
+ type: col.type ?? "unknown",
535
+ isPrimaryKey: (col.pk ?? 0) > 0
536
+ }))
537
+ };
538
+ })
539
+ );
540
+ return tables;
541
+ }
542
+ async getRelationships() {
543
+ const allTables = await this.#loadTables();
544
+ const allRelationships = await this.#loadRelationships(
545
+ allTables.map((t) => t.name)
546
+ );
547
+ return this.#applyTablesFilter(allTables, allRelationships).relationships;
548
+ }
549
+ async #loadRelationships(tableNames) {
550
+ const names = tableNames ?? (await this.#loadTables()).map((table) => table.name);
551
+ const relationshipGroups = await Promise.all(
552
+ names.map(async (tableName) => {
553
+ const rows = await this.#runQuery(
554
+ `PRAGMA foreign_key_list(${this.#quoteIdentifier(tableName)})`
555
+ );
556
+ const groups = /* @__PURE__ */ new Map();
557
+ for (const row of rows) {
558
+ if (row.id == null || row.table == null || row.from == null || row.to == null) {
559
+ continue;
560
+ }
561
+ const id = Number(row.id);
562
+ const existing = groups.get(id);
563
+ if (!existing) {
564
+ groups.set(id, {
565
+ table: tableName,
566
+ from: [String(row.from)],
567
+ referenced_table: String(row.table),
568
+ to: [String(row.to)]
569
+ });
570
+ } else {
571
+ existing.from.push(String(row.from));
572
+ existing.to.push(String(row.to));
573
+ }
574
+ }
575
+ return Array.from(groups.values());
576
+ })
577
+ );
578
+ return relationshipGroups.flat();
579
+ }
580
+ async #annotateRowCounts(tables, onProgress) {
581
+ const total = tables.length;
582
+ for (let i = 0; i < tables.length; i++) {
583
+ const table = tables[i];
584
+ const tableIdentifier = this.#formatTableIdentifier(table);
585
+ onProgress?.({
586
+ phase: "row_counts",
587
+ message: `Counting rows in ${table.name}...`,
588
+ current: i + 1,
589
+ total
590
+ });
591
+ try {
592
+ const rows = await this.#runQuery(
593
+ `SELECT COUNT(*) as count FROM ${tableIdentifier}`
594
+ );
595
+ const rowCount = this.#toNumber(rows[0]?.count);
596
+ if (rowCount != null) {
597
+ table.rowCount = rowCount;
598
+ table.sizeHint = this.#classifyRowCount(rowCount);
599
+ }
600
+ } catch {
601
+ continue;
602
+ }
603
+ }
604
+ }
605
+ async #annotateColumnStats(tables, onProgress) {
606
+ const total = tables.length;
607
+ for (let i = 0; i < tables.length; i++) {
608
+ const table = tables[i];
609
+ const tableIdentifier = this.#formatTableIdentifier(table);
610
+ onProgress?.({
611
+ phase: "column_stats",
612
+ message: `Collecting stats for ${table.name}...`,
613
+ current: i + 1,
614
+ total
615
+ });
616
+ for (const column of table.columns) {
617
+ if (!this.#shouldCollectStats(column.type)) {
618
+ continue;
619
+ }
620
+ const columnIdentifier = this.#quoteSqlIdentifier(column.name);
621
+ const sql = `
622
+ SELECT
623
+ MIN(${columnIdentifier}) AS min_value,
624
+ MAX(${columnIdentifier}) AS max_value,
625
+ AVG(CASE WHEN ${columnIdentifier} IS NULL THEN 1.0 ELSE 0.0 END) AS null_fraction
626
+ FROM ${tableIdentifier}
627
+ `;
628
+ try {
629
+ const rows = await this.#runQuery(sql);
630
+ if (!rows.length) {
631
+ continue;
632
+ }
633
+ const min = this.#normalizeValue(rows[0]?.min_value);
634
+ const max = this.#normalizeValue(rows[0]?.max_value);
635
+ const nullFraction = this.#toNumber(rows[0]?.null_fraction);
636
+ if (min != null || max != null || nullFraction != null) {
637
+ column.stats = {
638
+ min: min ?? void 0,
639
+ max: max ?? void 0,
640
+ nullFraction: nullFraction != null && Number.isFinite(nullFraction) ? Math.max(0, Math.min(1, nullFraction)) : void 0
641
+ };
642
+ }
643
+ } catch {
644
+ continue;
645
+ }
646
+ }
647
+ }
648
+ }
649
+ async #annotateIndexes(tables, onProgress) {
650
+ const total = tables.length;
651
+ for (let i = 0; i < tables.length; i++) {
652
+ const table = tables[i];
653
+ const tableIdentifier = this.#quoteIdentifier(
654
+ table.rawName ?? table.name
655
+ );
656
+ onProgress?.({
657
+ phase: "indexes",
658
+ message: `Loading indexes for ${table.name}...`,
659
+ current: i + 1,
660
+ total
661
+ });
662
+ let indexes = [];
663
+ try {
664
+ const indexList = await this.#runQuery(
665
+ `PRAGMA index_list(${tableIdentifier})`
666
+ );
667
+ indexes = await Promise.all(
668
+ indexList.filter((index) => index.name).map(async (index) => {
669
+ const indexName = String(index.name);
670
+ const indexInfo = await this.#runQuery(
671
+ `PRAGMA index_info('${indexName.replace(/'/g, "''")}')`
672
+ );
673
+ const columns = indexInfo.map((col) => col.name).filter((name) => Boolean(name));
674
+ for (const columnName of columns) {
675
+ const column = table.columns.find(
676
+ (col) => col.name === columnName
677
+ );
678
+ if (column) {
679
+ column.isIndexed = true;
680
+ }
681
+ }
682
+ return {
683
+ name: indexName,
684
+ columns,
685
+ unique: index.unique === 1,
686
+ primary: index.origin === "pk",
687
+ type: index.origin ?? void 0
688
+ };
689
+ })
690
+ );
691
+ } catch {
692
+ indexes = [];
693
+ }
694
+ if (indexes.length) {
695
+ table.indexes = indexes;
696
+ }
697
+ }
698
+ }
699
+ async #annotateLowCardinalityColumns(tables, onProgress) {
700
+ const total = tables.length;
701
+ for (let i = 0; i < tables.length; i++) {
702
+ const table = tables[i];
703
+ const tableIdentifier = this.#formatTableIdentifier(table);
704
+ onProgress?.({
705
+ phase: "low_cardinality",
706
+ message: `Analyzing cardinality in ${table.name}...`,
707
+ current: i + 1,
708
+ total
709
+ });
710
+ for (const column of table.columns) {
711
+ const columnIdentifier = this.#quoteSqlIdentifier(column.name);
712
+ const limit = LOW_CARDINALITY_LIMIT + 1;
713
+ const sql = `
714
+ SELECT DISTINCT ${columnIdentifier} AS value
715
+ FROM ${tableIdentifier}
716
+ WHERE ${columnIdentifier} IS NOT NULL
717
+ LIMIT ${limit}
718
+ `;
719
+ let rows = [];
720
+ try {
721
+ rows = await this.#runQuery(sql);
722
+ } catch {
723
+ continue;
724
+ }
725
+ if (!rows.length || rows.length > LOW_CARDINALITY_LIMIT) {
726
+ continue;
727
+ }
728
+ const values = [];
729
+ let shouldSkip = false;
730
+ for (const row of rows) {
731
+ const formatted = this.#normalizeValue(row.value);
732
+ if (formatted == null) {
733
+ shouldSkip = true;
734
+ break;
735
+ }
736
+ values.push(formatted);
737
+ }
738
+ if (shouldSkip || !values.length) {
739
+ continue;
740
+ }
741
+ column.kind = "LowCardinality";
742
+ column.values = values;
743
+ }
744
+ }
745
+ }
746
+ #quoteIdentifier(name) {
747
+ return `'${name.replace(/'/g, "''")}'`;
748
+ }
749
+ #quoteSqlIdentifier(identifier) {
750
+ return `"${identifier.replace(/"/g, '""')}"`;
751
+ }
752
+ #applyTablesFilter(tables, relationships) {
753
+ return applyTablesFilter(tables, relationships, this.#options.tables);
754
+ }
755
+ #formatTableIdentifier(table) {
756
+ const name = table.rawName ?? table.name;
757
+ if (table.schema) {
758
+ return `${this.#quoteSqlIdentifier(table.schema)}.${this.#quoteSqlIdentifier(name)}`;
759
+ }
760
+ return this.#quoteSqlIdentifier(name);
761
+ }
762
+ #toNumber(value) {
763
+ if (typeof value === "number" && Number.isFinite(value)) {
764
+ return value;
765
+ }
766
+ if (typeof value === "bigint") {
767
+ return Number(value);
768
+ }
769
+ if (typeof value === "string" && value.trim() !== "") {
770
+ const parsed = Number(value);
771
+ return Number.isFinite(parsed) ? parsed : null;
772
+ }
773
+ return null;
774
+ }
775
+ #classifyRowCount(count) {
776
+ if (count < 100) {
777
+ return "tiny";
778
+ }
779
+ if (count < 1e3) {
780
+ return "small";
781
+ }
782
+ if (count < 1e4) {
783
+ return "medium";
784
+ }
785
+ if (count < 1e5) {
786
+ return "large";
787
+ }
788
+ return "huge";
789
+ }
790
+ #shouldCollectStats(type) {
791
+ if (!type) {
792
+ return false;
793
+ }
794
+ const normalized = type.toLowerCase();
795
+ return /int|real|numeric|double|float|decimal|date|time|bool/.test(
796
+ normalized
797
+ );
798
+ }
799
+ #normalizeValue(value) {
800
+ if (value === null || value === void 0) {
801
+ return null;
802
+ }
803
+ if (typeof value === "string") {
804
+ return value;
805
+ }
806
+ if (typeof value === "number" || typeof value === "bigint") {
807
+ return String(value);
808
+ }
809
+ if (typeof value === "boolean") {
810
+ return value ? "true" : "false";
811
+ }
812
+ if (value instanceof Date) {
813
+ return value.toISOString();
814
+ }
815
+ if (typeof Buffer !== "undefined" && Buffer.isBuffer(value)) {
816
+ return value.toString("utf-8");
817
+ }
818
+ return null;
819
+ }
820
+ async #runQuery(sql) {
821
+ const result = await this.#options.execute(sql);
822
+ if (Array.isArray(result)) {
823
+ return result;
824
+ }
825
+ if (result && typeof result === "object" && "rows" in result && Array.isArray(result.rows)) {
826
+ return result.rows;
827
+ }
828
+ throw new Error(
829
+ "Sqlite adapter execute() must return an array of rows or an object with a rows array when introspecting."
830
+ );
831
+ }
832
+ async #resolveInfo() {
833
+ const { info } = this.#options;
834
+ if (!info) {
835
+ return { dialect: "sqlite" };
836
+ }
837
+ if (typeof info === "function") {
838
+ return info();
839
+ }
840
+ return info;
841
+ }
842
+ };
843
+
844
+ // packages/text2sql/src/lib/agents/brief.agent.ts
845
+ import { groq as groq2 } from "@ai-sdk/groq";
846
+ import { createUIMessageStream, tool as tool2 } from "ai";
847
+ import dedent from "dedent";
848
+ import { createHash } from "node:crypto";
849
+ import { existsSync } from "node:fs";
850
+ import { readFile, writeFile } from "node:fs/promises";
851
+ import { tmpdir } from "node:os";
852
+ import path from "node:path";
853
+ import z2 from "zod";
854
+ import {
855
+ agent as agent2,
856
+ generate,
857
+ toState as toState2,
858
+ user
859
+ } from "@deepagents/agent";
860
+ var BriefCache = class {
861
+ path;
862
+ constructor(watermark) {
863
+ const hash = createHash("md5").update(watermark).digest("hex");
864
+ this.path = path.join(tmpdir(), `db-brief-${hash}.txt`);
865
+ }
866
+ async get() {
867
+ if (existsSync(this.path)) {
868
+ return readFile(this.path, "utf-8");
869
+ }
870
+ return null;
871
+ }
872
+ set(brief) {
873
+ return writeFile(this.path, brief, "utf-8");
874
+ }
875
+ };
876
+ var briefAgent = agent2({
877
+ name: "db-brief-agent",
878
+ model: groq2("openai/gpt-oss-20b"),
879
+ prompt: (state) => dedent`
880
+ <identity>
881
+ You are a database analyst expert. Your job is to understand what a database represents and provide business context about it.
882
+ You have READ-ONLY access to the database.
883
+ </identity>
884
+
885
+ ${databaseSchemaPrompt(state)}
886
+
887
+ <instructions>
888
+ Write a business context that helps another agent answer questions accurately.
889
+
890
+ For EACH table, do queries ONE AT A TIME:
891
+ 1. SELECT COUNT(*) to get row count
892
+ 2. SELECT * LIMIT 3 to see sample data
893
+
894
+ Then write a report with:
895
+ - What business this database is for
896
+ - For each table: purpose, row count, and example of what the data looks like
897
+
898
+ Include concrete examples like "Track prices are $0.99", "Customer names like 'Lu\u00eds Gon\u00e7alves'", etc.
899
+
900
+ Keep it 400-600 words, conversational style.
901
+ </instructions>
902
+ `,
903
+ tools: {
904
+ query_database: tool2({
905
+ description: "Execute a SELECT query to explore the database and gather insights.",
906
+ inputSchema: z2.object({
907
+ sql: z2.string().describe("The SELECT query to execute"),
908
+ purpose: z2.string().describe("What insight you are trying to gather with this query")
909
+ }),
910
+ execute: ({ sql }, options) => {
911
+ const state = toState2(options);
912
+ return state.execute(sql);
913
+ }
914
+ })
915
+ }
916
+ });
917
+ async function runAndCache(introspection, cache) {
918
+ const { text } = await generate(
919
+ briefAgent,
920
+ [
921
+ user(
922
+ "Please analyze the database and write a contextual report about what this database represents."
923
+ )
924
+ ],
925
+ { introspection }
926
+ );
927
+ await cache.set(text);
928
+ return text;
929
+ }
930
+ async function generateBrief(introspection, cache) {
931
+ const brief = await cache.get();
932
+ if (!brief) {
933
+ return runAndCache(introspection, cache);
934
+ }
935
+ return brief;
936
+ }
937
+ function toBrief(forceRefresh = false) {
938
+ return (state, setState) => {
939
+ return createUIMessageStream({
940
+ execute: async ({ writer }) => {
941
+ if (forceRefresh) {
942
+ const brief = await runAndCache(state.introspection, state.cache);
943
+ writer.write({
944
+ type: "data-brief-agent",
945
+ data: {
946
+ cache: "forced",
947
+ brief
948
+ }
949
+ });
950
+ setState({ context: brief });
951
+ } else {
952
+ let brief = await state.cache.get();
953
+ if (!brief) {
954
+ writer.write({
955
+ type: "data-brief-agent",
956
+ data: {
957
+ cache: "miss"
958
+ }
959
+ });
960
+ brief = await runAndCache(state.introspection, state.cache);
961
+ writer.write({
962
+ type: "data-brief-agent",
963
+ data: {
964
+ cache: "new",
965
+ brief
966
+ }
967
+ });
968
+ } else {
969
+ writer.write({
970
+ type: "data-brief-agent",
971
+ data: {
972
+ cache: "hit",
973
+ brief
974
+ }
975
+ });
976
+ }
977
+ }
978
+ }
979
+ });
980
+ };
981
+ }
982
+
983
+ // packages/text2sql/src/lib/agents/explainer.agent.ts
984
+ import { groq as groq3 } from "@ai-sdk/groq";
985
+ import dedent2 from "dedent";
986
+ import z3 from "zod";
987
+ import { agent as agent3 } from "@deepagents/agent";
988
+ var explainerAgent = agent3({
989
+ name: "explainer",
990
+ model: groq3("openai/gpt-oss-20b"),
991
+ prompt: (state) => dedent2`
992
+ You are an expert SQL tutor.
993
+ Explain the following SQL query in plain English to a non-technical user.
994
+ Focus on the intent and logic, not the syntax.
995
+
996
+ <sql>
997
+ ${state?.sql}
998
+ </sql>
999
+ `,
1000
+ output: z3.object({
1001
+ explanation: z3.string().describe("The explanation of the SQL query.")
1002
+ })
1003
+ });
1004
+
1005
+ // packages/text2sql/src/lib/agents/suggestions.agents.ts
1006
+ import { groq as groq4 } from "@ai-sdk/groq";
1007
+ import dedent3 from "dedent";
1008
+ import z4 from "zod";
1009
+ import { agent as agent4, thirdPersonPrompt } from "@deepagents/agent";
1010
+ var suggestionsAgent = agent4({
1011
+ name: "text2sql-suggestions",
1012
+ model: groq4("openai/gpt-oss-20b"),
1013
+ output: z4.object({
1014
+ suggestions: z4.array(
1015
+ z4.object({
1016
+ question: z4.string().describe("A complex, high-impact business question."),
1017
+ sql: z4.string().describe("The SQL statement needed to answer the question."),
1018
+ businessValue: z4.string().describe("Why the question matters to stakeholders.")
1019
+ })
1020
+ ).min(1).max(5).describe("A set of up to two advanced question + SQL pairs.")
1021
+ }),
1022
+ prompt: (state) => {
1023
+ return dedent3`
1024
+ ${thirdPersonPrompt()}
1025
+
1026
+ <identity>
1027
+ You are a senior analytics strategist who proposes ambitious business questions
1028
+ and drafts the SQL needed to answer them. You specialize in identifying ideas
1029
+ that combine multiple tables, apply segmentation or time analysis, and surface
1030
+ metrics that drive executive decisions.
1031
+ </identity>
1032
+
1033
+ ${databaseSchemaPrompt(state)}
1034
+
1035
+ <instructions>
1036
+ - Recommend one or two UNIQUE questions that go beyond simple counts or listings.
1037
+ - Favor questions that require joins, aggregates, time comparisons, cohort analysis,
1038
+ or window functions.
1039
+ - For each question, explain the business reason stakeholders care about it.
1040
+ - Provide the complete SQL query that could answer the question in the given schema.
1041
+ - Keep result sets scoped with LIMIT clauses (max 50 rows) when returning raw rows.
1042
+ - Ensure table/column names match the provided schema exactly.
1043
+ - Use columns marked [LowCardinality: ...] to identify meaningful categorical filters or segmentations.
1044
+ - Leverage table [rows / size] hints to determine whether to aggregate (large tables) or inspect detailed data (tiny tables).
1045
+ - Reference PK/Indexed annotations and the Indexes list to recommend queries that use efficient join/filter paths.
1046
+ - Column annotations may expose ranges/null percentages—use them to suggest realistic thresholds or quality checks.
1047
+ - Consult <relationship_examples> to anchor your recommendations in the actual join paths between tables.
1048
+ - Output only information grounded in the schema/context provided.
1049
+ </instructions>
1050
+
1051
+ <response-format>
1052
+ Return valid JSON that satisfies the defined output schema.
1053
+ </response-format>
1054
+ `;
1055
+ }
1056
+ });
1057
+
1058
+ // packages/text2sql/src/lib/agents/synthesizer.agent.ts
1059
+ import { groq as groq5 } from "@ai-sdk/groq";
1060
+ import dedent4 from "dedent";
1061
+ import { agent as agent5 } from "@deepagents/agent";
1062
+ var synthesizerAgent = agent5({
1063
+ name: "synthesizer_agent",
1064
+ model: groq5("openai/gpt-oss-20b"),
1065
+ handoffDescription: "Use this tool to synthesizes the final user-facing response. This agent understands how the user interface works and can tailor the response accordingly.",
1066
+ prompt: (state) => {
1067
+ const contextInfo = state?.context ?? "No additional context provided.";
1068
+ return dedent4`
1069
+ <identity>
1070
+ You are a data insights companion helping users understand information using clear, everyday language.
1071
+ You communicate in a friendly, conversational manner.
1072
+ You only see the user's question and the results from internal systems. You do not know how those results were produced, so never reference technical systems or implementation details.
1073
+ </identity>
1074
+
1075
+ <context>
1076
+ ${contextInfo}
1077
+ </context>
1078
+
1079
+ <response-strategy>
1080
+ 1. Re-read the user's question, then inspect the <data> provided to understand what it represents.
1081
+ 2. Translate technical field names into friendly descriptions based on the domain context.
1082
+ 3. Explain the core insight in 2-4 sentences focused on what the data reveals.
1083
+ 4. When multiple records are present, highlight only the most relevant ones (max 5) with comparisons or rankings.
1084
+ 5. If data is empty or contains an error, state that plainly and suggest what to clarify or try next.
1085
+ 6. Close with an optional follow-up recommendation or next step based on the insights.
1086
+ </response-strategy>
1087
+
1088
+ <guardrails>
1089
+ - Never mention technical implementation details, data structures, or internal systems.
1090
+ - Keep tone casual, confident, and insight-driven; do not narrate your process.
1091
+ - Base every statement strictly on the <data> provided plus the context - no speculation.
1092
+ </guardrails>
1093
+ `;
1094
+ }
1095
+ });
1096
+
1097
+ // packages/text2sql/src/lib/agents/teachables.agent.ts
1098
+ import { groq as groq6 } from "@ai-sdk/groq";
1099
+ import dedent5 from "dedent";
1100
+ import z5 from "zod";
1101
+ import { agent as agent6, thirdPersonPrompt as thirdPersonPrompt2 } from "@deepagents/agent";
1102
+
1103
+ // packages/text2sql/src/lib/teach/xml.ts
1104
+ function wrapBlock(tag, children) {
1105
+ const content = children.filter((child) => Boolean(child)).join("\n");
1106
+ if (!content) {
1107
+ return "";
1108
+ }
1109
+ return `<${tag}>
1110
+ ${indentBlock(content, 2)}
1111
+ </${tag}>`;
1112
+ }
1113
+ function list(tag, values, childTag) {
1114
+ if (!values.length) {
1115
+ return "";
1116
+ }
1117
+ const children = values.map((value) => leaf(childTag, value)).join("\n");
1118
+ return `<${tag}>
1119
+ ${indentBlock(children, 2)}
1120
+ </${tag}>`;
1121
+ }
1122
+ function leaf(tag, value) {
1123
+ const safe = escapeXml(value);
1124
+ if (safe.includes("\n")) {
1125
+ return `<${tag}>
1126
+ ${indentBlock(safe, 2)}
1127
+ </${tag}>`;
1128
+ }
1129
+ return `<${tag}>${safe}</${tag}>`;
1130
+ }
1131
+ function indentBlock(text, spaces) {
1132
+ if (!text.trim()) {
1133
+ return "";
1134
+ }
1135
+ const padding = " ".repeat(spaces);
1136
+ return text.split("\n").map((line) => line.length ? padding + line : padding).join("\n");
1137
+ }
1138
+ function escapeXml(value) {
1139
+ return value.replaceAll(/&/g, "&amp;").replaceAll(/</g, "&lt;").replaceAll(/>/g, "&gt;").replaceAll(/"/g, "&quot;").replaceAll(/'/g, "&apos;");
1140
+ }
1141
+
1142
+ // packages/text2sql/src/lib/teach/teachables.ts
1143
+ function term(name, definition) {
1144
+ return {
1145
+ type: "term",
1146
+ format: () => wrapBlock("term", [leaf("name", name), leaf("definition", definition)])
1147
+ };
1148
+ }
1149
+ function hint(text) {
1150
+ return {
1151
+ type: "hint",
1152
+ format: () => leaf("hint", text)
1153
+ };
1154
+ }
1155
+ function guardrail(input) {
1156
+ const { rule, reason, action } = input;
1157
+ return {
1158
+ type: "guardrail",
1159
+ format: () => wrapBlock("guardrail", [
1160
+ leaf("rule", rule),
1161
+ reason ? leaf("reason", reason) : "",
1162
+ action ? leaf("action", action) : ""
1163
+ ])
1164
+ };
1165
+ }
1166
+ function explain(input) {
1167
+ const { concept, explanation, therefore } = input;
1168
+ return {
1169
+ type: "explain",
1170
+ format: () => wrapBlock("explanation", [
1171
+ leaf("concept", concept),
1172
+ leaf("details", explanation),
1173
+ therefore ? leaf("therefore", therefore) : ""
1174
+ ])
1175
+ };
1176
+ }
1177
+ function example(input) {
1178
+ const { question, sql, note } = input;
1179
+ return {
1180
+ type: "example",
1181
+ format: () => wrapBlock("example", [
1182
+ leaf("question", question),
1183
+ leaf("sql", sql),
1184
+ note ? leaf("note", note) : ""
1185
+ ])
1186
+ };
1187
+ }
1188
+ function clarification(input) {
1189
+ const { when, ask, reason } = input;
1190
+ return {
1191
+ type: "clarification",
1192
+ format: () => wrapBlock("clarification", [
1193
+ leaf("when", when),
1194
+ leaf("ask", ask),
1195
+ leaf("reason", reason)
1196
+ ])
1197
+ };
1198
+ }
1199
+ function workflow(input) {
1200
+ const { task, steps, triggers, notes } = input;
1201
+ return {
1202
+ type: "workflow",
1203
+ format: () => wrapBlock("workflow", [
1204
+ leaf("task", task),
1205
+ triggers?.length ? list("triggers", triggers, "trigger") : "",
1206
+ list("steps", steps, "step"),
1207
+ notes ? leaf("notes", notes) : ""
1208
+ ])
1209
+ };
1210
+ }
1211
+ function quirk(input) {
1212
+ const { issue, workaround } = input;
1213
+ return {
1214
+ type: "quirk",
1215
+ format: () => wrapBlock("quirk", [
1216
+ leaf("issue", issue),
1217
+ leaf("workaround", workaround)
1218
+ ])
1219
+ };
1220
+ }
1221
+ function styleGuide(input) {
1222
+ const { prefer, never, always } = input;
1223
+ return {
1224
+ type: "styleGuide",
1225
+ format: () => wrapBlock("style_guide", [
1226
+ leaf("prefer", prefer),
1227
+ always ? leaf("always", always) : "",
1228
+ never ? leaf("never", never) : ""
1229
+ ])
1230
+ };
1231
+ }
1232
+ function analogy(input) {
1233
+ const { concept, relationship, insight, therefore, pitfall } = input;
1234
+ return {
1235
+ type: "analogy",
1236
+ format: () => wrapBlock("analogy", [
1237
+ list("concepts", concept, "concept"),
1238
+ leaf("relationship", relationship),
1239
+ insight ? leaf("insight", insight) : "",
1240
+ therefore ? leaf("therefore", therefore) : "",
1241
+ pitfall ? leaf("pitfall", pitfall) : ""
1242
+ ])
1243
+ };
1244
+ }
1245
+ function toInstructions(...teachables) {
1246
+ if (!teachables.length) {
1247
+ return "";
1248
+ }
1249
+ const grouped = /* @__PURE__ */ new Map();
1250
+ for (const teachable of teachables) {
1251
+ const existing = grouped.get(teachable.type) ?? [];
1252
+ existing.push(teachable);
1253
+ grouped.set(teachable.type, existing);
1254
+ }
1255
+ const sections = SECTION_ORDER.map(({ type, tag }) => {
1256
+ const items = grouped.get(type);
1257
+ if (!items?.length) {
1258
+ return "";
1259
+ }
1260
+ const renderedItems = items.map((item) => item.format().trim()).filter(Boolean).map((item) => indentBlock(item, 2)).join("\n");
1261
+ if (!renderedItems.length) {
1262
+ return "";
1263
+ }
1264
+ return `<${tag}>
1265
+ ${renderedItems}
1266
+ </${tag}>`;
1267
+ }).filter((section) => Boolean(section));
1268
+ if (!sections.length) {
1269
+ return "";
1270
+ }
1271
+ const content = indentBlock(sections.join("\n"), 2);
1272
+ return `<teachings>
1273
+ ${content}
1274
+ </teachings>`;
1275
+ }
1276
+ var SECTION_ORDER = [
1277
+ { type: "guardrail", tag: "guardrails" },
1278
+ { type: "styleGuide", tag: "style_guides" },
1279
+ { type: "hint", tag: "hints" },
1280
+ { type: "clarification", tag: "clarifications" },
1281
+ { type: "workflow", tag: "workflows" },
1282
+ { type: "quirk", tag: "quirks" },
1283
+ { type: "term", tag: "terminology" },
1284
+ { type: "explain", tag: "explanations" },
1285
+ { type: "analogy", tag: "analogies" },
1286
+ { type: "example", tag: "examples" }
1287
+ ];
1288
+ function toTeachables(generated) {
1289
+ return generated.map((item) => {
1290
+ switch (item.type) {
1291
+ case "term":
1292
+ return term(item.name, item.definition);
1293
+ case "hint":
1294
+ return hint(item.text);
1295
+ case "guardrail":
1296
+ return guardrail({
1297
+ rule: item.rule,
1298
+ reason: item.reason,
1299
+ action: item.action
1300
+ });
1301
+ case "explain":
1302
+ return explain({
1303
+ concept: item.concept,
1304
+ explanation: item.explanation,
1305
+ therefore: item.therefore
1306
+ });
1307
+ case "example":
1308
+ return example({
1309
+ question: item.question,
1310
+ sql: item.sql,
1311
+ note: item.note
1312
+ });
1313
+ case "clarification":
1314
+ return clarification({
1315
+ when: item.when,
1316
+ ask: item.ask,
1317
+ reason: item.reason
1318
+ });
1319
+ case "workflow":
1320
+ return workflow({
1321
+ task: item.task,
1322
+ steps: item.steps,
1323
+ triggers: item.triggers,
1324
+ notes: item.notes
1325
+ });
1326
+ case "quirk":
1327
+ return quirk({
1328
+ issue: item.issue,
1329
+ workaround: item.workaround
1330
+ });
1331
+ case "styleGuide":
1332
+ return styleGuide({
1333
+ prefer: item.prefer,
1334
+ never: item.never,
1335
+ always: item.always
1336
+ });
1337
+ case "analogy":
1338
+ return analogy({
1339
+ concept: item.concept,
1340
+ relationship: item.relationship,
1341
+ insight: item.insight,
1342
+ therefore: item.therefore,
1343
+ pitfall: item.pitfall
1344
+ });
1345
+ }
1346
+ });
1347
+ }
1348
+ function userProfile(input) {
1349
+ return {
1350
+ type: "user_profile",
1351
+ format: () => {
1352
+ return "";
1353
+ }
1354
+ };
1355
+ }
1356
+
1357
+ // packages/text2sql/src/lib/agents/teachables.agent.ts
1358
+ var teachableSchema = z5.discriminatedUnion("type", [
1359
+ z5.object({
1360
+ type: z5.literal("term"),
1361
+ name: z5.string(),
1362
+ definition: z5.string()
1363
+ }),
1364
+ z5.object({
1365
+ type: z5.literal("hint"),
1366
+ text: z5.string()
1367
+ }),
1368
+ z5.object({
1369
+ type: z5.literal("guardrail"),
1370
+ rule: z5.string(),
1371
+ reason: z5.string().optional(),
1372
+ action: z5.string().optional()
1373
+ }),
1374
+ z5.object({
1375
+ type: z5.literal("explain"),
1376
+ concept: z5.string(),
1377
+ explanation: z5.string(),
1378
+ therefore: z5.string().optional()
1379
+ }),
1380
+ z5.object({
1381
+ type: z5.literal("example"),
1382
+ question: z5.string(),
1383
+ sql: z5.string(),
1384
+ note: z5.string().optional()
1385
+ }),
1386
+ z5.object({
1387
+ type: z5.literal("clarification"),
1388
+ when: z5.string(),
1389
+ ask: z5.string(),
1390
+ reason: z5.string()
1391
+ }),
1392
+ z5.object({
1393
+ type: z5.literal("workflow"),
1394
+ task: z5.string(),
1395
+ steps: z5.array(z5.string()).min(2),
1396
+ triggers: z5.array(z5.string()).optional(),
1397
+ notes: z5.string().optional()
1398
+ }),
1399
+ z5.object({
1400
+ type: z5.literal("quirk"),
1401
+ issue: z5.string(),
1402
+ workaround: z5.string()
1403
+ }),
1404
+ z5.object({
1405
+ type: z5.literal("styleGuide"),
1406
+ prefer: z5.string(),
1407
+ never: z5.string().optional(),
1408
+ always: z5.string().optional()
1409
+ }),
1410
+ z5.object({
1411
+ type: z5.literal("analogy"),
1412
+ concept: z5.array(z5.string()).min(2),
1413
+ relationship: z5.string(),
1414
+ insight: z5.string().optional(),
1415
+ therefore: z5.string().optional(),
1416
+ pitfall: z5.string().optional()
1417
+ })
1418
+ ]);
1419
+ var teachablesAuthorAgent = agent6({
1420
+ name: "teachables-author",
1421
+ model: groq6("openai/gpt-oss-20b"),
1422
+ output: z5.object({
1423
+ teachables: z5.array(teachableSchema).min(3).max(10).describe(
1424
+ "A concise, high-value set of teachables grounded in the provided schema."
1425
+ )
1426
+ }),
1427
+ prompt: (state) => dedent5`
1428
+ ${thirdPersonPrompt2()}
1429
+
1430
+ <identity>
1431
+ You design "teachables" for a Text2SQL system. Teachables become structured XML instructions.
1432
+ Choose only high-impact items that improve accuracy, safety, or clarity for this database.
1433
+ </identity>
1434
+
1435
+ ${databaseSchemaPrompt(state)}
1436
+
1437
+ <teachables_catalog>
1438
+ term: name + definition for domain vocabulary.
1439
+ hint: behavioral rule/constraint to apply by default.
1440
+ guardrail: hard safety/performance boundary with action and optional reason.
1441
+ explain: deeper concept metaphor/explanation (+ optional therefore).
1442
+ example: question + SQL (+ optional note).
1443
+ clarification: when/ask/reason to prompt the user before querying.
1444
+ workflow: task + ordered steps (+ optional triggers/notes).
1445
+ quirk: data edge case with workaround.
1446
+ styleGuide: prefer/never/always guidance for SQL output.
1447
+ analogy: comparison of two concepts with relationship (+ optional insight/therefore/pitfall).
1448
+ </teachables_catalog>
1449
+
1450
+ <instructions>
1451
+ - Ground everything in the provided schema/context; do not invent tables/columns.
1452
+ - Prefer guardrails + clarifications for performance, safety, and ambiguity.
1453
+ - Use examples only when a clear, schema-valid pattern is evident.
1454
+ - Keep the set lean (3-10 items) and non-duplicative; combine overlapping ideas.
1455
+ - Return JSON that satisfies the output schema; do not wrap in prose.
1456
+ </instructions>
1457
+ `
1458
+ });
1459
+
1460
+ // packages/text2sql/src/lib/history/history.ts
1461
+ var History = class {
1462
+ };
1463
+
1464
+ // packages/text2sql/src/lib/memory/user-profile.ts
1465
+ import { existsSync as existsSync2 } from "node:fs";
1466
+ import { readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
1467
+ import { tmpdir as tmpdir2 } from "node:os";
1468
+ import path2 from "node:path";
1469
+ var UserProfileStore = class {
1470
+ constructor(userId) {
1471
+ this.userId = userId;
1472
+ const safeUserId = userId.replace(/[^a-z0-9]/gi, "_").toLowerCase();
1473
+ this.path = path2.join(tmpdir2(), `user-profile-${safeUserId}.json`);
1474
+ }
1475
+ path;
1476
+ /**
1477
+ * Retrieve the full user profile data.
1478
+ */
1479
+ async get() {
1480
+ if (existsSync2(this.path)) {
1481
+ try {
1482
+ const content = await readFile2(this.path, "utf-8");
1483
+ return JSON.parse(content);
1484
+ } catch (error) {
1485
+ console.error("Failed to read user profile:", error);
1486
+ }
1487
+ }
1488
+ return {
1489
+ items: [],
1490
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
1491
+ };
1492
+ }
1493
+ /**
1494
+ * Save the user profile data.
1495
+ */
1496
+ async save(data) {
1497
+ data.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
1498
+ await writeFile2(this.path, JSON.stringify(data, null, 2), "utf-8");
1499
+ }
1500
+ /**
1501
+ * Add an item to the profile.
1502
+ */
1503
+ async add(type, text) {
1504
+ const data = await this.get();
1505
+ const exists = data.items.some(
1506
+ (item) => item.type === type && item.text === text
1507
+ );
1508
+ if (!exists) {
1509
+ data.items.push({ type, text });
1510
+ await this.save(data);
1511
+ }
1512
+ }
1513
+ /**
1514
+ * Remove a specific item from the profile.
1515
+ */
1516
+ async remove(type, text) {
1517
+ const data = await this.get();
1518
+ const filtered = data.items.filter((item) => {
1519
+ return !(item.type === type && item.text === text);
1520
+ });
1521
+ await this.save({ ...data, items: filtered });
1522
+ }
1523
+ /**
1524
+ * Clear the entire profile.
1525
+ */
1526
+ async clear() {
1527
+ await this.save({ items: [], lastUpdated: (/* @__PURE__ */ new Date()).toISOString() });
1528
+ }
1529
+ /**
1530
+ * Get the formatted XML string for the system prompt.
1531
+ */
1532
+ async toXml() {
1533
+ const data = await this.get();
1534
+ return toUserProfileXml(data.items);
1535
+ }
1536
+ };
1537
+ function toUserProfileXml(items) {
1538
+ if (items.length === 0) {
1539
+ return "";
1540
+ }
1541
+ const facts = items.filter((i) => i.type === "fact");
1542
+ const preferences = items.filter((i) => i.type === "preference");
1543
+ const present = items.filter((i) => i.type === "present");
1544
+ const sections = [];
1545
+ if (facts.length > 0) {
1546
+ const lines = facts.map((f) => `- ${f.text}`);
1547
+ sections.push(wrapBlock2("identity", lines));
1548
+ }
1549
+ if (preferences.length > 0) {
1550
+ const lines = preferences.map((p) => `- ${p.text}`);
1551
+ sections.push(wrapBlock2("preferences", lines));
1552
+ }
1553
+ if (present.length > 0) {
1554
+ const lines = present.map((c) => `- ${c.text}`);
1555
+ sections.push(wrapBlock2("working_context", lines));
1556
+ }
1557
+ if (sections.length === 0) return "";
1558
+ return `<user_profile>
1559
+ ${indentBlock2(sections.join("\n"), 2)}
1560
+ </user_profile>`;
1561
+ }
1562
+ function wrapBlock2(tag, lines) {
1563
+ if (lines.length === 0) return "";
1564
+ return `<${tag}>
1565
+ ${indentBlock2(lines.join("\n"), 2)}
1566
+ </${tag}>`;
1567
+ }
1568
+ function indentBlock2(text, spaces) {
1569
+ const padding = " ".repeat(spaces);
1570
+ return text.split("\n").map((line) => line.length ? padding + line : padding).join("\n");
1571
+ }
1572
+
1573
+ // packages/text2sql/src/lib/sql.ts
1574
+ var Text2Sql = class {
1575
+ #config;
1576
+ constructor(config) {
1577
+ this.#config = {
1578
+ ...config,
1579
+ instructions: config.instructions ?? [],
1580
+ tools: config.tools ?? {}
1581
+ };
1582
+ }
1583
+ async #getSql(stream2) {
1584
+ const chunks = await Array.fromAsync(
1585
+ stream2
1586
+ );
1587
+ const sql = chunks.at(-1);
1588
+ if (sql && sql.type === "data-text-delta") {
1589
+ return sql.data.text;
1590
+ }
1591
+ throw new Error("No SQL generated");
1592
+ }
1593
+ async explain(sql) {
1594
+ const { experimental_output } = await generate2(
1595
+ explainerAgent,
1596
+ [user2("Explain this SQL.")],
1597
+ { sql }
1598
+ );
1599
+ return experimental_output.explanation;
1600
+ }
1601
+ async toSql(input) {
1602
+ const [introspection, adapterInfo] = await Promise.all([
1603
+ this.#config.adapter.introspect(),
1604
+ this.#config.adapter.info()
1605
+ ]);
1606
+ const context = await generateBrief(introspection, this.#config.cache);
1607
+ return {
1608
+ generate: async () => {
1609
+ const { experimental_output: output } = await generate2(
1610
+ text2sqlOnly,
1611
+ [user2(input)],
1612
+ {
1613
+ adapterInfo: this.#config.adapter.formatInfo(adapterInfo),
1614
+ context,
1615
+ introspection,
1616
+ teachings: toInstructions(...this.#config.instructions)
1617
+ }
1618
+ );
1619
+ return output.sql;
1620
+ }
1621
+ };
1622
+ }
1623
+ async inspect() {
1624
+ const [introspection, adapterInfo] = await Promise.all([
1625
+ this.#config.adapter.introspect(),
1626
+ this.#config.adapter.info()
1627
+ ]);
1628
+ const context = await generateBrief(introspection, this.#config.cache);
1629
+ return text2sqlOnly.instructions({
1630
+ adapterInfo: this.#config.adapter.formatInfo(adapterInfo),
1631
+ context,
1632
+ introspection,
1633
+ teachings: toInstructions(...this.#config.instructions)
1634
+ });
1635
+ }
1636
+ instruct(...dataset) {
1637
+ this.#config.instructions.push(...dataset);
1638
+ }
1639
+ async teach(input) {
1640
+ const [introspection, adapterInfo] = await Promise.all([
1641
+ this.#config.adapter.introspect(),
1642
+ this.#config.adapter.info()
1643
+ ]);
1644
+ const context = await generateBrief(introspection, this.#config.cache);
1645
+ const { experimental_output } = await generate2(
1646
+ teachablesAuthorAgent,
1647
+ [user2(input)],
1648
+ {
1649
+ introspection,
1650
+ adapterInfo: this.#config.adapter.formatInfo(adapterInfo),
1651
+ context
1652
+ }
1653
+ );
1654
+ const teachables = toTeachables(experimental_output.teachables);
1655
+ this.#config.instructions.push(...teachables);
1656
+ return {
1657
+ teachables,
1658
+ teachings: toInstructions(...this.#config.instructions)
1659
+ };
1660
+ }
1661
+ async tag(input) {
1662
+ const [introspection, adapterInfo] = await Promise.all([
1663
+ this.#config.adapter.introspect(),
1664
+ this.#config.adapter.info()
1665
+ ]);
1666
+ const pipeline = pipe(
1667
+ {
1668
+ input,
1669
+ adapter: this.#config.adapter,
1670
+ cache: this.#config.cache,
1671
+ introspection,
1672
+ adapterInfo: this.#config.adapter.formatInfo(adapterInfo),
1673
+ messages: [user2(input)],
1674
+ renderingTools: this.#config.tools || {},
1675
+ teachings: toInstructions(...this.#config.instructions)
1676
+ },
1677
+ toBrief(),
1678
+ async (state, update) => {
1679
+ const { experimental_output: output } = await generate2(
1680
+ text2sqlOnly,
1681
+ state.messages,
1682
+ state
1683
+ );
1684
+ update({
1685
+ messages: [
1686
+ user2(
1687
+ dedent6`
1688
+ Based on the data provided, please explain in clear, conversational language what insights this reveals.
1689
+
1690
+ <user_question>${state.input}</user_question>
1691
+ <data>${JSON.stringify(this.#config.adapter.execute(output.sql))}</data>
1692
+ `
1693
+ )
1694
+ ]
1695
+ });
1696
+ return output.sql;
1697
+ },
1698
+ synthesizerAgent
1699
+ );
1700
+ const stream2 = pipeline();
1701
+ return {
1702
+ generate: async () => {
1703
+ const sql = await this.#getSql(stream2);
1704
+ return sql;
1705
+ },
1706
+ stream: () => {
1707
+ return stream2;
1708
+ }
1709
+ };
1710
+ }
1711
+ async suggest() {
1712
+ const [introspection, adapterInfo] = await Promise.all([
1713
+ this.#config.adapter.introspect(),
1714
+ this.#config.adapter.info()
1715
+ ]);
1716
+ const context = await generateBrief(introspection, this.#config.cache);
1717
+ const { experimental_output: output } = await generate2(
1718
+ suggestionsAgent,
1719
+ [
1720
+ user2(
1721
+ "Suggest high-impact business questions and matching SQL queries for this database."
1722
+ )
1723
+ ],
1724
+ {
1725
+ introspection,
1726
+ adapterInfo: this.#config.adapter.formatInfo(adapterInfo),
1727
+ context
1728
+ }
1729
+ );
1730
+ return output.suggestions;
1731
+ }
1732
+ async single(input) {
1733
+ const [introspection, adapterInfo] = await Promise.all([
1734
+ this.#config.adapter.introspect(),
1735
+ this.#config.adapter.info()
1736
+ ]);
1737
+ return stream(
1738
+ text2sqlMonolith.clone({
1739
+ tools: {
1740
+ ...text2sqlMonolith.handoff.tools,
1741
+ ...this.#config.tools
1742
+ }
1743
+ }),
1744
+ [user2(input)],
1745
+ {
1746
+ teachings: toInstructions(...this.#config.instructions),
1747
+ adapter: this.#config.adapter,
1748
+ introspection,
1749
+ adapterInfo: this.#config.adapter.formatInfo(adapterInfo),
1750
+ context: await generateBrief(introspection, this.#config.cache),
1751
+ renderingTools: this.#config.tools || {}
1752
+ }
1753
+ );
1754
+ }
1755
+ async chat(messages, params) {
1756
+ const [introspection, adapterInfo] = await Promise.all([
1757
+ this.#config.adapter.introspect(),
1758
+ this.#config.adapter.info()
1759
+ ]);
1760
+ const chat = await this.#config.history.upsertChat({
1761
+ id: params.chatId,
1762
+ userId: params.userId,
1763
+ title: "Chat " + params.chatId
1764
+ });
1765
+ const userProfileStore = new UserProfileStore(params.userId);
1766
+ const userProfileXml = await userProfileStore.toXml();
1767
+ const result = stream(
1768
+ text2sqlMonolith.clone({
1769
+ tools: {
1770
+ ...text2sqlMonolith.handoff.tools,
1771
+ ...this.#config.tools,
1772
+ update_user_profile: tool3({
1773
+ description: `Update the user's profile with new facts, preferences, or present context.
1774
+ Use this when the user explicitly states a preference (e.g., "I like dark mode", "Call me Ezz")
1775
+ or when their working context changes (e.g., "I'm working on a hackathon").`,
1776
+ inputSchema: z6.object({
1777
+ type: z6.enum(["fact", "preference", "present"]).describe("The type of information to update."),
1778
+ text: z6.string().describe(
1779
+ "The content of the fact, preference, or present context."
1780
+ ),
1781
+ action: z6.enum(["add", "remove"]).default("add").describe("Whether to add or remove the item.")
1782
+ }),
1783
+ execute: async ({ type, text, action }) => {
1784
+ if (action === "remove") {
1785
+ await userProfileStore.remove(type, text);
1786
+ return `Removed ${type}: ${text}`;
1787
+ }
1788
+ await userProfileStore.add(type, text);
1789
+ return `Added ${type}: ${text}`;
1790
+ }
1791
+ })
1792
+ }
1793
+ }),
1794
+ [...chat.messages.map((it) => it.content), ...messages],
1795
+ {
1796
+ teachings: toInstructions(...this.#config.instructions),
1797
+ adapter: this.#config.adapter,
1798
+ renderingTools: this.#config.tools || {},
1799
+ introspection,
1800
+ adapterInfo: this.#config.adapter.formatInfo(adapterInfo),
1801
+ context: await generateBrief(introspection, this.#config.cache),
1802
+ userProfile: userProfileXml
1803
+ }
1804
+ );
1805
+ return result.toUIMessageStream({
1806
+ onError: (error) => {
1807
+ if (NoSuchToolError.isInstance(error)) {
1808
+ return "The model tried to call a unknown tool.";
1809
+ } else if (InvalidToolInputError.isInstance(error)) {
1810
+ return "The model called a tool with invalid arguments.";
1811
+ } else if (ToolCallRepairError.isInstance(error)) {
1812
+ return "The model tried to call a tool with invalid arguments, but it was repaired.";
1813
+ } else {
1814
+ return "An unknown error occurred.";
1815
+ }
1816
+ },
1817
+ sendStart: true,
1818
+ sendFinish: true,
1819
+ sendReasoning: true,
1820
+ sendSources: true,
1821
+ originalMessages: messages,
1822
+ onFinish: async ({ messages: messages2 }) => {
1823
+ const userMessage = messages2.at(-2);
1824
+ const botMessage = messages2.at(-1);
1825
+ if (!userMessage || !botMessage) {
1826
+ throw new Error("Not implemented yet");
1827
+ }
1828
+ await this.#config.history.addMessage({
1829
+ id: v7(),
1830
+ chatId: params.chatId,
1831
+ role: userMessage.role,
1832
+ content: userMessage
1833
+ });
1834
+ await this.#config.history.addMessage({
1835
+ id: v7(),
1836
+ chatId: params.chatId,
1837
+ role: botMessage.role,
1838
+ content: botMessage
1839
+ });
1840
+ }
1841
+ });
1842
+ }
1843
+ };
1844
+ if (import.meta.main) {
1845
+ }
1846
+
1847
+ // packages/text2sql/src/lib/adapters/postgres.ts
1848
+ var POSTGRES_ERROR_MAP = {
1849
+ "42P01": {
1850
+ type: "MISSING_TABLE",
1851
+ hint: "Check the database schema for the correct table name. Include the schema prefix if necessary."
1852
+ },
1853
+ "42703": {
1854
+ type: "INVALID_COLUMN",
1855
+ hint: "Verify the column exists on the referenced table and use table aliases to disambiguate."
1856
+ },
1857
+ "42601": {
1858
+ type: "SYNTAX_ERROR",
1859
+ hint: "There is a SQL syntax error. Review keywords, punctuation, and the overall query shape."
1860
+ },
1861
+ "42P10": {
1862
+ type: "INVALID_COLUMN",
1863
+ hint: "Columns referenced in GROUP BY/SELECT must exist. Double-check the column names and aliases."
1864
+ },
1865
+ "42883": {
1866
+ type: "INVALID_FUNCTION",
1867
+ hint: "The function or operator you used is not recognized. Confirm its name and argument types."
1868
+ }
1869
+ };
1870
+ var LOW_CARDINALITY_LIMIT2 = 20;
1871
+ function isPostgresError(error) {
1872
+ return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string";
1873
+ }
1874
+ function formatPostgresError(sql, error) {
1875
+ const errorMessage = error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error occurred";
1876
+ if (isPostgresError(error)) {
1877
+ const metadata = POSTGRES_ERROR_MAP[error.code ?? ""];
1878
+ if (metadata) {
1879
+ return {
1880
+ error: errorMessage,
1881
+ error_type: metadata.type,
1882
+ suggestion: metadata.hint,
1883
+ sql_attempted: sql
1884
+ };
1885
+ }
1886
+ }
1887
+ return {
1888
+ error: errorMessage,
1889
+ error_type: "UNKNOWN_ERROR",
1890
+ suggestion: "Review the query and try again",
1891
+ sql_attempted: sql
1892
+ };
1893
+ }
1894
+ var Postgres = class extends Adapter {
1895
+ #options;
1896
+ #introspection = null;
1897
+ #info = null;
1898
+ constructor(options) {
1899
+ super();
1900
+ if (!options || typeof options.execute !== "function") {
1901
+ throw new Error("Postgres adapter requires an execute function.");
1902
+ }
1903
+ this.#options = {
1904
+ ...options,
1905
+ schemas: options.schemas?.length ? options.schemas : void 0
1906
+ };
1907
+ }
1908
+ async introspect(options) {
1909
+ const onProgress = options?.onProgress;
1910
+ if (this.#introspection) {
1911
+ return this.#introspection;
1912
+ }
1913
+ if (this.#options.introspect) {
1914
+ this.#introspection = await this.#options.introspect();
1915
+ return this.#introspection;
1916
+ }
1917
+ const allTables = await this.#loadTables();
1918
+ const allRelationships = await this.#loadRelationships();
1919
+ const { tables, relationships } = this.#applyTablesFilter(
1920
+ allTables,
1921
+ allRelationships
1922
+ );
1923
+ onProgress?.({
1924
+ phase: "tables",
1925
+ message: `Loaded ${tables.length} tables`,
1926
+ total: tables.length
1927
+ });
1928
+ onProgress?.({ phase: "row_counts", message: "Counting table rows..." });
1929
+ await this.#annotateRowCounts(tables, onProgress);
1930
+ onProgress?.({ phase: "primary_keys", message: "Detecting primary keys..." });
1931
+ await this.#annotatePrimaryKeys(tables);
1932
+ onProgress?.({ phase: "primary_keys", message: "Primary keys annotated" });
1933
+ onProgress?.({ phase: "indexes", message: "Loading index information..." });
1934
+ await this.#annotateIndexes(tables);
1935
+ onProgress?.({ phase: "indexes", message: "Indexes annotated" });
1936
+ onProgress?.({ phase: "column_stats", message: "Collecting column statistics..." });
1937
+ await this.#annotateColumnStats(tables, onProgress);
1938
+ onProgress?.({ phase: "low_cardinality", message: "Identifying low cardinality columns..." });
1939
+ await this.#annotateLowCardinalityColumns(tables, onProgress);
1940
+ onProgress?.({
1941
+ phase: "relationships",
1942
+ message: `Loaded ${relationships.length} relationships`
1943
+ });
1944
+ this.#introspection = { tables, relationships };
1945
+ return this.#introspection;
1946
+ }
1947
+ async execute(sql) {
1948
+ return this.#options.execute(sql);
1949
+ }
1950
+ async validate(sql) {
1951
+ const validator = this.#options.validate ?? (async (text) => {
1952
+ await this.#options.execute(`EXPLAIN ${text}`);
1953
+ });
1954
+ try {
1955
+ return await validator(sql);
1956
+ } catch (error) {
1957
+ return JSON.stringify(formatPostgresError(sql, error));
1958
+ }
1959
+ }
1960
+ async info() {
1961
+ if (this.#info) {
1962
+ return this.#info;
1963
+ }
1964
+ this.#info = await this.#resolveInfo();
1965
+ return this.#info;
1966
+ }
1967
+ formatInfo(info) {
1968
+ const lines = [`Dialect: ${info.dialect ?? "unknown"}`];
1969
+ if (info.version) {
1970
+ lines.push(`Version: ${info.version}`);
1971
+ }
1972
+ if (info.database) {
1973
+ lines.push(`Database: ${info.database}`);
1974
+ }
1975
+ if (info.host) {
1976
+ lines.push(`Host: ${info.host}`);
1977
+ }
1978
+ if (info.details && Object.keys(info.details).length) {
1979
+ lines.push(`Details: ${JSON.stringify(info.details)}`);
1980
+ }
1981
+ return lines.join("\n");
1982
+ }
1983
+ async getTables() {
1984
+ const allTables = await this.#loadTables();
1985
+ const allRelationships = await this.#loadRelationships();
1986
+ return this.#applyTablesFilter(allTables, allRelationships).tables;
1987
+ }
1988
+ async #loadTables() {
1989
+ const rows = await this.#runIntrospectionQuery(`
1990
+ SELECT
1991
+ c.table_schema,
1992
+ c.table_name,
1993
+ c.column_name,
1994
+ c.data_type
1995
+ FROM information_schema.columns AS c
1996
+ JOIN information_schema.tables AS t
1997
+ ON c.table_schema = t.table_schema
1998
+ AND c.table_name = t.table_name
1999
+ WHERE t.table_type = 'BASE TABLE'
2000
+ ${this.#buildSchemaFilter("c.table_schema")}
2001
+ ORDER BY c.table_schema, c.table_name, c.ordinal_position
2002
+ `);
2003
+ const tables = /* @__PURE__ */ new Map();
2004
+ for (const row of rows) {
2005
+ if (!row.table_name) {
2006
+ continue;
2007
+ }
2008
+ const schema = row.table_schema ?? "public";
2009
+ const tableName = row.table_name;
2010
+ const qualifiedName = `${schema}.${tableName}`;
2011
+ const table = tables.get(qualifiedName) ?? {
2012
+ name: qualifiedName,
2013
+ schema,
2014
+ rawName: tableName,
2015
+ columns: []
2016
+ };
2017
+ table.columns.push({
2018
+ name: row.column_name ?? "unknown",
2019
+ type: row.data_type ?? "unknown"
2020
+ });
2021
+ tables.set(qualifiedName, table);
2022
+ }
2023
+ return Array.from(tables.values());
2024
+ }
2025
+ async getRelationships() {
2026
+ const allTables = await this.#loadTables();
2027
+ const allRelationships = await this.#loadRelationships();
2028
+ return this.#applyTablesFilter(allTables, allRelationships).relationships;
2029
+ }
2030
+ async #loadRelationships() {
2031
+ const rows = await this.#runIntrospectionQuery(`
2032
+ SELECT
2033
+ tc.constraint_name,
2034
+ tc.table_schema,
2035
+ tc.table_name,
2036
+ kcu.column_name,
2037
+ ccu.table_schema AS foreign_table_schema,
2038
+ ccu.table_name AS foreign_table_name,
2039
+ ccu.column_name AS foreign_column_name
2040
+ FROM information_schema.table_constraints AS tc
2041
+ JOIN information_schema.key_column_usage AS kcu
2042
+ ON tc.constraint_name = kcu.constraint_name
2043
+ AND tc.table_schema = kcu.table_schema
2044
+ JOIN information_schema.constraint_column_usage AS ccu
2045
+ ON ccu.constraint_name = tc.constraint_name
2046
+ AND ccu.table_schema = tc.table_schema
2047
+ WHERE tc.constraint_type = 'FOREIGN KEY'
2048
+ ${this.#buildSchemaFilter("tc.table_schema")}
2049
+ ORDER BY tc.table_schema, tc.table_name, tc.constraint_name, kcu.ordinal_position
2050
+ `);
2051
+ const relationships = /* @__PURE__ */ new Map();
2052
+ for (const row of rows) {
2053
+ if (!row.table_name || !row.foreign_table_name || !row.constraint_name) {
2054
+ continue;
2055
+ }
2056
+ const schema = row.table_schema ?? "public";
2057
+ const referencedSchema = row.foreign_table_schema ?? "public";
2058
+ const key = `${schema}.${row.table_name}:${row.constraint_name}`;
2059
+ const relationship = relationships.get(key) ?? {
2060
+ table: `${schema}.${row.table_name}`,
2061
+ from: [],
2062
+ referenced_table: `${referencedSchema}.${row.foreign_table_name}`,
2063
+ to: []
2064
+ };
2065
+ relationship.from.push(row.column_name ?? "unknown");
2066
+ relationship.to.push(row.foreign_column_name ?? "unknown");
2067
+ relationships.set(key, relationship);
2068
+ }
2069
+ return Array.from(relationships.values());
2070
+ }
2071
+ async #annotateRowCounts(tables, onProgress) {
2072
+ const total = tables.length;
2073
+ for (let i = 0; i < tables.length; i++) {
2074
+ const table = tables[i];
2075
+ const tableIdentifier = this.#formatQualifiedTableName(table);
2076
+ onProgress?.({
2077
+ phase: "row_counts",
2078
+ message: `Counting rows in ${table.name}...`,
2079
+ current: i + 1,
2080
+ total
2081
+ });
2082
+ try {
2083
+ const rows = await this.#runIntrospectionQuery(`SELECT COUNT(*) as count FROM ${tableIdentifier}`);
2084
+ const rowCount = this.#toNumber(rows[0]?.count);
2085
+ if (rowCount != null) {
2086
+ table.rowCount = rowCount;
2087
+ table.sizeHint = this.#classifyRowCount(rowCount);
2088
+ }
2089
+ } catch {
2090
+ continue;
2091
+ }
2092
+ }
2093
+ }
2094
+ async #annotatePrimaryKeys(tables) {
2095
+ if (!tables.length) {
2096
+ return;
2097
+ }
2098
+ const tableMap = new Map(tables.map((table) => [table.name, table]));
2099
+ const rows = await this.#runIntrospectionQuery(`
2100
+ SELECT
2101
+ tc.table_schema,
2102
+ tc.table_name,
2103
+ kcu.column_name
2104
+ FROM information_schema.table_constraints AS tc
2105
+ JOIN information_schema.key_column_usage AS kcu
2106
+ ON tc.constraint_name = kcu.constraint_name
2107
+ AND tc.table_schema = kcu.table_schema
2108
+ WHERE tc.constraint_type = 'PRIMARY KEY'
2109
+ ${this.#buildSchemaFilter("tc.table_schema")}
2110
+ `);
2111
+ for (const row of rows) {
2112
+ if (!row.table_name) {
2113
+ continue;
2114
+ }
2115
+ const schema = row.table_schema ?? "public";
2116
+ const qualifiedName = `${schema}.${row.table_name}`;
2117
+ const table = tableMap.get(qualifiedName);
2118
+ if (!table) {
2119
+ continue;
2120
+ }
2121
+ const column = table.columns.find((col) => col.name === row.column_name);
2122
+ if (column) {
2123
+ column.isPrimaryKey = true;
2124
+ }
2125
+ }
2126
+ }
2127
+ async #annotateIndexes(tables) {
2128
+ if (!tables.length) {
2129
+ return;
2130
+ }
2131
+ const tableMap = new Map(tables.map((table) => [table.name, table]));
2132
+ const rows = await this.#runIntrospectionQuery(`
2133
+ SELECT
2134
+ n.nspname AS table_schema,
2135
+ t.relname AS table_name,
2136
+ i.relname AS index_name,
2137
+ a.attname AS column_name,
2138
+ ix.indisunique,
2139
+ ix.indisprimary,
2140
+ ix.indisclustered,
2141
+ am.amname AS method
2142
+ FROM pg_index ix
2143
+ JOIN pg_class t ON t.oid = ix.indrelid
2144
+ JOIN pg_namespace n ON n.oid = t.relnamespace
2145
+ JOIN pg_class i ON i.oid = ix.indexrelid
2146
+ JOIN pg_am am ON am.oid = i.relam
2147
+ LEFT JOIN LATERAL unnest(ix.indkey) WITH ORDINALITY AS key(attnum, ordinality) ON TRUE
2148
+ LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = key.attnum
2149
+ WHERE t.relkind = 'r'
2150
+ ${this.#buildSchemaFilter("n.nspname")}
2151
+ ORDER BY n.nspname, t.relname, i.relname, key.ordinality
2152
+ `);
2153
+ const indexMap = /* @__PURE__ */ new Map();
2154
+ for (const row of rows) {
2155
+ if (!row.table_name || !row.index_name) {
2156
+ continue;
2157
+ }
2158
+ const schema = row.table_schema ?? "public";
2159
+ const tableKey = `${schema}.${row.table_name}`;
2160
+ const table = tableMap.get(tableKey);
2161
+ if (!table) {
2162
+ continue;
2163
+ }
2164
+ const indexKey = `${tableKey}:${row.index_name}`;
2165
+ let index = indexMap.get(indexKey);
2166
+ if (!index) {
2167
+ index = {
2168
+ name: row.index_name,
2169
+ columns: [],
2170
+ unique: Boolean(row.indisunique ?? false),
2171
+ primary: Boolean(row.indisprimary ?? false),
2172
+ type: row.indisclustered ? "clustered" : row.method ?? void 0
2173
+ };
2174
+ indexMap.set(indexKey, index);
2175
+ if (!table.indexes) {
2176
+ table.indexes = [];
2177
+ }
2178
+ table.indexes.push(index);
2179
+ }
2180
+ if (row.column_name) {
2181
+ index.columns.push(row.column_name);
2182
+ const column = table.columns.find(
2183
+ (col) => col.name === row.column_name
2184
+ );
2185
+ if (column) {
2186
+ column.isIndexed = true;
2187
+ }
2188
+ }
2189
+ }
2190
+ }
2191
+ async #annotateColumnStats(tables, onProgress) {
2192
+ if (!tables.length) {
2193
+ return;
2194
+ }
2195
+ const total = tables.length;
2196
+ for (let i = 0; i < tables.length; i++) {
2197
+ const table = tables[i];
2198
+ const tableIdentifier = this.#formatQualifiedTableName(table);
2199
+ onProgress?.({
2200
+ phase: "column_stats",
2201
+ message: `Collecting stats for ${table.name}...`,
2202
+ current: i + 1,
2203
+ total
2204
+ });
2205
+ for (const column of table.columns) {
2206
+ if (!this.#shouldCollectStats(column.type)) {
2207
+ continue;
2208
+ }
2209
+ const columnIdentifier = this.#quoteIdentifier(column.name);
2210
+ const sql = `
2211
+ SELECT
2212
+ MIN(${columnIdentifier})::text AS min_value,
2213
+ MAX(${columnIdentifier})::text AS max_value,
2214
+ AVG(CASE WHEN ${columnIdentifier} IS NULL THEN 1.0 ELSE 0.0 END)::float AS null_fraction
2215
+ FROM ${tableIdentifier}
2216
+ `;
2217
+ try {
2218
+ const rows = await this.#runIntrospectionQuery(sql);
2219
+ if (!rows.length) {
2220
+ continue;
2221
+ }
2222
+ const min = rows[0]?.min_value ?? void 0;
2223
+ const max = rows[0]?.max_value ?? void 0;
2224
+ const nullFraction = this.#toNumber(rows[0]?.null_fraction);
2225
+ if (min != null || max != null || nullFraction != null) {
2226
+ column.stats = {
2227
+ min,
2228
+ max,
2229
+ nullFraction: nullFraction != null && Number.isFinite(nullFraction) ? Math.max(0, Math.min(1, nullFraction)) : void 0
2230
+ };
2231
+ }
2232
+ } catch {
2233
+ continue;
2234
+ }
2235
+ }
2236
+ }
2237
+ }
2238
+ async #annotateLowCardinalityColumns(tables, onProgress) {
2239
+ const total = tables.length;
2240
+ for (let i = 0; i < tables.length; i++) {
2241
+ const table = tables[i];
2242
+ const tableIdentifier = this.#formatQualifiedTableName(table);
2243
+ onProgress?.({
2244
+ phase: "low_cardinality",
2245
+ message: `Analyzing cardinality in ${table.name}...`,
2246
+ current: i + 1,
2247
+ total
2248
+ });
2249
+ for (const column of table.columns) {
2250
+ const columnIdentifier = this.#quoteIdentifier(column.name);
2251
+ const limit = LOW_CARDINALITY_LIMIT2 + 1;
2252
+ const sql = `
2253
+ SELECT DISTINCT ${columnIdentifier} AS value
2254
+ FROM ${tableIdentifier}
2255
+ WHERE ${columnIdentifier} IS NOT NULL
2256
+ LIMIT ${limit}
2257
+ `;
2258
+ let rows = [];
2259
+ try {
2260
+ rows = await this.#runIntrospectionQuery(sql);
2261
+ } catch {
2262
+ continue;
2263
+ }
2264
+ if (!rows.length || rows.length > LOW_CARDINALITY_LIMIT2) {
2265
+ continue;
2266
+ }
2267
+ const values = [];
2268
+ let shouldSkip = false;
2269
+ for (const row of rows) {
2270
+ const formatted = this.#normalizeValue(row.value);
2271
+ if (formatted == null) {
2272
+ shouldSkip = true;
2273
+ break;
2274
+ }
2275
+ values.push(formatted);
2276
+ }
2277
+ if (shouldSkip || !values.length) {
2278
+ continue;
2279
+ }
2280
+ column.kind = "LowCardinality";
2281
+ column.values = values;
2282
+ }
2283
+ }
2284
+ }
2285
+ #buildSchemaFilter(columnName) {
2286
+ if (this.#options.schemas && this.#options.schemas.length > 0) {
2287
+ const values = this.#options.schemas.map((schema) => `'${schema.replace(/'/g, "''")}'`).join(", ");
2288
+ return `AND ${columnName} IN (${values})`;
2289
+ }
2290
+ return `AND ${columnName} NOT IN ('pg_catalog', 'information_schema')`;
2291
+ }
2292
+ async #runIntrospectionQuery(sql) {
2293
+ const result = await this.#options.execute(sql);
2294
+ if (Array.isArray(result)) {
2295
+ return result;
2296
+ }
2297
+ if (result && typeof result === "object" && "rows" in result && Array.isArray(result.rows)) {
2298
+ return result.rows;
2299
+ }
2300
+ throw new Error(
2301
+ "Postgres adapter execute() must return an array of rows or an object with a rows array when introspecting."
2302
+ );
2303
+ }
2304
+ #quoteIdentifier(name) {
2305
+ return `"${name.replace(/"/g, '""')}"`;
2306
+ }
2307
+ #applyTablesFilter(tables, relationships) {
2308
+ return applyTablesFilter(tables, relationships, this.#options.tables);
2309
+ }
2310
+ #formatQualifiedTableName(table) {
2311
+ if (table.schema && table.rawName) {
2312
+ return `${this.#quoteIdentifier(table.schema)}.${this.#quoteIdentifier(table.rawName)}`;
2313
+ }
2314
+ if (table.name.includes(".")) {
2315
+ const [schemaPart, ...rest] = table.name.split(".");
2316
+ const tablePart = rest.join(".") || schemaPart;
2317
+ if (rest.length === 0) {
2318
+ return this.#quoteIdentifier(schemaPart);
2319
+ }
2320
+ return `${this.#quoteIdentifier(schemaPart)}.${this.#quoteIdentifier(tablePart)}`;
2321
+ }
2322
+ return this.#quoteIdentifier(table.name);
2323
+ }
2324
+ #toNumber(value) {
2325
+ if (typeof value === "number" && Number.isFinite(value)) {
2326
+ return value;
2327
+ }
2328
+ if (typeof value === "bigint") {
2329
+ return Number(value);
2330
+ }
2331
+ if (typeof value === "string" && value.trim() !== "") {
2332
+ const parsed = Number(value);
2333
+ return Number.isFinite(parsed) ? parsed : null;
2334
+ }
2335
+ return null;
2336
+ }
2337
+ #shouldCollectStats(type) {
2338
+ if (!type) {
2339
+ return false;
2340
+ }
2341
+ const normalized = type.toLowerCase();
2342
+ return /int|numeric|decimal|double|real|money|date|time|timestamp|bool/.test(
2343
+ normalized
2344
+ );
2345
+ }
2346
+ #classifyRowCount(count) {
2347
+ if (count < 100) {
2348
+ return "tiny";
2349
+ }
2350
+ if (count < 1e3) {
2351
+ return "small";
2352
+ }
2353
+ if (count < 1e4) {
2354
+ return "medium";
2355
+ }
2356
+ if (count < 1e5) {
2357
+ return "large";
2358
+ }
2359
+ return "huge";
2360
+ }
2361
+ #normalizeValue(value) {
2362
+ if (value === null || value === void 0) {
2363
+ return null;
2364
+ }
2365
+ if (typeof value === "string") {
2366
+ return value;
2367
+ }
2368
+ if (typeof value === "number" || typeof value === "bigint") {
2369
+ return String(value);
2370
+ }
2371
+ if (typeof value === "boolean") {
2372
+ return value ? "true" : "false";
2373
+ }
2374
+ if (value instanceof Date) {
2375
+ return value.toISOString();
2376
+ }
2377
+ if (typeof Buffer !== "undefined" && Buffer.isBuffer(value)) {
2378
+ return value.toString("utf-8");
2379
+ }
2380
+ return null;
2381
+ }
2382
+ async #resolveInfo() {
2383
+ const { info } = this.#options;
2384
+ if (!info) {
2385
+ return { dialect: "postgresql" };
2386
+ }
2387
+ if (typeof info === "function") {
2388
+ return info();
2389
+ }
2390
+ return info;
2391
+ }
2392
+ };
2393
+
2394
+ // packages/text2sql/src/lib/adapters/sqlserver.ts
2395
+ var SQL_SERVER_ERROR_MAP = {
2396
+ "208": {
2397
+ type: "MISSING_TABLE",
2398
+ hint: "Check that the table exists and include the schema prefix (e.g., dbo.TableName)."
2399
+ },
2400
+ "207": {
2401
+ type: "INVALID_COLUMN",
2402
+ hint: "Verify the column exists on the table and that any aliases are referenced correctly."
2403
+ },
2404
+ "156": {
2405
+ type: "SYNTAX_ERROR",
2406
+ hint: "There is a SQL syntax error. Review keywords, punctuation, and clauses such as GROUP BY."
2407
+ },
2408
+ "4104": {
2409
+ type: "INVALID_COLUMN",
2410
+ hint: "Columns must be qualified with table aliases when ambiguous. Double-check join aliases."
2411
+ },
2412
+ "1934": {
2413
+ type: "CONSTRAINT_ERROR",
2414
+ hint: "The query violates a constraint. Re-check join logic and filtering."
2415
+ }
2416
+ };
2417
+ var LOW_CARDINALITY_LIMIT3 = 20;
2418
+ function getErrorCode(error) {
2419
+ if (typeof error === "object" && error !== null && "number" in error && typeof error.number === "number") {
2420
+ return String(error.number);
2421
+ }
2422
+ if (typeof error === "object" && error !== null && "code" in error && typeof error.code === "string") {
2423
+ return error.code;
2424
+ }
2425
+ return null;
2426
+ }
2427
+ function formatSqlServerError(sql, error) {
2428
+ const errorMessage = error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error occurred";
2429
+ const code = getErrorCode(error);
2430
+ const metadata = code ? SQL_SERVER_ERROR_MAP[code] : void 0;
2431
+ if (metadata) {
2432
+ return {
2433
+ error: errorMessage,
2434
+ error_type: metadata.type,
2435
+ suggestion: metadata.hint,
2436
+ sql_attempted: sql
2437
+ };
2438
+ }
2439
+ return {
2440
+ error: errorMessage,
2441
+ error_type: "UNKNOWN_ERROR",
2442
+ suggestion: "Review the query and try again",
2443
+ sql_attempted: sql
2444
+ };
2445
+ }
2446
+ var SqlServer = class extends Adapter {
2447
+ #options;
2448
+ #introspection = null;
2449
+ #info = null;
2450
+ constructor(options) {
2451
+ super();
2452
+ if (!options || typeof options.execute !== "function") {
2453
+ throw new Error("SqlServer adapter requires an execute function.");
2454
+ }
2455
+ this.#options = {
2456
+ ...options,
2457
+ schemas: options.schemas?.length ? options.schemas : void 0
2458
+ };
2459
+ }
2460
+ async introspect(options) {
2461
+ const onProgress = options?.onProgress;
2462
+ if (this.#introspection) {
2463
+ return this.#introspection;
2464
+ }
2465
+ if (this.#options.introspect) {
2466
+ this.#introspection = await this.#options.introspect();
2467
+ return this.#introspection;
2468
+ }
2469
+ const allTables = await this.#loadTables();
2470
+ const allRelationships = await this.#loadRelationships();
2471
+ const { tables, relationships } = this.#applyTablesFilter(
2472
+ allTables,
2473
+ allRelationships
2474
+ );
2475
+ onProgress?.({
2476
+ phase: "tables",
2477
+ message: `Loaded ${tables.length} tables`,
2478
+ total: tables.length
2479
+ });
2480
+ onProgress?.({ phase: "row_counts", message: "Counting table rows..." });
2481
+ await this.#annotateRowCounts(tables, onProgress);
2482
+ onProgress?.({ phase: "primary_keys", message: "Detecting primary keys..." });
2483
+ await this.#annotatePrimaryKeys(tables);
2484
+ onProgress?.({ phase: "primary_keys", message: "Primary keys annotated" });
2485
+ onProgress?.({ phase: "indexes", message: "Loading index information..." });
2486
+ await this.#annotateIndexes(tables);
2487
+ onProgress?.({ phase: "indexes", message: "Indexes annotated" });
2488
+ onProgress?.({ phase: "column_stats", message: "Collecting column statistics..." });
2489
+ await this.#annotateColumnStats(tables, onProgress);
2490
+ onProgress?.({ phase: "low_cardinality", message: "Identifying low cardinality columns..." });
2491
+ await this.#annotateLowCardinalityColumns(tables, onProgress);
2492
+ onProgress?.({
2493
+ phase: "relationships",
2494
+ message: `Loaded ${relationships.length} relationships`
2495
+ });
2496
+ this.#introspection = { tables, relationships };
2497
+ return this.#introspection;
2498
+ }
2499
+ async execute(sql) {
2500
+ return this.#options.execute(sql);
2501
+ }
2502
+ async validate(sql) {
2503
+ const validator = this.#options.validate ?? (async (text) => {
2504
+ await this.#options.execute(
2505
+ `SET PARSEONLY ON; ${text}; SET PARSEONLY OFF;`
2506
+ );
2507
+ });
2508
+ try {
2509
+ return await validator(sql);
2510
+ } catch (error) {
2511
+ return JSON.stringify(formatSqlServerError(sql, error));
2512
+ }
2513
+ }
2514
+ async info() {
2515
+ if (this.#info) {
2516
+ return this.#info;
2517
+ }
2518
+ this.#info = await this.#resolveInfo();
2519
+ return this.#info;
2520
+ }
2521
+ formatInfo(info) {
2522
+ const lines = [`Dialect: ${info.dialect ?? "unknown"}`];
2523
+ if (info.version) {
2524
+ lines.push(`Version: ${info.version}`);
2525
+ }
2526
+ if (info.database) {
2527
+ lines.push(`Database: ${info.database}`);
2528
+ }
2529
+ if (info.host) {
2530
+ lines.push(`Host: ${info.host}`);
2531
+ }
2532
+ if (info.details && Object.keys(info.details).length) {
2533
+ lines.push(`Details: ${JSON.stringify(info.details)}`);
2534
+ }
2535
+ return lines.join("\n");
2536
+ }
2537
+ async getTables() {
2538
+ const allTables = await this.#loadTables();
2539
+ const allRelationships = await this.#loadRelationships();
2540
+ return this.#applyTablesFilter(allTables, allRelationships).tables;
2541
+ }
2542
+ async #loadTables() {
2543
+ const rows = await this.#runIntrospectionQuery(`
2544
+ SELECT
2545
+ c.TABLE_SCHEMA AS table_schema,
2546
+ c.TABLE_NAME AS table_name,
2547
+ c.COLUMN_NAME AS column_name,
2548
+ c.DATA_TYPE AS data_type
2549
+ FROM INFORMATION_SCHEMA.COLUMNS AS c
2550
+ JOIN INFORMATION_SCHEMA.TABLES AS t
2551
+ ON c.TABLE_SCHEMA = t.TABLE_SCHEMA
2552
+ AND c.TABLE_NAME = t.TABLE_NAME
2553
+ WHERE t.TABLE_TYPE = 'BASE TABLE'
2554
+ ${this.#buildSchemaFilter("c.TABLE_SCHEMA")}
2555
+ ORDER BY c.TABLE_SCHEMA, c.TABLE_NAME, c.ORDINAL_POSITION
2556
+ `);
2557
+ const tables = /* @__PURE__ */ new Map();
2558
+ for (const row of rows) {
2559
+ if (!row.table_name) {
2560
+ continue;
2561
+ }
2562
+ const schema = row.table_schema ?? "dbo";
2563
+ const tableName = row.table_name;
2564
+ const qualifiedName = `${schema}.${tableName}`;
2565
+ const table = tables.get(qualifiedName) ?? {
2566
+ name: qualifiedName,
2567
+ schema,
2568
+ rawName: tableName,
2569
+ columns: []
2570
+ };
2571
+ table.columns.push({
2572
+ name: row.column_name ?? "unknown",
2573
+ type: row.data_type ?? "unknown"
2574
+ });
2575
+ tables.set(qualifiedName, table);
2576
+ }
2577
+ return Array.from(tables.values());
2578
+ }
2579
+ async getRelationships() {
2580
+ const allTables = await this.#loadTables();
2581
+ const allRelationships = await this.#loadRelationships();
2582
+ return this.#applyTablesFilter(allTables, allRelationships).relationships;
2583
+ }
2584
+ async #loadRelationships() {
2585
+ const rows = await this.#runIntrospectionQuery(`
2586
+ SELECT
2587
+ fk.CONSTRAINT_NAME AS constraint_name,
2588
+ fk.TABLE_SCHEMA AS table_schema,
2589
+ fk.TABLE_NAME AS table_name,
2590
+ fk.COLUMN_NAME AS column_name,
2591
+ pk.TABLE_SCHEMA AS referenced_table_schema,
2592
+ pk.TABLE_NAME AS referenced_table_name,
2593
+ pk.COLUMN_NAME AS referenced_column_name
2594
+ FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS rc
2595
+ JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS fk
2596
+ ON fk.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
2597
+ JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS pk
2598
+ ON pk.CONSTRAINT_NAME = rc.UNIQUE_CONSTRAINT_NAME
2599
+ AND pk.ORDINAL_POSITION = fk.ORDINAL_POSITION
2600
+ WHERE 1 = 1
2601
+ ${this.#buildSchemaFilter("fk.TABLE_SCHEMA")}
2602
+ ORDER BY fk.TABLE_SCHEMA, fk.TABLE_NAME, fk.CONSTRAINT_NAME, fk.ORDINAL_POSITION
2603
+ `);
2604
+ const relationships = /* @__PURE__ */ new Map();
2605
+ for (const row of rows) {
2606
+ if (!row.constraint_name || !row.table_name || !row.referenced_table_name) {
2607
+ continue;
2608
+ }
2609
+ const schema = row.table_schema ?? "dbo";
2610
+ const referencedSchema = row.referenced_table_schema ?? "dbo";
2611
+ const key = `${schema}.${row.table_name}:${row.constraint_name}`;
2612
+ const relationship = relationships.get(key) ?? {
2613
+ table: `${schema}.${row.table_name}`,
2614
+ from: [],
2615
+ referenced_table: `${referencedSchema}.${row.referenced_table_name}`,
2616
+ to: []
2617
+ };
2618
+ relationship.from.push(row.column_name ?? "unknown");
2619
+ relationship.to.push(row.referenced_column_name ?? "unknown");
2620
+ relationships.set(key, relationship);
2621
+ }
2622
+ return Array.from(relationships.values());
2623
+ }
2624
+ async #annotateRowCounts(tables, onProgress) {
2625
+ const total = tables.length;
2626
+ for (let i = 0; i < tables.length; i++) {
2627
+ const table = tables[i];
2628
+ const tableIdentifier = this.#formatQualifiedTableName(table);
2629
+ onProgress?.({
2630
+ phase: "row_counts",
2631
+ message: `Counting rows in ${table.name}...`,
2632
+ current: i + 1,
2633
+ total
2634
+ });
2635
+ try {
2636
+ const rows = await this.#runIntrospectionQuery(`SELECT COUNT(*) as count FROM ${tableIdentifier}`);
2637
+ const rowCount = this.#toNumber(rows[0]?.count);
2638
+ if (rowCount != null) {
2639
+ table.rowCount = rowCount;
2640
+ table.sizeHint = this.#classifyRowCount(rowCount);
2641
+ }
2642
+ } catch {
2643
+ continue;
2644
+ }
2645
+ }
2646
+ }
2647
+ async #annotatePrimaryKeys(tables) {
2648
+ if (!tables.length) {
2649
+ return;
2650
+ }
2651
+ const tableMap = new Map(tables.map((table) => [table.name, table]));
2652
+ const rows = await this.#runIntrospectionQuery(`
2653
+ SELECT
2654
+ kc.TABLE_SCHEMA AS table_schema,
2655
+ kc.TABLE_NAME AS table_name,
2656
+ kc.COLUMN_NAME AS column_name
2657
+ FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS tc
2658
+ JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kc
2659
+ ON tc.CONSTRAINT_NAME = kc.CONSTRAINT_NAME
2660
+ AND tc.TABLE_SCHEMA = kc.TABLE_SCHEMA
2661
+ WHERE tc.CONSTRAINT_TYPE = 'PRIMARY KEY'
2662
+ ${this.#buildSchemaFilter("kc.TABLE_SCHEMA")}
2663
+ `);
2664
+ for (const row of rows) {
2665
+ if (!row.table_name || !row.column_name) {
2666
+ continue;
2667
+ }
2668
+ const schema = row.table_schema ?? "dbo";
2669
+ const tableKey = `${schema}.${row.table_name}`;
2670
+ const table = tableMap.get(tableKey);
2671
+ if (!table) {
2672
+ continue;
2673
+ }
2674
+ const column = table.columns.find((col) => col.name === row.column_name);
2675
+ if (column) {
2676
+ column.isPrimaryKey = true;
2677
+ }
2678
+ }
2679
+ }
2680
+ async #annotateIndexes(tables) {
2681
+ if (!tables.length) {
2682
+ return;
2683
+ }
2684
+ const tableMap = new Map(tables.map((table) => [table.name, table]));
2685
+ const rows = await this.#runIntrospectionQuery(`
2686
+ SELECT
2687
+ sch.name AS schema_name,
2688
+ t.name AS table_name,
2689
+ ind.name AS index_name,
2690
+ col.name AS column_name,
2691
+ ind.is_unique,
2692
+ ind.is_primary_key,
2693
+ ind.type_desc,
2694
+ ic.is_included_column
2695
+ FROM sys.indexes AS ind
2696
+ JOIN sys.tables AS t ON ind.object_id = t.object_id
2697
+ JOIN sys.schemas AS sch ON t.schema_id = sch.schema_id
2698
+ JOIN sys.index_columns AS ic
2699
+ ON ind.object_id = ic.object_id
2700
+ AND ind.index_id = ic.index_id
2701
+ JOIN sys.columns AS col
2702
+ ON ic.object_id = col.object_id
2703
+ AND ic.column_id = col.column_id
2704
+ WHERE ind.is_hypothetical = 0
2705
+ AND ind.name IS NOT NULL
2706
+ ${this.#buildSchemaFilter("sch.name")}
2707
+ ORDER BY sch.name, t.name, ind.name, ic.key_ordinal
2708
+ `);
2709
+ const indexMap = /* @__PURE__ */ new Map();
2710
+ for (const row of rows) {
2711
+ if (!row.table_name || !row.index_name) {
2712
+ continue;
2713
+ }
2714
+ const schema = row.schema_name ?? "dbo";
2715
+ const tableKey = `${schema}.${row.table_name}`;
2716
+ const table = tableMap.get(tableKey);
2717
+ if (!table) {
2718
+ continue;
2719
+ }
2720
+ const indexKey = `${tableKey}:${row.index_name}`;
2721
+ let index = indexMap.get(indexKey);
2722
+ if (!index) {
2723
+ index = {
2724
+ name: row.index_name,
2725
+ columns: [],
2726
+ unique: Boolean(row.is_unique),
2727
+ primary: Boolean(row.is_primary_key),
2728
+ type: row.type_desc ?? void 0
2729
+ };
2730
+ indexMap.set(indexKey, index);
2731
+ if (!table.indexes) {
2732
+ table.indexes = [];
2733
+ }
2734
+ table.indexes.push(index);
2735
+ }
2736
+ if (row.is_included_column) {
2737
+ continue;
2738
+ }
2739
+ if (row.column_name) {
2740
+ index.columns.push(row.column_name);
2741
+ const column = table.columns.find(
2742
+ (col) => col.name === row.column_name
2743
+ );
2744
+ if (column) {
2745
+ column.isIndexed = true;
2746
+ }
2747
+ }
2748
+ }
2749
+ }
2750
+ async #annotateColumnStats(tables, onProgress) {
2751
+ if (!tables.length) {
2752
+ return;
2753
+ }
2754
+ const total = tables.length;
2755
+ for (let i = 0; i < tables.length; i++) {
2756
+ const table = tables[i];
2757
+ const tableIdentifier = this.#formatQualifiedTableName(table);
2758
+ onProgress?.({
2759
+ phase: "column_stats",
2760
+ message: `Collecting stats for ${table.name}...`,
2761
+ current: i + 1,
2762
+ total
2763
+ });
2764
+ for (const column of table.columns) {
2765
+ if (!this.#shouldCollectStats(column.type)) {
2766
+ continue;
2767
+ }
2768
+ const columnIdentifier = this.#quoteIdentifier(column.name);
2769
+ const sql = `
2770
+ SELECT
2771
+ CONVERT(NVARCHAR(4000), MIN(${columnIdentifier})) AS min_value,
2772
+ CONVERT(NVARCHAR(4000), MAX(${columnIdentifier})) AS max_value,
2773
+ AVG(CASE WHEN ${columnIdentifier} IS NULL THEN 1.0 ELSE 0.0 END) AS null_fraction
2774
+ FROM ${tableIdentifier}
2775
+ `;
2776
+ try {
2777
+ const rows = await this.#runIntrospectionQuery(sql);
2778
+ if (!rows.length) {
2779
+ continue;
2780
+ }
2781
+ const nullFraction = this.#toNumber(rows[0]?.null_fraction);
2782
+ if (rows[0]?.min_value != null || rows[0]?.max_value != null || nullFraction != null) {
2783
+ column.stats = {
2784
+ min: rows[0]?.min_value ?? void 0,
2785
+ max: rows[0]?.max_value ?? void 0,
2786
+ nullFraction: nullFraction != null && Number.isFinite(nullFraction) ? Math.max(0, Math.min(1, nullFraction)) : void 0
2787
+ };
2788
+ }
2789
+ } catch {
2790
+ continue;
2791
+ }
2792
+ }
2793
+ }
2794
+ }
2795
+ async #annotateLowCardinalityColumns(tables, onProgress) {
2796
+ const total = tables.length;
2797
+ for (let i = 0; i < tables.length; i++) {
2798
+ const table = tables[i];
2799
+ const tableIdentifier = this.#formatQualifiedTableName(table);
2800
+ onProgress?.({
2801
+ phase: "low_cardinality",
2802
+ message: `Analyzing cardinality in ${table.name}...`,
2803
+ current: i + 1,
2804
+ total
2805
+ });
2806
+ for (const column of table.columns) {
2807
+ const columnIdentifier = this.#quoteIdentifier(column.name);
2808
+ const limit = LOW_CARDINALITY_LIMIT3 + 1;
2809
+ const sql = `
2810
+ SELECT DISTINCT TOP (${limit}) ${columnIdentifier} AS value
2811
+ FROM ${tableIdentifier}
2812
+ WHERE ${columnIdentifier} IS NOT NULL
2813
+ `;
2814
+ let rows = [];
2815
+ try {
2816
+ rows = await this.#runIntrospectionQuery(sql);
2817
+ } catch {
2818
+ continue;
2819
+ }
2820
+ if (!rows.length || rows.length > LOW_CARDINALITY_LIMIT3) {
2821
+ continue;
2822
+ }
2823
+ const values = [];
2824
+ let shouldSkip = false;
2825
+ for (const row of rows) {
2826
+ const formatted = this.#normalizeValue(row.value);
2827
+ if (formatted == null) {
2828
+ shouldSkip = true;
2829
+ break;
2830
+ }
2831
+ values.push(formatted);
2832
+ }
2833
+ if (shouldSkip || !values.length) {
2834
+ continue;
2835
+ }
2836
+ column.kind = "LowCardinality";
2837
+ column.values = values;
2838
+ }
2839
+ }
2840
+ }
2841
+ #buildSchemaFilter(columnName) {
2842
+ if (this.#options.schemas && this.#options.schemas.length > 0) {
2843
+ const values = this.#options.schemas.map((schema) => `'${schema.replace(/'/g, "''")}'`).join(", ");
2844
+ return `AND ${columnName} IN (${values})`;
2845
+ }
2846
+ return `AND ${columnName} NOT IN ('INFORMATION_SCHEMA', 'sys')`;
2847
+ }
2848
+ async #runIntrospectionQuery(sql) {
2849
+ const result = await this.#options.execute(sql);
2850
+ if (Array.isArray(result)) {
2851
+ return result;
2852
+ }
2853
+ if (result && typeof result === "object") {
2854
+ if ("rows" in result && Array.isArray(result.rows)) {
2855
+ return result.rows;
2856
+ }
2857
+ if ("recordset" in result && Array.isArray(result.recordset)) {
2858
+ return result.recordset;
2859
+ }
2860
+ if ("recordsets" in result && Array.isArray(result.recordsets) && Array.isArray(result.recordsets[0])) {
2861
+ return result.recordsets[0];
2862
+ }
2863
+ }
2864
+ throw new Error(
2865
+ "SqlServer adapter execute() must return an array of rows or an object with rows/recordset properties when introspecting."
2866
+ );
2867
+ }
2868
+ #quoteIdentifier(name) {
2869
+ return `[${name.replace(/]/g, "]]")}]`;
2870
+ }
2871
+ #applyTablesFilter(tables, relationships) {
2872
+ return applyTablesFilter(tables, relationships, this.#options.tables);
2873
+ }
2874
+ #formatQualifiedTableName(table) {
2875
+ if (table.schema && table.rawName) {
2876
+ return `${this.#quoteIdentifier(table.schema)}.${this.#quoteIdentifier(table.rawName)}`;
2877
+ }
2878
+ if (table.name.includes(".")) {
2879
+ const [schemaPart, ...rest] = table.name.split(".");
2880
+ const tablePart = rest.join(".") || schemaPart;
2881
+ if (rest.length === 0) {
2882
+ return this.#quoteIdentifier(schemaPart);
2883
+ }
2884
+ return `${this.#quoteIdentifier(schemaPart)}.${this.#quoteIdentifier(tablePart)}`;
2885
+ }
2886
+ return this.#quoteIdentifier(table.name);
2887
+ }
2888
+ #toNumber(value) {
2889
+ if (typeof value === "number" && Number.isFinite(value)) {
2890
+ return value;
2891
+ }
2892
+ if (typeof value === "bigint") {
2893
+ return Number(value);
2894
+ }
2895
+ if (typeof value === "string" && value.trim() !== "") {
2896
+ const parsed = Number(value);
2897
+ return Number.isFinite(parsed) ? parsed : null;
2898
+ }
2899
+ return null;
2900
+ }
2901
+ #classifyRowCount(count) {
2902
+ if (count < 100) {
2903
+ return "tiny";
2904
+ }
2905
+ if (count < 1e3) {
2906
+ return "small";
2907
+ }
2908
+ if (count < 1e4) {
2909
+ return "medium";
2910
+ }
2911
+ if (count < 1e5) {
2912
+ return "large";
2913
+ }
2914
+ return "huge";
2915
+ }
2916
+ #shouldCollectStats(type) {
2917
+ if (!type) {
2918
+ return false;
2919
+ }
2920
+ const normalized = type.toLowerCase();
2921
+ return /int|numeric|decimal|float|real|money|date|time|timestamp|bool/.test(
2922
+ normalized
2923
+ );
2924
+ }
2925
+ #normalizeValue(value) {
2926
+ if (value === null || value === void 0) {
2927
+ return null;
2928
+ }
2929
+ if (typeof value === "string") {
2930
+ return value;
2931
+ }
2932
+ if (typeof value === "number" || typeof value === "bigint") {
2933
+ return String(value);
2934
+ }
2935
+ if (typeof value === "boolean") {
2936
+ return value ? "true" : "false";
2937
+ }
2938
+ if (value instanceof Date) {
2939
+ return value.toISOString();
2940
+ }
2941
+ if (typeof Buffer !== "undefined" && Buffer.isBuffer(value)) {
2942
+ return value.toString("utf-8");
2943
+ }
2944
+ return null;
2945
+ }
2946
+ async #resolveInfo() {
2947
+ const { info } = this.#options;
2948
+ if (!info) {
2949
+ return { dialect: "sqlserver" };
2950
+ }
2951
+ if (typeof info === "function") {
2952
+ return info();
2953
+ }
2954
+ return info;
2955
+ }
2956
+ };
2957
+
2958
+ // packages/text2sql/src/lib/history/sqlite.history.ts
2959
+ import { DatabaseSync } from "node:sqlite";
2960
+
2961
+ // packages/text2sql/src/lib/history/history.sqlite.sql
2962
+ var history_sqlite_default = 'CREATE TABLE IF NOT EXISTS "chats" (\n "id" VARCHAR PRIMARY KEY,\n "title" VARCHAR,\n "userId" VARCHAR\n);\n\nCREATE TABLE IF NOT EXISTS "messages" (\n "id" VARCHAR PRIMARY KEY,\n "chatId" VARCHAR NOT NULL REFERENCES "chats" ("id") ON DELETE CASCADE,\n "createdAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n "role" VARCHAR NOT NULL,\n "content" TEXT NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS "messages_chat_id_idx" ON "messages" ("chatId");\n\nCREATE INDEX IF NOT EXISTS "messages_chat_id_created_at_idx" ON "messages" ("chatId", "createdAt");\n';
2963
+
2964
+ // packages/text2sql/src/lib/history/sqlite.history.ts
2965
+ var SqliteHistory = class extends History {
2966
+ #db;
2967
+ constructor(path3) {
2968
+ super();
2969
+ this.#db = new DatabaseSync(path3);
2970
+ this.#db.exec(history_sqlite_default);
2971
+ }
2972
+ async listChats(userId) {
2973
+ return this.#db.prepare(`SELECT * FROM chats WHERE "userId" = ?`).all(userId);
2974
+ }
2975
+ async getChat(chatId) {
2976
+ const rows = this.#db.prepare(
2977
+ `SELECT
2978
+ c.id as chatId, c."userId", c.title,
2979
+ m.id as messageId, m.role, m."createdAt", m.content
2980
+ FROM chats c
2981
+ LEFT JOIN messages m ON m."chatId" = c.id
2982
+ WHERE c.id = ?
2983
+ ORDER BY m."createdAt" ASC`
2984
+ ).all(chatId);
2985
+ if (!rows.length) return null;
2986
+ const firstRow = rows[0];
2987
+ const chat = {
2988
+ id: firstRow.chatId,
2989
+ userId: firstRow.userId,
2990
+ title: firstRow.title,
2991
+ messages: []
2992
+ };
2993
+ for (const row of rows) {
2994
+ if (row.messageId) {
2995
+ chat.messages.push({
2996
+ id: row.messageId,
2997
+ chatId: firstRow.chatId,
2998
+ role: row.role,
2999
+ createdAt: row.createdAt,
3000
+ content: JSON.parse(row.content)
3001
+ });
3002
+ }
3003
+ }
3004
+ return chat;
3005
+ }
3006
+ async createChat(chat) {
3007
+ this.#db.prepare(`INSERT INTO chats (id, "userId", title) VALUES (?, ?, ?)`).run(chat.id, chat.userId, chat.title || null);
3008
+ return chat;
3009
+ }
3010
+ async upsertChat(chat) {
3011
+ this.#db.prepare(
3012
+ `INSERT INTO chats (id, "userId", title) VALUES (?, ?, ?)
3013
+ ON CONFLICT(id) DO UPDATE SET title = excluded.title, "userId" = excluded."userId"`
3014
+ ).run(chat.id, chat.userId, chat.title || null);
3015
+ return this.getChat(chat.id);
3016
+ }
3017
+ async deleteChat(chatId) {
3018
+ this.#db.prepare(`DELETE FROM chats WHERE id = ?`).run(chatId);
3019
+ }
3020
+ async updateChat(chatId, updates) {
3021
+ if (updates.title !== void 0) {
3022
+ this.#db.prepare(`UPDATE chats SET title = ? WHERE id = ?`).run(updates.title, chatId);
3023
+ }
3024
+ }
3025
+ async addMessage(message) {
3026
+ const createdAt = message.createdAt ? message.createdAt.toISOString() : (/* @__PURE__ */ new Date()).toISOString();
3027
+ this.#db.prepare(
3028
+ `INSERT INTO messages (id, "chatId", role, "createdAt", content) VALUES (?, ?, ?, ?, ?)`
3029
+ ).run(
3030
+ message.id,
3031
+ message.chatId,
3032
+ message.role,
3033
+ createdAt,
3034
+ JSON.stringify(message.content)
3035
+ );
3036
+ }
3037
+ async upsertMessage(message) {
3038
+ const createdAt = message.createdAt ? message.createdAt.toISOString() : (/* @__PURE__ */ new Date()).toISOString();
3039
+ this.#db.prepare(
3040
+ `INSERT INTO messages (id, "chatId", role, "createdAt", content) VALUES (?, ?, ?, ?, ?)
3041
+ ON CONFLICT(id) DO UPDATE SET "chatId" = excluded."chatId", role = excluded.role, "createdAt" = excluded."createdAt", content = excluded.content`
3042
+ ).run(
3043
+ message.id,
3044
+ message.chatId,
3045
+ message.role,
3046
+ createdAt,
3047
+ JSON.stringify(message.content)
3048
+ );
3049
+ return {
3050
+ ...message,
3051
+ createdAt
3052
+ };
3053
+ }
3054
+ async deleteMessage(messageId) {
3055
+ this.#db.prepare(`DELETE FROM messages WHERE id = ?`).run(messageId);
3056
+ }
3057
+ };
3058
+
3059
+ // packages/text2sql/src/lib/history/memory.history.ts
3060
+ var InMemoryHistory = class extends SqliteHistory {
3061
+ constructor() {
3062
+ super(":memory:");
3063
+ }
3064
+ };
3065
+ export {
3066
+ Adapter,
3067
+ BriefCache,
3068
+ History,
3069
+ InMemoryHistory,
3070
+ Postgres,
3071
+ SqlServer,
3072
+ Sqlite,
3073
+ SqliteHistory,
3074
+ Text2Sql,
3075
+ analogy,
3076
+ applyTablesFilter,
3077
+ clarification,
3078
+ example,
3079
+ explain,
3080
+ filterRelationshipsByTables,
3081
+ filterTablesByName,
3082
+ formatError,
3083
+ formatPostgresError,
3084
+ formatSqlServerError,
3085
+ generateBrief,
3086
+ getTablesWithRelated,
3087
+ guardrail,
3088
+ hint,
3089
+ matchesFilter,
3090
+ quirk,
3091
+ styleGuide,
3092
+ suggestionsAgent,
3093
+ term,
3094
+ text2sqlMonolith,
3095
+ text2sqlOnly,
3096
+ toBrief,
3097
+ toInstructions,
3098
+ toTeachables,
3099
+ userProfile,
3100
+ workflow
3101
+ };
3102
+ //# sourceMappingURL=index.js.map