@deepagents/text2sql 0.10.1 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/README.md +32 -41
  2. package/dist/index.d.ts +1 -6
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +2338 -2395
  5. package/dist/index.js.map +4 -4
  6. package/dist/lib/adapters/adapter.d.ts +13 -1
  7. package/dist/lib/adapters/adapter.d.ts.map +1 -1
  8. package/dist/lib/adapters/groundings/abstract.grounding.d.ts +19 -3
  9. package/dist/lib/adapters/groundings/abstract.grounding.d.ts.map +1 -1
  10. package/dist/lib/adapters/groundings/column-stats.grounding.d.ts +1 -2
  11. package/dist/lib/adapters/groundings/column-stats.grounding.d.ts.map +1 -1
  12. package/dist/lib/adapters/groundings/column-values.grounding.d.ts +1 -2
  13. package/dist/lib/adapters/groundings/column-values.grounding.d.ts.map +1 -1
  14. package/dist/lib/adapters/groundings/constraint.grounding.d.ts +1 -1
  15. package/dist/lib/adapters/groundings/constraint.grounding.d.ts.map +1 -1
  16. package/dist/lib/adapters/groundings/index.js +15 -222
  17. package/dist/lib/adapters/groundings/index.js.map +3 -3
  18. package/dist/lib/adapters/groundings/indexes.grounding.d.ts +1 -1
  19. package/dist/lib/adapters/groundings/indexes.grounding.d.ts.map +1 -1
  20. package/dist/lib/adapters/groundings/info.grounding.d.ts +1 -1
  21. package/dist/lib/adapters/groundings/info.grounding.d.ts.map +1 -1
  22. package/dist/lib/adapters/groundings/report.grounding.d.ts +1 -1
  23. package/dist/lib/adapters/groundings/report.grounding.d.ts.map +1 -1
  24. package/dist/lib/adapters/groundings/row-count.grounding.d.ts +1 -1
  25. package/dist/lib/adapters/groundings/row-count.grounding.d.ts.map +1 -1
  26. package/dist/lib/adapters/groundings/table.grounding.d.ts +3 -3
  27. package/dist/lib/adapters/groundings/table.grounding.d.ts.map +1 -1
  28. package/dist/lib/adapters/groundings/view.grounding.d.ts +1 -1
  29. package/dist/lib/adapters/groundings/view.grounding.d.ts.map +1 -1
  30. package/dist/lib/adapters/mysql/index.js +343 -315
  31. package/dist/lib/adapters/mysql/index.js.map +4 -4
  32. package/dist/lib/adapters/postgres/index.js +385 -357
  33. package/dist/lib/adapters/postgres/index.js.map +4 -4
  34. package/dist/lib/adapters/spreadsheet/index.js +290 -223
  35. package/dist/lib/adapters/spreadsheet/index.js.map +4 -4
  36. package/dist/lib/adapters/sqlite/index.js +307 -279
  37. package/dist/lib/adapters/sqlite/index.js.map +4 -4
  38. package/dist/lib/adapters/sqlserver/index.js +383 -355
  39. package/dist/lib/adapters/sqlserver/index.js.map +4 -4
  40. package/dist/lib/agents/developer.agent.d.ts +33 -23
  41. package/dist/lib/agents/developer.agent.d.ts.map +1 -1
  42. package/dist/lib/agents/sql.agent.d.ts +4 -4
  43. package/dist/lib/agents/sql.agent.d.ts.map +1 -1
  44. package/dist/lib/agents/teachables.agent.d.ts +2 -2
  45. package/dist/lib/agents/teachables.agent.d.ts.map +1 -1
  46. package/dist/lib/agents/text2sql.agent.d.ts +18 -71
  47. package/dist/lib/agents/text2sql.agent.d.ts.map +1 -1
  48. package/dist/lib/fragments/schema.d.ts +214 -0
  49. package/dist/lib/fragments/schema.d.ts.map +1 -0
  50. package/dist/lib/instructions.d.ts +29 -2
  51. package/dist/lib/instructions.d.ts.map +1 -1
  52. package/dist/lib/instructions.js +336 -319
  53. package/dist/lib/instructions.js.map +4 -4
  54. package/dist/lib/sql.d.ts +13 -103
  55. package/dist/lib/sql.d.ts.map +1 -1
  56. package/dist/lib/synthesis/extractors/base-contextual-extractor.d.ts +2 -2
  57. package/dist/lib/synthesis/extractors/base-contextual-extractor.d.ts.map +1 -1
  58. package/dist/lib/synthesis/extractors/message-extractor.d.ts +1 -2
  59. package/dist/lib/synthesis/extractors/message-extractor.d.ts.map +1 -1
  60. package/dist/lib/synthesis/extractors/sql-extractor.d.ts.map +1 -1
  61. package/dist/lib/synthesis/index.js +1794 -572
  62. package/dist/lib/synthesis/index.js.map +4 -4
  63. package/dist/lib/synthesis/synthesizers/depth-evolver.d.ts.map +1 -1
  64. package/dist/lib/synthesis/synthesizers/persona-generator.d.ts +7 -17
  65. package/dist/lib/synthesis/synthesizers/persona-generator.d.ts.map +1 -1
  66. package/dist/lib/synthesis/synthesizers/schema-synthesizer.d.ts +2 -2
  67. package/dist/lib/synthesis/synthesizers/schema-synthesizer.d.ts.map +1 -1
  68. package/dist/lib/synthesis/synthesizers/teachings-generator.d.ts +8 -20
  69. package/dist/lib/synthesis/synthesizers/teachings-generator.d.ts.map +1 -1
  70. package/dist/lib/teach/teachings.d.ts +2 -2
  71. package/dist/lib/teach/teachings.d.ts.map +1 -1
  72. package/package.json +4 -3
  73. package/dist/lib/agents/chat1.agent.d.ts +0 -50
  74. package/dist/lib/agents/chat1.agent.d.ts.map +0 -1
  75. package/dist/lib/agents/chat2.agent.d.ts +0 -68
  76. package/dist/lib/agents/chat2.agent.d.ts.map +0 -1
  77. package/dist/lib/agents/chat3.agent.d.ts +0 -80
  78. package/dist/lib/agents/chat3.agent.d.ts.map +0 -1
  79. package/dist/lib/agents/chat4.agent.d.ts +0 -88
  80. package/dist/lib/agents/chat4.agent.d.ts.map +0 -1
  81. package/dist/lib/history/history.d.ts +0 -41
  82. package/dist/lib/history/history.d.ts.map +0 -1
  83. package/dist/lib/history/memory.history.d.ts +0 -5
  84. package/dist/lib/history/memory.history.d.ts.map +0 -1
  85. package/dist/lib/history/sqlite.history.d.ts +0 -15
  86. package/dist/lib/history/sqlite.history.d.ts.map +0 -1
  87. package/dist/lib/memory/memory.prompt.d.ts +0 -3
  88. package/dist/lib/memory/memory.prompt.d.ts.map +0 -1
  89. package/dist/lib/memory/memory.store.d.ts +0 -5
  90. package/dist/lib/memory/memory.store.d.ts.map +0 -1
  91. package/dist/lib/memory/sqlite.store.d.ts +0 -14
  92. package/dist/lib/memory/sqlite.store.d.ts.map +0 -1
  93. package/dist/lib/memory/store.d.ts +0 -40
  94. package/dist/lib/memory/store.d.ts.map +0 -1
  95. package/dist/lib/teach/teachables.d.ts +0 -648
  96. package/dist/lib/teach/teachables.d.ts.map +0 -1
  97. package/dist/lib/teach/xml.d.ts +0 -6
  98. package/dist/lib/teach/xml.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -1,3 +1,94 @@
1
+ // packages/text2sql/src/lib/fragments/schema.ts
2
+ function dialectInfo(input) {
3
+ return {
4
+ name: "dialectInfo",
5
+ data: {
6
+ dialect: input.dialect,
7
+ ...input.version && { version: input.version },
8
+ ...input.database && { database: input.database }
9
+ }
10
+ };
11
+ }
12
+ function table(input) {
13
+ return {
14
+ name: "table",
15
+ data: {
16
+ name: input.name,
17
+ ...input.schema && { schema: input.schema },
18
+ ...input.rowCount != null && { rowCount: input.rowCount },
19
+ ...input.sizeHint && { sizeHint: input.sizeHint },
20
+ columns: input.columns,
21
+ ...input.indexes?.length && { indexes: input.indexes },
22
+ ...input.constraints?.length && { constraints: input.constraints }
23
+ }
24
+ };
25
+ }
26
+ function column(input) {
27
+ return {
28
+ name: "column",
29
+ data: {
30
+ name: input.name,
31
+ type: input.type,
32
+ ...input.pk && { pk: true },
33
+ ...input.fk && { fk: input.fk },
34
+ ...input.unique && { unique: true },
35
+ ...input.notNull && { notNull: true },
36
+ ...input.default && { default: input.default },
37
+ ...input.indexed && { indexed: true },
38
+ ...input.values?.length && { values: input.values },
39
+ ...input.stats && { stats: input.stats }
40
+ }
41
+ };
42
+ }
43
+ function index(input) {
44
+ return {
45
+ name: "index",
46
+ data: {
47
+ name: input.name,
48
+ columns: input.columns,
49
+ ...input.unique && { unique: true },
50
+ ...input.type && { type: input.type }
51
+ }
52
+ };
53
+ }
54
+ function constraint(input) {
55
+ return {
56
+ name: "constraint",
57
+ data: {
58
+ name: input.name,
59
+ type: input.type,
60
+ ...input.columns?.length && { columns: input.columns },
61
+ ...input.definition && { definition: input.definition },
62
+ ...input.defaultValue && { defaultValue: input.defaultValue },
63
+ ...input.referencedTable && { referencedTable: input.referencedTable },
64
+ ...input.referencedColumns?.length && {
65
+ referencedColumns: input.referencedColumns
66
+ }
67
+ }
68
+ };
69
+ }
70
+ function view(input) {
71
+ return {
72
+ name: "view",
73
+ data: {
74
+ name: input.name,
75
+ ...input.schema && { schema: input.schema },
76
+ columns: input.columns,
77
+ ...input.definition && { definition: input.definition }
78
+ }
79
+ };
80
+ }
81
+ function relationship(input) {
82
+ return {
83
+ name: "relationship",
84
+ data: {
85
+ from: input.from,
86
+ to: input.to,
87
+ ...input.cardinality && { cardinality: input.cardinality }
88
+ }
89
+ };
90
+ }
91
+
1
92
  // packages/text2sql/src/lib/adapters/groundings/context.ts
