@deepagents/text2sql 4.0.0 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -114,7 +114,11 @@ import {
114
114
  npm,
115
115
  user,
116
116
  } from '@deepagents/context';
117
- import { createSqlCommandHooks, instructions } from '@deepagents/text2sql';
117
+ import {
118
+ createSqlCommandHooks,
119
+ instructions,
120
+ sqlValidateReminder,
121
+ } from '@deepagents/text2sql';
118
122
 
119
123
  const store = new InMemoryContextStore();
120
124
  const context = new ContextEngine({
@@ -149,6 +153,7 @@ const fragments = JSON.parse(
149
153
  await sandbox.sandbox.readFile(manifest.fragmentsPath),
150
154
  ) as ContextFragment[];
151
155
  context.set(...instructions(), ...fragments);
156
+ context.set(sqlValidateReminder());
152
157
 
153
158
  const ai = agent({
154
159
  name: 'sql-assistant',
@@ -178,6 +183,11 @@ schema fragments returned by `sql index`. The index output starts with an
178
183
  model must pass to `sql validate <db>` and `sql run <db>`. Add or replace the
179
184
  instructions with your own domain fragments as needed.
180
185
 
186
+ `sqlValidateReminder()` returns a context reminder fragment backed by
187
+ `SQL_VALIDATE_REMINDER`. It adds a tool-output system reminder when the current
188
+ assistant turn calls `sql run <db> "..."` without a prior `sql validate <db>
189
+ "..."` for the same configured adapter name and exact SQL text.
190
+
181
191
  ## Advanced: SQL CLI in Sandboxes
182
192
 
183
193
  `sql validate <db> "..."` and `sql run <db> "..."` are real commands from the
package/dist/bin/sql.js CHANGED
@@ -896,6 +896,25 @@ var Text2SqlUnknownAdapterError = class _Text2SqlUnknownAdapterError extends Err
896
896
  return error instanceof _Text2SqlUnknownAdapterError && error[text2SqlUnknownAdapterMarker] === true;
897
897
  }
898
898
  };
899
+ var text2SqlUnknownDatabaseMarker = Symbol("Text2SqlUnknownDatabaseError");
900
+ var Text2SqlUnknownDatabaseError = class _Text2SqlUnknownDatabaseError extends Error {
901
+ [text2SqlUnknownDatabaseMarker];
902
+ requested;
903
+ available;
904
+ constructor(requested, available) {
905
+ const list = available.join(", ") || "(none configured)";
906
+ super(
907
+ requested ? `unknown database "${requested}". Available: ${list}` : `no database specified. Available: ${list}`
908
+ );
909
+ this.name = "Text2SqlUnknownDatabaseError";
910
+ this.requested = requested;
911
+ this.available = available;
912
+ this[text2SqlUnknownDatabaseMarker] = true;
913
+ }
914
+ static isInstance(error) {
915
+ return error instanceof _Text2SqlUnknownDatabaseError && error[text2SqlUnknownDatabaseMarker] === true;
916
+ }
917
+ };
899
918
  var Text2Sql = class {
900
919
  #config;
901
920
  #indexer;
@@ -932,6 +951,29 @@ var Text2Sql = class {
932
951
  hasAdapter(name) {
933
952
  return name in this.#config.adapters;
934
953
  }
954
+ /**
955
+ * Resolve a user-supplied `<db>` token to a configured adapter name for the
956
+ * surfaces that accept one (CLI, sandbox `sql` command, JSON-RPC daemon, MCP):
957
+ * - exact match → that name
958
+ * - no match (or omitted) but exactly one adapter configured → route to it
959
+ * - otherwise → throw with the user-facing "unknown database" message
960
+ *
961
+ * Internal execution paths keep using the strict {@link Text2Sql.run}/
962
+ * {@link Text2Sql.validate}, which reject an unknown name outright.
963
+ */
964
+ resolveAdapterName(db) {
965
+ if (!db || db.trim() === "") {
966
+ const names2 = this.adapterNames();
967
+ if (names2.length === 1) return names2[0];
968
+ throw new Text2SqlUnknownDatabaseError(db, names2);
969
+ }
970
+ if (this.hasAdapter(db)) {
971
+ return db;
972
+ }
973
+ const names = this.adapterNames();
974
+ if (names.length === 1) return names[0];
975
+ throw new Text2SqlUnknownDatabaseError(db, names);
976
+ }
935
977
  async validate(name, sql) {
936
978
  const adapter = this.#requireAdapter(name);
937
979
  const result = this.#validateWith(adapter, sql);
@@ -980,17 +1022,6 @@ var Text2Sql = class {
980
1022
 
981
1023
  // packages/text2sql/src/bin/command.ts
982
1024
  import * as path3 from "node:path";
983
-
984
- // packages/text2sql/src/lib/resolve-adapter.ts
985
- function resolveAdapter(text2Sql, db) {
986
- if (text2Sql.hasAdapter(db)) return db;
987
- const names = text2Sql.adapterNames();
988
- if (names.length === 1) return names[0];
989
- const available = names.join(", ") || "(none configured)";
990
- throw new Error(`unknown database "${db}". Available: ${available}`);
991
- }
992
-
993
- // packages/text2sql/src/bin/command.ts
994
1025
  var OUT_DIR_OPTION = {
995
1026
  flag: "--out-dir <path>",
996
1027
  description: "Output directory (default: $TEXT2SQL_OUT_DIR or ./sql)"
@@ -1023,14 +1054,8 @@ var SqlCommand = class {
1023
1054
  throw new CommandError(this.name, message, exitCode);
1024
1055
  }
1025
1056
  resolveAdapterName(text2Sql, db) {
1026
- if (!db) {
1027
- const available = text2Sql.adapterNames().join(", ") || "(none configured)";
1028
- this.fail(
1029
- `missing database name. Usage: sql ${this.name} ${this.usage}. Available: ${available}`
1030
- );
1031
- }
1032
1057
  try {
1033
- return resolveAdapter(text2Sql, db);
1058
+ return text2Sql.resolveAdapterName(db);
1034
1059
  } catch (error) {
1035
1060
  this.fail(errorMessage2(error));
1036
1061
  }
package/dist/index.d.ts CHANGED
@@ -13,5 +13,6 @@ export * from './lib/fs/index.ts';
13
13
  export * from './lib/instructions.ts';
14
14
  export * from './lib/sql-hooks.ts';
15
15
  export { type CreateSqlCommandOptions, type CreateSqlCommandResult, createSqlCommand, } from './lib/sql-command.ts';
16
+ export { SQL_VALIDATE_REMINDER, sqlValidateReminder, } from './lib/sql-validate-reminder.ts';
16
17
  export * from './lib/sql.ts';
17
18
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,uBAAuB,CAAC;AAC/B,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,oCAAoC,CAAC;AACnD,cAAc,wBAAwB,CAAC;AACvC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,OAAO,EACL,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,gBAAgB,GACjB,MAAM,sBAAsB,CAAC;AAC9B,cAAc,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,uBAAuB,CAAC;AAC/B,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,oCAAoC,CAAC;AACnD,cAAc,wBAAwB,CAAC;AACvC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,OAAO,EACL,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,gBAAgB,GACjB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,qBAAqB,EACrB,mBAAmB,GACpB,MAAM,gCAAgC,CAAC;AACxC,cAAc,cAAc,CAAC"}
package/dist/index.js CHANGED
@@ -4684,39 +4684,11 @@ Action: Ask user: "Top by what metric\u2014total revenue, number of orders, or m
4684
4684
  }
4685
4685
 
4686
4686
  // packages/text2sql/src/lib/sql-meta-hook.ts
4687
+ import { useBashMeta } from "@deepagents/context";
4688
+
4689
+ // packages/text2sql/src/lib/sql-invocation.ts
4687
4690
  import { parse } from "just-bash";
4688
- import { asStaticWordText, useBashMeta } from "@deepagents/context";
4689
- var SQL_VALIDATE_REMINDER = 'Always run `sql validate <db> "..."` before `sql run <db> "..."` to catch syntax errors early.';
4690
- function createSqlMetaHook({ adapters }) {
4691
- const pendingByCommand = /* @__PURE__ */ new Map();
4692
- return {
4693
- track({ repaired, transformed }) {
4694
- const invocation = parseSqlQueryInvocation(repaired);
4695
- if (!invocation) return;
4696
- const pending = pendingByCommand.get(transformed) ?? [];
4697
- pending.push(invocation);
4698
- pendingByCommand.set(transformed, pending);
4699
- },
4700
- onAfterBashCall({ command }) {
4701
- const pending = pendingByCommand.get(command);
4702
- const invocation = pending?.shift();
4703
- if (pending?.length === 0) pendingByCommand.delete(command);
4704
- if (!invocation) return void 0;
4705
- if (invocation.subcommand === "run") {
4706
- useBashMeta()?.setReminder(SQL_VALIDATE_REMINDER);
4707
- }
4708
- const adapter = adapters[invocation.dbName];
4709
- if (!adapter) return void 0;
4710
- try {
4711
- useBashMeta()?.setHidden({
4712
- formattedSql: adapter.format(invocation.sql)
4713
- });
4714
- } catch {
4715
- }
4716
- return void 0;
4717
- }
4718
- };
4719
- }
4691
+ import { asStaticWordText } from "@deepagents/context";
4720
4692
  function parseSqlQueryInvocation(command) {
4721
4693
  const simpleCommand = parseSingleSimpleCommand(command);
4722
4694
  if (!simpleCommand) return null;
@@ -4767,6 +4739,35 @@ function joinSqlWords(words) {
4767
4739
  return sql || null;
4768
4740
  }
4769
4741
 
4742
+ // packages/text2sql/src/lib/sql-meta-hook.ts
4743
+ function createSqlMetaHook({ adapters }) {
4744
+ const pendingByCommand = /* @__PURE__ */ new Map();
4745
+ return {
4746
+ track({ repaired, transformed }) {
4747
+ const invocation = parseSqlQueryInvocation(repaired);
4748
+ if (!invocation) return;
4749
+ const pending = pendingByCommand.get(transformed) ?? [];
4750
+ pending.push(invocation);
4751
+ pendingByCommand.set(transformed, pending);
4752
+ },
4753
+ onAfterBashCall({ command }) {
4754
+ const pending = pendingByCommand.get(command);
4755
+ const invocation = pending?.shift();
4756
+ if (pending?.length === 0) pendingByCommand.delete(command);
4757
+ if (!invocation) return void 0;
4758
+ const adapter = adapters[invocation.dbName];
4759
+ if (!adapter) return void 0;
4760
+ try {
4761
+ useBashMeta()?.setHidden({
4762
+ formattedSql: adapter.format(invocation.sql)
4763
+ });
4764
+ } catch {
4765
+ }
4766
+ return void 0;
4767
+ }
4768
+ };
4769
+ }
4770
+
4770
4771
  // packages/text2sql/src/lib/sql-repair.ts
4771
4772
  import {
4772
4773
  buildSubcommandRepair,
@@ -5617,8 +5618,7 @@ import {
5617
5618
  BashException as BashException2,
5618
5619
  buildSubcommandRepair as buildSubcommandRepair2,
5619
5620
  defineSubcommandGroup,
5620
- repairQuotedArg as repairQuotedArg2,
5621
- useBashMeta as useBashMeta2
5621
+ repairQuotedArg as repairQuotedArg2
5622
5622
  } from "@deepagents/context";
5623
5623
 
5624
5624
  // packages/text2sql/src/lib/sql.ts
@@ -5652,6 +5652,25 @@ var Text2SqlUnknownAdapterError = class _Text2SqlUnknownAdapterError extends Err
5652
5652
  return error instanceof _Text2SqlUnknownAdapterError && error[text2SqlUnknownAdapterMarker] === true;
5653
5653
  }
5654
5654
  };
5655
+ var text2SqlUnknownDatabaseMarker = Symbol("Text2SqlUnknownDatabaseError");
5656
+ var Text2SqlUnknownDatabaseError = class _Text2SqlUnknownDatabaseError extends Error {
5657
+ [text2SqlUnknownDatabaseMarker];
5658
+ requested;
5659
+ available;
5660
+ constructor(requested, available) {
5661
+ const list = available.join(", ") || "(none configured)";
5662
+ super(
5663
+ requested ? `unknown database "${requested}". Available: ${list}` : `no database specified. Available: ${list}`
5664
+ );
5665
+ this.name = "Text2SqlUnknownDatabaseError";
5666
+ this.requested = requested;
5667
+ this.available = available;
5668
+ this[text2SqlUnknownDatabaseMarker] = true;
5669
+ }
5670
+ static isInstance(error) {
5671
+ return error instanceof _Text2SqlUnknownDatabaseError && error[text2SqlUnknownDatabaseMarker] === true;
5672
+ }
5673
+ };
5655
5674
  var Text2Sql = class {
5656
5675
  #config;
5657
5676
  #indexer;
@@ -5688,6 +5707,29 @@ var Text2Sql = class {
5688
5707
  hasAdapter(name) {
5689
5708
  return name in this.#config.adapters;
5690
5709
  }
5710
+ /**
5711
+ * Resolve a user-supplied `<db>` token to a configured adapter name for the
5712
+ * surfaces that accept one (CLI, sandbox `sql` command, JSON-RPC daemon, MCP):
5713
+ * - exact match → that name
5714
+ * - no match (or omitted) but exactly one adapter configured → route to it
5715
+ * - otherwise → throw with the user-facing "unknown database" message
5716
+ *
5717
+ * Internal execution paths keep using the strict {@link Text2Sql.run}/
5718
+ * {@link Text2Sql.validate}, which reject an unknown name outright.
5719
+ */
5720
+ resolveAdapterName(db) {
5721
+ if (!db || db.trim() === "") {
5722
+ const names2 = this.adapterNames();
5723
+ if (names2.length === 1) return names2[0];
5724
+ throw new Text2SqlUnknownDatabaseError(db, names2);
5725
+ }
5726
+ if (this.hasAdapter(db)) {
5727
+ return db;
5728
+ }
5729
+ const names = this.adapterNames();
5730
+ if (names.length === 1) return names[0];
5731
+ throw new Text2SqlUnknownDatabaseError(db, names);
5732
+ }
5691
5733
  async validate(name, sql) {
5692
5734
  const adapter = this.#requireAdapter(name);
5693
5735
  const result = this.#validateWith(adapter, sql);
@@ -5734,17 +5776,7 @@ var Text2Sql = class {
5734
5776
  }
5735
5777
  };
5736
5778
 
5737
- // packages/text2sql/src/lib/resolve-adapter.ts
5738
- function resolveAdapter(text2Sql, db) {
5739
- if (text2Sql.hasAdapter(db)) return db;
5740
- const names = text2Sql.adapterNames();
5741
- if (names.length === 1) return names[0];
5742
- const available = names.join(", ") || "(none configured)";
5743
- throw new Error(`unknown database "${db}". Available: ${available}`);
5744
- }
5745
-
5746
5779
  // packages/text2sql/src/lib/sql-command.ts
5747
- var SQL_VALIDATE_REMINDER2 = 'Always run `sql validate <db> "..."` before `sql run <db> "..."` to catch syntax errors early.';
5748
5780
  var SqlCommandError = class extends BashException2 {
5749
5781
  #subcommand;
5750
5782
  #exitCode;
@@ -5809,8 +5841,6 @@ function resolveOutputDir(ctx, flags, fallback) {
5809
5841
  async function handleRun(text2Sql, defaultOutputDir, args, ctx) {
5810
5842
  return runHandler("run", async () => {
5811
5843
  const flags = parseFlags("run", args);
5812
- const meta = useBashMeta2();
5813
- meta?.setReminder(SQL_VALIDATE_REMINDER2);
5814
5844
  const { name, sql } = takeDbAndSql("run", flags.positional, text2Sql);
5815
5845
  const outputDir = resolveOutputDir(ctx, flags, defaultOutputDir);
5816
5846
  const { rows, columns } = await text2Sql.run(name, sql);
@@ -5849,14 +5879,7 @@ async function runHandler(subcommand, fn) {
5849
5879
  }
5850
5880
  function takeDbAndSql(subcommand, positional, text2Sql) {
5851
5881
  const [db, ...rest] = positional;
5852
- if (!db) {
5853
- const available = text2Sql.adapterNames().join(", ");
5854
- throw new SqlCommandError(
5855
- subcommand,
5856
- `missing database name. Usage: sql ${subcommand} <db> "SELECT ...". Available: ${available}`
5857
- );
5858
- }
5859
- const name = resolveAdapter(text2Sql, db);
5882
+ const name = text2Sql.resolveAdapterName(db);
5860
5883
  const sql = rest.join(" ").trim();
5861
5884
  if (!sql) {
5862
5885
  throw new SqlCommandError(subcommand, "no query provided");
@@ -5874,6 +5897,47 @@ function repairDbNameAndQuotedArg(rawArgs) {
5874
5897
  if (repaired == null) return null;
5875
5898
  return `${dbName} ${repaired}`;
5876
5899
  }
5900
+
5901
+ // packages/text2sql/src/lib/sql-validate-reminder.ts
5902
+ import { isToolUIPart } from "ai";
5903
+ import { reminder } from "@deepagents/context";
5904
+ var SQL_VALIDATE_REMINDER = 'Always run `sql validate <db> "..."` before `sql run <db> "..."` to catch syntax errors early.';
5905
+ function extractSqlInvocation(command) {
5906
+ if (typeof command !== "string") return null;
5907
+ return parseSqlQueryInvocation(repairSqlCommand(command));
5908
+ }
5909
+ function inputCommand(input) {
5910
+ return input?.command;
5911
+ }
5912
+ function alreadyValidated(messages, run) {
5913
+ for (const message of messages ?? []) {
5914
+ for (const part of message.parts) {
5915
+ if (!isToolUIPart(part)) continue;
5916
+ const invocation = extractSqlInvocation(
5917
+ inputCommand(part.input)
5918
+ );
5919
+ if (invocation?.subcommand === "validate" && invocation.dbName === run.dbName && invocation.sql === run.sql) {
5920
+ return true;
5921
+ }
5922
+ }
5923
+ }
5924
+ return false;
5925
+ }
5926
+ function sqlRunMissingValidate() {
5927
+ return (ctx) => {
5928
+ const call = ctx.executingTool;
5929
+ if (!call || call.name !== "bash") return false;
5930
+ const run = extractSqlInvocation(inputCommand(call.input));
5931
+ if (run?.subcommand !== "run") return false;
5932
+ return !alreadyValidated(ctx.lastAssistantMessages, run);
5933
+ };
5934
+ }
5935
+ function sqlValidateReminder() {
5936
+ return reminder(SQL_VALIDATE_REMINDER, {
5937
+ target: "tool-output",
5938
+ when: sqlRunMissingValidate()
5939
+ });
5940
+ }
5877
5941
  export {
5878
5942
  Adapter,
5879
5943
  AdapterIndexer,
@@ -5888,11 +5952,13 @@ export {
5888
5952
  SQLReadOnlyError,
5889
5953
  SQLScopeError,
5890
5954
  SQLValidationError,
5955
+ SQL_VALIDATE_REMINDER,
5891
5956
  ScopedFs,
5892
5957
  SqliteFs,
5893
5958
  TEXT2SQL_INDEX_PROGRESS_CHUNK,
5894
5959
  Text2Sql,
5895
5960
  Text2SqlUnknownAdapterError,
5961
+ Text2SqlUnknownDatabaseError,
5896
5962
  Text2SqlValidationError,
5897
5963
  UnanswerableSQLError,
5898
5964
  applyTablesFilter,
@@ -5912,6 +5978,7 @@ export {
5912
5978
  matchesFilter,
5913
5979
  relationship,
5914
5980
  repairSqlCommand,
5981
+ sqlValidateReminder,
5915
5982
  suggestionsAgent,
5916
5983
  table,
5917
5984
  toSql,