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