@deepagents/text2sql 0.19.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -511,6 +511,32 @@ var fragments = [
511
511
  hint("When validating user SQL, explain any errors clearly")
512
512
  ];
513
513
 
514
+ // packages/text2sql/src/lib/agents/exceptions.ts
515
+ var sqlValidationMarker = Symbol("SQLValidationError");
516
+ var unanswerableSqlMarker = Symbol("UnanswerableSQLError");
517
+ var SQLValidationError = class _SQLValidationError extends Error {
518
+ [sqlValidationMarker];
519
+ constructor(message2) {
520
+ super(message2);
521
+ this.name = "SQLValidationError";
522
+ this[sqlValidationMarker] = true;
523
+ }
524
+ static isInstance(error) {
525
+ return error instanceof _SQLValidationError && error[sqlValidationMarker] === true;
526
+ }
527
+ };
528
+ var UnanswerableSQLError = class _UnanswerableSQLError extends Error {
529
+ [unanswerableSqlMarker];
530
+ constructor(message2) {
531
+ super(message2);
532
+ this.name = "UnanswerableSQLError";
533
+ this[unanswerableSqlMarker] = true;
534
+ }
535
+ static isInstance(error) {
536
+ return error instanceof _UnanswerableSQLError && error[unanswerableSqlMarker] === true;
537
+ }
538
+ };
539
+
514
540
  // packages/text2sql/src/lib/agents/result-tools.ts
515
541
  import { tool as tool2 } from "ai";
516
542
  import { createBashTool } from "bash-tool";
@@ -659,6 +685,14 @@ var BLOCKED_DB_CLIENT_COMMANDS = /* @__PURE__ */ new Set([
659
685
  ]);
660
686
  var BLOCKED_RAW_SQL_COMMANDS = /* @__PURE__ */ new Set(["select", "with"]);
661
687
  var ALLOWED_SQL_PROXY_SUBCOMMANDS = /* @__PURE__ */ new Set(["run", "validate"]);
