@deepagents/text2sql 0.18.0 → 0.19.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
@@ -519,7 +519,8 @@ import {
519
519
  Bash,
520
520
  MountableFs,
521
521
  OverlayFs,
522
- defineCommand
522
+ defineCommand,
523
+ parse
523
524
  } from "just-bash";
524
525
  import { AsyncLocalStorage } from "node:async_hooks";
525
526
  import * as path from "node:path";
@@ -650,6 +651,300 @@ function createSqlCommand(adapter, metaStore) {
650
651
  }
651
652
  });
652
653
  }
654
+ var BLOCKED_DB_CLIENT_COMMANDS = /* @__PURE__ */ new Set([
655
+ "psql",
656
+ "sqlite3",
657
+ "mysql",
658
+ "duckdb"
659
+ ]);
660
+ var BLOCKED_RAW_SQL_COMMANDS = /* @__PURE__ */ new Set(["select", "with"]);
661
+ var ALLOWED_SQL_PROXY_SUBCOMMANDS = /* @__PURE__ */ new Set(["run", "validate"]);
662
+ var SQL_PROXY_ENFORCEMENT_MESSAGE = [
663
+ "Direct database querying through bash is blocked.",
664
+ "Use SQL proxy commands in this order:",
665
+ '1) sql validate "SELECT ..."',
666
+ '2) sql run "SELECT ..."'
667
+ ].join("\n");
668
+ function cloneInspectionContext(context) {
669
+ return {
670
+ functionDefinitions: new Map(context.functionDefinitions),
671
+ callStack: new Set(context.callStack)
672
+ };
673
+ }
674
+ function asStaticWordText(word) {
675
+ if (!word) {
676
+ return null;
677
+ }
678
+ return asStaticWordPartText(
679
+ word.parts
680
+ );
681
+ }
682
+ function asStaticWordPartText(parts) {
683
+ let text = "";
684
+ for (const part of parts) {
685
+ const type = part.type;
686
+ if (type === "Literal" || type === "SingleQuoted" || type === "Escaped") {
687
+ if (typeof part.value !== "string") {
688
+ return null;
689
+ }
690
+ text += part.value;
691
+ continue;
692
+ }
693
+ if (type === "DoubleQuoted") {
694
+ if (!Array.isArray(part.parts)) {
695
+ return null;
696
+ }
697
+ const inner = asStaticWordPartText(
698
+ part.parts
699
+ );
700
+ if (inner == null) {
701
+ return null;
702
+ }
703
+ text += inner;
704
+ continue;
705
+ }
706
+ return null;
707
+ }
708
+ return text;
709
+ }
710
+ function isScriptNode(value) {
711
+ if (typeof value !== "object" || value === null) {
712
+ return false;
713
+ }
714
+ const node = value;
715
+ return node.type === "Script" && Array.isArray(node.statements);
716
+ }
717
+ function scriptContainsBlockedCommand(script, context) {
718
+ return statementsContainBlockedCommand(script.statements, context);
719
+ }
720
+ function statementsContainBlockedCommand(statements, context) {
721
+ for (const statement of statements) {
722
+ if (statementContainsBlockedCommand(statement, context)) {
723
+ return true;
724
+ }
725
+ }
726
+ return false;
727
+ }
728
+ function statementContainsBlockedCommand(statement, context) {
729
+ for (const pipeline of statement.pipelines) {
730
+ if (pipelineContainsBlockedCommand(pipeline, context)) {
731
+ return true;
732
+ }
733
+ }
734
+ return false;
735
+ }
736
+ function pipelineContainsBlockedCommand(pipeline, context) {
737
+ for (const command of pipeline.commands) {
738
+ if (command.type === "FunctionDef") {
739
+ context.functionDefinitions.set(command.name, command);
740
+ continue;
741
+ }
742
+ if (commandContainsBlockedCommand(command, context)) {
743
+ return true;
744
+ }
745
+ }
746
+ return false;
747
+ }
748
+ function stringCommandContainsBlockedCommand(command, context) {
749
+ let script;
750
+ try {
751
+ script = parse(command);
752
+ } catch {
753
+ return false;
754
+ }
755
+ return scriptContainsBlockedCommand(script, cloneInspectionContext(context));
756
+ }
757
+ function wordContainsBlockedCommand(word, context) {
758
+ if (!word) {
759
+ return false;
760
+ }
761
+ return wordPartContainsBlockedCommand(
762
+ word.parts,
763
+ context
764
+ );
765
+ }
766
+ function wordPartContainsBlockedCommand(parts, context) {
767
+ for (const part of parts) {
768
+ if (partContainsBlockedCommand(part, context)) {
769
+ return true;
770
+ }
771
+ }
772
+ return false;
773
+ }
774
+ function partContainsBlockedCommand(node, context) {
775
+ const type = node.type;
776
+ if (type === "CommandSubstitution" || type === "ProcessSubstitution") {
777
+ if (isScriptNode(node.body)) {
778
+ return scriptContainsBlockedCommand(
779
+ node.body,
780
+ cloneInspectionContext(context)
781
+ );
782
+ }
783
+ return false;
784
+ }
785
+ if (type === "ArithCommandSubst" && typeof node.command === "string") {
786
+ return stringCommandContainsBlockedCommand(node.command, context);
787
+ }
788
+ for (const value of Object.values(node)) {
789
+ if (Array.isArray(value)) {
790
+ for (const item of value) {
791
+ if (typeof item === "object" && item !== null) {
792
+ if (partContainsBlockedCommand(item, context)) {
793
+ return true;
794
+ }
795
+ }
796
+ }
797
+ continue;
798
+ }
799
+ if (typeof value === "object" && value !== null) {
800
+ if (partContainsBlockedCommand(value, context)) {
801
+ return true;
802
+ }
803
+ }
804
+ }
805
+ return false;
806
+ }
807
+ function functionInvocationContainsBlockedCommand(functionName, context) {
808
+ const definition = context.functionDefinitions.get(functionName);
809
+ if (!definition) {
810
+ return false;
811
+ }
812
+ if (context.callStack.has(functionName)) {
813
+ return false;
814
+ }
815
+ const invocationContext = cloneInspectionContext(context);
816
+ invocationContext.callStack.add(functionName);
817
+ return commandContainsBlockedCommand(definition.body, invocationContext);
818
+ }
819
+ function commandContainsBlockedCommand(command, context) {
820
+ switch (command.type) {
821
+ case "SimpleCommand":
822
+ return isBlockedSimpleCommand(command, context);
823
+ case "If":
824
+ return command.clauses.some(
825
+ (clause) => statementsContainBlockedCommand(
826
+ clause.condition,
827
+ cloneInspectionContext(context)
828
+ ) || statementsContainBlockedCommand(
829
+ clause.body,
830
+ cloneInspectionContext(context)
831
+ )
832
+ ) || (command.elseBody ? statementsContainBlockedCommand(
833
+ command.elseBody,
834
+ cloneInspectionContext(context)
835
+ ) : false);
836
+ case "For":
837
+ case "CStyleFor":
838
+ return statementsContainBlockedCommand(
839
+ command.body,
840
+ cloneInspectionContext(context)
841
+ );
842
+ case "While":
843
+ case "Until":
844
+ return statementsContainBlockedCommand(
845
+ command.condition,
846
+ cloneInspectionContext(context)
847
+ ) || statementsContainBlockedCommand(
848
+ command.body,
849
+ cloneInspectionContext(context)
850
+ );
851
+ case "Case":
852
+ return command.items.some(
853
+ (item) => statementsContainBlockedCommand(
854
+ item.body,
855
+ cloneInspectionContext(context)
856
+ )
857
+ );
858
+ case "Subshell":
859
+ case "Group":
860
+ return statementsContainBlockedCommand(
861
+ command.body,
862
+ cloneInspectionContext(context)
863
+ );
864
+ case "FunctionDef":
865
+ return false;
866
+ case "ArithmeticCommand":
867
+ case "ConditionalCommand":
868
+ return false;
869
+ default: {
870
+ const _unreachable = command;
871
+ return _unreachable;
872
+ }
873
+ }
874
+ }
875
+ function isBlockedSimpleCommand(command, context) {
876
+ if (wordContainsBlockedCommand(command.name, context)) {
877
+ return true;
878
+ }
879
+ if (command.args.some((arg) => wordContainsBlockedCommand(arg, context))) {
880
+ return true;
881
+ }
882
+ if (command.assignments.some(
883
+ (assignment) => wordContainsBlockedCommand(assignment.value, context) || (assignment.array?.some(
884
+ (value) => wordContainsBlockedCommand(value, context)
885
+ ) ?? false)
886
+ )) {
887
+ return true;
888
+ }
889
+ if (command.redirections.some((redirection) => {
890
+ if (redirection.target.type === "Word") {
891
+ return wordContainsBlockedCommand(
892
+ redirection.target,
893
+ context
894
+ );
895
+ }
896
+ if (redirection.target.type === "HereDoc" && redirection.target.content) {
897
+ return wordContainsBlockedCommand(redirection.target.content, context);
898
+ }
899
+ return false;
900
+ })) {
901
+ return true;
902
+ }
903
+ const commandName = asStaticWordText(command.name);
904
+ if (!commandName) {
905
+ return false;
906
+ }
907
+ const normalizedName = path.posix.basename(commandName).toLowerCase();
908
+ if (BLOCKED_DB_CLIENT_COMMANDS.has(normalizedName)) {
909
+ return true;
910
+ }
911
+ if (BLOCKED_RAW_SQL_COMMANDS.has(normalizedName)) {
912
+ return true;
913
+ }
914
+ if (normalizedName === "sql") {
915
+ const subcommand = asStaticWordText(command.args[0])?.toLowerCase();
916
+ return !subcommand || !ALLOWED_SQL_PROXY_SUBCOMMANDS.has(subcommand);
917
+ }
918
+ if (functionInvocationContainsBlockedCommand(commandName, context)) {
919
+ return true;
920
+ }
921
+ return false;
922
+ }
923
+ function getSqlProxyEnforcementResult(command) {
924
+ const trimmed = command.trim();
925
+ if (!trimmed) {
926
+ return null;
927
+ }
928
+ let script;
929
+ try {
930
+ script = parse(trimmed);
931
+ } catch {
932
+ return null;
933
+ }
934
+ const blocked = scriptContainsBlockedCommand(script, {
935
+ functionDefinitions: /* @__PURE__ */ new Map(),
936
+ callStack: /* @__PURE__ */ new Set()
937
+ });
938
+ if (!blocked) {
939
+ return null;
940
+ }
941
+ return {
942
+ stdout: "",
943
+ stderr: `${SQL_PROXY_ENFORCEMENT_MESSAGE}
944
+ `,
945
+ exitCode: 1
946
+ };
947
+ }
653
948
  async function createResultTools(options) {
654
949
  const { adapter, skillMounts, filesystem: baseFs } = options;
655
950
  const metaStore = new AsyncLocalStorage();
@@ -685,6 +980,16 @@ async function createResultTools(options) {
685
980
  return { result };
686
981
  }
687
982
  });
