@deepagents/text2sql 0.38.1 → 0.39.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
@@ -41,6 +41,7 @@ import {
41
41
  createBashTool,
42
42
  createRoutingSandbox,
43
43
  createVirtualSandbox,
44
+ user,
44
45
  } from '@deepagents/context';
45
46
  import { Text2Sql, sqlSandboxExtension } from '@deepagents/text2sql';
46
47
  import {
@@ -59,6 +60,11 @@ const pool = new pg.Pool({
59
60
  });
60
61
 
61
62
  const store = new InMemoryContextStore();
63
+ const engine = new ContextEngine({
64
+ store,
65
+ chatId: 'chat-123',
66
+ userId: 'user-456',
67
+ });
62
68
  const adapter = new Postgres({
63
69
  execute: async (sql) => {
64
70
  const result = await pool.query(sql);
@@ -86,15 +92,7 @@ const text2sql = new Text2Sql({
86
92
  model: groq('openai/gpt-oss-20b'),
87
93
  sandbox,
88
94
  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
- },
95
+ context: engine,
98
96
  });
99
97
 
100
98
  // Generate SQL
@@ -130,10 +128,10 @@ for composition patterns.
130
128
 
131
129
  ## Fragments
132
130
 
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`.
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 })`
134
+ helper exported from `@deepagents/text2sql`.
137
135
 
138
136
  ```typescript
139
137
  import {
@@ -146,31 +144,25 @@ import {
146
144
  } from '@deepagents/context';
147
145
 
148
146
  const store = new InMemoryContextStore();
147
+ const engine = new ContextEngine({
148
+ store,
149
+ chatId: 'chat-123',
150
+ userId: 'user-456',
151
+ });
149
152
 
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
- };
153
+ engine.set(
154
+ term('MRR', 'monthly recurring revenue'),
155
+ hint('Always exclude test accounts with email ending in @test.com'),
156
+ guardrail({
157
+ rule: 'Never expose individual salaries',
158
+ reason: 'Confidential HR data',
159
+ action: 'Aggregate by department instead',
160
+ }),
161
+ example({
162
+ question: 'show me churned customers',
163
+ answer: `SELECT * FROM customers WHERE status = 'churned' ORDER BY churned_at DESC`,
164
+ }),
165
+ );
174
166
  ```
175
167
 
176
168
  **Domain fragments** (11 types): `term`, `hint`, `guardrail`, `example`, `explain`, `clarification`, `workflow`, `quirk`, `styleGuide`, `analogy`, `glossary`.
@@ -196,23 +188,68 @@ Control what schema metadata the AI receives:
196
188
 
197
189
  ## Conversations
198
190
 
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:
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:
200
195
 
201
196
  ```typescript
202
- const stream = await text2sql.chat([
203
- { role: 'user', content: 'Show me orders from last month' },
204
- ]);
197
+ await engine.continue(user('Show me orders from last month'));
198
+ const stream = await text2sql.chat();
205
199
 
206
200
  for await (const chunk of stream) {
207
201
  // handle streaming response
208
202
  }
209
203
 
210
204
  // Continue the same conversation
211
- const followUp = await text2sql.chat([
212
- { role: 'user', content: 'Now filter to only completed ones' },
213
- ]);
205
+ await engine.continue(user('Now filter to only completed ones'));
206
+ const followUp = await text2sql.chat();
207
+ for await (const chunk of followUp) {
208
+ // handle streaming response
209
+ }
210
+ ```
211
+
212
+ While `chat()` builds or reads cached schema context, it emits progress chunks
213
+ before assistant text starts:
214
+
215
+ ```typescript
216
+ import {
217
+ TEXT2SQL_INDEX_PROGRESS_CHUNK,
218
+ type Text2SqlIndexProgressEvent,
219
+ } from '@deepagents/text2sql';
220
+
221
+ await engine.continue(user('Show me top 10 customers'));
222
+ const stream = await text2sql.chat();
223
+ const startedAt = new Map<string, number>();
224
+
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';
230
+
231
+ if (event.type.endsWith(':start') || event.type === 'index:start') {
232
+ startedAt.set(scope, event.timestampMs ?? Date.now());
233
+ }
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
+ }
214
247
  ```
215
248
 
249
+ Each progress event includes a generic Unix epoch `timestampMs` so clients can
250
+ calculate per-index, per-adapter, or per-phase durations without Text2SQL
251
+ imposing one duration policy.
252
+
216
253
  `toSql()` is stateless. It reads the current schema fragments, but it does not
217
254
  write messages, titles, or usage into your context store.
218
255
 
package/dist/index.js CHANGED
@@ -163,6 +163,17 @@ function relationship(input) {
163
163
  }
164
164
 
165
165
  // packages/text2sql/src/lib/adapters/groundings/context.ts
166
+ var noopProgress = () => {
167
+ };
168
+ function timestampProgress(onProgress) {
169
+ if (!onProgress) return noopProgress;
170
+ return (progress) => {
171
+ onProgress({
172
+ ...progress,
173
+ timestampMs: progress.timestampMs ?? Date.now()
174
+ });
175
+ };
176
+ }
166
177
  function createGroundingContext(options = {}) {
167
178
  return {
168
179
  tables: [],
@@ -170,7 +181,7 @@ function createGroundingContext(options = {}) {
170
181
  relationships: [],
171
182
  info: void 0,
172
183
  cache: /* @__PURE__ */ new Map(),
173
- onProgress: options.onProgress
184
+ onProgress: timestampProgress(options.onProgress)
174
185
  };
175
186
  }
176
187
 
@@ -317,7 +328,7 @@ var Adapter = class {
317
328
  for (const fn of this.grounding) {
318
329
  const grounding = fn(this);
319
330
  if (grounding.phase) {
320
- ctx.onProgress?.({
331
+ ctx.onProgress({
321
332
  type: "phase:start",
322
333
  phase: grounding.phase,
323
334
  message: `Starting ${grounding.name} introspection...`
@@ -327,7 +338,7 @@ var Adapter = class {
327
338
  await grounding.execute(ctx);
328
339
  } finally {
329
340
  if (grounding.phase) {
330
- ctx.onProgress?.({
341
+ ctx.onProgress({
331
342
  type: "phase:end",
332
343
  phase: grounding.phase,
333
344
  message: `Finished ${grounding.name} introspection.`
@@ -5238,6 +5249,17 @@ import {
5238
5249
  fragment as fragment3
5239
5250
  } from "@deepagents/context";
5240
5251
  var TEXT2SQL_INDEX_PROGRESS_CHUNK = "data-text2sql-index-progress";
5252
+ var noopProgressHandler = () => {
5253
+ };
5254
+ function timestampProgressHandler(onProgress) {
5255
+ if (!onProgress) return noopProgressHandler;
5256
+ return (event) => {
5257
+ onProgress({
5258
+ ...event,
5259
+ timestampMs: event.timestampMs ?? Date.now()
5260
+ });
5261
+ };
5262
+ }
5241
5263
  var Text2Sql = class {
5242
5264
  #config;
5243
5265
  #introspection;
@@ -5283,8 +5305,9 @@ var Text2Sql = class {
5283
5305
  return this.#index();
5284
5306
  }
5285
5307
  async #index(onProgress) {
5308
+ const progress = timestampProgressHandler(onProgress);
5286
5309
  const entries = Object.entries(this.#config.adapters);
5287
- onProgress?.({
5310
+ progress({
5288
5311
  type: "index:start",
5289
5312
  message: `Indexing ${entries.length} adapter${entries.length === 1 ? "" : "s"}...`,
5290
5313
  current: 0,
@@ -5292,13 +5315,13 @@ var Text2Sql = class {
5292
5315
  });
5293
5316
  const settled = await Promise.allSettled(
5294
5317
  entries.map(async ([name, adapter]) => {
5295
- const schema = await this.#indexAdapter(name, adapter, onProgress);
5318
+ const schema = await this.#indexAdapter(name, adapter, progress);
5296
5319
  return fragment3(name, ...schema);
5297
5320
  })
5298
5321
  );
5299
5322
  const failed = settled.find((result) => result.status === "rejected");
5300
5323
  if (failed) {
5301
- onProgress?.({
5324
+ progress({
5302
5325
  type: "index:error",
5303
5326
  message: failed.reason instanceof Error ? failed.reason.message : String(failed.reason)
5304
5327
  });
@@ -5310,7 +5333,7 @@ var Text2Sql = class {
5310
5333
  }
5311
5334
  return result.value;
5312
5335
  });
5313
- onProgress?.({
5336
+ progress({
5314
5337
  type: "index:end",
5315
5338
  message: "Finished indexing adapters.",
5316
5339
  current: entries.length,
@@ -5318,8 +5341,8 @@ var Text2Sql = class {
5318
5341
  });
5319
5342
  return wrapped;
5320
5343
  }
5321
- async #indexAdapter(name, adapter, onProgress) {
5322
- onProgress?.({
5344
+ async #indexAdapter(name, adapter, onProgress = noopProgressHandler) {
5345
+ onProgress({
5323
5346
  type: "adapter:start",
5324
5347
  adapter: name,
5325
5348
  message: `Indexing adapter "${name}"...`
@@ -5331,13 +5354,13 @@ var Text2Sql = class {
5331
5354
  try {
5332
5355
  const cached = await cache.read();
5333
5356
  if (cached) {
5334
- onProgress?.({
5357
+ onProgress({
5335
5358
  type: "adapter:cache-hit",
5336
5359
  adapter: name,
5337
5360
  message: `Using cached index for adapter "${name}".`,
5338
5361
  cached: true
5339
5362
  });
5340
- onProgress?.({
5363
+ onProgress({
5341
5364
  type: "adapter:end",
5342
5365
  adapter: name,
5343
5366
  message: `Finished indexing adapter "${name}".`,
@@ -5345,18 +5368,18 @@ var Text2Sql = class {
5345
5368
  });
5346
5369
  return cached;
5347
5370
  }
5348
- onProgress?.({
5371
+ onProgress({
5349
5372
  type: "adapter:cache-miss",
5350
5373
  adapter: name,
5351
5374
  message: `No cached index for adapter "${name}".`,
5352
5375
  cached: false
5353
5376
  });
5354
5377
  const ctx = createGroundingContext({
5355
- onProgress: (progress) => onProgress?.(this.#adapterProgressEvent(name, progress))
5378
+ onProgress: (event) => onProgress(this.#adapterProgressEvent(name, event))
5356
5379
  });
5357
5380
  const fragments = await adapter.introspect(ctx);
5358
5381
  await cache.write(fragments);
5359
- onProgress?.({
5382
+ onProgress({
5360
5383
  type: "adapter:end",
5361
5384
  adapter: name,
5362
5385
  message: `Finished indexing adapter "${name}".`,
@@ -5365,7 +5388,7 @@ var Text2Sql = class {
5365
5388
  return fragments;
5366
5389
  } catch (error) {
5367
5390
  const reason = error instanceof Error ? error.message : String(error);
5368
- onProgress?.({
5391
+ onProgress({
5369
5392
  type: "adapter:error",
5370
5393
  adapter: name,
5371
5394
  message: `Failed indexing adapter "${name}": ${reason}`
@@ -5384,7 +5407,8 @@ var Text2Sql = class {
5384
5407
  message: progress.message,
5385
5408
  current: progress.current,
5386
5409
  total: progress.total,
5387
- cached: false
5410
+ cached: false,
5411
+ timestampMs: progress.timestampMs
5388
5412
  };
5389
5413
  }
5390
5414
  async toPairs(adapterName, factory) {