@deepagents/text2sql 0.39.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.
Files changed (39) hide show
  1. package/README.md +157 -74
  2. package/dist/bin/sql.js +795 -0
  3. package/dist/index.d.ts +2 -3
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +3376 -3309
  6. package/dist/index.js.map +4 -4
  7. package/dist/lib/adapter-index.d.ts +35 -0
  8. package/dist/lib/adapter-index.d.ts.map +1 -0
  9. package/dist/lib/agents/question.agent.d.ts.map +1 -1
  10. package/dist/lib/agents/sql-transform-plugins.d.ts +3 -3
  11. package/dist/lib/agents/sql-transform-plugins.d.ts.map +1 -1
  12. package/dist/lib/agents/sql.agent.d.ts.map +1 -1
  13. package/dist/lib/instructions.d.ts +1 -1
  14. package/dist/lib/instructions.d.ts.map +1 -1
  15. package/dist/lib/sql-hooks.d.ts +15 -0
  16. package/dist/lib/sql-hooks.d.ts.map +1 -0
  17. package/dist/lib/sql-meta-hook.d.ts +20 -0
  18. package/dist/lib/sql-meta-hook.d.ts.map +1 -0
  19. package/dist/lib/sql-repair.d.ts +2 -0
  20. package/dist/lib/sql-repair.d.ts.map +1 -0
  21. package/dist/lib/sql-transform.d.ts +2 -0
  22. package/dist/lib/sql-transform.d.ts.map +1 -0
  23. package/dist/lib/sql.d.ts +5 -38
  24. package/dist/lib/sql.d.ts.map +1 -1
  25. package/dist/lib/synthesis/extractors/base-contextual-extractor.d.ts.map +1 -1
  26. package/dist/lib/synthesis/extractors/segmented-context-extractor.d.ts.map +1 -1
  27. package/dist/lib/synthesis/extractors/sql-extractor.d.ts.map +1 -1
  28. package/dist/lib/synthesis/index.js +59 -2
  29. package/dist/lib/synthesis/index.js.map +4 -4
  30. package/dist/lib/synthesis/synthesizers/breadth-evolver.d.ts.map +1 -1
  31. package/dist/lib/synthesis/synthesizers/depth-evolver.d.ts.map +1 -1
  32. package/dist/lib/synthesis/synthesizers/persona-generator.d.ts.map +1 -1
  33. package/package.json +9 -5
  34. package/dist/lib/agents/sql-command.d.ts +0 -11
  35. package/dist/lib/agents/sql-command.d.ts.map +0 -1
  36. package/dist/lib/demo.d.ts +0 -2
  37. package/dist/lib/demo.d.ts.map +0 -1
  38. package/dist/lib/sandbox.d.ts +0 -21
  39. package/dist/lib/sandbox.d.ts.map +0 -1
package/README.md CHANGED
@@ -26,24 +26,15 @@ 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
 
