@deepagents/text2sql 0.10.2 → 0.12.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 (109) hide show
  1. package/README.md +32 -41
  2. package/dist/index.d.ts +4 -7
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +3321 -2661
  5. package/dist/index.js.map +4 -4
  6. package/dist/lib/adapters/adapter.d.ts +13 -1
  7. package/dist/lib/adapters/adapter.d.ts.map +1 -1
  8. package/dist/lib/adapters/groundings/abstract.grounding.d.ts +19 -3
  9. package/dist/lib/adapters/groundings/abstract.grounding.d.ts.map +1 -1
  10. package/dist/lib/adapters/groundings/column-stats.grounding.d.ts +1 -2
  11. package/dist/lib/adapters/groundings/column-stats.grounding.d.ts.map +1 -1
  12. package/dist/lib/adapters/groundings/column-values.grounding.d.ts +1 -2
  13. package/dist/lib/adapters/groundings/column-values.grounding.d.ts.map +1 -1
  14. package/dist/lib/adapters/groundings/constraint.grounding.d.ts +1 -1
  15. package/dist/lib/adapters/groundings/constraint.grounding.d.ts.map +1 -1
  16. package/dist/lib/adapters/groundings/index.js +1952 -272
  17. package/dist/lib/adapters/groundings/index.js.map +4 -4
  18. package/dist/lib/adapters/groundings/indexes.grounding.d.ts +1 -1
  19. package/dist/lib/adapters/groundings/indexes.grounding.d.ts.map +1 -1
  20. package/dist/lib/adapters/groundings/info.grounding.d.ts +1 -1
  21. package/dist/lib/adapters/groundings/info.grounding.d.ts.map +1 -1
  22. package/dist/lib/adapters/groundings/report.grounding.d.ts +1 -1
  23. package/dist/lib/adapters/groundings/report.grounding.d.ts.map +1 -1
  24. package/dist/lib/adapters/groundings/row-count.grounding.d.ts +1 -1
  25. package/dist/lib/adapters/groundings/row-count.grounding.d.ts.map +1 -1
  26. package/dist/lib/adapters/groundings/table.grounding.d.ts +3 -3
  27. package/dist/lib/adapters/groundings/table.grounding.d.ts.map +1 -1
  28. package/dist/lib/adapters/groundings/view.grounding.d.ts +1 -1
  29. package/dist/lib/adapters/groundings/view.grounding.d.ts.map +1 -1
  30. package/dist/lib/adapters/mysql/index.js +2354 -439
  31. package/dist/lib/adapters/mysql/index.js.map +4 -4
  32. package/dist/lib/adapters/postgres/index.js +2415 -500
  33. package/dist/lib/adapters/postgres/index.js.map +4 -4
  34. package/dist/lib/adapters/spreadsheet/index.js +324 -272
  35. package/dist/lib/adapters/spreadsheet/index.js.map +4 -4
  36. package/dist/lib/adapters/sqlite/index.js +2337 -422
  37. package/dist/lib/adapters/sqlite/index.js.map +4 -4
  38. package/dist/lib/adapters/sqlserver/index.js +2413 -498
  39. package/dist/lib/adapters/sqlserver/index.js.map +4 -4
  40. package/dist/lib/agents/developer.agent.d.ts +33 -23
  41. package/dist/lib/agents/developer.agent.d.ts.map +1 -1
  42. package/dist/lib/agents/explainer.agent.d.ts +4 -5
  43. package/dist/lib/agents/explainer.agent.d.ts.map +1 -1
  44. package/dist/lib/agents/question.agent.d.ts.map +1 -1
  45. package/dist/lib/agents/result-tools.d.ts +34 -0
  46. package/dist/lib/agents/result-tools.d.ts.map +1 -0
  47. package/dist/lib/agents/sql.agent.d.ts +4 -4
  48. package/dist/lib/agents/sql.agent.d.ts.map +1 -1
  49. package/dist/lib/agents/teachables.agent.d.ts +2 -2
  50. package/dist/lib/agents/teachables.agent.d.ts.map +1 -1
  51. package/dist/lib/agents/text2sql.agent.d.ts +0 -74
  52. package/dist/lib/agents/text2sql.agent.d.ts.map +1 -1
  53. package/dist/lib/checkpoint.d.ts +1 -1
  54. package/dist/lib/checkpoint.d.ts.map +1 -1
  55. package/dist/lib/fragments/schema.d.ts +214 -0
  56. package/dist/lib/fragments/schema.d.ts.map +1 -0
  57. package/dist/lib/instructions.d.ts +10 -2
  58. package/dist/lib/instructions.d.ts.map +1 -1
  59. package/dist/lib/sql.d.ts +14 -104
  60. package/dist/lib/sql.d.ts.map +1 -1
  61. package/dist/lib/synthesis/extractors/base-contextual-extractor.d.ts +8 -9
  62. package/dist/lib/synthesis/extractors/base-contextual-extractor.d.ts.map +1 -1
  63. package/dist/lib/synthesis/extractors/last-query-extractor.d.ts.map +1 -1
  64. package/dist/lib/synthesis/extractors/message-extractor.d.ts +1 -2
  65. package/dist/lib/synthesis/extractors/message-extractor.d.ts.map +1 -1
  66. package/dist/lib/synthesis/extractors/segmented-context-extractor.d.ts +0 -6
  67. package/dist/lib/synthesis/extractors/segmented-context-extractor.d.ts.map +1 -1
  68. package/dist/lib/synthesis/extractors/sql-extractor.d.ts.map +1 -1
  69. package/dist/lib/synthesis/index.js +2489 -1112
  70. package/dist/lib/synthesis/index.js.map +4 -4
  71. package/dist/lib/synthesis/synthesizers/breadth-evolver.d.ts.map +1 -1
  72. package/dist/lib/synthesis/synthesizers/depth-evolver.d.ts.map +1 -1
  73. package/dist/lib/synthesis/synthesizers/persona-generator.d.ts +7 -17
  74. package/dist/lib/synthesis/synthesizers/persona-generator.d.ts.map +1 -1
  75. package/dist/lib/synthesis/synthesizers/schema-synthesizer.d.ts +2 -2
  76. package/dist/lib/synthesis/synthesizers/schema-synthesizer.d.ts.map +1 -1
  77. package/dist/lib/synthesis/synthesizers/teachings-generator.d.ts +8 -20
  78. package/dist/lib/synthesis/synthesizers/teachings-generator.d.ts.map +1 -1
  79. package/package.json +9 -14
  80. package/dist/lib/agents/chat1.agent.d.ts +0 -50
  81. package/dist/lib/agents/chat1.agent.d.ts.map +0 -1
  82. package/dist/lib/agents/chat2.agent.d.ts +0 -68
  83. package/dist/lib/agents/chat2.agent.d.ts.map +0 -1
  84. package/dist/lib/agents/chat3.agent.d.ts +0 -80
  85. package/dist/lib/agents/chat3.agent.d.ts.map +0 -1
  86. package/dist/lib/agents/chat4.agent.d.ts +0 -88
  87. package/dist/lib/agents/chat4.agent.d.ts.map +0 -1
  88. package/dist/lib/history/history.d.ts +0 -41
  89. package/dist/lib/history/history.d.ts.map +0 -1
  90. package/dist/lib/history/memory.history.d.ts +0 -5
  91. package/dist/lib/history/memory.history.d.ts.map +0 -1
  92. package/dist/lib/history/sqlite.history.d.ts +0 -15
  93. package/dist/lib/history/sqlite.history.d.ts.map +0 -1
  94. package/dist/lib/instructions.js +0 -415
  95. package/dist/lib/instructions.js.map +0 -7
  96. package/dist/lib/memory/memory.prompt.d.ts +0 -3
  97. package/dist/lib/memory/memory.prompt.d.ts.map +0 -1
  98. package/dist/lib/memory/memory.store.d.ts +0 -5
  99. package/dist/lib/memory/memory.store.d.ts.map +0 -1
  100. package/dist/lib/memory/sqlite.store.d.ts +0 -14
  101. package/dist/lib/memory/sqlite.store.d.ts.map +0 -1
  102. package/dist/lib/memory/store.d.ts +0 -40
  103. package/dist/lib/memory/store.d.ts.map +0 -1
  104. package/dist/lib/teach/teachables.d.ts +0 -648
  105. package/dist/lib/teach/teachables.d.ts.map +0 -1
  106. package/dist/lib/teach/teachings.d.ts +0 -11
  107. package/dist/lib/teach/teachings.d.ts.map +0 -1
  108. package/dist/lib/teach/xml.d.ts +0 -6
  109. package/dist/lib/teach/xml.d.ts.map +0 -1
@@ -1,15 +1,18 @@
1
1
  // packages/text2sql/src/lib/adapters/groundings/abstract.grounding.ts
