@deepagents/text2sql 0.16.0 → 0.17.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.
package/dist/index.js CHANGED
@@ -510,7 +510,6 @@ var fragments = [
510
510
  hint("Support both natural language questions AND raw SQL input"),
511
511
  hint("When validating user SQL, explain any errors clearly")
512
512
  ];
513
- var developer_agent_default = { tools, fragments };
514
513
 
515
514
  // packages/text2sql/src/lib/agents/result-tools.ts
516
515
  import { tool as tool2 } from "ai";
@@ -522,6 +521,7 @@ import {
522
521
  OverlayFs,
523
522
  defineCommand
524
523
  } from "just-bash";
524
+ import { AsyncLocalStorage } from "node:async_hooks";
525
525
  import * as path from "node:path";
526
526
  import { v7 } from "uuid";
527
527
  import z3 from "zod";
@@ -550,7 +550,7 @@ function validateReadOnly(query) {
550
550
  }
551
551
  return { valid: true };
552
552
  }
553
- function createSqlCommand(adapter) {
553
+ function createSqlCommand(adapter, metaStore) {
554
554
  return createCommand("sql", {
555
555
  run: {
556
556
  usage: 'run "SELECT ..."',
@@ -573,6 +573,8 @@ function createSqlCommand(adapter) {
573
573
  };
574
574
  }
575
575
  const query = adapter.format(rawQuery);
576
+ const store = metaStore.getStore();
577
+ if (store) store.value = { formattedSql: query };
576
578
  const syntaxError = await adapter.validate(query);
577
579
  if (syntaxError) {
578
580
  return {
@@ -629,6 +631,8 @@ function createSqlCommand(adapter) {
629
631
  };
630
632
  }
631
633
  const query = adapter.format(rawQuery);
634
+ const store = metaStore.getStore();
635
+ if (store) store.value = { formattedSql: query };
632
636
  const syntaxError = await adapter.validate(query);
633
637
  if (syntaxError) {
634
638
  return {
@@ -648,7 +652,8 @@ function createSqlCommand(adapter) {
648
652
  }
649
653
  async function createResultTools(options) {
650
654
  const { adapter, skillMounts, filesystem: baseFs } = options;
651
- const sqlCommand = createSqlCommand(adapter);
655
+ const metaStore = new AsyncLocalStorage();
656
+ const sqlCommand = createSqlCommand(adapter, metaStore);
652
657
  const fsMounts = skillMounts.map(({ host, sandbox: sandbox2 }) => ({
653
658
  mountPoint: path.dirname(sandbox2),
654
659
  filesystem: new OverlayFs({
@@ -687,10 +692,19 @@ async function createResultTools(options) {
687
692
  reasoning: z3.string().trim().describe("Brief reason for executing this command")
688
693
  }),
689
694
  execute: async ({ command }, execOptions) => {
690
- if (!tools2.bash.execute) {
695
+ const execute = tools2.bash.execute;
696
+ if (!execute) {
691
697
  throw new Error("bash tool execution is not available");
692
698
  }
693
- return tools2.bash.execute({ command }, execOptions);
699
+ return metaStore.run({}, async () => {
700
+ const result = await execute({ command }, execOptions);
701
+ const meta = metaStore.getStore()?.value;
702
+ return meta ? { ...result, meta } : result;
703
+ });
704
+ },
705
+ toModelOutput: ({ output }) => {
706
+ const { meta, ...rest } = output;
707
+ return { type: "json", value: rest };
694
708
  }
695
709
  });
696
710
  return {
@@ -714,8 +728,6 @@ import {
714
728
  defaultSettingsMiddleware,
715
729
  wrapLanguageModel
716
730
  } from "ai";
717
- import { Console } from "node:console";
718
- import { createWriteStream } from "node:fs";
719
731
  import pRetry from "p-retry";
720
732
  import z4 from "zod";
721
733
  import "@deepagents/agent";
@@ -726,11 +738,6 @@ import {
726
738
  structuredOutput as structuredOutput2,
727
739
  user as user2
728
740
  } from "@deepagents/context";
729
- var logger = new Console({
730
- stdout: createWriteStream("./sql-agent.log", { flags: "a" }),
731
- stderr: createWriteStream("./sql-agent-error.log", { flags: "a" }),
732
- inspectOptions: { depth: null }
733
- });
734
741
  var RETRY_TEMPERATURES = [0, 0.2, 0.3];
735
742
  function extractSql(output) {
736
743
  const match = output.match(/```sql\n?([\s\S]*?)```/);
@@ -869,7 +876,6 @@ async function withRetry(computation, options = { retries: 3 }) {
869
876
  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);
870
877
  },
871
878
  onFailedAttempt(context) {
872
- logger.error(`toSQL`, context.error);
873
879
  console.log(
874
880
  `Attempt ${context.attemptNumber} failed. There are ${context.retriesLeft} retries left.`
875
881
  );
@@ -939,9 +945,9 @@ var Checkpoint = class _Checkpoint {
939
945
  points;
940
946
  path;
941
947
  configHash;
942
- constructor(path5, configHash, points) {
948
+ constructor(path6, configHash, points) {
943
949
  this.points = points;
944
- this.path = path5;
950
+ this.path = path6;
945
951
  this.configHash = configHash;
946
952
  }
947
953
  /**
@@ -949,14 +955,14 @@ var Checkpoint = class _Checkpoint {
949
955
  * Handles corrupted files and config changes gracefully.
950
956
  */
951
957
  static async load(options) {
952
- const { path: path5, configHash } = options;
953
- if (existsSync(path5)) {
958
+ const { path: path6, configHash } = options;
959
+ if (existsSync(path6)) {
954
960
  try {
955
- const content = readFileSync(path5, "utf-8");
961
+ const content = readFileSync(path6, "utf-8");
956
962
  const file = JSON.parse(content);
957
963
  if (configHash && file.configHash && file.configHash !== configHash) {
958
964
  console.log("\u26A0 Config changed, starting fresh");
959
- return new _Checkpoint(path5, configHash, {});
965
+ return new _Checkpoint(path6, configHash, {});
960
966
  }
961
967
  const points = file.points ?? {};
962
968
  const totalEntries = Object.values(points).reduce(
@@ -964,14 +970,14 @@ var Checkpoint = class _Checkpoint {
964
970
  0
965
971
  );
966
972
  console.log(`\u2713 Resuming from checkpoint (${totalEntries} entries)`);
967
- return new _Checkpoint(path5, configHash, points);
973
+ return new _Checkpoint(path6, configHash, points);
968
974
  } catch {
969
975
  console.log("\u26A0 Checkpoint corrupted, starting fresh");
970
- return new _Checkpoint(path5, configHash, {});
976
+ return new _Checkpoint(path6, configHash, {});
971
977
  }
972
978
  }
973
979
  console.log("Starting new checkpoint");
974
- return new _Checkpoint(path5, configHash, {});
980
+ return new _Checkpoint(path6, configHash, {});
975
981
  }
976
982
  /**
977
983
  * Run a single computation with checkpointing.
@@ -1832,10 +1838,9 @@ import * as path4 from "node:path";
1832
1838
 
1833
1839
  // packages/text2sql/src/lib/fs/mssql/ddl.mssql-fs.ts
1834
1840
  function mssqlFsDDL(schema) {
1835
- const s = schema;
1836
1841
  return `
1837
- IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[${s}].[fs_entries]') AND type = 'U')
1838
- CREATE TABLE [${s}].[fs_entries] (
1842
+ IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[${schema}].[fs_entries]') AND type = 'U')
1843
+ CREATE TABLE [${schema}].[fs_entries] (
1839
1844
  path NVARCHAR(900) PRIMARY KEY,
1840
1845
  type NVARCHAR(20) NOT NULL,
1841
1846
  mode INT NOT NULL,
@@ -1845,17 +1850,17 @@ CREATE TABLE [${s}].[fs_entries] (
1845
1850
  );
1846
1851
  GO
1847
1852
 
1848
- IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'idx_${s}_fs_entries_type')
1849
- CREATE INDEX [idx_${s}_fs_entries_type] ON [${s}].[fs_entries](type);
1853
+ IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'idx_${schema}_fs_entries_type')
1854
+ CREATE INDEX [idx_${schema}_fs_entries_type] ON [${schema}].[fs_entries](type);
1850
1855
  GO
1851
1856
 
1852
- IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[${s}].[fs_chunks]') AND type = 'U')
1853
- CREATE TABLE [${s}].[fs_chunks] (
1857
+ IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[${schema}].[fs_chunks]') AND type = 'U')
1858
+ CREATE TABLE [${schema}].[fs_chunks] (
1854
1859
  path NVARCHAR(900) NOT NULL,
1855
1860
  chunkIndex INT NOT NULL,
1856
1861
  data VARBINARY(MAX) NOT NULL,
1857
1862
  PRIMARY KEY (path, chunkIndex),
1858
- FOREIGN KEY (path) REFERENCES [${s}].[fs_entries](path) ON DELETE CASCADE ON UPDATE CASCADE
1863
+ FOREIGN KEY (path) REFERENCES [${schema}].[fs_entries](path) ON DELETE CASCADE ON UPDATE CASCADE
1859
1864
  );
1860
1865
  GO
1861
1866
  `;
@@ -2079,11 +2084,21 @@ var MssqlFs = class _MssqlFs {
2079
2084
  );
2080
2085
  }
2081
2086
  }
2082
- async #readChunks(filePath) {
2083
- const rows = await this.#query(
2084
- `SELECT data FROM ${this.#t("fs_chunks")} WHERE path = @p0 ORDER BY chunkIndex`,
2085
- [filePath]
2086
- );
2087
+ async #readChunks(filePath, transaction) {
2088
+ let rows;
2089
+ if (transaction) {
2090
+ const req = transaction.request();
2091
+ req.input("p0", filePath);
2092
+ const result2 = await req.query(
2093
+ `SELECT data FROM ${this.#t("fs_chunks")} WHERE path = @p0 ORDER BY chunkIndex`
2094
+ );
2095
+ rows = result2.recordset;
2096
+ } else {
2097
+ rows = await this.#query(
2098
+ `SELECT data FROM ${this.#t("fs_chunks")} WHERE path = @p0 ORDER BY chunkIndex`,
2099
+ [filePath]
2100
+ );
2101
+ }
2087
2102
  if (rows.length === 0) {
2088
2103
  return new Uint8Array(0);
2089
2104
  }
@@ -2208,7 +2223,7 @@ var MssqlFs = class _MssqlFs {
2208
2223
  if (entry && entry.type !== "file") {
2209
2224
  throw new Error(`appendFile: not a file: ${filePath}`);
2210
2225
  }
2211
- const existing = entry ? await this.#readChunks(prefixed) : new Uint8Array(0);
2226
+ const existing = entry ? await this.#readChunks(prefixed, transaction) : new Uint8Array(0);
2212
2227
  const combined = new Uint8Array(existing.length + newData.length);
2213
2228
  combined.set(existing, 0);
2214
2229
  combined.set(newData, existing.length);
@@ -2563,6 +2578,11 @@ var MssqlFs = class _MssqlFs {
2563
2578
  const allEntriesResult = await allEntriesReq.query(
2564
2579
  `SELECT * FROM ${this.#t("fs_entries")} WHERE path = @p0 OR path LIKE @p0 + '/%' ORDER BY path DESC`
2565
2580
  );
2581
+ const destDeleteReq = transaction.request();
2582
+ destDeleteReq.input("dp0", destPrefixed);
2583
+ await destDeleteReq.query(
2584
+ `DELETE FROM ${this.#t("fs_entries")} WHERE path = @dp0 OR path LIKE @dp0 + '/%'`
2585
+ );
2566
2586
  for (const entry of [...allEntriesResult.recordset].reverse()) {
2567
2587
  const relativePath = path4.posix.relative(srcPrefixed, entry.path);
2568
2588
  const newPath = path4.posix.join(destPrefixed, relativePath);
@@ -2573,11 +2593,22 @@ var MssqlFs = class _MssqlFs {
2573
2593
  insertReq.input("p3", entry.size);
2574
2594
  insertReq.input("p4", Date.now());
2575
2595
  insertReq.input("p5", entry.symlinkTarget);
2576
- await insertReq.query(
2577
- `INSERT INTO ${this.#t("fs_entries")} (path, type, mode, size, mtime, symlinkTarget)
2578
- VALUES (@p0, @p1, @p2, @p3, @p4, @p5)`
2579
- );
2596
+ await insertReq.query(`
2597
+ MERGE ${this.#t("fs_entries")} AS target
2598
+ USING (SELECT @p0 AS path) AS source
2599
+ ON target.path = source.path
2600
+ WHEN MATCHED THEN
2601
+ UPDATE SET type = @p1, mode = @p2, size = @p3, mtime = @p4, symlinkTarget = @p5
2602
+ WHEN NOT MATCHED THEN
2603
+ INSERT (path, type, mode, size, mtime, symlinkTarget)
2604
+ VALUES (@p0, @p1, @p2, @p3, @p4, @p5);
2605
+ `);
2580
2606
  if (entry.type === "file") {
2607
+ const deleteChunksReq = transaction.request();
2608
+ deleteChunksReq.input("p0", newPath);
2609
+ await deleteChunksReq.query(
2610
+ `DELETE FROM ${this.#t("fs_chunks")} WHERE path = @p0`
2611
+ );
2581
2612
  const chunksReq = transaction.request();
2582
2613
  chunksReq.input("p0", entry.path);
2583
2614
  const chunksResult = await chunksReq.query(
@@ -2607,11 +2638,22 @@ var MssqlFs = class _MssqlFs {
2607
2638
  insertReq.input("p3", srcEntry.size);
2608
2639
  insertReq.input("p4", Date.now());
2609
2640
  insertReq.input("p5", srcEntry.symlinkTarget);
2610
- await insertReq.query(
2611
- `INSERT INTO ${this.#t("fs_entries")} (path, type, mode, size, mtime, symlinkTarget)
2612
- VALUES (@p0, @p1, @p2, @p3, @p4, @p5)`
2613
- );
2641
+ await insertReq.query(`
2642
+ MERGE ${this.#t("fs_entries")} AS target
2643
+ USING (SELECT @p0 AS path) AS source
2644
+ ON target.path = source.path
2645
+ WHEN MATCHED THEN
2646
+ UPDATE SET type = @p1, mode = @p2, size = @p3, mtime = @p4, symlinkTarget = @p5
2647
+ WHEN NOT MATCHED THEN
2648
+ INSERT (path, type, mode, size, mtime, symlinkTarget)
2649
+ VALUES (@p0, @p1, @p2, @p3, @p4, @p5);
2650
+ `);
2614
2651
  if (srcEntry.type === "file") {
2652
+ const deleteChunksReq = transaction.request();
2653
+ deleteChunksReq.input("p0", destPrefixed);
2654
+ await deleteChunksReq.query(
2655
+ `DELETE FROM ${this.#t("fs_chunks")} WHERE path = @p0`
2656
+ );
2615
2657
  const chunksReq = transaction.request();
2616
2658
  chunksReq.input("p0", srcPrefixed);
2617
2659
  const chunksResult = await chunksReq.query(
@@ -2775,136 +2817,1006 @@ var MssqlFs = class _MssqlFs {
2775
2817
  }
2776
2818
  };
2777
2819
 
2778
- // packages/text2sql/src/lib/fs/scoped-fs.ts
2779
- var ScopedFs = class {
2780
- #base;
2781
- #prefix;
2820
+ // packages/text2sql/src/lib/fs/postgres/postgres-fs.ts
2821
+ import { createRequire as createRequire2 } from "node:module";
2822
+ import * as path5 from "node:path";
2823
+
2824
+ // packages/text2sql/src/lib/fs/postgres/ddl.postgres-fs.ts
2825
+ function postgresFsDDL(schema) {
2826
+ return `
2827
+ CREATE SCHEMA IF NOT EXISTS "${schema}";
2828
+
2829
+ CREATE TABLE IF NOT EXISTS "${schema}"."fs_entries" (
2830
+ path TEXT PRIMARY KEY,
2831
+ type TEXT NOT NULL,
2832
+ mode INTEGER NOT NULL,
2833
+ size BIGINT NOT NULL,
2834
+ mtime BIGINT NOT NULL,
2835
+ symlink_target TEXT
2836
+ );
2837
+
2838
+ CREATE INDEX IF NOT EXISTS "idx_${schema}_fs_entries_type" ON "${schema}"."fs_entries"(type);
2839
+
2840
+ CREATE TABLE IF NOT EXISTS "${schema}"."fs_chunks" (
2841
+ path TEXT NOT NULL,
2842
+ chunk_index INTEGER NOT NULL,
2843
+ data BYTEA NOT NULL,
2844
+ PRIMARY KEY (path, chunk_index),
2845
+ FOREIGN KEY (path) REFERENCES "${schema}"."fs_entries"(path) ON DELETE CASCADE ON UPDATE CASCADE
2846
+ );
2847
+ `;
2848
+ }
2849
+
2850
+ // packages/text2sql/src/lib/fs/postgres/postgres-fs.ts
2851
+ var PostgresFs = class _PostgresFs {
2852
+ #pool;
2853
+ #chunkSize;
2854
+ #root;
2855
+ #schema;
2856
+ #ownsPool;
2857
+ #isInitialized = false;
2782
2858
  constructor(options) {
2783
- this.#base = options.base;
2784
- this.#prefix = options.prefix.replace(/\/$/, "");
2785
- }
2786
- #scope(path5) {
2787
- return `${this.#prefix}${path5}`;
2788
- }
2789
- #unscope(path5) {
2790
- if (path5 === this.#prefix) {
2791
- return "/";
2859
+ this.#chunkSize = options.chunkSize ?? 1024 * 1024;
2860
+ const schema = options.schema ?? "public";
2861
+ if (!/^[a-zA-Z_]\w*$/.test(schema)) {
2862
+ throw new Error(`Invalid schema name: "${schema}"`);
2792
2863
  }
2793
- if (path5.startsWith(this.#prefix + "/")) {
2794
- return path5.slice(this.#prefix.length) || "/";
2864
+ this.#schema = schema;
2865
+ const normalizedRoot = this.#normalizeRoot(options.root);
2866
+ this.#root = normalizedRoot === "/" ? "" : normalizedRoot;
2867
+ const pg = _PostgresFs.#requirePg();
2868
+ if (options.pool instanceof pg.Pool) {
2869
+ this.#pool = options.pool;
2870
+ this.#ownsPool = false;
2871
+ } else {
2872
+ this.#pool = typeof options.pool === "string" ? new pg.Pool({ connectionString: options.pool }) : new pg.Pool(options.pool);
2873
+ this.#ownsPool = true;
2795
2874
  }
2796
- return path5;
2797
2875
  }
2798
- async writeFile(path5, content, options) {
2799
- await this.#base.writeFile(this.#scope(path5), content, options);
2800
- }
2801
- async appendFile(path5, content, options) {
2802
- await this.#base.appendFile(this.#scope(path5), content, options);
2803
- }
2804
- async mkdir(path5, options) {
2805
- return this.#base.mkdir(this.#scope(path5), options);
2806
- }
2807
- async rm(path5, options) {
2808
- await this.#base.rm(this.#scope(path5), options);
2809
- }
2810
- async cp(src, dest, options) {
2811
- await this.#base.cp(this.#scope(src), this.#scope(dest), options);
2812
- }
2813
- async mv(src, dest) {
2814
- await this.#base.mv(this.#scope(src), this.#scope(dest));
2815
- }
2816
- async chmod(path5, mode) {
2817
- return this.#base.chmod(this.#scope(path5), mode);
2818
- }
2819
- async symlink(target, linkPath) {
2820
- await this.#base.symlink(target, this.#scope(linkPath));
2876
+ static #requirePg() {
2877
+ try {
2878
+ const require2 = createRequire2(import.meta.url);
2879
+ return require2("pg");
2880
+ } catch {
2881
+ throw new Error(
2882
+ 'PostgresFs requires the "pg" package. Install it with: npm install pg'
2883
+ );
2884
+ }
2821
2885
  }
2822
- async link(existingPath, newPath) {
2823
- await this.#base.link(this.#scope(existingPath), this.#scope(newPath));
2886
+ #t(name) {
2887
+ return `"${this.#schema}"."${name}"`;
2824
2888
  }
2825
- readFile(path5, options) {
2826
- return this.#base.readFile(this.#scope(path5), options);
2889
+ async initialize() {
2890
+ const ddl = postgresFsDDL(this.#schema);
2891
+ await this.#pool.query(ddl);
2892
+ const rootSlashExists = await this.#rawQuery(
2893
+ `SELECT EXISTS(SELECT 1 FROM ${this.#t("fs_entries")} WHERE path = '/') AS exists`
2894
+ );
2895
+ if (!rootSlashExists[0].exists) {
2896
+ await this.#rawExec(
2897
+ `INSERT INTO ${this.#t("fs_entries")} (path, type, mode, size, mtime) VALUES ('/', 'directory', 493, 0, $1)`,
2898
+ [Date.now()]
2899
+ );
2900
+ }
2901
+ if (this.#root) {
2902
+ await this.#createParentDirs(this.#root);
2903
+ const rootExists = await this.#rawQuery(
2904
+ `SELECT EXISTS(SELECT 1 FROM ${this.#t("fs_entries")} WHERE path = $1) AS exists`,
2905
+ [this.#root]
2906
+ );
2907
+ if (!rootExists[0].exists) {
2908
+ await this.#rawExec(
2909
+ `INSERT INTO ${this.#t("fs_entries")} (path, type, mode, size, mtime) VALUES ($1, 'directory', 493, 0, $2)`,
2910
+ [this.#root, Date.now()]
2911
+ );
2912
+ }
2913
+ }
2914
+ this.#isInitialized = true;
2827
2915
  }
2828
- readFileBuffer(path5) {
2829
- return this.#base.readFileBuffer(this.#scope(path5));
2916
+ #ensureInitialized() {
2917
+ if (!this.#isInitialized) {
2918
+ throw new Error(
2919
+ "PostgresFs not initialized. Call await fs.initialize() after construction."
2920
+ );
2921
+ }
2830
2922
  }
2831
- stat(path5) {
2832
- return this.#base.stat(this.#scope(path5));
2923
+ async #createParentDirs(p) {
2924
+ const segments = p.split("/").filter(Boolean);
2925
+ let currentPath = "/";
2926
+ for (let i = 0; i < segments.length - 1; i++) {
2927
+ currentPath = path5.posix.join(currentPath, segments[i]);
2928
+ const exists = await this.#rawQuery(
2929
+ `SELECT EXISTS(SELECT 1 FROM ${this.#t("fs_entries")} WHERE path = $1) AS exists`,
2930
+ [currentPath]
2931
+ );
2932
+ if (!exists[0].exists) {
2933
+ await this.#rawExec(
2934
+ `INSERT INTO ${this.#t("fs_entries")} (path, type, mode, size, mtime) VALUES ($1, 'directory', 493, 0, $2)`,
2935
+ [currentPath, Date.now()]
2936
+ );
2937
+ }
2938
+ }
2833
2939
  }
2834
- lstat(path5) {
2835
- return this.#base.lstat(this.#scope(path5));
2940
+ async #rawQuery(sql, params) {
2941
+ const result = await this.#pool.query(sql, params);
2942
+ return result.rows;
2836
2943
  }
2837
- readdir(path5) {
2838
- return this.#base.readdir(this.#scope(path5));
2944
+ async #rawExec(sql, params) {
2945
+ const result = await this.#pool.query(sql, params);
2946
+ return result.rowCount ?? 0;
2839
2947
  }
2840
- readdirWithFileTypes(path5) {
2841
- return this.#base.readdirWithFileTypes(this.#scope(path5));
2948
+ async #query(sql, params) {
2949
+ this.#ensureInitialized();
2950
+ return this.#rawQuery(sql, params);
2842
2951
  }
2843
- exists(path5) {
2844
- return this.#base.exists(this.#scope(path5));
2952
+ async #exec(sql, params) {
2953
+ this.#ensureInitialized();
2954
+ return this.#rawExec(sql, params);
2845
2955
  }
2846
- readlink(path5) {
2847
- return this.#base.readlink(this.#scope(path5));
2956
+ async #useTransaction(fn) {
2957
+ this.#ensureInitialized();
2958
+ const client = await this.#pool.connect();
2959
+ try {
2960
+ await client.query("BEGIN");
2961
+ const result = await fn(client);
2962
+ await client.query("COMMIT");
2963
+ return result;
2964
+ } catch (error) {
2965
+ await client.query("ROLLBACK");
2966
+ throw error;
2967
+ } finally {
2968
+ client.release();
2969
+ }
2848
2970
  }
2849
- realpath(path5) {
2850
- return this.#base.realpath(this.#scope(path5)).then((p) => this.#unscope(p));
2971
+ #normalizeRoot(root) {
2972
+ return path5.posix.resolve("/", root.trim());
2851
2973
  }
2852
- utimes(path5, atime, mtime) {
2853
- return this.#base.utimes(this.#scope(path5), atime, mtime);
2974
+ #prefixPath(p) {
2975
+ if (!this.#root) {
2976
+ return p;
2977
+ }
2978
+ if (p === "/") {
2979
+ return this.#root;
2980
+ }
2981
+ return path5.posix.join(this.#root, p);
2854
2982
  }
2855
- resolvePath(base, relativePath) {
2856
- return this.#base.resolvePath(base, relativePath);
2983
+ #unprefixPath(p) {
2984
+ if (!this.#root) {
2985
+ return p;
2986
+ }
2987
+ if (p === this.#root) {
2988
+ return "/";
2989
+ }
2990
+ if (p.startsWith(this.#root + "/")) {
2991
+ return p.slice(this.#root.length) || "/";
2992
+ }
2993
+ return p;
2857
2994
  }
2858
- getAllPaths() {
2859
- const allPaths = this.#base.getAllPaths?.() ?? [];
2860
- return allPaths.filter((p) => p.startsWith(this.#prefix)).map((p) => p.slice(this.#prefix.length) || "/");
2995
+ #normalizePath(p) {
2996
+ return path5.posix.resolve("/", p);
2861
2997
  }
2862
- };
2863
-
2864
- // packages/text2sql/src/lib/fs/tracked-fs.ts
2865
- var TrackedFs = class {
2866
- #base;
2867
- #createdFiles = /* @__PURE__ */ new Set();
2868
- constructor(base) {
2869
- this.#base = base;
2998
+ #dirname(p) {
2999
+ const dir = path5.posix.dirname(p);
3000
+ return dir === "" ? "/" : dir;
2870
3001
  }
2871
- getCreatedFiles() {
2872
- return [...this.#createdFiles];
3002
+ async #ensureParentExists(filePath, client) {
3003
+ const parent = this.#dirname(filePath);
3004
+ const rootPath = this.#root || "/";
3005
+ if (parent === rootPath || parent === "/") return;
3006
+ const result = await client.query(
3007
+ `SELECT type FROM ${this.#t("fs_entries")} WHERE path = $1`,
3008
+ [parent]
3009
+ );
3010
+ const entry = result.rows[0];
3011
+ if (!entry) {
3012
+ await this.#ensureParentExists(parent, client);
3013
+ await client.query(
3014
+ `INSERT INTO ${this.#t("fs_entries")} (path, type, mode, size, mtime) VALUES ($1, 'directory', 493, 0, $2)`,
3015
+ [parent, Date.now()]
3016
+ );
3017
+ } else if (entry.type !== "directory") {
3018
+ throw new Error(`mkdir: parent is not a directory: ${parent}`);
3019
+ }
2873
3020
  }
2874
- async writeFile(path5, content, options) {
2875
- await this.#base.writeFile(path5, content, options);
2876
- this.#createdFiles.add(path5);
3021
+ async #writeChunks(filePath, content, client) {
3022
+ await client.query(`DELETE FROM ${this.#t("fs_chunks")} WHERE path = $1`, [
3023
+ filePath
3024
+ ]);
3025
+ for (let i = 0; i < content.length; i += this.#chunkSize) {
3026
+ const chunk = content.slice(
3027
+ i,
3028
+ Math.min(i + this.#chunkSize, content.length)
3029
+ );
3030
+ await client.query(
3031
+ `INSERT INTO ${this.#t("fs_chunks")} (path, chunk_index, data) VALUES ($1, $2, $3)`,
3032
+ [filePath, Math.floor(i / this.#chunkSize), Buffer.from(chunk)]
3033
+ );
3034
+ }
2877
3035
  }
2878
- async appendFile(path5, content, options) {
2879
- await this.#base.appendFile(path5, content, options);
2880
- this.#createdFiles.add(path5);
3036
+ async #readChunks(filePath, client) {
3037
+ let rows;
3038
+ if (client) {
3039
+ const result2 = await client.query(
3040
+ `SELECT data FROM ${this.#t("fs_chunks")} WHERE path = $1 ORDER BY chunk_index`,
3041
+ [filePath]
3042
+ );
3043
+ rows = result2.rows;
3044
+ } else {
3045
+ rows = await this.#query(
3046
+ `SELECT data FROM ${this.#t("fs_chunks")} WHERE path = $1 ORDER BY chunk_index`,
3047
+ [filePath]
3048
+ );
3049
+ }
3050
+ if (rows.length === 0) {
3051
+ return new Uint8Array(0);
3052
+ }
3053
+ const totalSize = rows.reduce((sum, row) => sum + row.data.length, 0);
3054
+ const result = new Uint8Array(totalSize);
3055
+ let offset = 0;
3056
+ for (const row of rows) {
3057
+ result.set(new Uint8Array(row.data), offset);
3058
+ offset += row.data.length;
3059
+ }
3060
+ return result;
2881
3061
  }
2882
- async mkdir(path5, options) {
2883
- return this.#base.mkdir(path5, options);
3062
+ async #resolveSymlink(p, seen = /* @__PURE__ */ new Set()) {
3063
+ if (seen.has(p)) {
3064
+ throw new Error(`readFile: circular symlink: ${p}`);
3065
+ }
3066
+ const rows = await this.#query(
3067
+ `SELECT type, symlink_target FROM ${this.#t("fs_entries")} WHERE path = $1`,
3068
+ [p]
3069
+ );
3070
+ const entry = rows[0];
3071
+ if (!entry) {
3072
+ throw new Error(`ENOENT: no such file or directory: ${p}`);
3073
+ }
3074
+ if (entry.type !== "symlink") {
3075
+ return p;
3076
+ }
3077
+ seen.add(p);
3078
+ const target = this.#normalizePath(
3079
+ path5.posix.resolve(this.#dirname(p), entry.symlink_target)
3080
+ );
3081
+ return this.#resolveSymlink(target, seen);
2884
3082
  }
2885
- async rm(path5, options) {
2886
- await this.#base.rm(path5, options);
2887
- this.#createdFiles.delete(path5);
2888
- if (options?.recursive) {
2889
- const prefix = path5.endsWith("/") ? path5 : path5 + "/";
2890
- for (const file of this.#createdFiles) {
2891
- if (file.startsWith(prefix)) {
2892
- this.#createdFiles.delete(file);
2893
- }
2894
- }
3083
+ #toUint8Array(content, encoding) {
3084
+ if (content instanceof Uint8Array) {
3085
+ return content;
2895
3086
  }
3087
+ const enc = encoding ?? "utf8";
3088
+ return new Uint8Array(Buffer.from(content, enc));
2896
3089
  }
2897
- async cp(src, dest, options) {
2898
- await this.#base.cp(src, dest, options);
2899
- this.#createdFiles.add(dest);
3090
+ async close() {
3091
+ if (this.#ownsPool) {
3092
+ await this.#pool.end();
3093
+ }
2900
3094
  }
2901
- async mv(src, dest) {
2902
- await this.#base.mv(src, dest);
3095
+ // ============================================================================
3096
+ // IFileSystem Implementation
3097
+ // ============================================================================
3098
+ async readFile(filePath, options) {
3099
+ const normalized = this.#normalizePath(filePath);
3100
+ const prefixed = this.#prefixPath(normalized);
3101
+ const resolved = await this.#resolveSymlink(prefixed);
3102
+ const rows = await this.#query(
3103
+ `SELECT type FROM ${this.#t("fs_entries")} WHERE path = $1`,
3104
+ [resolved]
3105
+ );
3106
+ const entry = rows[0];
3107
+ if (!entry) {
3108
+ throw new Error(`ENOENT: no such file or directory: ${filePath}`);
3109
+ }
3110
+ if (entry.type === "directory") {
3111
+ throw new Error(`EISDIR: illegal operation on a directory: ${filePath}`);
3112
+ }
3113
+ const content = await this.#readChunks(resolved);
3114
+ const encoding = typeof options === "string" ? options : options?.encoding ?? "utf8";
3115
+ return Buffer.from(content).toString(encoding);
3116
+ }
3117
+ async readFileBuffer(filePath) {
3118
+ const normalized = this.#normalizePath(filePath);
3119
+ const prefixed = this.#prefixPath(normalized);
3120
+ const resolved = await this.#resolveSymlink(prefixed);
3121
+ const rows = await this.#query(
3122
+ `SELECT type FROM ${this.#t("fs_entries")} WHERE path = $1`,
3123
+ [resolved]
3124
+ );
3125
+ const entry = rows[0];
3126
+ if (!entry) {
3127
+ throw new Error(`ENOENT: no such file or directory: ${filePath}`);
3128
+ }
3129
+ if (entry.type === "directory") {
3130
+ throw new Error(`EISDIR: illegal operation on a directory: ${filePath}`);
3131
+ }
3132
+ return this.#readChunks(resolved);
3133
+ }
3134
+ async writeFile(filePath, content, options) {
3135
+ const normalized = this.#normalizePath(filePath);
3136
+ const prefixed = this.#prefixPath(normalized);
3137
+ const encoding = typeof options === "string" ? options : options?.encoding;
3138
+ const data = this.#toUint8Array(content, encoding);
3139
+ await this.#useTransaction(async (client) => {
3140
+ await this.#ensureParentExists(prefixed, client);
3141
+ await client.query(
3142
+ `INSERT INTO ${this.#t("fs_entries")} (path, type, mode, size, mtime)
3143
+ VALUES ($1, 'file', 420, $2, $3)
3144
+ ON CONFLICT (path) DO UPDATE SET type = 'file', size = EXCLUDED.size, mtime = EXCLUDED.mtime`,
3145
+ [prefixed, data.length, Date.now()]
3146
+ );
3147
+ await this.#writeChunks(prefixed, data, client);
3148
+ });
3149
+ }
3150
+ async appendFile(filePath, content, options) {
3151
+ const normalized = this.#normalizePath(filePath);
3152
+ const prefixed = this.#prefixPath(normalized);
3153
+ const encoding = typeof options === "string" ? options : options?.encoding;
3154
+ const newData = this.#toUint8Array(content, encoding);
3155
+ await this.#useTransaction(async (client) => {
3156
+ await this.#ensureParentExists(prefixed, client);
3157
+ const result = await client.query(
3158
+ `SELECT type FROM ${this.#t("fs_entries")} WHERE path = $1`,
3159
+ [prefixed]
3160
+ );
3161
+ const entry = result.rows[0];
3162
+ if (entry && entry.type !== "file") {
3163
+ throw new Error(`appendFile: not a file: ${filePath}`);
3164
+ }
3165
+ const existing = entry ? await this.#readChunks(prefixed, client) : new Uint8Array(0);
3166
+ const combined = new Uint8Array(existing.length + newData.length);
3167
+ combined.set(existing, 0);
3168
+ combined.set(newData, existing.length);
3169
+ await client.query(
3170
+ `INSERT INTO ${this.#t("fs_entries")} (path, type, mode, size, mtime)
3171
+ VALUES ($1, 'file', 420, $2, $3)
3172
+ ON CONFLICT (path) DO UPDATE SET size = EXCLUDED.size, mtime = EXCLUDED.mtime`,
3173
+ [prefixed, combined.length, Date.now()]
3174
+ );
3175
+ await this.#writeChunks(prefixed, combined, client);
3176
+ });
3177
+ }
3178
+ async exists(filePath) {
3179
+ const normalized = this.#normalizePath(filePath);
3180
+ const prefixed = this.#prefixPath(normalized);
3181
+ const rows = await this.#query(
3182
+ `SELECT EXISTS(SELECT 1 FROM ${this.#t("fs_entries")} WHERE path = $1) AS exists`,
3183
+ [prefixed]
3184
+ );
3185
+ return rows[0].exists;
3186
+ }
3187
+ async stat(filePath) {
3188
+ const normalized = this.#normalizePath(filePath);
3189
+ const prefixed = this.#prefixPath(normalized);
3190
+ const resolved = await this.#resolveSymlink(prefixed);
3191
+ const rows = await this.#query(
3192
+ `SELECT * FROM ${this.#t("fs_entries")} WHERE path = $1`,
3193
+ [resolved]
3194
+ );
3195
+ const entry = rows[0];
3196
+ if (!entry) {
3197
+ throw new Error(`ENOENT: no such file or directory: ${filePath}`);
3198
+ }
3199
+ return {
3200
+ isFile: entry.type === "file",
3201
+ isDirectory: entry.type === "directory",
3202
+ isSymbolicLink: false,
3203
+ mode: Number(entry.mode),
3204
+ size: Number(entry.size),
3205
+ mtime: new Date(Number(entry.mtime))
3206
+ };
3207
+ }
3208
+ async lstat(filePath) {
3209
+ const normalized = this.#normalizePath(filePath);
3210
+ const prefixed = this.#prefixPath(normalized);
3211
+ const rows = await this.#query(
3212
+ `SELECT * FROM ${this.#t("fs_entries")} WHERE path = $1`,
3213
+ [prefixed]
3214
+ );
3215
+ const entry = rows[0];
3216
+ if (!entry) {
3217
+ throw new Error(`ENOENT: no such file or directory: ${filePath}`);
3218
+ }
3219
+ return {
3220
+ isFile: entry.type === "file",
3221
+ isDirectory: entry.type === "directory",
3222
+ isSymbolicLink: entry.type === "symlink",
3223
+ mode: Number(entry.mode),
3224
+ size: Number(entry.size),
3225
+ mtime: new Date(Number(entry.mtime))
3226
+ };
3227
+ }
3228
+ async mkdir(dirPath, options) {
3229
+ const normalized = this.#normalizePath(dirPath);
3230
+ const prefixed = this.#prefixPath(normalized);
3231
+ const existingRows = await this.#query(
3232
+ `SELECT type FROM ${this.#t("fs_entries")} WHERE path = $1`,
3233
+ [prefixed]
3234
+ );
3235
+ const existing = existingRows[0];
3236
+ if (existing) {
3237
+ if (options?.recursive) {
3238
+ return;
3239
+ }
3240
+ throw new Error(`EEXIST: file already exists: ${dirPath}`);
3241
+ }
3242
+ await this.#useTransaction(async (client) => {
3243
+ if (options?.recursive) {
3244
+ const rootPath = this.#root || "/";
3245
+ const relativePath = path5.posix.relative(rootPath, prefixed);
3246
+ const segments = relativePath.split("/").filter(Boolean);
3247
+ let currentPath = rootPath;
3248
+ for (const segment of segments) {
3249
+ currentPath = path5.posix.join(currentPath, segment);
3250
+ const result = await client.query(
3251
+ `SELECT type FROM ${this.#t("fs_entries")} WHERE path = $1`,
3252
+ [currentPath]
3253
+ );
3254
+ const exists = result.rows[0];
3255
+ if (!exists) {
3256
+ await client.query(
3257
+ `INSERT INTO ${this.#t("fs_entries")} (path, type, mode, size, mtime) VALUES ($1, 'directory', 493, 0, $2)`,
3258
+ [currentPath, Date.now()]
3259
+ );
3260
+ } else if (exists.type !== "directory") {
3261
+ throw new Error(`mkdir: not a directory: ${currentPath}`);
3262
+ }
3263
+ }
3264
+ } else {
3265
+ const parent = this.#dirname(prefixed);
3266
+ const parentResult = await client.query(
3267
+ `SELECT type FROM ${this.#t("fs_entries")} WHERE path = $1`,
3268
+ [parent]
3269
+ );
3270
+ const parentEntry = parentResult.rows[0];
3271
+ if (!parentEntry) {
3272
+ throw new Error(`mkdir: parent does not exist: ${parent}`);
3273
+ }
3274
+ if (parentEntry.type !== "directory") {
3275
+ throw new Error(`mkdir: parent is not a directory: ${parent}`);
3276
+ }
3277
+ await client.query(
3278
+ `INSERT INTO ${this.#t("fs_entries")} (path, type, mode, size, mtime) VALUES ($1, 'directory', 493, 0, $2)`,
3279
+ [prefixed, Date.now()]
3280
+ );
3281
+ }
3282
+ });
3283
+ }
3284
+ async readdir(dirPath) {
3285
+ const normalized = this.#normalizePath(dirPath);
3286
+ const prefixed = this.#prefixPath(normalized);
3287
+ const resolved = await this.#resolveSymlink(prefixed);
3288
+ const entryRows = await this.#query(
3289
+ `SELECT type FROM ${this.#t("fs_entries")} WHERE path = $1`,
3290
+ [resolved]
3291
+ );
3292
+ const entry = entryRows[0];
3293
+ if (!entry) {
3294
+ throw new Error(`ENOENT: no such file or directory: ${dirPath}`);
3295
+ }
3296
+ if (entry.type !== "directory") {
3297
+ throw new Error(`ENOTDIR: not a directory: ${dirPath}`);
3298
+ }
3299
+ const prefix = resolved === "/" ? "/" : resolved + "/";
3300
+ const rows = await this.#query(
3301
+ `SELECT path FROM ${this.#t("fs_entries")}
3302
+ WHERE path LIKE $1 || '%'
3303
+ AND path != $2
3304
+ AND path NOT LIKE $1 || '%/%'`,
3305
+ [prefix, resolved]
3306
+ );
3307
+ return rows.map((row) => path5.posix.basename(row.path));
3308
+ }
3309
+ async readdirWithFileTypes(dirPath) {
3310
+ const normalized = this.#normalizePath(dirPath);
3311
+ const prefixed = this.#prefixPath(normalized);
3312
+ const resolved = await this.#resolveSymlink(prefixed);
3313
+ const entryRows = await this.#query(
3314
+ `SELECT type FROM ${this.#t("fs_entries")} WHERE path = $1`,
3315
+ [resolved]
3316
+ );
3317
+ const entry = entryRows[0];
3318
+ if (!entry) {
3319
+ throw new Error(`ENOENT: no such file or directory: ${dirPath}`);
3320
+ }
3321
+ if (entry.type !== "directory") {
3322
+ throw new Error(`ENOTDIR: not a directory: ${dirPath}`);
3323
+ }
3324
+ const prefix = resolved === "/" ? "/" : resolved + "/";
3325
+ const rows = await this.#query(
3326
+ `SELECT path, type FROM ${this.#t("fs_entries")}
3327
+ WHERE path LIKE $1 || '%'
3328
+ AND path != $2
3329
+ AND path NOT LIKE $1 || '%/%'`,
3330
+ [prefix, resolved]
3331
+ );
3332
+ return rows.map((row) => ({
3333
+ name: path5.posix.basename(row.path),
3334
+ isFile: row.type === "file",
3335
+ isDirectory: row.type === "directory",
3336
+ isSymbolicLink: row.type === "symlink"
3337
+ }));
3338
+ }
3339
+ async rm(filePath, options) {
3340
+ const normalized = this.#normalizePath(filePath);
3341
+ const prefixed = this.#prefixPath(normalized);
3342
+ const rows = await this.#query(
3343
+ `SELECT type FROM ${this.#t("fs_entries")} WHERE path = $1`,
3344
+ [prefixed]
3345
+ );
3346
+ const entry = rows[0];
3347
+ if (!entry) {
3348
+ if (options?.force) {
3349
+ return;
3350
+ }
3351
+ throw new Error(`ENOENT: no such file or directory: ${filePath}`);
3352
+ }
3353
+ await this.#useTransaction(async (client) => {
3354
+ if (entry.type === "directory") {
3355
+ const childrenResult = await client.query(
3356
+ `SELECT EXISTS(SELECT 1 FROM ${this.#t("fs_entries")} WHERE path LIKE $1 || '/%') AS exists`,
3357
+ [prefixed]
3358
+ );
3359
+ if (childrenResult.rows[0].exists && !options?.recursive) {
3360
+ throw new Error(`ENOTEMPTY: directory not empty: ${filePath}`);
3361
+ }
3362
+ await client.query(
3363
+ `DELETE FROM ${this.#t("fs_entries")} WHERE path = $1 OR path LIKE $1 || '/%'`,
3364
+ [prefixed]
3365
+ );
3366
+ } else {
3367
+ await client.query(
3368
+ `DELETE FROM ${this.#t("fs_entries")} WHERE path = $1`,
3369
+ [prefixed]
3370
+ );
3371
+ }
3372
+ });
3373
+ }
3374
+ async cp(src, dest, options) {
3375
+ const srcNormalized = this.#normalizePath(src);
3376
+ const destNormalized = this.#normalizePath(dest);
3377
+ const srcPrefixed = this.#prefixPath(srcNormalized);
3378
+ const destPrefixed = this.#prefixPath(destNormalized);
3379
+ const srcRows = await this.#query(
3380
+ `SELECT * FROM ${this.#t("fs_entries")} WHERE path = $1`,
3381
+ [srcPrefixed]
3382
+ );
3383
+ const srcEntry = srcRows[0];
3384
+ if (!srcEntry) {
3385
+ throw new Error(`ENOENT: no such file or directory: ${src}`);
3386
+ }
3387
+ if (srcEntry.type === "directory" && !options?.recursive) {
3388
+ throw new Error(`cp: -r not specified; omitting directory: ${src}`);
3389
+ }
3390
+ await this.#useTransaction(async (client) => {
3391
+ await this.#ensureParentExists(destPrefixed, client);
3392
+ if (srcEntry.type === "directory") {
3393
+ const allEntriesResult = await client.query(
3394
+ `SELECT * FROM ${this.#t("fs_entries")} WHERE path = $1 OR path LIKE $1 || '/%'`,
3395
+ [srcPrefixed]
3396
+ );
3397
+ for (const entry of allEntriesResult.rows) {
3398
+ const relativePath = path5.posix.relative(srcPrefixed, entry.path);
3399
+ const newPath = path5.posix.join(destPrefixed, relativePath);
3400
+ await client.query(
3401
+ `INSERT INTO ${this.#t("fs_entries")} (path, type, mode, size, mtime, symlink_target)
3402
+ VALUES ($1, $2, $3, $4, $5, $6)
3403
+ ON CONFLICT (path) DO UPDATE SET type = EXCLUDED.type, mode = EXCLUDED.mode, size = EXCLUDED.size, mtime = EXCLUDED.mtime, symlink_target = EXCLUDED.symlink_target`,
3404
+ [
3405
+ newPath,
3406
+ entry.type,
3407
+ entry.mode,
3408
+ entry.size,
3409
+ Date.now(),
3410
+ entry.symlink_target
3411
+ ]
3412
+ );
3413
+ if (entry.type === "file") {
3414
+ await client.query(
3415
+ `DELETE FROM ${this.#t("fs_chunks")} WHERE path = $1`,
3416
+ [newPath]
3417
+ );
3418
+ const chunksResult = await client.query(
3419
+ `SELECT chunk_index, data FROM ${this.#t("fs_chunks")} WHERE path = $1`,
3420
+ [entry.path]
3421
+ );
3422
+ for (const chunk of chunksResult.rows) {
3423
+ await client.query(
3424
+ `INSERT INTO ${this.#t("fs_chunks")} (path, chunk_index, data) VALUES ($1, $2, $3)`,
3425
+ [newPath, chunk.chunk_index, chunk.data]
3426
+ );
3427
+ }
3428
+ }
3429
+ }
3430
+ } else {
3431
+ await client.query(
3432
+ `INSERT INTO ${this.#t("fs_entries")} (path, type, mode, size, mtime, symlink_target)
3433
+ VALUES ($1, $2, $3, $4, $5, $6)
3434
+ ON CONFLICT (path) DO UPDATE SET type = EXCLUDED.type, mode = EXCLUDED.mode, size = EXCLUDED.size, mtime = EXCLUDED.mtime, symlink_target = EXCLUDED.symlink_target`,
3435
+ [
3436
+ destPrefixed,
3437
+ srcEntry.type,
3438
+ srcEntry.mode,
3439
+ srcEntry.size,
3440
+ Date.now(),
3441
+ srcEntry.symlink_target
3442
+ ]
3443
+ );
3444
+ if (srcEntry.type === "file") {
3445
+ const chunksResult = await client.query(
3446
+ `SELECT chunk_index, data FROM ${this.#t("fs_chunks")} WHERE path = $1`,
3447
+ [srcPrefixed]
3448
+ );
3449
+ await client.query(
3450
+ `DELETE FROM ${this.#t("fs_chunks")} WHERE path = $1`,
3451
+ [destPrefixed]
3452
+ );
3453
+ for (const chunk of chunksResult.rows) {
3454
+ await client.query(
3455
+ `INSERT INTO ${this.#t("fs_chunks")} (path, chunk_index, data) VALUES ($1, $2, $3)`,
3456
+ [destPrefixed, chunk.chunk_index, chunk.data]
3457
+ );
3458
+ }
3459
+ }
3460
+ }
3461
+ });
3462
+ }
3463
+ async mv(src, dest) {
3464
+ const srcNormalized = this.#normalizePath(src);
3465
+ const destNormalized = this.#normalizePath(dest);
3466
+ const srcPrefixed = this.#prefixPath(srcNormalized);
3467
+ const destPrefixed = this.#prefixPath(destNormalized);
3468
+ const srcRows = await this.#query(
3469
+ `SELECT * FROM ${this.#t("fs_entries")} WHERE path = $1`,
3470
+ [srcPrefixed]
3471
+ );
3472
+ const srcEntry = srcRows[0];
3473
+ if (!srcEntry) {
3474
+ throw new Error(`ENOENT: no such file or directory: ${src}`);
3475
+ }
3476
+ await this.#useTransaction(async (client) => {
3477
+ await this.#ensureParentExists(destPrefixed, client);
3478
+ if (srcEntry.type === "directory") {
3479
+ const allEntriesResult = await client.query(
3480
+ `SELECT * FROM ${this.#t("fs_entries")} WHERE path = $1 OR path LIKE $1 || '/%' ORDER BY path DESC`,
3481
+ [srcPrefixed]
3482
+ );
3483
+ await client.query(
3484
+ `DELETE FROM ${this.#t("fs_entries")} WHERE path = $1 OR path LIKE $1 || '/%'`,
3485
+ [destPrefixed]
3486
+ );
3487
+ for (const entry of [...allEntriesResult.rows].reverse()) {
3488
+ const relativePath = path5.posix.relative(srcPrefixed, entry.path);
3489
+ const newPath = path5.posix.join(destPrefixed, relativePath);
3490
+ await client.query(
3491
+ `INSERT INTO ${this.#t("fs_entries")} (path, type, mode, size, mtime, symlink_target)
3492
+ VALUES ($1, $2, $3, $4, $5, $6)
3493
+ ON CONFLICT (path) DO UPDATE SET type = EXCLUDED.type, mode = EXCLUDED.mode, size = EXCLUDED.size, mtime = EXCLUDED.mtime, symlink_target = EXCLUDED.symlink_target`,
3494
+ [
3495
+ newPath,
3496
+ entry.type,
3497
+ entry.mode,
3498
+ entry.size,
3499
+ Date.now(),
3500
+ entry.symlink_target
3501
+ ]
3502
+ );
3503
+ if (entry.type === "file") {
3504
+ await client.query(
3505
+ `DELETE FROM ${this.#t("fs_chunks")} WHERE path = $1`,
3506
+ [newPath]
3507
+ );
3508
+ const chunksResult = await client.query(
3509
+ `SELECT chunk_index, data FROM ${this.#t("fs_chunks")} WHERE path = $1`,
3510
+ [entry.path]
3511
+ );
3512
+ for (const chunk of chunksResult.rows) {
3513
+ await client.query(
3514
+ `INSERT INTO ${this.#t("fs_chunks")} (path, chunk_index, data) VALUES ($1, $2, $3)`,
3515
+ [newPath, chunk.chunk_index, chunk.data]
3516
+ );
3517
+ }
3518
+ }
3519
+ }
3520
+ await client.query(
3521
+ `DELETE FROM ${this.#t("fs_entries")} WHERE path = $1 OR path LIKE $1 || '/%'`,
3522
+ [srcPrefixed]
3523
+ );
3524
+ } else {
3525
+ await client.query(
3526
+ `INSERT INTO ${this.#t("fs_entries")} (path, type, mode, size, mtime, symlink_target)
3527
+ VALUES ($1, $2, $3, $4, $5, $6)
3528
+ ON CONFLICT (path) DO UPDATE SET type = EXCLUDED.type, mode = EXCLUDED.mode, size = EXCLUDED.size, mtime = EXCLUDED.mtime, symlink_target = EXCLUDED.symlink_target`,
3529
+ [
3530
+ destPrefixed,
3531
+ srcEntry.type,
3532
+ srcEntry.mode,
3533
+ srcEntry.size,
3534
+ Date.now(),
3535
+ srcEntry.symlink_target
3536
+ ]
3537
+ );
3538
+ if (srcEntry.type === "file") {
3539
+ await client.query(
3540
+ `DELETE FROM ${this.#t("fs_chunks")} WHERE path = $1`,
3541
+ [destPrefixed]
3542
+ );
3543
+ const chunksResult = await client.query(
3544
+ `SELECT chunk_index, data FROM ${this.#t("fs_chunks")} WHERE path = $1`,
3545
+ [srcPrefixed]
3546
+ );
3547
+ for (const chunk of chunksResult.rows) {
3548
+ await client.query(
3549
+ `INSERT INTO ${this.#t("fs_chunks")} (path, chunk_index, data) VALUES ($1, $2, $3)`,
3550
+ [destPrefixed, chunk.chunk_index, chunk.data]
3551
+ );
3552
+ }
3553
+ }
3554
+ await client.query(
3555
+ `DELETE FROM ${this.#t("fs_entries")} WHERE path = $1`,
3556
+ [srcPrefixed]
3557
+ );
3558
+ }
3559
+ });
3560
+ }
3561
+ resolvePath(base, relativePath) {
3562
+ return path5.posix.resolve(base, relativePath);
3563
+ }
3564
+ getAllPaths() {
3565
+ throw new Error(
3566
+ "getAllPaths() is not supported in PostgresFs - use getAllPathsAsync() instead"
3567
+ );
3568
+ }
3569
+ async getAllPathsAsync() {
3570
+ const rows = await this.#query(
3571
+ `SELECT path FROM ${this.#t("fs_entries")} ORDER BY path`
3572
+ );
3573
+ return rows.map((row) => row.path);
3574
+ }
3575
+ async realpath(filePath) {
3576
+ const normalized = this.#normalizePath(filePath);
3577
+ const prefixed = this.#prefixPath(normalized);
3578
+ const resolved = await this.#resolveSymlink(prefixed);
3579
+ const rows = await this.#query(
3580
+ `SELECT EXISTS(SELECT 1 FROM ${this.#t("fs_entries")} WHERE path = $1) AS exists`,
3581
+ [resolved]
3582
+ );
3583
+ if (!rows[0].exists) {
3584
+ throw new Error(`ENOENT: no such file or directory: ${filePath}`);
3585
+ }
3586
+ return this.#unprefixPath(resolved);
3587
+ }
3588
+ async utimes(filePath, _atime, mtime) {
3589
+ const normalized = this.#normalizePath(filePath);
3590
+ const prefixed = this.#prefixPath(normalized);
3591
+ const resolved = await this.#resolveSymlink(prefixed);
3592
+ const result = await this.#exec(
3593
+ `UPDATE ${this.#t("fs_entries")} SET mtime = $1 WHERE path = $2`,
3594
+ [mtime.getTime(), resolved]
3595
+ );
3596
+ if (result === 0) {
3597
+ throw new Error(`ENOENT: no such file or directory: ${filePath}`);
3598
+ }
3599
+ }
3600
+ async chmod(filePath, mode) {
3601
+ const normalized = this.#normalizePath(filePath);
3602
+ const prefixed = this.#prefixPath(normalized);
3603
+ const result = await this.#exec(
3604
+ `UPDATE ${this.#t("fs_entries")} SET mode = $1 WHERE path = $2`,
3605
+ [mode, prefixed]
3606
+ );
3607
+ if (result === 0) {
3608
+ throw new Error(`ENOENT: no such file or directory: ${filePath}`);
3609
+ }
3610
+ }
3611
+ async symlink(target, linkPath) {
3612
+ const normalized = this.#normalizePath(linkPath);
3613
+ const prefixed = this.#prefixPath(normalized);
3614
+ const existingRows = await this.#query(
3615
+ `SELECT EXISTS(SELECT 1 FROM ${this.#t("fs_entries")} WHERE path = $1) AS exists`,
3616
+ [prefixed]
3617
+ );
3618
+ if (existingRows[0].exists) {
3619
+ throw new Error(`EEXIST: file already exists: ${linkPath}`);
3620
+ }
3621
+ await this.#useTransaction(async (client) => {
3622
+ await this.#ensureParentExists(prefixed, client);
3623
+ await client.query(
3624
+ `INSERT INTO ${this.#t("fs_entries")} (path, type, mode, size, mtime, symlink_target)
3625
+ VALUES ($1, 'symlink', 511, 0, $2, $3)`,
3626
+ [prefixed, Date.now(), target]
3627
+ );
3628
+ });
3629
+ }
3630
+ async link(existingPath, newPath) {
3631
+ const srcNormalized = this.#normalizePath(existingPath);
3632
+ const destNormalized = this.#normalizePath(newPath);
3633
+ const srcPrefixed = this.#prefixPath(srcNormalized);
3634
+ const destPrefixed = this.#prefixPath(destNormalized);
3635
+ const srcRows = await this.#query(
3636
+ `SELECT * FROM ${this.#t("fs_entries")} WHERE path = $1`,
3637
+ [srcPrefixed]
3638
+ );
3639
+ const srcEntry = srcRows[0];
3640
+ if (!srcEntry) {
3641
+ throw new Error(`ENOENT: no such file or directory: ${existingPath}`);
3642
+ }
3643
+ if (srcEntry.type !== "file") {
3644
+ throw new Error(`link: not supported for directories: ${existingPath}`);
3645
+ }
3646
+ const existingRows = await this.#query(
3647
+ `SELECT EXISTS(SELECT 1 FROM ${this.#t("fs_entries")} WHERE path = $1) AS exists`,
3648
+ [destPrefixed]
3649
+ );
3650
+ if (existingRows[0].exists) {
3651
+ throw new Error(`EEXIST: file already exists: ${newPath}`);
3652
+ }
3653
+ await this.#useTransaction(async (client) => {
3654
+ await this.#ensureParentExists(destPrefixed, client);
3655
+ await client.query(
3656
+ `INSERT INTO ${this.#t("fs_entries")} (path, type, mode, size, mtime)
3657
+ VALUES ($1, 'file', $2, $3, $4)`,
3658
+ [destPrefixed, srcEntry.mode, srcEntry.size, Date.now()]
3659
+ );
3660
+ const chunksResult = await client.query(
3661
+ `SELECT chunk_index, data FROM ${this.#t("fs_chunks")} WHERE path = $1`,
3662
+ [srcPrefixed]
3663
+ );
3664
+ for (const chunk of chunksResult.rows) {
3665
+ await client.query(
3666
+ `INSERT INTO ${this.#t("fs_chunks")} (path, chunk_index, data) VALUES ($1, $2, $3)`,
3667
+ [destPrefixed, chunk.chunk_index, chunk.data]
3668
+ );
3669
+ }
3670
+ });
3671
+ }
3672
+ async readlink(linkPath) {
3673
+ const normalized = this.#normalizePath(linkPath);
3674
+ const prefixed = this.#prefixPath(normalized);
3675
+ const rows = await this.#query(
3676
+ `SELECT type, symlink_target FROM ${this.#t("fs_entries")} WHERE path = $1`,
3677
+ [prefixed]
3678
+ );
3679
+ const entry = rows[0];
3680
+ if (!entry) {
3681
+ throw new Error(`ENOENT: no such file or directory: ${linkPath}`);
3682
+ }
3683
+ if (entry.type !== "symlink") {
3684
+ throw new Error(`readlink: not a symbolic link: ${linkPath}`);
3685
+ }
3686
+ return entry.symlink_target;
3687
+ }
3688
+ };
3689
+
3690
+ // packages/text2sql/src/lib/fs/scoped-fs.ts
3691
+ var ScopedFs = class {
3692
+ #base;
3693
+ #prefix;
3694
+ constructor(options) {
3695
+ this.#base = options.base;
3696
+ this.#prefix = options.prefix.replace(/\/$/, "");
3697
+ }
3698
+ #scope(path6) {
3699
+ return `${this.#prefix}${path6}`;
3700
+ }
3701
+ #unscope(path6) {
3702
+ if (path6 === this.#prefix) {
3703
+ return "/";
3704
+ }
3705
+ if (path6.startsWith(this.#prefix + "/")) {
3706
+ return path6.slice(this.#prefix.length) || "/";
3707
+ }
3708
+ return path6;
3709
+ }
3710
+ async writeFile(path6, content, options) {
3711
+ await this.#base.writeFile(this.#scope(path6), content, options);
3712
+ }
3713
+ async appendFile(path6, content, options) {
3714
+ await this.#base.appendFile(this.#scope(path6), content, options);
3715
+ }
3716
+ async mkdir(path6, options) {
3717
+ return this.#base.mkdir(this.#scope(path6), options);
3718
+ }
3719
+ async rm(path6, options) {
3720
+ await this.#base.rm(this.#scope(path6), options);
3721
+ }
3722
+ async cp(src, dest, options) {
3723
+ await this.#base.cp(this.#scope(src), this.#scope(dest), options);
3724
+ }
3725
+ async mv(src, dest) {
3726
+ await this.#base.mv(this.#scope(src), this.#scope(dest));
3727
+ }
3728
+ async chmod(path6, mode) {
3729
+ return this.#base.chmod(this.#scope(path6), mode);
3730
+ }
3731
+ async symlink(target, linkPath) {
3732
+ await this.#base.symlink(target, this.#scope(linkPath));
3733
+ }
3734
+ async link(existingPath, newPath) {
3735
+ await this.#base.link(this.#scope(existingPath), this.#scope(newPath));
3736
+ }
3737
+ readFile(path6, options) {
3738
+ return this.#base.readFile(this.#scope(path6), options);
3739
+ }
3740
+ readFileBuffer(path6) {
3741
+ return this.#base.readFileBuffer(this.#scope(path6));
3742
+ }
3743
+ stat(path6) {
3744
+ return this.#base.stat(this.#scope(path6));
3745
+ }
3746
+ lstat(path6) {
3747
+ return this.#base.lstat(this.#scope(path6));
3748
+ }
3749
+ readdir(path6) {
3750
+ return this.#base.readdir(this.#scope(path6));
3751
+ }
3752
+ readdirWithFileTypes(path6) {
3753
+ return this.#base.readdirWithFileTypes(this.#scope(path6));
3754
+ }
3755
+ exists(path6) {
3756
+ return this.#base.exists(this.#scope(path6));
3757
+ }
3758
+ readlink(path6) {
3759
+ return this.#base.readlink(this.#scope(path6));
3760
+ }
3761
+ realpath(path6) {
3762
+ return this.#base.realpath(this.#scope(path6)).then((p) => this.#unscope(p));
3763
+ }
3764
+ utimes(path6, atime, mtime) {
3765
+ return this.#base.utimes(this.#scope(path6), atime, mtime);
3766
+ }
3767
+ resolvePath(base, relativePath) {
3768
+ return this.#base.resolvePath(base, relativePath);
3769
+ }
3770
+ getAllPaths() {
3771
+ const allPaths = this.#base.getAllPaths?.() ?? [];
3772
+ return allPaths.filter((p) => p.startsWith(this.#prefix)).map((p) => p.slice(this.#prefix.length) || "/");
3773
+ }
3774
+ };
3775
+
3776
+ // packages/text2sql/src/lib/fs/tracked-fs.ts
3777
+ var TrackedFs = class {
3778
+ #base;
3779
+ #createdFiles = /* @__PURE__ */ new Set();
3780
+ constructor(base) {
3781
+ this.#base = base;
3782
+ }
3783
+ getCreatedFiles() {
3784
+ return [...this.#createdFiles];
3785
+ }
3786
+ async writeFile(path6, content, options) {
3787
+ await this.#base.writeFile(path6, content, options);
3788
+ this.#createdFiles.add(path6);
3789
+ }
3790
+ async appendFile(path6, content, options) {
3791
+ await this.#base.appendFile(path6, content, options);
3792
+ this.#createdFiles.add(path6);
3793
+ }
3794
+ async mkdir(path6, options) {
3795
+ return this.#base.mkdir(path6, options);
3796
+ }
3797
+ async rm(path6, options) {
3798
+ await this.#base.rm(path6, options);
3799
+ this.#createdFiles.delete(path6);
3800
+ if (options?.recursive) {
3801
+ const prefix = path6.endsWith("/") ? path6 : path6 + "/";
3802
+ for (const file of this.#createdFiles) {
3803
+ if (file.startsWith(prefix)) {
3804
+ this.#createdFiles.delete(file);
3805
+ }
3806
+ }
3807
+ }
3808
+ }
3809
+ async cp(src, dest, options) {
3810
+ await this.#base.cp(src, dest, options);
3811
+ this.#createdFiles.add(dest);
3812
+ }
3813
+ async mv(src, dest) {
3814
+ await this.#base.mv(src, dest);
2903
3815
  this.#createdFiles.delete(src);
2904
3816
  this.#createdFiles.add(dest);
2905
3817
  }
2906
- async chmod(path5, mode) {
2907
- return this.#base.chmod(path5, mode);
3818
+ async chmod(path6, mode) {
3819
+ return this.#base.chmod(path6, mode);
2908
3820
  }
2909
3821
  async symlink(target, linkPath) {
2910
3822
  await this.#base.symlink(target, linkPath);
@@ -2914,29 +3826,29 @@ var TrackedFs = class {
2914
3826
  await this.#base.link(existingPath, newPath);
2915
3827
  this.#createdFiles.add(newPath);
2916
3828
  }
2917
- readFile(path5, options) {
2918
- return this.#base.readFile(path5, options);
3829
+ readFile(path6, options) {
3830
+ return this.#base.readFile(path6, options);
2919
3831
  }
2920
- readFileBuffer(path5) {
2921
- return this.#base.readFileBuffer(path5);
3832
+ readFileBuffer(path6) {
3833
+ return this.#base.readFileBuffer(path6);
2922
3834
  }
2923
- stat(path5) {
2924
- return this.#base.stat(path5);
3835
+ stat(path6) {
3836
+ return this.#base.stat(path6);
2925
3837
  }
2926
- lstat(path5) {
2927
- return this.#base.lstat(path5);
3838
+ lstat(path6) {
3839
+ return this.#base.lstat(path6);
2928
3840
  }
2929
- readdir(path5) {
2930
- return this.#base.readdir(path5);
3841
+ readdir(path6) {
3842
+ return this.#base.readdir(path6);
2931
3843
  }
2932
- readdirWithFileTypes(path5) {
2933
- return this.#base.readdirWithFileTypes(path5);
3844
+ readdirWithFileTypes(path6) {
3845
+ return this.#base.readdirWithFileTypes(path6);
2934
3846
  }
2935
- exists(path5) {
2936
- return this.#base.exists(path5);
3847
+ exists(path6) {
3848
+ return this.#base.exists(path6);
2937
3849
  }
2938
- readlink(path5) {
2939
- return this.#base.readlink(path5);
3850
+ readlink(path6) {
3851
+ return this.#base.readlink(path6);
2940
3852
  }
2941
3853
  resolvePath(base, relativePath) {
2942
3854
  return this.#base.resolvePath(base, relativePath);
@@ -2944,11 +3856,11 @@ var TrackedFs = class {
2944
3856
  getAllPaths() {
2945
3857
  return this.#base.getAllPaths?.() ?? [];
2946
3858
  }
2947
- realpath(path5) {
2948
- return this.#base.realpath(path5);
3859
+ realpath(path6) {
3860
+ return this.#base.realpath(path6);
2949
3861
  }
2950
- utimes(path5, atime, mtime) {
2951
- return this.#base.utimes(path5, atime, mtime);
3862
+ utimes(path6, atime, mtime) {
3863
+ return this.#base.utimes(path6, atime, mtime);
2952
3864
  }
2953
3865
  };
2954
3866
 
@@ -3433,15 +4345,24 @@ var Text2Sql = class {
3433
4345
  return producer.toPairs();
3434
4346
  }
3435
4347
  async chat(messages) {
4348
+ if (messages.length === 0) {
4349
+ throw new Error("messages must not be empty");
4350
+ }
3436
4351
  const trackedFs = new TrackedFs(this.#config.filesystem);
3437
4352
  const context = this.#config.context(
3438
4353
  ...guidelines(this.#config.teachingsOptions),
3439
4354
  ...await this.index()
3440
4355
  );
3441
- const userMsg = messages.at(-1);
3442
- if (userMsg) {
3443
- context.set(message(userMsg));
4356
+ const lastMessage = messages[messages.length - 1];
4357
+ let assistantMsgId;
4358
+ if (lastMessage.role === "assistant") {
4359
+ context.set(message(lastMessage));
4360
+ await context.save({ branch: false });
4361
+ assistantMsgId = lastMessage.id;
4362
+ } else {
4363
+ context.set(message(lastMessage));
3444
4364
  await context.save();
4365
+ assistantMsgId = generateId();
3445
4366
  }
3446
4367
  const { mounts: skillMounts } = context.getSkillMounts();
3447
4368
  const { tools: tools2 } = await createResultTools({
@@ -3449,7 +4370,6 @@ var Text2Sql = class {
3449
4370
  skillMounts,
3450
4371
  filesystem: trackedFs
3451
4372
  });
3452
- const assistantMsgId = generateId();
3453
4373
  const chatAgent = agent2({
3454
4374
  name: "text2sql",
3455
4375
  model: this.#config.model,
@@ -3516,40 +4436,6 @@ var Text2Sql = class {
3516
4436
  }
3517
4437
  });
3518
4438
  }
3519
- async developer(messages) {
3520
- const context = this.#config.context(
3521
- ...guidelines(this.#config.teachingsOptions),
3522
- ...developer_agent_default.fragments,
3523
- ...await this.index()
3524
- );
3525
- const userMsg = messages.at(-1);
3526
- if (userMsg) {
3527
- context.set(message(userMsg));
3528
- await context.save();
3529
- }
3530
- const developerAgent = agent2({
3531
- name: "developer",
3532
- model: this.#config.model,
3533
- context,
3534
- tools: developer_agent_default.tools
3535
- });
3536
- const result = await developerAgent.stream({
3537
- adapter: this.#config.adapter
3538
- });
3539
- return result.toUIMessageStream({
3540
- onError: (error) => this.#formatError(error),
3541
- sendStart: true,
3542
- sendFinish: true,
3543
- sendReasoning: true,
3544
- sendSources: true,
3545
- generateMessageId: generateId,
3546
- onFinish: async ({ responseMessage }) => {
3547
- context.set(assistant(responseMessage));
3548
- await context.save();
3549
- await context.trackUsage(await result.totalUsage);
3550
- }
3551
- });
3552
- }
3553
4439
  #formatError(error) {
3554
4440
  if (NoSuchToolError.isInstance(error)) {
3555
4441
  return "The model tried to call an unknown tool.";
@@ -3571,6 +4457,7 @@ export {
3571
4457
  JsonCache,
3572
4458
  MssqlFs,
3573
4459
  Point,
4460
+ PostgresFs,
3574
4461
  SQLValidationError,
3575
4462
  ScopedFs,
3576
4463
  SqliteFs,