2
93
  function createGroundingContext() {
3
94
  return {
@@ -10,24 +101,169 @@ function createGroundingContext() {
10
101
 
11
102
  // packages/text2sql/src/lib/adapters/adapter.ts
12
103
  var Adapter = class {
104
+ /**
105
+ * Introspect the database schema and return context fragments.
106
+ *
107
+ * Executes all configured groundings to populate the context, then
108
+ * generates fragments from the complete context data.
109
+ *
110
+ * @param ctx - Optional grounding context for sharing state between groundings
111
+ * @returns Array of context fragments representing the database schema
112
+ */
13
113
  async introspect(ctx = createGroundingContext()) {
14
- const lines = [];
15
114
  for (const fn of this.grounding) {
16
115
  const grounding = fn(this);
17
- lines.push({
18
- tag: grounding.tag,
19
- fn: await grounding.execute(ctx)
20
- });
116
+ await grounding.execute(ctx);
117
+ }
118
+ return this.#toSchemaFragments(ctx);
119
+ }
120
+ /**
121
+ * Convert complete grounding context to schema fragments.
122
+ * Called after all groundings have populated ctx with data.
123
+ */
124
+ #toSchemaFragments(ctx) {
125
+ const fragments2 = [];
126
+ if (ctx.info) {
127
+ fragments2.push(
128
+ dialectInfo({
129
+ dialect: ctx.info.dialect,
130
+ version: ctx.info.version,
131
+ database: ctx.info.database
132
+ })
133
+ );
134
+ }
135
+ for (const t of ctx.tables) {
136
+ fragments2.push(this.#tableToFragment(t));
137
+ }
138
+ for (const v of ctx.views) {
139
+ fragments2.push(this.#viewToFragment(v));
140
+ }
141
+ const tableMap = new Map(ctx.tables.map((t) => [t.name, t]));
142
+ for (const rel of ctx.relationships) {
143
+ const sourceTable = tableMap.get(rel.table);
144
+ const targetTable = tableMap.get(rel.referenced_table);
145
+ fragments2.push(
146
+ this.#relationshipToFragment(rel, sourceTable, targetTable)
147
+ );
148
+ }
149
+ if (ctx.report) {
150
+ fragments2.push({ name: "businessContext", data: ctx.report });
151
+ }
152
+ return fragments2;
153
+ }
154
+ /**
155
+ * Convert a Table to a table fragment with nested column, index, and constraint fragments.
156
+ */
157
+ #tableToFragment(t) {
158
+ const pkConstraint = t.constraints?.find((c) => c.type === "PRIMARY_KEY");
159
+ const pkColumns = new Set(pkConstraint?.columns ?? []);
160
+ const notNullColumns = new Set(
161
+ t.constraints?.filter((c) => c.type === "NOT_NULL").flatMap((c) => c.columns ?? []) ?? []
162
+ );
163
+ const defaultByColumn = /* @__PURE__ */ new Map();
164
+ for (const c of t.constraints?.filter((c2) => c2.type === "DEFAULT") ?? []) {
165
+ for (const col of c.columns ?? []) {
166
+ if (c.defaultValue != null) {
167
+ defaultByColumn.set(col, c.defaultValue);
168
+ }
169
+ }
21
170
  }
22
- return lines.map(({ fn, tag }) => {
23
- const description = fn();
24
- if (description === null) {
25
- return "";
171
+ const uniqueColumns = new Set(
172
+ t.constraints?.filter((c) => c.type === "UNIQUE" && c.columns?.length === 1).flatMap((c) => c.columns ?? []) ?? []
173
+ );
174
+ const fkByColumn = /* @__PURE__ */ new Map();
175
+ for (const c of t.constraints?.filter((c2) => c2.type === "FOREIGN_KEY") ?? []) {
176
+ const cols = c.columns ?? [];
177
+ const refCols = c.referencedColumns ?? [];
178
+ for (let i = 0; i < cols.length; i++) {
179
+ const refCol = refCols[i] ?? refCols[0] ?? cols[i];
180
+ fkByColumn.set(cols[i], `${c.referencedTable}.${refCol}`);
26
181
  }
27
- return `<${tag}>
28
- ${description}
29
- </${tag}>`;
30
- }).join("\n");
182
+ }
183
+ const columnFragments = t.columns.map(
184
+ (col) => column({
185
+ name: col.name,
186
+ type: col.type,
187
+ pk: pkColumns.has(col.name) || void 0,
188
+ fk: fkByColumn.get(col.name),
189
+ unique: uniqueColumns.has(col.name) || void 0,
190
+ notNull: notNullColumns.has(col.name) || void 0,
191
+ default: defaultByColumn.get(col.name),
192
+ indexed: col.isIndexed || void 0,
193
+ values: col.values,
194
+ stats: col.stats
195
+ })
196
+ );
197
+ const indexFragments = (t.indexes ?? []).map(
198
+ (idx) => index({
199
+ name: idx.name,
200
+ columns: idx.columns,
201
+ unique: idx.unique,
202
+ type: idx.type
203
+ })
204
+ );
205
+ const constraintFragments = (t.constraints ?? []).filter(
206
+ (c) => c.type === "CHECK" || c.type === "UNIQUE" && (c.columns?.length ?? 0) > 1
207
+ ).map(
208
+ (c) => constraint({
209
+ name: c.name,
210
+ type: c.type,
211
+ columns: c.columns,
212
+ definition: c.definition
213
+ })
214
+ );
215
+ return table({
216
+ name: t.name,
217
+ schema: t.schema,
218
+ rowCount: t.rowCount,
219
+ sizeHint: t.sizeHint,
220
+ columns: columnFragments,
221
+ indexes: indexFragments.length > 0 ? indexFragments : void 0,
222
+ constraints: constraintFragments.length > 0 ? constraintFragments : void 0
223
+ });
224
+ }
225
+ /**
226
+ * Convert a View to a view fragment with nested column fragments.
227
+ */
228
+ #viewToFragment(v) {
229
+ const columnFragments = v.columns.map(
230
+ (col) => column({
231
+ name: col.name,
232
+ type: col.type,
233
+ values: col.values,
234
+ stats: col.stats
235
+ })
236
+ );
237
+ return view({
238
+ name: v.name,
239
+ schema: v.schema,
240
+ columns: columnFragments,
241
+ definition: v.definition
242
+ });
243
+ }
244
+ /**
245
+ * Convert a Relationship to a relationship fragment.
246
+ * Infers cardinality from row counts if available.
247
+ */
248
+ #relationshipToFragment(rel, sourceTable, targetTable) {
249
+ const sourceCount = sourceTable?.rowCount;
250
+ const targetCount = targetTable?.rowCount;
251
+ let cardinality;
252
+ if (sourceCount != null && targetCount != null && targetCount > 0) {
253
+ const ratio = sourceCount / targetCount;
254
+ if (ratio > 5) {
255
+ cardinality = "many-to-one";
256
+ } else if (ratio < 1.2 && ratio > 0.8) {
257
+ cardinality = "one-to-one";
258
+ } else if (ratio < 0.2) {
259
+ cardinality = "one-to-many";
260
+ }
261
+ }
262
+ return relationship({
263
+ from: { table: rel.table, columns: rel.from },
264
+ to: { table: rel.referenced_table, columns: rel.to },
265
+ cardinality
266
+ });
31
267
  }
32
268
  /**
33
269
  * Convert unknown database value to number.
@@ -82,7 +318,7 @@ ${description}
82
318
  };
83
319
  function filterTablesByName(tables, filter) {
84
320
  if (!filter) return tables;
85
- return tables.filter((table) => matchesFilter(table.name, filter));
321
+ return tables.filter((table2) => matchesFilter(table2.name, filter));
86
322
  }
87
323
  function filterRelationshipsByTables(relationships, tableNames) {
88
324
  if (tableNames === void 0) {
@@ -103,7 +339,7 @@ function applyTablesFilter(tables, relationships, filter) {
103
339
  getTablesWithRelated(tables, relationships, filter)
104
340
  );
105
341
  return {
106
- tables: tables.filter((table) => allowedNames.has(table.name)),
342
+ tables: tables.filter((table2) => allowedNames.has(table2.name)),
107
343
  relationships: filterRelationshipsByTables(relationships, allowedNames)
108
344
  };
109
345
  }
@@ -150,629 +386,1841 @@ function getTablesWithRelated(allTables, relationships, filter) {
150
386
  }
151
387
 
152
388
  // packages/text2sql/src/lib/agents/developer.agent.ts
153
- import { groq as groq3 } from "@ai-sdk/groq";
154
389
  import { tool } from "ai";
155
390
  import dedent2 from "dedent";
156
- import z3 from "zod";
157
- import { agent as agent3, generate as generate2, toState } from "@deepagents/agent";
158
- import { scratchpad_tool } from "@deepagents/toolbox";
159
-
160
- // packages/text2sql/src/lib/agents/explainer.agent.ts
161
- import { groq } from "@ai-sdk/groq";
162
- import dedent from "dedent";
163
- import z from "zod";
164
- import { agent } from "@deepagents/agent";
165
- var explainerAgent = agent({
166
- name: "explainer",
167
- model: groq("openai/gpt-oss-20b"),
168
- prompt: (state) => dedent`
169
- You are an expert SQL tutor.
170
- Explain the following SQL query in plain English to a non-technical user.
171
- Focus on the intent and logic, not the syntax.
172
-
173
- <sql>
174
- ${state?.sql}
175
- </sql>
176
- `,
177
- output: z.object({
178
- explanation: z.string().describe("The explanation of the SQL query.")
179
- })
180
- });
181
-
182
- // packages/text2sql/src/lib/agents/sql.agent.ts
183
- import { groq as groq2 } from "@ai-sdk/groq";
184
- import {
185
- APICallError,
186
- JSONParseError,
187
- NoContentGeneratedError,
188
- NoObjectGeneratedError,
189
- NoOutputGeneratedError,
190
- TypeValidationError,
191
- defaultSettingsMiddleware,
192
- wrapLanguageModel
193
- } from "ai";
194
- import { Console } from "node:console";
195
- import { createWriteStream } from "node:fs";
196
- import pRetry from "p-retry";
197
391
  import z2 from "zod";
392
+ import { generate, toState } from "@deepagents/agent";
393
+
394
+ // packages/context/dist/index.js
395
+ import { encode } from "gpt-tokenizer";
396
+ import { generateId } from "ai";
397
+ import pluralize from "pluralize";
398
+ import { titlecase } from "stringcase";
399
+ import { defineCommand } from "just-bash";
400
+ import spawn from "nano-spawn";
401
+ import "bash-tool";
402
+ import spawn2 from "nano-spawn";
198
403
  import {
199
- agent as agent2,
200
- generate,
201
- toOutput,
202
- user
203
- } from "@deepagents/agent";
204
-
205
- // packages/text2sql/src/lib/teach/xml.ts
206
- function wrapBlock(tag, children) {
207
- const content = children.filter((child) => Boolean(child)).join("\n");
208
- if (!content) {
209
- return "";
404
+ createBashTool
405
+ } from "bash-tool";
406
+ import YAML from "yaml";
407
+ import { DatabaseSync } from "node:sqlite";
408
+ import {
409
+ Output,
410
+ convertToModelMessages,
411
+ createUIMessageStream,
412
+ generateId as generateId2,
413
+ generateText,
414
+ smoothStream,
415
+ stepCountIs,
416
+ streamText
417
+ } from "ai";
418
+ import chalk from "chalk";
419
+ import "zod";
420
+ import "@deepagents/agent";
421
+ var defaultTokenizer = {
422
+ encode(text) {
423
+ return encode(text);
424
+ },
425
+ count(text) {
426
+ return encode(text).length;
210
427
  }
211
- return `<${tag}>
212
- ${indentBlock(content, 2)}
213
- </${tag}>`;
214
- }
215
- function list(tag, values, childTag) {
216
- if (!values.length) {
217
- return "";
428
+ };
429
+ var ModelsRegistry = class {
430
+ #cache = /* @__PURE__ */ new Map();
431
+ #loaded = false;
432
+ #tokenizers = /* @__PURE__ */ new Map();
433
+ #defaultTokenizer = defaultTokenizer;
434
+ /**
435
+ * Load models data from models.dev API
436
+ */
437
+ async load() {
438
+ if (this.#loaded) return;
439
+ const response = await fetch("https://models.dev/api.json");
440
+ if (!response.ok) {
441
+ throw new Error(`Failed to fetch models: ${response.statusText}`);
442
+ }
443
+ const data = await response.json();
444
+ for (const [providerId, provider] of Object.entries(data)) {
445
+ for (const [modelId, model] of Object.entries(provider.models)) {
446
+ const info = {
447
+ id: model.id,
448
+ name: model.name,
449
+ family: model.family,
450
+ cost: model.cost,
451
+ limit: model.limit,
452
+ provider: providerId
453
+ };
454
+ this.#cache.set(`${providerId}:${modelId}`, info);
455
+ }
456
+ }
457
+ this.#loaded = true;
218
458
  }
219
- const children = values.map((value) => leaf(childTag, value)).join("\n");
220
- return `<${tag}>
221
- ${indentBlock(children, 2)}
222
- </${tag}>`;
223
- }
224
- function leaf(tag, value) {
225
- const safe = escapeXml(value);
226
- if (safe.includes("\n")) {
227
- return `<${tag}>
228
- ${indentBlock(safe, 2)}
229
- </${tag}>`;
459
+ /**
460
+ * Get model info by ID
461
+ * @param modelId - Model ID (e.g., "openai:gpt-4o")
462
+ */
463
+ get(modelId) {
464
+ return this.#cache.get(modelId);
230
465
  }
231
- return `<${tag}>${safe}</${tag}>`;
232
- }
233
- function indentBlock(text, spaces) {
234
- if (!text.trim()) {
235
- return "";
466
+ /**
467
+ * Check if a model exists in the registry
468
+ */
469
+ has(modelId) {
470
+ return this.#cache.has(modelId);
236
471
  }
237
- const padding = " ".repeat(spaces);
238
- return text.split("\n").map((line) => line.length ? padding + line : padding).join("\n");
239
- }
240
- function escapeXml(value) {
241
- if (value == null) {
242
- return "";
472
+ /**
473
+ * List all available model IDs
474
+ */
475
+ list() {
476
+ return [...this.#cache.keys()];
243
477
  }
244
- return value.replaceAll(/&/g, "&amp;").replaceAll(/</g, "&lt;").replaceAll(/>/g, "&gt;").replaceAll(/"/g, "&quot;").replaceAll(/'/g, "&apos;");
245
- }
246
-
247
- // packages/text2sql/src/lib/teach/teachables.ts
248
- function term(name, definition) {
249
- return {
250
- type: "term",
251
- encode: () => ({ type: "term", name, definition }),
252
- decode: () => wrapBlock("term", [leaf("name", name), leaf("definition", definition)])
253
- };
254
- }
255
- function hint(text) {
256
- return {
257
- type: "hint",
258
- encode: () => ({ type: "hint", text }),
259
- decode: () => leaf("hint", text)
260
- };
261
- }
262
- function guardrail(input) {
263
- const { rule, reason, action } = input;
264
- return {
265
- type: "guardrail",
266
- encode: () => ({ type: "guardrail", rule, reason, action }),
267
- decode: () => wrapBlock("guardrail", [
268
- leaf("rule", rule),
269
- reason ? leaf("reason", reason) : "",
270
- action ? leaf("action", action) : ""
271
- ])
272
- };
273
- }
274
- function explain(input) {
275
- const { concept, explanation, therefore } = input;
276
- return {
277
- type: "explain",
278
- encode: () => ({ type: "explain", concept, explanation, therefore }),
279
- decode: () => wrapBlock("explanation", [
280
- leaf("concept", concept),
281
- leaf("details", explanation),
282
- therefore ? leaf("therefore", therefore) : ""
283
- ])
284
- };
285
- }
286
- function example(input) {
287
- const { question, answer, note } = input;
288
- return {
289
- type: "example",
290
- encode: () => ({ type: "example", question, answer, note }),
291
- decode: () => wrapBlock("example", [
292
- leaf("question", question),
293
- leaf("answer", answer),
294
- note ? leaf("note", note) : ""
295
- ])
296
- };
297
- }
298
- function clarification(input) {
299
- const { when, ask, reason } = input;
300
- return {
301
- type: "clarification",
302
- encode: () => ({ type: "clarification", when, ask, reason }),
303
- decode: () => wrapBlock("clarification", [
304
- leaf("when", when),
305
- leaf("ask", ask),
306
- leaf("reason", reason)
307
- ])
308
- };
309
- }
310
- function workflow(input) {
311
- const { task, steps, triggers, notes } = input;
312
- return {
313
- type: "workflow",
314
- encode: () => ({ type: "workflow", task, steps, triggers, notes }),
315
- decode: () => wrapBlock("workflow", [
316
- leaf("task", task),
317
- triggers?.length ? list("triggers", triggers, "trigger") : "",
318
- list("steps", steps, "step"),
319
- notes ? leaf("notes", notes) : ""
320
- ])
321
- };
322
- }
323
- function quirk(input) {
324
- const { issue, workaround } = input;
325
- return {
326
- type: "quirk",
327
- encode: () => ({ type: "quirk", issue, workaround }),
328
- decode: () => wrapBlock("quirk", [
329
- leaf("issue", issue),
330
- leaf("workaround", workaround)
331
- ])
332
- };
333
- }
334
- function styleGuide(input) {
335
- const { prefer, never, always } = input;
336
- return {
337
- type: "styleGuide",
338
- encode: () => ({ type: "styleGuide", prefer, never, always }),
339
- decode: () => wrapBlock("style_guide", [
340
- leaf("prefer", prefer),
341
- always ? leaf("always", always) : "",
342
- never ? leaf("never", never) : ""
343
- ])
344
- };
345
- }
346
- function analogy(input) {
347
- const { concept, relationship, insight, therefore, pitfall } = input;
348
- return {
349
- type: "analogy",
350
- encode: () => ({
351
- type: "analogy",
352
- concept,
353
- relationship,
354
- insight,
355
- therefore,
356
- pitfall
357
- }),
358
- decode: () => wrapBlock("analogy", [
359
- list("concepts", concept, "concept"),
360
- leaf("relationship", relationship),
361
- insight ? leaf("insight", insight) : "",
362
- therefore ? leaf("therefore", therefore) : "",
363
- pitfall ? leaf("pitfall", pitfall) : ""
364
- ])
365
- };
366
- }
367
- function glossary(entries) {
368
- return {
369
- type: "glossary",
370
- encode: () => ({ type: "glossary", entries }),
371
- decode: () => wrapBlock(
372
- "glossary",
373
- Object.entries(entries).map(
374
- ([term2, sql]) => wrapBlock("entry", [leaf("term", term2), leaf("sql", sql)])
375
- )
376
- )
377
- };
478
+ /**
479
+ * Register a custom tokenizer for specific model families
480
+ * @param family - Model family name (e.g., "llama", "claude")
481
+ * @param tokenizer - Tokenizer implementation
482
+ */
483
+ registerTokenizer(family, tokenizer) {
484
+ this.#tokenizers.set(family, tokenizer);
485
+ }
486
+ /**
487
+ * Set the default tokenizer used when no family-specific tokenizer is registered
488
+ */
489
+ setDefaultTokenizer(tokenizer) {
490
+ this.#defaultTokenizer = tokenizer;
491
+ }
492
+ /**
493
+ * Get the appropriate tokenizer for a model
494
+ */
495
+ getTokenizer(modelId) {
496
+ const model = this.get(modelId);
497
+ if (model) {
498
+ const familyTokenizer = this.#tokenizers.get(model.family);
499
+ if (familyTokenizer) {
500
+ return familyTokenizer;
501
+ }
502
+ }
503
+ return this.#defaultTokenizer;
504
+ }
505
+ /**
506
+ * Estimate token count and cost for given text and model
507
+ * @param modelId - Model ID to use for pricing (e.g., "openai:gpt-4o")
508
+ * @param input - Input text (prompt)
509
+ */
510
+ estimate(modelId, input) {
511
+ const model = this.get(modelId);
512
+ if (!model) {
513
+ throw new Error(
514
+ `Model "${modelId}" not found. Call load() first or check model ID.`
515
+ );
516
+ }
517
+ const tokenizer = this.getTokenizer(modelId);
518
+ const tokens = tokenizer.count(input);
519
+ const cost = tokens / 1e6 * model.cost.input;
520
+ return {
521
+ model: model.id,
522
+ provider: model.provider,
523
+ tokens,
524
+ cost,
525
+ limits: {
526
+ context: model.limit.context,
527
+ output: model.limit.output,
528
+ exceedsContext: tokens > model.limit.context
529
+ },
530
+ fragments: []
531
+ };
532
+ }
533
+ };
534
+ var _registry = null;
535
+ function getModelsRegistry() {
536
+ if (!_registry) {
537
+ _registry = new ModelsRegistry();
538
+ }
539
+ return _registry;
378
540
  }
379
- function identity(input) {
380
- const { name, role } = input;
381
- return {
382
- type: "identity",
383
- encode: () => ({ type: "identity", name, role }),
384
- decode: () => wrapBlock("identity", [
385
- name ? leaf("name", name) : "",
386
- role ? leaf("role", role) : ""
387
- ])
388
- };
541
+ function isFragment(data) {
542
+ return typeof data === "object" && data !== null && "name" in data && "data" in data && typeof data.name === "string";
389
543
  }
390
- function persona(input) {
391
- const { name, role, tone } = input;
392
- return {
393
- type: "persona",
394
- encode: () => ({ type: "persona", name, role, tone: tone ?? "" }),
395
- decode: () => wrapBlock("persona", [
396
- leaf("name", name),
397
- leaf("role", role),
398
- tone ? leaf("tone", tone) : ""
399
- ])
400
- };
544
+ function isFragmentObject(data) {
545
+ return typeof data === "object" && data !== null && !Array.isArray(data) && !isFragment(data);
401
546
  }
402
- function alias(termName, meaning) {
403
- return {
404
- type: "alias",
405
- encode: () => ({ type: "alias", term: termName, meaning }),
406
- decode: () => wrapBlock("alias", [leaf("term", termName), leaf("meaning", meaning)])
407
- };
547
+ function isMessageFragment(fragment2) {
548
+ return fragment2.type === "message";
408
549
  }
409
- function preference(aspect, value) {
550
+ function user(content) {
551
+ const message2 = typeof content === "string" ? {
552
+ id: generateId(),
553
+ role: "user",
554
+ parts: [{ type: "text", text: content }]
555
+ } : content;
410
556
  return {
411
- type: "preference",
412
- encode: () => ({ type: "preference", aspect, value }),
413
- decode: () => wrapBlock("preference", [leaf("aspect", aspect), leaf("value", value)])
557
+ id: message2.id,
558
+ name: "user",
559
+ data: "content",
560
+ type: "message",
561
+ persist: true,
562
+ codec: {
563
+ decode() {
564
+ return message2;
565
+ },
566
+ encode() {
567
+ return message2;
568
+ }
569
+ }
414
570
  };
415
571
  }
416
- function context(description) {
572
+ function assistant(message2) {
417
573
  return {
418
- type: "context",
419
- encode: () => ({ type: "context", description }),
420
- decode: () => leaf("context", description)
574
+ id: message2.id,
575
+ name: "assistant",
576
+ data: "content",
577
+ type: "message",
578
+ persist: true,
579
+ codec: {
580
+ decode() {
581
+ return message2;
582
+ },
583
+ encode() {
584
+ return message2;
585
+ }
586
+ }
421
587
  };
422
588
  }
423
- function correction(subject, clarification2) {
589
+ function message(content) {
590
+ const message2 = typeof content === "string" ? {
591
+ id: generateId(),
592
+ role: "user",
593
+ parts: [{ type: "text", text: content }]
594
+ } : content;
424
595
  return {
425
- type: "correction",
426
- encode: () => ({ type: "correction", subject, clarification: clarification2 }),
427
- decode: () => wrapBlock("correction", [
428
- leaf("subject", subject),
429
- leaf("clarification", clarification2)
430
- ])
596
+ id: message2.id,
597
+ name: "message",
598
+ data: "content",
599
+ type: "message",
600
+ persist: true,
601
+ codec: {
602
+ decode() {
603
+ return message2;
604
+ },
605
+ encode() {
606
+ return message2;
607
+ }
608
+ }
431
609
  };
432
610
  }
433
- function teachable(tag, ...teachables) {
434
- return {
435
- type: "user_profile",
436
- encode: () => teachables[0]?.encode() ?? { type: "context", description: "" },
437
- decode: () => toInstructions(tag, ...teachables)
438
- };
611
+ function assistantText(content, options) {
612
+ const id = options?.id ?? crypto.randomUUID();
613
+ return assistant({
614
+ id,
615
+ role: "assistant",
616
+ parts: [{ type: "text", text: content }]
617
+ });
439
618
  }
440
- function toInstructions(tag, ...teachables) {
441
- if (!teachables.length) {
442
- return "";
619
+ var ContextRenderer = class {
620
+ options;
621
+ constructor(options = {}) {
622
+ this.options = options;
443
623
  }
444
- const grouped = /* @__PURE__ */ new Map();
445
- for (const teachable2 of teachables) {
446
- const existing = grouped.get(teachable2.type) ?? [];
447
- existing.push(teachable2);
448
- grouped.set(teachable2.type, existing);
624
+ /**
625
+ * Check if data is a primitive (string, number, boolean).
626
+ */
627
+ isPrimitive(data) {
628
+ return typeof data === "string" || typeof data === "number" || typeof data === "boolean";
449
629
  }
450
- const definedTypes = new Set(SECTION_ORDER.map((s) => s.type));
451
- const sections = SECTION_ORDER.map(({ type, tag: tag2 }) => {
452
- const items = grouped.get(type);
453
- if (!items?.length) {
454
- return "";
630
+ /**
631
+ * Group fragments by name for groupFragments option.
632
+ */
633
+ groupByName(fragments2) {
634
+ const groups = /* @__PURE__ */ new Map();
635
+ for (const fragment2 of fragments2) {
636
+ const existing = groups.get(fragment2.name) ?? [];
637
+ existing.push(fragment2);
638
+ groups.set(fragment2.name, existing);
455
639
  }
456
- const renderedItems = items.map((item) => item.decode().trim()).filter(Boolean).map((item) => indentBlock(item, 2)).join("\n");
457
- if (!renderedItems.length) {
640
+ return groups;
641
+ }
642
+ /**
643
+ * Template method - dispatches value to appropriate handler.
644
+ */
645
+ renderValue(key, value, ctx) {
646
+ if (value == null) {
458
647
  return "";
459
648
  }
460
- return `<${tag2}>
461
- ${renderedItems}
462
- </${tag2}>`;
463
- }).filter((section) => Boolean(section));
464
- for (const [type, items] of grouped) {
465
- if (definedTypes.has(type)) {
466
- continue;
649
+ if (isFragment(value)) {
650
+ return this.renderFragment(value, ctx);
651
+ }
652
+ if (Array.isArray(value)) {
653
+ return this.renderArray(key, value, ctx);
467
654
  }
468
- const renderedItems = items.map((item) => item.decode().trim()).filter(Boolean).map((item) => indentBlock(item, 2)).join("\n");
469
- if (renderedItems.length) {
470
- sections.push(renderedItems);
655
+ if (isFragmentObject(value)) {
656
+ return this.renderObject(key, value, ctx);
471
657
  }
658
+ return this.renderPrimitive(key, String(value), ctx);
472
659
  }
473
- if (!sections.length) {
474
- return "";
660
+ /**
661
+ * Render all entries of an object.
662
+ */
663
+ renderEntries(data, ctx) {
664
+ return Object.entries(data).map(([key, value]) => this.renderValue(key, value, ctx)).filter(Boolean);
475
665
  }
476
- const content = indentBlock(sections.join("\n"), 2);
477
- return `<${tag}>
478
- ${content}
479
- </${tag}>`;
480
- }
481
- var SECTION_ORDER = [
482
- // User context (render first - most important for personalization)
483
- { type: "identity", tag: "identity" },
484
- { type: "persona", tag: "persona" },
485
- { type: "context", tag: "user_context" },
486
- { type: "preference", tag: "user_preferences" },
487
- { type: "alias", tag: "user_vocabulary" },
488
- { type: "correction", tag: "user_corrections" },
489
- // Domain knowledge
490
- { type: "guardrail", tag: "guardrails" },
491
- { type: "styleGuide", tag: "style_guides" },
492
- { type: "hint", tag: "hints" },
493
- { type: "clarification", tag: "clarifications" },
494
- { type: "workflow", tag: "workflows" },
495
- { type: "quirk", tag: "quirks" },
496
- { type: "term", tag: "terminology" },
497
- { type: "explain", tag: "explanations" },
498
- { type: "analogy", tag: "analogies" },
499
- { type: "glossary", tag: "glossary" },
500
- { type: "example", tag: "examples" }
501
- ];
502
- function toTeachables(generated) {
503
- return generated.map((item) => {
504
- switch (item.type) {
505
- case "persona":
506
- return persona({ name: item.name, role: item.role, tone: item.tone });
507
- case "term":
508
- return term(item.name, item.definition);
509
- case "hint":
510
- return hint(item.text);
511
- case "guardrail":
512
- return guardrail({
513
- rule: item.rule,
514
- reason: item.reason,
515
- action: item.action
516
- });
517
- case "explain":
518
- return explain({
519
- concept: item.concept,
520
- explanation: item.explanation,
521
- therefore: item.therefore
522
- });
523
- case "example":
524
- return example({
525
- question: item.question,
526
- answer: item.answer,
527
- note: item.note
528
- });
529
- case "clarification":
530
- return clarification({
531
- when: item.when,
532
- ask: item.ask,
533
- reason: item.reason
534
- });
535
- case "workflow":
536
- return workflow({
537
- task: item.task,
538
- steps: item.steps,
539
- triggers: item.triggers,
540
- notes: item.notes
541
- });
542
- case "quirk":
543
- return quirk({
544
- issue: item.issue,
545
- workaround: item.workaround
546
- });
547
- case "styleGuide":
548
- return styleGuide({
549
- prefer: item.prefer,
550
- never: item.never,
551
- always: item.always
552
- });
553
- case "analogy":
554
- return analogy({
555
- concept: item.concept,
556
- relationship: item.relationship,
557
- insight: item.insight,
558
- therefore: item.therefore,
559
- pitfall: item.pitfall
560
- });
561
- case "glossary":
562
- return glossary(item.entries);
563
- // User-specific teachable types
564
- case "identity":
565
- return identity({ name: item.name, role: item.role });
566
- case "alias":
567
- return alias(item.term, item.meaning);
568
- case "preference":
569
- return preference(item.aspect, item.value);
570
- case "context":
571
- return context(item.description);
572
- case "correction":
573
- return correction(item.subject, item.clarification);
666
+ };
667
+ var XmlRenderer = class extends ContextRenderer {
668
+ render(fragments2) {
669
+ return fragments2.map((f) => this.#renderTopLevel(f)).filter(Boolean).join("\n");
670
+ }
671
+ #renderTopLevel(fragment2) {
672
+ if (this.isPrimitive(fragment2.data)) {
673
+ return this.#leafRoot(fragment2.name, String(fragment2.data));
574
674
  }
575
- });
576
- }
577
-
578
- // packages/text2sql/src/lib/agents/sql.agent.ts
579
- var logger = new Console({
580
- stdout: createWriteStream("./sql-agent.log", { flags: "a" }),
581
- stderr: createWriteStream("./sql-agent-error.log", { flags: "a" }),
582
- inspectOptions: { depth: null }
583
- });
584
- var RETRY_TEMPERATURES = [0, 0.2, 0.3];
585
- var sqlQueryAgent = agent2({
586
- name: "text2sql",
587
- model: groq2("openai/gpt-oss-20b"),
588
- logging: process.env.AGENT_LOGGING === "true",
589
- output: z2.union([
590
- z2.object({
591
- sql: z2.string().describe("The SQL query that answers the question"),
592
- reasoning: z2.string().optional().describe("The reasoning steps taken to generate the SQL")
593
- }),
594
- z2.object({
595
- error: z2.string().describe(
596
- "Error message explaining why the question cannot be answered with the given schema"
597
- )
598
- })
599
- ]),
600
- prompt: (state) => {
601
- return `
602
- ${state?.teachings || ""}
603
- ${state?.introspection || ""}
604
- `;
675
+ if (Array.isArray(fragment2.data)) {
676
+ return this.#renderArray(fragment2.name, fragment2.data, 0);
677
+ }
678
+ if (isFragment(fragment2.data)) {
679
+ const child = this.renderFragment(fragment2.data, { depth: 1, path: [] });
680
+ return this.#wrap(fragment2.name, [child]);
681
+ }
682
+ return this.#wrap(
683
+ fragment2.name,
684
+ this.renderEntries(fragment2.data, { depth: 1, path: [] })
685
+ );
605
686
  }
606
- });
607
- function extractSql(output) {
608
- const match = output.match(/```sql\n?([\s\S]*?)```/);
609
- return match ? match[1].trim() : output.trim();
687
+ #renderArray(name, items, depth) {
688
+ const fragmentItems = items.filter(isFragment);
689
+ const nonFragmentItems = items.filter((item) => !isFragment(item));
690
+ const children = [];
691
+ for (const item of nonFragmentItems) {
692
+ if (item != null) {
693
+ children.push(
694
+ this.#leaf(pluralize.singular(name), String(item), depth + 1)
695
+ );
696
+ }
697
+ }
698
+ if (this.options.groupFragments && fragmentItems.length > 0) {
699
+ const groups = this.groupByName(fragmentItems);
700
+ for (const [groupName, groupFragments] of groups) {
701
+ const groupChildren = groupFragments.map(
702
+ (frag) => this.renderFragment(frag, { depth: depth + 2, path: [] })
703
+ );
704
+ const pluralName = pluralize.plural(groupName);
705
+ children.push(this.#wrapIndented(pluralName, groupChildren, depth + 1));
706
+ }
707
+ } else {
708
+ for (const frag of fragmentItems) {
709
+ children.push(
710
+ this.renderFragment(frag, { depth: depth + 1, path: [] })
711
+ );
712
+ }
713
+ }
714
+ return this.#wrap(name, children);
715
+ }
716
+ #leafRoot(tag, value) {
717
+ const safe = this.#escape(value);
718
+ if (safe.includes("\n")) {
719
+ return `<${tag}>
720
+ ${this.#indent(safe, 2)}
721
+ </${tag}>`;
722
+ }
723
+ return `<${tag}>${safe}</${tag}>`;
724
+ }
725
+ renderFragment(fragment2, ctx) {
726
+ const { name, data } = fragment2;
727
+ if (this.isPrimitive(data)) {
728
+ return this.#leaf(name, String(data), ctx.depth);
729
+ }
730
+ if (isFragment(data)) {
731
+ const child = this.renderFragment(data, { ...ctx, depth: ctx.depth + 1 });
732
+ return this.#wrapIndented(name, [child], ctx.depth);
733
+ }
734
+ if (Array.isArray(data)) {
735
+ return this.#renderArrayIndented(name, data, ctx.depth);
736
+ }
737
+ const children = this.renderEntries(data, { ...ctx, depth: ctx.depth + 1 });
738
+ return this.#wrapIndented(name, children, ctx.depth);
739
+ }
740
+ #renderArrayIndented(name, items, depth) {
741
+ const fragmentItems = items.filter(isFragment);
742
+ const nonFragmentItems = items.filter((item) => !isFragment(item));
743
+ const children = [];
744
+ for (const item of nonFragmentItems) {
745
+ if (item != null) {
746
+ children.push(
747
+ this.#leaf(pluralize.singular(name), String(item), depth + 1)
748
+ );
749
+ }
750
+ }
751
+ if (this.options.groupFragments && fragmentItems.length > 0) {
752
+ const groups = this.groupByName(fragmentItems);
753
+ for (const [groupName, groupFragments] of groups) {
754
+ const groupChildren = groupFragments.map(
755
+ (frag) => this.renderFragment(frag, { depth: depth + 2, path: [] })
756
+ );
757
+ const pluralName = pluralize.plural(groupName);
758
+ children.push(this.#wrapIndented(pluralName, groupChildren, depth + 1));
759
+ }
760
+ } else {
761
+ for (const frag of fragmentItems) {
762
+ children.push(
763
+ this.renderFragment(frag, { depth: depth + 1, path: [] })
764
+ );
765
+ }
766
+ }
767
+ return this.#wrapIndented(name, children, depth);
768
+ }
769
+ renderPrimitive(key, value, ctx) {
770
+ return this.#leaf(key, value, ctx.depth);
771
+ }
772
+ renderArray(key, items, ctx) {
773
+ if (!items.length) {
774
+ return "";
775
+ }
776
+ const itemTag = pluralize.singular(key);
777
+ const children = items.filter((item) => item != null).map((item) => this.#leaf(itemTag, String(item), ctx.depth + 1));
778
+ return this.#wrapIndented(key, children, ctx.depth);
779
+ }
780
+ renderObject(key, obj, ctx) {
781
+ const children = this.renderEntries(obj, { ...ctx, depth: ctx.depth + 1 });
782
+ return this.#wrapIndented(key, children, ctx.depth);
783
+ }
784
+ #escape(value) {
785
+ if (value == null) {
786
+ return "";
787
+ }
788
+ return value.replaceAll(/&/g, "&amp;").replaceAll(/</g, "&lt;").replaceAll(/>/g, "&gt;").replaceAll(/"/g, "&quot;").replaceAll(/'/g, "&apos;");
789
+ }
790
+ #indent(text, spaces) {
791
+ if (!text.trim()) {
792
+ return "";
793
+ }
794
+ const padding = " ".repeat(spaces);
795
+ return text.split("\n").map((line) => line.length ? padding + line : padding).join("\n");
796
+ }
797
+ #leaf(tag, value, depth) {
798
+ const safe = this.#escape(value);
799
+ const pad = " ".repeat(depth);
800
+ if (safe.includes("\n")) {
801
+ return `${pad}<${tag}>
802
+ ${this.#indent(safe, (depth + 1) * 2)}
803
+ ${pad}</${tag}>`;
804
+ }
805
+ return `${pad}<${tag}>${safe}</${tag}>`;
806
+ }
807
+ #wrap(tag, children) {
808
+ const content = children.filter(Boolean).join("\n");
809
+ if (!content) {
810
+ return "";
811
+ }
812
+ return `<${tag}>
813
+ ${content}
814
+ </${tag}>`;
815
+ }
816
+ #wrapIndented(tag, children, depth) {
817
+ const content = children.filter(Boolean).join("\n");
818
+ if (!content) {
819
+ return "";
820
+ }
821
+ const pad = " ".repeat(depth);
822
+ return `${pad}<${tag}>
823
+ ${content}
824
+ ${pad}</${tag}>`;
825
+ }
826
+ };
827
+ var ContextStore = class {
828
+ };
829
+ var ContextEngine = class {
830
+ /** Non-message fragments (role, hints, etc.) - not persisted in graph */
831
+ #fragments = [];
832
+ /** Pending message fragments to be added to graph */
833
+ #pendingMessages = [];
834
+ #store;
835
+ #chatId;
836
+ #branchName;
837
+ #branch = null;
838
+ #chatData = null;
839
+ #initialized = false;
840
+ constructor(options) {
841
+ if (!options.chatId) {
842
+ throw new Error("chatId is required");
843
+ }
844
+ this.#store = options.store;
845
+ this.#chatId = options.chatId;
846
+ this.#branchName = options.branch ?? "main";
847
+ }
848
+ /**
849
+ * Initialize the chat and branch if they don't exist.
850
+ */
851
+ async #ensureInitialized() {
852
+ if (this.#initialized) {
853
+ return;
854
+ }
855
+ this.#chatData = await this.#store.upsertChat({ id: this.#chatId });
856
+ const existingBranch = await this.#store.getBranch(
857
+ this.#chatId,
858
+ this.#branchName
859
+ );
860
+ if (existingBranch) {
861
+ this.#branch = existingBranch;
862
+ } else {
863
+ this.#branch = {
864
+ id: crypto.randomUUID(),
865
+ chatId: this.#chatId,
866
+ name: this.#branchName,
867
+ headMessageId: null,
868
+ isActive: true,
869
+ createdAt: Date.now()
870
+ };
871
+ await this.#store.createBranch(this.#branch);
872
+ }
873
+ this.#initialized = true;
874
+ }
875
+ /**
876
+ * Create a new branch from a specific message.
877
+ * Shared logic between rewind() and btw().
878
+ */
879
+ async #createBranchFrom(messageId, switchTo) {
880
+ const branches = await this.#store.listBranches(this.#chatId);
881
+ const samePrefix = branches.filter(
882
+ (b) => b.name === this.#branchName || b.name.startsWith(`${this.#branchName}-v`)
883
+ );
884
+ const newBranchName = `${this.#branchName}-v${samePrefix.length + 1}`;
885
+ const newBranch = {
886
+ id: crypto.randomUUID(),
887
+ chatId: this.#chatId,
888
+ name: newBranchName,
889
+ headMessageId: messageId,
890
+ isActive: false,
891
+ createdAt: Date.now()
892
+ };
893
+ await this.#store.createBranch(newBranch);
894
+ if (switchTo) {
895
+ await this.#store.setActiveBranch(this.#chatId, newBranch.id);
896
+ this.#branch = { ...newBranch, isActive: true };
897
+ this.#branchName = newBranchName;
898
+ this.#pendingMessages = [];
899
+ }
900
+ const chain = await this.#store.getMessageChain(messageId);
901
+ return {
902
+ id: newBranch.id,
903
+ name: newBranch.name,
904
+ headMessageId: newBranch.headMessageId,
905
+ isActive: switchTo,
906
+ messageCount: chain.length,
907
+ createdAt: newBranch.createdAt
908
+ };
909
+ }
910
+ /**
911
+ * Get the current chat ID.
912
+ */
913
+ get chatId() {
914
+ return this.#chatId;
915
+ }
916
+ /**
917
+ * Get the current branch name.
918
+ */
919
+ get branch() {
920
+ return this.#branchName;
921
+ }
922
+ /**
923
+ * Get metadata for the current chat.
924
+ * Returns null if the chat hasn't been initialized yet.
925
+ */
926
+ get chat() {
927
+ if (!this.#chatData) {
928
+ return null;
929
+ }
930
+ return {
931
+ id: this.#chatData.id,
932
+ createdAt: this.#chatData.createdAt,
933
+ updatedAt: this.#chatData.updatedAt,
934
+ title: this.#chatData.title,
935
+ metadata: this.#chatData.metadata
936
+ };
937
+ }
938
+ /**
939
+ * Add fragments to the context.
940
+ *
941
+ * - Message fragments (user/assistant) are queued for persistence
942
+ * - Non-message fragments (role/hint) are kept in memory for system prompt
943
+ */
944
+ set(...fragments2) {
945
+ for (const fragment2 of fragments2) {
946
+ if (isMessageFragment(fragment2)) {
947
+ this.#pendingMessages.push(fragment2);
948
+ } else {
949
+ this.#fragments.push(fragment2);
950
+ }
951
+ }
952
+ return this;
953
+ }
954
+ // Unset a fragment by ID (not implemented yet)
955
+ unset(fragmentId) {
956
+ }
957
+ /**
958
+ * Render all fragments using the provided renderer.
959
+ * @internal Use resolve() instead for public API.
960
+ */
961
+ render(renderer) {
962
+ return renderer.render(this.#fragments);
963
+ }
964
+ /**
965
+ * Resolve context into AI SDK-ready format.
966
+ *
967
+ * - Initializes chat and branch if needed
968
+ * - Loads message history from the graph (walking parent chain)
969
+ * - Separates context fragments for system prompt
970
+ * - Combines with pending messages
971
+ *
972
+ * @example
973
+ * ```ts
974
+ * const context = new ContextEngine({ store, chatId: 'chat-1' })
975
+ * .set(role('You are helpful'), user('Hello'));
976
+ *
977
+ * const { systemPrompt, messages } = await context.resolve();
978
+ * await generateText({ system: systemPrompt, messages });
979
+ * ```
980
+ */
981
+ async resolve(options) {
982
+ await this.#ensureInitialized();
983
+ const systemPrompt = options.renderer.render(this.#fragments);
984
+ const messages = [];
985
+ if (this.#branch?.headMessageId) {
986
+ const chain = await this.#store.getMessageChain(
987
+ this.#branch.headMessageId
988
+ );
989
+ for (const msg of chain) {
990
+ messages.push(message(msg.data).codec?.decode());
991
+ }
992
+ }
993
+ for (const fragment2 of this.#pendingMessages) {
994
+ const decoded = fragment2.codec.decode();
995
+ messages.push(decoded);
996
+ }
997
+ return { systemPrompt, messages };
998
+ }
999
+ /**
1000
+ * Save pending messages to the graph.
1001
+ *
1002
+ * Each message is added as a node with parentId pointing to the previous message.
1003
+ * The branch head is updated to point to the last message.
1004
+ *
1005
+ * @example
1006
+ * ```ts
1007
+ * context.set(user('Hello'));
1008
+ * // AI responds...
1009
+ * context.set(assistant('Hi there!'));
1010
+ * await context.save(); // Persist to graph
1011
+ * ```
1012
+ */
1013
+ async save() {
1014
+ await this.#ensureInitialized();
1015
+ if (this.#pendingMessages.length === 0) {
1016
+ return;
1017
+ }
1018
+ let parentId = this.#branch.headMessageId;
1019
+ const now = Date.now();
1020
+ for (const fragment2 of this.#pendingMessages) {
1021
+ const messageData = {
1022
+ id: fragment2.id ?? crypto.randomUUID(),
1023
+ chatId: this.#chatId,
1024
+ parentId,
1025
+ name: fragment2.name,
1026
+ type: fragment2.type,
1027
+ data: fragment2.codec.encode(),
1028
+ createdAt: now
1029
+ };
1030
+ await this.#store.addMessage(messageData);
1031
+ parentId = messageData.id;
1032
+ }
1033
+ await this.#store.updateBranchHead(this.#branch.id, parentId);
1034
+ this.#branch.headMessageId = parentId;
1035
+ this.#pendingMessages = [];
1036
+ }
1037
+ /**
1038
+ * Estimate token count and cost for the full context.
1039
+ *
1040
+ * Includes:
1041
+ * - System prompt fragments (role, hints, etc.)
1042
+ * - Persisted chat messages (from store)
1043
+ * - Pending messages (not yet saved)
1044
+ *
1045
+ * @param modelId - Model ID (e.g., "openai:gpt-4o", "anthropic:claude-3-5-sonnet")
1046
+ * @param options - Optional settings
1047
+ * @returns Estimate result with token counts, costs, and per-fragment breakdown
1048
+ */
1049
+ async estimate(modelId, options = {}) {
1050
+ await this.#ensureInitialized();
1051
+ const renderer = options.renderer ?? new XmlRenderer();
1052
+ const registry = getModelsRegistry();
1053
+ await registry.load();
1054
+ const model = registry.get(modelId);
1055
+ if (!model) {
1056
+ throw new Error(
1057
+ `Model "${modelId}" not found. Call load() first or check model ID.`
1058
+ );
1059
+ }
1060
+ const tokenizer = registry.getTokenizer(modelId);
1061
+ const fragmentEstimates = [];
1062
+ for (const fragment2 of this.#fragments) {
1063
+ const rendered = renderer.render([fragment2]);
1064
+ const tokens = tokenizer.count(rendered);
1065
+ const cost = tokens / 1e6 * model.cost.input;
1066
+ fragmentEstimates.push({
1067
+ id: fragment2.id,
1068
+ name: fragment2.name,
1069
+ tokens,
1070
+ cost
1071
+ });
1072
+ }
1073
+ if (this.#branch?.headMessageId) {
1074
+ const chain = await this.#store.getMessageChain(
1075
+ this.#branch.headMessageId
1076
+ );
1077
+ for (const msg of chain) {
1078
+ const content = String(msg.data);
1079
+ const tokens = tokenizer.count(content);
1080
+ const cost = tokens / 1e6 * model.cost.input;
1081
+ fragmentEstimates.push({
1082
+ name: msg.name,
1083
+ id: msg.id,
1084
+ tokens,
1085
+ cost
1086
+ });
1087
+ }
1088
+ }
1089
+ for (const fragment2 of this.#pendingMessages) {
1090
+ const content = String(fragment2.data);
1091
+ const tokens = tokenizer.count(content);
1092
+ const cost = tokens / 1e6 * model.cost.input;
1093
+ fragmentEstimates.push({
1094
+ name: fragment2.name,
1095
+ id: fragment2.id,
1096
+ tokens,
1097
+ cost
1098
+ });
1099
+ }
1100
+ const totalTokens = fragmentEstimates.reduce((sum, f) => sum + f.tokens, 0);
1101
+ const totalCost = fragmentEstimates.reduce((sum, f) => sum + f.cost, 0);
1102
+ return {
1103
+ model: model.id,
1104
+ provider: model.provider,
1105
+ tokens: totalTokens,
1106
+ cost: totalCost,
1107
+ limits: {
1108
+ context: model.limit.context,
1109
+ output: model.limit.output,
1110
+ exceedsContext: totalTokens > model.limit.context
1111
+ },
1112
+ fragments: fragmentEstimates
1113
+ };
1114
+ }
1115
+ /**
1116
+ * Rewind to a specific message by ID.
1117
+ *
1118
+ * Creates a new branch from that message, preserving the original branch.
1119
+ * The new branch becomes active.
1120
+ *
1121
+ * @param messageId - The message ID to rewind to
1122
+ * @returns The new branch info
1123
+ *
1124
+ * @example
1125
+ * ```ts
1126
+ * context.set(user('What is 2 + 2?', { id: 'q1' }));
1127
+ * context.set(assistant('The answer is 5.', { id: 'wrong' })); // Oops!
1128
+ * await context.save();
1129
+ *
1130
+ * // Rewind to the question, creates new branch
1131
+ * const newBranch = await context.rewind('q1');
1132
+ *
1133
+ * // Now add correct answer on new branch
1134
+ * context.set(assistant('The answer is 4.'));
1135
+ * await context.save();
1136
+ * ```
1137
+ */
1138
+ async rewind(messageId) {
1139
+ await this.#ensureInitialized();
1140
+ const message2 = await this.#store.getMessage(messageId);
1141
+ if (!message2) {
1142
+ throw new Error(`Message "${messageId}" not found`);
1143
+ }
1144
+ if (message2.chatId !== this.#chatId) {
1145
+ throw new Error(`Message "${messageId}" belongs to a different chat`);
1146
+ }
1147
+ return this.#createBranchFrom(messageId, true);
1148
+ }
1149
+ /**
1150
+ * Create a checkpoint at the current position.
1151
+ *
1152
+ * A checkpoint is a named pointer to the current branch head.
1153
+ * Use restore() to return to this point later.
1154
+ *
1155
+ * @param name - Name for the checkpoint
1156
+ * @returns The checkpoint info
1157
+ *
1158
+ * @example
1159
+ * ```ts
1160
+ * context.set(user('I want to learn a new skill.'));
1161
+ * context.set(assistant('Would you like coding or cooking?'));
1162
+ * await context.save();
1163
+ *
1164
+ * // Save checkpoint before user's choice
1165
+ * const cp = await context.checkpoint('before-choice');
1166
+ * ```
1167
+ */
1168
+ async checkpoint(name) {
1169
+ await this.#ensureInitialized();
1170
+ if (!this.#branch?.headMessageId) {
1171
+ throw new Error("Cannot create checkpoint: no messages in conversation");
1172
+ }
1173
+ const checkpoint = {
1174
+ id: crypto.randomUUID(),
1175
+ chatId: this.#chatId,
1176
+ name,
1177
+ messageId: this.#branch.headMessageId,
1178
+ createdAt: Date.now()
1179
+ };
1180
+ await this.#store.createCheckpoint(checkpoint);
1181
+ return {
1182
+ id: checkpoint.id,
1183
+ name: checkpoint.name,
1184
+ messageId: checkpoint.messageId,
1185
+ createdAt: checkpoint.createdAt
1186
+ };
1187
+ }
1188
+ /**
1189
+ * Restore to a checkpoint by creating a new branch from that point.
1190
+ *
1191
+ * @param name - Name of the checkpoint to restore
1192
+ * @returns The new branch info
1193
+ *
1194
+ * @example
1195
+ * ```ts
1196
+ * // User chose cooking, but wants to try coding path
1197
+ * await context.restore('before-choice');
1198
+ *
1199
+ * context.set(user('I want to learn coding.'));
1200
+ * context.set(assistant('Python is a great starting language!'));
1201
+ * await context.save();
1202
+ * ```
1203
+ */
1204
+ async restore(name) {
1205
+ await this.#ensureInitialized();
1206
+ const checkpoint = await this.#store.getCheckpoint(this.#chatId, name);
1207
+ if (!checkpoint) {
1208
+ throw new Error(
1209
+ `Checkpoint "${name}" not found in chat "${this.#chatId}"`
1210
+ );
1211
+ }
1212
+ return this.rewind(checkpoint.messageId);
1213
+ }
1214
+ /**
1215
+ * Switch to a different branch by name.
1216
+ *
1217
+ * @param name - Branch name to switch to
1218
+ *
1219
+ * @example
1220
+ * ```ts
1221
+ * // List branches (via store)
1222
+ * const branches = await store.listBranches(context.chatId);
1223
+ * console.log(branches); // [{name: 'main', ...}, {name: 'main-v2', ...}]
1224
+ *
1225
+ * // Switch to original branch
1226
+ * await context.switchBranch('main');
1227
+ * ```
1228
+ */
1229
+ async switchBranch(name) {
1230
+ await this.#ensureInitialized();
1231
+ const branch = await this.#store.getBranch(this.#chatId, name);
1232
+ if (!branch) {
1233
+ throw new Error(`Branch "${name}" not found in chat "${this.#chatId}"`);
1234
+ }
1235
+ await this.#store.setActiveBranch(this.#chatId, branch.id);
1236
+ this.#branch = { ...branch, isActive: true };
1237
+ this.#branchName = name;
1238
+ this.#pendingMessages = [];
1239
+ }
1240
+ /**
1241
+ * Create a parallel branch from the current position ("by the way").
1242
+ *
1243
+ * Use this when you want to fork the conversation without leaving
1244
+ * the current branch. Common use case: user wants to ask another
1245
+ * question while waiting for the model to respond.
1246
+ *
1247
+ * Unlike rewind(), this method:
1248
+ * - Uses the current HEAD (no messageId needed)
1249
+ * - Does NOT switch to the new branch
1250
+ * - Keeps pending messages intact
1251
+ *
1252
+ * @returns The new branch info (does not switch to it)
1253
+ * @throws Error if no messages exist in the conversation
1254
+ *
1255
+ * @example
1256
+ * ```ts
1257
+ * // User asked a question, model is generating...
1258
+ * context.set(user('What is the weather?'));
1259
+ * await context.save();
1260
+ *
1261
+ * // User wants to ask something else without waiting
1262
+ * const newBranch = await context.btw();
1263
+ * // newBranch = { name: 'main-v2', ... }
1264
+ *
1265
+ * // Later, switch to the new branch and add the question
1266
+ * await context.switchBranch(newBranch.name);
1267
+ * context.set(user('Also, what time is it?'));
1268
+ * await context.save();
1269
+ * ```
1270
+ */
1271
+ async btw() {
1272
+ await this.#ensureInitialized();
1273
+ if (!this.#branch?.headMessageId) {
1274
+ throw new Error("Cannot create btw branch: no messages in conversation");
1275
+ }
1276
+ return this.#createBranchFrom(this.#branch.headMessageId, false);
1277
+ }
1278
+ /**
1279
+ * Update metadata for the current chat.
1280
+ *
1281
+ * @param updates - Partial metadata to merge (title, metadata)
1282
+ *
1283
+ * @example
1284
+ * ```ts
1285
+ * await context.updateChat({
1286
+ * title: 'Coding Help Session',
1287
+ * metadata: { tags: ['python', 'debugging'] }
1288
+ * });
1289
+ * ```
1290
+ */
1291
+ async updateChat(updates) {
1292
+ await this.#ensureInitialized();
1293
+ const storeUpdates = {};
1294
+ if (updates.title !== void 0) {
1295
+ storeUpdates.title = updates.title;
1296
+ }
1297
+ if (updates.metadata !== void 0) {
1298
+ storeUpdates.metadata = {
1299
+ ...this.#chatData?.metadata,
1300
+ ...updates.metadata
1301
+ };
1302
+ }
1303
+ this.#chatData = await this.#store.updateChat(this.#chatId, storeUpdates);
1304
+ }
1305
+ /**
1306
+ * Consolidate context fragments (no-op for now).
1307
+ *
1308
+ * This is a placeholder for future functionality that merges context fragments
1309
+ * using specific rules. Currently, it does nothing.
1310
+ *
1311
+ * @experimental
1312
+ */
1313
+ consolidate() {
1314
+ return void 0;
1315
+ }
1316
+ /**
1317
+ * Inspect the full context state for debugging.
1318
+ * Returns a comprehensive JSON-serializable object with all context information.
1319
+ *
1320
+ * @param options - Inspection options (modelId and renderer required)
1321
+ * @returns Complete inspection data including estimates, rendered output, fragments, and graph
1322
+ *
1323
+ * @example
1324
+ * ```ts
1325
+ * const inspection = await context.inspect({
1326
+ * modelId: 'openai:gpt-4o',
1327
+ * renderer: new XmlRenderer(),
1328
+ * });
1329
+ * console.log(JSON.stringify(inspection, null, 2));
1330
+ *
1331
+ * // Or write to file for analysis
1332
+ * await fs.writeFile('context-debug.json', JSON.stringify(inspection, null, 2));
1333
+ * ```
1334
+ */
1335
+ async inspect(options) {
1336
+ await this.#ensureInitialized();
1337
+ const { renderer } = options;
1338
+ const estimateResult = await this.estimate(options.modelId, { renderer });
1339
+ const rendered = renderer.render(this.#fragments);
1340
+ const persistedMessages = [];
1341
+ if (this.#branch?.headMessageId) {
1342
+ const chain = await this.#store.getMessageChain(
1343
+ this.#branch.headMessageId
1344
+ );
1345
+ persistedMessages.push(...chain);
1346
+ }
1347
+ const graph = await this.#store.getGraph(this.#chatId);
1348
+ return {
1349
+ estimate: estimateResult,
1350
+ rendered,
1351
+ fragments: {
1352
+ context: [...this.#fragments],
1353
+ pending: [...this.#pendingMessages],
1354
+ persisted: persistedMessages
1355
+ },
1356
+ graph,
1357
+ meta: {
1358
+ chatId: this.#chatId,
1359
+ branch: this.#branchName,
1360
+ timestamp: Date.now()
1361
+ }
1362
+ };
1363
+ }
1364
+ };
1365
+ function hint(text) {
1366
+ return {
1367
+ name: "hint",
1368
+ data: text
1369
+ };
610
1370
  }
611
- var marker = Symbol("SQLValidationError");
612
- var SQLValidationError = class _SQLValidationError extends Error {
613
- [marker];
614
- constructor(message) {
615
- super(message);
616
- this.name = "SQLValidationError";
617
- this[marker] = true;
1371
+ function guardrail(input) {
1372
+ return {
1373
+ name: "guardrail",
1374
+ data: {
1375
+ rule: input.rule,
1376
+ ...input.reason && { reason: input.reason },
1377
+ ...input.action && { action: input.action }
1378
+ }
1379
+ };
1380
+ }
1381
+ function clarification(input) {
1382
+ return {
1383
+ name: "clarification",
1384
+ data: {
1385
+ when: input.when,
1386
+ ask: input.ask,
1387
+ reason: input.reason
1388
+ }
1389
+ };
1390
+ }
1391
+ function workflow(input) {
1392
+ return {
1393
+ name: "workflow",
1394
+ data: {
1395
+ task: input.task,
1396
+ steps: input.steps,
1397
+ ...input.triggers?.length && { triggers: input.triggers },
1398
+ ...input.notes && { notes: input.notes }
1399
+ }
1400
+ };
1401
+ }
1402
+ function styleGuide(input) {
1403
+ return {
1404
+ name: "styleGuide",
1405
+ data: {
1406
+ prefer: input.prefer,
1407
+ ...input.never && { never: input.never },
1408
+ ...input.always && { always: input.always }
1409
+ }
1410
+ };
1411
+ }
1412
+ function persona(input) {
1413
+ return {
1414
+ name: "persona",
1415
+ data: {
1416
+ name: input.name,
1417
+ role: input.role,
1418
+ ...input.tone && { tone: input.tone }
1419
+ }
1420
+ };
1421
+ }
1422
+ function pass(part) {
1423
+ return { type: "pass", part };
1424
+ }
1425
+ function runGuardrailChain(part, guardrails, context) {
1426
+ let currentPart = part;
1427
+ for (const guardrail2 of guardrails) {
1428
+ const result = guardrail2.handle(currentPart, context);
1429
+ if (result.type === "fail") {
1430
+ return result;
1431
+ }
1432
+ currentPart = result.part;
1433
+ }
1434
+ return pass(currentPart);
1435
+ }
1436
+ var STORE_DDL = `
1437
+ -- Chats table
1438
+ -- createdAt/updatedAt: DEFAULT for insert, inline SET for updates
1439
+ CREATE TABLE IF NOT EXISTS chats (
1440
+ id TEXT PRIMARY KEY,
1441
+ title TEXT,
1442
+ metadata TEXT,
1443
+ createdAt INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),
1444
+ updatedAt INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000)
1445
+ );
1446
+
1447
+ CREATE INDEX IF NOT EXISTS idx_chats_updatedAt ON chats(updatedAt);
1448
+
1449
+ -- Messages table (nodes in the DAG)
1450
+ CREATE TABLE IF NOT EXISTS messages (
1451
+ id TEXT PRIMARY KEY,
1452
+ chatId TEXT NOT NULL,
1453
+ parentId TEXT,
1454
+ name TEXT NOT NULL,
1455
+ type TEXT,
1456
+ data TEXT NOT NULL,
1457
+ createdAt INTEGER NOT NULL,
1458
+ FOREIGN KEY (chatId) REFERENCES chats(id) ON DELETE CASCADE,
1459
+ FOREIGN KEY (parentId) REFERENCES messages(id)
1460
+ );
1461
+
1462
+ CREATE INDEX IF NOT EXISTS idx_messages_chatId ON messages(chatId);
1463
+ CREATE INDEX IF NOT EXISTS idx_messages_parentId ON messages(parentId);
1464
+
1465
+ -- Branches table (pointers to head messages)
1466
+ CREATE TABLE IF NOT EXISTS branches (
1467
+ id TEXT PRIMARY KEY,
1468
+ chatId TEXT NOT NULL,
1469
+ name TEXT NOT NULL,
1470
+ headMessageId TEXT,
1471
+ isActive INTEGER NOT NULL DEFAULT 0,
1472
+ createdAt INTEGER NOT NULL,
1473
+ FOREIGN KEY (chatId) REFERENCES chats(id) ON DELETE CASCADE,
1474
+ FOREIGN KEY (headMessageId) REFERENCES messages(id),
1475
+ UNIQUE(chatId, name)
1476
+ );
1477
+
1478
+ CREATE INDEX IF NOT EXISTS idx_branches_chatId ON branches(chatId);
1479
+
1480
+ -- Checkpoints table (pointers to message nodes)
1481
+ CREATE TABLE IF NOT EXISTS checkpoints (
1482
+ id TEXT PRIMARY KEY,
1483
+ chatId TEXT NOT NULL,
1484
+ name TEXT NOT NULL,
1485
+ messageId TEXT NOT NULL,
1486
+ createdAt INTEGER NOT NULL,
1487
+ FOREIGN KEY (chatId) REFERENCES chats(id) ON DELETE CASCADE,
1488
+ FOREIGN KEY (messageId) REFERENCES messages(id),
1489
+ UNIQUE(chatId, name)
1490
+ );
1491
+
1492
+ CREATE INDEX IF NOT EXISTS idx_checkpoints_chatId ON checkpoints(chatId);
1493
+
1494
+ -- FTS5 virtual table for full-text search
1495
+ -- messageId/chatId/name are UNINDEXED (stored but not searchable, used for filtering/joining)
1496
+ -- Only 'content' is indexed for full-text search
1497
+ CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(
1498
+ messageId UNINDEXED,
1499
+ chatId UNINDEXED,
1500
+ name UNINDEXED,
1501
+ content,
1502
+ tokenize='porter unicode61'
1503
+ );
1504
+ `;
1505
+ var SqliteContextStore = class extends ContextStore {
1506
+ #db;
1507
+ constructor(path3) {
1508
+ super();
1509
+ this.#db = new DatabaseSync(path3);
1510
+ this.#db.exec("PRAGMA foreign_keys = ON");
1511
+ this.#db.exec(STORE_DDL);
1512
+ }
1513
+ // ==========================================================================
1514
+ // Chat Operations
1515
+ // ==========================================================================
1516
+ async createChat(chat) {
1517
+ this.#db.prepare(
1518
+ `INSERT INTO chats (id, title, metadata)
1519
+ VALUES (?, ?, ?)`
1520
+ ).run(
1521
+ chat.id,
1522
+ chat.title ?? null,
1523
+ chat.metadata ? JSON.stringify(chat.metadata) : null
1524
+ );
1525
+ }
1526
+ async upsertChat(chat) {
1527
+ const row = this.#db.prepare(
1528
+ `INSERT INTO chats (id, title, metadata)
1529
+ VALUES (?, ?, ?)
1530
+ ON CONFLICT(id) DO UPDATE SET id = excluded.id
1531
+ RETURNING *`
1532
+ ).get(
1533
+ chat.id,
1534
+ chat.title ?? null,
1535
+ chat.metadata ? JSON.stringify(chat.metadata) : null
1536
+ );
1537
+ return {
1538
+ id: row.id,
1539
+ title: row.title ?? void 0,
1540
+ metadata: row.metadata ? JSON.parse(row.metadata) : void 0,
1541
+ createdAt: row.createdAt,
1542
+ updatedAt: row.updatedAt
1543
+ };
1544
+ }
1545
+ async getChat(chatId) {
1546
+ const row = this.#db.prepare("SELECT * FROM chats WHERE id = ?").get(chatId);
1547
+ if (!row) {
1548
+ return void 0;
1549
+ }
1550
+ return {
1551
+ id: row.id,
1552
+ title: row.title ?? void 0,
1553
+ metadata: row.metadata ? JSON.parse(row.metadata) : void 0,
1554
+ createdAt: row.createdAt,
1555
+ updatedAt: row.updatedAt
1556
+ };
1557
+ }
1558
+ async updateChat(chatId, updates) {
1559
+ const setClauses = ["updatedAt = strftime('%s', 'now') * 1000"];
1560
+ const params = [];
1561
+ if (updates.title !== void 0) {
1562
+ setClauses.push("title = ?");
1563
+ params.push(updates.title ?? null);
1564
+ }
1565
+ if (updates.metadata !== void 0) {
1566
+ setClauses.push("metadata = ?");
1567
+ params.push(JSON.stringify(updates.metadata));
1568
+ }
1569
+ params.push(chatId);
1570
+ const row = this.#db.prepare(
1571
+ `UPDATE chats SET ${setClauses.join(", ")} WHERE id = ? RETURNING *`
1572
+ ).get(...params);
1573
+ return {
1574
+ id: row.id,
1575
+ title: row.title ?? void 0,
1576
+ metadata: row.metadata ? JSON.parse(row.metadata) : void 0,
1577
+ createdAt: row.createdAt,
1578
+ updatedAt: row.updatedAt
1579
+ };
1580
+ }
1581
+ async listChats() {
1582
+ const rows = this.#db.prepare(
1583
+ `SELECT
1584
+ c.id,
1585
+ c.title,
1586
+ c.createdAt,
1587
+ c.updatedAt,
1588
+ COUNT(DISTINCT m.id) as messageCount,
1589
+ COUNT(DISTINCT b.id) as branchCount
1590
+ FROM chats c
1591
+ LEFT JOIN messages m ON m.chatId = c.id
1592
+ LEFT JOIN branches b ON b.chatId = c.id
1593
+ GROUP BY c.id
1594
+ ORDER BY c.updatedAt DESC`
1595
+ ).all();
1596
+ return rows.map((row) => ({
1597
+ id: row.id,
1598
+ title: row.title ?? void 0,
1599
+ messageCount: row.messageCount,
1600
+ branchCount: row.branchCount,
1601
+ createdAt: row.createdAt,
1602
+ updatedAt: row.updatedAt
1603
+ }));
1604
+ }
1605
+ // ==========================================================================
1606
+ // Message Operations (Graph Nodes)
1607
+ // ==========================================================================
1608
+ async addMessage(message2) {
1609
+ this.#db.prepare(
1610
+ `INSERT INTO messages (id, chatId, parentId, name, type, data, createdAt)
1611
+ VALUES (?, ?, ?, ?, ?, ?, ?)
1612
+ ON CONFLICT(id) DO UPDATE SET
1613
+ parentId = excluded.parentId,
1614
+ name = excluded.name,
1615
+ type = excluded.type,
1616
+ data = excluded.data`
1617
+ ).run(
1618
+ message2.id,
1619
+ message2.chatId,
1620
+ message2.parentId,
1621
+ message2.name,
1622
+ message2.type ?? null,
1623
+ JSON.stringify(message2.data),
1624
+ message2.createdAt
1625
+ );
1626
+ const content = typeof message2.data === "string" ? message2.data : JSON.stringify(message2.data);
1627
+ this.#db.prepare(`DELETE FROM messages_fts WHERE messageId = ?`).run(message2.id);
1628
+ this.#db.prepare(
1629
+ `INSERT INTO messages_fts(messageId, chatId, name, content)
1630
+ VALUES (?, ?, ?, ?)`
1631
+ ).run(message2.id, message2.chatId, message2.name, content);
1632
+ }
1633
+ async getMessage(messageId) {
1634
+ const row = this.#db.prepare("SELECT * FROM messages WHERE id = ?").get(messageId);
1635
+ if (!row) {
1636
+ return void 0;
1637
+ }
1638
+ return {
1639
+ id: row.id,
1640
+ chatId: row.chatId,
1641
+ parentId: row.parentId,
1642
+ name: row.name,
1643
+ type: row.type ?? void 0,
1644
+ data: JSON.parse(row.data),
1645
+ createdAt: row.createdAt
1646
+ };
1647
+ }
1648
+ async getMessageChain(headId) {
1649
+ const rows = this.#db.prepare(
1650
+ `WITH RECURSIVE chain AS (
1651
+ SELECT *, 0 as depth FROM messages WHERE id = ?
1652
+ UNION ALL
1653
+ SELECT m.*, c.depth + 1 FROM messages m
1654
+ INNER JOIN chain c ON m.id = c.parentId
1655
+ )
1656
+ SELECT * FROM chain
1657
+ ORDER BY depth DESC`
1658
+ ).all(headId);
1659
+ return rows.map((row) => ({
1660
+ id: row.id,
1661
+ chatId: row.chatId,
1662
+ parentId: row.parentId,
1663
+ name: row.name,
1664
+ type: row.type ?? void 0,
1665
+ data: JSON.parse(row.data),
1666
+ createdAt: row.createdAt
1667
+ }));
1668
+ }
1669
+ async hasChildren(messageId) {
1670
+ const row = this.#db.prepare(
1671
+ "SELECT EXISTS(SELECT 1 FROM messages WHERE parentId = ?) as hasChildren"
1672
+ ).get(messageId);
1673
+ return row.hasChildren === 1;
1674
+ }
1675
+ // ==========================================================================
1676
+ // Branch Operations
1677
+ // ==========================================================================
1678
+ async createBranch(branch) {
1679
+ this.#db.prepare(
1680
+ `INSERT INTO branches (id, chatId, name, headMessageId, isActive, createdAt)
1681
+ VALUES (?, ?, ?, ?, ?, ?)`
1682
+ ).run(
1683
+ branch.id,
1684
+ branch.chatId,
1685
+ branch.name,
1686
+ branch.headMessageId,
1687
+ branch.isActive ? 1 : 0,
1688
+ branch.createdAt
1689
+ );
1690
+ }
1691
+ async getBranch(chatId, name) {
1692
+ const row = this.#db.prepare("SELECT * FROM branches WHERE chatId = ? AND name = ?").get(chatId, name);
1693
+ if (!row) {
1694
+ return void 0;
1695
+ }
1696
+ return {
1697
+ id: row.id,
1698
+ chatId: row.chatId,
1699
+ name: row.name,
1700
+ headMessageId: row.headMessageId,
1701
+ isActive: row.isActive === 1,
1702
+ createdAt: row.createdAt
1703
+ };
1704
+ }
1705
+ async getActiveBranch(chatId) {
1706
+ const row = this.#db.prepare("SELECT * FROM branches WHERE chatId = ? AND isActive = 1").get(chatId);
1707
+ if (!row) {
1708
+ return void 0;
1709
+ }
1710
+ return {
1711
+ id: row.id,
1712
+ chatId: row.chatId,
1713
+ name: row.name,
1714
+ headMessageId: row.headMessageId,
1715
+ isActive: true,
1716
+ createdAt: row.createdAt
1717
+ };
1718
+ }
1719
+ async setActiveBranch(chatId, branchId) {
1720
+ this.#db.prepare("UPDATE branches SET isActive = 0 WHERE chatId = ?").run(chatId);
1721
+ this.#db.prepare("UPDATE branches SET isActive = 1 WHERE id = ?").run(branchId);
1722
+ }
1723
+ async updateBranchHead(branchId, messageId) {
1724
+ this.#db.prepare("UPDATE branches SET headMessageId = ? WHERE id = ?").run(messageId, branchId);
1725
+ }
1726
+ async listBranches(chatId) {
1727
+ const branches = this.#db.prepare(
1728
+ `SELECT
1729
+ b.id,
1730
+ b.name,
1731
+ b.headMessageId,
1732
+ b.isActive,
1733
+ b.createdAt
1734
+ FROM branches b
1735
+ WHERE b.chatId = ?
1736
+ ORDER BY b.createdAt ASC`
1737
+ ).all(chatId);
1738
+ const result = [];
1739
+ for (const branch of branches) {
1740
+ let messageCount = 0;
1741
+ if (branch.headMessageId) {
1742
+ const countRow = this.#db.prepare(
1743
+ `WITH RECURSIVE chain AS (
1744
+ SELECT id, parentId FROM messages WHERE id = ?
1745
+ UNION ALL
1746
+ SELECT m.id, m.parentId FROM messages m
1747
+ INNER JOIN chain c ON m.id = c.parentId
1748
+ )
1749
+ SELECT COUNT(*) as count FROM chain`
1750
+ ).get(branch.headMessageId);
1751
+ messageCount = countRow.count;
1752
+ }
1753
+ result.push({
1754
+ id: branch.id,
1755
+ name: branch.name,
1756
+ headMessageId: branch.headMessageId,
1757
+ isActive: branch.isActive === 1,
1758
+ messageCount,
1759
+ createdAt: branch.createdAt
1760
+ });
1761
+ }
1762
+ return result;
1763
+ }
1764
+ // ==========================================================================
1765
+ // Checkpoint Operations
1766
+ // ==========================================================================
1767
+ async createCheckpoint(checkpoint) {
1768
+ this.#db.prepare(
1769
+ `INSERT INTO checkpoints (id, chatId, name, messageId, createdAt)
1770
+ VALUES (?, ?, ?, ?, ?)
1771
+ ON CONFLICT(chatId, name) DO UPDATE SET
1772
+ messageId = excluded.messageId,
1773
+ createdAt = excluded.createdAt`
1774
+ ).run(
1775
+ checkpoint.id,
1776
+ checkpoint.chatId,
1777
+ checkpoint.name,
1778
+ checkpoint.messageId,
1779
+ checkpoint.createdAt
1780
+ );
1781
+ }
1782
+ async getCheckpoint(chatId, name) {
1783
+ const row = this.#db.prepare("SELECT * FROM checkpoints WHERE chatId = ? AND name = ?").get(chatId, name);
1784
+ if (!row) {
1785
+ return void 0;
1786
+ }
1787
+ return {
1788
+ id: row.id,
1789
+ chatId: row.chatId,
1790
+ name: row.name,
1791
+ messageId: row.messageId,
1792
+ createdAt: row.createdAt
1793
+ };
1794
+ }
1795
+ async listCheckpoints(chatId) {
1796
+ const rows = this.#db.prepare(
1797
+ `SELECT id, name, messageId, createdAt
1798
+ FROM checkpoints
1799
+ WHERE chatId = ?
1800
+ ORDER BY createdAt DESC`
1801
+ ).all(chatId);
1802
+ return rows.map((row) => ({
1803
+ id: row.id,
1804
+ name: row.name,
1805
+ messageId: row.messageId,
1806
+ createdAt: row.createdAt
1807
+ }));
1808
+ }
1809
+ async deleteCheckpoint(chatId, name) {
1810
+ this.#db.prepare("DELETE FROM checkpoints WHERE chatId = ? AND name = ?").run(chatId, name);
1811
+ }
1812
+ // ==========================================================================
1813
+ // Search Operations
1814
+ // ==========================================================================
1815
+ async searchMessages(chatId, query, options) {
1816
+ const limit = options?.limit ?? 20;
1817
+ const roles = options?.roles;
1818
+ let sql = `
1819
+ SELECT
1820
+ m.id,
1821
+ m.chatId,
1822
+ m.parentId,
1823
+ m.name,
1824
+ m.type,
1825
+ m.data,
1826
+ m.createdAt,
1827
+ fts.rank,
1828
+ snippet(messages_fts, 3, '<mark>', '</mark>', '...', 32) as snippet
1829
+ FROM messages_fts fts
1830
+ JOIN messages m ON m.id = fts.messageId
1831
+ WHERE messages_fts MATCH ?
1832
+ AND fts.chatId = ?
1833
+ `;
1834
+ const params = [query, chatId];
1835
+ if (roles && roles.length > 0) {
1836
+ const placeholders = roles.map(() => "?").join(", ");
1837
+ sql += ` AND fts.name IN (${placeholders})`;
1838
+ params.push(...roles);
1839
+ }
1840
+ sql += " ORDER BY fts.rank LIMIT ?";
1841
+ params.push(limit);
1842
+ const rows = this.#db.prepare(sql).all(...params);
1843
+ return rows.map((row) => ({
1844
+ message: {
1845
+ id: row.id,
1846
+ chatId: row.chatId,
1847
+ parentId: row.parentId,
1848
+ name: row.name,
1849
+ type: row.type ?? void 0,
1850
+ data: JSON.parse(row.data),
1851
+ createdAt: row.createdAt
1852
+ },
1853
+ rank: row.rank,
1854
+ snippet: row.snippet
1855
+ }));
1856
+ }
1857
+ // ==========================================================================
1858
+ // Visualization Operations
1859
+ // ==========================================================================
1860
+ async getGraph(chatId) {
1861
+ const messageRows = this.#db.prepare(
1862
+ `SELECT id, parentId, name, data, createdAt
1863
+ FROM messages
1864
+ WHERE chatId = ?
1865
+ ORDER BY createdAt ASC`
1866
+ ).all(chatId);
1867
+ const nodes = messageRows.map((row) => {
1868
+ const data = JSON.parse(row.data);
1869
+ const content = typeof data === "string" ? data : JSON.stringify(data);
1870
+ return {
1871
+ id: row.id,
1872
+ parentId: row.parentId,
1873
+ role: row.name,
1874
+ content: content.length > 50 ? content.slice(0, 50) + "..." : content,
1875
+ createdAt: row.createdAt
1876
+ };
1877
+ });
1878
+ const branchRows = this.#db.prepare(
1879
+ `SELECT name, headMessageId, isActive
1880
+ FROM branches
1881
+ WHERE chatId = ?
1882
+ ORDER BY createdAt ASC`
1883
+ ).all(chatId);
1884
+ const branches = branchRows.map((row) => ({
1885
+ name: row.name,
1886
+ headMessageId: row.headMessageId,
1887
+ isActive: row.isActive === 1
1888
+ }));
1889
+ const checkpointRows = this.#db.prepare(
1890
+ `SELECT name, messageId
1891
+ FROM checkpoints
1892
+ WHERE chatId = ?
1893
+ ORDER BY createdAt ASC`
1894
+ ).all(chatId);
1895
+ const checkpoints = checkpointRows.map((row) => ({
1896
+ name: row.name,
1897
+ messageId: row.messageId
1898
+ }));
1899
+ return {
1900
+ chatId,
1901
+ nodes,
1902
+ branches,
1903
+ checkpoints
1904
+ };
618
1905
  }
619
- static isInstance(error) {
620
- return error instanceof _SQLValidationError && error[marker] === true;
1906
+ };
1907
+ var InMemoryContextStore = class extends SqliteContextStore {
1908
+ constructor() {
1909
+ super(":memory:");
621
1910
  }
622
1911
  };
623
- var UnanswerableSQLError = class _UnanswerableSQLError extends Error {
624
- constructor(message) {
625
- super(message);
626
- this.name = "UnanswerableSQLError";
1912
+ var Agent = class _Agent {
1913
+ #options;
1914
+ #guardrails = [];
1915
+ tools;
1916
+ constructor(options) {
1917
+ this.#options = options;
1918
+ this.tools = options.tools || {};
1919
+ this.#guardrails = options.guardrails || [];
1920
+ }
1921
+ async generate(contextVariables, config) {
1922
+ if (!this.#options.context) {
1923
+ throw new Error(`Agent ${this.#options.name} is missing a context.`);
1924
+ }
1925
+ if (!this.#options.model) {
1926
+ throw new Error(`Agent ${this.#options.name} is missing a model.`);
1927
+ }
1928
+ const { messages, systemPrompt } = await this.#options.context.resolve({
1929
+ renderer: new XmlRenderer()
1930
+ });
1931
+ return generateText({
1932
+ abortSignal: config?.abortSignal,
1933
+ providerOptions: this.#options.providerOptions,
1934
+ model: this.#options.model,
1935
+ system: systemPrompt,
1936
+ messages: convertToModelMessages(messages),
1937
+ stopWhen: stepCountIs(25),
1938
+ tools: this.#options.tools,
1939
+ experimental_context: contextVariables,
1940
+ toolChoice: this.#options.toolChoice,
1941
+ onStepFinish: (step) => {
1942
+ const toolCall = step.toolCalls.at(-1);
1943
+ if (toolCall) {
1944
+ console.log(
1945
+ `Debug: ${chalk.yellow("ToolCalled")}: ${toolCall.toolName}(${JSON.stringify(toolCall.input)})`
1946
+ );
1947
+ }
1948
+ }
1949
+ });
627
1950
  }
628
- static isInstance(error) {
629
- return error instanceof _UnanswerableSQLError;
1951
+ /**
1952
+ * Stream a response from the agent.
1953
+ *
1954
+ * When guardrails are configured, `toUIMessageStream()` is wrapped to provide
1955
+ * self-correction behavior. Direct access to fullStream/textStream bypasses guardrails.
1956
+ *
1957
+ * @example
1958
+ * ```typescript
1959
+ * const stream = await agent.stream({});
1960
+ *
1961
+ * // With guardrails - use toUIMessageStream for protection
1962
+ * await printer.readableStream(stream.toUIMessageStream());
1963
+ *
1964
+ * // Or use printer.stdout which uses toUIMessageStream internally
1965
+ * await printer.stdout(stream);
1966
+ * ```
1967
+ */
1968
+ async stream(contextVariables, config) {
1969
+ if (!this.#options.context) {
1970
+ throw new Error(`Agent ${this.#options.name} is missing a context.`);
1971
+ }
1972
+ if (!this.#options.model) {
1973
+ throw new Error(`Agent ${this.#options.name} is missing a model.`);
1974
+ }
1975
+ const result = await this.#createRawStream(contextVariables, config);
1976
+ if (this.#guardrails.length === 0) {
1977
+ return result;
1978
+ }
1979
+ return this.#wrapWithGuardrails(result, contextVariables, config);
630
1980
  }
631
- };
632
- async function toSql(options) {
633
- const { maxRetries = 3 } = options;
634
- return withRetry(
635
- async (attemptNumber, errors, attempts) => {
636
- const agentInstance = sqlQueryAgent.clone({
637
- model: wrapLanguageModel({
638
- model: options.model ?? sqlQueryAgent.model,
639
- middleware: defaultSettingsMiddleware({
640
- settings: {
641
- temperature: RETRY_TEMPERATURES[attemptNumber - 1] ?? 0.3,
642
- topP: 1
1981
+ /**
1982
+ * Create a raw stream without guardrail processing.
1983
+ */
1984
+ async #createRawStream(contextVariables, config) {
1985
+ const { messages, systemPrompt } = await this.#options.context.resolve({
1986
+ renderer: new XmlRenderer()
1987
+ });
1988
+ const runId = generateId2();
1989
+ return streamText({
1990
+ abortSignal: config?.abortSignal,
1991
+ providerOptions: this.#options.providerOptions,
1992
+ model: this.#options.model,
1993
+ system: systemPrompt,
1994
+ messages: convertToModelMessages(messages),
1995
+ stopWhen: stepCountIs(25),
1996
+ experimental_transform: config?.transform ?? smoothStream(),
1997
+ tools: this.#options.tools,
1998
+ experimental_context: contextVariables,
1999
+ toolChoice: this.#options.toolChoice,
2000
+ onStepFinish: (step) => {
2001
+ const toolCall = step.toolCalls.at(-1);
2002
+ if (toolCall) {
2003
+ console.log(
2004
+ `Debug: (${runId}) ${chalk.bold.yellow("ToolCalled")}: ${toolCall.toolName}(${JSON.stringify(toolCall.input)})`
2005
+ );
2006
+ }
2007
+ }
2008
+ });
2009
+ }
2010
+ /**
2011
+ * Wrap a StreamTextResult with guardrail protection on toUIMessageStream().
2012
+ *
2013
+ * When a guardrail fails:
2014
+ * 1. Accumulated text + feedback is appended as the assistant's self-correction
2015
+ * 2. The feedback is written to the output stream (user sees the correction)
2016
+ * 3. A new stream is started and the model continues from the correction
2017
+ */
2018
+ #wrapWithGuardrails(result, contextVariables, config) {
2019
+ const maxRetries = config?.maxRetries ?? this.#options.maxGuardrailRetries ?? 3;
2020
+ const context = this.#options.context;
2021
+ const originalToUIMessageStream = result.toUIMessageStream.bind(result);
2022
+ result.toUIMessageStream = (options) => {
2023
+ return createUIMessageStream({
2024
+ generateId: generateId2,
2025
+ execute: async ({ writer }) => {
2026
+ let currentResult = result;
2027
+ let attempt = 0;
2028
+ const guardrailContext = {
2029
+ availableTools: Object.keys(this.tools)
2030
+ };
2031
+ while (attempt < maxRetries) {
2032
+ if (config?.abortSignal?.aborted) {
2033
+ writer.write({ type: "finish" });
2034
+ return;
643
2035
  }
644
- })
645
- })
2036
+ attempt++;
2037
+ let accumulatedText = "";
2038
+ let guardrailFailed = false;
2039
+ let failureFeedback = "";
2040
+ const uiStream = currentResult === result ? originalToUIMessageStream(options) : currentResult.toUIMessageStream(options);
2041
+ for await (const part of uiStream) {
2042
+ const checkResult = runGuardrailChain(
2043
+ part,
2044
+ this.#guardrails,
2045
+ guardrailContext
2046
+ );
2047
+ if (checkResult.type === "fail") {
2048
+ guardrailFailed = true;
2049
+ failureFeedback = checkResult.feedback;
2050
+ console.log(
2051
+ chalk.yellow(
2052
+ `[${this.#options.name}] Guardrail triggered (attempt ${attempt}/${maxRetries}): ${failureFeedback.slice(0, 50)}...`
2053
+ )
2054
+ );
2055
+ break;
2056
+ }
2057
+ if (checkResult.part.type === "text-delta") {
2058
+ accumulatedText += checkResult.part.delta;
2059
+ }
2060
+ writer.write(checkResult.part);
2061
+ }
2062
+ if (!guardrailFailed) {
2063
+ writer.write({ type: "finish" });
2064
+ return;
2065
+ }
2066
+ if (attempt >= maxRetries) {
2067
+ console.error(
2068
+ chalk.red(
2069
+ `[${this.#options.name}] Guardrail retry limit (${maxRetries}) exceeded.`
2070
+ )
2071
+ );
2072
+ writer.write({ type: "finish" });
2073
+ return;
2074
+ }
2075
+ writer.write({
2076
+ type: "text-delta",
2077
+ id: generateId2(),
2078
+ delta: ` ${failureFeedback}`
2079
+ });
2080
+ const selfCorrectionText = accumulatedText + " " + failureFeedback;
2081
+ context.set(assistantText(selfCorrectionText));
2082
+ await context.save();
2083
+ currentResult = await this.#createRawStream(
2084
+ contextVariables,
2085
+ config
2086
+ );
2087
+ }
2088
+ },
2089
+ onError: (error) => {
2090
+ const message2 = error instanceof Error ? error.message : String(error);
2091
+ return `Stream failed: ${message2}`;
2092
+ }
646
2093
  });
647
- const messages = errors.length ? [
648
- user(options.input),
649
- user(
650
- `<validation_error>Your previous SQL query had the following error: ${errors.at(-1)?.message}. Please fix the query.</validation_error>`
651
- )
652
- ] : [user(options.input)];
653
- const output = await toOutput(
654
- generate(agentInstance, messages, {
655
- introspection: options.introspection,
656
- teachings: toInstructions(
657
- "instructions",
658
- persona({
659
- name: "Freya",
660
- role: "You are an expert SQL query generator. You translate natural language questions into precise, efficient SQL queries based on the provided database schema."
661
- }),
662
- ...options.instructions
663
- )
664
- })
665
- );
666
- if ("error" in output) {
667
- throw new UnanswerableSQLError(output.error);
668
- }
669
- const sql = extractSql(output.sql);
670
- const validationError = await options.adapter.validate(sql);
671
- if (validationError) {
672
- throw new SQLValidationError(validationError);
673
- }
674
- return {
675
- attempts,
676
- sql,
677
- errors: errors.length ? errors.map(formatErrorMessage) : void 0
678
- };
679
- },
680
- { retries: maxRetries - 1 }
681
- );
682
- }
683
- function formatErrorMessage(error) {
684
- if (APICallError.isInstance(error)) {
685
- if (error.message.startsWith("Failed to validate JSON")) {
686
- return `Schema validation failed: ${error.message}`;
687
- }
688
- return error.message;
2094
+ };
2095
+ return result;
689
2096
  }
690
- if (SQLValidationError.isInstance(error)) {
691
- return `SQL Validation Error: ${error.message}`;
2097
+ clone(overrides) {
2098
+ return new _Agent({
2099
+ ...this.#options,
2100
+ ...overrides
2101
+ });
692
2102
  }
693
- return error.message;
2103
+ };
2104
+ function agent(options) {
2105
+ return new Agent(options);
694
2106
  }
695
- async function withRetry(computation, options = { retries: 3 }) {
696
- const errors = [];
697
- let attempts = 0;
698
- return pRetry(
699
- (attemptNumber) => {
700
- return computation(attemptNumber, errors, ++attempts);
2107
+ function structuredOutput(options) {
2108
+ return {
2109
+ async generate(contextVariables, config) {
2110
+ if (!options.context) {
2111
+ throw new Error(
2112
+ `structuredOutput "${options.name}" is missing a context.`
2113
+ );
2114
+ }
2115
+ if (!options.model) {
2116
+ throw new Error(
2117
+ `structuredOutput "${options.name}" is missing a model.`
2118
+ );
2119
+ }
2120
+ const { messages, systemPrompt } = await options.context.resolve({
2121
+ renderer: new XmlRenderer()
2122
+ });
2123
+ const result = await generateText({
2124
+ abortSignal: config?.abortSignal,
2125
+ providerOptions: options.providerOptions,
2126
+ model: options.model,
2127
+ system: systemPrompt,
2128
+ messages: convertToModelMessages(messages),
2129
+ stopWhen: stepCountIs(25),
2130
+ experimental_context: contextVariables,
2131
+ experimental_output: Output.object({ schema: options.schema })
2132
+ });
2133
+ return result.experimental_output;
701
2134
  },
702
- {
703
- retries: options.retries,
704
- shouldRetry: (context2) => {
705
- if (UnanswerableSQLError.isInstance(context2.error)) {
706
- return false;
707
- }
708
- if (SQLValidationError.isInstance(context2.error)) {
709
- return true;
710
- }
711
- console.log({
712
- NoObjectGeneratedError: NoObjectGeneratedError.isInstance(
713
- context2.error
714
- ),
715
- NoOutputGeneratedError: NoOutputGeneratedError.isInstance(
716
- context2.error
717
- ),
718
- APICallError: APICallError.isInstance(context2.error),
719
- JSONParseError: JSONParseError.isInstance(context2.error),
720
- TypeValidationError: TypeValidationError.isInstance(context2.error),
721
- NoContentGeneratedError: NoContentGeneratedError.isInstance(
722
- context2.error
723
- )
724
- });
725
- return APICallError.isInstance(context2.error) || JSONParseError.isInstance(context2.error) || TypeValidationError.isInstance(context2.error) || NoObjectGeneratedError.isInstance(context2.error) || NoOutputGeneratedError.isInstance(context2.error) || NoContentGeneratedError.isInstance(context2.error);
726
- },
727
- onFailedAttempt(context2) {
728
- logger.error(`toSQL`, context2.error);
729
- console.log(
730
- `Attempt ${context2.attemptNumber} failed. There are ${context2.retriesLeft} retries left.`
2135
+ async stream(contextVariables, config) {
2136
+ if (!options.context) {
2137
+ throw new Error(
2138
+ `structuredOutput "${options.name}" is missing a context.`
2139
+ );
2140
+ }
2141
+ if (!options.model) {
2142
+ throw new Error(
2143
+ `structuredOutput "${options.name}" is missing a model.`
731
2144
  );
732
- errors.push(context2.error);
733
2145
  }
2146
+ const { messages, systemPrompt } = await options.context.resolve({
2147
+ renderer: new XmlRenderer()
2148
+ });
2149
+ return streamText({
2150
+ abortSignal: config?.abortSignal,
2151
+ providerOptions: options.providerOptions,
2152
+ model: options.model,
2153
+ system: systemPrompt,
2154
+ messages: convertToModelMessages(messages),
2155
+ stopWhen: stepCountIs(25),
2156
+ experimental_transform: config?.transform ?? smoothStream(),
2157
+ experimental_context: contextVariables,
2158
+ experimental_output: Output.object({ schema: options.schema })
2159
+ });
734
2160
  }
735
- );
2161
+ };
736
2162
  }
737
2163
 
2164
+ // packages/text2sql/src/lib/agents/explainer.agent.ts
2165
+ import { groq } from "@ai-sdk/groq";
2166
+ import dedent from "dedent";
2167
+ import z from "zod";
2168
+ import { agent as agent2 } from "@deepagents/agent";
2169
+ var explainerAgent = agent2({
2170
+ name: "explainer",
2171
+ model: groq("openai/gpt-oss-20b"),
2172
+ prompt: (state) => dedent`
2173
+ You are an expert SQL tutor.
2174
+ Explain the following SQL query in plain English to a non-technical user.
2175
+ Focus on the intent and logic, not the syntax.
2176
+
2177
+ <sql>
2178
+ ${state?.sql}
2179
+ </sql>
2180
+ `,
2181
+ output: z.object({
2182
+ explanation: z.string().describe("The explanation of the SQL query.")
2183
+ })
2184
+ });
2185
+
738
2186
  // packages/text2sql/src/lib/agents/developer.agent.ts
739
2187
  var tools = {
740
2188
  /**
741
- * Generate SQL from natural language question.
742
- * Uses the toSql function with retry logic and validation.
2189
+ * Validate SQL query syntax before execution.
743
2190
  */
744
- generate_sql: tool({
745
- description: dedent2`
746
- Generate a validated SQL query from a natural language question.
747
- The query is automatically validated against the database schema.
748
- Use this when the user asks a question that requires data retrieval.
749
-
750
- Returns the SQL query along with generation metadata (attempts, any errors encountered).
751
- `,
752
- inputSchema: z3.object({
753
- question: z3.string().min(1).describe("The natural language question to convert to SQL")
2191
+ validate_query: tool({
2192
+ 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.`,
2193
+ inputSchema: z2.object({
2194
+ sql: z2.string().describe("The SQL query to validate.")
754
2195
  }),
755
- execute: async ({ question }, options) => {
2196
+ execute: async ({ sql }, options) => {
756
2197
  const state = toState(options);
757
- try {
758
- const result = await toSql({
759
- input: question,
760
- adapter: state.adapter,
761
- introspection: state.introspection,
762
- instructions: state.instructions
763
- });
764
- return {
765
- success: true,
766
- sql: result.sql,
767
- attempts: result.attempts,
768
- errors: result.errors
769
- };
770
- } catch (error) {
771
- return {
772
- success: false,
773
- error: error instanceof Error ? error.message : String(error)
774
- };
2198
+ const result = await state.adapter.validate(sql);
2199
+ if (typeof result === "string") {
2200
+ return `Validation Error: ${result}`;
775
2201
  }
2202
+ return "Query is valid.";
2203
+ }
2204
+ }),
2205
+ /**
2206
+ * Execute SQL query against the database.
2207
+ */
2208
+ db_query: tool({
2209
+ 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.`,
2210
+ inputSchema: z2.object({
2211
+ reasoning: z2.string().describe(
2212
+ "Your reasoning for why this SQL query is relevant to the user request."
2213
+ ),
2214
+ sql: z2.string().min(1, { message: "SQL query cannot be empty." }).refine(
2215
+ (sql) => sql.trim().toUpperCase().startsWith("SELECT") || sql.trim().toUpperCase().startsWith("WITH"),
2216
+ {
2217
+ message: "Only read-only SELECT or WITH queries are allowed."
2218
+ }
2219
+ ).describe("The SQL query to execute against the database.")
2220
+ }),
2221
+ execute: ({ sql }, options) => {
2222
+ const state = toState(options);
2223
+ return state.adapter.execute(sql);
776
2224
  }
777
2225
  }),
778
2226
  /**
@@ -785,129 +2233,44 @@ var tools = {
785
2233
 
786
2234
  The explanation focuses on intent and logic, not syntax.
787
2235
  `,
788
- inputSchema: z3.object({
789
- sql: z3.string().min(1).describe("The SQL query to explain")
2236
+ inputSchema: z2.object({
2237
+ sql: z2.string().min(1).describe("The SQL query to explain")
790
2238
  }),
791
2239
  execute: async ({ sql }) => {
792
- const { experimental_output } = await generate2(explainerAgent, [], {
2240
+ const { experimental_output } = await generate(explainerAgent, [], {
793
2241
  sql
794
2242
  });
795
2243
  return { explanation: experimental_output.explanation };
796
2244
  }
797
- }),
798
- /**
799
- * Show database schema introspection.
800
- */
801
- show_schema: tool({
802
- description: dedent2`
803
- Display the database schema introspection.
804
- Use this when the user wants to see available tables, columns, or relationships.
805
-
806
- Optionally filter by table name to reduce output.
807
- `,
808
- inputSchema: z3.object({
809
- table: z3.string().optional().describe(
810
- "Optional: filter to show only a specific table. If omitted, shows full schema."
811
- )
812
- }),
813
- execute: async ({ table }, options) => {
814
- const state = toState(options);
815
- if (!table) {
816
- return { schema: state.introspection };
817
- }
818
- const lines = state.introspection.split("\n");
819
- const tableLines = [];
820
- let inTable = false;
821
- let depth = 0;
822
- for (const line of lines) {
823
- const lowerLine = line.toLowerCase();
824
- if (lowerLine.includes(`name="${table.toLowerCase()}"`) || lowerLine.includes(`table="${table.toLowerCase()}"`)) {
825
- inTable = true;
826
- depth = 1;
827
- tableLines.push(line);
828
- continue;
829
- }
830
- if (inTable) {
831
- tableLines.push(line);
832
- if (line.includes("</")) {
833
- depth--;
834
- }
835
- if (line.includes("<") && !line.includes("</") && !line.includes("/>")) {
836
- depth++;
837
- }
838
- if (depth <= 0) {
839
- break;
840
- }
841
- }
842
- }
843
- if (tableLines.length === 0) {
844
- return {
845
- schema: `Table "${table}" not found in schema. Use show_schema without a table filter to see all available tables.`
846
- };
847
- }
848
- return { schema: tableLines.join("\n") };
849
- }
850
- }),
851
- /**
852
- * Developer scratchpad for notes and reasoning.
853
- */
854
- scratchpad: scratchpad_tool
2245
+ })
855
2246
  };
856
- var developerAgent = agent3({
857
- model: groq3("gpt-oss-20b"),
858
- tools,
859
- name: "developer_agent",
860
- prompt: (state) => {
861
- return dedent2`
862
- You are an expert SQL developer assistant helping power users build and refine queries.
863
-
864
- ## Your Capabilities
865
-
866
- You have access to the following tools:
867
-
868
- 1. **generate_sql**: Convert natural language questions to validated SQL queries
869
- - Automatically validates against the database schema
870
- - Returns generation metadata (attempts, errors if any)
871
-
872
- 2. **explain_sql**: Get a plain-English explanation of any SQL query
873
- - Helps users understand complex queries
874
- - Focuses on intent and logic, not syntax
875
-
876
- 3. **show_schema**: Display database schema information
877
- - Can show full schema or filter by table name
878
- - Use to explore available tables and columns
879
-
880
- 4. **scratchpad**: Record your reasoning and notes
881
-
882
- ## Guidelines
883
-
884
- - Be transparent: show the SQL you generate before explaining it
885
- - Be precise: provide exact column names and table references
886
- - Be helpful: suggest refinements and alternatives when appropriate
887
- - Support both natural language questions AND raw SQL input
888
- - When validating user SQL, explain any errors clearly
889
- - Use show_schema proactively when you need to verify table/column names
890
-
891
- ${state?.teachings || ""}
892
- ${state?.introspection || ""}
893
- `;
894
- }
895
- });
2247
+ var fragments = [
2248
+ persona({
2249
+ name: "developer_assistant",
2250
+ role: "You are an expert SQL developer assistant helping power users build and refine queries."
2251
+ }),
2252
+ hint("Be transparent: show the SQL you generate before explaining it"),
2253
+ hint("Be precise: provide exact column names and table references"),
2254
+ hint("Suggest refinements and alternatives when appropriate"),
2255
+ hint("Support both natural language questions AND raw SQL input"),
2256
+ hint("When validating user SQL, explain any errors clearly")
2257
+ ];
2258
+ var developer_agent_default = { tools, fragments };
896
2259
 
897
2260
  // packages/text2sql/src/lib/agents/suggestions.agents.ts
898
- import { groq as groq4 } from "@ai-sdk/groq";
2261
+ import { groq as groq2 } from "@ai-sdk/groq";
899
2262
  import dedent3 from "dedent";
900
- import z4 from "zod";
901
- import { agent as agent4, thirdPersonPrompt } from "@deepagents/agent";
902
- var suggestionsAgent = agent4({
2263
+ import z3 from "zod";
2264
+ import { agent as agent3, thirdPersonPrompt } from "@deepagents/agent";
2265
+ var suggestionsAgent = agent3({
903
2266
  name: "text2sql-suggestions",
904
- model: groq4("openai/gpt-oss-20b"),
905
- output: z4.object({
906
- suggestions: z4.array(
907
- z4.object({
908
- question: z4.string().describe("A complex, high-impact business question."),
909
- sql: z4.string().describe("The SQL statement needed to answer the question."),
910
- businessValue: z4.string().describe("Why the question matters to stakeholders.")
2267
+ model: groq2("openai/gpt-oss-20b"),
2268
+ output: z3.object({
2269
+ suggestions: z3.array(
2270
+ z3.object({
2271
+ question: z3.string().describe("A complex, high-impact business question."),
2272
+ sql: z3.string().describe("The SQL statement needed to answer the question."),
2273
+ businessValue: z3.string().describe("Why the question matters to stakeholders.")
911
2274
  })
912
2275
  ).min(1).max(5).describe("A set of up to two advanced question + SQL pairs.")
913
2276
  }),
@@ -947,187 +2310,33 @@ var suggestionsAgent = agent4({
947
2310
  });
948
2311
 
949
2312
  // packages/text2sql/src/lib/agents/text2sql.agent.ts
950
- import { groq as groq5 } from "@ai-sdk/groq";
2313
+ import { groq as groq3 } from "@ai-sdk/groq";
951
2314
  import { tool as tool2 } from "ai";
952
- import z5 from "zod";
953
- import { agent as agent5, toState as toState2 } from "@deepagents/agent";
954
- import { scratchpad_tool as scratchpad_tool2 } from "@deepagents/toolbox";
955
-
956
- // packages/text2sql/src/lib/memory/memory.prompt.ts
957
- var memory_prompt_default = toInstructions(
958
- "memory_guidelines",
959
- // Section 1: Core behavior
960
- guardrail({
961
- rule: "Silently store user information when revealed. Respond to memory-related requests.",
962
- reason: "Memory enables personalized responses across conversations",
963
- action: "Store silently on reveal. Respond explicitly when asked about memories."
964
- }),
965
- // Section 2: Workflows for memory operations
966
- workflow({
967
- task: "Store user identity silently",
968
- triggers: ["I'm a", "I work as", "my name is", "my role is"],
969
- steps: [
970
- "Extract name and/or role from the statement",
971
- 'Call remember_memory() with type: "identity" and name/role fields',
972
- "Do NOT announce or confirm - continue conversation naturally"
973
- ],
974
- notes: "This should happen silently without disrupting the conversation flow."
975
- }),
976
- workflow({
977
- task: "Store user preferences and context silently",
978
- triggers: ["When I say", "I prefer", "I'm working on"],
979
- steps: [
980
- "Identify the memory type (alias/preference/context/correction)",
981
- "Extract the relevant information",
982
- "Call remember_memory() with appropriate type and fields",
983
- "Do NOT announce or confirm - continue conversation naturally"
984
- ],
985
- notes: "This should happen silently without disrupting the conversation flow."
986
- }),
987
- workflow({
988
- task: "Forget a memory",
989
- triggers: ["forget that", "remove my", "delete the", "don't remember that"],
990
- steps: [
991
- "Call recall_memory() to list relevant memories",
992
- "Find the memory ID that matches user request",
993
- "Call forget_memory({ id }) with the found ID",
994
- "Confirm to user what was forgotten"
995
- ]
996
- }),
997
- workflow({
998
- task: "Update a memory",
999
- triggers: ["actually now I", "I changed", "update my", "no longer"],
1000
- steps: [
1001
- "Call recall_memory() to find the existing memory",
1002
- "Get the memory ID from results",
1003
- "Call update_memory({ id, memory }) with new data",
1004
- "Confirm the update to user"
1005
- ]
1006
- }),
1007
- // Section 3: Type disambiguation
1008
- explain({
1009
- concept: "identity vs context",
1010
- explanation: "Identity = WHO the user is (name and/or role, permanent). Context = WHAT they are working on (temporary focus).",
1011
- therefore: "Identity rarely changes. Context changes per project/task."
1012
- }),
1013
- explain({
1014
- concept: "alias vs correction",
1015
- explanation: "Alias = user defines their own term/shorthand. Correction = user fixes a misunderstanding about existing data/schema.",
1016
- therefore: "Alias is vocabulary. Correction is data clarification."
1017
- }),
1018
- explain({
1019
- concept: "preference memory type",
1020
- explanation: "Stores output/style/format preferences. Fields: { aspect: string, value: string }",
1021
- therefore: "Use for formatting, limits, display style, data scope filters"
1022
- }),
1023
- // Section 4: Clarifications for ambiguous situations
1024
- clarification({
1025
- when: 'user says something like "X actually means Y" but unclear if defining their term or correcting data',
1026
- ask: "Are you defining your own shorthand for this term, or correcting how the data/schema actually works?",
1027
- reason: "Alias is personal vocabulary. Correction is a data/schema clarification that applies universally."
1028
- }),
1029
- clarification({
1030
- when: "user mentions a project or task that could be their identity or current focus",
1031
- ask: "Is this your ongoing identity (name/role), or a specific project you are currently working on?",
1032
- reason: "Identity is permanent. Context is temporary focus that may change."
1033
- }),
1034
- // Section 5: Examples
1035
- // Identity - role
1036
- example({
1037
- question: "I'm the VP of Sales",
1038
- answer: 'remember_memory({ memory: { type: "identity", role: "VP of Sales" }})',
1039
- note: "Identity stores role"
1040
- }),
1041
- // Identity - name
1042
- example({
1043
- question: "My name is Sarah",
1044
- answer: 'remember_memory({ memory: { type: "identity", name: "Sarah" }})',
1045
- note: "Identity stores name"
1046
- }),
1047
- // Context
1048
- example({
1049
- question: "I'm analyzing Q4 performance",
1050
- answer: 'remember_memory({ memory: { type: "context", description: "Analyzing Q4 performance" }})',
1051
- note: "Current task = context"
1052
- }),
1053
- // Alias
1054
- example({
1055
- question: 'When I say "big customers", I mean revenue > $1M',
1056
- answer: 'remember_memory({ memory: { type: "alias", term: "big customers", meaning: "revenue > $1M" }})',
1057
- note: "User defining their vocabulary = alias"
1058
- }),
1059
- // Correction
1060
- example({
1061
- question: 'No, the status column uses 1 for active, not the string "active"',
1062
- answer: 'remember_memory({ memory: { type: "correction", subject: "status column values", clarification: "Uses 1 for active, not string" }})',
1063
- note: "Correcting schema/data assumption = correction"
1064
- }),
1065
- // Preference
1066
- example({
1067
- question: "Always show dates as YYYY-MM-DD",
1068
- answer: 'remember_memory({ memory: { type: "preference", aspect: "date format", value: "YYYY-MM-DD" }})'
1069
- }),
1070
- // Recall
1071
- example({
1072
- question: "What do you remember about me?",
1073
- answer: "recall_memory({})",
1074
- note: "List all stored memories"
1075
- }),
1076
- // Section 6: What NOT to remember
1077
- hint('Do NOT remember one-time query details like "show last 10 orders"'),
1078
- hint(
1079
- "Do NOT remember information already stored - use recall_memory to check first"
1080
- ),
1081
- hint("Do NOT remember obvious or universal facts")
1082
- );
1083
-
1084
- // packages/text2sql/src/lib/agents/text2sql.agent.ts
2315
+ import z4 from "zod";
2316
+ import { toState as toState2 } from "@deepagents/agent";
2317
+ import { scratchpad_tool } from "@deepagents/toolbox";
1085
2318
  var tools2 = {
1086
2319
  validate_query: tool2({
1087
2320
  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.`,
1088
- inputSchema: z5.object({
1089
- sql: z5.string().describe("The SQL query to validate.")
2321
+ inputSchema: z4.object({
2322
+ sql: z4.string().describe("The SQL query to validate.")
1090
2323
  }),
1091
2324
  execute: async ({ sql }, options) => {
1092
2325
  const state = toState2(options);
1093
2326
  const result = await state.adapter.validate(sql);
1094
2327
  if (typeof result === "string") {
1095
2328
  return `Validation Error: ${result}`;
1096
- }
1097
- return "Query is valid.";
1098
- }
1099
- }),
1100
- get_sample_rows: tool2({
1101
- description: `Sample rows from a table to understand data formatting, codes, and value patterns. Use BEFORE writing queries when:
1102
- - Column types in schema don't reveal format (e.g., "status" could be 'active'/'inactive' or 1/0)
1103
- - Date/time formats are unclear (ISO, Unix timestamp, locale-specific)
1104
- - You need to understand lookup table codes or enum values
1105
- - Column names are ambiguous (e.g., "type", "category", "code")`,
1106
- inputSchema: z5.object({
1107
- tableName: z5.string().describe("The name of the table to sample."),
1108
- columns: z5.array(z5.string()).optional().describe(
1109
- "Specific columns to sample. If omitted, samples all columns."
1110
- ),
1111
- limit: z5.number().min(1).max(10).default(3).optional().describe("Number of rows to sample (1-10, default 3).")
1112
- }),
1113
- execute: ({ tableName, columns, limit = 3 }, options) => {
1114
- const safeLimit = Math.min(Math.max(1, limit), 10);
1115
- const state = toState2(options);
1116
- const sql = state.adapter.buildSampleRowsQuery(
1117
- tableName,
1118
- columns,
1119
- safeLimit
1120
- );
1121
- return state.adapter.execute(sql);
2329
+ }
2330
+ return "Query is valid.";
1122
2331
  }
1123
2332
  }),
1124
2333
  db_query: tool2({
1125
2334
  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.`,
1126
- inputSchema: z5.object({
1127
- reasoning: z5.string().describe(
2335
+ inputSchema: z4.object({
2336
+ reasoning: z4.string().describe(
1128
2337
  "Your reasoning for why this SQL query is relevant to the user request."
1129
2338
  ),
1130
- sql: z5.string().min(1, { message: "SQL query cannot be empty." }).refine(
2339
+ sql: z4.string().min(1, { message: "SQL query cannot be empty." }).refine(
1131
2340
  (sql) => sql.trim().toUpperCase().startsWith("SELECT") || sql.trim().toUpperCase().startsWith("WITH"),
1132
2341
  {
1133
2342
  message: "Only read-only SELECT or WITH queries are allowed."
@@ -1139,198 +2348,20 @@ var tools2 = {
1139
2348
  return state.adapter.execute(sql);
1140
2349
  }
1141
2350
  }),
1142
- scratchpad: scratchpad_tool2
1143
- };
1144
- var userMemoryTypes = [
1145
- "identity",
1146
- "alias",
1147
- "preference",
1148
- "context",
1149
- "correction"
1150
- ];
1151
- var userMemorySchema = z5.discriminatedUnion("type", [
1152
- z5.object({
1153
- type: z5.literal("identity"),
1154
- description: z5.string().describe("The user's identity: role or/and name")
1155
- }),
1156
- z5.object({
1157
- type: z5.literal("alias"),
1158
- term: z5.string().describe("The term the user uses"),
1159
- meaning: z5.string().describe("What the user means by this term")
1160
- }),
1161
- z5.object({
1162
- type: z5.literal("preference"),
1163
- aspect: z5.string().describe("What aspect of output this preference applies to"),
1164
- value: z5.string().describe("The user's preference")
1165
- }),
1166
- z5.object({
1167
- type: z5.literal("context"),
1168
- description: z5.string().describe("What the user is currently working on")
1169
- }),
1170
- z5.object({
1171
- type: z5.literal("correction"),
1172
- subject: z5.string().describe("What was misunderstood"),
1173
- clarification: z5.string().describe("The correct understanding")
1174
- })
1175
- ]);
1176
- var memoryTools = {
1177
- remember_memory: tool2({
1178
- description: "Store something about the user for future conversations. Use silently when user shares facts, preferences, vocabulary, corrections, or context.",
1179
- inputSchema: z5.object({ memory: userMemorySchema }),
1180
- execute: async ({ memory }, options) => {
1181
- const state = toState2(
1182
- options
1183
- );
1184
- await state.memory.remember(state.userId, memory);
1185
- return "Remembered.";
1186
- }
1187
- }),
1188
- forget_memory: tool2({
1189
- description: "Forget a specific memory. Use when user asks to remove something.",
1190
- inputSchema: z5.object({
1191
- id: z5.string().describe("The ID of the teachable to forget")
1192
- }),
1193
- execute: async ({ id }, options) => {
1194
- const state = toState2(options);
1195
- await state.memory.forget(id);
1196
- return "Forgotten.";
1197
- }
1198
- }),
1199
- recall_memory: tool2({
1200
- description: "List stored memories for the current user. Use when user asks what you remember about them or wants to see their stored preferences.",
1201
- inputSchema: z5.object({
1202
- type: z5.enum(userMemoryTypes).optional().catch(void 0).describe("Optional: filter by memory type")
1203
- }),
1204
- execute: async ({ type }, options) => {
1205
- const state = toState2(
1206
- options
1207
- );
1208
- const memories = await state.memory.recall(state.userId, type);
1209
- if (memories.length === 0) {
1210
- return type ? `No ${type} memories stored.` : "No memories stored.";
1211
- }
1212
- return memories.map((m) => ({
1213
- id: m.id,
1214
- type: m.type,
1215
- data: m.data,
1216
- createdAt: m.createdAt
1217
- }));
1218
- }
1219
- }),
1220
- update_memory: tool2({
1221
- description: "Update an existing memory. Use when user wants to modify something you previously stored.",
1222
- inputSchema: z5.object({
1223
- memory: userMemorySchema,
1224
- id: z5.string().describe("The ID of the memory to update")
1225
- }),
1226
- execute: async ({ id, memory }, options) => {
1227
- const state = toState2(options);
1228
- await state.memory.update(id, memory);
1229
- return "Updated.";
1230
- }
1231
- })
2351
+ scratchpad: scratchpad_tool
1232
2352
  };
1233
- var chainOfThoughtPrompt = `
1234
- ## Query Reasoning Process
1235
-
1236
- Let's think step by step before writing SQL:
1237
-
1238
- 1. **Schema Link**: Which tables and columns are relevant? Verify they exist in the schema.
1239
- 2. **Join Path**: If multiple tables, what relationships connect them?
1240
- 3. **Filters**: What WHERE conditions are needed?
1241
- 4. **Aggregation**: Is COUNT, SUM, AVG, GROUP BY, or HAVING required?
1242
- 5. **Output**: What columns to SELECT and any ORDER BY or LIMIT?
1243
- 6. **Verify**: Do all referenced tables and columns exist in the schema above?
1244
-
1245
- For simple queries, steps 2-4 may not apply\u2014skip them.
1246
-
1247
- For complex queries requiring multiple data points, decompose into sub-questions:
1248
- - Break the question into simpler parts (Q1, Q2, ...)
1249
- - Determine if each part needs a subquery or CTE
1250
- - Combine the parts into the final query
1251
-
1252
- Keep reasoning brief. Verbose explanations cause errors.
1253
- `;
1254
- var fewShotExamples = `
1255
- ## Examples
1256
-
1257
- ### Example 1: Simple Filter
1258
- Question: "How many records in table_a have column_x equal to 'value'?"
1259
- Reasoning:
1260
- - Schema Link: table_a, column_x
1261
- - Filters: column_x = 'value'
1262
- - Aggregation: COUNT(*)
1263
- SQL: SELECT COUNT(*) FROM table_a WHERE column_x = 'value'
1264
-
1265
- ### Example 2: JOIN Query
1266
- Question: "Show column_y from table_b for each record in table_a where column_x is 'value'"
1267
- Reasoning:
1268
- - Schema Link: table_a (column_x, id), table_b (column_y, fk_a)
1269
- - Join Path: table_a.id \u2192 table_b.fk_a
1270
- - Filters: column_x = 'value'
1271
- - Output: column_y from table_b
1272
- SQL: SELECT b.column_y FROM table_a a JOIN table_b b ON b.fk_a = a.id WHERE a.column_x = 'value'
1273
-
1274
- ### Example 3: Aggregation with GROUP BY
1275
- Question: "What is the total of column_y grouped by column_x?"
1276
- Reasoning:
1277
- - Schema Link: table_a (column_x, column_y)
1278
- - Aggregation: SUM(column_y), GROUP BY column_x
1279
- - Output: column_x, sum
1280
- SQL: SELECT column_x, SUM(column_y) as total FROM table_a GROUP BY column_x
1281
-
1282
- ### Example 4: Complex Aggregation
1283
- Question: "Which values of column_x have more than 10 records, sorted by count descending?"
1284
- Reasoning:
1285
- - Schema Link: table_a (column_x)
1286
- - Aggregation: COUNT(*), GROUP BY column_x, HAVING > 10
1287
- - Output: column_x, count, ORDER BY count DESC
1288
- SQL: SELECT column_x, COUNT(*) as cnt FROM table_a GROUP BY column_x HAVING COUNT(*) > 10 ORDER BY cnt DESC
1289
-
1290
- ### Example 5: Subquery (Decomposition)
1291
- Question: "Show records from table_a where column_y is above average"
1292
- Reasoning:
1293
- - Decompose:
1294
- - Q1: What is the average of column_y?
1295
- - Q2: Which records have column_y above that value?
1296
- - Schema Link: table_a (column_y)
1297
- - Filters: column_y > (result of Q1)
1298
- - Output: all columns from matching records
1299
- - Verify: table_a and column_y exist \u2713
1300
- SQL: SELECT * FROM table_a WHERE column_y > (SELECT AVG(column_y) FROM table_a)
1301
-
1302
- ### Example 6: Complex Multi-Join (Decomposition)
1303
- Question: "Find the top 3 categories by total sales amount for orders placed last month"
1304
- Reasoning:
1305
- - Decompose:
1306
- - Q1: Which orders were placed last month?
1307
- - Q2: What is the total sales per category for those orders?
1308
- - Q3: Which 3 categories have the highest totals?
1309
- - Schema Link: orders (order_date, id), order_items (order_id, amount, product_id), products (id, category_id), categories (id, name)
1310
- - Join Path: orders \u2192 order_items \u2192 products \u2192 categories
1311
- - Filters: order_date within last month
1312
- - Aggregation: SUM(amount), GROUP BY category
1313
- - Output: category name, total, ORDER BY total DESC, LIMIT 3
1314
- - Verify: all tables and columns exist \u2713
1315
- SQL: SELECT c.name, SUM(oi.amount) as total FROM orders o JOIN order_items oi ON oi.order_id = o.id JOIN products p ON p.id = oi.product_id JOIN categories c ON c.id = p.category_id WHERE o.order_date >= DATE('now', '-1 month') GROUP BY c.id, c.name ORDER BY total DESC LIMIT 3
1316
- `;
1317
- var t_a_g = agent5({
1318
- model: groq5("openai/gpt-oss-20b"),
2353
+ var t_a_g = agent({
2354
+ model: groq3("openai/gpt-oss-20b"),
1319
2355
  tools: tools2,
1320
- name: "text2sql",
1321
- prompt: (state) => {
1322
- const hasMemory = !!state?.memory;
1323
- return `
1324
- ${state?.teachings || ""}
1325
- ${state?.introspection || ""}
1326
-
1327
- ${chainOfThoughtPrompt}
1328
-
1329
- ${fewShotExamples}
1330
-
1331
- ${hasMemory ? memory_prompt_default : ""}
1332
- `;
1333
- }
2356
+ name: "text2sql"
2357
+ // prompt: (state) => {
2358
+ // return `
2359
+ // ${state?.teachings || ''}
2360
+ // ${state?.introspection || ''}
2361
+ // ${chainOfThoughtPrompt}
2362
+ // ${fewShotExamples}
2363
+ // `;
2364
+ // },
1334
2365
  });
1335
2366
 
1336
2367
  // packages/text2sql/src/lib/checkpoint.ts
@@ -1536,842 +2567,186 @@ var JsonCache = class extends FileCache {
1536
2567
  }
1537
2568
  };
1538
2569
 
1539
- // packages/text2sql/src/lib/history/history.ts
1540
- var History = class {
1541
- };
1542
-
1543
- // packages/text2sql/src/lib/history/sqlite.history.ts
1544
- import { DatabaseSync } from "node:sqlite";
1545
-
1546
- // packages/text2sql/src/lib/history/history.sqlite.sql
1547
- 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';
1548
-
1549
- // packages/text2sql/src/lib/history/sqlite.history.ts
1550
- var SqliteHistory = class extends History {
1551
- #db;
1552
- constructor(path2) {
1553
- super();
1554
- this.#db = new DatabaseSync(path2);
1555
- this.#db.exec(history_sqlite_default);
1556
- }
1557
- async listChats(userId) {
1558
- return this.#db.prepare(`SELECT * FROM chats WHERE "userId" = ?`).all(userId);
1559
- }
1560
- async getChat(chatId) {
1561
- const rows = this.#db.prepare(
1562
- `SELECT
1563
- c.id as chatId, c."userId", c.title,
1564
- m.id as messageId, m.role, m."createdAt", m.content
1565
- FROM chats c
1566
- LEFT JOIN messages m ON m."chatId" = c.id
1567
- WHERE c.id = ?
1568
- ORDER BY m."createdAt" ASC`
1569
- ).all(chatId);
1570
- if (!rows.length) return null;
1571
- const firstRow = rows[0];
1572
- const chat = {
1573
- id: firstRow.chatId,
1574
- userId: firstRow.userId,
1575
- title: firstRow.title,
1576
- messages: []
1577
- };
1578
- for (const row of rows) {
1579
- if (row.messageId) {
1580
- chat.messages.push({
1581
- id: row.messageId,
1582
- chatId: firstRow.chatId,
1583
- role: row.role,
1584
- createdAt: row.createdAt,
1585
- content: JSON.parse(row.content)
1586
- });
1587
- }
1588
- }
1589
- return chat;
1590
- }
1591
- async createChat(chat) {
1592
- this.#db.prepare(`INSERT INTO chats (id, "userId", title) VALUES (?, ?, ?)`).run(chat.id, chat.userId, chat.title || null);
1593
- return chat;
1594
- }
1595
- async upsertChat(chat) {
1596
- this.#db.prepare(
1597
- `INSERT INTO chats (id, "userId", title) VALUES (?, ?, ?)
1598
- ON CONFLICT(id) DO UPDATE SET title = excluded.title, "userId" = excluded."userId"`
1599
- ).run(chat.id, chat.userId, chat.title || null);
1600
- return this.getChat(chat.id);
1601
- }
1602
- async deleteChat(chatId) {
1603
- this.#db.prepare(`DELETE FROM chats WHERE id = ?`).run(chatId);
1604
- }
1605
- async updateChat(chatId, updates) {
1606
- if (updates.title !== void 0) {
1607
- this.#db.prepare(`UPDATE chats SET title = ? WHERE id = ?`).run(updates.title, chatId);
1608
- }
1609
- }
1610
- async addMessage(message) {
1611
- const createdAt = message.createdAt ? message.createdAt.toISOString() : (/* @__PURE__ */ new Date()).toISOString();
1612
- this.#db.prepare(
1613
- `INSERT INTO messages (id, "chatId", role, "createdAt", content) VALUES (?, ?, ?, ?, ?)`
1614
- ).run(
1615
- message.id,
1616
- message.chatId,
1617
- message.role,
1618
- createdAt,
1619
- JSON.stringify(message.content)
1620
- );
1621
- }
1622
- async upsertMessage(message) {
1623
- const createdAt = message.createdAt ? message.createdAt.toISOString() : (/* @__PURE__ */ new Date()).toISOString();
1624
- this.#db.prepare(
1625
- `INSERT INTO messages (id, "chatId", role, "createdAt", content) VALUES (?, ?, ?, ?, ?)
1626
- ON CONFLICT(id) DO UPDATE SET "chatId" = excluded."chatId", role = excluded.role, "createdAt" = excluded."createdAt", content = excluded.content`
1627
- ).run(
1628
- message.id,
1629
- message.chatId,
1630
- message.role,
1631
- createdAt,
1632
- JSON.stringify(message.content)
1633
- );
1634
- return {
1635
- ...message,
1636
- createdAt
1637
- };
1638
- }
1639
- async deleteMessage(messageId) {
1640
- this.#db.prepare(`DELETE FROM messages WHERE id = ?`).run(messageId);
1641
- }
1642
- };
1643
-
1644
- // packages/text2sql/src/lib/history/memory.history.ts
1645
- var InMemoryHistory = class extends SqliteHistory {
1646
- constructor() {
1647
- super(":memory:");
1648
- }
1649
- };
1650
-
1651
- // packages/text2sql/src/lib/memory/sqlite.store.ts
1652
- import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
1653
- import { v7 } from "uuid";
1654
-
1655
- // packages/text2sql/src/lib/memory/store.sqlite.sql
1656
- var store_sqlite_default = 'CREATE TABLE IF NOT EXISTS "teachables" (\n "id" VARCHAR PRIMARY KEY,\n "userId" VARCHAR,\n "type" VARCHAR NOT NULL,\n "data" TEXT NOT NULL,\n "createdAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n "updatedAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP\n);\n\nCREATE INDEX IF NOT EXISTS "teachables_user_id_idx" ON "teachables" ("userId");\n\nCREATE INDEX IF NOT EXISTS "teachables_type_idx" ON "teachables" ("type");\n\nCREATE INDEX IF NOT EXISTS "teachables_user_type_idx" ON "teachables" ("userId", "type");\n';
1657
-
1658
- // packages/text2sql/src/lib/memory/store.ts
1659
- var TeachablesStore = class {
1660
- };
1661
-
1662
- // packages/text2sql/src/lib/memory/sqlite.store.ts
1663
- function rowToStoredTeachable(row) {
1664
- return {
1665
- id: row.id,
1666
- userId: row.userId,
1667
- type: row.type,
1668
- data: JSON.parse(row.data),
1669
- createdAt: row.createdAt,
1670
- updatedAt: row.updatedAt
1671
- };
1672
- }
1673
- var SqliteTeachablesStore = class extends TeachablesStore {
1674
- #db;
1675
- constructor(path2) {
1676
- super();
1677
- this.#db = new DatabaseSync2(path2);
1678
- this.#db.exec(store_sqlite_default);
1679
- }
1680
- async remember(userId, data) {
1681
- const id = v7();
1682
- const now = (/* @__PURE__ */ new Date()).toISOString();
1683
- this.#db.prepare(
1684
- "INSERT INTO teachables (id, userId, type, data, createdAt, updatedAt) VALUES (?, ?, ?, ?, ?, ?)"
1685
- ).run(id, userId, data.type, JSON.stringify(data), now, now);
1686
- return await this.get(id);
1687
- }
1688
- async recall(userId, type) {
1689
- let rows;
1690
- if (type === void 0) {
1691
- rows = this.#db.prepare("SELECT * FROM teachables WHERE userId = ? ORDER BY createdAt").all(userId);
1692
- } else {
1693
- rows = this.#db.prepare(
1694
- "SELECT * FROM teachables WHERE userId = ? AND type = ? ORDER BY createdAt"
1695
- ).all(userId, type);
1696
- }
1697
- return rows.map(rowToStoredTeachable);
1698
- }
1699
- async get(id) {
1700
- const row = this.#db.prepare("SELECT * FROM teachables WHERE id = ?").get(id);
1701
- if (!row) return null;
1702
- return rowToStoredTeachable(row);
1703
- }
1704
- async update(id, data) {
1705
- const now = (/* @__PURE__ */ new Date()).toISOString();
1706
- this.#db.prepare(
1707
- "UPDATE teachables SET data = ?, type = ?, updatedAt = ? WHERE id = ?"
1708
- ).run(JSON.stringify(data), data.type, now, id);
1709
- return await this.get(id);
1710
- }
1711
- async forget(id) {
1712
- this.#db.prepare("DELETE FROM teachables WHERE id = ?").run(id);
1713
- }
1714
- async forgetAll(userId) {
1715
- this.#db.prepare("DELETE FROM teachables WHERE userId = ?").run(userId);
1716
- }
1717
- async toTeachables(userId) {
1718
- const stored = await this.recall(userId);
1719
- return toTeachables(stored.map((s) => s.data));
1720
- }
1721
- };
1722
-
1723
- // packages/text2sql/src/lib/memory/memory.store.ts
1724
- var InMemoryTeachablesStore = class extends SqliteTeachablesStore {
1725
- constructor() {
1726
- super(":memory:");
1727
- }
1728
- };
1729
-
1730
2570
  // packages/text2sql/src/lib/sql.ts
1731
2571
  import {
1732
2572
  APICallError as APICallError2,
1733
2573
  InvalidToolInputError,
1734
2574
  NoSuchToolError,
1735
2575
  ToolCallRepairError,
1736
- generateId
2576
+ generateId as generateId3
1737
2577
  } from "ai";
1738
- import { v7 as v72 } from "uuid";
1739
- import {
1740
- generate as generate5,
1741
- stream,
1742
- user as user4
1743
- } from "@deepagents/agent";
1744
-
1745
- // packages/text2sql/src/lib/agents/chat1.agent.ts
1746
- import { groq as groq6 } from "@ai-sdk/groq";
1747
- import { tool as tool3 } from "ai";
1748
- import z6 from "zod";
1749
- import { agent as agent6, toState as toState3 } from "@deepagents/agent";
1750
- import { scratchpad_tool as scratchpad_tool3 } from "@deepagents/toolbox";
1751
- var tools3 = {
1752
- query_database: tool3({
1753
- description: `Query the database to answer a question. Provide your question in natural language and this tool will:
1754
- 1. Generate the appropriate SQL query
1755
- 2. Validate the SQL syntax
1756
- 3. Execute the query
1757
- 4. Return the results
2578
+ import "@deepagents/agent";
1758
2579
 
1759
- Use this tool when you need to retrieve data to answer the user's question.`,
1760
- inputSchema: z6.object({
1761
- question: z6.string().min(1).describe(
1762
- "The question to answer, expressed in natural language. Be specific about what data you need."
1763
- ),
1764
- reasoning: z6.string().optional().describe(
1765
- "Your reasoning for why this query is needed to answer the user."
1766
- )
1767
- }),
1768
- execute: async ({ question }, options) => {
1769
- const state = toState3(options);
1770
- try {
1771
- const sqlResult = await toSql({
1772
- input: question,
1773
- adapter: state.adapter,
1774
- introspection: state.introspection,
1775
- instructions: state.instructions
1776
- });
1777
- if (!sqlResult.sql) {
1778
- return {
1779
- success: false,
1780
- error: sqlResult.errors?.join("; ") || "Failed to generate SQL"
1781
- };
1782
- }
1783
- const data = await state.adapter.execute(sqlResult.sql);
1784
- return {
1785
- success: true,
1786
- sql: sqlResult.sql,
1787
- data
1788
- };
1789
- } catch (error) {
1790
- return {
1791
- success: false,
1792
- error: error instanceof Error ? error.message : "Unknown error occurred"
1793
- };
1794
- }
1795
- }
1796
- }),
1797
- scratchpad: scratchpad_tool3
1798
- };
1799
- var chat1Agent = agent6({
1800
- name: "chat1-combined",
1801
- model: groq6("openai/gpt-oss-20b"),
1802
- tools: tools3,
1803
- prompt: (state) => {
1804
- return `
1805
- ${state?.teachings || ""}
1806
- ${state?.introspection || ""}
1807
- `;
1808
- }
2580
+ // packages/text2sql/src/lib/agents/sql.agent.ts
2581
+ import { groq as groq4 } from "@ai-sdk/groq";
2582
+ import {
2583
+ APICallError,
2584
+ JSONParseError,
2585
+ NoContentGeneratedError,
2586
+ NoObjectGeneratedError,
2587
+ NoOutputGeneratedError,
2588
+ TypeValidationError,
2589
+ defaultSettingsMiddleware,
2590
+ wrapLanguageModel
2591
+ } from "ai";
2592
+ import { Console } from "node:console";
2593
+ import { createWriteStream } from "node:fs";
2594
+ import pRetry from "p-retry";
2595
+ import z5 from "zod";
2596
+ import "@deepagents/agent";
2597
+ var logger = new Console({
2598
+ stdout: createWriteStream("./sql-agent.log", { flags: "a" }),
2599
+ stderr: createWriteStream("./sql-agent-error.log", { flags: "a" }),
2600
+ inspectOptions: { depth: null }
1809
2601
  });
1810
-
1811
- // packages/text2sql/src/lib/agents/chat2.agent.ts
1812
- import { groq as groq7 } from "@ai-sdk/groq";
1813
- import { tool as tool4 } from "ai";
1814
- import z7 from "zod";
1815
- import { agent as agent7, toState as toState4 } from "@deepagents/agent";
1816
- import { scratchpad_tool as scratchpad_tool4 } from "@deepagents/toolbox";
1817
- var tools4 = {
1818
- generate_sql: tool4({
1819
- description: `Generate a SQL query from a natural language question. This tool will:
1820
- 1. Translate your question into SQL
1821
- 2. Validate the SQL syntax
1822
- 3. Retry with corrections if validation fails
1823
- 4. Return the validated SQL for your review
1824
-
1825
- Use this BEFORE execute_sql to see what query will be run. You can then:
1826
- - Explain the approach to the user
1827
- - Decide if the SQL looks correct
1828
- - Refine your question and regenerate if needed`,
1829
- inputSchema: z7.object({
1830
- question: z7.string().min(1).describe(
1831
- "The question to translate into SQL. Be specific about what data you need."
1832
- ),
1833
- reasoning: z7.string().optional().describe("Your reasoning for why this data is needed.")
1834
- }),
1835
- execute: async ({ question }, options) => {
1836
- const state = toState4(options);
1837
- try {
1838
- const sqlResult = await toSql({
1839
- input: question,
1840
- adapter: state.adapter,
1841
- introspection: state.introspection,
1842
- instructions: state.instructions
1843
- });
1844
- if (!sqlResult.sql) {
1845
- return {
1846
- success: false,
1847
- error: sqlResult.errors?.join("; ") || "Failed to generate SQL",
1848
- validationErrors: sqlResult.errors
1849
- };
1850
- }
1851
- return {
1852
- success: true,
1853
- sql: sqlResult.sql,
1854
- validationErrors: sqlResult.errors
1855
- };
1856
- } catch (error) {
1857
- return {
1858
- success: false,
1859
- error: error instanceof Error ? error.message : "Unknown error occurred"
1860
- };
1861
- }
1862
- }
1863
- }),
1864
- execute_sql: tool4({
1865
- description: `Execute a SQL query and return the results. Use this AFTER generate_sql to run the query.
1866
-
1867
- Only SELECT and WITH (CTE) queries are allowed - no data modification.`,
1868
- inputSchema: z7.object({
1869
- sql: z7.string().min(1).refine(
1870
- (sql) => sql.trim().toUpperCase().startsWith("SELECT") || sql.trim().toUpperCase().startsWith("WITH"),
1871
- {
1872
- message: "Only read-only SELECT or WITH queries are allowed."
1873
- }
1874
- ).describe("The SQL query to execute (must be SELECT or WITH)."),
1875
- reasoning: z7.string().optional().describe("Brief explanation of what this query retrieves.")
1876
- }),
1877
- execute: async ({ sql }, options) => {
1878
- const state = toState4(options);
1879
- try {
1880
- const data = await state.adapter.execute(sql);
1881
- return {
1882
- success: true,
1883
- data,
1884
- rowCount: Array.isArray(data) ? data.length : void 0
1885
- };
1886
- } catch (error) {
1887
- return {
1888
- success: false,
1889
- error: error instanceof Error ? error.message : "Query execution failed"
1890
- };
1891
- }
1892
- }
1893
- }),
1894
- scratchpad: scratchpad_tool4
2602
+ var RETRY_TEMPERATURES = [0, 0.2, 0.3];
2603
+ function extractSql(output) {
2604
+ const match = output.match(/```sql\n?([\s\S]*?)```/);
2605
+ return match ? match[1].trim() : output.trim();
2606
+ }
2607
+ var marker = Symbol("SQLValidationError");
2608
+ var SQLValidationError = class _SQLValidationError extends Error {
2609
+ [marker];
2610
+ constructor(message2) {
2611
+ super(message2);
2612
+ this.name = "SQLValidationError";
2613
+ this[marker] = true;
2614
+ }
2615
+ static isInstance(error) {
2616
+ return error instanceof _SQLValidationError && error[marker] === true;
2617
+ }
1895
2618
  };
1896
- var chat2Agent = agent7({
1897
- name: "chat2-with-peek",
1898
- model: groq7("openai/gpt-oss-20b"),
1899
- tools: tools4,
1900
- prompt: (state) => {
1901
- return `
1902
- ${state?.teachings || ""}
1903
- ${state?.introspection || ""}
1904
-
1905
- When answering questions that require database queries:
1906
- 1. First use generate_sql to create the SQL query
1907
- 2. Review the generated SQL to ensure it matches the user's intent
1908
- 3. Use execute_sql to run the query
1909
- 4. Present the results to the user
1910
-
1911
- If the generated SQL doesn't look right, you can refine your question and regenerate.
1912
- `;
2619
+ var UnanswerableSQLError = class _UnanswerableSQLError extends Error {
2620
+ constructor(message2) {
2621
+ super(message2);
2622
+ this.name = "UnanswerableSQLError";
1913
2623
  }
1914
- });
1915
-
1916
- // packages/text2sql/src/lib/agents/chat3.agent.ts
1917
- import { groq as groq8 } from "@ai-sdk/groq";
1918
- import { defaultSettingsMiddleware as defaultSettingsMiddleware2, tool as tool5, wrapLanguageModel as wrapLanguageModel2 } from "ai";
1919
- import z8 from "zod";
1920
- import { agent as agent8, generate as generate3, toState as toState5, user as user2 } from "@deepagents/agent";
1921
- import { scratchpad_tool as scratchpad_tool5 } from "@deepagents/toolbox";
1922
- var collaborativeSqlOutputSchema = z8.discriminatedUnion("status", [
1923
- z8.object({
1924
- status: z8.literal("success"),
1925
- sql: z8.string().describe("The generated SQL query"),
1926
- confidence: z8.enum(["high", "medium", "low"]).describe("Confidence level in this SQL being correct"),
1927
- assumptions: z8.array(z8.string()).optional().describe("Assumptions made during SQL generation"),
1928
- reasoning: z8.string().optional().describe("Brief explanation of the query approach")
1929
- }),
1930
- z8.object({
1931
- status: z8.literal("clarification_needed"),
1932
- question: z8.string().describe("Question to clarify the request"),
1933
- context: z8.string().optional().describe("Why this clarification is needed"),
1934
- options: z8.array(z8.string()).optional().describe("Possible options if applicable")
1935
- }),
1936
- z8.object({
1937
- status: z8.literal("unanswerable"),
1938
- reason: z8.string().describe("Why this question cannot be answered"),
1939
- suggestions: z8.array(z8.string()).optional().describe("Alternative questions that could be answered")
1940
- })
1941
- ]);
1942
- var collaborativeSqlAgent = agent8({
1943
- name: "collaborative-sql",
1944
- model: groq8("openai/gpt-oss-20b"),
1945
- output: collaborativeSqlOutputSchema,
1946
- prompt: (state) => {
1947
- return `
1948
- ${toInstructions(
1949
- "instructions",
1950
- persona({
1951
- name: "SQLCollab",
1952
- role: "You are an expert SQL query generator that collaborates with the user to ensure accuracy."
1953
- }),
1954
- ...state?.instructions || []
1955
- )}
1956
- ${state?.introspection || ""}
1957
-
1958
- IMPORTANT: You have three response options:
1959
-
1960
- 1. SUCCESS - When you can confidently generate SQL:
1961
- - Provide the SQL query
1962
- - Rate your confidence (high/medium/low)
1963
- - List any assumptions you made
1964
-
1965
- 2. CLARIFICATION_NEEDED - When the question is ambiguous:
1966
- - Ask a specific clarifying question
1967
- - Explain why clarification is needed
1968
- - Provide options if applicable
1969
-
1970
- 3. UNANSWERABLE - When the question cannot be answered with available data:
1971
- - Explain why
1972
- - Suggest alternative questions that could be answered
1973
-
1974
- Prefer asking for clarification over making low-confidence guesses.
1975
- `;
2624
+ static isInstance(error) {
2625
+ return error instanceof _UnanswerableSQLError;
1976
2626
  }
1977
- });
1978
- var tools5 = {
1979
- consult_sql_agent: tool5({
1980
- description: `Consult the SQL specialist agent to generate a query. The SQL agent may:
1981
- - Return a SQL query with confidence level and assumptions
1982
- - Ask for clarification if the question is ambiguous
1983
- - Indicate if the question cannot be answered with available data
1984
-
1985
- Based on the response:
1986
- - If clarification is needed, you can provide context or ask the user
1987
- - If assumptions were made, verify them with the user for important queries
1988
- - If unanswerable, relay the suggestions to the user`,
1989
- inputSchema: z8.object({
1990
- question: z8.string().min(1).describe("The question to translate into SQL."),
1991
- context: z8.string().optional().describe("Additional context from the conversation that might help."),
1992
- previousClarification: z8.string().optional().describe(
1993
- "Answer to a previous clarification question from the SQL agent."
1994
- )
1995
- }),
1996
- execute: async ({ question, context: context2, previousClarification }, options) => {
1997
- const state = toState5(options);
1998
- try {
1999
- let fullQuestion = question;
2000
- if (context2) {
2001
- fullQuestion = `${question}
2002
-
2003
- Additional context: ${context2}`;
2004
- }
2005
- if (previousClarification) {
2006
- fullQuestion = `${fullQuestion}
2007
-
2008
- Clarification provided: ${previousClarification}`;
2009
- }
2010
- const agentInstance = collaborativeSqlAgent.clone({
2011
- model: wrapLanguageModel2({
2012
- model: collaborativeSqlAgent.model,
2013
- middleware: defaultSettingsMiddleware2({
2014
- settings: { temperature: 0.1 }
2015
- })
2016
- })
2017
- });
2018
- const { experimental_output: output } = await generate3(
2019
- agentInstance,
2020
- [user2(fullQuestion)],
2021
- state
2627
+ };
2628
+ async function toSql(options) {
2629
+ const { maxRetries = 3 } = options;
2630
+ return withRetry(
2631
+ async (attemptNumber, errors, attempts) => {
2632
+ const context = new ContextEngine({
2633
+ store: new InMemoryContextStore(),
2634
+ chatId: `sql-gen-${crypto.randomUUID()}`
2635
+ });
2636
+ context.set(
2637
+ persona({
2638
+ name: "Freya",
2639
+ role: "You are an expert SQL query generator. You translate natural language questions into precise, efficient SQL queries based on the provided database schema."
2640
+ }),
2641
+ ...options.instructions,
2642
+ ...options.schemaFragments
2643
+ );
2644
+ if (errors.length) {
2645
+ context.set(
2646
+ user(options.input),
2647
+ user(
2648
+ `<validation_error>Your previous SQL query had the following error: ${errors.at(-1)?.message}. Please fix the query.</validation_error>`
2649
+ )
2022
2650
  );
2023
- if (output.status === "success") {
2024
- const validationError = await state.adapter.validate(output.sql);
2025
- if (validationError) {
2026
- return {
2027
- success: false,
2028
- error: `SQL validation failed: ${validationError}`
2029
- };
2030
- }
2031
- const data = await state.adapter.execute(output.sql);
2032
- return {
2033
- success: true,
2034
- sql: output.sql,
2035
- data,
2036
- confidence: output.confidence,
2037
- assumptions: output.assumptions
2038
- };
2039
- }
2040
- if (output.status === "clarification_needed") {
2041
- return {
2042
- success: false,
2043
- clarificationNeeded: output.question,
2044
- clarificationContext: output.context,
2045
- clarificationOptions: output.options
2046
- };
2047
- }
2048
- if (output.status === "unanswerable") {
2049
- return {
2050
- success: false,
2051
- unanswerableReason: output.reason,
2052
- suggestions: output.suggestions
2053
- };
2054
- }
2055
- return {
2056
- success: false,
2057
- error: "Unexpected response from SQL agent"
2058
- };
2059
- } catch (error) {
2060
- return {
2061
- success: false,
2062
- error: error instanceof Error ? error.message : "Unknown error occurred"
2063
- };
2651
+ } else {
2652
+ context.set(user(options.input));
2653
+ }
2654
+ const sqlOutput = structuredOutput({
2655
+ name: "text2sql",
2656
+ model: wrapLanguageModel({
2657
+ model: options.model ?? groq4("openai/gpt-oss-20b"),
2658
+ middleware: defaultSettingsMiddleware({
2659
+ settings: {
2660
+ temperature: RETRY_TEMPERATURES[attemptNumber - 1] ?? 0.3,
2661
+ topP: 1
2662
+ }
2663
+ })
2664
+ }),
2665
+ context,
2666
+ schema: z5.union([
2667
+ z5.object({
2668
+ sql: z5.string().describe("The SQL query that answers the question"),
2669
+ reasoning: z5.string().optional().describe("The reasoning steps taken to generate the SQL")
2670
+ }),
2671
+ z5.object({
2672
+ error: z5.string().describe(
2673
+ "Error message explaining why the question cannot be answered with the given schema"
2674
+ )
2675
+ })
2676
+ ])
2677
+ });
2678
+ const output = await sqlOutput.generate();
2679
+ if ("error" in output) {
2680
+ throw new UnanswerableSQLError(output.error);
2064
2681
  }
2065
- }
2066
- }),
2067
- execute_sql: tool5({
2068
- description: `Execute a SQL query directly. Use this when you have SQL that you want to run
2069
- (e.g., after receiving SQL from consult_sql_agent or for follow-up queries).`,
2070
- inputSchema: z8.object({
2071
- sql: z8.string().min(1).refine(
2072
- (sql) => sql.trim().toUpperCase().startsWith("SELECT") || sql.trim().toUpperCase().startsWith("WITH"),
2073
- {
2074
- message: "Only read-only SELECT or WITH queries are allowed."
2075
- }
2076
- ).describe("The SQL query to execute.")
2077
- }),
2078
- execute: async ({ sql }, options) => {
2079
- const state = toState5(options);
2080
- try {
2081
- const validationError = await state.adapter.validate(sql);
2082
- if (validationError) {
2083
- return {
2084
- success: false,
2085
- error: `Validation failed: ${validationError}`
2086
- };
2087
- }
2088
- const data = await state.adapter.execute(sql);
2089
- return {
2090
- success: true,
2091
- data,
2092
- rowCount: Array.isArray(data) ? data.length : void 0
2093
- };
2094
- } catch (error) {
2095
- return {
2096
- success: false,
2097
- error: error instanceof Error ? error.message : "Execution failed"
2098
- };
2682
+ const sql = extractSql(output.sql);
2683
+ const validationError = await options.adapter.validate(sql);
2684
+ if (validationError) {
2685
+ throw new SQLValidationError(validationError);
2099
2686
  }
2687
+ return {
2688
+ attempts,
2689
+ sql,
2690
+ errors: errors.length ? errors.map(formatErrorMessage) : void 0
2691
+ };
2692
+ },
2693
+ { retries: maxRetries - 1 }
2694
+ );
2695
+ }
2696
+ function formatErrorMessage(error) {
2697
+ if (APICallError.isInstance(error)) {
2698
+ if (error.message.startsWith("Failed to validate JSON")) {
2699
+ return `Schema validation failed: ${error.message}`;
2100
2700
  }
2101
- }),
2102
- scratchpad: scratchpad_tool5
2103
- };
2104
- var chat3Agent = agent8({
2105
- name: "chat3-collaborative",
2106
- model: groq8("openai/gpt-oss-20b"),
2107
- tools: tools5,
2108
- prompt: (state) => {
2109
- return `
2110
- ${state?.teachings || ""}
2111
- ${state?.introspection || ""}
2112
-
2113
- When answering questions that require database queries, use the consult_sql_agent tool.
2114
-
2115
- The SQL agent may respond in three ways:
2116
- 1. SUCCESS with SQL, confidence, and assumptions - review the confidence and assumptions
2117
- 2. CLARIFICATION_NEEDED with a question - either answer from context or ask the user
2118
- 3. UNANSWERABLE with reason and suggestions - relay this to the user helpfully
2119
-
2120
- For medium/low confidence results, consider mentioning the assumptions to the user.
2121
- For clarification requests, try to answer from conversation context first before asking the user.
2122
- `;
2701
+ return error.message;
2123
2702
  }
2124
- });
2125
-
2126
- // packages/text2sql/src/lib/agents/chat4.agent.ts
2127
- import { groq as groq9 } from "@ai-sdk/groq";
2128
- import { defaultSettingsMiddleware as defaultSettingsMiddleware3, tool as tool6, wrapLanguageModel as wrapLanguageModel3 } from "ai";
2129
- import z9 from "zod";
2130
- import { agent as agent9, generate as generate4, toState as toState6, user as user3 } from "@deepagents/agent";
2131
- import { scratchpad_tool as scratchpad_tool6 } from "@deepagents/toolbox";
2132
- var questionDecompositionSchema = z9.object({
2133
- originalQuestion: z9.string().describe("The original question being decomposed"),
2134
- breakdown: z9.array(z9.string()).min(1).describe(
2135
- "Semantic breakdown of the question into its component parts. Each part describes an aspect of what is being asked, NOT how to implement it."
2136
- ),
2137
- entities: z9.array(z9.string()).optional().describe(
2138
- "Key entities/concepts mentioned (e.g., customers, orders, products)"
2139
- ),
2140
- filters: z9.array(z9.string()).optional().describe(
2141
- 'Filtering criteria mentioned (e.g., "last quarter", "above $100")'
2142
- ),
2143
- aggregation: z9.string().optional().describe(
2144
- 'Type of aggregation if any (e.g., "count", "sum", "average", "top N")'
2145
- ),
2146
- ambiguities: z9.array(z9.string()).optional().describe("Any ambiguous parts that might need clarification")
2147
- });
2148
- var decompositionSqlOutputSchema = z9.union([
2149
- z9.object({
2150
- sql: z9.string().describe("The SQL query that answers the decomposed question"),
2151
- reasoning: z9.string().optional().describe("How each breakdown component was addressed")
2152
- }),
2153
- z9.object({
2154
- error: z9.string().describe("Error message if the question cannot be answered")
2155
- })
2156
- ]);
2157
- var decompositionSqlAgent = agent9({
2158
- name: "decomposition-sql",
2159
- model: groq9("openai/gpt-oss-20b"),
2160
- output: decompositionSqlOutputSchema,
2161
- prompt: (state) => {
2162
- return `
2163
- ${toInstructions(
2164
- "instructions",
2165
- persona({
2166
- name: "SQLDecomp",
2167
- role: "You are an expert SQL query generator. You receive questions broken down into semantic components and generate precise SQL."
2168
- }),
2169
- ...state?.instructions || []
2170
- )}
2171
- ${state?.introspection || ""}
2172
-
2173
- You will receive questions in a decomposed format with:
2174
- - breakdown: Semantic parts of the question
2175
- - entities: Key concepts mentioned
2176
- - filters: Filtering criteria
2177
- - aggregation: Type of aggregation needed
2178
- - ambiguities: Potentially unclear parts
2179
-
2180
- Address each component of the breakdown in your SQL.
2181
- If there are ambiguities, make reasonable assumptions and note them in your reasoning.
2182
- `;
2703
+ if (SQLValidationError.isInstance(error)) {
2704
+ return `SQL Validation Error: ${error.message}`;
2183
2705
  }
2184
- });
2185
- var RETRY_TEMPERATURES2 = [0, 0.2, 0.3];
2186
- var tools6 = {
2187
- query_with_decomposition: tool6({
2188
- description: `Query the database using question decomposition. This tool:
2189
- 1. Breaks down your question into semantic components (entities, filters, aggregations)
2190
- 2. Passes the decomposition to the SQL specialist
2191
- 3. Generates and validates SQL
2192
- 4. Executes and returns results
2193
-
2194
- This approach helps ensure all aspects of the question are addressed in the query.`,
2195
- inputSchema: z9.object({
2196
- question: z9.string().min(1).describe("The question to answer."),
2197
- breakdown: z9.array(z9.string()).min(1).describe(
2198
- 'Break down the question into its semantic parts. Each part should describe an ASPECT of what is being asked, not instructions. Example for "top customers by revenue last month": ["customers who made purchases", "revenue from those purchases", "time period: last month", "ranking: top by total revenue"]'
2199
- ),
2200
- entities: z9.array(z9.string()).optional().describe(
2201
- 'Key entities mentioned (e.g., ["customers", "orders", "products"])'
2202
- ),
2203
- filters: z9.array(z9.string()).optional().describe('Filter criteria (e.g., ["last month", "status = active"])'),
2204
- aggregation: z9.string().optional().describe(
2205
- 'Aggregation type if any (e.g., "sum revenue", "count orders", "top 10")'
2206
- ),
2207
- ambiguities: z9.array(z9.string()).optional().describe("Note any ambiguous parts you identified")
2208
- }),
2209
- execute: async ({ question, breakdown, entities, filters, aggregation, ambiguities }, options) => {
2210
- const state = toState6(options);
2211
- const decomposition = {
2212
- originalQuestion: question,
2213
- breakdown,
2214
- entities,
2215
- filters,
2216
- aggregation,
2217
- ambiguities
2218
- };
2219
- const decomposedPrompt = formatDecomposition(decomposition);
2220
- try {
2221
- let lastError;
2222
- for (let attempt = 0; attempt < RETRY_TEMPERATURES2.length; attempt++) {
2223
- const temperature = RETRY_TEMPERATURES2[attempt];
2224
- const agentInstance = decompositionSqlAgent.clone({
2225
- model: wrapLanguageModel3({
2226
- model: decompositionSqlAgent.model,
2227
- middleware: defaultSettingsMiddleware3({
2228
- settings: { temperature }
2229
- })
2230
- })
2231
- });
2232
- const prompt = lastError ? `${decomposedPrompt}
2233
-
2234
- Previous attempt failed with: ${lastError}. Please fix the query.` : decomposedPrompt;
2235
- const { experimental_output: output } = await generate4(
2236
- agentInstance,
2237
- [user3(prompt)],
2238
- state
2239
- );
2240
- if ("error" in output) {
2241
- return {
2242
- success: false,
2243
- question,
2244
- decomposition,
2245
- error: output.error,
2246
- attempts: attempt + 1
2247
- };
2248
- }
2249
- const validationError = await state.adapter.validate(output.sql);
2250
- if (validationError) {
2251
- lastError = validationError;
2252
- continue;
2253
- }
2254
- const data = await state.adapter.execute(output.sql);
2255
- return {
2256
- success: true,
2257
- question,
2258
- decomposition,
2259
- sql: output.sql,
2260
- data,
2261
- reasoning: output.reasoning,
2262
- attempts: attempt + 1
2263
- };
2264
- }
2265
- return {
2266
- success: false,
2267
- question,
2268
- decomposition,
2269
- error: `Failed after ${RETRY_TEMPERATURES2.length} attempts. Last error: ${lastError}`,
2270
- attempts: RETRY_TEMPERATURES2.length
2271
- };
2272
- } catch (error) {
2273
- return {
2274
- success: false,
2275
- question,
2276
- decomposition,
2277
- error: error instanceof Error ? error.message : "Unknown error occurred"
2278
- };
2279
- }
2280
- }
2281
- }),
2282
- execute_sql: tool6({
2283
- description: `Execute a SQL query directly. Use for follow-up queries or when you already have SQL.`,
2284
- inputSchema: z9.object({
2285
- sql: z9.string().min(1).refine(
2286
- (sql) => sql.trim().toUpperCase().startsWith("SELECT") || sql.trim().toUpperCase().startsWith("WITH"),
2287
- {
2288
- message: "Only read-only SELECT or WITH queries are allowed."
2706
+ return error.message;
2707
+ }
2708
+ async function withRetry(computation, options = { retries: 3 }) {
2709
+ const errors = [];
2710
+ let attempts = 0;
2711
+ return pRetry(
2712
+ (attemptNumber) => {
2713
+ return computation(attemptNumber, errors, ++attempts);
2714
+ },
2715
+ {
2716
+ retries: options.retries,
2717
+ shouldRetry: (context) => {
2718
+ if (UnanswerableSQLError.isInstance(context.error)) {
2719
+ return false;
2289
2720
  }
2290
- ).describe("The SQL query to execute.")
2291
- }),
2292
- execute: async ({ sql }, options) => {
2293
- const state = toState6(options);
2294
- try {
2295
- const validationError = await state.adapter.validate(sql);
2296
- if (validationError) {
2297
- return {
2298
- success: false,
2299
- error: `Validation failed: ${validationError}`
2300
- };
2721
+ if (SQLValidationError.isInstance(context.error)) {
2722
+ return true;
2301
2723
  }
2302
- const data = await state.adapter.execute(sql);
2303
- return {
2304
- success: true,
2305
- data,
2306
- rowCount: Array.isArray(data) ? data.length : void 0
2307
- };
2308
- } catch (error) {
2309
- return {
2310
- success: false,
2311
- error: error instanceof Error ? error.message : "Execution failed"
2312
- };
2724
+ console.log({
2725
+ NoObjectGeneratedError: NoObjectGeneratedError.isInstance(
2726
+ context.error
2727
+ ),
2728
+ NoOutputGeneratedError: NoOutputGeneratedError.isInstance(
2729
+ context.error
2730
+ ),
2731
+ APICallError: APICallError.isInstance(context.error),
2732
+ JSONParseError: JSONParseError.isInstance(context.error),
2733
+ TypeValidationError: TypeValidationError.isInstance(context.error),
2734
+ NoContentGeneratedError: NoContentGeneratedError.isInstance(
2735
+ context.error
2736
+ )
2737
+ });
2738
+ return APICallError.isInstance(context.error) || JSONParseError.isInstance(context.error) || TypeValidationError.isInstance(context.error) || NoObjectGeneratedError.isInstance(context.error) || NoOutputGeneratedError.isInstance(context.error) || NoContentGeneratedError.isInstance(context.error);
2739
+ },
2740
+ onFailedAttempt(context) {
2741
+ logger.error(`toSQL`, context.error);
2742
+ console.log(
2743
+ `Attempt ${context.attemptNumber} failed. There are ${context.retriesLeft} retries left.`
2744
+ );
2745
+ errors.push(context.error);
2313
2746
  }
2314
2747
  }
2315
- }),
2316
- scratchpad: scratchpad_tool6
2317
- };
2318
- function formatDecomposition(decomposition) {
2319
- const parts = [
2320
- `Original Question: ${decomposition.originalQuestion}`,
2321
- "",
2322
- "Question Breakdown:",
2323
- ...decomposition.breakdown.map((part, i) => ` ${i + 1}. ${part}`)
2324
- ];
2325
- if (decomposition.entities?.length) {
2326
- parts.push("", `Entities: ${decomposition.entities.join(", ")}`);
2327
- }
2328
- if (decomposition.filters?.length) {
2329
- parts.push("", `Filters: ${decomposition.filters.join(", ")}`);
2330
- }
2331
- if (decomposition.aggregation) {
2332
- parts.push("", `Aggregation: ${decomposition.aggregation}`);
2333
- }
2334
- if (decomposition.ambiguities?.length) {
2335
- parts.push(
2336
- "",
2337
- "Potential Ambiguities:",
2338
- ...decomposition.ambiguities.map((a) => ` - ${a}`)
2339
- );
2340
- }
2341
- parts.push(
2342
- "",
2343
- "Generate SQL that addresses each component of the breakdown."
2344
2748
  );
2345
- return parts.join("\n");
2346
2749
  }
2347
- var chat4Agent = agent9({
2348
- name: "chat4-decomposition",
2349
- model: groq9("openai/gpt-oss-20b"),
2350
- tools: tools6,
2351
- prompt: (state) => {
2352
- return `
2353
- ${state?.teachings || ""}
2354
- ${state?.introspection || ""}
2355
-
2356
- When answering questions that require database queries, use the query_with_decomposition tool.
2357
-
2358
- IMPORTANT: You must break down the question into semantic parts - describe WHAT is being asked, not HOW to implement it.
2359
-
2360
- Good breakdown example for "Which customers bought the most expensive products last quarter?":
2361
- - "customers who made purchases" (entity relationship)
2362
- - "products they purchased" (what products)
2363
- - "expensive products - need definition" (filter criteria - note ambiguity)
2364
- - "last quarter" (time filter)
2365
- - "most - ranking by count or value?" (aggregation - note ambiguity)
2366
-
2367
- Bad breakdown (too instructional):
2368
- - "JOIN customers with orders" (this is HOW, not WHAT)
2369
- - "Use ORDER BY and LIMIT" (this is implementation)
2370
-
2371
- Break the question into its semantic aspects, and let the SQL specialist figure out the implementation.
2372
- `;
2373
- }
2374
- });
2375
2750
 
2376
2751
  // packages/text2sql/src/lib/teach/teachings.ts
2377
2752
  function guidelines(options = {}) {
@@ -2514,80 +2889,41 @@ var Text2Sql = class {
2514
2889
  constructor(config) {
2515
2890
  this.#config = {
2516
2891
  adapter: config.adapter,
2517
- history: config.history,
2892
+ store: config.store,
2518
2893
  instructions: [
2519
2894
  ...guidelines(config.teachingsOptions),
2520
2895
  ...config.instructions ?? []
2521
2896
  ],
2522
2897
  tools: config.tools ?? {},
2523
2898
  model: config.model,
2524
- memory: config.memory,
2525
- introspection: new FileCache("introspection-" + config.version)
2899
+ introspection: new JsonCache(
2900
+ "introspection-" + config.version
2901
+ )
2526
2902
  };
2527
2903
  }
2528
- async explain(sql) {
2529
- const { experimental_output } = await generate5(
2530
- explainerAgent,
2531
- [user4("Explain this SQL.")],
2532
- { sql }
2533
- );
2534
- return experimental_output.explanation;
2535
- }
2536
2904
  async toSql(input) {
2537
- const introspection = await this.index();
2905
+ const schemaFragments = await this.index();
2538
2906
  const result = await toSql({
2539
2907
  input,
2540
2908
  adapter: this.#config.adapter,
2541
- introspection,
2909
+ schemaFragments,
2542
2910
  instructions: this.#config.instructions,
2543
2911
  model: this.#config.model
2544
2912
  });
2545
2913
  return result.sql;
2546
2914
  }
2547
- instruct(...dataset) {
2548
- this.#config.instructions.push(...dataset);
2549
- }
2550
- async inspect(agent10) {
2551
- const [grounding] = await Promise.all([this.index()]);
2552
- const renderToolNames = Object.keys(this.#config.tools ?? {}).filter(
2553
- (name) => name.startsWith("render_")
2554
- );
2555
- const allInstructions = [
2556
- ...this.#config.instructions,
2557
- guardrail({
2558
- rule: "ALWAYS use `get_sample_rows` before writing queries that filter or compare against string columns.",
2559
- reason: "Prevents SQL errors from wrong value formats.",
2560
- action: "Target specific columns (e.g., get_sample_rows('table', ['status', 'type']))."
2561
- }),
2562
- ...renderToolNames.length ? [
2563
- hint(`Rendering tools available: ${renderToolNames.join(", ")}.`),
2564
- styleGuide({
2565
- prefer: "Use render_* tools for trend/over time/monthly requests or chart asks",
2566
- always: "Include text insight alongside visualizations. Prefer line charts for time-based data."
2567
- })
2568
- ] : []
2569
- ];
2570
- const tools7 = Object.keys({
2571
- ...agent10.handoff.tools,
2572
- ...this.#config.memory ? memoryTools : {},
2573
- ...this.#config.tools
2574
- });
2575
- return {
2576
- tools: tools7,
2577
- prompt: agent10.instructions({
2578
- introspection: grounding,
2579
- teachings: toInstructions("instructions", ...allInstructions)
2580
- })
2581
- };
2582
- }
2583
- async index(options) {
2584
- const cached = await this.#config.introspection.get();
2915
+ /**
2916
+ * Introspect the database schema and return context fragments.
2917
+ * Results are cached to avoid repeated introspection.
2918
+ */
2919
+ async index() {
2920
+ const cached = await this.#config.introspection.read();
2585
2921
  if (cached) {
2586
2922
  return cached;
2587
2923
  }
2588
- const introspection = await this.#config.adapter.introspect();
2589
- await this.#config.introspection.set(introspection);
2590
- return introspection;
2924
+ const fragments2 = await this.#config.adapter.introspect();
2925
+ await this.#config.introspection.write(fragments2);
2926
+ return fragments2;
2591
2927
  }
2592
2928
  /**
2593
2929
  * Generate training data pairs using a producer factory.
@@ -2612,529 +2948,136 @@ var Text2Sql = class {
2612
2948
  const producer = factory(this.#config.adapter);
2613
2949
  return producer.toPairs();
2614
2950
  }
2615
- // public async suggest() {
2616
- // const [introspection, adapterInfo] = await Promise.all([
2617
- // this.index(),
2618
- // this.#config.adapter.introspect(),
2619
- // ]);
2620
- // const { experimental_output: output } = await generate(
2621
- // suggestionsAgent,
2622
- // [
2623
- // user(
2624
- // 'Suggest high-impact business questions and matching SQL queries for this database.',
2625
- // ),
2626
- // ],
2627
- // {
2628
- // },
2629
- // );
2630
- // return output.suggestions;
2631
- // }
2632
- async chat(messages, params) {
2633
- const [introspection, userTeachables] = await Promise.all([
2634
- this.index({ onProgress: console.log }),
2635
- this.#config.memory ? this.#config.memory.toTeachables(params.userId) : []
2636
- ]);
2637
- const chat = await this.#config.history.upsertChat({
2638
- id: params.chatId,
2639
- userId: params.userId,
2640
- title: "Chat " + params.chatId
2641
- });
2642
- const renderToolNames = Object.keys(this.#config.tools ?? {}).filter(
2643
- (name) => name.startsWith("render_")
2644
- );
2645
- const instructions = [
2646
- ...this.#config.instructions,
2647
- guardrail({
2648
- rule: "ALWAYS use `get_sample_rows` before writing queries that filter or compare against string columns.",
2649
- reason: "Prevents SQL errors from wrong value formats.",
2650
- action: "Target specific columns (e.g., get_sample_rows('table', ['status', 'type']))."
2651
- }),
2652
- ...renderToolNames.length ? [
2653
- hint(`Rendering tools available: ${renderToolNames.join(", ")}.`),
2654
- styleGuide({
2655
- prefer: "Use render_* tools for trend/over time/monthly requests or chart asks",
2656
- always: "Include text insight alongside visualizations. Prefer line charts for time-based data."
2657
- })
2658
- ] : []
2659
- ];
2660
- const originalMessage = [
2661
- ...chat.messages.map((it) => it.content),
2662
- ...messages
2663
- ];
2664
- const result = stream(
2665
- t_a_g.clone({
2666
- model: this.#config.model,
2667
- tools: {
2668
- ...t_a_g.handoff.tools,
2669
- ...this.#config.memory ? memoryTools : {},
2670
- ...this.#config.tools
2671
- }
2672
- }),
2673
- originalMessage,
2674
- {
2675
- teachings: toInstructions(
2676
- "instructions",
2677
- persona({
2678
- name: "Freya",
2679
- role: "You are an expert SQL query generator, answering business questions with accurate queries.",
2680
- tone: "Your tone should be concise and business-friendly."
2681
- }),
2682
- ...instructions,
2683
- teachable("user_profile", ...userTeachables)
2684
- ),
2685
- adapter: this.#config.adapter,
2686
- introspection,
2687
- memory: this.#config.memory,
2688
- userId: params.userId
2689
- }
2690
- );
2691
- return this.#createUIMessageStream(
2692
- result,
2693
- messages,
2694
- params,
2695
- originalMessage
2696
- );
2697
- }
2698
2951
  /**
2699
- * Chat1 - Combined tool, no peek.
2700
- *
2701
- * Uses a single `query_database` tool that:
2702
- * 1. Takes a natural language question
2703
- * 2. Internally calls toSql() to generate validated SQL
2704
- * 3. Executes the SQL
2705
- * 4. Returns both SQL and results
2706
- *
2707
- * The agent does NOT see the SQL before execution.
2952
+ * Build instructions for rendering tools (hint + styleGuide).
2953
+ * Returns fragments to include when render_* tools are available.
2708
2954
  */
2709
- async chat1(messages, params) {
2710
- const [introspection, userTeachables] = await Promise.all([
2711
- this.index({ onProgress: console.log }),
2712
- this.#config.memory ? this.#config.memory.toTeachables(params.userId) : []
2713
- ]);
2714
- const chat = await this.#config.history.upsertChat({
2715
- id: params.chatId,
2716
- userId: params.userId,
2717
- title: "Chat " + params.chatId
2718
- });
2955
+ #buildRenderingInstructions() {
2719
2956
  const renderToolNames = Object.keys(this.#config.tools ?? {}).filter(
2720
2957
  (name) => name.startsWith("render_")
2721
2958
  );
2722
- const instructions = [
2723
- ...this.#config.instructions,
2724
- ...renderToolNames.length ? [
2725
- hint(`Rendering tools available: ${renderToolNames.join(", ")}.`),
2726
- styleGuide({
2727
- prefer: "Use render_* tools for trend/over time/monthly requests or chart asks",
2728
- always: "Include text insight alongside visualizations. Prefer line charts for time-based data."
2729
- })
2730
- ] : []
2731
- ];
2732
- const originalMessage = [
2733
- ...chat.messages.map((it) => it.content),
2734
- ...messages
2959
+ if (!renderToolNames.length) {
2960
+ return [];
2961
+ }
2962
+ return [
2963
+ hint(`Rendering tools available: ${renderToolNames.join(", ")}.`),
2964
+ styleGuide({
2965
+ prefer: "Use render_* tools for trend/over time/monthly requests or chart asks",
2966
+ always: "Include text insight alongside visualizations. Prefer line charts for time-based data."
2967
+ })
2735
2968
  ];
2736
- const result = stream(
2737
- chat1Agent.clone({
2738
- model: this.#config.model,
2739
- tools: {
2740
- ...tools3,
2741
- ...this.#config.memory ? memoryTools : {},
2742
- ...this.#config.tools
2743
- }
2744
- }),
2745
- originalMessage,
2746
- {
2747
- teachings: toInstructions(
2748
- "instructions",
2749
- ...instructions,
2750
- teachable("user_profile", ...userTeachables)
2751
- ),
2752
- adapter: this.#config.adapter,
2753
- introspection,
2754
- instructions: this.#config.instructions,
2755
- memory: this.#config.memory,
2756
- userId: params.userId
2757
- }
2758
- );
2759
- return this.#createUIMessageStream(
2760
- result,
2761
- messages,
2762
- params,
2763
- originalMessage
2764
- );
2765
2969
  }
2766
- /**
2767
- * Chat2 - Separate generate + execute tools (with peek).
2768
- *
2769
- * Uses two separate tools:
2770
- * 1. `generate_sql` - Takes a question, returns validated SQL
2771
- * 2. `execute_sql` - Takes SQL, executes it
2772
- *
2773
- * The agent sees the SQL before execution and can review/refine.
2774
- */
2775
- async chat2(messages, params) {
2776
- const [introspection, userTeachables] = await Promise.all([
2777
- this.index({ onProgress: console.log }),
2778
- this.#config.memory ? this.#config.memory.toTeachables(params.userId) : []
2779
- ]);
2780
- const chat = await this.#config.history.upsertChat({
2781
- id: params.chatId,
2782
- userId: params.userId,
2783
- title: "Chat " + params.chatId
2784
- });
2785
- const renderToolNames = Object.keys(this.#config.tools ?? {}).filter(
2786
- (name) => name.startsWith("render_")
2787
- );
2788
- const instructions = [
2970
+ async chat(messages, params) {
2971
+ const schemaFragments = await this.index();
2972
+ const context = new ContextEngine({
2973
+ store: this.#config.store,
2974
+ chatId: params.chatId
2975
+ }).set(
2789
2976
  ...this.#config.instructions,
2790
- ...renderToolNames.length ? [
2791
- hint(`Rendering tools available: ${renderToolNames.join(", ")}.`),
2792
- styleGuide({
2793
- prefer: "Use render_* tools for trend/over time/monthly requests or chart asks",
2794
- always: "Include text insight alongside visualizations. Prefer line charts for time-based data."
2795
- })
2796
- ] : []
2797
- ];
2798
- const originalMessage = [
2799
- ...chat.messages.map((it) => it.content),
2800
- ...messages
2801
- ];
2802
- const result = stream(
2803
- chat2Agent.clone({
2804
- model: this.#config.model,
2805
- tools: {
2806
- ...tools4,
2807
- ...this.#config.memory ? memoryTools : {},
2808
- ...this.#config.tools
2809
- }
2810
- }),
2811
- originalMessage,
2812
- {
2813
- teachings: toInstructions(
2814
- "instructions",
2815
- ...instructions,
2816
- teachable("user_profile", ...userTeachables)
2817
- ),
2818
- adapter: this.#config.adapter,
2819
- introspection,
2820
- instructions: this.#config.instructions,
2821
- memory: this.#config.memory,
2822
- userId: params.userId
2823
- }
2977
+ ...this.#buildRenderingInstructions(),
2978
+ ...schemaFragments
2824
2979
  );
2825
- return this.#createUIMessageStream(
2826
- result,
2827
- messages,
2828
- params,
2829
- originalMessage
2830
- );
2831
- }
2832
- /**
2833
- * Chat3 - Agent conversation/collaboration.
2834
- *
2835
- * Enables richer interaction where the SQL agent can:
2836
- * - Surface confidence levels
2837
- * - State assumptions
2838
- * - Request clarification when uncertain
2839
- */
2840
- async chat3(messages, params) {
2841
- const [introspection, userTeachables] = await Promise.all([
2842
- this.index({ onProgress: console.log }),
2843
- this.#config.memory ? this.#config.memory.toTeachables(params.userId) : []
2844
- ]);
2845
- const chat = await this.#config.history.upsertChat({
2846
- id: params.chatId,
2847
- userId: params.userId,
2848
- title: "Chat " + params.chatId
2980
+ const userMsg = messages.at(-1);
2981
+ if (userMsg) {
2982
+ context.set(user(userMsg));
2983
+ await context.save();
2984
+ }
2985
+ const chatAgent = t_a_g.clone({
2986
+ model: this.#config.model,
2987
+ context,
2988
+ tools: { ...t_a_g.tools, ...this.#config.tools }
2849
2989
  });
2850
- const renderToolNames = Object.keys(this.#config.tools ?? {}).filter(
2851
- (name) => name.startsWith("render_")
2852
- );
2853
- const instructions = [
2854
- ...this.#config.instructions,
2855
- ...renderToolNames.length ? [
2856
- hint(`Rendering tools available: ${renderToolNames.join(", ")}.`),
2857
- styleGuide({
2858
- prefer: "Use render_* tools for trend/over time/monthly requests or chart asks",
2859
- always: "Include text insight alongside visualizations. Prefer line charts for time-based data."
2860
- })
2861
- ] : []
2862
- ];
2863
- const originalMessage = [
2864
- ...chat.messages.map((it) => it.content),
2865
- ...messages
2866
- ];
2867
- const result = stream(
2868
- chat3Agent.clone({
2869
- model: this.#config.model,
2870
- tools: {
2871
- ...tools5,
2872
- ...this.#config.memory ? memoryTools : {},
2873
- ...this.#config.tools
2874
- }
2875
- }),
2876
- originalMessage,
2877
- {
2878
- teachings: toInstructions(
2879
- "instructions",
2880
- ...instructions,
2881
- teachable("user_profile", ...userTeachables)
2882
- ),
2883
- adapter: this.#config.adapter,
2884
- introspection,
2885
- instructions: this.#config.instructions,
2886
- memory: this.#config.memory,
2887
- userId: params.userId
2990
+ const result = await chatAgent.stream({});
2991
+ return result.toUIMessageStream({
2992
+ onError: (error) => this.#formatError(error),
2993
+ sendStart: true,
2994
+ sendFinish: true,
2995
+ sendReasoning: true,
2996
+ sendSources: true,
2997
+ generateMessageId: generateId3,
2998
+ onFinish: async ({ responseMessage }) => {
2999
+ context.set(assistant(responseMessage));
3000
+ await context.save();
2888
3001
  }
2889
- );
2890
- return this.#createUIMessageStream(
2891
- result,
2892
- messages,
2893
- params,
2894
- originalMessage
2895
- );
2896
- }
2897
- /**
2898
- * Chat4 - Question decomposition approach.
2899
- *
2900
- * Breaks down questions into semantic components before SQL generation:
2901
- * - entities: Key concepts mentioned
2902
- * - filters: Filtering criteria
2903
- * - aggregation: Type of aggregation
2904
- * - breakdown: Semantic parts of the question
2905
- *
2906
- * This helps ensure all aspects of the question are addressed.
2907
- */
2908
- async chat4(messages, params) {
2909
- const [introspection, userTeachables] = await Promise.all([
2910
- this.index({ onProgress: console.log }),
2911
- this.#config.memory ? this.#config.memory.toTeachables(params.userId) : []
2912
- ]);
2913
- const chat = await this.#config.history.upsertChat({
2914
- id: params.chatId,
2915
- userId: params.userId,
2916
- title: "Chat " + params.chatId
2917
3002
  });
2918
- const renderToolNames = Object.keys(this.#config.tools ?? {}).filter(
2919
- (name) => name.startsWith("render_")
2920
- );
2921
- const instructions = [
2922
- ...this.#config.instructions,
2923
- ...renderToolNames.length ? [
2924
- hint(`Rendering tools available: ${renderToolNames.join(", ")}.`),
2925
- styleGuide({
2926
- prefer: "Use render_* tools for trend/over time/monthly requests or chart asks",
2927
- always: "Include text insight alongside visualizations. Prefer line charts for time-based data."
2928
- })
2929
- ] : []
2930
- ];
2931
- const originalMessage = [
2932
- ...chat.messages.map((it) => it.content),
2933
- ...messages
2934
- ];
2935
- const result = stream(
2936
- chat4Agent.clone({
2937
- model: this.#config.model,
2938
- tools: {
2939
- ...tools6,
2940
- ...this.#config.memory ? memoryTools : {},
2941
- ...this.#config.tools
2942
- }
2943
- }),
2944
- originalMessage,
2945
- {
2946
- teachings: toInstructions(
2947
- "instructions",
2948
- ...instructions,
2949
- teachable("user_profile", ...userTeachables)
2950
- ),
2951
- adapter: this.#config.adapter,
2952
- introspection,
2953
- instructions: this.#config.instructions,
2954
- memory: this.#config.memory,
2955
- userId: params.userId
2956
- }
2957
- );
2958
- return this.#createUIMessageStream(
2959
- result,
2960
- messages,
2961
- params,
2962
- originalMessage
2963
- );
2964
3003
  }
2965
3004
  /**
2966
- * Developer-focused conversational interface for SQL generation.
2967
- *
2968
- * Provides power-user tools for query building without execution:
2969
- * - generate_sql: Convert natural language to validated SQL
2970
- * - validate_sql: Check SQL syntax
2971
- * - explain_sql: Get plain-English explanations
2972
- * - show_schema: Explore database schema on demand
2973
- *
2974
- * @example
2975
- * ```typescript
2976
- * const result = await text2sql.developer(
2977
- * [user("Generate a query to find top customers by revenue")],
2978
- * { chatId: 'dev-session-1', userId: 'dev-1' }
2979
- * );
2980
- * // Agent responds with SQL, can validate, explain, or refine iteratively
2981
- * ```
3005
+ * Developer chat interface - power-user mode for SQL generation.
3006
+ * Uses db_query tool for direct SQL execution (LLM writes SQL).
2982
3007
  */
2983
3008
  async developer(messages, params) {
2984
- const [introspection, userTeachables] = await Promise.all([
2985
- this.index({ onProgress: console.log }),
2986
- this.#config.memory ? this.#config.memory.toTeachables(params.userId) : []
2987
- ]);
2988
- return withChat(
2989
- this.#config.history,
2990
- params,
2991
- messages,
2992
- (originalMessages) => stream(
2993
- developerAgent.clone({
2994
- model: this.#config.model
2995
- }),
2996
- originalMessages,
2997
- {
2998
- teachings: toInstructions(
2999
- "instructions",
3000
- ...this.#config.instructions,
3001
- teachable("user_profile", ...userTeachables)
3002
- ),
3003
- adapter: this.#config.adapter,
3004
- introspection,
3005
- instructions: this.#config.instructions
3006
- }
3007
- )
3009
+ const schemaFragments = await this.index();
3010
+ const context = new ContextEngine({
3011
+ store: this.#config.store,
3012
+ chatId: params.chatId
3013
+ }).set(
3014
+ ...developer_agent_default.fragments,
3015
+ ...this.#config.instructions,
3016
+ ...schemaFragments
3008
3017
  );
3009
- }
3010
- /**
3011
- * Helper to create UI message stream with common error handling and persistence.
3012
- */
3013
- #createUIMessageStream(result, messages, params, originalMessages) {
3018
+ const userMsg = messages.at(-1);
3019
+ if (userMsg) {
3020
+ context.set(user(userMsg));
3021
+ await context.save();
3022
+ }
3023
+ const developerAgent = agent({
3024
+ name: "developer",
3025
+ model: this.#config.model,
3026
+ context,
3027
+ tools: developer_agent_default.tools
3028
+ });
3029
+ const result = await developerAgent.stream({
3030
+ adapter: this.#config.adapter
3031
+ });
3014
3032
  return result.toUIMessageStream({
3015
- onError: (error) => {
3016
- if (NoSuchToolError.isInstance(error)) {
3017
- return "The model tried to call an unknown tool.";
3018
- } else if (InvalidToolInputError.isInstance(error)) {
3019
- return "The model called a tool with invalid arguments.";
3020
- } else if (ToolCallRepairError.isInstance(error)) {
3021
- return "The model tried to call a tool with invalid arguments, but it was repaired.";
3022
- } else if (APICallError2.isInstance(error)) {
3023
- console.error("Upstream API call failed:", error);
3024
- return `Upstream API call failed with status ${error.statusCode}: ${error.message}`;
3025
- } else {
3026
- return JSON.stringify(error);
3027
- }
3028
- },
3033
+ onError: (error) => this.#formatError(error),
3029
3034
  sendStart: true,
3030
3035
  sendFinish: true,
3031
3036
  sendReasoning: true,
3032
3037
  sendSources: true,
3033
- originalMessages,
3034
- generateMessageId: generateId,
3035
- onFinish: async ({ responseMessage, isContinuation }) => {
3036
- const userMessage = messages.at(-1);
3037
- if (!isContinuation && userMessage) {
3038
- console.log(
3039
- "Saving user message to history:",
3040
- JSON.stringify(userMessage)
3041
- );
3042
- await this.#config.history.addMessage({
3043
- id: v72(),
3044
- chatId: params.chatId,
3045
- role: userMessage.role,
3046
- content: userMessage
3047
- });
3048
- }
3049
- await this.#config.history.addMessage({
3050
- id: v72(),
3051
- chatId: params.chatId,
3052
- role: responseMessage.role,
3053
- content: responseMessage
3054
- });
3038
+ generateMessageId: generateId3,
3039
+ onFinish: async ({ responseMessage }) => {
3040
+ context.set(assistant(responseMessage));
3041
+ await context.save();
3055
3042
  }
3056
3043
  });
3057
3044
  }
3058
- };
3059
- async function withChat(history, params, messages, streamFn) {
3060
- const chat = await history.upsertChat({
3061
- id: params.chatId,
3062
- userId: params.userId,
3063
- title: "Chat " + params.chatId
3064
- });
3065
- const originalMessages = [
3066
- ...chat.messages.map((it) => it.content),
3067
- ...messages
3068
- ];
3069
- const result = streamFn(originalMessages);
3070
- return result.toUIMessageStream({
3071
- onError: (error) => {
3072
- if (NoSuchToolError.isInstance(error)) {
3073
- return "The model tried to call an unknown tool.";
3074
- } else if (InvalidToolInputError.isInstance(error)) {
3075
- return "The model called a tool with invalid arguments.";
3076
- } else if (ToolCallRepairError.isInstance(error)) {
3077
- return "The model tried to call a tool with invalid arguments, but it was repaired.";
3078
- } else if (APICallError2.isInstance(error)) {
3079
- console.error("Upstream API call failed:", error);
3080
- return `Upstream API call failed with status ${error.statusCode}: ${error.message}`;
3081
- } else {
3082
- return JSON.stringify(error);
3083
- }
3084
- },
3085
- sendStart: true,
3086
- sendFinish: true,
3087
- sendReasoning: true,
3088
- sendSources: true,
3089
- originalMessages,
3090
- generateMessageId: generateId,
3091
- onFinish: async ({ responseMessage, isContinuation }) => {
3092
- const userMessage = messages.at(-1);
3093
- if (!isContinuation && userMessage) {
3094
- console.log(
3095
- "Saving user message to history:",
3096
- JSON.stringify(userMessage)
3097
- );
3098
- await history.addMessage({
3099
- id: v72(),
3100
- chatId: params.chatId,
3101
- role: userMessage.role,
3102
- content: userMessage
3103
- });
3104
- }
3105
- await history.addMessage({
3106
- id: v72(),
3107
- chatId: params.chatId,
3108
- role: responseMessage.role,
3109
- content: responseMessage
3110
- });
3045
+ #formatError(error) {
3046
+ if (NoSuchToolError.isInstance(error)) {
3047
+ return "The model tried to call an unknown tool.";
3048
+ } else if (InvalidToolInputError.isInstance(error)) {
3049
+ return "The model called a tool with invalid arguments.";
3050
+ } else if (ToolCallRepairError.isInstance(error)) {
3051
+ return "The model tried to call a tool with invalid arguments, but it was repaired.";
3052
+ } else if (APICallError2.isInstance(error)) {
3053
+ console.error("Upstream API call failed:", error);
3054
+ return `Upstream API call failed with status ${error.statusCode}: ${error.message}`;
3111
3055
  }
3112
- });
3113
- }
3056
+ return JSON.stringify(error);
3057
+ }
3058
+ };
3114
3059
  export {
3115
3060
  Adapter,
3116
3061
  Checkpoint,
3117
3062
  FileCache,
3118
- History,
3119
- InMemoryHistory,
3120
- InMemoryTeachablesStore,
3121
3063
  JsonCache,
3122
3064
  Point,
3123
- SqliteHistory,
3124
- SqliteTeachablesStore,
3125
- TeachablesStore,
3126
3065
  Text2Sql,
3127
3066
  applyTablesFilter,
3128
- developerAgent,
3067
+ column,
3068
+ constraint,
3069
+ dialectInfo,
3129
3070
  filterRelationshipsByTables,
3130
3071
  filterTablesByName,
3131
3072
  getTablesWithRelated,
3132
3073
  guidelines,
3133
3074
  hashConfig,
3075
+ index,
3134
3076
  matchesFilter,
3135
- memoryTools,
3077
+ relationship,
3136
3078
  suggestionsAgent,
3137
3079
  t_a_g,
3138
- withChat
3080
+ table,
3081
+ view
3139
3082
  };
3140
3083
  //# sourceMappingURL=index.js.map