2
2
  var AbstractGrounding = class {
3
- tag;
4
- constructor(tag) {
5
- this.tag = tag;
3
+ /**
4
+ * Grounding identifier for debugging/logging.
5
+ */
6
+ name;
7
+ constructor(name) {
8
+ this.name = name;
6
9
  }
7
10
  };
8
11
 
9
12
  // packages/text2sql/src/lib/adapters/groundings/column-stats.grounding.ts
10
13
  var ColumnStatsGrounding = class extends AbstractGrounding {
11
14
  constructor(config = {}) {
12
- super("column_stats");
15
+ super("columnStats");
13
16
  }
14
17
  /**
15
18
  * Execute the grounding process.
@@ -34,10 +37,6 @@ var ColumnStatsGrounding = class extends AbstractGrounding {
34
37
  }
35
38
  }
36
39
  }
37
- return () => this.#describe();
38
- }
39
- #describe() {
40
- return null;
41
40
  }
42
41
  };
43
42
 
@@ -45,7 +44,7 @@ var ColumnStatsGrounding = class extends AbstractGrounding {
45
44
  var ColumnValuesGrounding = class extends AbstractGrounding {
46
45
  lowCardinalityLimit;
47
46
  constructor(config = {}) {
48
- super("column_values");
47
+ super("columnValues");
49
48
  this.lowCardinalityLimit = config.lowCardinalityLimit ?? 20;
50
49
  }
51
50
  /**
@@ -150,7 +149,6 @@ var ColumnValuesGrounding = class extends AbstractGrounding {
150
149
  }
151
150
  }
152
151
  }
153
- return () => this.#describe();
154
152
  }
155
153
  /**
156
154
  * Resolve column values from all sources in priority order.
@@ -174,15 +172,12 @@ var ColumnValuesGrounding = class extends AbstractGrounding {
174
172
  }
175
173
  return void 0;
176
174
  }
177
- #describe() {
178
- return null;
179
- }
180
175
  };
181
176
 
182
177
  // packages/text2sql/src/lib/adapters/groundings/constraint.grounding.ts
183
178
  var ConstraintGrounding = class extends AbstractGrounding {
184
179
  constructor(config = {}) {
185
- super("constraints");
180
+ super("constraint");
186
181
  }
187
182
  /**
188
183
  * Execute the grounding process.
@@ -196,7 +191,6 @@ var ConstraintGrounding = class extends AbstractGrounding {
196
191
  console.warn("Error collecting constraints for", table.name, error);
197
192
  }
198
193
  }
199
- return () => null;
200
194
  }
201
195
  };
202
196
 
@@ -213,7 +207,7 @@ function createGroundingContext() {
213
207
  // packages/text2sql/src/lib/adapters/groundings/info.grounding.ts
214
208
  var InfoGrounding = class extends AbstractGrounding {
215
209
  constructor(config = {}) {
216
- super("dialect_info");
210
+ super("dialectInfo");
217
211
  }
218
212
  /**
219
213
  * Execute the grounding process.
@@ -221,72 +215,1902 @@ var InfoGrounding = class extends AbstractGrounding {
221
215
  */
222
216
  async execute(ctx) {
223
217
  ctx.info = await this.collectInfo();
224
- const lines = [`Dialect: ${ctx.info.dialect ?? "unknown"}`];
225
- if (ctx.info.version) {
226
- lines.push(`Version: ${ctx.info.version}`);
227
- }
228
- if (ctx.info.database) {
229
- lines.push(`Database: ${ctx.info.database}`);
230
- }
231
- if (ctx.info.details && Object.keys(ctx.info.details).length) {
232
- lines.push(`Details: ${JSON.stringify(ctx.info.details)}`);
233
- }
234
- return () => lines.join("\n");
235
218
  }
236
219
  };
237
220
 
238
221
  // packages/text2sql/src/lib/adapters/groundings/report.grounding.ts
239
- import { groq } from "@ai-sdk/groq";
222
+ import { groq as groq2 } from "@ai-sdk/groq";
240
223
  import { tool } from "ai";
241
224
  import dedent from "dedent";
242
225
  import z from "zod";
226
+ import "@deepagents/agent";
227
+
228
+ // packages/context/dist/index.js
229
+ import { encode } from "gpt-tokenizer";
230
+ import { generateId } from "ai";
231
+ import pluralize from "pluralize";
232
+ import { titlecase } from "stringcase";
233
+ import chalk from "chalk";
234
+ import { defineCommand } from "just-bash";
235
+ import spawn from "nano-spawn";
236
+ import "bash-tool";
237
+ import spawn2 from "nano-spawn";
238
+ import {
239
+ createBashTool
240
+ } from "bash-tool";
241
+ import YAML from "yaml";
242
+ import { DatabaseSync } from "node:sqlite";
243
+ import { groq } from "@ai-sdk/groq";
243
244
  import {
244
- agent,
245
- generate,
246
- toState,
247
- user
248
- } from "@deepagents/agent";
249
- var reportAgent = agent({
250
- name: "db-report-agent",
251
- model: groq("openai/gpt-oss-20b"),
252
- prompt: () => dedent`
253
- <identity>
254
- You are a database analyst expert. Your job is to understand what
255
- a database represents and provide business context about it.
256
- You have READ-ONLY access to the database.
257
- </identity>
245
+ NoSuchToolError,
246
+ Output,
247
+ convertToModelMessages,
248
+ createUIMessageStream,
249
+ generateId as generateId2,
250
+ generateText,
251
+ smoothStream,
252
+ stepCountIs,
253
+ streamText
254
+ } from "ai";
255
+ import chalk2 from "chalk";
256
+ import "zod";
257
+ import "@deepagents/agent";
258
+ var defaultTokenizer = {
259
+ encode(text) {
260
+ return encode(text);
261
+ },
262
+ count(text) {
263
+ return encode(text).length;
264
+ }
265
+ };
266
+ var ModelsRegistry = class {
267
+ #cache = /* @__PURE__ */ new Map();
268
+ #loaded = false;
269
+ #tokenizers = /* @__PURE__ */ new Map();
270
+ #defaultTokenizer = defaultTokenizer;
271
+ /**
272
+ * Load models data from models.dev API
273
+ */
274
+ async load() {
275
+ if (this.#loaded) return;
276
+ const response = await fetch("https://models.dev/api.json");
277
+ if (!response.ok) {
278
+ throw new Error(`Failed to fetch models: ${response.statusText}`);
279
+ }
280
+ const data = await response.json();
281
+ for (const [providerId, provider] of Object.entries(data)) {
282
+ for (const [modelId, model] of Object.entries(provider.models)) {
283
+ const info = {
284
+ id: model.id,
285
+ name: model.name,
286
+ family: model.family,
287
+ cost: model.cost,
288
+ limit: model.limit,
289
+ provider: providerId
290
+ };
291
+ this.#cache.set(`${providerId}:${modelId}`, info);
292
+ }
293
+ }
294
+ this.#loaded = true;
295
+ }
296
+ /**
297
+ * Get model info by ID
298
+ * @param modelId - Model ID (e.g., "openai:gpt-4o")
299
+ */
300
+ get(modelId) {
301
+ return this.#cache.get(modelId);
302
+ }
303
+ /**
304
+ * Check if a model exists in the registry
305
+ */
306
+ has(modelId) {
307
+ return this.#cache.has(modelId);
308
+ }
309
+ /**
310
+ * List all available model IDs
311
+ */
312
+ list() {
313
+ return [...this.#cache.keys()];
314
+ }
315
+ /**
316
+ * Register a custom tokenizer for specific model families
317
+ * @param family - Model family name (e.g., "llama", "claude")
318
+ * @param tokenizer - Tokenizer implementation
319
+ */
320
+ registerTokenizer(family, tokenizer) {
321
+ this.#tokenizers.set(family, tokenizer);
322
+ }
323
+ /**
324
+ * Set the default tokenizer used when no family-specific tokenizer is registered
325
+ */
326
+ setDefaultTokenizer(tokenizer) {
327
+ this.#defaultTokenizer = tokenizer;
328
+ }
329
+ /**
330
+ * Get the appropriate tokenizer for a model
331
+ */
332
+ getTokenizer(modelId) {
333
+ const model = this.get(modelId);
334
+ if (model) {
335
+ const familyTokenizer = this.#tokenizers.get(model.family);
336
+ if (familyTokenizer) {
337
+ return familyTokenizer;
338
+ }
339
+ }
340
+ return this.#defaultTokenizer;
341
+ }
342
+ /**
343
+ * Estimate token count and cost for given text and model
344
+ * @param modelId - Model ID to use for pricing (e.g., "openai:gpt-4o")
345
+ * @param input - Input text (prompt)
346
+ */
347
+ estimate(modelId, input) {
348
+ const model = this.get(modelId);
349
+ if (!model) {
350
+ throw new Error(
351
+ `Model "${modelId}" not found. Call load() first or check model ID.`
352
+ );
353
+ }
354
+ const tokenizer = this.getTokenizer(modelId);
355
+ const tokens = tokenizer.count(input);
356
+ const cost = tokens / 1e6 * model.cost.input;
357
+ return {
358
+ model: model.id,
359
+ provider: model.provider,
360
+ tokens,
361
+ cost,
362
+ limits: {
363
+ context: model.limit.context,
364
+ output: model.limit.output,
365
+ exceedsContext: tokens > model.limit.context
366
+ },
367
+ fragments: []
368
+ };
369
+ }
370
+ };
371
+ var _registry = null;
372
+ function getModelsRegistry() {
373
+ if (!_registry) {
374
+ _registry = new ModelsRegistry();
375
+ }
376
+ return _registry;
377
+ }
378
+ function isFragment(data) {
379
+ return typeof data === "object" && data !== null && "name" in data && "data" in data && typeof data.name === "string";
380
+ }
381
+ function isFragmentObject(data) {
382
+ return typeof data === "object" && data !== null && !Array.isArray(data) && !isFragment(data);
383
+ }
384
+ function isMessageFragment(fragment2) {
385
+ return fragment2.type === "message";
386
+ }
387
+ function fragment(name, ...children) {
388
+ return {
389
+ name,
390
+ data: children
391
+ };
392
+ }
393
+ function user(content) {
394
+ const message2 = typeof content === "string" ? {
395
+ id: generateId(),
396
+ role: "user",
397
+ parts: [{ type: "text", text: content }]
398
+ } : content;
399
+ return {
400
+ id: message2.id,
401
+ name: "user",
402
+ data: "content",
403
+ type: "message",
404
+ persist: true,
405
+ codec: {
406
+ decode() {
407
+ return message2;
408
+ },
409
+ encode() {
410
+ return message2;
411
+ }
412
+ }
413
+ };
414
+ }
415
+ function assistant(message2) {
416
+ return {
417
+ id: message2.id,
418
+ name: "assistant",
419
+ data: "content",
420
+ type: "message",
421
+ persist: true,
422
+ codec: {
423
+ decode() {
424
+ return message2;
425
+ },
426
+ encode() {
427
+ return message2;
428
+ }
429
+ }
430
+ };
431
+ }
432
+ function message(content) {
433
+ const message2 = typeof content === "string" ? {
434
+ id: generateId(),
435
+ role: "user",
436
+ parts: [{ type: "text", text: content }]
437
+ } : content;
438
+ return {
439
+ id: message2.id,
440
+ name: "message",
441
+ data: "content",
442
+ type: "message",
443
+ persist: true,
444
+ codec: {
445
+ decode() {
446
+ return message2;
447
+ },
448
+ encode() {
449
+ return message2;
450
+ }
451
+ }
452
+ };
453
+ }
454
+ function assistantText(content, options) {
455
+ const id = options?.id ?? crypto.randomUUID();
456
+ return assistant({
457
+ id,
458
+ role: "assistant",
459
+ parts: [{ type: "text", text: content }]
460
+ });
461
+ }
462
+ var ContextRenderer = class {
463
+ options;
464
+ constructor(options = {}) {
465
+ this.options = options;
466
+ }
467
+ /**
468
+ * Check if data is a primitive (string, number, boolean).
469
+ */
470
+ isPrimitive(data) {
471
+ return typeof data === "string" || typeof data === "number" || typeof data === "boolean";
472
+ }
473
+ /**
474
+ * Group fragments by name for groupFragments option.
475
+ */
476
+ groupByName(fragments) {
477
+ const groups = /* @__PURE__ */ new Map();
478
+ for (const fragment2 of fragments) {
479
+ const existing = groups.get(fragment2.name) ?? [];
480
+ existing.push(fragment2);
481
+ groups.set(fragment2.name, existing);
482
+ }
483
+ return groups;
484
+ }
485
+ /**
486
+ * Remove null/undefined from fragments and fragment data recursively.
487
+ * This protects renderers from nullish values and ensures they are ignored
488
+ * consistently across all output formats.
489
+ */
490
+ sanitizeFragments(fragments) {
491
+ const sanitized = [];
492
+ for (const fragment2 of fragments) {
493
+ const cleaned = this.sanitizeFragment(fragment2, /* @__PURE__ */ new WeakSet());
494
+ if (cleaned) {
495
+ sanitized.push(cleaned);
496
+ }
497
+ }
498
+ return sanitized;
499
+ }
500
+ sanitizeFragment(fragment2, seen) {
501
+ const data = this.sanitizeData(fragment2.data, seen);
502
+ if (data == null) {
503
+ return null;
504
+ }
505
+ return {
506
+ ...fragment2,
507
+ data
508
+ };
509
+ }
510
+ sanitizeData(data, seen) {
511
+ if (data == null) {
512
+ return void 0;
513
+ }
514
+ if (isFragment(data)) {
515
+ return this.sanitizeFragment(data, seen) ?? void 0;
516
+ }
517
+ if (Array.isArray(data)) {
518
+ if (seen.has(data)) {
519
+ return void 0;
520
+ }
521
+ seen.add(data);
522
+ const cleaned = [];
523
+ for (const item of data) {
524
+ const sanitizedItem = this.sanitizeData(item, seen);
525
+ if (sanitizedItem != null) {
526
+ cleaned.push(sanitizedItem);
527
+ }
528
+ }
529
+ return cleaned;
530
+ }
531
+ if (isFragmentObject(data)) {
532
+ if (seen.has(data)) {
533
+ return void 0;
534
+ }
535
+ seen.add(data);
536
+ const cleaned = {};
537
+ for (const [key, value] of Object.entries(data)) {
538
+ const sanitizedValue = this.sanitizeData(value, seen);
539
+ if (sanitizedValue != null) {
540
+ cleaned[key] = sanitizedValue;
541
+ }
542
+ }
543
+ return cleaned;
544
+ }
545
+ return data;
546
+ }
547
+ /**
548
+ * Template method - dispatches value to appropriate handler.
549
+ */
550
+ renderValue(key, value, ctx) {
551
+ if (value == null) {
552
+ return "";
553
+ }
554
+ if (isFragment(value)) {
555
+ return this.renderFragment(value, ctx);
556
+ }
557
+ if (Array.isArray(value)) {
558
+ return this.renderArray(key, value, ctx);
559
+ }
560
+ if (isFragmentObject(value)) {
561
+ return this.renderObject(key, value, ctx);
562
+ }
563
+ return this.renderPrimitive(key, String(value), ctx);
564
+ }
565
+ /**
566
+ * Render all entries of an object.
567
+ */
568
+ renderEntries(data, ctx) {
569
+ return Object.entries(data).map(([key, value]) => this.renderValue(key, value, ctx)).filter(Boolean);
570
+ }
571
+ };
572
+ var XmlRenderer = class extends ContextRenderer {
573
+ render(fragments) {
574
+ const sanitized = this.sanitizeFragments(fragments);
575
+ return sanitized.map((f) => this.#renderTopLevel(f)).filter(Boolean).join("\n");
576
+ }
577
+ #renderTopLevel(fragment2) {
578
+ if (this.isPrimitive(fragment2.data)) {
579
+ return this.#leafRoot(fragment2.name, String(fragment2.data));
580
+ }
581
+ if (Array.isArray(fragment2.data)) {
582
+ return this.#renderArray(fragment2.name, fragment2.data, 0);
583
+ }
584
+ if (isFragment(fragment2.data)) {
585
+ const child = this.renderFragment(fragment2.data, { depth: 1, path: [] });
586
+ return this.#wrap(fragment2.name, [child]);
587
+ }
588
+ if (isFragmentObject(fragment2.data)) {
589
+ return this.#wrap(
590
+ fragment2.name,
591
+ this.renderEntries(fragment2.data, { depth: 1, path: [] })
592
+ );
593
+ }
594
+ return "";
595
+ }
596
+ #renderArray(name, items, depth) {
597
+ const fragmentItems = items.filter(isFragment);
598
+ const nonFragmentItems = items.filter((item) => !isFragment(item));
599
+ const children = [];
600
+ for (const item of nonFragmentItems) {
601
+ if (item != null) {
602
+ if (isFragmentObject(item)) {
603
+ children.push(
604
+ this.#wrapIndented(
605
+ pluralize.singular(name),
606
+ this.renderEntries(item, { depth: depth + 2, path: [] }),
607
+ depth + 1
608
+ )
609
+ );
610
+ } else {
611
+ children.push(
612
+ this.#leaf(pluralize.singular(name), String(item), depth + 1)
613
+ );
614
+ }
615
+ }
616
+ }
617
+ if (this.options.groupFragments && fragmentItems.length > 0) {
618
+ const groups = this.groupByName(fragmentItems);
619
+ for (const [groupName, groupFragments] of groups) {
620
+ const groupChildren = groupFragments.map(
621
+ (frag) => this.renderFragment(frag, { depth: depth + 2, path: [] })
622
+ );
623
+ const pluralName = pluralize.plural(groupName);
624
+ children.push(this.#wrapIndented(pluralName, groupChildren, depth + 1));
625
+ }
626
+ } else {
627
+ for (const frag of fragmentItems) {
628
+ children.push(
629
+ this.renderFragment(frag, { depth: depth + 1, path: [] })
630
+ );
631
+ }
632
+ }
633
+ return this.#wrap(name, children);
634
+ }
635
+ #leafRoot(tag, value) {
636
+ const safe = this.#escape(value);
637
+ if (safe.includes("\n")) {
638
+ return `<${tag}>
639
+ ${this.#indent(safe, 2)}
640
+ </${tag}>`;
641
+ }
642
+ return `<${tag}>${safe}</${tag}>`;
643
+ }
644
+ renderFragment(fragment2, ctx) {
645
+ const { name, data } = fragment2;
646
+ if (this.isPrimitive(data)) {
647
+ return this.#leaf(name, String(data), ctx.depth);
648
+ }
649
+ if (isFragment(data)) {
650
+ const child = this.renderFragment(data, { ...ctx, depth: ctx.depth + 1 });
651
+ return this.#wrapIndented(name, [child], ctx.depth);
652
+ }
653
+ if (Array.isArray(data)) {
654
+ return this.#renderArrayIndented(name, data, ctx.depth);
655
+ }
656
+ if (isFragmentObject(data)) {
657
+ const children = this.renderEntries(data, {
658
+ ...ctx,
659
+ depth: ctx.depth + 1
660
+ });
661
+ return this.#wrapIndented(name, children, ctx.depth);
662
+ }
663
+ return "";
664
+ }
665
+ #renderArrayIndented(name, items, depth) {
666
+ const fragmentItems = items.filter(isFragment);
667
+ const nonFragmentItems = items.filter((item) => !isFragment(item));
668
+ const children = [];
669
+ for (const item of nonFragmentItems) {
670
+ if (item != null) {
671
+ if (isFragmentObject(item)) {
672
+ children.push(
673
+ this.#wrapIndented(
674
+ pluralize.singular(name),
675
+ this.renderEntries(item, { depth: depth + 2, path: [] }),
676
+ depth + 1
677
+ )
678
+ );
679
+ } else {
680
+ children.push(
681
+ this.#leaf(pluralize.singular(name), String(item), depth + 1)
682
+ );
683
+ }
684
+ }
685
+ }
686
+ if (this.options.groupFragments && fragmentItems.length > 0) {
687
+ const groups = this.groupByName(fragmentItems);
688
+ for (const [groupName, groupFragments] of groups) {
689
+ const groupChildren = groupFragments.map(
690
+ (frag) => this.renderFragment(frag, { depth: depth + 2, path: [] })
691
+ );
692
+ const pluralName = pluralize.plural(groupName);
693
+ children.push(this.#wrapIndented(pluralName, groupChildren, depth + 1));
694
+ }
695
+ } else {
696
+ for (const frag of fragmentItems) {
697
+ children.push(
698
+ this.renderFragment(frag, { depth: depth + 1, path: [] })
699
+ );
700
+ }
701
+ }
702
+ return this.#wrapIndented(name, children, depth);
703
+ }
704
+ renderPrimitive(key, value, ctx) {
705
+ return this.#leaf(key, value, ctx.depth);
706
+ }
707
+ renderArray(key, items, ctx) {
708
+ if (!items.length) {
709
+ return "";
710
+ }
711
+ const itemTag = pluralize.singular(key);
712
+ const children = items.filter((item) => item != null).map((item) => {
713
+ if (isFragment(item)) {
714
+ return this.renderFragment(item, { ...ctx, depth: ctx.depth + 1 });
715
+ }
716
+ if (isFragmentObject(item)) {
717
+ return this.#wrapIndented(
718
+ itemTag,
719
+ this.renderEntries(item, { ...ctx, depth: ctx.depth + 2 }),
720
+ ctx.depth + 1
721
+ );
722
+ }
723
+ return this.#leaf(itemTag, String(item), ctx.depth + 1);
724
+ });
725
+ return this.#wrapIndented(key, children, ctx.depth);
726
+ }
727
+ renderObject(key, obj, ctx) {
728
+ const children = this.renderEntries(obj, { ...ctx, depth: ctx.depth + 1 });
729
+ return this.#wrapIndented(key, children, ctx.depth);
730
+ }
731
+ #escape(value) {
732
+ if (value == null) {
733
+ return "";
734
+ }
735
+ return value.replaceAll(/&/g, "&amp;").replaceAll(/</g, "&lt;").replaceAll(/>/g, "&gt;").replaceAll(/"/g, "&quot;").replaceAll(/'/g, "&apos;");
736
+ }
737
+ #indent(text, spaces) {
738
+ if (!text.trim()) {
739
+ return "";
740
+ }
741
+ const padding = " ".repeat(spaces);
742
+ return text.split("\n").map((line) => line.length ? padding + line : padding).join("\n");
743
+ }
744
+ #leaf(tag, value, depth) {
745
+ const safe = this.#escape(value);
746
+ const pad = " ".repeat(depth);
747
+ if (safe.includes("\n")) {
748
+ return `${pad}<${tag}>
749
+ ${this.#indent(safe, (depth + 1) * 2)}
750
+ ${pad}</${tag}>`;
751
+ }
752
+ return `${pad}<${tag}>${safe}</${tag}>`;
753
+ }
754
+ #wrap(tag, children) {
755
+ const content = children.filter(Boolean).join("\n");
756
+ if (!content) {
757
+ return "";
758
+ }
759
+ return `<${tag}>
760
+ ${content}
761
+ </${tag}>`;
762
+ }
763
+ #wrapIndented(tag, children, depth) {
764
+ const content = children.filter(Boolean).join("\n");
765
+ if (!content) {
766
+ return "";
767
+ }
768
+ const pad = " ".repeat(depth);
769
+ return `${pad}<${tag}>
770
+ ${content}
771
+ ${pad}</${tag}>`;
772
+ }
773
+ };
774
+ var ContextStore = class {
775
+ };
776
+ var ContextEngine = class {
777
+ /** Non-message fragments (role, hints, etc.) - not persisted in graph */
778
+ #fragments = [];
779
+ /** Pending message fragments to be added to graph */
780
+ #pendingMessages = [];
781
+ #store;
782
+ #chatId;
783
+ #userId;
784
+ #branchName;
785
+ #branch = null;
786
+ #chatData = null;
787
+ #initialized = false;
788
+ constructor(options) {
789
+ if (!options.chatId) {
790
+ throw new Error("chatId is required");
791
+ }
792
+ if (!options.userId) {
793
+ throw new Error("userId is required");
794
+ }
795
+ this.#store = options.store;
796
+ this.#chatId = options.chatId;
797
+ this.#userId = options.userId;
798
+ this.#branchName = "main";
799
+ }
800
+ /**
801
+ * Initialize the chat and branch if they don't exist.
802
+ */
803
+ async #ensureInitialized() {
804
+ if (this.#initialized) {
805
+ return;
806
+ }
807
+ this.#chatData = await this.#store.upsertChat({
808
+ id: this.#chatId,
809
+ userId: this.#userId
810
+ });
811
+ this.#branch = await this.#store.getActiveBranch(this.#chatId);
812
+ this.#initialized = true;
813
+ }
814
+ /**
815
+ * Create a new branch from a specific message.
816
+ * Shared logic between rewind() and btw().
817
+ */
818
+ async #createBranchFrom(messageId, switchTo) {
819
+ const branches = await this.#store.listBranches(this.#chatId);
820
+ const samePrefix = branches.filter(
821
+ (b) => b.name === this.#branchName || b.name.startsWith(`${this.#branchName}-v`)
822
+ );
823
+ const newBranchName = `${this.#branchName}-v${samePrefix.length + 1}`;
824
+ const newBranch = {
825
+ id: crypto.randomUUID(),
826
+ chatId: this.#chatId,
827
+ name: newBranchName,
828
+ headMessageId: messageId,
829
+ isActive: false,
830
+ createdAt: Date.now()
831
+ };
832
+ await this.#store.createBranch(newBranch);
833
+ if (switchTo) {
834
+ await this.#store.setActiveBranch(this.#chatId, newBranch.id);
835
+ this.#branch = { ...newBranch, isActive: true };
836
+ this.#branchName = newBranchName;
837
+ this.#pendingMessages = [];
838
+ }
839
+ const chain = await this.#store.getMessageChain(messageId);
840
+ return {
841
+ id: newBranch.id,
842
+ name: newBranch.name,
843
+ headMessageId: newBranch.headMessageId,
844
+ isActive: switchTo,
845
+ messageCount: chain.length,
846
+ createdAt: newBranch.createdAt
847
+ };
848
+ }
849
+ /**
850
+ * Get the current chat ID.
851
+ */
852
+ get chatId() {
853
+ return this.#chatId;
854
+ }
855
+ /**
856
+ * Get the current branch name.
857
+ */
858
+ get branch() {
859
+ return this.#branchName;
860
+ }
861
+ /**
862
+ * Get metadata for the current chat.
863
+ * Returns null if the chat hasn't been initialized yet.
864
+ */
865
+ get chat() {
866
+ if (!this.#chatData) {
867
+ return null;
868
+ }
869
+ return {
870
+ id: this.#chatData.id,
871
+ userId: this.#chatData.userId,
872
+ createdAt: this.#chatData.createdAt,
873
+ updatedAt: this.#chatData.updatedAt,
874
+ title: this.#chatData.title,
875
+ metadata: this.#chatData.metadata
876
+ };
877
+ }
878
+ /**
879
+ * Add fragments to the context.
880
+ *
881
+ * - Message fragments (user/assistant) are queued for persistence
882
+ * - Non-message fragments (role/hint) are kept in memory for system prompt
883
+ */
884
+ set(...fragments) {
885
+ for (const fragment2 of fragments) {
886
+ if (isMessageFragment(fragment2)) {
887
+ this.#pendingMessages.push(fragment2);
888
+ } else {
889
+ this.#fragments.push(fragment2);
890
+ }
891
+ }
892
+ return this;
893
+ }
894
+ // Unset a fragment by ID (not implemented yet)
895
+ unset(fragmentId) {
896
+ }
897
+ /**
898
+ * Render all fragments using the provided renderer.
899
+ * @internal Use resolve() instead for public API.
900
+ */
901
+ render(renderer) {
902
+ return renderer.render(this.#fragments);
903
+ }
904
+ /**
905
+ * Resolve context into AI SDK-ready format.
906
+ *
907
+ * - Initializes chat and branch if needed
908
+ * - Loads message history from the graph (walking parent chain)
909
+ * - Separates context fragments for system prompt
910
+ * - Combines with pending messages
911
+ *
912
+ * @example
913
+ * ```ts
914
+ * const context = new ContextEngine({ store, chatId: 'chat-1', userId: 'user-1' })
915
+ * .set(role('You are helpful'), user('Hello'));
916
+ *
917
+ * const { systemPrompt, messages } = await context.resolve();
918
+ * await generateText({ system: systemPrompt, messages });
919
+ * ```
920
+ */
921
+ async resolve(options) {
922
+ await this.#ensureInitialized();
923
+ const systemPrompt = options.renderer.render(this.#fragments);
924
+ const messages = [];
925
+ if (this.#branch?.headMessageId) {
926
+ const chain = await this.#store.getMessageChain(
927
+ this.#branch.headMessageId
928
+ );
929
+ for (const msg of chain) {
930
+ messages.push(message(msg.data).codec?.decode());
931
+ }
932
+ }
933
+ for (const fragment2 of this.#pendingMessages) {
934
+ const decoded = fragment2.codec.decode();
935
+ messages.push(decoded);
936
+ }
937
+ return { systemPrompt, messages };
938
+ }
939
+ /**
940
+ * Save pending messages to the graph.
941
+ *
942
+ * Each message is added as a node with parentId pointing to the previous message.
943
+ * The branch head is updated to point to the last message.
944
+ *
945
+ * @example
946
+ * ```ts
947
+ * context.set(user('Hello'));
948
+ * // AI responds...
949
+ * context.set(assistant('Hi there!'));
950
+ * await context.save(); // Persist to graph
951
+ * ```
952
+ */
953
+ async save() {
954
+ await this.#ensureInitialized();
955
+ if (this.#pendingMessages.length === 0) {
956
+ return;
957
+ }
958
+ let parentId = this.#branch.headMessageId;
959
+ const now = Date.now();
960
+ for (const fragment2 of this.#pendingMessages) {
961
+ const messageData = {
962
+ id: fragment2.id ?? crypto.randomUUID(),
963
+ chatId: this.#chatId,
964
+ parentId,
965
+ name: fragment2.name,
966
+ type: fragment2.type,
967
+ data: fragment2.codec.encode(),
968
+ createdAt: now
969
+ };
970
+ await this.#store.addMessage(messageData);
971
+ parentId = messageData.id;
972
+ }
973
+ await this.#store.updateBranchHead(this.#branch.id, parentId);
974
+ this.#branch.headMessageId = parentId;
975
+ this.#pendingMessages = [];
976
+ }
977
+ /**
978
+ * Estimate token count and cost for the full context.
979
+ *
980
+ * Includes:
981
+ * - System prompt fragments (role, hints, etc.)
982
+ * - Persisted chat messages (from store)
983
+ * - Pending messages (not yet saved)
984
+ *
985
+ * @param modelId - Model ID (e.g., "openai:gpt-4o", "anthropic:claude-3-5-sonnet")
986
+ * @param options - Optional settings
987
+ * @returns Estimate result with token counts, costs, and per-fragment breakdown
988
+ */
989
+ async estimate(modelId, options = {}) {
990
+ await this.#ensureInitialized();
991
+ const renderer = options.renderer ?? new XmlRenderer();
992
+ const registry = getModelsRegistry();
993
+ await registry.load();
994
+ const model = registry.get(modelId);
995
+ if (!model) {
996
+ throw new Error(
997
+ `Model "${modelId}" not found. Call load() first or check model ID.`
998
+ );
999
+ }
1000
+ const tokenizer = registry.getTokenizer(modelId);
1001
+ const fragmentEstimates = [];
1002
+ for (const fragment2 of this.#fragments) {
1003
+ const rendered = renderer.render([fragment2]);
1004
+ const tokens = tokenizer.count(rendered);
1005
+ const cost = tokens / 1e6 * model.cost.input;
1006
+ fragmentEstimates.push({
1007
+ id: fragment2.id,
1008
+ name: fragment2.name,
1009
+ tokens,
1010
+ cost
1011
+ });
1012
+ }
1013
+ if (this.#branch?.headMessageId) {
1014
+ const chain = await this.#store.getMessageChain(
1015
+ this.#branch.headMessageId
1016
+ );
1017
+ for (const msg of chain) {
1018
+ const content = String(msg.data);
1019
+ const tokens = tokenizer.count(content);
1020
+ const cost = tokens / 1e6 * model.cost.input;
1021
+ fragmentEstimates.push({
1022
+ name: msg.name,
1023
+ id: msg.id,
1024
+ tokens,
1025
+ cost
1026
+ });
1027
+ }
1028
+ }
1029
+ for (const fragment2 of this.#pendingMessages) {
1030
+ const content = String(fragment2.data);
1031
+ const tokens = tokenizer.count(content);
1032
+ const cost = tokens / 1e6 * model.cost.input;
1033
+ fragmentEstimates.push({
1034
+ name: fragment2.name,
1035
+ id: fragment2.id,
1036
+ tokens,
1037
+ cost
1038
+ });
1039
+ }
1040
+ const totalTokens = fragmentEstimates.reduce((sum, f) => sum + f.tokens, 0);
1041
+ const totalCost = fragmentEstimates.reduce((sum, f) => sum + f.cost, 0);
1042
+ return {
1043
+ model: model.id,
1044
+ provider: model.provider,
1045
+ tokens: totalTokens,
1046
+ cost: totalCost,
1047
+ limits: {
1048
+ context: model.limit.context,
1049
+ output: model.limit.output,
1050
+ exceedsContext: totalTokens > model.limit.context
1051
+ },
1052
+ fragments: fragmentEstimates
1053
+ };
1054
+ }
1055
+ /**
1056
+ * Rewind to a specific message by ID.
1057
+ *
1058
+ * Creates a new branch from that message, preserving the original branch.
1059
+ * The new branch becomes active.
1060
+ *
1061
+ * @param messageId - The message ID to rewind to
1062
+ * @returns The new branch info
1063
+ *
1064
+ * @example
1065
+ * ```ts
1066
+ * context.set(user('What is 2 + 2?', { id: 'q1' }));
1067
+ * context.set(assistant('The answer is 5.', { id: 'wrong' })); // Oops!
1068
+ * await context.save();
1069
+ *
1070
+ * // Rewind to the question, creates new branch
1071
+ * const newBranch = await context.rewind('q1');
1072
+ *
1073
+ * // Now add correct answer on new branch
1074
+ * context.set(assistant('The answer is 4.'));
1075
+ * await context.save();
1076
+ * ```
1077
+ */
1078
+ async rewind(messageId) {
1079
+ await this.#ensureInitialized();
1080
+ const message2 = await this.#store.getMessage(messageId);
1081
+ if (!message2) {
1082
+ throw new Error(`Message "${messageId}" not found`);
1083
+ }
1084
+ if (message2.chatId !== this.#chatId) {
1085
+ throw new Error(`Message "${messageId}" belongs to a different chat`);
1086
+ }
1087
+ return this.#createBranchFrom(messageId, true);
1088
+ }
1089
+ /**
1090
+ * Create a checkpoint at the current position.
1091
+ *
1092
+ * A checkpoint is a named pointer to the current branch head.
1093
+ * Use restore() to return to this point later.
1094
+ *
1095
+ * @param name - Name for the checkpoint
1096
+ * @returns The checkpoint info
1097
+ *
1098
+ * @example
1099
+ * ```ts
1100
+ * context.set(user('I want to learn a new skill.'));
1101
+ * context.set(assistant('Would you like coding or cooking?'));
1102
+ * await context.save();
1103
+ *
1104
+ * // Save checkpoint before user's choice
1105
+ * const cp = await context.checkpoint('before-choice');
1106
+ * ```
1107
+ */
1108
+ async checkpoint(name) {
1109
+ await this.#ensureInitialized();
1110
+ if (!this.#branch?.headMessageId) {
1111
+ throw new Error("Cannot create checkpoint: no messages in conversation");
1112
+ }
1113
+ const checkpoint = {
1114
+ id: crypto.randomUUID(),
1115
+ chatId: this.#chatId,
1116
+ name,
1117
+ messageId: this.#branch.headMessageId,
1118
+ createdAt: Date.now()
1119
+ };
1120
+ await this.#store.createCheckpoint(checkpoint);
1121
+ return {
1122
+ id: checkpoint.id,
1123
+ name: checkpoint.name,
1124
+ messageId: checkpoint.messageId,
1125
+ createdAt: checkpoint.createdAt
1126
+ };
1127
+ }
1128
+ /**
1129
+ * Restore to a checkpoint by creating a new branch from that point.
1130
+ *
1131
+ * @param name - Name of the checkpoint to restore
1132
+ * @returns The new branch info
1133
+ *
1134
+ * @example
1135
+ * ```ts
1136
+ * // User chose cooking, but wants to try coding path
1137
+ * await context.restore('before-choice');
1138
+ *
1139
+ * context.set(user('I want to learn coding.'));
1140
+ * context.set(assistant('Python is a great starting language!'));
1141
+ * await context.save();
1142
+ * ```
1143
+ */
1144
+ async restore(name) {
1145
+ await this.#ensureInitialized();
1146
+ const checkpoint = await this.#store.getCheckpoint(this.#chatId, name);
1147
+ if (!checkpoint) {
1148
+ throw new Error(
1149
+ `Checkpoint "${name}" not found in chat "${this.#chatId}"`
1150
+ );
1151
+ }
1152
+ return this.rewind(checkpoint.messageId);
1153
+ }
1154
+ /**
1155
+ * Switch to a different branch by name.
1156
+ *
1157
+ * @param name - Branch name to switch to
1158
+ *
1159
+ * @example
1160
+ * ```ts
1161
+ * // List branches (via store)
1162
+ * const branches = await store.listBranches(context.chatId);
1163
+ * console.log(branches); // [{name: 'main', ...}, {name: 'main-v2', ...}]
1164
+ *
1165
+ * // Switch to original branch
1166
+ * await context.switchBranch('main');
1167
+ * ```
1168
+ */
1169
+ async switchBranch(name) {
1170
+ await this.#ensureInitialized();
1171
+ const branch = await this.#store.getBranch(this.#chatId, name);
1172
+ if (!branch) {
1173
+ throw new Error(`Branch "${name}" not found in chat "${this.#chatId}"`);
1174
+ }
1175
+ await this.#store.setActiveBranch(this.#chatId, branch.id);
1176
+ this.#branch = { ...branch, isActive: true };
1177
+ this.#branchName = name;
1178
+ this.#pendingMessages = [];
1179
+ }
1180
+ /**
1181
+ * Create a parallel branch from the current position ("by the way").
1182
+ *
1183
+ * Use this when you want to fork the conversation without leaving
1184
+ * the current branch. Common use case: user wants to ask another
1185
+ * question while waiting for the model to respond.
1186
+ *
1187
+ * Unlike rewind(), this method:
1188
+ * - Uses the current HEAD (no messageId needed)
1189
+ * - Does NOT switch to the new branch
1190
+ * - Keeps pending messages intact
1191
+ *
1192
+ * @returns The new branch info (does not switch to it)
1193
+ * @throws Error if no messages exist in the conversation
1194
+ *
1195
+ * @example
1196
+ * ```ts
1197
+ * // User asked a question, model is generating...
1198
+ * context.set(user('What is the weather?'));
1199
+ * await context.save();
1200
+ *
1201
+ * // User wants to ask something else without waiting
1202
+ * const newBranch = await context.btw();
1203
+ * // newBranch = { name: 'main-v2', ... }
1204
+ *
1205
+ * // Later, switch to the new branch and add the question
1206
+ * await context.switchBranch(newBranch.name);
1207
+ * context.set(user('Also, what time is it?'));
1208
+ * await context.save();
1209
+ * ```
1210
+ */
1211
+ async btw() {
1212
+ await this.#ensureInitialized();
1213
+ if (!this.#branch?.headMessageId) {
1214
+ throw new Error("Cannot create btw branch: no messages in conversation");
1215
+ }
1216
+ return this.#createBranchFrom(this.#branch.headMessageId, false);
1217
+ }
1218
+ /**
1219
+ * Update metadata for the current chat.
1220
+ *
1221
+ * @param updates - Partial metadata to merge (title, metadata)
1222
+ *
1223
+ * @example
1224
+ * ```ts
1225
+ * await context.updateChat({
1226
+ * title: 'Coding Help Session',
1227
+ * metadata: { tags: ['python', 'debugging'] }
1228
+ * });
1229
+ * ```
1230
+ */
1231
+ async updateChat(updates) {
1232
+ await this.#ensureInitialized();
1233
+ const storeUpdates = {};
1234
+ if (updates.title !== void 0) {
1235
+ storeUpdates.title = updates.title;
1236
+ }
1237
+ if (updates.metadata !== void 0) {
1238
+ storeUpdates.metadata = {
1239
+ ...this.#chatData?.metadata,
1240
+ ...updates.metadata
1241
+ };
1242
+ }
1243
+ this.#chatData = await this.#store.updateChat(this.#chatId, storeUpdates);
1244
+ }
1245
+ /**
1246
+ * Consolidate context fragments (no-op for now).
1247
+ *
1248
+ * This is a placeholder for future functionality that merges context fragments
1249
+ * using specific rules. Currently, it does nothing.
1250
+ *
1251
+ * @experimental
1252
+ */
1253
+ consolidate() {
1254
+ return void 0;
1255
+ }
1256
+ /**
1257
+ * Inspect the full context state for debugging.
1258
+ * Returns a JSON-serializable object with context information.
1259
+ *
1260
+ * @param options - Inspection options (modelId and renderer required)
1261
+ * @returns Complete inspection data including estimates, rendered output, fragments, and graph
1262
+ *
1263
+ * @example
1264
+ * ```ts
1265
+ * const inspection = await context.inspect({
1266
+ * modelId: 'openai:gpt-4o',
1267
+ * renderer: new XmlRenderer(),
1268
+ * });
1269
+ * console.log(JSON.stringify(inspection, null, 2));
1270
+ *
1271
+ * // Or write to file for analysis
1272
+ * await fs.writeFile('context-debug.json', JSON.stringify(inspection, null, 2));
1273
+ * ```
1274
+ */
1275
+ async inspect(options) {
1276
+ await this.#ensureInitialized();
1277
+ const { renderer } = options;
1278
+ const estimateResult = await this.estimate(options.modelId, { renderer });
1279
+ const rendered = renderer.render(this.#fragments);
1280
+ const persistedMessages = [];
1281
+ if (this.#branch?.headMessageId) {
1282
+ const chain = await this.#store.getMessageChain(
1283
+ this.#branch.headMessageId
1284
+ );
1285
+ persistedMessages.push(...chain);
1286
+ }
1287
+ const graph = await this.#store.getGraph(this.#chatId);
1288
+ return {
1289
+ estimate: estimateResult,
1290
+ rendered,
1291
+ fragments: {
1292
+ context: [...this.#fragments],
1293
+ pending: [...this.#pendingMessages],
1294
+ persisted: persistedMessages
1295
+ },
1296
+ graph,
1297
+ meta: {
1298
+ chatId: this.#chatId,
1299
+ branch: this.#branchName,
1300
+ timestamp: Date.now()
1301
+ }
1302
+ };
1303
+ }
1304
+ };
1305
+ function persona(input) {
1306
+ return {
1307
+ name: "persona",
1308
+ data: {
1309
+ name: input.name,
1310
+ ...input.role && { role: input.role },
1311
+ ...input.objective && { objective: input.objective },
1312
+ ...input.tone && { tone: input.tone }
1313
+ }
1314
+ };
1315
+ }
1316
+ function pass(part) {
1317
+ return { type: "pass", part };
1318
+ }
1319
+ function runGuardrailChain(part, guardrails, context) {
1320
+ let currentPart = part;
1321
+ for (const guardrail2 of guardrails) {
1322
+ const result = guardrail2.handle(currentPart, context);
1323
+ if (result.type === "fail") {
1324
+ return result;
1325
+ }
1326
+ currentPart = result.part;
1327
+ }
1328
+ return pass(currentPart);
1329
+ }
1330
+ var STORE_DDL = `
1331
+ -- Chats table
1332
+ -- createdAt/updatedAt: DEFAULT for insert, inline SET for updates
1333
+ CREATE TABLE IF NOT EXISTS chats (
1334
+ id TEXT PRIMARY KEY,
1335
+ userId TEXT NOT NULL,
1336
+ title TEXT,
1337
+ metadata TEXT,
1338
+ createdAt INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),
1339
+ updatedAt INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000)
1340
+ );
258
1341
 
259
- <instructions>
260
- Write a business context that helps another agent answer questions accurately.
1342
+ CREATE INDEX IF NOT EXISTS idx_chats_updatedAt ON chats(updatedAt);
1343
+ CREATE INDEX IF NOT EXISTS idx_chats_userId ON chats(userId);
261
1344
 
262
- For EACH table, do queries ONE AT A TIME:
263
- 1. SELECT COUNT(*) to get row count
264
- 2. SELECT * LIMIT 3 to see sample data
1345
+ -- Messages table (nodes in the DAG)
1346
+ CREATE TABLE IF NOT EXISTS messages (
1347
+ id TEXT PRIMARY KEY,
1348
+ chatId TEXT NOT NULL,
1349
+ parentId TEXT,
1350
+ name TEXT NOT NULL,
1351
+ type TEXT,
1352
+ data TEXT NOT NULL,
1353
+ createdAt INTEGER NOT NULL,
1354
+ FOREIGN KEY (chatId) REFERENCES chats(id) ON DELETE CASCADE,
1355
+ FOREIGN KEY (parentId) REFERENCES messages(id)
1356
+ );
265
1357
 
266
- Then write a report with:
267
- - What business this database is for
268
- - For each table: purpose, row count, and example of what the data looks like
1358
+ CREATE INDEX IF NOT EXISTS idx_messages_chatId ON messages(chatId);
1359
+ CREATE INDEX IF NOT EXISTS idx_messages_parentId ON messages(parentId);
269
1360
 
270
- Include concrete examples like "Track prices are $0.99",
271
- "Customer names like 'Luís Gonçalves'", etc.
1361
+ -- Branches table (pointers to head messages)
1362
+ CREATE TABLE IF NOT EXISTS branches (
1363
+ id TEXT PRIMARY KEY,
1364
+ chatId TEXT NOT NULL,
1365
+ name TEXT NOT NULL,
1366
+ headMessageId TEXT,
1367
+ isActive INTEGER NOT NULL DEFAULT 0,
1368
+ createdAt INTEGER NOT NULL,
1369
+ FOREIGN KEY (chatId) REFERENCES chats(id) ON DELETE CASCADE,
1370
+ FOREIGN KEY (headMessageId) REFERENCES messages(id),
1371
+ UNIQUE(chatId, name)
1372
+ );
272
1373
 
273
- Keep it 400-600 words, conversational style.
274
- </instructions>
275
- `,
276
- tools: {
277
- query_database: tool({
278
- description: "Execute a SELECT query to explore the database and gather insights.",
279
- inputSchema: z.object({
280
- sql: z.string().describe("The SELECT query to execute"),
281
- purpose: z.string().describe("What insight you are trying to gather with this query")
282
- }),
283
- execute: ({ sql }, options) => {
284
- const state = toState(options);
285
- return state.adapter.execute(sql);
1374
+ CREATE INDEX IF NOT EXISTS idx_branches_chatId ON branches(chatId);
1375
+
1376
+ -- Checkpoints table (pointers to message nodes)
1377
+ CREATE TABLE IF NOT EXISTS checkpoints (
1378
+ id TEXT PRIMARY KEY,
1379
+ chatId TEXT NOT NULL,
1380
+ name TEXT NOT NULL,
1381
+ messageId TEXT NOT NULL,
1382
+ createdAt INTEGER NOT NULL,
1383
+ FOREIGN KEY (chatId) REFERENCES chats(id) ON DELETE CASCADE,
1384
+ FOREIGN KEY (messageId) REFERENCES messages(id),
1385
+ UNIQUE(chatId, name)
1386
+ );
1387
+
1388
+ CREATE INDEX IF NOT EXISTS idx_checkpoints_chatId ON checkpoints(chatId);
1389
+
1390
+ -- FTS5 virtual table for full-text search
1391
+ -- messageId/chatId/name are UNINDEXED (stored but not searchable, used for filtering/joining)
1392
+ -- Only 'content' is indexed for full-text search
1393
+ CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(
1394
+ messageId UNINDEXED,
1395
+ chatId UNINDEXED,
1396
+ name UNINDEXED,
1397
+ content,
1398
+ tokenize='porter unicode61'
1399
+ );
1400
+ `;
1401
+ var SqliteContextStore = class extends ContextStore {
1402
+ #db;
1403
+ constructor(path3) {
1404
+ super();
1405
+ this.#db = new DatabaseSync(path3);
1406
+ this.#db.exec("PRAGMA foreign_keys = ON");
1407
+ this.#db.exec(STORE_DDL);
1408
+ }
1409
+ /**
1410
+ * Execute a function within a transaction.
1411
+ * Automatically commits on success or rolls back on error.
1412
+ */
1413
+ #useTransaction(fn) {
1414
+ this.#db.exec("BEGIN TRANSACTION");
1415
+ try {
1416
+ const result = fn();
1417
+ this.#db.exec("COMMIT");
1418
+ return result;
1419
+ } catch (error) {
1420
+ this.#db.exec("ROLLBACK");
1421
+ throw error;
1422
+ }
1423
+ }
1424
+ // ==========================================================================
1425
+ // Chat Operations
1426
+ // ==========================================================================
1427
+ async createChat(chat) {
1428
+ this.#useTransaction(() => {
1429
+ this.#db.prepare(
1430
+ `INSERT INTO chats (id, userId, title, metadata)
1431
+ VALUES (?, ?, ?, ?)`
1432
+ ).run(
1433
+ chat.id,
1434
+ chat.userId,
1435
+ chat.title ?? null,
1436
+ chat.metadata ? JSON.stringify(chat.metadata) : null
1437
+ );
1438
+ this.#db.prepare(
1439
+ `INSERT INTO branches (id, chatId, name, headMessageId, isActive, createdAt)
1440
+ VALUES (?, ?, 'main', NULL, 1, ?)`
1441
+ ).run(crypto.randomUUID(), chat.id, Date.now());
1442
+ });
1443
+ }
1444
+ async upsertChat(chat) {
1445
+ return this.#useTransaction(() => {
1446
+ const row = this.#db.prepare(
1447
+ `INSERT INTO chats (id, userId, title, metadata)
1448
+ VALUES (?, ?, ?, ?)
1449
+ ON CONFLICT(id) DO UPDATE SET id = excluded.id
1450
+ RETURNING *`
1451
+ ).get(
1452
+ chat.id,
1453
+ chat.userId,
1454
+ chat.title ?? null,
1455
+ chat.metadata ? JSON.stringify(chat.metadata) : null
1456
+ );
1457
+ this.#db.prepare(
1458
+ `INSERT OR IGNORE INTO branches (id, chatId, name, headMessageId, isActive, createdAt)
1459
+ VALUES (?, ?, 'main', NULL, 1, ?)`
1460
+ ).run(crypto.randomUUID(), chat.id, Date.now());
1461
+ return {
1462
+ id: row.id,
1463
+ userId: row.userId,
1464
+ title: row.title ?? void 0,
1465
+ metadata: row.metadata ? JSON.parse(row.metadata) : void 0,
1466
+ createdAt: row.createdAt,
1467
+ updatedAt: row.updatedAt
1468
+ };
1469
+ });
1470
+ }
1471
+ async getChat(chatId) {
1472
+ const row = this.#db.prepare("SELECT * FROM chats WHERE id = ?").get(chatId);
1473
+ if (!row) {
1474
+ return void 0;
1475
+ }
1476
+ return {
1477
+ id: row.id,
1478
+ userId: row.userId,
1479
+ title: row.title ?? void 0,
1480
+ metadata: row.metadata ? JSON.parse(row.metadata) : void 0,
1481
+ createdAt: row.createdAt,
1482
+ updatedAt: row.updatedAt
1483
+ };
1484
+ }
1485
+ async updateChat(chatId, updates) {
1486
+ const setClauses = ["updatedAt = strftime('%s', 'now') * 1000"];
1487
+ const params = [];
1488
+ if (updates.title !== void 0) {
1489
+ setClauses.push("title = ?");
1490
+ params.push(updates.title ?? null);
1491
+ }
1492
+ if (updates.metadata !== void 0) {
1493
+ setClauses.push("metadata = ?");
1494
+ params.push(JSON.stringify(updates.metadata));
1495
+ }
1496
+ params.push(chatId);
1497
+ const row = this.#db.prepare(
1498
+ `UPDATE chats SET ${setClauses.join(", ")} WHERE id = ? RETURNING *`
1499
+ ).get(...params);
1500
+ return {
1501
+ id: row.id,
1502
+ userId: row.userId,
1503
+ title: row.title ?? void 0,
1504
+ metadata: row.metadata ? JSON.parse(row.metadata) : void 0,
1505
+ createdAt: row.createdAt,
1506
+ updatedAt: row.updatedAt
1507
+ };
1508
+ }
1509
+ async listChats(options) {
1510
+ const params = [];
1511
+ let whereClause = "";
1512
+ let limitClause = "";
1513
+ if (options?.userId) {
1514
+ whereClause = "WHERE c.userId = ?";
1515
+ params.push(options.userId);
1516
+ }
1517
+ if (options?.limit !== void 0) {
1518
+ limitClause = " LIMIT ?";
1519
+ params.push(options.limit);
1520
+ if (options.offset !== void 0) {
1521
+ limitClause += " OFFSET ?";
1522
+ params.push(options.offset);
1523
+ }
1524
+ }
1525
+ const rows = this.#db.prepare(
1526
+ `SELECT
1527
+ c.id,
1528
+ c.userId,
1529
+ c.title,
1530
+ c.createdAt,
1531
+ c.updatedAt,
1532
+ COUNT(DISTINCT m.id) as messageCount,
1533
+ COUNT(DISTINCT b.id) as branchCount
1534
+ FROM chats c
1535
+ LEFT JOIN messages m ON m.chatId = c.id
1536
+ LEFT JOIN branches b ON b.chatId = c.id
1537
+ ${whereClause}
1538
+ GROUP BY c.id
1539
+ ORDER BY c.updatedAt DESC${limitClause}`
1540
+ ).all(...params);
1541
+ return rows.map((row) => ({
1542
+ id: row.id,
1543
+ userId: row.userId,
1544
+ title: row.title ?? void 0,
1545
+ messageCount: row.messageCount,
1546
+ branchCount: row.branchCount,
1547
+ createdAt: row.createdAt,
1548
+ updatedAt: row.updatedAt
1549
+ }));
1550
+ }
1551
+ async deleteChat(chatId, options) {
1552
+ return this.#useTransaction(() => {
1553
+ const messageIds = this.#db.prepare("SELECT id FROM messages WHERE chatId = ?").all(chatId);
1554
+ let sql = "DELETE FROM chats WHERE id = ?";
1555
+ const params = [chatId];
1556
+ if (options?.userId !== void 0) {
1557
+ sql += " AND userId = ?";
1558
+ params.push(options.userId);
1559
+ }
1560
+ const result = this.#db.prepare(sql).run(...params);
1561
+ if (result.changes > 0 && messageIds.length > 0) {
1562
+ const placeholders = messageIds.map(() => "?").join(", ");
1563
+ this.#db.prepare(
1564
+ `DELETE FROM messages_fts WHERE messageId IN (${placeholders})`
1565
+ ).run(...messageIds.map((m) => m.id));
1566
+ }
1567
+ return result.changes > 0;
1568
+ });
1569
+ }
1570
+ // ==========================================================================
1571
+ // Message Operations (Graph Nodes)
1572
+ // ==========================================================================
1573
+ async addMessage(message2) {
1574
+ this.#db.prepare(
1575
+ `INSERT INTO messages (id, chatId, parentId, name, type, data, createdAt)
1576
+ VALUES (?, ?, ?, ?, ?, ?, ?)
1577
+ ON CONFLICT(id) DO UPDATE SET
1578
+ parentId = excluded.parentId,
1579
+ name = excluded.name,
1580
+ type = excluded.type,
1581
+ data = excluded.data`
1582
+ ).run(
1583
+ message2.id,
1584
+ message2.chatId,
1585
+ message2.parentId,
1586
+ message2.name,
1587
+ message2.type ?? null,
1588
+ JSON.stringify(message2.data),
1589
+ message2.createdAt
1590
+ );
1591
+ const content = typeof message2.data === "string" ? message2.data : JSON.stringify(message2.data);
1592
+ this.#db.prepare(`DELETE FROM messages_fts WHERE messageId = ?`).run(message2.id);
1593
+ this.#db.prepare(
1594
+ `INSERT INTO messages_fts(messageId, chatId, name, content)
1595
+ VALUES (?, ?, ?, ?)`
1596
+ ).run(message2.id, message2.chatId, message2.name, content);
1597
+ }
1598
+ async getMessage(messageId) {
1599
+ const row = this.#db.prepare("SELECT * FROM messages WHERE id = ?").get(messageId);
1600
+ if (!row) {
1601
+ return void 0;
1602
+ }
1603
+ return {
1604
+ id: row.id,
1605
+ chatId: row.chatId,
1606
+ parentId: row.parentId,
1607
+ name: row.name,
1608
+ type: row.type ?? void 0,
1609
+ data: JSON.parse(row.data),
1610
+ createdAt: row.createdAt
1611
+ };
1612
+ }
1613
+ async getMessageChain(headId) {
1614
+ const rows = this.#db.prepare(
1615
+ `WITH RECURSIVE chain AS (
1616
+ SELECT *, 0 as depth FROM messages WHERE id = ?
1617
+ UNION ALL
1618
+ SELECT m.*, c.depth + 1 FROM messages m
1619
+ INNER JOIN chain c ON m.id = c.parentId
1620
+ )
1621
+ SELECT * FROM chain
1622
+ ORDER BY depth DESC`
1623
+ ).all(headId);
1624
+ return rows.map((row) => ({
1625
+ id: row.id,
1626
+ chatId: row.chatId,
1627
+ parentId: row.parentId,
1628
+ name: row.name,
1629
+ type: row.type ?? void 0,
1630
+ data: JSON.parse(row.data),
1631
+ createdAt: row.createdAt
1632
+ }));
1633
+ }
1634
+ async hasChildren(messageId) {
1635
+ const row = this.#db.prepare(
1636
+ "SELECT EXISTS(SELECT 1 FROM messages WHERE parentId = ?) as hasChildren"
1637
+ ).get(messageId);
1638
+ return row.hasChildren === 1;
1639
+ }
1640
+ async getMessages(chatId) {
1641
+ const chat = await this.getChat(chatId);
1642
+ if (!chat) {
1643
+ throw new Error(`Chat "${chatId}" not found`);
1644
+ }
1645
+ const activeBranch = await this.getActiveBranch(chatId);
1646
+ if (!activeBranch?.headMessageId) {
1647
+ return [];
1648
+ }
1649
+ return this.getMessageChain(activeBranch.headMessageId);
1650
+ }
1651
+ // ==========================================================================
1652
+ // Branch Operations
1653
+ // ==========================================================================
1654
+ async createBranch(branch) {
1655
+ this.#db.prepare(
1656
+ `INSERT INTO branches (id, chatId, name, headMessageId, isActive, createdAt)
1657
+ VALUES (?, ?, ?, ?, ?, ?)`
1658
+ ).run(
1659
+ branch.id,
1660
+ branch.chatId,
1661
+ branch.name,
1662
+ branch.headMessageId,
1663
+ branch.isActive ? 1 : 0,
1664
+ branch.createdAt
1665
+ );
1666
+ }
1667
+ async getBranch(chatId, name) {
1668
+ const row = this.#db.prepare("SELECT * FROM branches WHERE chatId = ? AND name = ?").get(chatId, name);
1669
+ if (!row) {
1670
+ return void 0;
1671
+ }
1672
+ return {
1673
+ id: row.id,
1674
+ chatId: row.chatId,
1675
+ name: row.name,
1676
+ headMessageId: row.headMessageId,
1677
+ isActive: row.isActive === 1,
1678
+ createdAt: row.createdAt
1679
+ };
1680
+ }
1681
+ async getActiveBranch(chatId) {
1682
+ const row = this.#db.prepare("SELECT * FROM branches WHERE chatId = ? AND isActive = 1").get(chatId);
1683
+ if (!row) {
1684
+ return void 0;
1685
+ }
1686
+ return {
1687
+ id: row.id,
1688
+ chatId: row.chatId,
1689
+ name: row.name,
1690
+ headMessageId: row.headMessageId,
1691
+ isActive: true,
1692
+ createdAt: row.createdAt
1693
+ };
1694
+ }
1695
+ async setActiveBranch(chatId, branchId) {
1696
+ this.#db.prepare("UPDATE branches SET isActive = 0 WHERE chatId = ?").run(chatId);
1697
+ this.#db.prepare("UPDATE branches SET isActive = 1 WHERE id = ?").run(branchId);
1698
+ }
1699
+ async updateBranchHead(branchId, messageId) {
1700
+ this.#db.prepare("UPDATE branches SET headMessageId = ? WHERE id = ?").run(messageId, branchId);
1701
+ }
1702
+ async listBranches(chatId) {
1703
+ const branches = this.#db.prepare(
1704
+ `SELECT
1705
+ b.id,
1706
+ b.name,
1707
+ b.headMessageId,
1708
+ b.isActive,
1709
+ b.createdAt
1710
+ FROM branches b
1711
+ WHERE b.chatId = ?
1712
+ ORDER BY b.createdAt ASC`
1713
+ ).all(chatId);
1714
+ const result = [];
1715
+ for (const branch of branches) {
1716
+ let messageCount = 0;
1717
+ if (branch.headMessageId) {
1718
+ const countRow = this.#db.prepare(
1719
+ `WITH RECURSIVE chain AS (
1720
+ SELECT id, parentId FROM messages WHERE id = ?
1721
+ UNION ALL
1722
+ SELECT m.id, m.parentId FROM messages m
1723
+ INNER JOIN chain c ON m.id = c.parentId
1724
+ )
1725
+ SELECT COUNT(*) as count FROM chain`
1726
+ ).get(branch.headMessageId);
1727
+ messageCount = countRow.count;
1728
+ }
1729
+ result.push({
1730
+ id: branch.id,
1731
+ name: branch.name,
1732
+ headMessageId: branch.headMessageId,
1733
+ isActive: branch.isActive === 1,
1734
+ messageCount,
1735
+ createdAt: branch.createdAt
1736
+ });
1737
+ }
1738
+ return result;
1739
+ }
1740
+ // ==========================================================================
1741
+ // Checkpoint Operations
1742
+ // ==========================================================================
1743
+ async createCheckpoint(checkpoint) {
1744
+ this.#db.prepare(
1745
+ `INSERT INTO checkpoints (id, chatId, name, messageId, createdAt)
1746
+ VALUES (?, ?, ?, ?, ?)
1747
+ ON CONFLICT(chatId, name) DO UPDATE SET
1748
+ messageId = excluded.messageId,
1749
+ createdAt = excluded.createdAt`
1750
+ ).run(
1751
+ checkpoint.id,
1752
+ checkpoint.chatId,
1753
+ checkpoint.name,
1754
+ checkpoint.messageId,
1755
+ checkpoint.createdAt
1756
+ );
1757
+ }
1758
+ async getCheckpoint(chatId, name) {
1759
+ const row = this.#db.prepare("SELECT * FROM checkpoints WHERE chatId = ? AND name = ?").get(chatId, name);
1760
+ if (!row) {
1761
+ return void 0;
1762
+ }
1763
+ return {
1764
+ id: row.id,
1765
+ chatId: row.chatId,
1766
+ name: row.name,
1767
+ messageId: row.messageId,
1768
+ createdAt: row.createdAt
1769
+ };
1770
+ }
1771
+ async listCheckpoints(chatId) {
1772
+ const rows = this.#db.prepare(
1773
+ `SELECT id, name, messageId, createdAt
1774
+ FROM checkpoints
1775
+ WHERE chatId = ?
1776
+ ORDER BY createdAt DESC`
1777
+ ).all(chatId);
1778
+ return rows.map((row) => ({
1779
+ id: row.id,
1780
+ name: row.name,
1781
+ messageId: row.messageId,
1782
+ createdAt: row.createdAt
1783
+ }));
1784
+ }
1785
+ async deleteCheckpoint(chatId, name) {
1786
+ this.#db.prepare("DELETE FROM checkpoints WHERE chatId = ? AND name = ?").run(chatId, name);
1787
+ }
1788
+ // ==========================================================================
1789
+ // Search Operations
1790
+ // ==========================================================================
1791
+ async searchMessages(chatId, query, options) {
1792
+ const limit = options?.limit ?? 20;
1793
+ const roles = options?.roles;
1794
+ let sql = `
1795
+ SELECT
1796
+ m.id,
1797
+ m.chatId,
1798
+ m.parentId,
1799
+ m.name,
1800
+ m.type,
1801
+ m.data,
1802
+ m.createdAt,
1803
+ fts.rank,
1804
+ snippet(messages_fts, 3, '<mark>', '</mark>', '...', 32) as snippet
1805
+ FROM messages_fts fts
1806
+ JOIN messages m ON m.id = fts.messageId
1807
+ WHERE messages_fts MATCH ?
1808
+ AND fts.chatId = ?
1809
+ `;
1810
+ const params = [query, chatId];
1811
+ if (roles && roles.length > 0) {
1812
+ const placeholders = roles.map(() => "?").join(", ");
1813
+ sql += ` AND fts.name IN (${placeholders})`;
1814
+ params.push(...roles);
1815
+ }
1816
+ sql += " ORDER BY fts.rank LIMIT ?";
1817
+ params.push(limit);
1818
+ const rows = this.#db.prepare(sql).all(...params);
1819
+ return rows.map((row) => ({
1820
+ message: {
1821
+ id: row.id,
1822
+ chatId: row.chatId,
1823
+ parentId: row.parentId,
1824
+ name: row.name,
1825
+ type: row.type ?? void 0,
1826
+ data: JSON.parse(row.data),
1827
+ createdAt: row.createdAt
1828
+ },
1829
+ rank: row.rank,
1830
+ snippet: row.snippet
1831
+ }));
1832
+ }
1833
+ // ==========================================================================
1834
+ // Visualization Operations
1835
+ // ==========================================================================
1836
+ async getGraph(chatId) {
1837
+ const messageRows = this.#db.prepare(
1838
+ `SELECT id, parentId, name, data, createdAt
1839
+ FROM messages
1840
+ WHERE chatId = ?
1841
+ ORDER BY createdAt ASC`
1842
+ ).all(chatId);
1843
+ const nodes = messageRows.map((row) => {
1844
+ const data = JSON.parse(row.data);
1845
+ const content = typeof data === "string" ? data : JSON.stringify(data);
1846
+ return {
1847
+ id: row.id,
1848
+ parentId: row.parentId,
1849
+ role: row.name,
1850
+ content: content.length > 50 ? content.slice(0, 50) + "..." : content,
1851
+ createdAt: row.createdAt
1852
+ };
1853
+ });
1854
+ const branchRows = this.#db.prepare(
1855
+ `SELECT name, headMessageId, isActive
1856
+ FROM branches
1857
+ WHERE chatId = ?
1858
+ ORDER BY createdAt ASC`
1859
+ ).all(chatId);
1860
+ const branches = branchRows.map((row) => ({
1861
+ name: row.name,
1862
+ headMessageId: row.headMessageId,
1863
+ isActive: row.isActive === 1
1864
+ }));
1865
+ const checkpointRows = this.#db.prepare(
1866
+ `SELECT name, messageId
1867
+ FROM checkpoints
1868
+ WHERE chatId = ?
1869
+ ORDER BY createdAt ASC`
1870
+ ).all(chatId);
1871
+ const checkpoints = checkpointRows.map((row) => ({
1872
+ name: row.name,
1873
+ messageId: row.messageId
1874
+ }));
1875
+ return {
1876
+ chatId,
1877
+ nodes,
1878
+ branches,
1879
+ checkpoints
1880
+ };
1881
+ }
1882
+ };
1883
+ var InMemoryContextStore = class extends SqliteContextStore {
1884
+ constructor() {
1885
+ super(":memory:");
1886
+ }
1887
+ };
1888
+ var Agent = class _Agent {
1889
+ #options;
1890
+ #guardrails = [];
1891
+ tools;
1892
+ constructor(options) {
1893
+ this.#options = options;
1894
+ this.tools = options.tools || {};
1895
+ this.#guardrails = options.guardrails || [];
1896
+ }
1897
+ async generate(contextVariables, config) {
1898
+ if (!this.#options.context) {
1899
+ throw new Error(`Agent ${this.#options.name} is missing a context.`);
1900
+ }
1901
+ if (!this.#options.model) {
1902
+ throw new Error(`Agent ${this.#options.name} is missing a model.`);
1903
+ }
1904
+ const { messages, systemPrompt } = await this.#options.context.resolve({
1905
+ renderer: new XmlRenderer()
1906
+ });
1907
+ return generateText({
1908
+ abortSignal: config?.abortSignal,
1909
+ providerOptions: this.#options.providerOptions,
1910
+ model: this.#options.model,
1911
+ system: systemPrompt,
1912
+ messages: await convertToModelMessages(messages),
1913
+ stopWhen: stepCountIs(25),
1914
+ tools: this.#options.tools,
1915
+ experimental_context: contextVariables,
1916
+ experimental_repairToolCall: repairToolCall,
1917
+ toolChoice: this.#options.toolChoice,
1918
+ onStepFinish: (step) => {
1919
+ const toolCall = step.toolCalls.at(-1);
1920
+ if (toolCall) {
1921
+ console.log(
1922
+ `Debug: ${chalk2.yellow("ToolCalled")}: ${toolCall.toolName}(${JSON.stringify(toolCall.input)})`
1923
+ );
1924
+ }
1925
+ }
1926
+ });
1927
+ }
1928
+ /**
1929
+ * Stream a response from the agent.
1930
+ *
1931
+ * When guardrails are configured, `toUIMessageStream()` is wrapped to provide
1932
+ * self-correction behavior. Direct access to fullStream/textStream bypasses guardrails.
1933
+ *
1934
+ * @example
1935
+ * ```typescript
1936
+ * const stream = await agent.stream({});
1937
+ *
1938
+ * // With guardrails - use toUIMessageStream for protection
1939
+ * await printer.readableStream(stream.toUIMessageStream());
1940
+ *
1941
+ * // Or use printer.stdout which uses toUIMessageStream internally
1942
+ * await printer.stdout(stream);
1943
+ * ```
1944
+ */
1945
+ async stream(contextVariables, config) {
1946
+ if (!this.#options.context) {
1947
+ throw new Error(`Agent ${this.#options.name} is missing a context.`);
1948
+ }
1949
+ if (!this.#options.model) {
1950
+ throw new Error(`Agent ${this.#options.name} is missing a model.`);
1951
+ }
1952
+ const result = await this.#createRawStream(contextVariables, config);
1953
+ if (this.#guardrails.length === 0) {
1954
+ return result;
1955
+ }
1956
+ return this.#wrapWithGuardrails(result, contextVariables, config);
1957
+ }
1958
+ /**
1959
+ * Create a raw stream without guardrail processing.
1960
+ */
1961
+ async #createRawStream(contextVariables, config) {
1962
+ const { messages, systemPrompt } = await this.#options.context.resolve({
1963
+ renderer: new XmlRenderer()
1964
+ });
1965
+ const runId = generateId2();
1966
+ return streamText({
1967
+ abortSignal: config?.abortSignal,
1968
+ providerOptions: this.#options.providerOptions,
1969
+ model: this.#options.model,
1970
+ system: systemPrompt,
1971
+ messages: await convertToModelMessages(messages),
1972
+ experimental_repairToolCall: repairToolCall,
1973
+ stopWhen: stepCountIs(50),
1974
+ experimental_transform: config?.transform ?? smoothStream(),
1975
+ tools: this.#options.tools,
1976
+ experimental_context: contextVariables,
1977
+ toolChoice: this.#options.toolChoice,
1978
+ onStepFinish: (step) => {
1979
+ const toolCall = step.toolCalls.at(-1);
1980
+ if (toolCall) {
1981
+ console.log(
1982
+ `Debug: (${runId}) ${chalk2.bold.yellow("ToolCalled")}: ${toolCall.toolName}(${JSON.stringify(toolCall.input)})`
1983
+ );
1984
+ }
286
1985
  }
287
- })
1986
+ });
1987
+ }
1988
+ /**
1989
+ * Wrap a StreamTextResult with guardrail protection on toUIMessageStream().
1990
+ *
1991
+ * When a guardrail fails:
1992
+ * 1. Accumulated text + feedback is appended as the assistant's self-correction
1993
+ * 2. The feedback is written to the output stream (user sees the correction)
1994
+ * 3. A new stream is started and the model continues from the correction
1995
+ */
1996
+ #wrapWithGuardrails(result, contextVariables, config) {
1997
+ const maxRetries = config?.maxRetries ?? this.#options.maxGuardrailRetries ?? 3;
1998
+ const context = this.#options.context;
1999
+ const originalToUIMessageStream = result.toUIMessageStream.bind(result);
2000
+ result.toUIMessageStream = (options) => {
2001
+ return createUIMessageStream({
2002
+ generateId: generateId2,
2003
+ execute: async ({ writer }) => {
2004
+ let currentResult = result;
2005
+ let attempt = 0;
2006
+ const guardrailContext = {
2007
+ availableTools: Object.keys(this.tools)
2008
+ };
2009
+ while (attempt < maxRetries) {
2010
+ if (config?.abortSignal?.aborted) {
2011
+ writer.write({ type: "finish" });
2012
+ return;
2013
+ }
2014
+ attempt++;
2015
+ let accumulatedText = "";
2016
+ let guardrailFailed = false;
2017
+ let failureFeedback = "";
2018
+ const uiStream = currentResult === result ? originalToUIMessageStream(options) : currentResult.toUIMessageStream(options);
2019
+ for await (const part of uiStream) {
2020
+ const checkResult = runGuardrailChain(
2021
+ part,
2022
+ this.#guardrails,
2023
+ guardrailContext
2024
+ );
2025
+ if (checkResult.type === "fail") {
2026
+ guardrailFailed = true;
2027
+ failureFeedback = checkResult.feedback;
2028
+ console.log(
2029
+ chalk2.yellow(
2030
+ `[${this.#options.name}] Guardrail triggered (attempt ${attempt}/${maxRetries}): ${failureFeedback.slice(0, 50)}...`
2031
+ )
2032
+ );
2033
+ break;
2034
+ }
2035
+ if (checkResult.part.type === "text-delta") {
2036
+ accumulatedText += checkResult.part.delta;
2037
+ }
2038
+ writer.write(checkResult.part);
2039
+ }
2040
+ if (!guardrailFailed) {
2041
+ writer.write({ type: "finish" });
2042
+ return;
2043
+ }
2044
+ if (attempt >= maxRetries) {
2045
+ console.error(
2046
+ chalk2.red(
2047
+ `[${this.#options.name}] Guardrail retry limit (${maxRetries}) exceeded.`
2048
+ )
2049
+ );
2050
+ writer.write({ type: "finish" });
2051
+ return;
2052
+ }
2053
+ writer.write({
2054
+ type: "text-delta",
2055
+ id: generateId2(),
2056
+ delta: ` ${failureFeedback}`
2057
+ });
2058
+ const selfCorrectionText = accumulatedText + " " + failureFeedback;
2059
+ context.set(assistantText(selfCorrectionText));
2060
+ await context.save();
2061
+ currentResult = await this.#createRawStream(
2062
+ contextVariables,
2063
+ config
2064
+ );
2065
+ }
2066
+ },
2067
+ onError: (error) => {
2068
+ const message2 = error instanceof Error ? error.message : String(error);
2069
+ return `Stream failed: ${message2}`;
2070
+ }
2071
+ });
2072
+ };
2073
+ return result;
2074
+ }
2075
+ clone(overrides) {
2076
+ return new _Agent({
2077
+ ...this.#options,
2078
+ ...overrides
2079
+ });
288
2080
  }
