@deepagents/text2sql 0.11.0 → 0.12.1

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 (54) hide show
  1. package/dist/index.d.ts +3 -1
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +1334 -491
  4. package/dist/index.js.map +4 -4
  5. package/dist/lib/adapters/groundings/index.js +2034 -50
  6. package/dist/lib/adapters/groundings/index.js.map +4 -4
  7. package/dist/lib/adapters/groundings/report.grounding.d.ts.map +1 -1
  8. package/dist/lib/adapters/mysql/index.js +2034 -50
  9. package/dist/lib/adapters/mysql/index.js.map +4 -4
  10. package/dist/lib/adapters/postgres/index.js +2034 -50
  11. package/dist/lib/adapters/postgres/index.js.map +4 -4
  12. package/dist/lib/adapters/spreadsheet/index.js +35 -49
  13. package/dist/lib/adapters/spreadsheet/index.js.map +4 -4
  14. package/dist/lib/adapters/sqlite/index.js +2034 -50
  15. package/dist/lib/adapters/sqlite/index.js.map +4 -4
  16. package/dist/lib/adapters/sqlserver/column-stats.sqlserver.grounding.d.ts.map +1 -1
  17. package/dist/lib/adapters/sqlserver/index.js +2035 -53
  18. package/dist/lib/adapters/sqlserver/index.js.map +4 -4
  19. package/dist/lib/agents/developer.agent.d.ts.map +1 -1
  20. package/dist/lib/agents/explainer.agent.d.ts +4 -5
  21. package/dist/lib/agents/explainer.agent.d.ts.map +1 -1
  22. package/dist/lib/agents/question.agent.d.ts.map +1 -1
  23. package/dist/lib/agents/result-tools.d.ts +37 -0
  24. package/dist/lib/agents/result-tools.d.ts.map +1 -0
  25. package/dist/lib/agents/sql.agent.d.ts +1 -1
  26. package/dist/lib/agents/sql.agent.d.ts.map +1 -1
  27. package/dist/lib/agents/teachables.agent.d.ts.map +1 -1
  28. package/dist/lib/agents/text2sql.agent.d.ts +0 -21
  29. package/dist/lib/agents/text2sql.agent.d.ts.map +1 -1
  30. package/dist/lib/checkpoint.d.ts +1 -1
  31. package/dist/lib/checkpoint.d.ts.map +1 -1
  32. package/dist/lib/instructions.d.ts +9 -28
  33. package/dist/lib/instructions.d.ts.map +1 -1
  34. package/dist/lib/sql.d.ts +1 -1
  35. package/dist/lib/sql.d.ts.map +1 -1
  36. package/dist/lib/synthesis/extractors/base-contextual-extractor.d.ts +6 -7
  37. package/dist/lib/synthesis/extractors/base-contextual-extractor.d.ts.map +1 -1
  38. package/dist/lib/synthesis/extractors/last-query-extractor.d.ts.map +1 -1
  39. package/dist/lib/synthesis/extractors/segmented-context-extractor.d.ts +0 -6
  40. package/dist/lib/synthesis/extractors/segmented-context-extractor.d.ts.map +1 -1
  41. package/dist/lib/synthesis/extractors/sql-extractor.d.ts.map +1 -1
  42. package/dist/lib/synthesis/index.js +2394 -2132
  43. package/dist/lib/synthesis/index.js.map +4 -4
  44. package/dist/lib/synthesis/synthesizers/breadth-evolver.d.ts.map +1 -1
  45. package/dist/lib/synthesis/synthesizers/depth-evolver.d.ts +3 -3
  46. package/dist/lib/synthesis/synthesizers/depth-evolver.d.ts.map +1 -1
  47. package/dist/lib/synthesis/synthesizers/persona-generator.d.ts.map +1 -1
  48. package/dist/lib/synthesis/synthesizers/schema-synthesizer.d.ts +1 -1
  49. package/dist/lib/synthesis/synthesizers/schema-synthesizer.d.ts.map +1 -1
  50. package/package.json +9 -15
  51. package/dist/lib/instructions.js +0 -432
  52. package/dist/lib/instructions.js.map +0 -7
  53. package/dist/lib/teach/teachings.d.ts +0 -11
  54. package/dist/lib/teach/teachings.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -387,15 +387,16 @@ function getTablesWithRelated(allTables, relationships, filter) {
387
387
 
388
388
  // packages/text2sql/src/lib/agents/developer.agent.ts
389
389
  import { tool } from "ai";
390
- import dedent2 from "dedent";
390
+ import dedent from "dedent";
391
391
  import z2 from "zod";
392
- import { generate, toState } from "@deepagents/agent";
392
+ import { toState } from "@deepagents/agent";
393
393
 
394
394
  // packages/context/dist/index.js
395
395
  import { encode } from "gpt-tokenizer";
396
396
  import { generateId } from "ai";
397
397
  import pluralize from "pluralize";
398
398
  import { titlecase } from "stringcase";
399
+ import chalk from "chalk";
399
400
  import { defineCommand } from "just-bash";
400
401
  import spawn from "nano-spawn";
401
402
  import "bash-tool";
@@ -405,7 +406,9 @@ import {
405
406
  } from "bash-tool";
406
407
  import YAML from "yaml";
407
408
  import { DatabaseSync } from "node:sqlite";
409
+ import { groq } from "@ai-sdk/groq";
408
410
  import {
411
+ NoSuchToolError,
409
412
  Output,
410
413
  convertToModelMessages,
411
414
  createUIMessageStream,
@@ -415,7 +418,7 @@ import {
415
418
  stepCountIs,
416
419
  streamText
417
420
  } from "ai";
418
- import chalk from "chalk";
421
+ import chalk2 from "chalk";
419
422
  import "zod";
420
423
  import "@deepagents/agent";
421
424
  var defaultTokenizer = {
@@ -547,6 +550,12 @@ function isFragmentObject(data) {
547
550
  function isMessageFragment(fragment2) {
548
551
  return fragment2.type === "message";
549
552
  }
553
+ function fragment(name, ...children) {
554
+ return {
555
+ name,
556
+ data: children
557
+ };
558
+ }
550
559
  function user(content) {
551
560
  const message2 = typeof content === "string" ? {
552
561
  id: generateId(),
@@ -594,7 +603,7 @@ function message(content) {
594
603
  } : content;
595
604
  return {
596
605
  id: message2.id,
597
- name: "message",
606
+ name: message2.role,
598
607
  data: "content",
599
608
  type: "message",
600
609
  persist: true,
@@ -616,6 +625,22 @@ function assistantText(content, options) {
616
625
  parts: [{ type: "text", text: content }]
617
626
  });
618
627
  }
628
+ var LAZY_ID = Symbol("lazy-id");
629
+ function isLazyFragment(fragment2) {
630
+ return LAZY_ID in fragment2;
631
+ }
632
+ function lastAssistantMessage(content) {
633
+ return {
634
+ name: "assistant",
635
+ type: "message",
636
+ persist: true,
637
+ data: "content",
638
+ [LAZY_ID]: {
639
+ type: "last-assistant",
640
+ content
641
+ }
642
+ };
643
+ }
619
644
  var ContextRenderer = class {
620
645
  options;
621
646
  constructor(options = {}) {
@@ -639,6 +664,68 @@ var ContextRenderer = class {
639
664
  }
640
665
  return groups;
641
666
  }
667
+ /**
668
+ * Remove null/undefined from fragments and fragment data recursively.
669
+ * This protects renderers from nullish values and ensures they are ignored
670
+ * consistently across all output formats.
671
+ */
672
+ sanitizeFragments(fragments2) {
673
+ const sanitized = [];
674
+ for (const fragment2 of fragments2) {
675
+ const cleaned = this.sanitizeFragment(fragment2, /* @__PURE__ */ new WeakSet());
676
+ if (cleaned) {
677
+ sanitized.push(cleaned);
678
+ }
679
+ }
680
+ return sanitized;
681
+ }
682
+ sanitizeFragment(fragment2, seen) {
683
+ const data = this.sanitizeData(fragment2.data, seen);
684
+ if (data == null) {
685
+ return null;
686
+ }
687
+ return {
688
+ ...fragment2,
689
+ data
690
+ };
691
+ }
692
+ sanitizeData(data, seen) {
693
+ if (data == null) {
694
+ return void 0;
695
+ }
696
+ if (isFragment(data)) {
697
+ return this.sanitizeFragment(data, seen) ?? void 0;
698
+ }
699
+ if (Array.isArray(data)) {
700
+ if (seen.has(data)) {
701
+ return void 0;
702
+ }
703
+ seen.add(data);
704
+ const cleaned = [];
705
+ for (const item of data) {
706
+ const sanitizedItem = this.sanitizeData(item, seen);
707
+ if (sanitizedItem != null) {
708
+ cleaned.push(sanitizedItem);
709
+ }
710
+ }
711
+ return cleaned;
712
+ }
713
+ if (isFragmentObject(data)) {
714
+ if (seen.has(data)) {
715
+ return void 0;
716
+ }
717
+ seen.add(data);
718
+ const cleaned = {};
719
+ for (const [key, value] of Object.entries(data)) {
720
+ const sanitizedValue = this.sanitizeData(value, seen);
721
+ if (sanitizedValue != null) {
722
+ cleaned[key] = sanitizedValue;
723
+ }
724
+ }
725
+ return cleaned;
726
+ }
727
+ return data;
728
+ }
642
729
  /**
643
730
  * Template method - dispatches value to appropriate handler.
644
731
  */
@@ -666,7 +753,8 @@ var ContextRenderer = class {
666
753
  };
667
754
  var XmlRenderer = class extends ContextRenderer {
668
755
  render(fragments2) {
669
- return fragments2.map((f) => this.#renderTopLevel(f)).filter(Boolean).join("\n");
756
+ const sanitized = this.sanitizeFragments(fragments2);
757
+ return sanitized.map((f) => this.#renderTopLevel(f)).filter(Boolean).join("\n");
670
758
  }
671
759
  #renderTopLevel(fragment2) {
672
760
  if (this.isPrimitive(fragment2.data)) {
@@ -679,10 +767,13 @@ var XmlRenderer = class extends ContextRenderer {
679
767
  const child = this.renderFragment(fragment2.data, { depth: 1, path: [] });
680
768
  return this.#wrap(fragment2.name, [child]);
681
769
  }
682
- return this.#wrap(
683
- fragment2.name,
684
- this.renderEntries(fragment2.data, { depth: 1, path: [] })
685
- );
770
+ if (isFragmentObject(fragment2.data)) {
771
+ return this.#wrap(
772
+ fragment2.name,
773
+ this.renderEntries(fragment2.data, { depth: 1, path: [] })
774
+ );
775
+ }
776
+ return "";
686
777
  }
687
778
  #renderArray(name, items, depth) {
688
779
  const fragmentItems = items.filter(isFragment);
@@ -690,9 +781,19 @@ var XmlRenderer = class extends ContextRenderer {
690
781
  const children = [];
691
782
  for (const item of nonFragmentItems) {
692
783
  if (item != null) {
693
- children.push(
694
- this.#leaf(pluralize.singular(name), String(item), depth + 1)
695
- );
784
+ if (isFragmentObject(item)) {
785
+ children.push(
786
+ this.#wrapIndented(
787
+ pluralize.singular(name),
788
+ this.renderEntries(item, { depth: depth + 2, path: [] }),
789
+ depth + 1
790
+ )
791
+ );
792
+ } else {
793
+ children.push(
794
+ this.#leaf(pluralize.singular(name), String(item), depth + 1)
795
+ );
796
+ }
696
797
  }
697
798
  }
698
799
  if (this.options.groupFragments && fragmentItems.length > 0) {
@@ -734,8 +835,14 @@ ${this.#indent(safe, 2)}
734
835
  if (Array.isArray(data)) {
735
836
  return this.#renderArrayIndented(name, data, ctx.depth);
736
837
  }
737
- const children = this.renderEntries(data, { ...ctx, depth: ctx.depth + 1 });
738
- return this.#wrapIndented(name, children, ctx.depth);
838
+ if (isFragmentObject(data)) {
839
+ const children = this.renderEntries(data, {
840
+ ...ctx,
841
+ depth: ctx.depth + 1
842
+ });
843
+ return this.#wrapIndented(name, children, ctx.depth);
844
+ }
845
+ return "";
739
846
  }
740
847
  #renderArrayIndented(name, items, depth) {
741
848
  const fragmentItems = items.filter(isFragment);
@@ -743,9 +850,19 @@ ${this.#indent(safe, 2)}
743
850
  const children = [];
744
851
  for (const item of nonFragmentItems) {
745
852
  if (item != null) {
746
- children.push(
747
- this.#leaf(pluralize.singular(name), String(item), depth + 1)
748
- );
853
+ if (isFragmentObject(item)) {
854
+ children.push(
855
+ this.#wrapIndented(
856
+ pluralize.singular(name),
857
+ this.renderEntries(item, { depth: depth + 2, path: [] }),
858
+ depth + 1
859
+ )
860
+ );
861
+ } else {
862
+ children.push(
863
+ this.#leaf(pluralize.singular(name), String(item), depth + 1)
864
+ );
865
+ }
749
866
  }
750
867
  }
751
868
  if (this.options.groupFragments && fragmentItems.length > 0) {
@@ -774,7 +891,19 @@ ${this.#indent(safe, 2)}
774
891
  return "";
775
892
  }
776
893
  const itemTag = pluralize.singular(key);
777
- const children = items.filter((item) => item != null).map((item) => this.#leaf(itemTag, String(item), ctx.depth + 1));
894
+ const children = items.filter((item) => item != null).map((item) => {
895
+ if (isFragment(item)) {
896
+ return this.renderFragment(item, { ...ctx, depth: ctx.depth + 1 });
897
+ }
898
+ if (isFragmentObject(item)) {
899
+ return this.#wrapIndented(
900
+ itemTag,
901
+ this.renderEntries(item, { ...ctx, depth: ctx.depth + 2 }),
902
+ ctx.depth + 1
903
+ );
904
+ }
905
+ return this.#leaf(itemTag, String(item), ctx.depth + 1);
906
+ });
778
907
  return this.#wrapIndented(key, children, ctx.depth);
779
908
  }
780
909
  renderObject(key, obj, ctx) {
@@ -833,6 +962,7 @@ var ContextEngine = class {
833
962
  #pendingMessages = [];
834
963
  #store;
835
964
  #chatId;
965
+ #userId;
836
966
  #branchName;
837
967
  #branch = null;
838
968
  #chatData = null;
@@ -841,9 +971,13 @@ var ContextEngine = class {
841
971
  if (!options.chatId) {
842
972
  throw new Error("chatId is required");
843
973
  }
974
+ if (!options.userId) {
975
+ throw new Error("userId is required");
976
+ }
844
977
  this.#store = options.store;
845
978
  this.#chatId = options.chatId;
846
- this.#branchName = options.branch ?? "main";
979
+ this.#userId = options.userId;
980
+ this.#branchName = "main";
847
981
  }
848
982
  /**
849
983
  * Initialize the chat and branch if they don't exist.
@@ -852,24 +986,11 @@ var ContextEngine = class {
852
986
  if (this.#initialized) {
853
987
  return;
854
988
  }
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
- }
989
+ this.#chatData = await this.#store.upsertChat({
990
+ id: this.#chatId,
991
+ userId: this.#userId
992
+ });
993
+ this.#branch = await this.#store.getActiveBranch(this.#chatId);
873
994
  this.#initialized = true;
874
995
  }
875
996
  /**
@@ -929,6 +1050,7 @@ var ContextEngine = class {
929
1050
  }
930
1051
  return {
931
1052
  id: this.#chatData.id,
1053
+ userId: this.#chatData.userId,
932
1054
  createdAt: this.#chatData.createdAt,
933
1055
  updatedAt: this.#chatData.updatedAt,
934
1056
  title: this.#chatData.title,
@@ -971,7 +1093,7 @@ var ContextEngine = class {
971
1093
  *
972
1094
  * @example
973
1095
  * ```ts
974
- * const context = new ContextEngine({ store, chatId: 'chat-1' })
1096
+ * const context = new ContextEngine({ store, chatId: 'chat-1', userId: 'user-1' })
975
1097
  * .set(role('You are helpful'), user('Hello'));
976
1098
  *
977
1099
  * const { systemPrompt, messages } = await context.resolve();
@@ -1015,6 +1137,12 @@ var ContextEngine = class {
1015
1137
  if (this.#pendingMessages.length === 0) {
1016
1138
  return;
1017
1139
  }
1140
+ for (let i = 0; i < this.#pendingMessages.length; i++) {
1141
+ const fragment2 = this.#pendingMessages[i];
1142
+ if (isLazyFragment(fragment2)) {
1143
+ this.#pendingMessages[i] = await this.#resolveLazyFragment(fragment2);
1144
+ }
1145
+ }
1018
1146
  let parentId = this.#branch.headMessageId;
1019
1147
  const now = Date.now();
1020
1148
  for (const fragment2 of this.#pendingMessages) {
@@ -1034,6 +1162,39 @@ var ContextEngine = class {
1034
1162
  this.#branch.headMessageId = parentId;
1035
1163
  this.#pendingMessages = [];
1036
1164
  }
1165
+ /**
1166
+ * Resolve a lazy fragment by finding the appropriate ID.
1167
+ */
1168
+ async #resolveLazyFragment(fragment2) {
1169
+ const lazy = fragment2[LAZY_ID];
1170
+ if (lazy.type === "last-assistant") {
1171
+ const lastId = await this.#getLastAssistantId();
1172
+ return assistantText(lazy.content, { id: lastId ?? crypto.randomUUID() });
1173
+ }
1174
+ throw new Error(`Unknown lazy fragment type: ${lazy.type}`);
1175
+ }
1176
+ /**
1177
+ * Find the most recent assistant message ID (pending or persisted).
1178
+ */
1179
+ async #getLastAssistantId() {
1180
+ for (let i = this.#pendingMessages.length - 1; i >= 0; i--) {
1181
+ const msg = this.#pendingMessages[i];
1182
+ if (msg.name === "assistant" && !isLazyFragment(msg)) {
1183
+ return msg.id;
1184
+ }
1185
+ }
1186
+ if (this.#branch?.headMessageId) {
1187
+ const chain = await this.#store.getMessageChain(
1188
+ this.#branch.headMessageId
1189
+ );
1190
+ for (let i = chain.length - 1; i >= 0; i--) {
1191
+ if (chain[i].name === "assistant") {
1192
+ return chain[i].id;
1193
+ }
1194
+ }
1195
+ }
1196
+ return void 0;
1197
+ }
1037
1198
  /**
1038
1199
  * Estimate token count and cost for the full context.
1039
1200
  *
@@ -1313,9 +1474,38 @@ var ContextEngine = class {
1313
1474
  consolidate() {
1314
1475
  return void 0;
1315
1476
  }
1477
+ /**
1478
+ * Extract skill path mappings from available_skills fragments.
1479
+ * Returns array of { host, sandbox } for mounting in sandbox filesystem.
1480
+ *
1481
+ * Reads the original `paths` configuration stored in fragment metadata
1482
+ * by the skills() fragment helper.
1483
+ *
1484
+ * @example
1485
+ * ```ts
1486
+ * const context = new ContextEngine({ store, chatId, userId })
1487
+ * .set(skills({ paths: [{ host: './skills', sandbox: '/skills' }] }));
1488
+ *
1489
+ * const mounts = context.getSkillMounts();
1490
+ * // [{ host: './skills', sandbox: '/skills' }]
1491
+ * ```
1492
+ */
1493
+ getSkillMounts() {
1494
+ const mounts = [];
1495
+ for (const fragment2 of this.#fragments) {
1496
+ if (fragment2.name === "available_skills" && fragment2.metadata && Array.isArray(fragment2.metadata.paths)) {
1497
+ for (const mapping of fragment2.metadata.paths) {
1498
+ if (typeof mapping === "object" && mapping !== null && typeof mapping.host === "string" && typeof mapping.sandbox === "string") {
1499
+ mounts.push({ host: mapping.host, sandbox: mapping.sandbox });
1500
+ }
1501
+ }
1502
+ }
1503
+ }
1504
+ return mounts;
1505
+ }
1316
1506
  /**
1317
1507
  * Inspect the full context state for debugging.
1318
- * Returns a comprehensive JSON-serializable object with all context information.
1508
+ * Returns a JSON-serializable object with context information.
1319
1509
  *
1320
1510
  * @param options - Inspection options (modelId and renderer required)
1321
1511
  * @returns Complete inspection data including estimates, rendered output, fragments, and graph
@@ -1378,6 +1568,26 @@ function guardrail(input) {
1378
1568
  }
1379
1569
  };
1380
1570
  }
1571
+ function explain(input) {
1572
+ return {
1573
+ name: "explain",
1574
+ data: {
1575
+ concept: input.concept,
1576
+ explanation: input.explanation,
1577
+ ...input.therefore && { therefore: input.therefore }
1578
+ }
1579
+ };
1580
+ }
1581
+ function example(input) {
1582
+ return {
1583
+ name: "example",
1584
+ data: {
1585
+ question: input.question,
1586
+ answer: input.answer,
1587
+ ...input.note && { note: input.note }
1588
+ }
1589
+ };
1590
+ }
1381
1591
  function clarification(input) {
1382
1592
  return {
1383
1593
  name: "clarification",
@@ -1399,6 +1609,15 @@ function workflow(input) {
1399
1609
  }
1400
1610
  };
1401
1611
  }
1612
+ function quirk(input) {
1613
+ return {
1614
+ name: "quirk",
1615
+ data: {
1616
+ issue: input.issue,
1617
+ workaround: input.workaround
1618
+ }
1619
+ };
1620
+ }
1402
1621
  function styleGuide(input) {
1403
1622
  return {
1404
1623
  name: "styleGuide",
@@ -1409,12 +1628,40 @@ function styleGuide(input) {
1409
1628
  }
1410
1629
  };
1411
1630
  }
1631
+ function role(content) {
1632
+ return {
1633
+ name: "role",
1634
+ data: content
1635
+ };
1636
+ }
1637
+ function principle(input) {
1638
+ return {
1639
+ name: "principle",
1640
+ data: {
1641
+ title: input.title,
1642
+ description: input.description,
1643
+ ...input.policies?.length && { policies: input.policies }
1644
+ }
1645
+ };
1646
+ }
1647
+ function policy(input) {
1648
+ return {
1649
+ name: "policy",
1650
+ data: {
1651
+ rule: input.rule,
1652
+ ...input.before && { before: input.before },
1653
+ ...input.reason && { reason: input.reason },
1654
+ ...input.policies?.length && { policies: input.policies }
1655
+ }
1656
+ };
1657
+ }
1412
1658
  function persona(input) {
1413
1659
  return {
1414
1660
  name: "persona",
1415
1661
  data: {
1416
1662
  name: input.name,
1417
- role: input.role,
1663
+ ...input.role && { role: input.role },
1664
+ ...input.objective && { objective: input.objective },
1418
1665
  ...input.tone && { tone: input.tone }
1419
1666
  }
1420
1667
  };
@@ -1422,6 +1669,9 @@ function persona(input) {
1422
1669
  function pass(part) {
1423
1670
  return { type: "pass", part };
1424
1671
  }
1672
+ function fail(feedback) {
1673
+ return { type: "fail", feedback };
1674
+ }
1425
1675
  function runGuardrailChain(part, guardrails, context) {
1426
1676
  let currentPart = part;
1427
1677
  for (const guardrail2 of guardrails) {
@@ -1433,11 +1683,77 @@ function runGuardrailChain(part, guardrails, context) {
1433
1683
  }
1434
1684
  return pass(currentPart);
1435
1685
  }
1686
+ var errorRecoveryGuardrail = {
1687
+ id: "error-recovery",
1688
+ name: "API Error Recovery",
1689
+ handle: (part, context) => {
1690
+ if (part.type !== "error") {
1691
+ return pass(part);
1692
+ }
1693
+ const errorText = part.errorText || "";
1694
+ const prefix = chalk.bold.magenta("[ErrorRecovery]");
1695
+ console.log(
1696
+ `${prefix} ${chalk.red("Caught error:")} ${chalk.dim(errorText.slice(0, 150))}`
1697
+ );
1698
+ const logAndFail = (pattern, feedback) => {
1699
+ console.log(
1700
+ `${prefix} ${chalk.yellow("Pattern:")} ${chalk.cyan(pattern)}`
1701
+ );
1702
+ console.log(
1703
+ `${prefix} ${chalk.green("Feedback:")} ${chalk.dim(feedback.slice(0, 80))}...`
1704
+ );
1705
+ return fail(feedback);
1706
+ };
1707
+ if (errorText.includes("Tool choice is none")) {
1708
+ if (context.availableTools.length > 0) {
1709
+ return logAndFail(
1710
+ "Tool choice is none",
1711
+ `I tried to call a tool that doesn't exist. Available tools: ${context.availableTools.join(", ")}. Let me use one of these instead.`
1712
+ );
1713
+ }
1714
+ return logAndFail(
1715
+ "Tool choice is none (no tools)",
1716
+ "I tried to call a tool, but no tools are available. Let me respond with plain text instead."
1717
+ );
1718
+ }
1719
+ if (errorText.includes("not in request.tools") || errorText.includes("tool") && errorText.includes("not found")) {
1720
+ const toolMatch = errorText.match(/tool '([^']+)'/);
1721
+ const toolName = toolMatch ? toolMatch[1] : "unknown";
1722
+ if (context.availableTools.length > 0) {
1723
+ return logAndFail(
1724
+ `Unregistered tool: ${toolName}`,
1725
+ `I tried to call "${toolName}" but it doesn't exist. Available tools: ${context.availableTools.join(", ")}. Let me use one of these instead.`
1726
+ );
1727
+ }
1728
+ return logAndFail(
1729
+ `Unregistered tool: ${toolName} (no tools)`,
1730
+ `I tried to call "${toolName}" but no tools are available. Let me respond with plain text instead.`
1731
+ );
1732
+ }
1733
+ if (errorText.includes("Failed to parse tool call arguments") || errorText.includes("parse tool call") || errorText.includes("invalid JSON")) {
1734
+ return logAndFail(
1735
+ "Malformed JSON arguments",
1736
+ "I generated malformed JSON for the tool arguments. Let me format my tool call properly with valid JSON."
1737
+ );
1738
+ }
1739
+ if (errorText.includes("Parsing failed")) {
1740
+ return logAndFail(
1741
+ "Parsing failed",
1742
+ "My response format was invalid. Let me try again with a properly formatted response."
1743
+ );
1744
+ }
1745
+ return logAndFail(
1746
+ "Unknown error",
1747
+ `An error occurred: ${errorText}. Let me try a different approach.`
1748
+ );
1749
+ }
1750
+ };
1436
1751
  var STORE_DDL = `
1437
1752
  -- Chats table
1438
1753
  -- createdAt/updatedAt: DEFAULT for insert, inline SET for updates
1439
1754
  CREATE TABLE IF NOT EXISTS chats (
1440
1755
  id TEXT PRIMARY KEY,
1756
+ userId TEXT NOT NULL,
1441
1757
  title TEXT,
1442
1758
  metadata TEXT,
1443
1759
  createdAt INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),
@@ -1445,6 +1761,7 @@ CREATE TABLE IF NOT EXISTS chats (
1445
1761
  );
1446
1762
 
1447
1763
  CREATE INDEX IF NOT EXISTS idx_chats_updatedAt ON chats(updatedAt);
1764
+ CREATE INDEX IF NOT EXISTS idx_chats_userId ON chats(userId);
1448
1765
 
1449
1766
  -- Messages table (nodes in the DAG)
1450
1767
  CREATE TABLE IF NOT EXISTS messages (
@@ -1510,37 +1827,67 @@ var SqliteContextStore = class extends ContextStore {
1510
1827
  this.#db.exec("PRAGMA foreign_keys = ON");
1511
1828
  this.#db.exec(STORE_DDL);
1512
1829
  }
1830
+ /**
1831
+ * Execute a function within a transaction.
1832
+ * Automatically commits on success or rolls back on error.
1833
+ */
1834
+ #useTransaction(fn) {
1835
+ this.#db.exec("BEGIN TRANSACTION");
1836
+ try {
1837
+ const result = fn();
1838
+ this.#db.exec("COMMIT");
1839
+ return result;
1840
+ } catch (error) {
1841
+ this.#db.exec("ROLLBACK");
1842
+ throw error;
1843
+ }
1844
+ }
1513
1845
  // ==========================================================================
1514
1846
  // Chat Operations
1515
1847
  // ==========================================================================
1516
1848
  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
- );
1849
+ this.#useTransaction(() => {
1850
+ this.#db.prepare(
1851
+ `INSERT INTO chats (id, userId, title, metadata)
1852
+ VALUES (?, ?, ?, ?)`
1853
+ ).run(
1854
+ chat.id,
1855
+ chat.userId,
1856
+ chat.title ?? null,
1857
+ chat.metadata ? JSON.stringify(chat.metadata) : null
1858
+ );
1859
+ this.#db.prepare(
1860
+ `INSERT INTO branches (id, chatId, name, headMessageId, isActive, createdAt)
1861
+ VALUES (?, ?, 'main', NULL, 1, ?)`
1862
+ ).run(crypto.randomUUID(), chat.id, Date.now());
1863
+ });
1525
1864
  }