33
33
  ```typescript
34
34
  import { groq } from '@ai-sdk/groq';
35
- import { InMemoryFs } from 'just-bash';
36
35
  import pg from 'pg';
37
36
 
38
- import {
39
- ContextEngine,
40
- InMemoryContextStore,
41
- createBashTool,
42
- createRoutingSandbox,
43
- createVirtualSandbox,
44
- user,
45
- } from '@deepagents/context';
46
- import { Text2Sql, sqlSandboxExtension } from '@deepagents/text2sql';
37
+ import { Text2Sql } from '@deepagents/text2sql';
47
38
  import {
48
39
  Postgres,
49
40
  columnValues,
@@ -59,12 +50,6 @@ const pool = new pg.Pool({
59
50
  connectionString: process.env.DATABASE_URL,
60
51
  });
61
52
 
62
- const store = new InMemoryContextStore();
63
- const engine = new ContextEngine({
64
- store,
65
- chatId: 'chat-123',
66
- userId: 'user-456',
67
- });
68
53
  const adapter = new Postgres({
69
54
  execute: async (sql) => {
70
55
  const result = await pool.query(sql);
@@ -80,19 +65,11 @@ const adapter = new Postgres({
80
65
  columnValues(),
81
66
  ],
82
67
  });
83
- const sandbox = await createBashTool({
84
- sandbox: await createRoutingSandbox({
85
- backend: await createVirtualSandbox({ fs: new InMemoryFs() }),
86
- hostExtensions: [sqlSandboxExtension({ main: adapter })],
87
- }),
88
- });
89
68
 
90
69
  const text2sql = new Text2Sql({
91
70
  version: 'v1',
92
71
  model: groq('openai/gpt-oss-20b'),
93
- sandbox,
94
72
  adapters: { main: adapter },
95
- context: engine,
96
73
  });
97
74
 
98
75
  // Generate SQL
@@ -105,32 +82,131 @@ console.log(sql);
105
82
 
106
83
  The adapter-map key (`main` here) is the adapter name. Reuse that same key in
107
84
  `text2sql.toSql(..., 'main')` and in any `sql validate <db> "..."` /
108
- `sql run <db> "..."` sandbox calls.
85
+ `sql run <db> "..."` calls from a sandbox where the package CLI is installed.
109
86
 
110
87
  Adapter names must match `/^[A-Za-z_][A-Za-z0-9_]*$/`. If you build adapter
111
88
  maps dynamically, use `isValidAdapterName(name)` to check one key or
112
89
  `validateAdapterNames(names)` to fail fast before constructing `Text2Sql` or
113
- `sqlSandboxExtension`.
90
+ the sandbox-side adapter module used by the `sql` CLI.
114
91
 
115
92
  ## AI Model Providers
116
93
 
117
94
  Text2SQL works with any model provider supported by the [Vercel AI SDK](https://sdk.vercel.ai/docs), including OpenAI, Anthropic, Google, Groq, and more.
118
95
 
119
- ## Advanced: Composing Sandbox Extensions
96
+ ## Building a Conversational Agent
97
+
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`:
101
+
102
+ ```typescript
103
+ import {
104
+ ContextEngine,
105
+ type ContextFragment,
106
+ InMemoryContextStore,
107
+ agent,
108
+ chat,
109
+ createContainerTool,
110
+ errorRecoveryGuardrail,
111
+ npm,
112
+ user,
113
+ } from '@deepagents/context';
114
+ import { createSqlCommandHooks, instructions } from '@deepagents/text2sql';
115
+
116
+ const store = new InMemoryContextStore();
117
+ const context = new ContextEngine({
118
+ store,
119
+ chatId: 'chat-123',
120
+ userId: 'user-456',
121
+ });
122
+
123
+ const sandbox = await createContainerTool({
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
+ ],
133
+ env: {
134
+ TEXT2SQL_ADAPTERS: '/workspace/text2sql-adapters.ts',
135
+ },
136
+ ...createSqlCommandHooks({ adapters: { main: adapter } }),
137
+ });
138
+
139
+ const indexResult = await sandbox.sandbox.executeCommand('sql index');
140
+ if (indexResult.exitCode !== 0) throw new Error(indexResult.stderr);
141
+ const manifest = JSON.parse(indexResult.stdout) as { fragmentsPath: string };
142
+ const fragments = JSON.parse(
143
+ await sandbox.sandbox.readFile(manifest.fragmentsPath),
144
+ ) as ContextFragment[];
145
+ context.set(...instructions(), ...fragments);
146
+
147
+ const ai = agent({
148
+ name: 'sql-assistant',
149
+ sandbox,
150
+ model: groq('openai/gpt-oss-20b'),
151
+ context,
152
+ guardrails: [errorRecoveryGuardrail],
153
+ maxGuardrailRetries: 3,
154
+ });
155
+
156
+ await context.continue(user('Show me the top 10 customers by revenue'));
157
+ const stream = await chat(ai);
158
+
159
+ for await (const chunk of stream) {
160
+ // handle streaming response
161
+ }
162
+ ```
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
+
168
+ `instructions()` returns the SQL-flavored system fragments (policies, workflows,
169
+ clarifications, style guides) — spread them into `context.set()` alongside the
170
+ schema fragments returned by `sql index`. Add or replace them with your
171
+ own domain fragments as needed.
172
+
173
+ ## Advanced: SQL CLI in Sandboxes
120
174
 