289
- });
2081
+ };
2082
+ function agent(options) {
2083
+ return new Agent(options);
2084
+ }
2085
+ var repairToolCall = async ({
2086
+ toolCall,
2087
+ tools,
2088
+ inputSchema,
2089
+ error
2090
+ }) => {
2091
+ console.log(
2092
+ `Debug: ${chalk2.yellow("RepairingToolCall")}: ${toolCall.toolName}`,
2093
+ error.name
2094
+ );
2095
+ if (NoSuchToolError.isInstance(error)) {
2096
+ return null;
2097
+ }
2098
+ const tool2 = tools[toolCall.toolName];
2099
+ const { output } = await generateText({
2100
+ model: groq("openai/gpt-oss-20b"),
2101
+ output: Output.object({ schema: tool2.inputSchema }),
2102
+ prompt: [
2103
+ `The model tried to call the tool "${toolCall.toolName}" with the following inputs:`,
2104
+ JSON.stringify(toolCall.input),
2105
+ `The tool accepts the following schema:`,
2106
+ JSON.stringify(inputSchema(toolCall)),
2107
+ "Please fix the inputs."
2108
+ ].join("\n")
2109
+ });
2110
+ return { ...toolCall, input: JSON.stringify(output) };
2111
+ };
2112
+
2113
+ // packages/text2sql/src/lib/adapters/groundings/report.grounding.ts
290
2114
  var ReportGrounding = class extends AbstractGrounding {
291
2115
  #adapter;
292
2116
  #model;
@@ -295,7 +2119,7 @@ var ReportGrounding = class extends AbstractGrounding {
295
2119
  constructor(adapter, config = {}) {
296
2120
  super("business_context");
297
2121
  this.#adapter = adapter;
298
- this.#model = config.model ?? groq("openai/gpt-oss-20b");
2122
+ this.#model = config.model ?? groq2("openai/gpt-oss-20b");
299
2123
  this.#cache = config.cache;
300
2124
  this.#forceRefresh = config.forceRefresh ?? false;
301
2125
  }
@@ -304,7 +2128,7 @@ var ReportGrounding = class extends AbstractGrounding {
304
2128
  const cached = await this.#cache.get();
305
2129
  if (cached) {
306
2130
  ctx.report = cached;
307
- return () => cached;
2131
+ return;
308
2132
  }
309
2133
  }
310
2134
  const report = await this.#generateReport();
@@ -312,26 +2136,71 @@ var ReportGrounding = class extends AbstractGrounding {
312
2136
  if (this.#cache) {
313
2137
  await this.#cache.set(report);
314
2138
  }
315
- return () => report;
316
2139
  }
317
2140
  async #generateReport() {
318
- const { text } = await generate(
319
- reportAgent.clone({ model: this.#model }),
320
- [
321
- user(
322
- "Please analyze the database and write a contextual report about what this database represents."
323
- )
324
- ],
325
- { adapter: this.#adapter }
2141
+ const context = new ContextEngine({
2142
+ store: new InMemoryContextStore(),
2143
+ chatId: `report-gen-${crypto.randomUUID()}`,
2144
+ userId: "system"
2145
+ });
2146
+ context.set(
2147
+ persona({
2148
+ name: "db-report-agent",
2149
+ role: "Database analyst",
2150
+ objective: "Analyze the database and write a contextual report about what it represents"
2151
+ }),
2152
+ fragment(
2153
+ "instructions",
2154
+ dedent`
2155
+ Write a business context that helps another agent answer questions accurately.
2156
+
2157
+ For EACH table, do queries ONE AT A TIME:
2158
+ 1. SELECT COUNT(*) to get row count
2159
+ 2. SELECT * LIMIT 3 to see sample data
2160
+
2161
+ Then write a report with:
2162
+ - What business this database is for
2163
+ - For each table: purpose, row count, and example of what the data looks like
2164
+
2165
+ Include concrete examples like "Track prices are $0.99",
2166
+ "Customer names like 'Luís Gonçalves'", etc.
2167
+
2168
+ Keep it 400-600 words, conversational style.
2169
+ `
2170
+ ),
2171
+ user(
2172
+ "Please analyze the database and write a contextual report about what this database represents."
2173
+ )
326
2174
  );
327
- return text;
2175
+ const adapter = this.#adapter;
2176
+ const reportAgent = agent({
2177
+ name: "db-report-agent",
2178
+ model: this.#model,
2179
+ context,
2180
+ tools: {
2181
+ query_database: tool({
2182
+ description: "Execute a SELECT query to explore the database and gather insights.",
2183
+ inputSchema: z.object({
2184
+ sql: z.string().describe("The SELECT query to execute"),
2185
+ purpose: z.string().describe(
2186
+ "What insight you are trying to gather with this query"
2187
+ )
2188
+ }),
2189
+ execute: ({ sql }) => {
2190
+ return adapter.execute(sql);
2191
+ }
2192
+ })
2193
+ }
2194
+ });
2195
+ const result = await reportAgent.generate({});
2196
+ return result.text;
328
2197
  }
329
2198
  };
330
2199
 
331
2200
  // packages/text2sql/src/lib/adapters/groundings/row-count.grounding.ts
332
2201
  var RowCountGrounding = class extends AbstractGrounding {
333
2202
  constructor(config = {}) {
334
- super("row_counts");
2203
+ super("rowCount");
335
2204
  }
336
2205
  /**
337
2206
  * Execute the grounding process.
@@ -345,7 +2214,6 @@ var RowCountGrounding = class extends AbstractGrounding {
345
2214
  table.sizeHint = this.#classifyRowCount(count);
346
2215
  }
347
2216
  }
348
- return () => null;
349
2217
  }
350
2218
  /**
351
2219
  * Classify row count into a size hint category.
@@ -360,13 +2228,12 @@ var RowCountGrounding = class extends AbstractGrounding {
360
2228
  };
361
2229
 
362
2230
  // packages/text2sql/src/lib/adapters/groundings/table.grounding.ts
363
- import pluralize from "pluralize";
364
2231
  var TableGrounding = class extends AbstractGrounding {
365
2232
  #filter;
366
2233
  #forward;
367
2234
  #backward;
368
2235
  constructor(config = {}) {
369
- super("tables");
2236
+ super("table");
370
2237
  this.#filter = config.filter;
371
2238
  this.#forward = config.forward;
372
2239
  this.#backward = config.backward;
@@ -384,7 +2251,7 @@ var TableGrounding = class extends AbstractGrounding {
384
2251
  seedTables.map((name) => this.getTable(name))
385
2252
  );
386
2253
  ctx.tables.push(...tables2);
387
- return () => this.#describeTables(tables2);
2254
+ return;
388
2255
  }
389
2256
  const tables = {};
390
2257
  const allRelationships = [];
@@ -440,7 +2307,6 @@ var TableGrounding = class extends AbstractGrounding {
440
2307
  const tablesList = Object.values(tables);
441
2308
  ctx.tables.push(...tablesList);
442
2309
  ctx.relationships.push(...allRelationships);
443
- return () => this.#describeTables(tablesList);
444
2310
  }
445
2311
  /**
446
2312
  * Apply the filter to get seed table names.
@@ -470,163 +2336,13 @@ var TableGrounding = class extends AbstractGrounding {
470
2336
  all.push(rel);
471
2337
  }
472
2338
  }
473
- #describeTables(tables) {
474
- if (!tables.length) {
475
- return "Schema unavailable.";
476
- }
477
- return tables.map((table) => {
478
- const rowCountInfo = table.rowCount != null ? ` [rows: ${table.rowCount}${table.sizeHint ? `, size: ${table.sizeHint}` : ""}]` : "";
479
- const pkConstraint = table.constraints?.find(
480
- (c) => c.type === "PRIMARY_KEY"
481
- );
482
- const pkColumns = new Set(pkConstraint?.columns ?? []);
483
- const notNullColumns = new Set(
484
- table.constraints?.filter((c) => c.type === "NOT_NULL").flatMap((c) => c.columns ?? []) ?? []
485
- );
486
- const defaultByColumn = /* @__PURE__ */ new Map();
487
- for (const c of table.constraints?.filter(
488
- (c2) => c2.type === "DEFAULT"
489
- ) ?? []) {
490
- for (const col of c.columns ?? []) {
491
- if (c.defaultValue != null) {
492
- defaultByColumn.set(col, c.defaultValue);
493
- }
494
- }
495
- }
496
- const uniqueColumns = new Set(
497
- table.constraints?.filter((c) => c.type === "UNIQUE" && c.columns?.length === 1).flatMap((c) => c.columns ?? []) ?? []
498
- );
499
- const fkByColumn = /* @__PURE__ */ new Map();
500
- for (const c of table.constraints?.filter(
501
- (c2) => c2.type === "FOREIGN_KEY"
502
- ) ?? []) {
503
- const cols = c.columns ?? [];
504
- const refCols = c.referencedColumns ?? [];
505
- for (let i = 0; i < cols.length; i++) {
506
- const refCol = refCols[i] ?? refCols[0] ?? cols[i];
507
- fkByColumn.set(cols[i], `${c.referencedTable}.${refCol}`);
508
- }
509
- }
510
- const columns = table.columns.map((column) => {
511
- const annotations = [];
512
- const isPrimaryKey = pkColumns.has(column.name);
513
- if (isPrimaryKey) {
514
- annotations.push("PK");
515
- }
516
- if (fkByColumn.has(column.name)) {
517
- annotations.push(`FK -> ${fkByColumn.get(column.name)}`);
518
- }
519
- if (uniqueColumns.has(column.name)) {
520
- annotations.push("UNIQUE");
521
- }
522
- if (notNullColumns.has(column.name)) {
523
- annotations.push("NOT NULL");
524
- }
525
- if (defaultByColumn.has(column.name)) {
526
- annotations.push(`DEFAULT: ${defaultByColumn.get(column.name)}`);
527
- }
528
- if (column.isIndexed && !isPrimaryKey) {
529
- annotations.push("Indexed");
530
- }
531
- if (column.kind === "Enum" && column.values?.length) {
532
- annotations.push(`Enum: ${column.values.join(", ")}`);
533
- } else if (column.kind === "LowCardinality" && column.values?.length) {
534
- annotations.push(`LowCardinality: ${column.values.join(", ")}`);
535
- }
536
- if (column.stats) {
537
- const statParts = [];
538
- if (column.stats.min != null || column.stats.max != null) {
539
- const minText = column.stats.min ?? "n/a";
540
- const maxText = column.stats.max ?? "n/a";
541
- statParts.push(`range ${minText} \u2192 ${maxText}`);
542
- }
543
- if (column.stats.nullFraction != null && Number.isFinite(column.stats.nullFraction)) {
544
- const percent = Math.round(column.stats.nullFraction * 1e3) / 10;
545
- statParts.push(`null\u2248${percent}%`);
546
- }
547
- if (statParts.length) {
548
- annotations.push(statParts.join(", "));
549
- }
550
- }
551
- const annotationText = annotations.length ? ` [${annotations.join(", ")}]` : "";
552
- return ` - ${column.name} (${column.type})${annotationText}`;
553
- }).join("\n");
554
- const indexes = table.indexes?.length ? `
555
- Indexes:
556
- ${table.indexes.map((index) => {
557
- const props = [];
558
- if (index.unique) {
559
- props.push("UNIQUE");
560
- }
561
- if (index.type) {
562
- props.push(index.type);
563
- }
564
- const propsText = props.length ? ` (${props.join(", ")})` : "";
565
- const columnsText = index.columns?.length ? index.columns.join(", ") : "expression";
566
- return ` - ${index.name}${propsText}: ${columnsText}`;
567
- }).join("\n")}` : "";
568
- const multiColumnUniques = table.constraints?.filter(
569
- (c) => c.type === "UNIQUE" && (c.columns?.length ?? 0) > 1
570
- ) ?? [];
571
- const uniqueConstraints = multiColumnUniques.length ? `
572
- Unique Constraints:
573
- ${multiColumnUniques.map((c) => ` - ${c.name}: (${c.columns?.join(", ")})`).join("\n")}` : "";
574
- const checkConstraints = table.constraints?.filter((c) => c.type === "CHECK") ?? [];
575
- const checks = checkConstraints.length ? `
576
- Check Constraints:
577
- ${checkConstraints.map((c) => ` - ${c.name}: ${c.definition}`).join("\n")}` : "";
578
- return `- Table: ${table.name}${rowCountInfo}
579
- Columns:
580
- ${columns}${indexes}${uniqueConstraints}${checks}`;
581
- }).join("\n\n");
582
- }
583
- #formatTableLabel = (tableName) => {
584
- const base = tableName.split(".").pop() ?? tableName;
585
- return base.replace(/_/g, " ");
586
- };
587
- #describeRelationships = (tables, relationships) => {
588
- if (!relationships.length) {
589
- return "None detected";
590
- }
591
- const tableMap = new Map(tables.map((table) => [table.name, table]));
592
- return relationships.map((relationship) => {
593
- const sourceLabel = this.#formatTableLabel(relationship.table);
594
- const targetLabel = this.#formatTableLabel(
595
- relationship.referenced_table
596
- );
597
- const singularSource = pluralize.singular(sourceLabel);
598
- const pluralSource = pluralize.plural(sourceLabel);
599
- const singularTarget = pluralize.singular(targetLabel);
600
- const pluralTarget = pluralize.plural(targetLabel);
601
- const sourceTable = tableMap.get(relationship.table);
602
- const targetTable = tableMap.get(relationship.referenced_table);
603
- const sourceCount = sourceTable?.rowCount;
604
- const targetCount = targetTable?.rowCount;
605
- const ratio = sourceCount != null && targetCount != null && targetCount > 0 ? sourceCount / targetCount : null;
606
- let cardinality = "each";
607
- if (ratio != null) {
608
- if (ratio > 5) {
609
- cardinality = `many-to-one (\u2248${sourceCount} vs ${targetCount})`;
610
- } else if (ratio < 1.2 && ratio > 0.8) {
611
- cardinality = `roughly 1:1 (${sourceCount} vs ${targetCount})`;
612
- } else if (ratio < 0.2) {
613
- cardinality = `one-to-many (${sourceCount} vs ${targetCount})`;
614
- }
615
- }
616
- const mappings = relationship.from.map((fromCol, idx) => {
617
- const targetCol = relationship.to[idx] ?? relationship.to[0] ?? fromCol;
618
- return `${relationship.table}.${fromCol} -> ${relationship.referenced_table}.${targetCol}`;
619
- }).join(", ");
620
- return `- ${relationship.table} (${relationship.from.join(", ")}) -> ${relationship.referenced_table} (${relationship.to.join(", ")}) [${cardinality}]`;
621
- }).join("\n");
622
- };
623
2339
  };