1526
1865
  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
- };
1866
+ return this.#useTransaction(() => {
1867
+ const row = this.#db.prepare(
1868
+ `INSERT INTO chats (id, userId, title, metadata)
1869
+ VALUES (?, ?, ?, ?)
1870
+ ON CONFLICT(id) DO UPDATE SET id = excluded.id
1871
+ RETURNING *`
1872
+ ).get(
1873
+ chat.id,
1874
+ chat.userId,
1875
+ chat.title ?? null,
1876
+ chat.metadata ? JSON.stringify(chat.metadata) : null
1877
+ );
1878
+ this.#db.prepare(
1879
+ `INSERT OR IGNORE INTO branches (id, chatId, name, headMessageId, isActive, createdAt)
1880
+ VALUES (?, ?, 'main', NULL, 1, ?)`
1881
+ ).run(crypto.randomUUID(), chat.id, Date.now());
1882
+ return {
1883
+ id: row.id,
1884
+ userId: row.userId,
1885
+ title: row.title ?? void 0,
1886
+ metadata: row.metadata ? JSON.parse(row.metadata) : void 0,
1887
+ createdAt: row.createdAt,
1888
+ updatedAt: row.updatedAt
1889
+ };
1890
+ });
1544
1891
  }
1545
1892
  async getChat(chatId) {
1546
1893
  const row = this.#db.prepare("SELECT * FROM chats WHERE id = ?").get(chatId);
@@ -1549,6 +1896,7 @@ var SqliteContextStore = class extends ContextStore {
1549
1896
  }
1550
1897
  return {
1551
1898
  id: row.id,
1899
+ userId: row.userId,
1552
1900
  title: row.title ?? void 0,
1553
1901
  metadata: row.metadata ? JSON.parse(row.metadata) : void 0,
1554
1902
  createdAt: row.createdAt,
@@ -1572,16 +1920,33 @@ var SqliteContextStore = class extends ContextStore {
1572
1920
  ).get(...params);
1573
1921
  return {
1574
1922
  id: row.id,
1923
+ userId: row.userId,
1575
1924
  title: row.title ?? void 0,
1576
1925
  metadata: row.metadata ? JSON.parse(row.metadata) : void 0,
1577
1926
  createdAt: row.createdAt,
1578
1927
  updatedAt: row.updatedAt
1579
1928
  };
1580
1929
  }
1581
- async listChats() {
1930
+ async listChats(options) {
1931
+ const params = [];
1932
+ let whereClause = "";
1933
+ let limitClause = "";
1934
+ if (options?.userId) {
1935
+ whereClause = "WHERE c.userId = ?";
1936
+ params.push(options.userId);
1937
+ }
1938
+ if (options?.limit !== void 0) {
1939
+ limitClause = " LIMIT ?";
1940
+ params.push(options.limit);
1941
+ if (options.offset !== void 0) {
1942
+ limitClause += " OFFSET ?";
1943
+ params.push(options.offset);
1944
+ }
1945
+ }
1582
1946
  const rows = this.#db.prepare(
1583
1947
  `SELECT
1584
1948
  c.id,
1949
+ c.userId,
1585
1950
  c.title,
1586
1951
  c.createdAt,
1587
1952
  c.updatedAt,
@@ -1590,11 +1955,13 @@ var SqliteContextStore = class extends ContextStore {
1590
1955
  FROM chats c
1591
1956
  LEFT JOIN messages m ON m.chatId = c.id
1592
1957
  LEFT JOIN branches b ON b.chatId = c.id
1958
+ ${whereClause}
1593
1959
  GROUP BY c.id
1594
- ORDER BY c.updatedAt DESC`
1595
- ).all();
1960
+ ORDER BY c.updatedAt DESC${limitClause}`
1961
+ ).all(...params);
1596
1962
  return rows.map((row) => ({
1597
1963
  id: row.id,
1964
+ userId: row.userId,
1598
1965
  title: row.title ?? void 0,
1599
1966
  messageCount: row.messageCount,
1600
1967
  branchCount: row.branchCount,
@@ -1602,22 +1969,42 @@ var SqliteContextStore = class extends ContextStore {
1602
1969
  updatedAt: row.updatedAt
1603
1970
  }));
1604
1971
  }
1972
+ async deleteChat(chatId, options) {
1973
+ return this.#useTransaction(() => {
1974
+ const messageIds = this.#db.prepare("SELECT id FROM messages WHERE chatId = ?").all(chatId);
1975
+ let sql = "DELETE FROM chats WHERE id = ?";
1976
+ const params = [chatId];
1977
+ if (options?.userId !== void 0) {
1978
+ sql += " AND userId = ?";
1979
+ params.push(options.userId);
1980
+ }
1981
+ const result = this.#db.prepare(sql).run(...params);
1982
+ if (result.changes > 0 && messageIds.length > 0) {
1983
+ const placeholders = messageIds.map(() => "?").join(", ");
1984
+ this.#db.prepare(
1985
+ `DELETE FROM messages_fts WHERE messageId IN (${placeholders})`
1986
+ ).run(...messageIds.map((m) => m.id));
1987
+ }
1988
+ return result.changes > 0;
1989
+ });
1990
+ }
1605
1991
  // ==========================================================================
1606
1992
  // Message Operations (Graph Nodes)
1607
1993
  // ==========================================================================
1608
1994
  async addMessage(message2) {
1995
+ const existingParent = message2.parentId === message2.id ? this.#db.prepare("SELECT parentId FROM messages WHERE id = ?").get(message2.id) : void 0;
1996
+ const parentId = message2.parentId === message2.id ? existingParent?.parentId ?? null : message2.parentId;
1609
1997
  this.#db.prepare(
1610
1998
  `INSERT INTO messages (id, chatId, parentId, name, type, data, createdAt)
1611
1999
  VALUES (?, ?, ?, ?, ?, ?, ?)
1612
2000
  ON CONFLICT(id) DO UPDATE SET
1613
- parentId = excluded.parentId,
1614
2001
  name = excluded.name,
1615
2002
  type = excluded.type,
1616
2003
  data = excluded.data`
1617
2004
  ).run(
1618
2005
  message2.id,
1619
2006
  message2.chatId,
1620
- message2.parentId,
2007
+ parentId,
1621
2008
  message2.name,
1622
2009
  message2.type ?? null,
1623
2010
  JSON.stringify(message2.data),
@@ -1672,6 +2059,17 @@ var SqliteContextStore = class extends ContextStore {
1672
2059
  ).get(messageId);
1673
2060
  return row.hasChildren === 1;
1674
2061
  }
2062
+ async getMessages(chatId) {
2063
+ const chat = await this.getChat(chatId);
2064
+ if (!chat) {
2065
+ throw new Error(`Chat "${chatId}" not found`);
2066
+ }
2067
+ const activeBranch = await this.getActiveBranch(chatId);
2068
+ if (!activeBranch?.headMessageId) {
2069
+ return [];
2070
+ }
2071
+ return this.getMessageChain(activeBranch.headMessageId);
2072
+ }
1675
2073
  // ==========================================================================
1676
2074
  // Branch Operations
1677
2075
  // ==========================================================================
@@ -1933,16 +2331,17 @@ var Agent = class _Agent {
1933
2331
  providerOptions: this.#options.providerOptions,
1934
2332
  model: this.#options.model,
1935
2333
  system: systemPrompt,
1936
- messages: convertToModelMessages(messages),
2334
+ messages: await convertToModelMessages(messages),
1937
2335
  stopWhen: stepCountIs(25),
1938
2336
  tools: this.#options.tools,
1939
2337
  experimental_context: contextVariables,
2338
+ experimental_repairToolCall: repairToolCall,
1940
2339
  toolChoice: this.#options.toolChoice,
1941
2340
  onStepFinish: (step) => {
1942
2341
  const toolCall = step.toolCalls.at(-1);
1943
2342
  if (toolCall) {
1944
2343
  console.log(
1945
- `Debug: ${chalk.yellow("ToolCalled")}: ${toolCall.toolName}(${JSON.stringify(toolCall.input)})`
2344
+ `Debug: ${chalk2.yellow("ToolCalled")}: ${toolCall.toolName}(${JSON.stringify(toolCall.input)})`
1946
2345
  );
1947
2346
  }
1948
2347
  }
@@ -1991,8 +2390,9 @@ var Agent = class _Agent {
1991
2390
  providerOptions: this.#options.providerOptions,
1992
2391
  model: this.#options.model,
1993
2392
  system: systemPrompt,
1994
- messages: convertToModelMessages(messages),
1995
- stopWhen: stepCountIs(25),
2393
+ messages: await convertToModelMessages(messages),
2394
+ experimental_repairToolCall: repairToolCall,
2395
+ stopWhen: stepCountIs(50),
1996
2396
  experimental_transform: config?.transform ?? smoothStream(),
1997
2397
  tools: this.#options.tools,
1998
2398
  experimental_context: contextVariables,
@@ -2001,7 +2401,7 @@ var Agent = class _Agent {
2001
2401
  const toolCall = step.toolCalls.at(-1);
2002
2402
  if (toolCall) {
2003
2403
  console.log(
2004
- `Debug: (${runId}) ${chalk.bold.yellow("ToolCalled")}: ${toolCall.toolName}(${JSON.stringify(toolCall.input)})`
2404
+ `Debug: (${runId}) ${chalk2.bold.yellow("ToolCalled")}: ${toolCall.toolName}(${JSON.stringify(toolCall.input)})`
2005
2405
  );
2006
2406
  }
2007
2407
  }
@@ -2048,7 +2448,7 @@ var Agent = class _Agent {
2048
2448
  guardrailFailed = true;
2049
2449
  failureFeedback = checkResult.feedback;
2050
2450
  console.log(
2051
- chalk.yellow(
2451
+ chalk2.yellow(
2052
2452
  `[${this.#options.name}] Guardrail triggered (attempt ${attempt}/${maxRetries}): ${failureFeedback.slice(0, 50)}...`
2053
2453
  )
2054
2454
  );
@@ -2065,20 +2465,16 @@ var Agent = class _Agent {
2065
2465
  }
2066
2466
  if (attempt >= maxRetries) {
2067
2467
  console.error(
2068
- chalk.red(
2468
+ chalk2.red(
2069
2469
  `[${this.#options.name}] Guardrail retry limit (${maxRetries}) exceeded.`
2070
2470
  )
2071
2471
  );
2072
2472
  writer.write({ type: "finish" });
2073
2473
  return;
2074
2474
  }
2075
- writer.write({
2076
- type: "text-delta",
2077
- id: generateId2(),
2078
- delta: ` ${failureFeedback}`
2079
- });
2475
+ writeText(writer, failureFeedback);
2080
2476
  const selfCorrectionText = accumulatedText + " " + failureFeedback;
2081
- context.set(assistantText(selfCorrectionText));
2477
+ context.set(lastAssistantMessage(selfCorrectionText));
2082
2478
  await context.save();
2083
2479
  currentResult = await this.#createRawStream(
2084
2480
  contextVariables,
@@ -2108,14 +2504,10 @@ function structuredOutput(options) {
2108
2504
  return {
2109
2505
  async generate(contextVariables, config) {
2110
2506
  if (!options.context) {
2111
- throw new Error(
2112
- `structuredOutput "${options.name}" is missing a context.`
2113
- );
2507
+ throw new Error(`structuredOutput is missing a context.`);
2114
2508
  }
2115
2509
  if (!options.model) {
2116
- throw new Error(
2117
- `structuredOutput "${options.name}" is missing a model.`
2118
- );
2510
+ throw new Error(`structuredOutput is missing a model.`);
2119
2511
  }
2120
2512
  const { messages, systemPrompt } = await options.context.resolve({
2121
2513
  renderer: new XmlRenderer()
@@ -2125,23 +2517,21 @@ function structuredOutput(options) {
2125
2517
  providerOptions: options.providerOptions,
2126
2518
  model: options.model,
2127
2519
  system: systemPrompt,
2128
- messages: convertToModelMessages(messages),
2520
+ messages: await convertToModelMessages(messages),
2129
2521
  stopWhen: stepCountIs(25),
2522
+ experimental_repairToolCall: repairToolCall,
2130
2523
  experimental_context: contextVariables,
2131
- experimental_output: Output.object({ schema: options.schema })
2524
+ output: Output.object({ schema: options.schema }),
2525
+ tools: options.tools
2132
2526
  });
2133
- return result.experimental_output;
2527
+ return result.output;
2134
2528
  },
2135
2529
  async stream(contextVariables, config) {
2136
2530
  if (!options.context) {
2137
- throw new Error(
2138
- `structuredOutput "${options.name}" is missing a context.`
2139
- );
2531
+ throw new Error(`structuredOutput is missing a context.`);
2140
2532
  }
2141
2533
  if (!options.model) {
2142
- throw new Error(
2143
- `structuredOutput "${options.name}" is missing a model.`
2144
- );
2534
+ throw new Error(`structuredOutput is missing a model.`);
2145
2535
  }
2146
2536
  const { messages, systemPrompt } = await options.context.resolve({
2147
2537
  renderer: new XmlRenderer()
@@ -2151,37 +2541,90 @@ function structuredOutput(options) {
2151
2541
  providerOptions: options.providerOptions,
2152
2542
  model: options.model,
2153
2543
  system: systemPrompt,
2154
- messages: convertToModelMessages(messages),
2155
- stopWhen: stepCountIs(25),
2544
+ experimental_repairToolCall: repairToolCall,
2545
+ messages: await convertToModelMessages(messages),
2546
+ stopWhen: stepCountIs(50),
2156
2547
  experimental_transform: config?.transform ?? smoothStream(),
2157
2548
  experimental_context: contextVariables,
2158
- experimental_output: Output.object({ schema: options.schema })
2549
+ output: Output.object({ schema: options.schema }),
2550
+ tools: options.tools
2159
2551
  });
2160
2552
  }
2161
2553
  };
2162
2554
  }
2555
+ var repairToolCall = async ({
2556
+ toolCall,
2557
+ tools: tools3,
2558
+ inputSchema,
2559
+ error
2560
+ }) => {
2561
+ console.log(
2562
+ `Debug: ${chalk2.yellow("RepairingToolCall")}: ${toolCall.toolName}`,
2563
+ error.name
2564
+ );
2565
+ if (NoSuchToolError.isInstance(error)) {
2566
+ return null;
2567
+ }
2568
+ const tool3 = tools3[toolCall.toolName];
2569
+ const { output } = await generateText({
2570
+ model: groq("openai/gpt-oss-20b"),
2571
+ output: Output.object({ schema: tool3.inputSchema }),
2572
+ prompt: [
2573
+ `The model tried to call the tool "${toolCall.toolName}" with the following inputs:`,
2574
+ JSON.stringify(toolCall.input),
2575
+ `The tool accepts the following schema:`,
2576
+ JSON.stringify(inputSchema(toolCall)),
2577
+ "Please fix the inputs."
2578
+ ].join("\n")
2579
+ });
2580
+ return { ...toolCall, input: JSON.stringify(output) };
2581
+ };
2582
+ function writeText(writer, text) {
2583
+ const feedbackPartId = generateId2();
2584
+ writer.write({
2585
+ id: feedbackPartId,
2586
+ type: "text-start"
2587
+ });
2588
+ writer.write({
2589
+ id: feedbackPartId,
2590
+ type: "text-delta",
2591
+ delta: ` ${text}`
2592
+ });
2593
+ writer.write({
2594
+ id: feedbackPartId,
2595
+ type: "text-end"
2596
+ });
2597
+ }
2163
2598
 
2164
2599
  // packages/text2sql/src/lib/agents/explainer.agent.ts
2165
- import { groq } from "@ai-sdk/groq";
2166
- import dedent from "dedent";
2600
+ import { groq as groq2 } from "@ai-sdk/groq";
2167
2601
  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
- })
2602
+ var outputSchema = z.object({
2603
+ explanation: z.string().describe("The explanation of the SQL query.")
2184
2604
  });
2605
+ async function explainSql(sql) {
2606
+ const context = new ContextEngine({
2607
+ store: new InMemoryContextStore(),
2608
+ chatId: `explainer-${crypto.randomUUID()}`,
2609
+ userId: "system"
2610
+ });
2611
+ context.set(
2612
+ persona({
2613
+ name: "explainer",
2614
+ role: "You are an expert SQL tutor.",
2615
+ objective: "Explain SQL queries in plain English that non-technical users understand"
2616
+ }),
2617
+ fragment("sql", sql),
2618
+ fragment("task", "Focus on the intent and logic, not the syntax."),
2619
+ user("Explain this SQL query in plain English to a non-technical user.")
2620
+ );
2621
+ const explainerOutput = structuredOutput({
2622
+ model: groq2("openai/gpt-oss-20b"),
2623
+ context,
2624
+ schema: outputSchema
2625
+ });
2626
+ return explainerOutput.generate();
2627
+ }
2185
2628
 
2186
2629
  // packages/text2sql/src/lib/agents/developer.agent.ts
2187
2630
  var tools = {
@@ -2227,7 +2670,7 @@ var tools = {
2227
2670
  * Get plain-English explanation of a SQL query.
2228
2671
  */
2229
2672
  explain_sql: tool({
2230
- description: dedent2`
2673
+ description: dedent`
2231
2674
  Get a plain-English explanation of a SQL query.
2232
2675
  Use this to help the user understand what a query does.
2233
2676
 
@@ -2237,17 +2680,15 @@ var tools = {
2237
2680
  sql: z2.string().min(1).describe("The SQL query to explain")
2238
2681
  }),
2239
2682
  execute: async ({ sql }) => {
2240
- const { experimental_output } = await generate(explainerAgent, [], {
2241
- sql
2242
- });
2243
- return { explanation: experimental_output.explanation };
2683
+ return explainSql(sql);
2244
2684
  }
2245
2685
  })
2246
2686
  };
2247
2687
  var fragments = [
2248
2688
  persona({
2249
2689
  name: "developer_assistant",
2250
- role: "You are an expert SQL developer assistant helping power users build and refine queries."
2690
+ role: "You are an expert SQL developer assistant helping power users build and refine queries.",
2691
+ objective: "Help power users build and refine SQL queries with precision and clarity"
2251
2692
  }),
2252
2693
  hint("Be transparent: show the SQL you generate before explaining it"),
2253
2694
  hint("Be precise: provide exact column names and table references"),
@@ -2257,69 +2698,402 @@ var fragments = [
2257
2698
  ];
2258
2699
  var developer_agent_default = { tools, fragments };
2259
2700
 
2260
- // packages/text2sql/src/lib/agents/suggestions.agents.ts
2261
- import { groq as groq2 } from "@ai-sdk/groq";
2262
- import dedent3 from "dedent";
2263
- import z3 from "zod";
2264
- import { agent as agent3, thirdPersonPrompt } from "@deepagents/agent";
2265
- var suggestionsAgent = agent3({
2266
- name: "text2sql-suggestions",
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.")
2274
- })
2275
- ).min(1).max(5).describe("A set of up to two advanced question + SQL pairs.")
2276
- }),
2277
- prompt: (state) => {
2278
- return dedent3`
2279
- ${thirdPersonPrompt()}
2280
-
2281
- <identity>
2282
- You are a senior analytics strategist who proposes ambitious business questions
2283
- and drafts the SQL needed to answer them. You specialize in identifying ideas
2284
- that combine multiple tables, apply segmentation or time analysis, and surface
2285
- metrics that drive executive decisions.
2286
- </identity>
2287
-
2288
-
2289
- <instructions>
2290
- - Recommend one or two UNIQUE questions that go beyond simple counts or listings.
2291
- - Favor questions that require joins, aggregates, time comparisons, cohort analysis,
2292
- or window functions.
2293
- - For each question, explain the business reason stakeholders care about it.
2294
- - Provide the complete SQL query that could answer the question in the given schema.
2295
- - Keep result sets scoped with LIMIT clauses (max 50 rows) when returning raw rows.
2296
- - Ensure table/column names match the provided schema exactly.
2297
- - Use columns marked [LowCardinality: ...] to identify meaningful categorical filters or segmentations.
2298
- - Leverage table [rows / size] hints to determine whether to aggregate (large tables) or inspect detailed data (tiny tables).
2299
- - Reference PK/Indexed annotations and the Indexes list to recommend queries that use efficient join/filter paths.
2300
- - Column annotations may expose ranges/null percentages—use them to suggest realistic thresholds or quality checks.
2301
- - Consult <relationship_examples> to anchor your recommendations in the actual join paths between tables.
2302
- - Output only information grounded in the schema/context provided.
2303
- </instructions>
2701
+ // packages/text2sql/src/lib/agents/result-tools.ts
2702
+ import { createBashTool as createBashTool2 } from "bash-tool";
2703
+ import chalk3 from "chalk";
2704
+ import {
2705
+ Bash,
2706
+ InMemoryFs,
2707
+ MountableFs,
2708
+ OverlayFs,
2709
+ ReadWriteFs,
2710
+ defineCommand as defineCommand2
2711
+ } from "just-bash";
2712
+ import * as fs from "node:fs/promises";
2713
+ import * as path from "node:path";
2714
+ import { v7 } from "uuid";
2715
+ function createCommand(name, subcommands) {
2716
+ const usageLines = Object.entries(subcommands).map(([, def]) => ` ${name} ${def.usage.padEnd(30)} ${def.description}`).join("\n");
2717
+ return defineCommand2(name, async (args, ctx) => {
2718
+ const subcommand = args[0];
2719
+ const restArgs = args.slice(1);
2720
+ if (subcommand && subcommand in subcommands) {
2721
+ return subcommands[subcommand].handler(restArgs, ctx);
2722
+ }
2723
+ return {
2724
+ stdout: "",
2725
+ stderr: `${name}: ${subcommand ? `unknown subcommand '${subcommand}'` : "missing subcommand"}
2304
2726
 
2305
- <response-format>
2306
- Return valid JSON that satisfies the defined output schema.
2307
- </response-format>
2727
+ Usage:
2728
+ ${usageLines}`,
2729
+ exitCode: 1
2730
+ };
2731
+ });
2732
+ }
2733
+ function validateReadOnly(query) {
2734
+ const upper = query.toUpperCase().trim();
2735
+ if (!upper.startsWith("SELECT") && !upper.startsWith("WITH")) {
2736
+ return { valid: false, error: "only SELECT or WITH queries allowed" };
2737
+ }
2738
+ return { valid: true };
2739
+ }
2740
+ function createSqlCommand(adapter) {
2741
+ return createCommand("sql", {
2742
+ run: {
2743
+ usage: 'run "SELECT ..."',
2744
+ description: "Execute query and store results",
2745
+ handler: async (args, ctx) => {
2746
+ const query = args.join(" ").trim();
2747
+ if (!query) {
2748
+ return {
2749
+ stdout: "",
2750
+ stderr: "sql run: no query provided",
2751
+ exitCode: 1
2752
+ };
2753
+ }
2754
+ const validation = validateReadOnly(query);
2755
+ if (!validation.valid) {
2756
+ return {
2757
+ stdout: "",
2758
+ stderr: `sql run: ${validation.error}`,
2759
+ exitCode: 1
2760
+ };
2761
+ }
2762
+ try {
2763
+ const rows = await adapter.execute(query);
2764
+ const rowsArray = Array.isArray(rows) ? rows : [];
2765
+ const filePath = `/results/${v7()}.json`;
2766
+ await ctx.fs.writeFile(filePath, JSON.stringify(rowsArray, null, 2));
2767
+ const columns = rowsArray.length > 0 ? Object.keys(rowsArray[0]) : [];
2768
+ return {
2769
+ stdout: [
2770
+ filePath,
2771
+ `columns: ${columns.join(", ") || "(none)"}`,
2772
+ `rows: ${rowsArray.length}`
2773
+ ].join("\n") + "\n",
2774
+ stderr: "",
2775
+ exitCode: 0
2776
+ };
2777
+ } catch (error) {
2778
+ return {
2779
+ stdout: "",
2780
+ stderr: `sql run: ${error instanceof Error ? error.message : String(error)}`,
2781
+ exitCode: 1
2782
+ };
2783
+ }
2784
+ }
2785
+ },
2786
+ validate: {
2787
+ usage: 'validate "SELECT ..."',
2788
+ description: "Validate query syntax",
2789
+ handler: (args) => {
2790
+ const query = args.join(" ").trim();
2791
+ if (!query) {
2792
+ return {
2793
+ stdout: "",
2794
+ stderr: "sql validate: no query provided",
2795
+ exitCode: 1
2796
+ };
2797
+ }
2798
+ const validation = validateReadOnly(query);
2799
+ if (!validation.valid) {
2800
+ return {
2801
+ stdout: "",
2802
+ stderr: `sql validate: ${validation.error}`,
2803
+ exitCode: 1
2804
+ };
2805
+ }
2806
+ return {
2807
+ stdout: "valid\n",
2808
+ stderr: "",
2809
+ exitCode: 0
2810
+ };
2811
+ }
2812
+ }
2813
+ });
2814
+ }
2815
+ async function createResultTools(options) {
2816
+ const { adapter, chatId, messageId, skillMounts = [] } = options;
2817
+ const sqlCommand = createSqlCommand(adapter);
2818
+ const root = process.env.TEXT2SQL_FS_ROOT || process.cwd();
2819
+ const chatDir = path.join(root, "artifacts", chatId);
2820
+ const resultsDir = path.join(chatDir, messageId, "results");
2821
+ await fs.mkdir(resultsDir, { recursive: true });
2822
+ const fsMounts = skillMounts.map(({ host, sandbox: sandbox2 }) => ({
2823
+ mountPoint: sandbox2,
2824
+ filesystem: new OverlayFs({
2825
+ root: host,
2826
+ mountPoint: "/",
2827
+ readOnly: true
2828
+ })
2829
+ }));
2830
+ const filesystem = new MountableFs({
2831
+ base: new InMemoryFs(),
2832
+ mounts: [
2833
+ ...fsMounts,
2834
+ {
2835
+ mountPoint: "/results",
2836
+ filesystem: new ReadWriteFs({ root: resultsDir })
2837
+ },
2838
+ {
2839
+ mountPoint: "/artifacts",
2840
+ filesystem: new ReadWriteFs({ root: chatDir })
2841
+ }
2842
+ ]
2843
+ });
2844
+ const bashInstance = new Bash({
2845
+ customCommands: [sqlCommand],
2846
+ fs: filesystem
2847
+ });
2848
+ const { bash, sandbox } = await createBashTool2({
2849
+ sandbox: bashInstance,
2850
+ destination: "/",
2851
+ onBeforeBashCall: ({ command }) => {
2852
+ console.log(chalk3.cyan(`[onBeforeBashCall]: ${command}`));
2853
+ return { command };
2854
+ },
2855
+ onAfterBashCall: ({ result }) => {
2856
+ if (result.exitCode !== 0) {
2857
+ console.log(chalk3.yellow(`[onAfterBashCall]: ${result.exitCode}`));
2858
+ }
2859
+ return { result };
2860
+ }
2861
+ });
2862
+ return { bash, sandbox };
2863
+ }
2864
+
2865
+ // packages/text2sql/src/lib/agents/sql.agent.ts
2866
+ import { groq as groq3 } from "@ai-sdk/groq";
2867
+ import {
2868
+ APICallError,
2869
+ JSONParseError,
2870
+ NoContentGeneratedError,
2871
+ NoObjectGeneratedError,
2872
+ NoOutputGeneratedError,
2873
+ TypeValidationError,
2874
+ defaultSettingsMiddleware,
2875
+ wrapLanguageModel
2876
+ } from "ai";
2877
+ import { Console } from "node:console";
2878
+ import { createWriteStream } from "node:fs";
2879
+ import pRetry from "p-retry";
2880
+ import z3 from "zod";
2881
+ import "@deepagents/agent";
2882
+ var logger = new Console({
2883
+ stdout: createWriteStream("./sql-agent.log", { flags: "a" }),
2884
+ stderr: createWriteStream("./sql-agent-error.log", { flags: "a" }),
2885
+ inspectOptions: { depth: null }
2886
+ });
2887
+ var RETRY_TEMPERATURES = [0, 0.2, 0.3];
2888
+ function extractSql(output) {
2889
+ const match = output.match(/```sql\n?([\s\S]*?)```/);
2890
+ return match ? match[1].trim() : output.trim();
2891
+ }
2892
+ var marker = Symbol("SQLValidationError");
2893
+ var SQLValidationError = class _SQLValidationError extends Error {
2894
+ [marker];
2895
+ constructor(message2) {
2896
+ super(message2);
2897
+ this.name = "SQLValidationError";
2898
+ this[marker] = true;
2899
+ }
2900
+ static isInstance(error) {
2901
+ return error instanceof _SQLValidationError && error[marker] === true;
2902
+ }
2903
+ };
2904
+ var UnanswerableSQLError = class _UnanswerableSQLError extends Error {
2905
+ constructor(message2) {
2906
+ super(message2);
2907
+ this.name = "UnanswerableSQLError";
2908
+ }
2909
+ static isInstance(error) {
2910
+ return error instanceof _UnanswerableSQLError;
2911
+ }
2912
+ };
2913
+ async function toSql(options) {
2914
+ const { maxRetries = 3 } = options;
2915
+ return withRetry(
2916
+ async (attemptNumber, errors, attempts) => {
2917
+ const context = new ContextEngine({
2918
+ store: new InMemoryContextStore(),
2919
+ chatId: `sql-gen-${crypto.randomUUID()}`,
2920
+ userId: "system"
2921
+ });
2922
+ context.set(
2923
+ persona({
2924
+ name: "Freya",
2925
+ role: "You are an expert SQL query generator. You translate natural language questions into precise, efficient SQL queries based on the provided database schema.",
2926
+ objective: "Translate natural language questions into precise, efficient SQL queries"
2927
+ }),
2928
+ ...options.instructions,
2929
+ ...options.schemaFragments
2930
+ );
2931
+ if (errors.length) {
2932
+ context.set(
2933
+ user(options.input),
2934
+ user(
2935
+ `<validation_error>Your previous SQL query had the following error: ${errors.at(-1)?.message}. Please fix the query.</validation_error>`
2936
+ )
2937
+ );
2938
+ } else {
2939
+ context.set(user(options.input));
2940
+ }
2941
+ const temperature = RETRY_TEMPERATURES[attemptNumber - 1] ?? RETRY_TEMPERATURES[RETRY_TEMPERATURES.length - 1];
2942
+ const baseModel = options.model ?? groq3("openai/gpt-oss-20b");
2943
+ const model = wrapLanguageModel({
2944
+ model: baseModel,
2945
+ middleware: defaultSettingsMiddleware({ settings: { temperature } })
2946
+ });
2947
+ const sqlOutput = structuredOutput({
2948
+ model,
2949
+ context,
2950
+ schema: z3.union([
2951
+ z3.object({
2952
+ sql: z3.string().describe("The SQL query that answers the question"),
2953
+ reasoning: z3.string().optional().describe("The reasoning steps taken to generate the SQL")
2954
+ }),
2955
+ z3.object({
2956
+ error: z3.string().describe(
2957
+ "Error message explaining why the question cannot be answered with the given schema"
2958
+ )
2959
+ })
2960
+ ])
2961
+ });
2962
+ const output = await sqlOutput.generate();
2963
+ if ("error" in output) {
2964
+ throw new UnanswerableSQLError(output.error);
2965
+ }
2966
+ const sql = extractSql(output.sql);
2967
+ const validationError = await options.adapter.validate(sql);
2968
+ if (validationError) {
2969
+ throw new SQLValidationError(validationError);
2970
+ }
2971
+ return {
2972
+ attempts,
2973
+ sql,
2974
+ errors: errors.length ? errors.map(formatErrorMessage) : void 0
2975
+ };
2976
+ },
2977
+ { retries: maxRetries - 1 }
2978
+ );
2979
+ }
2980
+ function formatErrorMessage(error) {
2981
+ if (APICallError.isInstance(error)) {
2982
+ if (error.message.startsWith("Failed to validate JSON")) {
2983
+ return `Schema validation failed: ${error.message}`;
2984
+ }
2985
+ return error.message;
2986
+ }
2987
+ if (SQLValidationError.isInstance(error)) {
2988
+ return `SQL Validation Error: ${error.message}`;
2989
+ }
2990
+ return error.message;
2991
+ }
2992
+ async function withRetry(computation, options = { retries: 3 }) {
2993
+ const errors = [];
2994
+ let attempts = 0;
2995
+ return pRetry(
2996
+ (attemptNumber) => {
2997
+ return computation(attemptNumber, errors, ++attempts);
2998
+ },
2999
+ {
3000
+ retries: options.retries,
3001
+ shouldRetry: (context) => {
3002
+ if (UnanswerableSQLError.isInstance(context.error)) {
3003
+ return false;
3004
+ }
3005
+ if (SQLValidationError.isInstance(context.error)) {
3006
+ return true;
3007
+ }
3008
+ console.log({
3009
+ NoObjectGeneratedError: NoObjectGeneratedError.isInstance(
3010
+ context.error
3011
+ ),
3012
+ NoOutputGeneratedError: NoOutputGeneratedError.isInstance(
3013
+ context.error
3014
+ ),
3015
+ APICallError: APICallError.isInstance(context.error),
3016
+ JSONParseError: JSONParseError.isInstance(context.error),
3017
+ TypeValidationError: TypeValidationError.isInstance(context.error),
3018
+ NoContentGeneratedError: NoContentGeneratedError.isInstance(
3019
+ context.error
3020
+ )
3021
+ });
3022
+ 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);
3023
+ },
3024
+ onFailedAttempt(context) {
3025
+ logger.error(`toSQL`, context.error);
3026
+ console.log(
3027
+ `Attempt ${context.attemptNumber} failed. There are ${context.retriesLeft} retries left.`
3028
+ );
3029
+ errors.push(context.error);
3030
+ }
3031
+ }
3032
+ );
3033
+ }
3034
+
3035
+ // packages/text2sql/src/lib/agents/suggestions.agents.ts
3036
+ import { groq as groq4 } from "@ai-sdk/groq";
3037
+ import dedent2 from "dedent";
3038
+ import z4 from "zod";
3039
+ import { agent as agent2, thirdPersonPrompt } from "@deepagents/agent";
3040
+ var suggestionsAgent = agent2({
3041
+ name: "text2sql-suggestions",
3042
+ model: groq4("openai/gpt-oss-20b"),
3043
+ output: z4.object({
3044
+ suggestions: z4.array(
3045
+ z4.object({
3046
+ question: z4.string().describe("A complex, high-impact business question."),
3047
+ sql: z4.string().describe("The SQL statement needed to answer the question."),
3048
+ businessValue: z4.string().describe("Why the question matters to stakeholders.")
3049
+ })
3050
+ ).min(1).max(5).describe("A set of up to two advanced question + SQL pairs.")
3051
+ }),
3052
+ prompt: (state) => {
3053
+ return dedent2`
3054
+ ${thirdPersonPrompt()}
3055
+
3056
+ <identity>
3057
+ You are a senior analytics strategist who proposes ambitious business questions
3058
+ and drafts the SQL needed to answer them. You specialize in identifying ideas
3059
+ that combine multiple tables, apply segmentation or time analysis, and surface
3060
+ metrics that drive executive decisions.
3061
+ </identity>
3062
+
3063
+
3064
+ <instructions>
3065
+ - Recommend one or two UNIQUE questions that go beyond simple counts or listings.
3066
+ - Favor questions that require joins, aggregates, time comparisons, cohort analysis,
3067
+ or window functions.
3068
+ - For each question, explain the business reason stakeholders care about it.
3069
+ - Provide the complete SQL query that could answer the question in the given schema.
3070
+ - Keep result sets scoped with LIMIT clauses (max 50 rows) when returning raw rows.
3071
+ - Ensure table/column names match the provided schema exactly.
3072
+ - Use columns marked [LowCardinality: ...] to identify meaningful categorical filters or segmentations.
3073
+ - Leverage table [rows / size] hints to determine whether to aggregate (large tables) or inspect detailed data (tiny tables).
3074
+ - Reference PK/Indexed annotations and the Indexes list to recommend queries that use efficient join/filter paths.
3075
+ - Column annotations may expose ranges/null percentages—use them to suggest realistic thresholds or quality checks.
3076
+ - Consult <relationship_examples> to anchor your recommendations in the actual join paths between tables.
3077
+ - Output only information grounded in the schema/context provided.
3078
+ </instructions>
3079
+
3080
+ <response-format>
3081
+ Return valid JSON that satisfies the defined output schema.
3082
+ </response-format>
2308
3083
  `;
2309
3084
  }
2310
3085
  });
2311
3086
 
2312
3087
  // packages/text2sql/src/lib/agents/text2sql.agent.ts
2313
- import { groq as groq3 } from "@ai-sdk/groq";
2314
3088
  import { tool as tool2 } from "ai";
2315
- import z4 from "zod";
3089
+ import z5 from "zod";
2316
3090
  import { toState as toState2 } from "@deepagents/agent";
2317
3091
  import { scratchpad_tool } from "@deepagents/toolbox";
2318
3092
  var tools2 = {
2319
3093
  validate_query: tool2({
2320
3094
  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.`,
2321
- inputSchema: z4.object({
2322
- sql: z4.string().describe("The SQL query to validate.")
3095
+ inputSchema: z5.object({
3096
+ sql: z5.string().describe("The SQL query to validate.")
2323
3097
  }),
2324
3098
  execute: async ({ sql }, options) => {
2325
3099
  const state = toState2(options);
@@ -2332,11 +3106,11 @@ var tools2 = {
2332
3106
  }),
2333
3107
  db_query: tool2({
2334
3108
  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.`,
2335
- inputSchema: z4.object({
2336
- reasoning: z4.string().describe(
3109
+ inputSchema: z5.object({
3110
+ reasoning: z5.string().describe(
2337
3111
  "Your reasoning for why this SQL query is relevant to the user request."
2338
3112
  ),
2339
- sql: z4.string().min(1, { message: "SQL query cannot be empty." }).refine(
3113
+ sql: z5.string().min(1, { message: "SQL query cannot be empty." }).refine(
2340
3114
  (sql) => sql.trim().toUpperCase().startsWith("SELECT") || sql.trim().toUpperCase().startsWith("WITH"),
2341
3115
  {
2342
3116
  message: "Only read-only SELECT or WITH queries are allowed."
@@ -2350,44 +3124,33 @@ var tools2 = {
2350
3124
  }),
2351
3125
  scratchpad: scratchpad_tool
2352
3126
  };
2353
- var t_a_g = agent({
2354
- model: groq3("openai/gpt-oss-20b"),
2355
- tools: tools2,
2356
- name: "text2sql"
2357
- // prompt: (state) => {
2358
- // return `
2359
- // ${state?.teachings || ''}
2360
- // ${state?.introspection || ''}
2361
- // ${chainOfThoughtPrompt}
2362
- // ${fewShotExamples}
2363
- // `;
2364
- // },
2365
- });
2366
3127
 
2367
3128
  // packages/text2sql/src/lib/checkpoint.ts
2368
3129
  import { createHash } from "node:crypto";
2369
3130
  import { existsSync, readFileSync, renameSync, writeFileSync } from "node:fs";
2370
3131
  import pLimit from "p-limit";
2371
3132
  var Checkpoint = class _Checkpoint {
2372
- constructor(path2, configHash, points) {
2373
- this.path = path2;
2374
- this.configHash = configHash;
3133
+ points;
3134
+ path;
3135
+ configHash;
3136
+ constructor(path3, configHash, points) {
2375
3137
  this.points = points;
3138
+ this.path = path3;
3139
+ this.configHash = configHash;
2376
3140
  }
2377
- points;
2378
3141
  /**
2379
3142
  * Load checkpoint from file, or return empty checkpoint if none exists.
2380
3143
  * Handles corrupted files and config changes gracefully.
2381
3144
  */
2382
3145
  static async load(options) {
2383
- const { path: path2, configHash } = options;
2384
- if (existsSync(path2)) {
3146
+ const { path: path3, configHash } = options;
3147
+ if (existsSync(path3)) {
2385
3148
  try {
2386
- const content = readFileSync(path2, "utf-8");
3149
+ const content = readFileSync(path3, "utf-8");
2387
3150
  const file = JSON.parse(content);
2388
3151
  if (configHash && file.configHash && file.configHash !== configHash) {
2389
3152
  console.log("\u26A0 Config changed, starting fresh");
2390
- return new _Checkpoint(path2, configHash, {});
3153
+ return new _Checkpoint(path3, configHash, {});
2391
3154
  }
2392
3155
  const points = file.points ?? {};
2393
3156
  const totalEntries = Object.values(points).reduce(
@@ -2395,14 +3158,14 @@ var Checkpoint = class _Checkpoint {
2395
3158
  0
2396
3159
  );
2397
3160
  console.log(`\u2713 Resuming from checkpoint (${totalEntries} entries)`);
2398
- return new _Checkpoint(path2, configHash, points);
3161
+ return new _Checkpoint(path3, configHash, points);
2399
3162
  } catch {
2400
3163
  console.log("\u26A0 Checkpoint corrupted, starting fresh");
2401
- return new _Checkpoint(path2, configHash, {});
3164
+ return new _Checkpoint(path3, configHash, {});
2402
3165
  }
2403
3166
  }
2404
3167
  console.log("Starting new checkpoint");
2405
- return new _Checkpoint(path2, configHash, {});
3168
+ return new _Checkpoint(path3, configHash, {});
2406
3169
  }
2407
3170
  /**
2408
3171
  * Run a single computation with checkpointing.
@@ -2487,14 +3250,16 @@ function hash(value) {
2487
3250
  return createHash("md5").update(JSON.stringify(value)).digest("hex");
2488
3251
  }
2489
3252
  var Point = class {
3253
+ #cache;
3254
+ data;
3255
+ persist;
2490
3256
  constructor(data, persist) {
2491
- this.data = data;
2492
- this.persist = persist;
2493
3257
  this.#cache = new Map(
2494
3258
  data.entries.map((e) => [e.inputHash, e.output])
2495
3259
  );
3260
+ this.data = data;
3261
+ this.persist = persist;
2496
3262
  }
2497
- #cache;
2498
3263
  /**
2499
3264
  * Execute computation if input wasn't processed before.
2500
3265
  * Returns cached output if input hash exists, otherwise executes, saves, and returns.
@@ -2534,12 +3299,12 @@ import { createHash as createHash2 } from "node:crypto";
2534
3299
  import { existsSync as existsSync2 } from "node:fs";
2535
3300
  import { readFile, writeFile } from "node:fs/promises";
2536
3301
  import { tmpdir } from "node:os";
2537
- import path from "node:path";
3302
+ import path2 from "node:path";
2538
3303
  var FileCache = class {
2539
3304
  path;
2540
3305
  constructor(watermark, extension = ".txt") {
2541
3306
  const hash2 = createHash2("md5").update(watermark).digest("hex");
2542
- this.path = path.join(tmpdir(), `text2sql-${hash2}${extension}`);
3307
+ this.path = path2.join(tmpdir(), `text2sql-${hash2}${extension}`);
2543
3308
  }
2544
3309
  async get() {
2545
3310
  if (existsSync2(this.path)) {
@@ -2567,302 +3332,328 @@ var JsonCache = class extends FileCache {
2567
3332
  }
2568
3333
  };
2569
3334
 
2570
- // packages/text2sql/src/lib/sql.ts
2571
- import {
2572
- APICallError as APICallError2,
2573
- InvalidToolInputError,
2574
- NoSuchToolError,
2575
- ToolCallRepairError,
2576
- generateId as generateId3
2577
- } from "ai";
2578
- import "@deepagents/agent";
2579
-
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 }
2601
- });
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
- }
2618
- };
2619
- var UnanswerableSQLError = class _UnanswerableSQLError extends Error {
2620
- constructor(message2) {
2621
- super(message2);
2622
- this.name = "UnanswerableSQLError";
2623
- }
2624
- static isInstance(error) {
2625
- return error instanceof _UnanswerableSQLError;
2626
- }
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
- )
2650
- );
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")
3335
+ // packages/text2sql/src/lib/instructions.ts
3336
+ function reasoningFramework() {
3337
+ return [
3338
+ role(
3339
+ "You are a very strong reasoner and planner. Use these critical instructions to structure your plans, thoughts, and responses."
3340
+ ),
3341
+ fragment(
3342
+ "Meta-cognitive reasoning framework",
3343
+ hint(
3344
+ "Before taking any action (either tool calls *or* responses to the user), you must proactively, methodically, and independently plan and reason about:"
3345
+ ),
3346
+ // 1) Logical dependencies and constraints
3347
+ principle({
3348
+ title: "Logical dependencies and constraints",
3349
+ description: "Analyze the intended action against the following factors. Resolve conflicts in order of importance:",
3350
+ policies: [
3351
+ policy({
3352
+ rule: "Policy-based rules, mandatory prerequisites, and constraints."
2670
3353
  }),
2671
- z5.object({
2672
- error: z5.string().describe(
2673
- "Error message explaining why the question cannot be answered with the given schema"
2674
- )
3354
+ policy({
3355
+ rule: "Order of operations: Ensure taking an action does not prevent a subsequent necessary action.",
3356
+ policies: [
3357
+ "The user may request actions in a random order, but you may need to reorder operations to maximize successful completion of the task."
3358
+ ]
3359
+ }),
3360
+ policy({
3361
+ rule: "Other prerequisites (information and/or actions needed)."
3362
+ }),
3363
+ policy({ rule: "Explicit user constraints or preferences." })
3364
+ ]
3365
+ }),
3366
+ // 2) Risk assessment
3367
+ principle({
3368
+ title: "Risk assessment",
3369
+ description: "What are the consequences of taking the action? Will the new state cause any future issues?",
3370
+ policies: [
3371
+ "For exploratory tasks (like searches), missing *optional* parameters is a LOW risk. **Prefer calling the tool with the available information over asking the user, unless** your Rule 1 (Logical Dependencies) reasoning determines that optional information is required for a later step in your plan."
3372
+ ]
3373
+ }),
3374
+ // 3) Abductive reasoning and hypothesis exploration
3375
+ principle({
3376
+ title: "Abductive reasoning and hypothesis exploration",
3377
+ description: "At each step, identify the most logical and likely reason for any problem encountered.",
3378
+ policies: [
3379
+ "Look beyond immediate or obvious causes. The most likely reason may not be the simplest and may require deeper inference.",
3380
+ "Hypotheses may require additional research. Each hypothesis may take multiple steps to test.",
3381
+ "Prioritize hypotheses based on likelihood, but do not discard less likely ones prematurely. A low-probability event may still be the root cause."
3382
+ ]
3383
+ }),
3384
+ // 4) Outcome evaluation and adaptability
3385
+ principle({
3386
+ title: "Outcome evaluation and adaptability",
3387
+ description: "Does the previous observation require any changes to your plan?",
3388
+ policies: [
3389
+ "If your initial hypotheses are disproven, actively generate new ones based on the gathered information."
3390
+ ]
3391
+ }),
3392
+ // 5) Information availability
3393
+ principle({
3394
+ title: "Information availability",
3395
+ description: "Incorporate all applicable and alternative sources of information, including:",
3396
+ policies: [
3397
+ "Using available tools and their capabilities",
3398
+ "All policies, rules, checklists, and constraints",
3399
+ "Previous observations and conversation history",
3400
+ "Information only available by asking the user"
3401
+ ]
3402
+ }),
3403
+ // 6) Precision and Grounding
3404
+ principle({
3405
+ title: "Precision and Grounding",
3406
+ description: "Ensure your reasoning is extremely precise and relevant to each exact ongoing situation.",
3407
+ policies: [
3408
+ "Verify your claims by quoting the exact applicable information (including policies) when referring to them."
3409
+ ]
3410
+ }),
3411
+ // 7) Completeness
3412
+ principle({
3413
+ title: "Completeness",
3414
+ description: "Ensure that all requirements, constraints, options, and preferences are exhaustively incorporated into your plan.",
3415
+ policies: [
3416
+ policy({
3417
+ rule: "Resolve conflicts using the order of importance in #1."
3418
+ }),
3419
+ policy({
3420
+ rule: "Avoid premature conclusions: There may be multiple relevant options for a given situation.",
3421
+ policies: [
3422
+ "To check for whether an option is relevant, reason about all information sources from #5.",
3423
+ "You may need to consult the user to even know whether something is applicable. Do not assume it is not applicable without checking."
3424
+ ]
3425
+ }),
3426
+ policy({
3427
+ rule: "Review applicable sources of information from #5 to confirm which are relevant to the current state."
2675
3428
  })
2676
- ])
2677
- });
2678
- const output = await sqlOutput.generate();
2679
- if ("error" in output) {
2680
- throw new UnanswerableSQLError(output.error);
2681
- }
2682
- const sql = extractSql(output.sql);
2683
- const validationError = await options.adapter.validate(sql);
2684
- if (validationError) {
2685
- throw new SQLValidationError(validationError);
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}`;
2700
- }
2701
- return error.message;
2702
- }
2703
- if (SQLValidationError.isInstance(error)) {
2704
- return `SQL Validation Error: ${error.message}`;
2705
- }
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;
2720
- }
2721
- if (SQLValidationError.isInstance(context.error)) {
2722
- return true;
2723
- }
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);
2746
- }
2747
- }
2748
- );
3429
+ ]
3430
+ }),
3431
+ // 8) Persistence and patience
3432
+ principle({
3433
+ title: "Persistence and patience",
3434
+ description: "Do not give up unless all the reasoning above is exhausted.",
3435
+ policies: [
3436
+ "Don't be dissuaded by time taken or user frustration.",
3437
+ "This persistence must be intelligent: On *transient* errors (e.g. please try again), you *must* retry **unless an explicit retry limit (e.g., max x tries) has been reached**. If such a limit is hit, you *must* stop. On *other* errors, you must change your strategy or arguments, not repeat the same failed call."
3438
+ ]
3439
+ }),
3440
+ // 9) Inhibit your response
3441
+ principle({
3442
+ title: "Inhibit your response",
3443
+ description: "Only take an action after all the above reasoning is completed. Once you've taken an action, you cannot take it back."
3444
+ })
3445
+ )
3446
+ ];
2749
3447
  }
2750
-
2751
- // packages/text2sql/src/lib/teach/teachings.ts
2752
3448
  function guidelines(options = {}) {
2753
3449
  const { date = "strict" } = options;
2754
3450
  const baseTeachings = [
2755
- // Schema adherence
2756
- hint(
2757
- "Use only tables and columns that exist in the schema. Never reference non-existent entities."
2758
- ),
2759
- hint(
2760
- "If the user asks to show a table or entity without specifying columns, use SELECT *."
2761
- ),
2762
- hint(
2763
- "When showing items associated with another entity, include the item ID and the related details requested."
2764
- ),
2765
- hint(
2766
- 'When asked to "show" items, list them unless the user explicitly asks to count or total.'
2767
- ),
2768
- hint(
2769
- "Use canonical/LowCardinality values verbatim for filtering; [rows/size] hints suggest when to aggregate instead of listing."
2770
- ),
2771
- // Joins and relationships
2772
- hint(
2773
- "Use appropriate JOINs based on the relationships defined in the schema."
2774
- ),
2775
- hint(
2776
- "Favor PK/indexed columns for joins and filters; follow relationship metadata for join direction and cardinality."
2777
- ),
2778
- // Aggregations and calculations
2779
- hint(
2780
- "Apply proper aggregations (COUNT, SUM, AVG, etc.) when the question implies summarization."
2781
- ),
2782
- hint(
2783
- 'When asked "how many X are there" about types/categories/statuses (e.g., "how many statuses are there?"), use COUNT(DISTINCT column). This asks about variety, not row count.'
3451
+ // Include the meta-cognitive reasoning framework
3452
+ ...reasoningFramework(),
3453
+ // Prerequisite policies (must do X before Y)
3454
+ fragment(
3455
+ "Prerequisite policies",
3456
+ policy({
3457
+ rule: "YOU MUST inspect schema structure and available tables",
3458
+ before: "generating ANY SQL query",
3459
+ reason: "NEVER generate SQL without knowing valid tables, columns, and relationships"
3460
+ }),
3461
+ policy({
3462
+ rule: "YOU MUST resolve ambiguous business terms with the user",
3463
+ before: "making ANY assumptions about terminology meaning",
3464
+ reason: "NEVER guess domain-specific language\u2014ask for clarification"
3465
+ }),
3466
+ policy({
3467
+ rule: "YOU MUST validate SQL syntax",
3468
+ before: "executing ANY query against the database",
3469
+ reason: "NEVER execute unvalidated queries"
3470
+ }),
3471
+ policy({
3472
+ rule: "YOU MUST complete ALL reasoning steps",
3473
+ before: "taking ANY tool call or response action",
3474
+ reason: "Once an action is taken, it CANNOT be undone. NO EXCEPTIONS."
3475
+ })
2784
3476
  ),
2785
- hint(
2786
- "Use window functions when the question requires ranking, running totals, or comparisons across rows."
3477
+ // Few-shot: Applying reasoning principles
3478
+ fragment(
3479
+ "Reasoning examples",
3480
+ example({
3481
+ question: "Show me sales last month",
3482
+ answer: `Applying Principle 1 (Logical dependencies):
3483
+ - Need: schema to know which table has sales data
3484
+ - Need: clarify "last month" = calendar month or rolling 30 days?
3485
+
3486
+ Applying Principle 5 (Information availability):
3487
+ - Schema shows: orders table with created_at, total columns
3488
+ - Missing: user's definition of "last month"
3489
+
3490
+ Action: Ask user for date range clarification BEFORE generating SQL.`
3491
+ }),
3492
+ example({
3493
+ question: "Why did my query return no results?",
3494
+ answer: `Applying Principle 3 (Abductive reasoning):
3495
+ - Hypothesis 1 (most likely): Filter too restrictive
3496
+ - Hypothesis 2: Data doesn't exist for that period
3497
+ - Hypothesis 3: JOIN eliminated matching rows
3498
+
3499
+ Testing hypotheses:
3500
+ 1. Remove filters one by one to isolate the issue
3501
+ 2. Check date range actually has data
3502
+ 3. Run subqueries separately to verify each table
3503
+
3504
+ Action: Start with most likely hypothesis, test incrementally. NEVER guess.`
3505
+ }),
3506
+ example({
3507
+ question: "Get me the top customers",
3508
+ answer: `Applying Principle 1 (Logical dependencies):
3509
+ - "Top" is ambiguous\u2014by revenue? by order count? by recency?
3510
+
3511
+ Applying Principle 9 (Inhibition):
3512
+ - MUST NOT generate SQL until "top" is defined
3513
+
3514
+ Action: Ask user: "Top by what metric\u2014total revenue, number of orders, or most recent activity?"`
3515
+ })
2787
3516
  ),
2788
- // Query semantics
2789
- hint(
2790
- 'Words like "reach", "reached", "hit" with a value (e.g., "temperature reach 80") mean >= (greater than or equal), not = (exact match).'
3517
+ // Schema adherence - consolidated into clear rules
3518
+ fragment(
3519
+ "Schema adherence",
3520
+ hint(
3521
+ "Use only tables and columns from the schema. For unspecified columns, use SELECT *. When showing related items, include IDs and requested details."
3522
+ ),
3523
+ hint(
3524
+ '"Show" means list items; "count" or "total" means aggregate. Use canonical values verbatim for filtering.'
3525
+ )
2791
3526
  ),
3527
+ // Joins - use relationship metadata
2792
3528
  hint(
2793
- 'For "shared by" two groups or mutually exclusive conditions (e.g., population > 1500 AND < 500), use INTERSECT between separate queries. A single WHERE with contradictory AND returns nothing.'
3529
+ "Use JOINs based on schema relationships. Favor PK/indexed columns; follow relationship metadata for direction and cardinality."
2794
3530
  ),
2795
- hint(
2796
- 'When filtering by a specific value from a joined table (e.g., "students who registered course statistics"), always include that WHERE condition. Do not omit mentioned filters.'
3531
+ // Aggregations - explain the concepts
3532
+ fragment(
3533
+ "Aggregations",
3534
+ hint(
3535
+ "Apply COUNT, SUM, AVG when the question implies summarization. Use window functions for ranking, running totals, or row comparisons."
3536
+ ),
3537
+ explain({
3538
+ concept: "counting variety vs counting rows",
3539
+ explanation: '"How many types/categories/statuses exist?" asks about variety (unique values), not total row count',
3540
+ therefore: "Use COUNT(DISTINCT column) for variety questions"
3541
+ })
2797
3542
  ),
2798
- hint(
2799
- "Handle NULL values appropriately using IS NULL, IS NOT NULL, or COALESCE."
3543
+ // Query semantics - explain concepts and document quirks
3544
+ fragment(
3545
+ "Query interpretation",
3546
+ explain({
3547
+ concept: "threshold language",
3548
+ explanation: 'Words like "reach", "hit", "exceed" with a value imply a threshold being met or passed',
3549
+ therefore: "Translate to >= (greater than or equal), not = (exact match)"
3550
+ }),
3551
+ quirk({
3552
+ issue: "Contradictory WHERE conditions (e.g., value > 100 AND value < 50) return empty results",
3553
+ workaround: 'Use INTERSECT between separate queries when finding items "shared by" groups with mutually exclusive conditions'
3554
+ }),
3555
+ quirk({
3556
+ issue: "NULL values behave unexpectedly in comparisons and aggregations",
3557
+ workaround: "Use IS NULL, IS NOT NULL, or COALESCE() to handle NULLs explicitly"
3558
+ }),
3559
+ hint(
3560
+ "Always include mentioned filters from joined tables in WHERE conditions."
3561
+ )
2800
3562
  ),
2801
- // Style and readability
3563
+ // Style preferences
2802
3564
  styleGuide({
2803
- prefer: 'For table aliases, use the full table name (e.g., "FROM users AS users", "JOIN order_items AS order_items"). For column aliases, use descriptive names that reflect the data (e.g., "COUNT(*) AS total_orders").',
2804
- never: "Use abbreviated table aliases (u, oi, ca) or generic positional aliases (t1, t2, a, b)."
3565
+ prefer: "Full table names as aliases (users AS users). Descriptive column aliases (COUNT(*) AS total_count).",
3566
+ never: "Abbreviated aliases (u, oi) or positional aliases (t1, t2, a, b)."
2805
3567
  }),
2806
3568
  styleGuide({
2807
- prefer: "Summaries should be concise, business-friendly, highlight key comparisons, and add a short helpful follow-up when useful."
2808
- }),
2809
- // Guardrails - Query safety
2810
- guardrail({
2811
- rule: "Generate ONLY valid, executable SQL.",
2812
- reason: "Invalid SQL wastes resources and confuses users.",
2813
- action: "Validate syntax and schema references before returning."
3569
+ prefer: "Concise, business-friendly summaries with key comparisons and helpful follow-ups."
2814
3570
  }),
2815
- guardrail({
2816
- rule: "Only generate SELECT/WITH statements (read-only queries).",
2817
- reason: "Prevents accidental data modification.",
2818
- action: "Never generate INSERT, UPDATE, DELETE, DROP, or other DDL/DML statements."
2819
- }),
2820
- guardrail({
2821
- rule: "Avoid unbounded scans on large tables.",
2822
- reason: "Protects performance and prevents runaway queries.",
2823
- action: "Ensure filters are applied on indexed columns before querying broad fact tables."
2824
- }),
2825
- guardrail({
2826
- rule: "Do not add LIMIT unless explicitly requested.",
2827
- action: 'Only add LIMIT when user explicitly asks for "top N", "first N", or similar. Do NOT add LIMIT for "list all", "show all", or simple "list" queries.',
2828
- reason: "Adding arbitrary limits changes query semantics."
2829
- }),
2830
- guardrail({
2831
- rule: "Add ORDER BY where appropriate for deterministic results.",
2832
- reason: "Ensures consistent query output.",
2833
- action: "Include ORDER BY when results have a natural ordering or when combined with LIMIT."
2834
- }),
2835
- guardrail({
2836
- rule: "Prevent cartesian or guesswork joins.",
2837
- reason: "Protect correctness and performance.",
2838
- action: "If join keys are missing or unclear, inspect relationships and ask for the intended join path before executing."
3571
+ // Safety guardrails - consolidated
3572
+ fragment(
3573
+ "Query safety",
3574
+ guardrail({
3575
+ rule: "Generate only valid, executable SELECT/WITH statements.",
3576
+ reason: "Read-only access prevents data modification.",
3577
+ action: "Never generate INSERT, UPDATE, DELETE, DROP, or DDL statements."
3578
+ }),
3579
+ guardrail({
3580
+ rule: "Avoid unbounded scans and cartesian joins.",
3581
+ reason: "Protects performance and correctness.",
3582
+ action: "Apply filters on indexed columns. If join keys are unclear, ask for clarification."
3583
+ }),
3584
+ guardrail({
3585
+ rule: "Preserve query semantics.",
3586
+ reason: "Arbitrary modifications change results.",
3587
+ action: 'Only add LIMIT for explicit "top N" requests. Add ORDER BY for deterministic results.'
3588
+ }),
3589
+ guardrail({
3590
+ rule: "Seek clarification for genuine ambiguity.",
3591
+ reason: "Prevents incorrect assumptions.",
3592
+ action: "Ask a focused question before guessing."
3593
+ })
3594
+ ),
3595
+ clarification({
3596
+ when: "Ambiguous ranking language (top, best, active) without a metric.",
3597
+ ask: "Clarify the ranking metric or definition.",
3598
+ reason: "Ensures correct aggregation and ordering."
2839
3599
  }),
2840
- guardrail({
2841
- rule: "Ensure the query is optimized for the schema.",
2842
- reason: "Better performance and resource usage.",
2843
- action: "Use indexed columns for filtering, avoid SELECT * on large joins, prefer specific column selection when appropriate."
3600
+ hint(
3601
+ 'Use sample cell values from schema hints to match exact casing and format in WHERE conditions (e.g., "Male" vs "male" vs "M").'
3602
+ ),
3603
+ workflow({
3604
+ task: "SQL generation",
3605
+ steps: [
3606
+ "Schema linking: identify which tables and columns are mentioned or implied by the question.",
3607
+ "Check if column names match question terms (e.g., Total_X). Select directly if match found.",
3608
+ "Identify SQL patterns needed: aggregation, segmentation, time range, ranking.",
3609
+ "Select tables and relationships. Note lookup tables and filter values from schema.",
3610
+ "Plan join/filter/aggregation order based on table sizes and indexes.",
3611
+ "Generate SQL that answers the question.",
3612
+ "Verify: mentally translate SQL back to natural language. Does it match the original question?"
3613
+ ]
2844
3614
  }),
2845
- guardrail({
2846
- rule: "When facing genuine ambiguity with multiple valid interpretations, seek clarification.",
2847
- reason: "Prevents incorrect assumptions in edge cases.",
2848
- action: "Ask a focused clarifying question before proceeding with a guess."
3615
+ workflow({
3616
+ task: "Error recovery",
3617
+ triggers: ["SQL error", "query failed", "execution error"],
3618
+ steps: [
3619
+ "Classify the error type: syntax error, missing join, wrong aggregation, invalid column, type mismatch.",
3620
+ "For syntax errors: check SQL keywords, quotes, parentheses balance.",
3621
+ "For missing join: identify unlinked tables and add appropriate JOIN clause.",
3622
+ "For wrong aggregation: verify GROUP BY includes all non-aggregated SELECT columns.",
3623
+ "For invalid column: re-check schema for correct column name and table.",
3624
+ "Apply targeted fix based on error classification. Avoid blind regeneration."
3625
+ ],
3626
+ notes: "Maximum 3 retry attempts. If still failing, explain the issue to the user."
2849
3627
  }),
2850
- // Clarifications
2851
- clarification({
2852
- when: 'The request uses ambiguous scoring or ranking language (e.g., "top", "best", "active") without a metric.',
2853
- ask: "Clarify the ranking metric or definition before writing the query.",
2854
- reason: "Ensures the correct aggregation/ordering is used."
3628
+ workflow({
3629
+ task: "Complex query decomposition",
3630
+ triggers: [
3631
+ "multiple conditions",
3632
+ "nested requirements",
3633
+ "compare across",
3634
+ "for each"
3635
+ ],
3636
+ steps: [
3637
+ "Identify if question has multiple independent parts or nested dependencies.",
3638
+ "For independent parts: break into sub-questions, solve each, then combine with UNION or JOIN.",
3639
+ "For nested dependencies: solve inner requirement first, use result in outer query (subquery or CTE).",
3640
+ "For comparisons across groups: use window functions or self-joins.",
3641
+ "Combine sub-results into final answer. Verify completeness."
3642
+ ],
3643
+ notes: "Complex questions often need CTEs (WITH clauses) for clarity and reusability."
2855
3644
  }),
2856
- // Workflow
2857
3645
  workflow({
2858
- task: "SQL generation plan",
3646
+ task: "Multi-turn context",
3647
+ triggers: ["follow-up", "and also", "what about", "same but", "instead"],
2859
3648
  steps: [
2860
- 'Scan column names for terms matching the question. If a phrase like "total X" or "number of Y" matches a column name (e.g., Total_X, Num_Y), select that column directly instead of aggregating.',
2861
- "Translate the question into SQL patterns (aggregation, segmentation, time range, ranking) only if no column name match.",
2862
- "Choose tables/relations that satisfy those patterns; note lookup tables and filter values implied by schema hints.",
2863
- "Sketch join/filter/aggregation order considering table sizes, indexes, and stats.",
2864
- "Generate precise, validated SQL that answers the question."
2865
- ]
3649
+ 'Identify references to previous context: "it", "that", "those", "the same".',
3650
+ "Resolve references using conversation history: which tables, filters, or results were mentioned.",
3651
+ 'For refinements ("but only X"): add filter to previous query.',
3652
+ 'For extensions ("and also Y"): expand SELECT or add JOIN.',
3653
+ 'For pivots ("what about Z instead"): replace the changed element, keep unchanged parts.',
3654
+ "Maintain consistency with previous query structure when possible."
3655
+ ],
3656
+ notes: "If reference is ambiguous, ask which previous result or entity the user means."
2866
3657
  })
2867
3658
  ];
2868
3659
  if (date === "strict") {
@@ -2884,6 +3675,14 @@ function guidelines(options = {}) {
2884
3675
  }
2885
3676
 
2886
3677
  // packages/text2sql/src/lib/sql.ts
3678
+ import {
3679
+ APICallError as APICallError2,
3680
+ InvalidToolInputError,
3681
+ NoSuchToolError as NoSuchToolError2,
3682
+ ToolCallRepairError,
3683
+ generateId as generateId3
3684
+ } from "ai";
3685
+ import "@deepagents/agent";
2887
3686
  var Text2Sql = class {
2888
3687
  #config;
2889
3688
  constructor(config) {
@@ -2971,21 +3770,60 @@ var Text2Sql = class {
2971
3770
  const schemaFragments = await this.index();
2972
3771
  const context = new ContextEngine({
2973
3772
  store: this.#config.store,
2974
- chatId: params.chatId
3773
+ chatId: params.chatId,
3774
+ userId: params.userId
2975
3775
  }).set(
2976
- ...this.#config.instructions,
3776
+ ...schemaFragments,
2977
3777
  ...this.#buildRenderingInstructions(),
2978
- ...schemaFragments
3778
+ fragment(
3779
+ "Bash tool usage",
3780
+ workflow({
3781
+ task: "Query execution",
3782
+ steps: [
3783
+ 'Execute SQL through bash tool: sql run "SELECT ..."',
3784
+ "Read the output: file path, column names, and row count.",
3785
+ "Use column names to construct jq filters: cat <path> | jq '.[] | {col1, col2}'",
3786
+ "For large results, slice first: cat <path> | jq '.[:10]'"
3787
+ ]
3788
+ }),
3789
+ hint(
3790
+ `You cannot access sql through a tool, it'll fail so the proper way to access it is through the bash tool using "sql run" and "sql validate" commands.`
3791
+ ),
3792
+ hint(
3793
+ "The sql command outputs: file path, column names (comma-separated), and row count. Use column names to construct precise jq queries."
3794
+ ),
3795
+ hint(
3796
+ 'This is virtual bash environment and "sql" commands proxy to the database hence you cannot access sql files directly.'
3797
+ ),
3798
+ hint(
3799
+ "If a query fails, the sql command returns an error message in stderr."
3800
+ )
3801
+ ),
3802
+ ...this.#config.instructions
2979
3803
  );
2980
3804
  const userMsg = messages.at(-1);
2981
3805
  if (userMsg) {
2982
- context.set(user(userMsg));
3806
+ context.set(message(userMsg));
2983
3807
  await context.save();
2984
3808
  }
2985
- const chatAgent = t_a_g.clone({
3809
+ const messageId = userMsg?.id ?? generateId3();
3810
+ const skillMounts = context.getSkillMounts();
3811
+ const { bash } = await createResultTools({
3812
+ adapter: this.#config.adapter,
3813
+ chatId: params.chatId,
3814
+ messageId,
3815
+ skillMounts
3816
+ });
3817
+ const chatAgent = agent({
3818
+ name: "text2sql",
2986
3819
  model: this.#config.model,
2987
3820
  context,
2988
- tools: { ...t_a_g.tools, ...this.#config.tools }
3821
+ tools: {
3822
+ bash,
3823
+ ...this.#config.tools
3824
+ },
3825
+ guardrails: [errorRecoveryGuardrail],
3826
+ maxGuardrailRetries: 3
2989
3827
  });
2990
3828
  const result = await chatAgent.stream({});
2991
3829
  return result.toUIMessageStream({
@@ -2994,6 +3832,7 @@ var Text2Sql = class {
2994
3832
  sendFinish: true,
2995
3833
  sendReasoning: true,
2996
3834
  sendSources: true,
3835
+ originalMessages: messages,
2997
3836
  generateMessageId: generateId3,
2998
3837
  onFinish: async ({ responseMessage }) => {
2999
3838
  context.set(assistant(responseMessage));
@@ -3009,7 +3848,8 @@ var Text2Sql = class {
3009
3848
  const schemaFragments = await this.index();
3010
3849
  const context = new ContextEngine({
3011
3850
  store: this.#config.store,
3012
- chatId: params.chatId
3851
+ chatId: params.chatId,
3852
+ userId: params.userId
3013
3853
  }).set(
3014
3854
  ...developer_agent_default.fragments,
3015
3855
  ...this.#config.instructions,
@@ -3017,7 +3857,7 @@ var Text2Sql = class {
3017
3857
  );
3018
3858
  const userMsg = messages.at(-1);
3019
3859
  if (userMsg) {
3020
- context.set(user(userMsg));
3860
+ context.set(message(userMsg));
3021
3861
  await context.save();
3022
3862
  }
3023
3863
  const developerAgent = agent({
@@ -3043,7 +3883,7 @@ var Text2Sql = class {
3043
3883
  });
3044
3884
  }
3045
3885
  #formatError(error) {
3046
- if (NoSuchToolError.isInstance(error)) {
3886
+ if (NoSuchToolError2.isInstance(error)) {
3047
3887
  return "The model tried to call an unknown tool.";
3048
3888
  } else if (InvalidToolInputError.isInstance(error)) {
3049
3889
  return "The model called a tool with invalid arguments.";
@@ -3062,10 +3902,13 @@ export {
3062
3902
  FileCache,
3063
3903
  JsonCache,
3064
3904
  Point,
3905
+ SQLValidationError,
3065
3906
  Text2Sql,
3907
+ UnanswerableSQLError,
3066
3908
  applyTablesFilter,
3067
3909
  column,
3068
3910
  constraint,
3911
+ createResultTools,
3069
3912
  dialectInfo,
3070
3913
  filterRelationshipsByTables,
3071
3914
  filterTablesByName,
@@ -3076,8 +3919,8 @@ export {
3076
3919
  matchesFilter,
3077
3920
  relationship,
3078
3921
  suggestionsAgent,
3079
- t_a_g,
3080
3922
  table,
3923
+ toSql,
3081
3924
  view
3082
3925
  };
3083
3926
  //# sourceMappingURL=index.js.map