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