688
+ var SHELL_INTERPRETER_COMMANDS = /* @__PURE__ */ new Set([
689
+ "bash",
690
+ "sh",
691
+ "zsh",
692
+ "dash",
693
+ "ksh"
694
+ ]);
695
+ var WRAPPER_COMMANDS = /* @__PURE__ */ new Set(["env", "command", "eval"]);
662
696
  var SQL_PROXY_ENFORCEMENT_MESSAGE = [
663
697
  "Direct database querying through bash is blocked.",
664
698
  "Use SQL proxy commands in this order:",
@@ -714,82 +748,94 @@ function isScriptNode(value) {
714
748
  const node = value;
715
749
  return node.type === "Script" && Array.isArray(node.statements);
716
750
  }
717
- function scriptContainsBlockedCommand(script, context) {
718
- return statementsContainBlockedCommand(script.statements, context);
751
+ function scriptContainsBlockedCommand(script, context, mode = "blocked-only") {
752
+ return statementsContainBlockedCommand(script.statements, context, mode);
719
753
  }
720
- function statementsContainBlockedCommand(statements, context) {
754
+ function statementsContainBlockedCommand(statements, context, mode) {
721
755
  for (const statement of statements) {
722
- if (statementContainsBlockedCommand(statement, context)) {
756
+ if (statementContainsBlockedCommand(statement, context, mode)) {
723
757
  return true;
724
758
  }
725
759
  }
726
760
  return false;
727
761
  }
728
- function statementContainsBlockedCommand(statement, context) {
762
+ function statementContainsBlockedCommand(statement, context, mode) {
729
763
  for (const pipeline of statement.pipelines) {
730
- if (pipelineContainsBlockedCommand(pipeline, context)) {
764
+ if (pipelineContainsBlockedCommand(pipeline, context, mode)) {
731
765
  return true;
732
766
  }
733
767
  }
734
768
  return false;
735
769
  }
736
- function pipelineContainsBlockedCommand(pipeline, context) {
737
- for (const command of pipeline.commands) {
770
+ function pipelineContainsBlockedCommand(pipeline, context, mode) {
771
+ for (const [index2, command] of pipeline.commands.entries()) {
738
772
  if (command.type === "FunctionDef") {
739
773
  context.functionDefinitions.set(command.name, command);
740
774
  continue;
741
775
  }
742
- if (commandContainsBlockedCommand(command, context)) {
776
+ if (commandContainsBlockedCommand(command, context, mode, {
777
+ stdinFromPipe: index2 > 0
778
+ })) {
743
779
  return true;
744
780
  }
745
781
  }
746
782
  return false;
747
783
  }
748
- function stringCommandContainsBlockedCommand(command, context) {
784
+ function stringCommandContainsBlockedCommand(command, context, mode = "blocked-only") {
749
785
  let script;
750
786
  try {
751
787
  script = parse(command);
752
788
  } catch {
753
789
  return false;
754
790
  }
755
- return scriptContainsBlockedCommand(script, cloneInspectionContext(context));
791
+ return scriptContainsBlockedCommand(
792
+ script,
793
+ cloneInspectionContext(context),
794
+ mode
795
+ );
756
796
  }
757
- function wordContainsBlockedCommand(word, context) {
797
+ function wordContainsBlockedCommand(word, context, mode) {
758
798
  if (!word) {
759
799
  return false;
760
800
  }
761
801
  return wordPartContainsBlockedCommand(
762
802
  word.parts,
763
- context
803
+ context,
804
+ mode
764
805
  );
765
806
  }
766
- function wordPartContainsBlockedCommand(parts, context) {
807
+ function wordPartContainsBlockedCommand(parts, context, mode) {
767
808
  for (const part of parts) {
768
- if (partContainsBlockedCommand(part, context)) {
809
+ if (partContainsBlockedCommand(part, context, mode)) {
769
810
  return true;
770
811
  }
771
812
  }
772
813
  return false;
773
814
  }
774
- function partContainsBlockedCommand(node, context) {
815
+ function partContainsBlockedCommand(node, context, mode) {
775
816
  const type = node.type;
776
817
  if (type === "CommandSubstitution" || type === "ProcessSubstitution") {
777
818
  if (isScriptNode(node.body)) {
778
819
  return scriptContainsBlockedCommand(
779
820
  node.body,
780
- cloneInspectionContext(context)
821
+ cloneInspectionContext(context),
822
+ mode
781
823
  );
782
824
  }
783
825
  return false;
784
826
  }
785
827
  if (type === "ArithCommandSubst" && typeof node.command === "string") {
786
- return stringCommandContainsBlockedCommand(node.command, context);
828
+ return stringCommandContainsBlockedCommand(node.command, context, mode);
787
829
  }
788
830
  for (const value of Object.values(node)) {
789
831
  if (Array.isArray(value)) {
790
832
  for (const item of value) {
791
833
  if (typeof item === "object" && item !== null) {
792
- if (partContainsBlockedCommand(item, context)) {
834
+ if (partContainsBlockedCommand(
835
+ item,
836
+ context,
837
+ mode
838
+ )) {
793
839
  return true;
794
840
  }
795
841
  }
@@ -797,14 +843,18 @@ function partContainsBlockedCommand(node, context) {
797
843
  continue;
798
844
  }
799
845
  if (typeof value === "object" && value !== null) {
800
- if (partContainsBlockedCommand(value, context)) {
846
+ if (partContainsBlockedCommand(
847
+ value,
848
+ context,
849
+ mode
850
+ )) {
801
851
  return true;
802
852
  }
803
853
  }
804
854
  }
805
855
  return false;
806
856
  }
807
- function functionInvocationContainsBlockedCommand(functionName, context) {
857
+ function functionInvocationContainsBlockedCommand(functionName, context, mode) {
808
858
  const definition = context.functionDefinitions.get(functionName);
809
859
  if (!definition) {
810
860
  return false;
@@ -814,52 +864,306 @@ function functionInvocationContainsBlockedCommand(functionName, context) {
814
864
  }
815
865
  const invocationContext = cloneInspectionContext(context);
816
866
  invocationContext.callStack.add(functionName);
817
- return commandContainsBlockedCommand(definition.body, invocationContext);
867
+ return commandContainsBlockedCommand(
868
+ definition.body,
869
+ invocationContext,
870
+ mode,
871
+ { stdinFromPipe: false }
872
+ );
873
+ }
874
+ function isAsciiLetter(character) {
875
+ const charCode = character.charCodeAt(0);
876
+ return charCode >= 65 && charCode <= 90 || charCode >= 97 && charCode <= 122;
877
+ }
878
+ function isAsciiDigit(character) {
879
+ const charCode = character.charCodeAt(0);
880
+ return charCode >= 48 && charCode <= 57;
881
+ }
882
+ function isValidEnvVariableName(name) {
883
+ if (!name) {
884
+ return false;
885
+ }
886
+ const firstChar = name[0];
887
+ if (!(isAsciiLetter(firstChar) || firstChar === "_")) {
888
+ return false;
889
+ }
890
+ for (let index2 = 1; index2 < name.length; index2 += 1) {
891
+ const char = name[index2];
892
+ if (!(isAsciiLetter(char) || isAsciiDigit(char) || char === "_")) {
893
+ return false;
894
+ }
895
+ }
896
+ return true;
897
+ }
898
+ function isEnvAssignmentToken(token) {
899
+ const separatorIndex = token.indexOf("=");
900
+ if (separatorIndex <= 0) {
901
+ return false;
902
+ }
903
+ return isValidEnvVariableName(token.slice(0, separatorIndex));
904
+ }
905
+ function parseShortOptionCluster(option) {
906
+ if (!option.startsWith("-") || option.startsWith("--") || option.length <= 1) {
907
+ return {
908
+ valid: false,
909
+ hasCommandFlag: false,
910
+ hasStdinFlag: false,
911
+ consumesNextArg: false
912
+ };
913
+ }
914
+ let hasCommandFlag = false;
915
+ let hasStdinFlag = false;
916
+ let consumesNextArg = false;
917
+ for (let index2 = 1; index2 < option.length; index2 += 1) {
918
+ const char = option[index2];
919
+ if (!isAsciiLetter(char)) {
920
+ return {
921
+ valid: false,
922
+ hasCommandFlag: false,
923
+ hasStdinFlag: false,
924
+ consumesNextArg: false
925
+ };
926
+ }
927
+ if (char === "c") {
928
+ hasCommandFlag = true;
929
+ } else if (char === "s") {
930
+ hasStdinFlag = true;
931
+ } else if (char === "O" || char === "o") {
932
+ consumesNextArg = true;
933
+ }
934
+ }
935
+ return { valid: true, hasCommandFlag, hasStdinFlag, consumesNextArg };
936
+ }
937
+ function getShellInvocationDescriptor(args) {
938
+ let readsFromStdin = false;
939
+ const longOptionsWithValue = /* @__PURE__ */ new Set(["--rcfile", "--init-file"]);
940
+ for (let index2 = 0; index2 < args.length; index2 += 1) {
941
+ const token = asStaticWordText(args[index2]);
942
+ if (token == null) {
943
+ return { kind: "unknown", payload: null };
944
+ }
945
+ if (token === "--") {
946
+ if (index2 + 1 >= args.length) {
947
+ break;
948
+ }
949
+ return {
950
+ kind: "script",
951
+ payload: asStaticWordText(args[index2 + 1])
952
+ };
953
+ }
954
+ if (token === "--command") {
955
+ return {
956
+ kind: "command",
957
+ payload: asStaticWordText(args[index2 + 1])
958
+ };
959
+ }
960
+ if (token.startsWith("--command=")) {
961
+ return {
962
+ kind: "command",
963
+ payload: token.slice("--command=".length)
964
+ };
965
+ }
966
+ if (token.startsWith("--")) {
967
+ if (token.includes("=")) {
968
+ continue;
969
+ }
970
+ if (longOptionsWithValue.has(token)) {
971
+ if (index2 + 1 >= args.length) {
972
+ return { kind: "unknown", payload: null };
973
+ }
974
+ index2 += 1;
975
+ }
976
+ continue;
977
+ }
978
+ if (token.startsWith("-") && !token.startsWith("--")) {
979
+ const parsed = parseShortOptionCluster(token);
980
+ if (!parsed.valid) {
981
+ return { kind: "unknown", payload: null };
982
+ }
983
+ if (parsed.hasCommandFlag) {
984
+ return {
985
+ kind: "command",
986
+ payload: asStaticWordText(args[index2 + 1])
987
+ };
988
+ }
989
+ if (parsed.hasStdinFlag) {
990
+ readsFromStdin = true;
991
+ }
992
+ if (parsed.consumesNextArg) {
993
+ if (index2 + 1 >= args.length) {
994
+ return { kind: "unknown", payload: null };
995
+ }
996
+ index2 += 1;
997
+ }
998
+ continue;
999
+ }
1000
+ return {
1001
+ kind: "script",
1002
+ payload: token
1003
+ };
1004
+ }
1005
+ if (readsFromStdin) {
1006
+ return { kind: "stdin", payload: null };
1007
+ }
1008
+ return { kind: "none", payload: null };
1009
+ }
1010
+ function getHereDocPayload(redirections) {
1011
+ const payloads = [];
1012
+ for (const redirection of redirections) {
1013
+ if (redirection.target.type !== "HereDoc") {
1014
+ continue;
1015
+ }
1016
+ if (!redirection.target.content) {
1017
+ payloads.push("");
1018
+ continue;
1019
+ }
1020
+ const payload = asStaticWordText(redirection.target.content);
1021
+ if (payload == null) {
1022
+ return { hasHereDoc: true, payload: null };
1023
+ }
1024
+ payloads.push(payload);
1025
+ }
1026
+ if (payloads.length === 0) {
1027
+ return { hasHereDoc: false, payload: null };
1028
+ }
1029
+ return { hasHereDoc: true, payload: payloads.join("\n") };
1030
+ }
1031
+ function joinStaticWords(words) {
1032
+ const tokens = [];
1033
+ for (const word of words) {
1034
+ const token = asStaticWordText(word);
1035
+ if (token == null) {
1036
+ return null;
1037
+ }
1038
+ tokens.push(token);
1039
+ }
1040
+ return tokens.join(" ");
1041
+ }
1042
+ function resolveEnvWrapperCommand(args) {
1043
+ let index2 = 0;
1044
+ while (index2 < args.length) {
1045
+ const token = asStaticWordText(args[index2]);
1046
+ if (token == null) {
1047
+ return { kind: "unknown" };
1048
+ }
1049
+ if (token === "--") {
1050
+ index2 += 1;
1051
+ break;
1052
+ }
1053
+ if (token === "-u" || token === "--unset" || token === "--chdir") {
1054
+ if (index2 + 1 >= args.length) {
1055
+ return { kind: "unknown" };
1056
+ }
1057
+ index2 += 2;
1058
+ continue;
1059
+ }
1060
+ if (token.startsWith("--unset=") || token.startsWith("--chdir=")) {
1061
+ index2 += 1;
1062
+ continue;
1063
+ }
1064
+ if (token.startsWith("-") && token !== "-" && !isEnvAssignmentToken(token)) {
1065
+ index2 += 1;
1066
+ continue;
1067
+ }
1068
+ if (isEnvAssignmentToken(token)) {
1069
+ index2 += 1;
1070
+ continue;
1071
+ }
1072
+ break;
1073
+ }
1074
+ if (index2 >= args.length) {
1075
+ return { kind: "none" };
1076
+ }
1077
+ return {
1078
+ kind: "resolved",
1079
+ name: args[index2],
1080
+ args: args.slice(index2 + 1)
1081
+ };
1082
+ }
1083
+ function resolveCommandWrapperCommand(args) {
1084
+ let index2 = 0;
1085
+ let lookupOnly = false;
1086
+ while (index2 < args.length) {
1087
+ const token = asStaticWordText(args[index2]);
1088
+ if (token == null) {
1089
+ return { kind: "unknown" };
1090
+ }
1091
+ if (token === "--") {
1092
+ index2 += 1;
1093
+ break;
1094
+ }
1095
+ if (token === "-v" || token === "-V") {
1096
+ lookupOnly = true;
1097
+ index2 += 1;
1098
+ continue;
1099
+ }
1100
+ if (token.startsWith("-") && token !== "-") {
1101
+ index2 += 1;
1102
+ continue;
1103
+ }
1104
+ break;
1105
+ }
1106
+ if (lookupOnly || index2 >= args.length) {
1107
+ return { kind: "none" };
1108
+ }
1109
+ return {
1110
+ kind: "resolved",
1111
+ name: args[index2],
1112
+ args: args.slice(index2 + 1)
1113
+ };
818
1114
  }
819
- function commandContainsBlockedCommand(command, context) {
1115
+ function commandContainsBlockedCommand(command, context, mode, options = { stdinFromPipe: false }) {
820
1116
  switch (command.type) {
821
1117
  case "SimpleCommand":
822
- return isBlockedSimpleCommand(command, context);
1118
+ return isBlockedSimpleCommand(command, context, mode, options);
823
1119
  case "If":
824
1120
  return command.clauses.some(
825
1121
  (clause) => statementsContainBlockedCommand(
826
1122
  clause.condition,
827
- cloneInspectionContext(context)
1123
+ cloneInspectionContext(context),
1124
+ mode
828
1125
  ) || statementsContainBlockedCommand(
829
1126
  clause.body,
830
- cloneInspectionContext(context)
1127
+ cloneInspectionContext(context),
1128
+ mode
831
1129
  )
832
1130
  ) || (command.elseBody ? statementsContainBlockedCommand(
833
1131
  command.elseBody,
834
- cloneInspectionContext(context)
1132
+ cloneInspectionContext(context),
1133
+ mode
835
1134
  ) : false);
836
1135
  case "For":
837
1136
  case "CStyleFor":
838
1137
  return statementsContainBlockedCommand(
839
1138
  command.body,
840
- cloneInspectionContext(context)
1139
+ cloneInspectionContext(context),
1140
+ mode
841
1141
  );
842
1142
  case "While":
843
1143
  case "Until":
844
1144
  return statementsContainBlockedCommand(
845
1145
  command.condition,
846
- cloneInspectionContext(context)
1146
+ cloneInspectionContext(context),
1147
+ mode
847
1148
  ) || statementsContainBlockedCommand(
848
1149
  command.body,
849
- cloneInspectionContext(context)
1150
+ cloneInspectionContext(context),
1151
+ mode
850
1152
  );
851
1153
  case "Case":
852
1154
  return command.items.some(
853
1155
  (item) => statementsContainBlockedCommand(
854
1156
  item.body,
855
- cloneInspectionContext(context)
1157
+ cloneInspectionContext(context),
1158
+ mode
856
1159
  )
857
1160
  );
858
1161
  case "Subshell":
859
1162
  case "Group":
860
1163
  return statementsContainBlockedCommand(
861
1164
  command.body,
862
- cloneInspectionContext(context)
1165
+ cloneInspectionContext(context),
1166
+ mode
863
1167
  );
864
1168
  case "FunctionDef":
865
1169
  return false;
@@ -872,16 +1176,16 @@ function commandContainsBlockedCommand(command, context) {
872
1176
  }
873
1177
  }
874
1178
  }
875
- function isBlockedSimpleCommand(command, context) {
876
- if (wordContainsBlockedCommand(command.name, context)) {
1179
+ function isBlockedSimpleCommand(command, context, mode, options) {
1180
+ if (wordContainsBlockedCommand(command.name, context, mode)) {
877
1181
  return true;
878
1182
  }
879
- if (command.args.some((arg) => wordContainsBlockedCommand(arg, context))) {
1183
+ if (command.args.some((arg) => wordContainsBlockedCommand(arg, context, mode))) {
880
1184
  return true;
881
1185
  }
882
1186
  if (command.assignments.some(
883
- (assignment) => wordContainsBlockedCommand(assignment.value, context) || (assignment.array?.some(
884
- (value) => wordContainsBlockedCommand(value, context)
1187
+ (assignment) => wordContainsBlockedCommand(assignment.value, context, mode) || (assignment.array?.some(
1188
+ (value) => wordContainsBlockedCommand(value, context, mode)
885
1189
  ) ?? false)
886
1190
  )) {
887
1191
  return true;
@@ -890,11 +1194,16 @@ function isBlockedSimpleCommand(command, context) {
890
1194
  if (redirection.target.type === "Word") {
891
1195
  return wordContainsBlockedCommand(
892
1196
  redirection.target,
893
- context
1197
+ context,
1198
+ mode
894
1199
  );
895
1200
  }
896
1201
  if (redirection.target.type === "HereDoc" && redirection.target.content) {
897
- return wordContainsBlockedCommand(redirection.target.content, context);
1202
+ return wordContainsBlockedCommand(
1203
+ redirection.target.content,
1204
+ context,
1205
+ mode
1206
+ );
898
1207
  }
899
1208
  return false;
900
1209
  })) {
@@ -913,9 +1222,92 @@ function isBlockedSimpleCommand(command, context) {
913
1222
  }
914
1223
  if (normalizedName === "sql") {
915
1224
  const subcommand = asStaticWordText(command.args[0])?.toLowerCase();
916
- return !subcommand || !ALLOWED_SQL_PROXY_SUBCOMMANDS.has(subcommand);
1225
+ if (!subcommand) {
1226
+ return true;
1227
+ }
1228
+ if (mode === "block-all-sql") {
1229
+ return true;
1230
+ }
1231
+ return !ALLOWED_SQL_PROXY_SUBCOMMANDS.has(subcommand);
1232
+ }
1233
+ const inspectWrappedCommand = (resolved) => {
1234
+ if (resolved.kind === "none") {
1235
+ return false;
1236
+ }
1237
+ if (resolved.kind === "unknown" || !resolved.name || !resolved.args) {
1238
+ return true;
1239
+ }
1240
+ return isBlockedSimpleCommand(
1241
+ {
1242
+ name: resolved.name,
1243
+ args: resolved.args,
1244
+ assignments: [],
1245
+ redirections: []
1246
+ },
1247
+ context,
1248
+ "block-all-sql",
1249
+ options
1250
+ );
1251
+ };
1252
+ if (WRAPPER_COMMANDS.has(normalizedName)) {
1253
+ if (normalizedName === "env") {
1254
+ return inspectWrappedCommand(resolveEnvWrapperCommand(command.args));
1255
+ }
1256
+ if (normalizedName === "command") {
1257
+ return inspectWrappedCommand(resolveCommandWrapperCommand(command.args));
1258
+ }
1259
+ const evalScript = joinStaticWords(command.args);
1260
+ if (evalScript == null) {
1261
+ return true;
1262
+ }
1263
+ if (!evalScript.trim()) {
1264
+ return false;
1265
+ }
1266
+ return stringCommandContainsBlockedCommand(
1267
+ evalScript,
1268
+ context,
1269
+ "block-all-sql"
1270
+ );
1271
+ }
1272
+ if (SHELL_INTERPRETER_COMMANDS.has(normalizedName)) {
1273
+ const shellInvocation = getShellInvocationDescriptor(command.args);
1274
+ if (shellInvocation.kind === "unknown") {
1275
+ return true;
1276
+ }
1277
+ if (shellInvocation.kind === "command") {
1278
+ if (!shellInvocation.payload) {
1279
+ return true;
1280
+ }
1281
+ if (stringCommandContainsBlockedCommand(
1282
+ shellInvocation.payload,
1283
+ context,
1284
+ "block-all-sql"
1285
+ )) {
1286
+ return true;
1287
+ }
1288
+ return false;
1289
+ }
1290
+ const hereDoc = getHereDocPayload(command.redirections);
1291
+ if (hereDoc.hasHereDoc) {
1292
+ if (hereDoc.payload == null) {
1293
+ return true;
1294
+ }
1295
+ if (hereDoc.payload.trim().length > 0 && stringCommandContainsBlockedCommand(
1296
+ hereDoc.payload,
1297
+ context,
1298
+ "block-all-sql"
1299
+ )) {
1300
+ return true;
1301
+ }
1302
+ }
1303
+ if (shellInvocation.kind === "script") {
1304
+ return true;
1305
+ }
1306
+ if (options.stdinFromPipe || shellInvocation.kind === "stdin") {
1307
+ return !hereDoc.hasHereDoc;
1308
+ }
917
1309
  }
918
- if (functionInvocationContainsBlockedCommand(commandName, context)) {
1310
+ if (functionInvocationContainsBlockedCommand(commandName, context, mode)) {
919
1311
  return true;
920
1312
  }
921
1313
  return false;
@@ -1040,42 +1432,231 @@ import {
1040
1432
  defaultSettingsMiddleware,
1041
1433
  wrapLanguageModel
1042
1434
  } from "ai";
1435
+ import dedent2 from "dedent";
1043
1436
  import pRetry from "p-retry";
1044
1437
  import z4 from "zod";
1045
1438
  import "@deepagents/agent";
1046
1439
  import {
1047
1440
  ContextEngine as ContextEngine2,
1048
1441
  InMemoryContextStore as InMemoryContextStore2,
1442
+ example,
1443
+ fragment as fragment2,
1444
+ guardrail,
1445
+ hint as hint2,
1049
1446
  persona as persona3,
1447
+ policy,
1050
1448
  structuredOutput as structuredOutput2,
1051
- user as user2
1449
+ user as user2,
1450
+ workflow
1052
1451
  } from "@deepagents/context";
1053
- var RETRY_TEMPERATURES = [0, 0.2, 0.3];
1452
+ var RETRY_TEMPERATURES = [0, 0.4, 0.8];
1453
+ var SQL_AGENT_ROLE = "Expert SQL query generator.";
1454
+ var SQL_AGENT_OBJECTIVE = "Generate precise SQL grounded in provided schema.";
1455
+ var SQL_AGENT_POLICIES = [
1456
+ fragment2(
1457
+ "schema_mapping",
1458
+ policy({
1459
+ rule: "Translate natural language into precise SQL grounded in available schema entities."
1460
+ }),
1461
+ hint2("Preserve schema spelling exactly, including typos in column names.")
1462
+ ),
1463
+ fragment2(
1464
+ "projection_minimality",
1465
+ policy({
1466
+ rule: "Return only columns requested by the question; do not add helper columns unless explicitly requested."
1467
+ }),
1468
+ policy({
1469
+ rule: 'For requests of the form "X sorted/ordered by Y", project X only unless Y is explicitly requested as an output field.'
1470
+ }),
1471
+ policy({
1472
+ rule: "Prefer selecting schema columns directly without derived expressions when direct selection answers the request."
1473
+ }),
1474
+ hint2(
1475
+ "Do not include ORDER BY, GROUP BY, or JOIN helper columns in SELECT output unless the question explicitly asks for them."
1476
+ ),
1477
+ policy({
1478
+ rule: "Use DISTINCT only when uniqueness is explicitly requested (for example distinct/unique/different/no duplicates)."
1479
+ }),
1480
+ hint2(
1481
+ 'Do not infer DISTINCT from generic wording such as "some", plural nouns, or entity-set phrasing; for transactional/attendance-style tables, default to raw rows unless uniqueness is explicitly requested.'
1482
+ )
1483
+ ),
1484
+ fragment2(
1485
+ "date_transform_safety",
1486
+ policy({
1487
+ rule: "Do not assume VARCHAR/TEXT values are parseable dates. Avoid date extraction functions on text columns by default."
1488
+ }),
1489
+ policy({
1490
+ rule: "Use date-part extraction only when both conditions hold: the question explicitly asks for transformation and schema values require transformation to produce that unit."
1491
+ }),
1492
+ hint2(
1493
+ "Do not apply SUBSTR, STRFTIME, DATE_PART, YEAR, or similar extraction functions unless the question explicitly asks for transformation and schema values require it."
1494
+ ),
1495
+ hint2(
1496
+ "If a column already represents the requested concept (for example a stored year-like value), use the column as-is."
1497
+ )
1498
+ ),
1499
+ fragment2(
1500
+ "sql_minimality",
1501
+ guardrail({
1502
+ rule: "Never hallucinate tables or columns.",
1503
+ reason: "Schema fidelity is required.",
1504
+ action: "Use only available schema entities."
1505
+ }),
1506
+ guardrail({
1507
+ rule: "Avoid unnecessary transformations and derived projections.",
1508
+ reason: "Extra transformations frequently change semantics and reduce correctness.",
1509
+ action: "Do not add date parsing, substring extraction, or derived columns unless explicitly required by the question or schema."
1510
+ })
1511
+ ),
1512
+ fragment2(
1513
+ "preflight_checklist",
1514
+ workflow({
1515
+ task: "Final SQL preflight before returning output",
1516
+ steps: [
1517
+ "Verify selected columns match the question and remove unrequested helper projections.",
1518
+ "If aggregate values are used only for ranking/filtering, keep them out of SELECT unless explicitly requested.",
1519
+ "Prefer raw schema columns over derived expressions when raw columns already satisfy the request.",
1520
+ "If a candidate query uses STRFTIME, SUBSTR, DATE_PART, YEAR, or similar extraction on text-like columns, remove that transformation unless explicitly required by the question.",
1521
+ "Return only schema-grounded SQL using existing tables and columns."
1522
+ ]
1523
+ })
1524
+ ),
1525
+ fragment2(
1526
+ "set_semantics",
1527
+ policy({
1528
+ rule: "For questions asking where both condition A and condition B hold over an attribute, compute the intersection of qualifying sets for that attribute."
1529
+ }),
1530
+ policy({
1531
+ rule: "Do not force the same entity instance to satisfy both conditions unless the question explicitly requests the same person/row/entity."
1532
+ }),
1533
+ hint2(
1534
+ "Prefer INTERSECT (or logically equivalent set-based shape) over requiring the same physical row/entity to satisfy both conditions unless explicitly requested."
1535
+ ),
1536
+ hint2(
1537
+ "When two conditions describe different row groups whose shared attribute is requested, build each group separately and intersect the attribute values."
1538
+ ),
1539
+ hint2(
1540
+ "Do not collapse cross-group conditions into a single-row AND predicate when the intent is shared values across groups."
1541
+ ),
1542
+ policy({
1543
+ rule: "If two predicates on the same field cannot both be true for one row, do not combine them with AND; use set operations across separate filtered subsets when shared values are requested."
1544
+ })
1545
+ ),
1546
+ fragment2(
1547
+ "predicate_column_alignment",
1548
+ policy({
1549
+ rule: "Match literal values to semantically compatible columns. Do not compare descriptive names to identifier columns."
1550
+ }),
1551
+ hint2(
1552
+ "When a filter value is a descriptive label (for example a department name), join through the lookup table and filter on its name/title column, not on *_id columns."
1553
+ ),
1554
+ hint2(
1555
+ "When relation roles are explicit in wording (for example host/home/source/destination), prefer foreign keys with matching role qualifiers over generic similarly named columns."
1556
+ ),
1557
+ policy({
1558
+ rule: "When multiple foreign-key candidates exist, select the column whose qualifier best matches the relationship described in the question."
1559
+ }),
1560
+ policy({
1561
+ rule: "For hosting/held semantics, prefer host_* relationship columns when available over generic *_id alternatives."
1562
+ }),
1563
+ hint2(
1564
+ 'Interpret wording like "held/hosted a competition or event" as a hosting relationship and map to host_* foreign keys when present.'
1565
+ ),
1566
+ policy({
1567
+ rule: "Do not compare descriptive labels or names to *_id columns; join to the table containing the descriptive field and filter there."
1568
+ }),
1569
+ policy({
1570
+ rule: "Keep numeric identifiers unquoted when used as numeric equality filters unless schema indicates text identifiers."
1571
+ }),
1572
+ policy({
1573
+ rule: "When filtering by a descriptive label value and a related table exposes a corresponding *_name or title column, join to that table and filter on the descriptive column."
1574
+ })
1575
+ ),
1576
+ fragment2(
1577
+ "ordering_semantics",
1578
+ policy({
1579
+ rule: "Respect explicit sort direction terms. If direction is not specified, use ascending order unless a superlative intent (most/least/highest/lowest) implies direction."
1580
+ }),
1581
+ policy({
1582
+ rule: "When ranking categories by frequency, use COUNT for ordering but keep output focused on requested category fields unless counts are explicitly requested."
1583
+ }),
1584
+ policy({
1585
+ rule: "Do not use DESC unless descending direction is explicit or a superlative intent requires descending ranking."
1586
+ }),
1587
+ policy({
1588
+ rule: 'For "most common/frequent <attribute>" requests, return the attribute value(s) only; use counts only for ordering/filtering unless the question explicitly asks to return counts.'
1589
+ }),
1590
+ hint2(
1591
+ 'Use DESC with LIMIT 1 for "most/highest/largest"; use ASC with LIMIT 1 for "least/lowest/smallest".'
1592
+ )
1593
+ ),
1594
+ fragment2(
1595
+ "negative_membership_queries",
1596
+ policy({
1597
+ rule: "For requests asking entities that did not participate/host/appear in related records, prefer NOT IN or NOT EXISTS against the related foreign-key set."
1598
+ }),
1599
+ hint2(
1600
+ "Map role-bearing relationship columns carefully (for example host_* foreign keys for hosting relationships) instead of generic IDs when role wording is explicit."
1601
+ ),
1602
+ hint2(
1603
+ 'For "never had/never exceeded" conditions over history tables, exclude entities via NOT IN/NOT EXISTS against the disqualifying entity-id set (often built with GROUP BY/HAVING MAX(...)).'
1604
+ )
1605
+ ),
1606
+ fragment2(
1607
+ "join_completeness",
1608
+ policy({
1609
+ rule: "Preserve entity-restricting joins implied by the question. Do not widen results by querying only a broader attribute table when a subset entity table is available."
1610
+ }),
1611
+ policy({
1612
+ rule: "If an entity term in the question maps to a table, keep that table in query scope and join to attribute tables rather than dropping the entity table."
1613
+ }),
1614
+ hint2(
1615
+ "If the question targets a specific entity group, include that entity table and its join conditions even when selected columns come from a related table."
1616
+ ),
1617
+ hint2(
1618
+ "When the question names an entity type and a relation table links to that entity via *_id, include the entity table in scope instead of counting only relation rows."
1619
+ ),
1620
+ hint2(
1621
+ "Prefer INNER JOIN by default; use LEFT JOIN only when the question explicitly requests including unmatched rows or zero-related entities."
1622
+ )
1623
+ ),
1624
+ fragment2(
1625
+ "aggregation_exactness",
1626
+ policy({
1627
+ rule: "Preserve requested aggregation semantics exactly: use COUNT(*) by default for total rows, use COUNT(DISTINCT ...) only when uniqueness is explicitly requested, and group by stable entity keys when computing per-entity aggregates."
1628
+ }),
1629
+ policy({
1630
+ rule: "For questions asking which entity has lowest/highest average of a metric, compute AVG(metric) per entity (GROUP BY entity) and rank those aggregates."
1631
+ }),
1632
+ hint2(
1633
+ 'For "how many <entities>" questions over relation records, default to COUNT(*) on qualifying rows unless explicit uniqueness language is present.'
1634
+ )
1635
+ ),
1636
+ fragment2(
1637
+ "query_shape_examples",
1638
+ example({
1639
+ question: "List categories ordered by how many records belong to each category.",
1640
+ answer: "SELECT category FROM records GROUP BY category ORDER BY COUNT(*)"
1641
+ }),
1642
+ example({
1643
+ question: "Show labels shared by rows with metric > 100 and rows with metric < 10.",
1644
+ answer: "SELECT label FROM records WHERE metric > 100 INTERSECT SELECT label FROM records WHERE metric < 10"
1645
+ }),
1646
+ example({
1647
+ question: "List locations that have not hosted any event.",
1648
+ answer: "SELECT location_name FROM locations WHERE location_id NOT IN (SELECT host_location_id FROM events)"
1649
+ }),
1650
+ example({
1651
+ question: "List the most common category across records.",
1652
+ answer: "SELECT category FROM records GROUP BY category ORDER BY COUNT(*) DESC LIMIT 1"
1653
+ })
1654
+ )
1655
+ ];
1054
1656
  function extractSql(output) {
1055
1657
  const match = output.match(/```sql\n?([\s\S]*?)```/);
1056
1658
  return match ? match[1].trim() : output.trim();
1057
1659
  }
1058
- var marker = Symbol("SQLValidationError");
1059
- var SQLValidationError = class _SQLValidationError extends Error {
1060
- [marker];
1061
- constructor(message2) {
1062
- super(message2);
1063
- this.name = "SQLValidationError";
1064
- this[marker] = true;
1065
- }
1066
- static isInstance(error) {
1067
- return error instanceof _SQLValidationError && error[marker] === true;
1068
- }
1069
- };
1070
- var UnanswerableSQLError = class _UnanswerableSQLError extends Error {
1071
- constructor(message2) {
1072
- super(message2);
1073
- this.name = "UnanswerableSQLError";
1074
- }
1075
- static isInstance(error) {
1076
- return error instanceof _UnanswerableSQLError;
1077
- }
1078
- };
1079
1660
  async function toSql(options) {
1080
1661
  const { maxRetries = 3 } = options;
1081
1662
  return withRetry(
@@ -1088,20 +1669,38 @@ async function toSql(options) {
1088
1669
  context.set(
1089
1670
  persona3({
1090
1671
  name: "Freya",
1091
- role: "You are an expert SQL query generator. You translate natural language questions into precise, efficient SQL queries based on the provided database schema.",
1092
- objective: "Translate natural language questions into precise, efficient SQL queries"
1672
+ role: SQL_AGENT_ROLE,
1673
+ objective: SQL_AGENT_OBJECTIVE
1674
+ // role: `You are a data science expert that provides well-reasoned and detailed responses.`,
1675
+ // objective: `Your task is to understand the schema and generate a valid SQL query to answer the question. You first think about the reasoning process as an internal monologue and then provide the user with the answer.`,
1093
1676
  }),
1677
+ ...SQL_AGENT_POLICIES,
1094
1678
  ...options.fragments
1095
1679
  );
1096
1680
  if (errors.length) {
1681
+ const lastError = errors.at(-1);
1097
1682
  context.set(
1098
- user2(options.input),
1099
- user2(
1100
- `<validation_error>Your previous SQL query had the following error: ${errors.at(-1)?.message}. Please fix the query.</validation_error>`
1683
+ user2(dedent2`
1684
+ Answer the following question with the SQL code. Use the piece of evidence and base your answer on the database schema.
1685
+ Given the question, the evidence and the database schema, return the SQL script that addresses the question.
1686
+
1687
+ Question: ${options.input}
1688
+ `),
1689
+ UnanswerableSQLError.isInstance(lastError) ? user2(
1690
+ `<retry_instruction>Your previous response marked the task as unanswerable. Re-evaluate using best-effort schema mapping. If the core intent is answerable with existing tables/columns, return SQL. Return error only when required core intent cannot be mapped without inventing schema elements.</retry_instruction>`
1691
+ ) : user2(
1692
+ `<validation_error>Your previous SQL query had the following error: ${lastError?.message}. Please fix the query.</validation_error>`
1101
1693
  )
1102
1694
  );
1103
1695
  } else {
1104
- context.set(user2(options.input));
1696
+ context.set(
1697
+ user2(dedent2`
1698
+ Answer the following question with the SQL code. Use the piece of evidence and base your answer on the database schema.
1699
+ Given the question, the evidence and the database schema, return the SQL script that addresses the question.
1700
+
1701
+ Question: ${options.input}
1702
+ `)
1703
+ );
1105
1704
  }
1106
1705
  const temperature = RETRY_TEMPERATURES[attemptNumber - 1] ?? RETRY_TEMPERATURES[RETRY_TEMPERATURES.length - 1];
1107
1706
  const baseModel = options.model ?? groq2("openai/gpt-oss-20b");
@@ -1127,19 +1726,45 @@ async function toSql(options) {
1127
1726
  })
1128
1727
  });
1129
1728
  const { result: output } = await sqlOutput.generate();
1729
+ const finalizeSql = async (rawSql) => {
1730
+ const sql = options.adapter.format(extractSql(rawSql));
1731
+ const validationError = await options.adapter.validate(sql);
1732
+ if (validationError) {
1733
+ throw new SQLValidationError(validationError);
1734
+ }
1735
+ return {
1736
+ attempts,
1737
+ sql,
1738
+ errors: errors.length ? errors.map(formatErrorMessage) : void 0
1739
+ };
1740
+ };
1130
1741
  if ("error" in output) {
1131
- throw new UnanswerableSQLError(output.error);
1132
- }
1133
- const sql = options.adapter.format(extractSql(output.sql));
1134
- const validationError = await options.adapter.validate(sql);
1135
- if (validationError) {
1136
- throw new SQLValidationError(validationError);
1742
+ context.set(
1743
+ user2(
1744
+ "<best_effort_fallback>Do not return unanswerable. Produce the best valid SQL query that answers the core intent using only available schema entities.</best_effort_fallback>"
1745
+ )
1746
+ );
1747
+ const forcedSqlOutput = structuredOutput2({
1748
+ model,
1749
+ context,
1750
+ schema: z4.object({
1751
+ sql: z4.string().describe(
1752
+ "Best-effort SQL query that answers the core intent using only available schema entities."
1753
+ ),
1754
+ reasoning: z4.string().describe("Reasoning steps for best-effort schema mapping.")
1755
+ })
1756
+ });
1757
+ try {
1758
+ const forced = await forcedSqlOutput.generate();
1759
+ return await finalizeSql(forced.sql);
1760
+ } catch (error) {
1761
+ if (SQLValidationError.isInstance(error) || APICallError.isInstance(error) || JSONParseError.isInstance(error) || TypeValidationError.isInstance(error) || NoObjectGeneratedError.isInstance(error) || NoOutputGeneratedError.isInstance(error) || NoContentGeneratedError.isInstance(error)) {
1762
+ throw error;
1763
+ }
1764
+ throw new UnanswerableSQLError(output.error);
1765
+ }
1137
1766
  }
1138
- return {
1139
- attempts,
1140
- sql,
1141
- errors: errors.length ? errors.map(formatErrorMessage) : void 0
1142
- };
1767
+ return await finalizeSql(output.sql);
1143
1768
  },
1144
1769
  { retries: maxRetries - 1 }
1145
1770
  );
@@ -1202,9 +1827,6 @@ async function withRetry(computation, options = { retries: 3 }) {
1202
1827
  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);
1203
1828
  },
1204
1829
  onFailedAttempt(context) {
1205
- console.log(
1206
- `Attempt ${context.attemptNumber} failed. There are ${context.retriesLeft} retries left.`
1207
- );
1208
1830
  errors.push(context.error);
1209
1831
  }
1210
1832
  }
@@ -1213,7 +1835,7 @@ async function withRetry(computation, options = { retries: 3 }) {
1213
1835
 
1214
1836
  // packages/text2sql/src/lib/agents/suggestions.agents.ts
1215
1837
  import { groq as groq3 } from "@ai-sdk/groq";
1216
- import dedent2 from "dedent";
1838
+ import dedent3 from "dedent";
1217
1839
  import z5 from "zod";
1218
1840
  import { agent, thirdPersonPrompt } from "@deepagents/agent";
1219
1841
  var suggestionsAgent = agent({
@@ -1229,7 +1851,7 @@ var suggestionsAgent = agent({
1229
1851
  ).min(1).max(5).describe("A set of up to two advanced question + SQL pairs.")
1230
1852
  }),
1231
1853
  prompt: (state) => {
1232
- return dedent2`
1854
+ return dedent3`
1233
1855
  ${thirdPersonPrompt()}
1234
1856
 
1235
1857
  <identity>
@@ -4193,26 +4815,26 @@ var TrackedFs = class {
4193
4815
  // packages/text2sql/src/lib/instructions.ts
4194
4816
  import {
4195
4817
  clarification,
4196
- example,
4818
+ example as example2,
4197
4819
  explain,
4198
- fragment as fragment2,
4199
- guardrail,
4200
- hint as hint2,
4201
- policy,
4820
+ fragment as fragment3,
4821
+ guardrail as guardrail2,
4822
+ hint as hint3,
4823
+ policy as policy2,
4202
4824
  principle,
4203
4825
  quirk,
4204
4826
  role,
4205
4827
  styleGuide,
4206
- workflow
4828
+ workflow as workflow2
4207
4829
  } from "@deepagents/context";
4208
4830
  function reasoningFramework() {
4209
4831
  return [
4210
4832
  role(
4211
4833
  "You are a very strong reasoner and planner. Use these critical instructions to structure your plans, thoughts, and responses."
4212
4834
  ),
4213
- fragment2(
4835
+ fragment3(
4214
4836
  "meta-cognitive-reasoning-framework",
4215
- hint2(
4837
+ hint3(
4216
4838
  "Before taking any action (either tool calls *or* responses to the user), you must proactively, methodically, and independently plan and reason about:"
4217
4839
  ),
4218
4840
  // 1) Logical dependencies and constraints
@@ -4220,19 +4842,19 @@ function reasoningFramework() {
4220
4842
  title: "Logical dependencies and constraints",
4221
4843
  description: "Analyze the intended action against the following factors. Resolve conflicts in order of importance:",
4222
4844
  policies: [
4223
- policy({
4845
+ policy2({
4224
4846
  rule: "Policy-based rules, mandatory prerequisites, and constraints."
4225
4847
  }),
4226
- policy({
4848
+ policy2({
4227
4849
  rule: "Order of operations: Ensure taking an action does not prevent a subsequent necessary action.",
4228
4850
  policies: [
4229
4851
  "The user may request actions in a random order, but you may need to reorder operations to maximize successful completion of the task."
4230
4852
  ]
4231
4853
  }),
4232
- policy({
4854
+ policy2({
4233
4855
  rule: "Other prerequisites (information and/or actions needed)."
4234
4856
  }),
4235
- policy({ rule: "Explicit user constraints or preferences." })
4857
+ policy2({ rule: "Explicit user constraints or preferences." })
4236
4858
  ]
4237
4859
  }),
4238
4860
  // 2) Risk assessment
@@ -4285,17 +4907,17 @@ function reasoningFramework() {
4285
4907
  title: "Completeness",
4286
4908
  description: "Ensure that all requirements, constraints, options, and preferences are exhaustively incorporated into your plan.",
4287
4909
  policies: [
4288
- policy({
4910
+ policy2({
4289
4911
  rule: "Resolve conflicts using the order of importance in #1."
4290
4912
  }),
4291
- policy({
4913
+ policy2({
4292
4914
  rule: "Avoid premature conclusions: There may be multiple relevant options for a given situation.",
4293
4915
  policies: [
4294
4916
  "To check for whether an option is relevant, reason about all information sources from #5.",
4295
4917
  "You may need to consult the user to even know whether something is applicable. Do not assume it is not applicable without checking."
4296
4918
  ]
4297
4919
  }),
4298
- policy({
4920
+ policy2({
4299
4921
  rule: "Review applicable sources of information from #5 to confirm which are relevant to the current state."
4300
4922
  })
4301
4923
  ]
@@ -4327,33 +4949,33 @@ function guidelines(options = {}) {
4327
4949
  // Include the meta-cognitive reasoning framework
4328
4950
  ...reasoningFramework(),
4329
4951
  // Prerequisite policies (must do X before Y)
4330
- fragment2(
4952
+ fragment3(
4331
4953
  "prerequisite_policies",
4332
- policy({
4954
+ policy2({
4333
4955
  rule: "YOU MUST inspect schema structure and available tables",
4334
4956
  before: "generating ANY SQL query",
4335
4957
  reason: "NEVER generate SQL without knowing valid tables, columns, and relationships"
4336
4958
  }),
4337
- policy({
4959
+ policy2({
4338
4960
  rule: "YOU MUST resolve ambiguous business terms with the user",
4339
4961
  before: "making ANY assumptions about terminology meaning",
4340
4962
  reason: "NEVER guess domain-specific language\u2014ask for clarification"
4341
4963
  }),
4342
- policy({
4964
+ policy2({
4343
4965
  rule: "YOU MUST validate SQL syntax",
4344
4966
  before: "executing ANY query against the database",
4345
4967
  reason: "NEVER execute unvalidated queries"
4346
4968
  }),
4347
- policy({
4969
+ policy2({
4348
4970
  rule: "YOU MUST complete ALL reasoning steps",
4349
4971
  before: "taking ANY tool call or response action",
4350
4972
  reason: "Once an action is taken, it CANNOT be undone. NO EXCEPTIONS."
4351
4973
  })
4352
4974
  ),
4353
4975
  // Few-shot: Applying reasoning principles
4354
- fragment2(
4976
+ fragment3(
4355
4977
  "reasoning-examples",
4356
- example({
4978
+ example2({
4357
4979
  question: "Show me sales last month",
4358
4980
  answer: `Applying Principle 1 (Logical dependencies):
4359
4981
  - Need: schema to know which table has sales data
@@ -4365,7 +4987,7 @@ Applying Principle 5 (Information availability):
4365
4987
 
4366
4988
  Action: Ask user for date range clarification BEFORE generating SQL.`
4367
4989
  }),
4368
- example({
4990
+ example2({
4369
4991
  question: "Why did my query return no results?",
4370
4992
  answer: `Applying Principle 3 (Abductive reasoning):
4371
4993
  - Hypothesis 1 (most likely): Filter too restrictive
@@ -4379,7 +5001,7 @@ Testing hypotheses:
4379
5001
 
4380
5002
  Action: Start with most likely hypothesis, test incrementally. NEVER guess.`
4381
5003
  }),
4382
- example({
5004
+ example2({
4383
5005
  question: "Get me the top customers",
4384
5006
  answer: `Applying Principle 1 (Logical dependencies):
4385
5007
  - "Top" is ambiguous\u2014by revenue? by order count? by recency?
@@ -4391,16 +5013,16 @@ Action: Ask user: "Top by what metric\u2014total revenue, number of orders, or m
4391
5013
  })
4392
5014
  ),
4393
5015
  // Schema adherence - consolidated into clear rules
4394
- fragment2(
5016
+ fragment3(
4395
5017
  "schema_adherence",
4396
- hint2(
5018
+ hint3(
4397
5019
  "Use only tables and columns from the schema. For unspecified columns, use SELECT *. When showing related items, include IDs and requested details."
4398
5020
  ),
4399
- hint2(
5021
+ hint3(
4400
5022
  '"Show" means list items; "count" or "total" means aggregate. Use canonical values verbatim for filtering.'
4401
5023
  )
4402
5024
  ),
4403
- fragment2(
5025
+ fragment3(
4404
5026
  "Column statistics",
4405
5027
  explain({
4406
5028
  concept: "nDistinct in column stats",
@@ -4412,18 +5034,18 @@ Action: Ask user: "Top by what metric\u2014total revenue, number of orders, or m
4412
5034
  explanation: "Measures how closely the physical row order matches the logical sort order of the column. Values near 1 or -1 mean the data is well-ordered; near 0 means scattered",
4413
5035
  therefore: "High correlation means range queries (BETWEEN, >, <) on that column benefit from index scans. Low correlation means the index is less effective for ranges"
4414
5036
  }),
4415
- hint2(
5037
+ hint3(
4416
5038
  "When min/max stats are available, use them to validate filter values. If a user asks for values outside the known range, warn them the query may return no results."
4417
5039
  )
4418
5040
  ),
4419
5041
  // Joins - use relationship metadata
4420
- hint2(
5042
+ hint3(
4421
5043
  "Use JOINs based on schema relationships. Favor PK/indexed columns; follow relationship metadata for direction and cardinality."
4422
5044
  ),
4423
5045
  // Aggregations - explain the concepts
4424
- fragment2(
5046
+ fragment3(
4425
5047
  "Aggregations",
4426
- hint2(
5048
+ hint3(
4427
5049
  "Apply COUNT, SUM, AVG when the question implies summarization. Use window functions for ranking, running totals, or row comparisons."
4428
5050
  ),
4429
5051
  explain({
@@ -4433,7 +5055,7 @@ Action: Ask user: "Top by what metric\u2014total revenue, number of orders, or m
4433
5055
  })
4434
5056
  ),
4435
5057
  // Query semantics - explain concepts and document quirks
4436
- fragment2(
5058
+ fragment3(
4437
5059
  "Query interpretation",
4438
5060
  explain({
4439
5061
  concept: "threshold language",
@@ -4448,7 +5070,7 @@ Action: Ask user: "Top by what metric\u2014total revenue, number of orders, or m
4448
5070
  issue: "NULL values behave unexpectedly in comparisons and aggregations",
4449
5071
  workaround: "Use IS NULL, IS NOT NULL, or COALESCE() to handle NULLs explicitly"
4450
5072
  }),
4451
- hint2(
5073
+ hint3(
4452
5074
  "Always include mentioned filters from joined tables in WHERE conditions."
4453
5075
  )
4454
5076
  ),
@@ -4461,24 +5083,24 @@ Action: Ask user: "Top by what metric\u2014total revenue, number of orders, or m
4461
5083
  prefer: "Concise, business-friendly summaries with key comparisons and helpful follow-ups."
4462
5084
  }),
4463
5085
  // Safety guardrails - consolidated
4464
- fragment2(
5086
+ fragment3(
4465
5087
  "Query safety",
4466
- guardrail({
5088
+ guardrail2({
4467
5089
  rule: "Generate only valid, executable SELECT/WITH statements.",
4468
5090
  reason: "Read-only access prevents data modification.",
4469
5091
  action: "Never generate INSERT, UPDATE, DELETE, DROP, or DDL statements."
4470
5092
  }),
4471
- guardrail({
5093
+ guardrail2({
4472
5094
  rule: "Avoid unbounded scans and cartesian joins.",
4473
5095
  reason: "Protects performance and correctness.",
4474
5096
  action: "Apply filters on indexed columns. If join keys are unclear, ask for clarification."
4475
5097
  }),
4476
- guardrail({
5098
+ guardrail2({
4477
5099
  rule: "Preserve query semantics.",
4478
5100
  reason: "Arbitrary modifications change results.",
4479
5101
  action: 'Only add LIMIT for explicit "top N" requests. Add ORDER BY for deterministic results.'
4480
5102
  }),
4481
- guardrail({
5103
+ guardrail2({
4482
5104
  rule: "Seek clarification for genuine ambiguity.",
4483
5105
  reason: "Prevents incorrect assumptions.",
4484
5106
  action: "Ask a focused question before guessing."
@@ -4489,10 +5111,10 @@ Action: Ask user: "Top by what metric\u2014total revenue, number of orders, or m
4489
5111
  ask: "Clarify the ranking metric or definition.",
4490
5112
  reason: "Ensures correct aggregation and ordering."
4491
5113
  }),
4492
- hint2(
5114
+ hint3(
4493
5115
  'Use sample cell values from schema hints to match exact casing and format in WHERE conditions (e.g., "Male" vs "male" vs "M").'
4494
5116
  ),
4495
- workflow({
5117
+ workflow2({
4496
5118
  task: "SQL generation",
4497
5119
  steps: [
4498
5120
  "Schema linking: identify which tables and columns are mentioned or implied by the question.",
@@ -4504,7 +5126,7 @@ Action: Ask user: "Top by what metric\u2014total revenue, number of orders, or m
4504
5126
  "Verify: mentally translate SQL back to natural language. Does it match the original question?"
4505
5127
  ]
4506
5128
  }),
4507
- workflow({
5129
+ workflow2({
4508
5130
  task: "Error recovery",
4509
5131
  triggers: ["SQL error", "query failed", "execution error"],
4510
5132
  steps: [
@@ -4517,7 +5139,7 @@ Action: Ask user: "Top by what metric\u2014total revenue, number of orders, or m
4517
5139
  ],
4518
5140
  notes: "Maximum 3 retry attempts. If still failing, explain the issue to the user."
4519
5141
  }),
4520
- workflow({
5142
+ workflow2({
4521
5143
  task: "Complex query decomposition",
4522
5144
  triggers: [
4523
5145
  "multiple conditions",
@@ -4534,7 +5156,7 @@ Action: Ask user: "Top by what metric\u2014total revenue, number of orders, or m
4534
5156
  ],
4535
5157
  notes: "Complex questions often need CTEs (WITH clauses) for clarity and reusability."
4536
5158
  }),
4537
- workflow({
5159
+ workflow2({
4538
5160
  task: "Multi-turn context",
4539
5161
  triggers: ["follow-up", "and also", "what about", "same but", "instead"],
4540
5162
  steps: [
@@ -4547,9 +5169,9 @@ Action: Ask user: "Top by what metric\u2014total revenue, number of orders, or m
4547
5169
  ],
4548
5170
  notes: "If reference is ambiguous, ask which previous result or entity the user means."
4549
5171
  }),
4550
- fragment2(
5172
+ fragment3(
4551
5173
  "Bash tool usage",
4552
- workflow({
5174
+ workflow2({
4553
5175
  task: "Query execution",
4554
5176
  steps: [
4555
5177
  'Execute SQL through bash tool: sql run "SELECT ..."',
@@ -4558,16 +5180,16 @@ Action: Ask user: "Top by what metric\u2014total revenue, number of orders, or m
4558
5180
  "For large results, slice first: cat <path> | jq '.[:10]'"
4559
5181
  ]
4560
5182
  }),
4561
- hint2(
5183
+ hint3(
4562
5184
  `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.`
4563
5185
  ),
4564
- hint2(
5186
+ hint3(
4565
5187
  "The sql command outputs: file path, column names (comma-separated), and row count. Use column names to construct precise jq queries."
4566
5188
  ),
4567
- hint2(
5189
+ hint3(
4568
5190
  'This is virtual bash environment and "sql" commands proxy to the database hence you cannot access sql files directly.'
4569
5191
  ),
4570
- hint2(
5192
+ hint3(
4571
5193
  "If a query fails, the sql command returns an error message in stderr."
4572
5194
  )
4573
5195
  )
@@ -4582,7 +5204,7 @@ Action: Ask user: "Top by what metric\u2014total revenue, number of orders, or m
4582
5204
  );
4583
5205
  } else {
4584
5206
  baseTeachings.push(
4585
- hint2(
5207
+ hint3(
4586
5208
  'When a month, day, or time period is mentioned without a year (e.g., "in August", "on Monday"), assume ALL occurrences of that period in the data. Do not ask for year clarification.'
4587
5209
  )
4588
5210
  );