@deepagents/text2sql 0.38.1 → 1.0.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 (59) hide show
  1. package/README.md +166 -72
  2. package/dist/bin/sql.js +699 -0
  3. package/dist/index.d.ts +2 -3
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +1989 -1898
  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/adapters/adapter.d.ts +1 -0
  10. package/dist/lib/adapters/adapter.d.ts.map +1 -1
  11. package/dist/lib/adapters/bigquery/index.js +24 -13
  12. package/dist/lib/adapters/bigquery/index.js.map +2 -2
  13. package/dist/lib/adapters/groundings/context.d.ts +2 -2
  14. package/dist/lib/adapters/groundings/context.d.ts.map +1 -1
  15. package/dist/lib/adapters/groundings/index.js +20 -9
  16. package/dist/lib/adapters/groundings/index.js.map +2 -2
  17. package/dist/lib/adapters/mysql/index.js +23 -12
  18. package/dist/lib/adapters/mysql/index.js.map +2 -2
  19. package/dist/lib/adapters/postgres/index.js +31 -18
  20. package/dist/lib/adapters/postgres/index.js.map +3 -3
  21. package/dist/lib/adapters/postgres/postgres.d.ts.map +1 -1
  22. package/dist/lib/adapters/spreadsheet/index.js +20 -9
  23. package/dist/lib/adapters/spreadsheet/index.js.map +2 -2
  24. package/dist/lib/adapters/sqlite/index.js +23 -12
  25. package/dist/lib/adapters/sqlite/index.js.map +2 -2
  26. package/dist/lib/adapters/sqlserver/index.js +31 -18
  27. package/dist/lib/adapters/sqlserver/index.js.map +3 -3
  28. package/dist/lib/adapters/sqlserver/sqlserver.d.ts.map +1 -1
  29. package/dist/lib/agents/question.agent.d.ts.map +1 -1
  30. package/dist/lib/agents/sql-transform-plugins.d.ts +3 -3
  31. package/dist/lib/agents/sql-transform-plugins.d.ts.map +1 -1
  32. package/dist/lib/agents/sql.agent.d.ts.map +1 -1
  33. package/dist/lib/instructions.d.ts +1 -1
  34. package/dist/lib/instructions.d.ts.map +1 -1
  35. package/dist/lib/sql-hooks.d.ts +15 -0
  36. package/dist/lib/sql-hooks.d.ts.map +1 -0
  37. package/dist/lib/sql-meta-hook.d.ts +20 -0
  38. package/dist/lib/sql-meta-hook.d.ts.map +1 -0
  39. package/dist/lib/sql-repair.d.ts +2 -0
  40. package/dist/lib/sql-repair.d.ts.map +1 -0
  41. package/dist/lib/sql-transform.d.ts +2 -0
  42. package/dist/lib/sql-transform.d.ts.map +1 -0
  43. package/dist/lib/sql.d.ts +5 -37
  44. package/dist/lib/sql.d.ts.map +1 -1
  45. package/dist/lib/synthesis/extractors/base-contextual-extractor.d.ts.map +1 -1
  46. package/dist/lib/synthesis/extractors/segmented-context-extractor.d.ts.map +1 -1
  47. package/dist/lib/synthesis/extractors/sql-extractor.d.ts.map +1 -1
  48. package/dist/lib/synthesis/index.js +59 -2
  49. package/dist/lib/synthesis/index.js.map +4 -4
  50. package/dist/lib/synthesis/synthesizers/breadth-evolver.d.ts.map +1 -1
  51. package/dist/lib/synthesis/synthesizers/depth-evolver.d.ts.map +1 -1
  52. package/dist/lib/synthesis/synthesizers/persona-generator.d.ts.map +1 -1
  53. package/package.json +13 -9
  54. package/dist/lib/agents/sql-command.d.ts +0 -11
  55. package/dist/lib/agents/sql-command.d.ts.map +0 -1
  56. package/dist/lib/demo.d.ts +0 -2
  57. package/dist/lib/demo.d.ts.map +0 -1
  58. package/dist/lib/sandbox.d.ts +0 -21
  59. package/dist/lib/sandbox.d.ts.map +0 -1
