@deepagents/text2sql 1.0.0 → 2.1.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
@@ -26,7 +26,7 @@ npm install mysql2 # MySQL / MariaDB
26
26
  npm install @google-cloud/bigquery # BigQuery
27
27
  ```
28
28
 
29
- Requires Node.js LTS (20+).
29
+ Requires Node.js LTS
30
30
 
31
31
  ## Quick Start
32
32
 
@@ -95,10 +95,9 @@ Text2SQL works with any model provider supported by the [Vercel AI SDK](https://
95
95
 
96
96
  ## Building a Conversational Agent
97
97
 
98
- `Text2Sql` ships only the SQL-aware primitives: `toSql`, `toPairs`,
99
- `AdapterIndexer`, and `instructions()`. You build the chat agent yourself by
100
- composing a `ContextEngine`, a sandbox, and `agent` + `chat` from
101
- `@deepagents/context`:
98
+ `Text2Sql` owns SQL generation and schema indexing, but you own the chat loop.
99
+ Build the agent by composing a `ContextEngine`, a sandbox, and `agent` +
100
+ `chat` from `@deepagents/context`:
102
101
 
103
102
  ```typescript
104
103
  import {
@@ -123,6 +122,14 @@ const context = new ContextEngine({
123
122
 
124
123
  const sandbox = await createContainerTool({
125
124
  installers: [npm('@deepagents/text2sql', { ensureRuntime: true })],
125
+ volumes: [
126
+ {
127
+ type: 'bind',
128
+ hostPath: process.cwd(),
129
+ containerPath: '/workspace',
130
+ readOnly: true,
131
+ },
132
+ ],
126
133
  env: {
127
134
  TEXT2SQL_ADAPTERS: '/workspace/text2sql-adapters.ts',
128
135
  },
@@ -154,6 +161,10 @@ for await (const chunk of stream) {
154
161
  }
155
162
  ```
156
163
 
164
+ The `/workspace/text2sql-adapters.ts` module must exist in the sandbox and
165
+ default-export your adapter map. Mount your project into `/workspace` (as above)
166
+ or upload/write that module before you call `sql index` or `chat()`.
167
+
157
168
  `instructions()` returns the SQL-flavored system fragments (policies, workflows,
158
169
  clarifications, style guides) — spread them into `context.set()` alongside the
159
170
  schema fragments returned by `sql index`. Add or replace them with your
@@ -162,11 +173,25 @@ own domain fragments as needed.
162
173
  ## Advanced: SQL CLI in Sandboxes
163
174
 
164
175
  `sql validate <db> "..."` and `sql run <db> "..."` are real commands from the
165
- `@deepagents/text2sql` package. `sql index [adapter ...]` writes schema
166
- fragments plus progress events for chat setup. Install the package inside the
167
- sandbox and set `TEXT2SQL_ADAPTERS` to a module whose default export is
168
- `Record<string, Adapter>`. Missing `sql` means the sandbox was not prepared
169
- correctly.
176
+ `@deepagents/text2sql` package. `sql index` writes schema fragments plus
177
+ progress events for chat setup, indexing all configured adapters by default
178
+ (same as `--all`) unless adapter names are provided.
179
+
180
+ Install the package inside the sandbox and set `TEXT2SQL_ADAPTERS` to a module
181
+ whose default export is `Record<string, Adapter>`. Missing `sql` means the
182
+ sandbox was not prepared correctly.
183
+
184
+ `sql index` output details:
185
+
186
+ - `stdout`: JSON manifest with `fragmentsPath`, `eventsPath`, adapters, and
187
+ fragment count.
188
+ - `--verbose pretty` or `--verbose json`: mirrors progress events to `stderr`
189
+ while keeping `stdout` as the manifest.
190
+ - `--out-dir <path>`: writes artifacts under that path (default:
191
+ `$TEXT2SQL_OUT_DIR` or `./sql`).
192
+
193
+ Set `TEXT2SQL_INDEX_VERSION` to manage cache invalidation across runs. Cache
194
+ keys are `index-<version>-<adapter>`, so bump the version when schema changes.
170
195
 
171
196
  Spread `createSqlCommandHooks({ adapters })` into `createBashTool()` or
172
197
  `createContainerTool()` for model-driven bash calls. The before hook preserves
@@ -339,6 +364,7 @@ Full documentation available at [januarylabs.github.io/deepagents](https://janua
339
364
 
340
365
  - [Getting Started](https://januarylabs.github.io/deepagents/docs/text2sql/getting-started)
341
366
  - [Generate SQL](https://januarylabs.github.io/deepagents/docs/text2sql/to-sql)
367
+ - [Agent + SQLite Store Recipe](https://januarylabs.github.io/deepagents/docs/text2sql/recipes/agent-sqlite-store)
342
368
  - [Teach the System](https://januarylabs.github.io/deepagents/docs/text2sql/teach-the-system)
343
369
  - [Build Conversations](https://januarylabs.github.io/deepagents/docs/text2sql/build-conversations)
344
370
  - [Grounding](https://januarylabs.github.io/deepagents/docs/text2sql/grounding)
package/dist/bin/sql.js CHANGED
@@ -4,6 +4,15 @@
4
4
  import { cac } from "cac";
5
5
 
6
6
  // packages/text2sql/src/bin/command.ts
7
+ import * as path from "node:path";
8
+ var OUT_DIR_OPTION = {
9
+ flag: "--out-dir <path>",
10
+ description: "Output directory (default: $TEXT2SQL_OUT_DIR or ./sql)"
11
+ };
12
+ function resolveOutputDir(ctx, options) {
13
+ const explicit = typeof options.outDir === "string" ? options.outDir : void 0;
14
+ return path.resolve(ctx.cwd, explicit ?? ctx.env.TEXT2SQL_OUT_DIR ?? "./sql");
15
+ }
7
16
  var CommandError = class extends Error {
8
17
  command;
9
18
  exitCode;
@@ -13,15 +22,11 @@ var CommandError = class extends Error {
13
22
  this.command = command;
14
23
  this.exitCode = exitCode;
15
24
  }
16
- toResult() {
17
- return {
18
- stdout: "",
19
- stderr: `sql ${this.command}: ${this.message}
20
- `,
21
- exitCode: this.exitCode
22
- };
23
- }
24
25
  };
26
+ function renderCommandError(error, stderr) {
27
+ stderr.write(`sql ${error.command}: ${error.message}
28
+ `);
29
+ }
25
30
  function errorMessage(error) {
26
31
  return error instanceof Error ? error.message : String(error);
27
32
  }
@@ -73,9 +78,15 @@ var SqlQueryCommand = class extends SqlCommand {
73
78
  };
74
79
 
75
80
  // packages/text2sql/src/bin/commands/index.ts
76
- import { appendFileSync } from "node:fs";
77
- import { mkdir, writeFile as writeFile2 } from "node:fs/promises";
78
- import * as path2 from "node:path";
81
+ import { constants, createWriteStream } from "node:fs";
82
+ import {
83
+ mkdir,
84
+ open,
85
+ stat,
86
+ writeFile as writeFile2
87
+ } from "node:fs/promises";
88
+ import * as path3 from "node:path";
89
+ import { finished } from "node:stream/promises";
79
90
  import { v7 } from "uuid";
80
91
 
81
92
  // packages/text2sql/src/lib/adapter-index.ts
@@ -186,12 +197,12 @@ import { createHash } from "node:crypto";
186
197
  import { existsSync } from "node:fs";
187
198
  import { readFile, writeFile } from "node:fs/promises";
188
199
  import { tmpdir } from "node:os";
189
- import path from "node:path";
200
+ import path2 from "node:path";
190
201
  var FileCache = class {
191
202
  path;
192
203
  constructor(watermark, extension = ".txt") {
193
204
  const hash = createHash("md5").update(watermark).digest("hex");
194
- this.path = path.join(tmpdir(), `text2sql-${hash}${extension}`);
205
+ this.path = path2.join(tmpdir(), `text2sql-${hash}${extension}`);
195
206
  }
196
207
  async get() {
197
208
  if (existsSync(this.path)) {
@@ -369,7 +380,7 @@ var AdapterIndexer = class {
369
380
  return configured;
370
381
  }
371
382
  if (this.#version) {
372
- return `introspection-${this.#version}-${name}`;
383
+ return `index-${this.#version}-${name}`;
373
384
  }
374
385
  return void 0;
375
386
  }
@@ -391,44 +402,82 @@ function errorMessage2(error) {
391
402
  return error instanceof Error ? error.message : String(error);
392
403
  }
393
404
 
405
+ // packages/text2sql/src/bin/commands/indexing-formatter.ts
406
+ var REDUNDANT_MESSAGE_TYPES = /* @__PURE__ */ new Set([
407
+ "adapter:start",
408
+ "adapter:end"
409
+ ]);
410
+ function formatPretty(event) {
411
+ const head = event.adapter ? `${event.type} ${event.adapter}` : event.type;
412
+ const parts = [`[${head}]`];
413
+ if (event.phase) parts.push(`phase=${event.phase}`);
414
+ if (event.table) parts.push(`table=${event.table}`);
415
+ if (typeof event.current === "number" && typeof event.total === "number" && !event.type.endsWith(":start")) {
416
+ parts.push(`${event.current}/${event.total}`);
417
+ }
418
+ if (event.cached) parts.push("cached");
419
+ if (!REDUNDANT_MESSAGE_TYPES.has(event.type)) parts.push(event.message);
420
+ return parts.join(" ");
421
+ }
422
+
394
423
  // packages/text2sql/src/bin/commands/index.ts
395
424
  var IndexCommand = class extends SqlCommand {
396
425
  name = "index";
397
426
  description = "Index adapter schemas and write context artifacts";
398
427
  args = "[...adapters]";
399
- usage = "[--all] [adapter ...]";
400
- helpDisplay = "[--all] [adapter ...]";
428
+ usage = "[--all] [--verbose [pretty|json]] [adapter ...]";
429
+ helpDisplay = "[--all] [--verbose] [adapter ...]";
401
430
  options = [
402
- { flag: "--all", description: "Index all adapters (default)" }
431
+ { flag: "--all", description: "Index all adapters (default)" },
432
+ {
433
+ flag: "-v, --verbose [format]",
434
+ description: "Mirror progress events to stderr (format: pretty (default) | json)"
435
+ },
436
+ OUT_DIR_OPTION
403
437
  ];
404
438
  async execute(ctx, args, options) {
405
439
  const adapterNames = args[0] ?? [];
406
440
  const requested = options.all ? [] : adapterNames;
407
441
  const names = this.resolveNames(ctx.adapters, requested);
408
- const outputDir = path2.resolve(ctx.cwd, "./sql");
442
+ const verbose = this.resolveVerbose(options.verbose);
443
+ const outputDir = resolveOutputDir(ctx, options);
409
444
  const id = v7();
410
- const fragmentsPath = path2.join(outputDir, `index-${id}.json`);
411
- const eventsPath = path2.join(outputDir, `index-${id}.events.ndjson`);
445
+ const fragmentsPath = path3.join(outputDir, `index-${id}.json`);
446
+ const explicitEventsPath = resolveExplicitEventsPath(ctx);
447
+ const eventsPath = explicitEventsPath ?? path3.join(outputDir, `index-${id}.events.ndjson`);
412
448
  try {
413
449
  await mkdir(outputDir, { recursive: true });
414
- await writeFile2(eventsPath, "");
415
- const indexer = new AdapterIndexer({ adapters: ctx.adapters });
416
- const fragments = await indexer.index({
417
- adapterNames: names,
418
- onProgress: (event) => appendProgressEvent(eventsPath, event)
419
- });
420
- await writeFile2(fragmentsPath, JSON.stringify(fragments, null, 2));
421
- const manifest = {
422
- fragmentsPath,
450
+ if (explicitEventsPath) {
451
+ await mkdir(path3.dirname(explicitEventsPath), { recursive: true });
452
+ }
453
+ const eventsStream = await openEventsWriter(
423
454
  eventsPath,
424
- adapters: names,
425
- fragments: countSchemaFragments(fragments)
426
- };
427
- return {
428
- stdout: JSON.stringify(manifest, null, 2) + "\n",
429
- stderr: "",
430
- exitCode: 0
431
- };
455
+ explicitEventsPath,
456
+ ctx.stderr
457
+ );
458
+ try {
459
+ const indexer = new AdapterIndexer({
460
+ adapters: ctx.adapters,
461
+ version: ctx.env.TEXT2SQL_INDEX_VERSION
462
+ });
463
+ const fragments = await indexer.index({
464
+ adapterNames: names,
465
+ onProgress: createProgressHandler(eventsStream, verbose, ctx.stderr)
466
+ });
467
+ await writeFile2(fragmentsPath, JSON.stringify(fragments, null, 2));
468
+ const manifest = {
469
+ fragmentsPath,
470
+ eventsPath,
471
+ adapters: names,
472
+ fragments: countSchemaFragments(fragments)
473
+ };
474
+ ctx.stdout.write(JSON.stringify(manifest, null, 2) + "\n");
475
+ return 0;
476
+ } finally {
477
+ eventsStream.end();
478
+ await finished(eventsStream).catch(() => {
479
+ });
480
+ }
432
481
  } catch (error) {
433
482
  this.fail(errorMessage(error));
434
483
  }
@@ -444,15 +493,82 @@ var IndexCommand = class extends SqlCommand {
444
493
  }
445
494
  return names;
446
495
  }
496
+ resolveVerbose(value) {
497
+ if (value === void 0 || value === false) return null;
498
+ if (value === true || value === "" || value === "pretty") return "pretty";
499
+ if (value === "json") return "json";
500
+ this.fail(
501
+ `invalid --verbose value "${String(value)}". Expected "pretty" or "json".`
502
+ );
503
+ }
447
504
  };
448
- function appendProgressEvent(eventsPath, event) {
449
- appendFileSync(
450
- eventsPath,
451
- JSON.stringify({
452
- ...event,
453
- timestampMs: event.timestampMs ?? Date.now()
454
- }) + "\n"
455
- );
505
+ function createProgressHandler(eventsStream, verbose, stderr) {
506
+ return (event) => {
507
+ const stamped = { ...event, timestampMs: event.timestampMs ?? Date.now() };
508
+ const json = JSON.stringify(stamped) + "\n";
509
+ eventsStream.write(json);
510
+ if (verbose === "pretty") stderr.write(formatPretty(stamped) + "\n");
511
+ else if (verbose === "json") stderr.write(json);
512
+ };
513
+ }
514
+ function resolveExplicitEventsPath(ctx) {
515
+ const explicit = ctx.env.TEXT2SQL_INDEX_EVENTS_PATH?.trim();
516
+ return explicit ? path3.resolve(ctx.cwd, explicit) : null;
517
+ }
518
+ async function openEventsWriter(eventsPath, explicitEventsPath, stderr) {
519
+ const probe = explicitEventsPath ? await probeFifoWriter(explicitEventsPath, stderr) : null;
520
+ const stream = createWriteStream(eventsPath, {
521
+ flags: "a",
522
+ encoding: "utf-8"
523
+ });
524
+ try {
525
+ await waitForOpen(stream);
526
+ return stream;
527
+ } catch (err) {
528
+ stream.destroy();
529
+ throw err;
530
+ } finally {
531
+ await probe?.close().catch(() => {
532
+ });
533
+ }
534
+ }
535
+ async function probeFifoWriter(eventsPath, stderr) {
536
+ let stats;
537
+ try {
538
+ stats = await stat(eventsPath);
539
+ } catch (err) {
540
+ if (err.code === "ENOENT") return null;
541
+ throw err;
542
+ }
543
+ if (!stats.isFIFO()) return null;
544
+ try {
545
+ return await open(eventsPath, constants.O_WRONLY | constants.O_NONBLOCK);
546
+ } catch (err) {
547
+ if (err.code === "ENXIO") {
548
+ stderr.write(`sql index: waiting for reader on FIFO ${eventsPath}...
549
+ `);
550
+ return null;
551
+ }
552
+ throw err;
553
+ }
554
+ }
555
+ function waitForOpen(stream) {
556
+ return new Promise((resolve4, reject) => {
557
+ if (!stream.pending) {
558
+ resolve4();
559
+ return;
560
+ }
561
+ const onOpen = () => {
562
+ stream.off("error", onError);
563
+ resolve4();
564
+ };
565
+ const onError = (err) => {
566
+ stream.off("open", onOpen);
567
+ reject(err);
568
+ };
569
+ stream.once("open", onOpen);
570
+ stream.once("error", onError);
571
+ });
456
572
  }
457
573
  function countSchemaFragments(fragments) {
458
574
  return fragments.reduce((count, adapterFragment) => {
@@ -468,21 +584,16 @@ function dedupe(values) {
468
584
 
469
585
  // packages/text2sql/src/bin/commands/run.ts
470
586
  import { mkdir as mkdir2, writeFile as writeFile3 } from "node:fs/promises";
471
- import * as path3 from "node:path";
587
+ import * as path4 from "node:path";
472
588
  import { v7 as v72 } from "uuid";
473
589
  var RunCommand = class extends SqlQueryCommand {
474
590
  name = "run";
475
591
  description = "Execute query against <db> and store results";
476
592
  usage = '<db> "SELECT ..." [--out-dir <path>]';
477
593
  helpDisplay = '<db> "SELECT ..."';
478
- options = [
479
- {
480
- flag: "--out-dir <path>",
481
- description: "Directory for `sql run` result files (default: $TEXT2SQL_OUT_DIR or ./sql)"
482
- }
483
- ];
594
+ options = [OUT_DIR_OPTION];
484
595
  async afterValidation(ctx, adapter, sql, options) {
485
- const outputDir = this.resolveOutputDir(ctx, options);
596
+ const outputDir = resolveOutputDir(ctx, options);
486
597
  let rows;
487
598
  try {
488
599
  rows = await adapter.execute(sql);
@@ -492,7 +603,7 @@ var RunCommand = class extends SqlQueryCommand {
492
603
  if (!Array.isArray(rows)) {
493
604
  this.fail("adapter.execute must return an array of rows");
494
605
  }
495
- const outPath = path3.join(outputDir, `${v72()}.json`);
606
+ const outPath = path4.join(outputDir, `${v72()}.json`);
496
607
  try {
497
608
  await mkdir2(outputDir, { recursive: true });
498
609
  await writeFile3(outPath, JSON.stringify(rows, null, 2));
@@ -500,22 +611,13 @@ var RunCommand = class extends SqlQueryCommand {
500
611
  this.fail(errorMessage(error));
501
612
  }
502
613
  const columns = rows.length > 0 ? Object.keys(rows[0]) : [];
503
- return {
504
- stdout: [
505
- `results stored in ${outPath}`,
506
- `columns: ${columns.join(", ") || "(none)"}`,
507
- `rows: ${rows.length}`
508
- ].join("\n") + "\n",
509
- stderr: "",
510
- exitCode: 0
511
- };
512
- }
513
- resolveOutputDir(ctx, options) {
514
- const explicit = typeof options.outDir === "string" ? options.outDir : void 0;
515
- return path3.resolve(
516
- ctx.cwd,
517
- explicit ?? ctx.env.TEXT2SQL_OUT_DIR ?? "./sql"
518
- );
614
+ ctx.stdout.write(`results stored in ${outPath}
615
+ `);
616
+ ctx.stdout.write(`columns: ${columns.join(", ") || "(none)"}
617
+ `);
618
+ ctx.stdout.write(`rows: ${rows.length}
619
+ `);
620
+ return 0;
519
621
  }
520
622
  };
521
623
 
@@ -525,8 +627,9 @@ var ValidateCommand = class extends SqlQueryCommand {
525
627
  description = "Validate query syntax against <db>";
526
628
  usage = '<db> "SELECT ..."';
527
629
  helpDisplay = '<db> "SELECT ..."';
528
- async afterValidation() {
529
- return { stdout: "valid\n", stderr: "", exitCode: 0 };
630
+ async afterValidation(ctx) {
631
+ ctx.stdout.write("valid\n");
632
+ return 0;
530
633
  }
531
634
  };
532
635
 
@@ -603,7 +706,7 @@ cli.usage("<run|validate|index> ...").help(
603
706
  for (const command of commands) {
604
707
  registerCommand(cli, command);
605
708
  }
606
- process.exit(await runCli());
709
+ process.exitCode = await runCli();
607
710
  async function runCli() {
608
711
  try {
609
712
  cli.parse(process.argv, { run: false });
@@ -645,7 +748,9 @@ async function runCommand(command, positional, options) {
645
748
  ctx = {
646
749
  adapters: await loadAdapters(),
647
750
  cwd: process.cwd(),
648
- env: process.env
751
+ env: process.env,
752
+ stdout: process.stdout,
753
+ stderr: process.stderr
649
754
  };
650
755
  } catch (error) {
651
756
  process.stderr.write(`${errorMessage(error)}
@@ -653,12 +758,10 @@ async function runCommand(command, positional, options) {
653
758
  return 2;
654
759
  }
655
760
  try {
656
- const result = await command.execute(ctx, positional, options);
657
- writeCommandResult(result);
658
- return result.exitCode;
761
+ return await command.execute(ctx, positional, options);
659
762
  } catch (error) {
660
763
  if (error instanceof CommandError) {
661
- writeCommandResult(error.toResult());
764
+ renderCommandError(error, process.stderr);
662
765
  return error.exitCode;
663
766
  }
664
767
  throw error;
@@ -673,13 +776,6 @@ function rewriteCommandsHelp(body) {
673
776
  );
674
777
  }, body);
675
778
  }
676
- function writeCommandResult(result) {
677
- if (result.stdout) process.stdout.write(result.stdout);
678
- if (result.stderr) {
679
- const out = result.stderr.endsWith("\n") ? result.stderr : result.stderr + "\n";
680
- process.stderr.write(out);
681
- }
682
- }
683
779
  function writeCliError(error) {
684
780
  if (error instanceof Error && error.name === "CACError") {
685
781
  process.stderr.write(`sql: ${error.message}
@@ -688,7 +784,7 @@ function writeCliError(error) {
688
784
  return 2;
689
785
  }
690
786
  if (error instanceof CommandError) {
691
- writeCommandResult(error.toResult());
787
+ renderCommandError(error, process.stderr);
692
788
  return error.exitCode;
693
789
  }
694
790
  process.stderr.write(
package/dist/index.js CHANGED
@@ -1442,7 +1442,7 @@ var AdapterIndexer = class {
1442
1442
  return configured;
1443
1443
  }
1444
1444
  if (this.#version) {
1445
- return `introspection-${this.#version}-${name}`;
1445
+ return `index-${this.#version}-${name}`;
1446
1446
  }
1447
1447
  return void 0;
1448
1448
  }