983
+ const guardedSandbox = {
984
+ ...sandbox,
985
+ executeCommand: async (command) => {
986
+ const blockedResult = getSqlProxyEnforcementResult(command);
987
+ if (blockedResult) {
988
+ return blockedResult;
989
+ }
990
+ return sandbox.executeCommand(command);
991
+ }
992
+ };
688
993
  const bash = tool2({
689
994
  ...tools2.bash,
690
995
  inputSchema: z3.object({
@@ -696,6 +1001,10 @@ async function createResultTools(options) {
696
1001
  if (!execute) {
697
1002
  throw new Error("bash tool execution is not available");
698
1003
  }
1004
+ const blockedResult = getSqlProxyEnforcementResult(command);
1005
+ if (blockedResult) {
1006
+ return blockedResult;
1007
+ }
699
1008
  return metaStore.run({}, async () => {
700
1009
  const result = await execute({ command }, execOptions);
701
1010
  const meta = metaStore.getStore()?.value;
@@ -703,12 +1012,15 @@ async function createResultTools(options) {
703
1012
  });
704
1013
  },
705
1014
  toModelOutput: ({ output }) => {
1015
+ if (typeof output !== "object" || output === null) {
1016
+ return { type: "json", value: output };
1017
+ }
706
1018
  const { meta, ...rest } = output;
707
1019
  return { type: "json", value: rest };
708
1020
  }
709
1021
  });
710
1022
  return {
711
- sandbox,
1023
+ sandbox: guardedSandbox,
712
1024
  tools: {
713
1025
  ...tools2,
714
1026
  bash
@@ -779,8 +1091,7 @@ async function toSql(options) {
779
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.",
780
1092
  objective: "Translate natural language questions into precise, efficient SQL queries"
781
1093
  }),
782
- ...options.instructions,
783
- ...options.schemaFragments
1094
+ ...options.fragments
784
1095
  );
785
1096
  if (errors.length) {
786
1097
  context.set(
@@ -4317,8 +4628,7 @@ var Text2Sql = class {
4317
4628
  const result = await toSql({
4318
4629
  input,
4319
4630
  adapter: this.#config.adapter,
4320
- schemaFragments,
4321
- instructions: [],
4631
+ fragments: schemaFragments,
4322
4632
  model: this.#config.model
4323
4633
  });
4324
4634
  return result.sql;