package/README.md CHANGED
@@ -32,17 +32,9 @@ Requires Node.js LTS (20+).
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
- } from '@deepagents/context';
45
- import { Text2Sql, sqlSandboxExtension } from '@deepagents/text2sql';
37
+ import { Text2Sql } from '@deepagents/text2sql';
46
38
  import {
47
39
  Postgres,
48
40
  columnValues,
@@ -58,7 +50,6 @@ const pool = new pg.Pool({
58
50
  connectionString: process.env.DATABASE_URL,
59
51
  });
60
52
 
61
- const store = new InMemoryContextStore();
62
53
  const adapter = new Postgres({
63
54
  execute: async (sql) => {
64
55
  const result = await pool.query(sql);
@@ -74,27 +65,11 @@ const adapter = new Postgres({
74
65
  columnValues(),
75
66
  ],
76
67
  });
77
- const sandbox = await createBashTool({
78
- sandbox: await createRoutingSandbox({
79
- backend: await createVirtualSandbox({ fs: new InMemoryFs() }),
80
- hostExtensions: [sqlSandboxExtension({ main: adapter })],
81
- }),
82
- });
83
68
 
84
69
  const text2sql = new Text2Sql({
85
70
  version: 'v1',
86
71
  model: groq('openai/gpt-oss-20b'),
87
- sandbox,
88
72
  adapters: { main: adapter },
89
- context: (...fragments) => {
90
- const engine = new ContextEngine({
91
- store,
92
- chatId: 'chat-123',
93
- userId: 'user-456',
94
- });
95
- engine.set(...fragments);
96
- return engine;
97
- },
98
73
  });
99
74
 
100
75
  // Generate SQL
@@ -107,33 +82,107 @@ console.log(sql);
107
82
 
108
83
  The adapter-map key (`main` here) is the adapter name. Reuse that same key in
109
84
  `text2sql.toSql(..., 'main')` and in any `sql validate <db> "..."` /
110
- `sql run <db> "..."` sandbox calls.
85
+ `sql run <db> "..."` calls from a sandbox where the package CLI is installed.
111
86
 
112
87
  Adapter names must match `/^[A-Za-z_][A-Za-z0-9_]*$/`. If you build adapter
113
88
  maps dynamically, use `isValidAdapterName(name)` to check one key or
114
89
  `validateAdapterNames(names)` to fail fast before constructing `Text2Sql` or
115
- `sqlSandboxExtension`.
90
+ the sandbox-side adapter module used by the `sql` CLI.
116
91
 
117
92
  ## AI Model Providers
118
93
 
119
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.
120
95
 
121
- ## Advanced: Composing Sandbox Extensions
96
+ ## Building a Conversational Agent
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`:
102
+
103
+ ```typescript
104
+ import {
105
+ ContextEngine,
106
+ type ContextFragment,
107
+ InMemoryContextStore,
108
+ agent,
109
+ chat,
110
+ createContainerTool,
111
+ errorRecoveryGuardrail,
112
+ npm,
113
+ user,
114
+ } from '@deepagents/context';
115
+ import { createSqlCommandHooks, instructions } from '@deepagents/text2sql';
116
+
117
+ const store = new InMemoryContextStore();
118
+ const context = new ContextEngine({
119
+ store,
120
+ chatId: 'chat-123',
121
+ userId: 'user-456',
122
+ });
123
+
124
+ const sandbox = await createContainerTool({
125
+ installers: [npm('@deepagents/text2sql', { ensureRuntime: true })],
126
+ env: {
127
+ TEXT2SQL_ADAPTERS: '/workspace/text2sql-adapters.ts',
128
+ },
129
+ ...createSqlCommandHooks({ adapters: { main: adapter } }),
130
+ });
131
+
132
+ const indexResult = await sandbox.sandbox.executeCommand('sql index');
133
+ if (indexResult.exitCode !== 0) throw new Error(indexResult.stderr);
134
+ const manifest = JSON.parse(indexResult.stdout) as { fragmentsPath: string };
135
+ const fragments = JSON.parse(
136
+ await sandbox.sandbox.readFile(manifest.fragmentsPath),
137
+ ) as ContextFragment[];
138
+ context.set(...instructions(), ...fragments);
139
+
140
+ const ai = agent({
141
+ name: 'sql-assistant',
142
+ sandbox,
143
+ model: groq('openai/gpt-oss-20b'),
144
+ context,
145
+ guardrails: [errorRecoveryGuardrail],
146
+ maxGuardrailRetries: 3,
147
+ });
148
+
149
+ await context.continue(user('Show me the top 10 customers by revenue'));
150
+ const stream = await chat(ai);
122
151
 
123
- `Text2Sql` takes a caller-owned `sandbox`. `sqlSandboxExtension(adaptersMap)`
124
- returns a `SandboxExtension` that bundles the `sql` subcommand, transform
125
- plugins, and arg-repair hook. In the simplest case, that map is
126
- `{ main: adapter }`. Compose it (alongside any of your own extensions) via
127
- `createBashTool` + `createRoutingSandbox` + `createVirtualSandbox`.
128
- See [Caller-Owned Sandbox](https://januarylabs.github.io/deepagents/docs/text2sql/sqlv3)
129
- for composition patterns.
152
+ for await (const chunk of stream) {
153
+ // handle streaming response
154
+ }
155
+ ```
156
+
157
+ `instructions()` returns the SQL-flavored system fragments (policies, workflows,
158
+ clarifications, style guides) — spread them into `context.set()` alongside the
159
+ schema fragments returned by `sql index`. Add or replace them with your
160
+ own domain fragments as needed.
161
+
162
+ ## Advanced: SQL CLI in Sandboxes
163
+
164
+ `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.
170
+
171
+ Spread `createSqlCommandHooks({ adapters })` into `createBashTool()` or
172
+ `createContainerTool()` for model-driven bash calls. The before hook preserves
173
+ the old virtual-command tolerance for common LLM quote mistakes, rewrites SQL
174
+ identifier backticks so bash does not run them as command substitutions, and
175
+ blocks raw database access so read-only and scope checks stay behind
176
+ `sql validate` / `sql run`. The after hook restores hidden `formattedSql`
177
+ metadata from the host adapter map without putting that concern into the real
178
+ CLI.
130
179
 
131
180
  ## Fragments
132
181
 
133
- Inject domain knowledge using fragments from `@deepagents/context` inside the
134
- `context` factory you pass to `Text2Sql`. Those fragments affect `chat()`
135
- sessions. If you need direct SQL generation with extra fragments, use the
136
- lower-level `toSql({ fragments })` helper exported from `@deepagents/text2sql`.
182
+ Inject domain knowledge by setting fragments on the `ContextEngine` you build
183
+ for the agent. Those fragments affect every `chat()` turn. If you need direct
184
+ SQL generation with extra fragments, use the lower-level `toSql({ fragments })`
185
+ helper exported from `@deepagents/text2sql`.
137
186
 
138
187
  ```typescript
139
188
  import {
@@ -146,31 +195,25 @@ import {
146
195
  } from '@deepagents/context';
147
196
 
148
197
  const store = new InMemoryContextStore();
198
+ const context = new ContextEngine({
199
+ store,
200
+ chatId: 'chat-123',
201
+ userId: 'user-456',
202
+ });
149
203
 
150
- const context = (...fragments) => {
151
- const engine = new ContextEngine({
152
- store,
153
- chatId: 'chat-123',
154
- userId: 'user-456',
155
- });
156
-
157
- engine.set(
158
- term('MRR', 'monthly recurring revenue'),
159
- hint('Always exclude test accounts with email ending in @test.com'),
160
- guardrail({
161
- rule: 'Never expose individual salaries',
162
- reason: 'Confidential HR data',
163
- action: 'Aggregate by department instead',
164
- }),
165
- example({
166
- question: 'show me churned customers',
167
- answer: `SELECT * FROM customers WHERE status = 'churned' ORDER BY churned_at DESC`,
168
- }),
169
- ...fragments,
170
- );
171
-
172
- return engine;
173
- };
204
+ context.set(
205
+ term('MRR', 'monthly recurring revenue'),
206
+ hint('Always exclude test accounts with email ending in @test.com'),
207
+ guardrail({
208
+ rule: 'Never expose individual salaries',
209
+ reason: 'Confidential HR data',
210
+ action: 'Aggregate by department instead',
211
+ }),
212
+ example({
213
+ question: 'show me churned customers',
214
+ answer: `SELECT * FROM customers WHERE status = 'churned' ORDER BY churned_at DESC`,
215
+ }),
216
+ );
174
217
  ```
175
218
 
176
219
  **Domain fragments** (11 types): `term`, `hint`, `guardrail`, `example`, `explain`, `clarification`, `workflow`, `quirk`, `styleGuide`, `analogy`, `glossary`.
@@ -196,23 +239,75 @@ Control what schema metadata the AI receives:
196
239
 
197
240
  ## Conversations
198
241
 
199
- `chat()` persists history through the `ContextEngine` returned by your `context` factory. Reuse the same store, `chatId`, and `userId` to continue the same thread. Pass only the new incoming message(s) for the current turn; do not replay earlier turns that are already stored in the context store:
242
+ `chat()` (from `@deepagents/context`) persists history through the
243
+ `ContextEngine` you build for the agent. Reuse the same store, `chatId`, and
244
+ `userId` to continue the same thread. Before each turn, append only the new
245
+ incoming message to the engine; do not replay earlier turns that are already
246
+ stored in the context store:
200
247
 
201
248
  ```typescript
202
- const stream = await text2sql.chat([
203
- { role: 'user', content: 'Show me orders from last month' },
204
- ]);
249
+ await context.continue(user('Show me orders from last month'));
250
+ const stream = await chat(ai);
205
251
 
206
252
  for await (const chunk of stream) {
207
253
  // handle streaming response
208
254
  }
209
255
 
210
256
  // Continue the same conversation
211
- const followUp = await text2sql.chat([
212
- { role: 'user', content: 'Now filter to only completed ones' },
213
- ]);
257
+ await context.continue(user('Now filter to only completed ones'));
258
+ const followUp = await chat(ai);
259
+ for await (const chunk of followUp) {
260
+ // handle streaming response
261
+ }
214
262
  ```
215
263
 
264
+ ## Streaming Index Progress
265
+
266
+ `AdapterIndexer#index({ onProgress })` emits progress events while it warms or reads
267
+ the schema cache for host-side indexing. The sandbox CLI writes the same event
268
+ shape to the `eventsPath` file returned by `sql index`:
269
+
270
+ ```typescript
271
+ import { createUIMessageStream } from 'ai';
272
+
273
+ import {
274
+ AdapterIndexer,
275
+ TEXT2SQL_INDEX_PROGRESS_CHUNK,
276
+ type Text2SqlIndexProgressEvent,
277
+ } from '@deepagents/text2sql';
278
+
279
+ const indexer = new AdapterIndexer({ adapters, version: 'v1' });
280
+
281
+ await context.continue(user('Show me top 10 customers'));
282
+
283
+ const stream = createUIMessageStream({
284
+ execute: async ({ writer }) => {
285
+ const head = await context.headMessage();
286
+ if (!head || head.name !== 'assistant') {
287
+ throw new Error(
288
+ 'expected head to be an assistant message — call context.continue() before chat()',
289
+ );
290
+ }
291
+ writer.write({ type: 'start', messageId: head.id });
292
+
293
+ const fragments = await indexer.index({
294
+ onProgress: (event) =>
295
+ writer.write({
296
+ type: TEXT2SQL_INDEX_PROGRESS_CHUNK,
297
+ data: event,
298
+ }),
299
+ });
300
+
301
+ context.set(...instructions(), ...fragments);
302
+ writer.merge(await chat(ai));
303
+ },
304
+ });
305
+ ```
306
+
307
+ Each progress event includes a generic Unix epoch `timestampMs` so clients can
308
+ calculate per-index, per-adapter, or per-phase durations without Text2SQL
309
+ imposing one duration policy.
310
+
216
311
  `toSql()` is stateless. It reads the current schema fragments, but it does not
217
312
  write messages, titles, or usage into your context store.
218
313
 
@@ -244,7 +339,6 @@ Full documentation available at [januarylabs.github.io/deepagents](https://janua
244
339
 
245
340
  - [Getting Started](https://januarylabs.github.io/deepagents/docs/text2sql/getting-started)
246
341
  - [Generate SQL](https://januarylabs.github.io/deepagents/docs/text2sql/to-sql)
247
- - [Caller-Owned Sandbox](https://januarylabs.github.io/deepagents/docs/text2sql/sqlv3)
248
342
  - [Teach the System](https://januarylabs.github.io/deepagents/docs/text2sql/teach-the-system)
249
343
  - [Build Conversations](https://januarylabs.github.io/deepagents/docs/text2sql/build-conversations)
250
344
  - [Grounding](https://januarylabs.github.io/deepagents/docs/text2sql/grounding)