121
- `Text2Sql` takes a caller-owned `sandbox`. `sqlSandboxExtension(adaptersMap)`
122
- returns a `SandboxExtension` that bundles the `sql` subcommand, transform
123
- plugins, and arg-repair hook. In the simplest case, that map is
124
- `{ main: adapter }`. Compose it (alongside any of your own extensions) via
125
- `createBashTool` + `createRoutingSandbox` + `createVirtualSandbox`.
126
- See [Caller-Owned Sandbox](https://januarylabs.github.io/deepagents/docs/text2sql/sqlv3)
127
- for composition patterns.
175
+ `sql validate <db> "..."` and `sql run <db> "..."` are real commands from the
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.
195
+
196
+ Spread `createSqlCommandHooks({ adapters })` into `createBashTool()` or
197
+ `createContainerTool()` for model-driven bash calls. The before hook preserves
198
+ the old virtual-command tolerance for common LLM quote mistakes, rewrites SQL
199
+ identifier backticks so bash does not run them as command substitutions, and
200
+ blocks raw database access so read-only and scope checks stay behind
201
+ `sql validate` / `sql run`. The after hook restores hidden `formattedSql`
202
+ metadata from the host adapter map without putting that concern into the real
203
+ CLI.
128
204
 
129
205
  ## Fragments
130
206
 
131
- Inject domain knowledge by setting fragments on the `ContextEngine` you pass to
132
- `Text2Sql`. Those fragments affect `chat()` sessions. If you need direct SQL
133
- generation with extra fragments, use the lower-level `toSql({ fragments })`
207
+ Inject domain knowledge by setting fragments on the `ContextEngine` you build
208
+ for the agent. Those fragments affect every `chat()` turn. If you need direct
209
+ SQL generation with extra fragments, use the lower-level `toSql({ fragments })`
134
210
  helper exported from `@deepagents/text2sql`.
135
211
 
136
212
  ```typescript
@@ -144,13 +220,13 @@ import {
144
220
  } from '@deepagents/context';
145
221
 
146
222
  const store = new InMemoryContextStore();
147
- const engine = new ContextEngine({
223
+ const context = new ContextEngine({
148
224
  store,
149
225
  chatId: 'chat-123',
150
226
  userId: 'user-456',
151
227
  });
152
228
 
153
- engine.set(
229
+ context.set(
154
230
  term('MRR', 'monthly recurring revenue'),
155
231
  hint('Always exclude test accounts with email ending in @test.com'),
156
232
  guardrail({
@@ -188,62 +264,69 @@ Control what schema metadata the AI receives:
188
264
 
189
265
  ## Conversations
190
266
 
191
- `chat()` persists history through the `ContextEngine` you pass to `Text2Sql`.
192
- Reuse the same store, `chatId`, and `userId` to continue the same thread.
193
- Before each turn, append only the new incoming message to the engine; do not
194
- replay earlier turns that are already stored in the context store:
267
+ `chat()` (from `@deepagents/context`) persists history through the
268
+ `ContextEngine` you build for the agent. Reuse the same store, `chatId`, and
269
+ `userId` to continue the same thread. Before each turn, append only the new
270
+ incoming message to the engine; do not replay earlier turns that are already
271
+ stored in the context store:
195
272
 
196
273
  ```typescript
197
- await engine.continue(user('Show me orders from last month'));
198
- const stream = await text2sql.chat();
274
+ await context.continue(user('Show me orders from last month'));
275
+ const stream = await chat(ai);
199
276
 
200
277
  for await (const chunk of stream) {
201
278
  // handle streaming response
202
279
  }
203
280
 
204
281
  // Continue the same conversation
205
- await engine.continue(user('Now filter to only completed ones'));
206
- const followUp = await text2sql.chat();
282
+ await context.continue(user('Now filter to only completed ones'));
283
+ const followUp = await chat(ai);
207
284
  for await (const chunk of followUp) {
208
285
  // handle streaming response
209
286
  }
210
287
  ```
211
288
 
212
- While `chat()` builds or reads cached schema context, it emits progress chunks
213
- before assistant text starts:
289
+ ## Streaming Index Progress
290
+
291
+ `AdapterIndexer#index({ onProgress })` emits progress events while it warms or reads
292
+ the schema cache for host-side indexing. The sandbox CLI writes the same event
293
+ shape to the `eventsPath` file returned by `sql index`:
214
294
 
215
295
  ```typescript
296
+ import { createUIMessageStream } from 'ai';
297
+
216
298
  import {
299
+ AdapterIndexer,
217
300
  TEXT2SQL_INDEX_PROGRESS_CHUNK,
218
301
  type Text2SqlIndexProgressEvent,
219
302
  } from '@deepagents/text2sql';
220
303
 
221
- await engine.continue(user('Show me top 10 customers'));
222
- const stream = await text2sql.chat();
223
- const startedAt = new Map<string, number>();
304
+ const indexer = new AdapterIndexer({ adapters, version: 'v1' });
224
305
 
225
- for await (const chunk of stream) {
226
- if (chunk.type === TEXT2SQL_INDEX_PROGRESS_CHUNK) {
227
- const event = chunk.data as Text2SqlIndexProgressEvent;
228
- const scope =
229
- [event.adapter, event.phase].filter(Boolean).join(':') || 'index';
306
+ await context.continue(user('Show me top 10 customers'));
230
307
 
231
- if (event.type.endsWith(':start') || event.type === 'index:start') {
232
- startedAt.set(scope, event.timestampMs ?? Date.now());
308
+ const stream = createUIMessageStream({
309
+ execute: async ({ writer }) => {
310
+ const head = await context.headMessage();
311
+ if (!head || head.name !== 'assistant') {
312
+ throw new Error(
313
+ 'expected head to be an assistant message — call context.continue() before chat()',
314
+ );
233
315
  }
234
-
235
- if (event.type.endsWith(':end') || event.type.endsWith(':error')) {
236
- const durationMs =
237
- (event.timestampMs ?? Date.now()) -
238
- (startedAt.get(scope) ?? event.timestampMs ?? Date.now());
239
- console.log(event.message, durationMs);
240
- }
241
-
242
- continue;
243
- }
244
-
245
- // handle assistant stream chunks
246
- }
316
+ writer.write({ type: 'start', messageId: head.id });
317
+
318
+ const fragments = await indexer.index({
319
+ onProgress: (event) =>
320
+ writer.write({
321
+ type: TEXT2SQL_INDEX_PROGRESS_CHUNK,
322
+ data: event,
323
+ }),
324
+ });
325
+
326
+ context.set(...instructions(), ...fragments);
327
+ writer.merge(await chat(ai));
328
+ },
329
+ });
247
330
  ```
248
331
 
249
332
  Each progress event includes a generic Unix epoch `timestampMs` so clients can
@@ -281,7 +364,7 @@ Full documentation available at [januarylabs.github.io/deepagents](https://janua
281
364
 
282
365
  - [Getting Started](https://januarylabs.github.io/deepagents/docs/text2sql/getting-started)
283
366
  - [Generate SQL](https://januarylabs.github.io/deepagents/docs/text2sql/to-sql)
284
- - [Caller-Owned Sandbox](https://januarylabs.github.io/deepagents/docs/text2sql/sqlv3)
367
+ - [Agent + SQLite Store Recipe](https://januarylabs.github.io/deepagents/docs/text2sql/recipes/agent-sqlite-store)
285
368
  - [Teach the System](https://januarylabs.github.io/deepagents/docs/text2sql/teach-the-system)
286
369
  - [Build Conversations](https://januarylabs.github.io/deepagents/docs/text2sql/build-conversations)
287
370
  - [Grounding](https://januarylabs.github.io/deepagents/docs/text2sql/grounding)