624
2340
 
625
2341
  // packages/text2sql/src/lib/adapters/groundings/view.grounding.ts
626
2342
  var ViewGrounding = class extends AbstractGrounding {
627
2343
  #filter;
628
2344
  constructor(config = {}) {
629
- super("views");
2345
+ super("view");
630
2346
  this.#filter = config.filter;
631
2347
  }
632
2348
  /**
@@ -639,42 +2355,6 @@ var ViewGrounding = class extends AbstractGrounding {
639
2355
  viewNames.map((name) => this.getView(name))
640
2356
  );
641
2357
  ctx.views.push(...views);
642
- return () => this.#describe(views);
643
- }
644
- #describe(views) {
645
- if (!views.length) {
646
- return "No views available.";
647
- }
648
- return views.map((view) => {
649
- const columns = view.columns.map((column) => {
650
- const annotations = [];
651
- if (column.kind === "LowCardinality" && column.values?.length) {
652
- annotations.push(`LowCardinality: ${column.values.join(", ")}`);
653
- }
654
- if (column.stats) {
655
- const statParts = [];
656
- if (column.stats.min != null || column.stats.max != null) {
657
- const minText = column.stats.min ?? "n/a";
658
- const maxText = column.stats.max ?? "n/a";
659
- statParts.push(`range ${minText} \u2192 ${maxText}`);
660
- }
661
- if (column.stats.nullFraction != null && Number.isFinite(column.stats.nullFraction)) {
662
- const percent = Math.round(column.stats.nullFraction * 1e3) / 10;
663
- statParts.push(`null\u2248${percent}%`);
664
- }
665
- if (statParts.length) {
666
- annotations.push(statParts.join(", "));
667
- }
668
- }
669
- const annotationText = annotations.length ? ` [${annotations.join(", ")}]` : "";
670
- return ` - ${column.name} (${column.type})${annotationText}`;
671
- }).join("\n");
672
- const definition = view.definition ? `
673
- Definition: ${view.definition.length > 200 ? view.definition.slice(0, 200) + "..." : view.definition}` : "";
674
- return `- View: ${view.name}${definition}
675
- Columns:
676
- ${columns}`;
677
- }).join("\n\n");
678
2358
  }
679
2359
  /**
680
2360
  * Apply the filter to get view names.