@deepagents/text2sql 0.2.1 → 0.2.3

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/dist/index.js CHANGED
@@ -293,6 +293,23 @@ import {
293
293
  user as user2
294
294
  } from "@deepagents/agent";
295
295
 
296
+ // packages/text2sql/src/lib/agents/brief.agent.ts
297
+ import { groq as groq2 } from "@ai-sdk/groq";
298
+ import { createUIMessageStream, tool as tool2 } from "ai";
299
+ import dedent from "dedent";
300
+ import { createHash } from "node:crypto";
301
+ import { existsSync } from "node:fs";
302
+ import { readFile, writeFile } from "node:fs/promises";
303
+ import { tmpdir } from "node:os";
304
+ import path from "node:path";
305
+ import z2 from "zod";
306
+ import {
307
+ agent as agent2,
308
+ generate,
309
+ toState as toState2,
310
+ user
311
+ } from "@deepagents/agent";
312
+
296
313
  // packages/text2sql/src/lib/adapters/adapter.ts
297
314
  var Adapter = class {
298
315
  async resolveTables(filter) {
@@ -370,1485 +387,1498 @@ function getTablesWithRelated(allTables, relationships, filter) {
370
387
  return Array.from(result);
371
388
  }
372
389
 
373
- // packages/text2sql/src/lib/adapters/sqlite.ts
374
- var SQL_ERROR_MAP = [
375
- {
376
- pattern: /^no such table: .+$/,
377
- type: "MISSING_TABLE",
378
- hint: "Check the database schema for the correct table name. The table you referenced does not exist."
379
- },
380
- {
381
- pattern: /^no such column: .+$/,
382
- type: "INVALID_COLUMN",
383
- hint: "Check the table schema for correct column names. The column may not exist or is ambiguous (exists in multiple joined tables)."
384
- },
385
- {
386
- pattern: /^ambiguous column name: .+$/,
387
- type: "INVALID_COLUMN",
388
- hint: "Check the table schema for correct column names. The column may not exist or is ambiguous (exists in multiple joined tables)."
389
- },
390
- {
391
- pattern: /^near ".+": syntax error$/,
392
- type: "SYNTAX_ERROR",
393
- hint: "There is a SQL syntax error. Review the query structure, keywords, and punctuation."
394
- },
395
- {
396
- pattern: /^no tables specified$/,
397
- type: "SYNTAX_ERROR",
398
- hint: "There is a SQL syntax error. Review the query structure, keywords, and punctuation."
399
- },
400
- {
401
- pattern: /^attempt to write a readonly database$/,
402
- type: "CONSTRAINT_ERROR",
403
- hint: "A database constraint was violated. This should not happen with read-only queries."
404
- }
405
- ];
406
- var LOW_CARDINALITY_LIMIT = 20;
407
- function formatError(sql, error) {
408
- const errorMessage = error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error occurred";
409
- const errorInfo = SQL_ERROR_MAP.find((it) => it.pattern.test(errorMessage));
410
- if (!errorInfo) {
411
- return {
412
- error: errorMessage,
413
- error_type: "UNKNOWN_ERROR",
414
- suggestion: "Review the query and try again",
415
- sql_attempted: sql
416
- };
417
- }
418
- return {
419
- error: errorMessage,
420
- error_type: errorInfo.type,
421
- suggestion: errorInfo.hint,
422
- sql_attempted: sql
423
- };
424
- }
425
- var Sqlite = class extends Adapter {
426
- #options;
427
- #introspection = null;
428
- #info = null;
429
- constructor(options) {
430
- super();
431
- if (!options || typeof options.execute !== "function") {
432
- throw new Error("Sqlite adapter requires an execute function.");
433
- }
434
- this.#options = options;
390
+ // packages/text2sql/src/lib/agents/brief.agent.ts
391
+ var TmpCache = class {
392
+ path;
393
+ constructor(watermark, extension = ".txt") {
394
+ const hash = createHash("md5").update(watermark).digest("hex");
395
+ this.path = path.join(tmpdir(), `text2sql-${hash}${extension}`);
435
396
  }
436
- async introspect(options) {
437
- const onProgress = options?.onProgress;
438
- if (this.#introspection) {
439
- return this.#introspection;
440
- }
441
- if (this.#options.introspect) {
442
- this.#introspection = await this.#options.introspect();
443
- return this.#introspection;
397
+ async get() {
398
+ if (existsSync(this.path)) {
399
+ return readFile(this.path, "utf-8");
444
400
  }
445
- const allTables = await this.#loadTables();
446
- const allRelationships = await this.#loadRelationships(
447
- allTables.map((t) => t.name)
448
- );
449
- const { tables, relationships } = this.#applyTablesFilter(
450
- allTables,
451
- allRelationships
452
- );
453
- onProgress?.({
454
- phase: "tables",
455
- message: `Loaded ${tables.length} tables`,
456
- total: tables.length
457
- });
458
- onProgress?.({ phase: "row_counts", message: "Counting table rows..." });
459
- await this.#annotateRowCounts(tables, onProgress);
460
- onProgress?.({
461
- phase: "column_stats",
462
- message: "Collecting column statistics..."
463
- });
464
- await this.#annotateColumnStats(tables, onProgress);
465
- onProgress?.({ phase: "indexes", message: "Loading index information..." });
466
- await this.#annotateIndexes(tables, onProgress);
467
- onProgress?.({
468
- phase: "low_cardinality",
469
- message: "Identifying low cardinality columns..."
470
- });
471
- await this.#annotateLowCardinalityColumns(tables, onProgress);
472
- onProgress?.({
473
- phase: "relationships",
474
- message: "Loading foreign key relationships..."
475
- });
476
- this.#introspection = { tables, relationships };
477
- return this.#introspection;
478
- }
479
- async execute(sql) {
480
- return this.#options.execute(sql);
401
+ return null;
481
402
  }
482
- async validate(sql) {
483
- const validator = this.#options.validate ?? (async (text) => {
484
- await this.#options.execute(`EXPLAIN ${text}`);
485
- });
486
- try {
487
- return await validator(sql);
488
- } catch (error) {
489
- return JSON.stringify(formatError(sql, error));
490
- }
403
+ set(content) {
404
+ return writeFile(this.path, content, "utf-8");
491
405
  }
492
- async info() {
493
- if (this.#info) {
494
- return this.#info;
495
- }
496
- this.#info = await this.#resolveInfo();
497
- return this.#info;
406
+ };
407
+ var JsonCache = class extends TmpCache {
408
+ constructor(watermark) {
409
+ super(watermark, ".json");
498
410
  }
499
- formatInfo(info) {
500
- const lines = [`Dialect: ${info.dialect ?? "unknown"}`];
501
- if (info.version) {
502
- lines.push(`Version: ${info.version}`);
503
- }
504
- if (info.database) {
505
- lines.push(`Database: ${info.database}`);
506
- }
507
- if (info.host) {
508
- lines.push(`Host: ${info.host}`);
509
- }
510
- if (info.details && Object.keys(info.details).length) {
511
- lines.push(`Details: ${JSON.stringify(info.details)}`);
411
+ async read() {
412
+ const content = await this.get();
413
+ if (content) {
414
+ return JSON.parse(content);
512
415
  }
513
- return lines.join("\n");
416
+ return null;
514
417
  }
515
- async getTables() {
516
- const allTables = await this.#loadTables();
517
- const allRelationships = await this.#loadRelationships(
518
- allTables.map((t) => t.name)
519
- );
520
- return this.#applyTablesFilter(allTables, allRelationships).tables;
418
+ write(data) {
419
+ return this.set(JSON.stringify(data));
521
420
  }
522
- async #loadTables() {
523
- const rows = await this.#runQuery(
524
- `SELECT name FROM sqlite_master WHERE type='table' ORDER BY name`
525
- );
526
- const tableNames = rows.map((row) => row.name).filter(
527
- (name) => typeof name === "string" && !name.startsWith("sqlite_")
528
- );
529
- const tables = await Promise.all(
530
- tableNames.map(async (tableName) => {
531
- const columns = await this.#runQuery(
532
- `PRAGMA table_info(${this.#quoteIdentifier(tableName)})`
533
- );
534
- return {
535
- name: tableName,
536
- rawName: tableName,
537
- columns: columns.map((col) => ({
538
- name: col.name ?? "unknown",
539
- type: col.type ?? "unknown",
540
- isPrimaryKey: (col.pk ?? 0) > 0
541
- }))
542
- };
543
- })
544
- );
545
- return tables;
421
+ };
422
+ var briefAgent = agent2({
423
+ name: "db-brief-agent",
424
+ model: groq2("openai/gpt-oss-20b"),
425
+ prompt: (state) => dedent`
426
+ <identity>
427
+ You are a database analyst expert. Your job is to understand what a database represents and provide business context about it.
428
+ You have READ-ONLY access to the database.
429
+ </identity>
430
+
431
+ ${databaseSchemaPrompt(state)}
432
+
433
+ <instructions>
434
+ Write a business context that helps another agent answer questions accurately.
435
+
436
+ For EACH table, do queries ONE AT A TIME:
437
+ 1. SELECT COUNT(*) to get row count
438
+ 2. SELECT * LIMIT 3 to see sample data
439
+
440
+ Then write a report with:
441
+ - What business this database is for
442
+ - For each table: purpose, row count, and example of what the data looks like
443
+
444
+ Include concrete examples like "Track prices are $0.99", "Customer names like 'Lu\u00eds Gon\u00e7alves'", etc.
445
+
446
+ Keep it 400-600 words, conversational style.
447
+ </instructions>
448
+ `,
449
+ tools: {
450
+ query_database: tool2({
451
+ description: "Execute a SELECT query to explore the database and gather insights.",
452
+ inputSchema: z2.object({
453
+ sql: z2.string().describe("The SELECT query to execute"),
454
+ purpose: z2.string().describe("What insight you are trying to gather with this query")
455
+ }),
456
+ execute: ({ sql }, options) => {
457
+ const state = toState2(options);
458
+ return state.execute(sql);
459
+ }
460
+ })
546
461
  }
547
- async getRelationships() {
548
- const allTables = await this.#loadTables();
549
- const allRelationships = await this.#loadRelationships(
550
- allTables.map((t) => t.name)
551
- );
552
- return this.#applyTablesFilter(allTables, allRelationships).relationships;
462
+ });
463
+ async function runAndCache(introspection, cache) {
464
+ const { text } = await generate(
465
+ briefAgent,
466
+ [
467
+ user(
468
+ "Please analyze the database and write a contextual report about what this database represents."
469
+ )
470
+ ],
471
+ { introspection }
472
+ );
473
+ await cache.set(text);
474
+ return text;
475
+ }
476
+ async function generateBrief(introspection, cache) {
477
+ const brief = await cache.get();
478
+ if (!brief) {
479
+ return runAndCache(introspection, cache);
553
480
  }
554
- async #loadRelationships(tableNames) {
555
- const names = tableNames ?? (await this.#loadTables()).map((table) => table.name);
556
- const relationshipGroups = await Promise.all(
557
- names.map(async (tableName) => {
558
- const rows = await this.#runQuery(
559
- `PRAGMA foreign_key_list(${this.#quoteIdentifier(tableName)})`
560
- );
561
- const groups = /* @__PURE__ */ new Map();
562
- for (const row of rows) {
563
- if (row.id == null || row.table == null || row.from == null || row.to == null) {
564
- continue;
565
- }
566
- const id = Number(row.id);
567
- const existing = groups.get(id);
568
- if (!existing) {
569
- groups.set(id, {
570
- table: tableName,
571
- from: [String(row.from)],
572
- referenced_table: String(row.table),
573
- to: [String(row.to)]
481
+ return brief;
482
+ }
483
+ function toBrief(forceRefresh = false) {
484
+ return (state, setState) => {
485
+ return createUIMessageStream({
486
+ execute: async ({ writer }) => {
487
+ if (forceRefresh) {
488
+ const brief = await runAndCache(state.introspection, state.cache);
489
+ writer.write({
490
+ type: "data-brief-agent",
491
+ data: {
492
+ cache: "forced",
493
+ brief
494
+ }
495
+ });
496
+ setState({ context: brief });
497
+ } else {
498
+ let brief = await state.cache.get();
499
+ if (!brief) {
500
+ writer.write({
501
+ type: "data-brief-agent",
502
+ data: {
503
+ cache: "miss"
504
+ }
505
+ });
506
+ brief = await runAndCache(state.introspection, state.cache);
507
+ writer.write({
508
+ type: "data-brief-agent",
509
+ data: {
510
+ cache: "new",
511
+ brief
512
+ }
574
513
  });
575
514
  } else {
576
- existing.from.push(String(row.from));
577
- existing.to.push(String(row.to));
515
+ writer.write({
516
+ type: "data-brief-agent",
517
+ data: {
518
+ cache: "hit",
519
+ brief
520
+ }
521
+ });
578
522
  }
579
523
  }
580
- return Array.from(groups.values());
581
- })
582
- );
583
- return relationshipGroups.flat();
584
- }
585
- async #annotateRowCounts(tables, onProgress) {
586
- const total = tables.length;
587
- for (let i = 0; i < tables.length; i++) {
588
- const table = tables[i];
589
- const tableIdentifier = this.#formatTableIdentifier(table);
590
- onProgress?.({
591
- phase: "row_counts",
592
- message: `Counting rows in ${table.name}...`,
593
- current: i + 1,
594
- total
595
- });
596
- try {
597
- const rows = await this.#runQuery(
598
- `SELECT COUNT(*) as count FROM ${tableIdentifier}`
599
- );
600
- const rowCount = this.#toNumber(rows[0]?.count);
601
- if (rowCount != null) {
602
- table.rowCount = rowCount;
603
- table.sizeHint = this.#classifyRowCount(rowCount);
604
- }
605
- } catch {
606
- continue;
607
524
  }
608
- }
525
+ });
526
+ };
527
+ }
528
+
529
+ // packages/text2sql/src/lib/agents/explainer.agent.ts
530
+ import { groq as groq3 } from "@ai-sdk/groq";
531
+ import dedent2 from "dedent";
532
+ import z3 from "zod";
533
+ import { agent as agent3 } from "@deepagents/agent";
534
+ var explainerAgent = agent3({
535
+ name: "explainer",
536
+ model: groq3("openai/gpt-oss-20b"),
537
+ prompt: (state) => dedent2`
538
+ You are an expert SQL tutor.
539
+ Explain the following SQL query in plain English to a non-technical user.
540
+ Focus on the intent and logic, not the syntax.
541
+
542
+ <sql>
543
+ ${state?.sql}
544
+ </sql>
545
+ `,
546
+ output: z3.object({
547
+ explanation: z3.string().describe("The explanation of the SQL query.")
548
+ })
549
+ });
550
+
551
+ // packages/text2sql/src/lib/agents/suggestions.agents.ts
552
+ import { groq as groq4 } from "@ai-sdk/groq";
553
+ import dedent3 from "dedent";
554
+ import z4 from "zod";
555
+ import { agent as agent4, thirdPersonPrompt } from "@deepagents/agent";
556
+ var suggestionsAgent = agent4({
557
+ name: "text2sql-suggestions",
558
+ model: groq4("openai/gpt-oss-20b"),
559
+ output: z4.object({
560
+ suggestions: z4.array(
561
+ z4.object({
562
+ question: z4.string().describe("A complex, high-impact business question."),
563
+ sql: z4.string().describe("The SQL statement needed to answer the question."),
564
+ businessValue: z4.string().describe("Why the question matters to stakeholders.")
565
+ })
566
+ ).min(1).max(5).describe("A set of up to two advanced question + SQL pairs.")
567
+ }),
568
+ prompt: (state) => {
569
+ return dedent3`
570
+ ${thirdPersonPrompt()}
571
+
572
+ <identity>
573
+ You are a senior analytics strategist who proposes ambitious business questions
574
+ and drafts the SQL needed to answer them. You specialize in identifying ideas
575
+ that combine multiple tables, apply segmentation or time analysis, and surface
576
+ metrics that drive executive decisions.
577
+ </identity>
578
+
579
+ ${databaseSchemaPrompt(state)}
580
+
581
+ <instructions>
582
+ - Recommend one or two UNIQUE questions that go beyond simple counts or listings.
583
+ - Favor questions that require joins, aggregates, time comparisons, cohort analysis,
584
+ or window functions.
585
+ - For each question, explain the business reason stakeholders care about it.
586
+ - Provide the complete SQL query that could answer the question in the given schema.
587
+ - Keep result sets scoped with LIMIT clauses (max 50 rows) when returning raw rows.
588
+ - Ensure table/column names match the provided schema exactly.
589
+ - Use columns marked [LowCardinality: ...] to identify meaningful categorical filters or segmentations.
590
+ - Leverage table [rows / size] hints to determine whether to aggregate (large tables) or inspect detailed data (tiny tables).
591
+ - Reference PK/Indexed annotations and the Indexes list to recommend queries that use efficient join/filter paths.
592
+ - Column annotations may expose ranges/null percentages—use them to suggest realistic thresholds or quality checks.
593
+ - Consult <relationship_examples> to anchor your recommendations in the actual join paths between tables.
594
+ - Output only information grounded in the schema/context provided.
595
+ </instructions>
596
+
597
+ <response-format>
598
+ Return valid JSON that satisfies the defined output schema.
599
+ </response-format>
600
+ `;
609
601
  }
610
- async #annotateColumnStats(tables, onProgress) {
611
- const total = tables.length;
612
- for (let i = 0; i < tables.length; i++) {
613
- const table = tables[i];
614
- const tableIdentifier = this.#formatTableIdentifier(table);
615
- onProgress?.({
616
- phase: "column_stats",
617
- message: `Collecting stats for ${table.name}...`,
618
- current: i + 1,
619
- total
620
- });
621
- for (const column of table.columns) {
622
- if (!this.#shouldCollectStats(column.type)) {
623
- continue;
624
- }
625
- const columnIdentifier = this.#quoteSqlIdentifier(column.name);
626
- const sql = `
627
- SELECT
628
- MIN(${columnIdentifier}) AS min_value,
629
- MAX(${columnIdentifier}) AS max_value,
630
- AVG(CASE WHEN ${columnIdentifier} IS NULL THEN 1.0 ELSE 0.0 END) AS null_fraction
631
- FROM ${tableIdentifier}
632
- `;
633
- try {
634
- const rows = await this.#runQuery(sql);
635
- if (!rows.length) {
636
- continue;
637
- }
638
- const min = this.#normalizeValue(rows[0]?.min_value);
639
- const max = this.#normalizeValue(rows[0]?.max_value);
640
- const nullFraction = this.#toNumber(rows[0]?.null_fraction);
641
- if (min != null || max != null || nullFraction != null) {
642
- column.stats = {
643
- min: min ?? void 0,
644
- max: max ?? void 0,
645
- nullFraction: nullFraction != null && Number.isFinite(nullFraction) ? Math.max(0, Math.min(1, nullFraction)) : void 0
646
- };
647
- }
648
- } catch {
649
- continue;
650
- }
651
- }
652
- }
602
+ });
603
+
604
+ // packages/text2sql/src/lib/agents/synthesizer.agent.ts
605
+ import { groq as groq5 } from "@ai-sdk/groq";
606
+ import dedent4 from "dedent";
607
+ import { agent as agent5 } from "@deepagents/agent";
608
+ var synthesizerAgent = agent5({
609
+ name: "synthesizer_agent",
610
+ model: groq5("openai/gpt-oss-20b"),
611
+ handoffDescription: "Use this tool to synthesizes the final user-facing response. This agent understands how the user interface works and can tailor the response accordingly.",
612
+ prompt: (state) => {
613
+ const contextInfo = state?.context ?? "No additional context provided.";
614
+ return dedent4`
615
+ <identity>
616
+ You are a data insights companion helping users understand information using clear, everyday language.
617
+ You communicate in a friendly, conversational manner.
618
+ You only see the user's question and the results from internal systems. You do not know how those results were produced, so never reference technical systems or implementation details.
619
+ </identity>
620
+
621
+ <context>
622
+ ${contextInfo}
623
+ </context>
624
+
625
+ <response-strategy>
626
+ 1. Re-read the user's question, then inspect the <data> provided to understand what it represents.
627
+ 2. Translate technical field names into friendly descriptions based on the domain context.
628
+ 3. Explain the core insight in 2-4 sentences focused on what the data reveals.
629
+ 4. When multiple records are present, highlight only the most relevant ones (max 5) with comparisons or rankings.
630
+ 5. If data is empty or contains an error, state that plainly and suggest what to clarify or try next.
631
+ 6. Close with an optional follow-up recommendation or next step based on the insights.
632
+ </response-strategy>
633
+
634
+ <guardrails>
635
+ - Never mention technical implementation details, data structures, or internal systems.
636
+ - Keep tone casual, confident, and insight-driven; do not narrate your process.
637
+ - Base every statement strictly on the <data> provided plus the context - no speculation.
638
+ </guardrails>
639
+ `;
653
640
  }
654
- async #annotateIndexes(tables, onProgress) {
655
- const total = tables.length;
656
- for (let i = 0; i < tables.length; i++) {
657
- const table = tables[i];
658
- const tableIdentifier = this.#quoteIdentifier(
659
- table.rawName ?? table.name
660
- );
661
- onProgress?.({
662
- phase: "indexes",
663
- message: `Loading indexes for ${table.name}...`,
664
- current: i + 1,
665
- total
666
- });
667
- let indexes = [];
668
- try {
669
- const indexList = await this.#runQuery(
670
- `PRAGMA index_list(${tableIdentifier})`
671
- );
672
- indexes = await Promise.all(
673
- indexList.filter((index) => index.name).map(async (index) => {
674
- const indexName = String(index.name);
675
- const indexInfo = await this.#runQuery(
676
- `PRAGMA index_info('${indexName.replace(/'/g, "''")}')`
677
- );
678
- const columns = indexInfo.map((col) => col.name).filter((name) => Boolean(name));
679
- for (const columnName of columns) {
680
- const column = table.columns.find(
681
- (col) => col.name === columnName
682
- );
683
- if (column) {
684
- column.isIndexed = true;
685
- }
686
- }
687
- return {
688
- name: indexName,
689
- columns,
690
- unique: index.unique === 1,
691
- primary: index.origin === "pk",
692
- type: index.origin ?? void 0
693
- };
694
- })
695
- );
696
- } catch {
697
- indexes = [];
698
- }
699
- if (indexes.length) {
700
- table.indexes = indexes;
701
- }
702
- }
641
+ });
642
+
643
+ // packages/text2sql/src/lib/agents/teachables.agent.ts
644
+ import { groq as groq6 } from "@ai-sdk/groq";
645
+ import dedent5 from "dedent";
646
+ import z5 from "zod";
647
+ import { agent as agent6, thirdPersonPrompt as thirdPersonPrompt2 } from "@deepagents/agent";
648
+
649
+ // packages/text2sql/src/lib/teach/xml.ts
650
+ function wrapBlock(tag, children) {
651
+ const content = children.filter((child) => Boolean(child)).join("\n");
652
+ if (!content) {
653
+ return "";
703
654
  }
704
- async #annotateLowCardinalityColumns(tables, onProgress) {
705
- const total = tables.length;
706
- for (let i = 0; i < tables.length; i++) {
707
- const table = tables[i];
708
- const tableIdentifier = this.#formatTableIdentifier(table);
709
- onProgress?.({
710
- phase: "low_cardinality",
711
- message: `Analyzing cardinality in ${table.name}...`,
712
- current: i + 1,
713
- total
714
- });
715
- for (const column of table.columns) {
716
- const columnIdentifier = this.#quoteSqlIdentifier(column.name);
717
- const limit = LOW_CARDINALITY_LIMIT + 1;
718
- const sql = `
719
- SELECT DISTINCT ${columnIdentifier} AS value
720
- FROM ${tableIdentifier}
721
- WHERE ${columnIdentifier} IS NOT NULL
722
- LIMIT ${limit}
723
- `;
724
- let rows = [];
725
- try {
726
- rows = await this.#runQuery(sql);
727
- } catch {
728
- continue;
729
- }
730
- if (!rows.length || rows.length > LOW_CARDINALITY_LIMIT) {
731
- continue;
732
- }
733
- const values = [];
734
- let shouldSkip = false;
735
- for (const row of rows) {
736
- const formatted = this.#normalizeValue(row.value);
737
- if (formatted == null) {
738
- shouldSkip = true;
739
- break;
740
- }
741
- values.push(formatted);
742
- }
743
- if (shouldSkip || !values.length) {
744
- continue;
745
- }
746
- column.kind = "LowCardinality";
747
- column.values = values;
748
- }
749
- }
655
+ return `<${tag}>
656
+ ${indentBlock(content, 2)}
657
+ </${tag}>`;
658
+ }
659
+ function list(tag, values, childTag) {
660
+ if (!values.length) {
661
+ return "";
750
662
  }
751
- #quoteIdentifier(name) {
752
- return `'${name.replace(/'/g, "''")}'`;
663
+ const children = values.map((value) => leaf(childTag, value)).join("\n");
664
+ return `<${tag}>
665
+ ${indentBlock(children, 2)}
666
+ </${tag}>`;
667
+ }
668
+ function leaf(tag, value) {
669
+ const safe = escapeXml(value);
670
+ if (safe.includes("\n")) {
671
+ return `<${tag}>
672
+ ${indentBlock(safe, 2)}
673
+ </${tag}>`;
753
674
  }
754
- #quoteSqlIdentifier(identifier) {
755
- return `"${identifier.replace(/"/g, '""')}"`;
675
+ return `<${tag}>${safe}</${tag}>`;
676
+ }
677
+ function indentBlock(text, spaces) {
678
+ if (!text.trim()) {
679
+ return "";
756
680
  }
757
- #applyTablesFilter(tables, relationships) {
758
- return applyTablesFilter(tables, relationships, this.#options.tables);
681
+ const padding = " ".repeat(spaces);
682
+ return text.split("\n").map((line) => line.length ? padding + line : padding).join("\n");
683
+ }
684
+ function escapeXml(value) {
685
+ return value.replaceAll(/&/g, "&amp;").replaceAll(/</g, "&lt;").replaceAll(/>/g, "&gt;").replaceAll(/"/g, "&quot;").replaceAll(/'/g, "&apos;");
686
+ }
687
+
688
+ // packages/text2sql/src/lib/teach/teachables.ts
689
+ function term(name, definition) {
690
+ return {
691
+ type: "term",
692
+ format: () => wrapBlock("term", [leaf("name", name), leaf("definition", definition)])
693
+ };
694
+ }
695
+ function hint(text) {
696
+ return {
697
+ type: "hint",
698
+ format: () => leaf("hint", text)
699
+ };
700
+ }
701
+ function guardrail(input) {
702
+ const { rule, reason, action } = input;
703
+ return {
704
+ type: "guardrail",
705
+ format: () => wrapBlock("guardrail", [
706
+ leaf("rule", rule),
707
+ reason ? leaf("reason", reason) : "",
708
+ action ? leaf("action", action) : ""
709
+ ])
710
+ };
711
+ }
712
+ function explain(input) {
713
+ const { concept, explanation, therefore } = input;
714
+ return {
715
+ type: "explain",
716
+ format: () => wrapBlock("explanation", [
717
+ leaf("concept", concept),
718
+ leaf("details", explanation),
719
+ therefore ? leaf("therefore", therefore) : ""
720
+ ])
721
+ };
722
+ }
723
+ function example(input) {
724
+ const { question, sql, note } = input;
725
+ return {
726
+ type: "example",
727
+ format: () => wrapBlock("example", [
728
+ leaf("question", question),
729
+ leaf("sql", sql),
730
+ note ? leaf("note", note) : ""
731
+ ])
732
+ };
733
+ }
734
+ function clarification(input) {
735
+ const { when, ask, reason } = input;
736
+ return {
737
+ type: "clarification",
738
+ format: () => wrapBlock("clarification", [
739
+ leaf("when", when),
740
+ leaf("ask", ask),
741
+ leaf("reason", reason)
742
+ ])
743
+ };
744
+ }
745
+ function workflow(input) {
746
+ const { task, steps, triggers, notes } = input;
747
+ return {
748
+ type: "workflow",
749
+ format: () => wrapBlock("workflow", [
750
+ leaf("task", task),
751
+ triggers?.length ? list("triggers", triggers, "trigger") : "",
752
+ list("steps", steps, "step"),
753
+ notes ? leaf("notes", notes) : ""
754
+ ])
755
+ };
756
+ }
757
+ function quirk(input) {
758
+ const { issue, workaround } = input;
759
+ return {
760
+ type: "quirk",
761
+ format: () => wrapBlock("quirk", [
762
+ leaf("issue", issue),
763
+ leaf("workaround", workaround)
764
+ ])
765
+ };
766
+ }
767
+ function styleGuide(input) {
768
+ const { prefer, never, always } = input;
769
+ return {
770
+ type: "styleGuide",
771
+ format: () => wrapBlock("style_guide", [
772
+ leaf("prefer", prefer),
773
+ always ? leaf("always", always) : "",
774
+ never ? leaf("never", never) : ""
775
+ ])
776
+ };
777
+ }
778
+ function analogy(input) {
779
+ const { concept, relationship, insight, therefore, pitfall } = input;
780
+ return {
781
+ type: "analogy",
782
+ format: () => wrapBlock("analogy", [
783
+ list("concepts", concept, "concept"),
784
+ leaf("relationship", relationship),
785
+ insight ? leaf("insight", insight) : "",
786
+ therefore ? leaf("therefore", therefore) : "",
787
+ pitfall ? leaf("pitfall", pitfall) : ""
788
+ ])
789
+ };
790
+ }
791
+ function toInstructions(...teachables) {
792
+ if (!teachables.length) {
793
+ return "";
759
794
  }
760
- #formatTableIdentifier(table) {
761
- const name = table.rawName ?? table.name;
762
- if (table.schema) {
763
- return `${this.#quoteSqlIdentifier(table.schema)}.${this.#quoteSqlIdentifier(name)}`;
764
- }
765
- return this.#quoteSqlIdentifier(name);
795
+ const grouped = /* @__PURE__ */ new Map();
796
+ for (const teachable of teachables) {
797
+ const existing = grouped.get(teachable.type) ?? [];
798
+ existing.push(teachable);
799
+ grouped.set(teachable.type, existing);
766
800
  }
767
- #toNumber(value) {
768
- if (typeof value === "number" && Number.isFinite(value)) {
769
- return value;
770
- }
771
- if (typeof value === "bigint") {
772
- return Number(value);
801
+ const sections = SECTION_ORDER.map(({ type, tag }) => {
802
+ const items = grouped.get(type);
803
+ if (!items?.length) {
804
+ return "";
773
805
  }
774
- if (typeof value === "string" && value.trim() !== "") {
775
- const parsed = Number(value);
776
- return Number.isFinite(parsed) ? parsed : null;
806
+ const renderedItems = items.map((item) => item.format().trim()).filter(Boolean).map((item) => indentBlock(item, 2)).join("\n");
807
+ if (!renderedItems.length) {
808
+ return "";
777
809
  }
778
- return null;
810
+ return `<${tag}>
811
+ ${renderedItems}
812
+ </${tag}>`;
813
+ }).filter((section) => Boolean(section));
814
+ if (!sections.length) {
815
+ return "";
779
816
  }
780
- #classifyRowCount(count) {
781
- if (count < 100) {
782
- return "tiny";
783
- }
784
- if (count < 1e3) {
785
- return "small";
786
- }
787
- if (count < 1e4) {
788
- return "medium";
817
+ const content = indentBlock(sections.join("\n"), 2);
818
+ return `<teachings>
819
+ ${content}
820
+ </teachings>`;
821
+ }
822
+ var SECTION_ORDER = [
823
+ { type: "guardrail", tag: "guardrails" },
824
+ { type: "styleGuide", tag: "style_guides" },
825
+ { type: "hint", tag: "hints" },
826
+ { type: "clarification", tag: "clarifications" },
827
+ { type: "workflow", tag: "workflows" },
828
+ { type: "quirk", tag: "quirks" },
829
+ { type: "term", tag: "terminology" },
830
+ { type: "explain", tag: "explanations" },
831
+ { type: "analogy", tag: "analogies" },
832
+ { type: "example", tag: "examples" }
833
+ ];
834
+ function toTeachables(generated) {
835
+ return generated.map((item) => {
836
+ switch (item.type) {
837
+ case "term":
838
+ return term(item.name, item.definition);
839
+ case "hint":
840
+ return hint(item.text);
841
+ case "guardrail":
842
+ return guardrail({
843
+ rule: item.rule,
844
+ reason: item.reason,
845
+ action: item.action
846
+ });
847
+ case "explain":
848
+ return explain({
849
+ concept: item.concept,
850
+ explanation: item.explanation,
851
+ therefore: item.therefore
852
+ });
853
+ case "example":
854
+ return example({
855
+ question: item.question,
856
+ sql: item.sql,
857
+ note: item.note
858
+ });
859
+ case "clarification":
860
+ return clarification({
861
+ when: item.when,
862
+ ask: item.ask,
863
+ reason: item.reason
864
+ });
865
+ case "workflow":
866
+ return workflow({
867
+ task: item.task,
868
+ steps: item.steps,
869
+ triggers: item.triggers,
870
+ notes: item.notes
871
+ });
872
+ case "quirk":
873
+ return quirk({
874
+ issue: item.issue,
875
+ workaround: item.workaround
876
+ });
877
+ case "styleGuide":
878
+ return styleGuide({
879
+ prefer: item.prefer,
880
+ never: item.never,
881
+ always: item.always
882
+ });
883
+ case "analogy":
884
+ return analogy({
885
+ concept: item.concept,
886
+ relationship: item.relationship,
887
+ insight: item.insight,
888
+ therefore: item.therefore,
889
+ pitfall: item.pitfall
890
+ });
789
891
  }
790
- if (count < 1e5) {
791
- return "large";
892
+ });
893
+ }
894
+ function userProfile(input) {
895
+ return {
896
+ type: "user_profile",
897
+ format: () => {
898
+ return "";
792
899
  }
793
- return "huge";
900
+ };
901
+ }
902
+
903
+ // packages/text2sql/src/lib/agents/teachables.agent.ts
904
+ var teachableSchema = z5.discriminatedUnion("type", [
905
+ z5.object({
906
+ type: z5.literal("term"),
907
+ name: z5.string(),
908
+ definition: z5.string()
909
+ }),
910
+ z5.object({
911
+ type: z5.literal("hint"),
912
+ text: z5.string()
913
+ }),
914
+ z5.object({
915
+ type: z5.literal("guardrail"),
916
+ rule: z5.string(),
917
+ reason: z5.string().optional(),
918
+ action: z5.string().optional()
919
+ }),
920
+ z5.object({
921
+ type: z5.literal("explain"),
922
+ concept: z5.string(),
923
+ explanation: z5.string(),
924
+ therefore: z5.string().optional()
925
+ }),
926
+ z5.object({
927
+ type: z5.literal("example"),
928
+ question: z5.string(),
929
+ sql: z5.string(),
930
+ note: z5.string().optional()
931
+ }),
932
+ z5.object({
933
+ type: z5.literal("clarification"),
934
+ when: z5.string(),
935
+ ask: z5.string(),
936
+ reason: z5.string()
937
+ }),
938
+ z5.object({
939
+ type: z5.literal("workflow"),
940
+ task: z5.string(),
941
+ steps: z5.array(z5.string()).min(2),
942
+ triggers: z5.array(z5.string()).optional(),
943
+ notes: z5.string().optional()
944
+ }),
945
+ z5.object({
946
+ type: z5.literal("quirk"),
947
+ issue: z5.string(),
948
+ workaround: z5.string()
949
+ }),
950
+ z5.object({
951
+ type: z5.literal("styleGuide"),
952
+ prefer: z5.string(),
953
+ never: z5.string().optional(),
954
+ always: z5.string().optional()
955
+ }),
956
+ z5.object({
957
+ type: z5.literal("analogy"),
958
+ concept: z5.array(z5.string()).min(2),
959
+ relationship: z5.string(),
960
+ insight: z5.string().optional(),
961
+ therefore: z5.string().optional(),
962
+ pitfall: z5.string().optional()
963
+ })
964
+ ]);
965
+ var teachablesAuthorAgent = agent6({
966
+ name: "teachables-author",
967
+ model: groq6("openai/gpt-oss-20b"),
968
+ output: z5.object({
969
+ teachables: z5.array(teachableSchema).min(3).max(10).describe(
970
+ "A concise, high-value set of teachables grounded in the provided schema."
971
+ )
972
+ }),
973
+ prompt: (state) => dedent5`
974
+ ${thirdPersonPrompt2()}
975
+
976
+ <identity>
977
+ You design "teachables" for a Text2SQL system. Teachables become structured XML instructions.
978
+ Choose only high-impact items that improve accuracy, safety, or clarity for this database.
979
+ </identity>
980
+
981
+ ${databaseSchemaPrompt(state)}
982
+
983
+ <teachables_catalog>
984
+ term: name + definition for domain vocabulary.
985
+ hint: behavioral rule/constraint to apply by default.
986
+ guardrail: hard safety/performance boundary with action and optional reason.
987
+ explain: deeper concept metaphor/explanation (+ optional therefore).
988
+ example: question + SQL (+ optional note).
989
+ clarification: when/ask/reason to prompt the user before querying.
990
+ workflow: task + ordered steps (+ optional triggers/notes).
991
+ quirk: data edge case with workaround.
992
+ styleGuide: prefer/never/always guidance for SQL output.
993
+ analogy: comparison of two concepts with relationship (+ optional insight/therefore/pitfall).
994
+ </teachables_catalog>
995
+
996
+ <instructions>
997
+ - Ground everything in the provided schema/context; do not invent tables/columns.
998
+ - Prefer guardrails + clarifications for performance, safety, and ambiguity.
999
+ - Use examples only when a clear, schema-valid pattern is evident.
1000
+ - Keep the set lean (3-10 items) and non-duplicative; combine overlapping ideas.
1001
+ - Return JSON that satisfies the output schema; do not wrap in prose.
1002
+ </instructions>
1003
+ `
1004
+ });
1005
+
1006
+ // packages/text2sql/src/lib/history/history.ts
1007
+ var History = class {
1008
+ };
1009
+
1010
+ // packages/text2sql/src/lib/memory/user-profile.ts
1011
+ import { existsSync as existsSync2 } from "node:fs";
1012
+ import { readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
1013
+ import { tmpdir as tmpdir2 } from "node:os";
1014
+ import path2 from "node:path";
1015
+ var UserProfileStore = class {
1016
+ constructor(userId) {
1017
+ this.userId = userId;
1018
+ const safeUserId = userId.replace(/[^a-z0-9]/gi, "_").toLowerCase();
1019
+ this.path = path2.join(tmpdir2(), `user-profile-${safeUserId}.json`);
794
1020
  }
795
- #shouldCollectStats(type) {
796
- if (!type) {
797
- return false;
1021
+ path;
1022
+ /**
1023
+ * Retrieve the full user profile data.
1024
+ */
1025
+ async get() {
1026
+ if (existsSync2(this.path)) {
1027
+ try {
1028
+ const content = await readFile2(this.path, "utf-8");
1029
+ return JSON.parse(content);
1030
+ } catch (error) {
1031
+ console.error("Failed to read user profile:", error);
1032
+ }
798
1033
  }
799
- const normalized = type.toLowerCase();
800
- return /int|real|numeric|double|float|decimal|date|time|bool/.test(
801
- normalized
802
- );
1034
+ return {
1035
+ items: [],
1036
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
1037
+ };
803
1038
  }
804
- #normalizeValue(value) {
805
- if (value === null || value === void 0) {
806
- return null;
807
- }
808
- if (typeof value === "string") {
809
- return value;
810
- }
811
- if (typeof value === "number" || typeof value === "bigint") {
812
- return String(value);
813
- }
814
- if (typeof value === "boolean") {
815
- return value ? "true" : "false";
816
- }
817
- if (value instanceof Date) {
818
- return value.toISOString();
819
- }
820
- if (typeof Buffer !== "undefined" && Buffer.isBuffer(value)) {
821
- return value.toString("utf-8");
822
- }
823
- return null;
1039
+ /**
1040
+ * Save the user profile data.
1041
+ */
1042
+ async save(data) {
1043
+ data.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
1044
+ await writeFile2(this.path, JSON.stringify(data, null, 2), "utf-8");
824
1045
  }
825
- async #runQuery(sql) {
826
- const result = await this.#options.execute(sql);
827
- if (Array.isArray(result)) {
828
- return result;
829
- }
830
- if (result && typeof result === "object" && "rows" in result && Array.isArray(result.rows)) {
831
- return result.rows;
832
- }
833
- throw new Error(
834
- "Sqlite adapter execute() must return an array of rows or an object with a rows array when introspecting."
1046
+ /**
1047
+ * Add an item to the profile.
1048
+ */
1049
+ async add(type, text) {
1050
+ const data = await this.get();
1051
+ const exists = data.items.some(
1052
+ (item) => item.type === type && item.text === text
835
1053
  );
836
- }
837
- async #resolveInfo() {
838
- const { info } = this.#options;
839
- if (!info) {
840
- return { dialect: "sqlite" };
841
- }
842
- if (typeof info === "function") {
843
- return info();
1054
+ if (!exists) {
1055
+ data.items.push({ type, text });
1056
+ await this.save(data);
844
1057
  }
845
- return info;
846
1058
  }
847
- };
848
-
849
- // packages/text2sql/src/lib/agents/brief.agent.ts
850
- import { groq as groq2 } from "@ai-sdk/groq";
851
- import { createUIMessageStream, tool as tool2 } from "ai";
852
- import dedent from "dedent";
853
- import { createHash } from "node:crypto";
854
- import { existsSync } from "node:fs";
855
- import { readFile, writeFile } from "node:fs/promises";
856
- import { tmpdir } from "node:os";
857
- import path from "node:path";
858
- import z2 from "zod";
859
- import {
860
- agent as agent2,
861
- generate,
862
- toState as toState2,
863
- user
864
- } from "@deepagents/agent";
865
- var BriefCache = class {
866
- path;
867
- constructor(watermark) {
868
- const hash = createHash("md5").update(watermark).digest("hex");
869
- this.path = path.join(tmpdir(), `db-brief-${hash}.txt`);
1059
+ /**
1060
+ * Remove a specific item from the profile.
1061
+ */
1062
+ async remove(type, text) {
1063
+ const data = await this.get();
1064
+ const filtered = data.items.filter((item) => {
1065
+ return !(item.type === type && item.text === text);
1066
+ });
1067
+ await this.save({ ...data, items: filtered });
870
1068
  }
871
- async get() {
872
- if (existsSync(this.path)) {
873
- return readFile(this.path, "utf-8");
874
- }
875
- return null;
1069
+ /**
1070
+ * Clear the entire profile.
1071
+ */
1072
+ async clear() {
1073
+ await this.save({ items: [], lastUpdated: (/* @__PURE__ */ new Date()).toISOString() });
876
1074
  }
877
- set(brief) {
878
- return writeFile(this.path, brief, "utf-8");
1075
+ /**
1076
+ * Get the formatted XML string for the system prompt.
1077
+ */
1078
+ async toXml() {
1079
+ const data = await this.get();
1080
+ return toUserProfileXml(data.items);
879
1081
  }
880
1082
  };
881
- var briefAgent = agent2({
882
- name: "db-brief-agent",
883
- model: groq2("openai/gpt-oss-20b"),
884
- prompt: (state) => dedent`
885
- <identity>
886
- You are a database analyst expert. Your job is to understand what a database represents and provide business context about it.
887
- You have READ-ONLY access to the database.
888
- </identity>
889
-
890
- ${databaseSchemaPrompt(state)}
891
-
892
- <instructions>
893
- Write a business context that helps another agent answer questions accurately.
894
-
895
- For EACH table, do queries ONE AT A TIME:
896
- 1. SELECT COUNT(*) to get row count
897
- 2. SELECT * LIMIT 3 to see sample data
898
-
899
- Then write a report with:
900
- - What business this database is for
901
- - For each table: purpose, row count, and example of what the data looks like
902
-
903
- Include concrete examples like "Track prices are $0.99", "Customer names like 'Lu\u00eds Gon\u00e7alves'", etc.
904
-
905
- Keep it 400-600 words, conversational style.
906
- </instructions>
907
- `,
908
- tools: {
909
- query_database: tool2({
910
- description: "Execute a SELECT query to explore the database and gather insights.",
911
- inputSchema: z2.object({
912
- sql: z2.string().describe("The SELECT query to execute"),
913
- purpose: z2.string().describe("What insight you are trying to gather with this query")
914
- }),
915
- execute: ({ sql }, options) => {
916
- const state = toState2(options);
917
- return state.execute(sql);
918
- }
919
- })
1083
+ function toUserProfileXml(items) {
1084
+ if (items.length === 0) {
1085
+ return "";
920
1086
  }
921
- });
922
- async function runAndCache(introspection, cache) {
923
- const { text } = await generate(
924
- briefAgent,
925
- [
926
- user(
927
- "Please analyze the database and write a contextual report about what this database represents."
928
- )
929
- ],
930
- { introspection }
931
- );
932
- await cache.set(text);
933
- return text;
934
- }
935
- async function generateBrief(introspection, cache) {
936
- const brief = await cache.get();
937
- if (!brief) {
938
- return runAndCache(introspection, cache);
1087
+ const facts = items.filter((i) => i.type === "fact");
1088
+ const preferences = items.filter((i) => i.type === "preference");
1089
+ const present = items.filter((i) => i.type === "present");
1090
+ const sections = [];
1091
+ if (facts.length > 0) {
1092
+ const lines = facts.map((f) => `- ${f.text}`);
1093
+ sections.push(wrapBlock2("identity", lines));
939
1094
  }
940
- return brief;
1095
+ if (preferences.length > 0) {
1096
+ const lines = preferences.map((p) => `- ${p.text}`);
1097
+ sections.push(wrapBlock2("preferences", lines));
1098
+ }
1099
+ if (present.length > 0) {
1100
+ const lines = present.map((c) => `- ${c.text}`);
1101
+ sections.push(wrapBlock2("working_context", lines));
1102
+ }
1103
+ if (sections.length === 0) return "";
1104
+ return `<user_profile>
1105
+ ${indentBlock2(sections.join("\n"), 2)}
1106
+ </user_profile>`;
941
1107
  }
942
- function toBrief(forceRefresh = false) {
943
- return (state, setState) => {
944
- return createUIMessageStream({
945
- execute: async ({ writer }) => {
946
- if (forceRefresh) {
947
- const brief = await runAndCache(state.introspection, state.cache);
948
- writer.write({
949
- type: "data-brief-agent",
950
- data: {
951
- cache: "forced",
952
- brief
953
- }
954
- });
955
- setState({ context: brief });
956
- } else {
957
- let brief = await state.cache.get();
958
- if (!brief) {
959
- writer.write({
960
- type: "data-brief-agent",
961
- data: {
962
- cache: "miss"
963
- }
964
- });
965
- brief = await runAndCache(state.introspection, state.cache);
966
- writer.write({
967
- type: "data-brief-agent",
968
- data: {
969
- cache: "new",
970
- brief
971
- }
972
- });
973
- } else {
974
- writer.write({
975
- type: "data-brief-agent",
976
- data: {
977
- cache: "hit",
978
- brief
979
- }
980
- });
981
- }
982
- }
983
- }
984
- });
985
- };
1108
+ function wrapBlock2(tag, lines) {
1109
+ if (lines.length === 0) return "";
1110
+ return `<${tag}>
1111
+ ${indentBlock2(lines.join("\n"), 2)}
1112
+ </${tag}>`;
1113
+ }
1114
+ function indentBlock2(text, spaces) {
1115
+ const padding = " ".repeat(spaces);
1116
+ return text.split("\n").map((line) => line.length ? padding + line : padding).join("\n");
986
1117
  }
987
1118
 
988
- // packages/text2sql/src/lib/agents/explainer.agent.ts
989
- import { groq as groq3 } from "@ai-sdk/groq";
990
- import dedent2 from "dedent";
991
- import z3 from "zod";
992
- import { agent as agent3 } from "@deepagents/agent";
993
- var explainerAgent = agent3({
994
- name: "explainer",
995
- model: groq3("openai/gpt-oss-20b"),
996
- prompt: (state) => dedent2`
997
- You are an expert SQL tutor.
998
- Explain the following SQL query in plain English to a non-technical user.
999
- Focus on the intent and logic, not the syntax.
1000
-
1001
- <sql>
1002
- ${state?.sql}
1003
- </sql>
1004
- `,
1005
- output: z3.object({
1006
- explanation: z3.string().describe("The explanation of the SQL query.")
1007
- })
1008
- });
1009
-
1010
- // packages/text2sql/src/lib/agents/suggestions.agents.ts
1011
- import { groq as groq4 } from "@ai-sdk/groq";
1012
- import dedent3 from "dedent";
1013
- import z4 from "zod";
1014
- import { agent as agent4, thirdPersonPrompt } from "@deepagents/agent";
1015
- var suggestionsAgent = agent4({
1016
- name: "text2sql-suggestions",
1017
- model: groq4("openai/gpt-oss-20b"),
1018
- output: z4.object({
1019
- suggestions: z4.array(
1020
- z4.object({
1021
- question: z4.string().describe("A complex, high-impact business question."),
1022
- sql: z4.string().describe("The SQL statement needed to answer the question."),
1023
- businessValue: z4.string().describe("Why the question matters to stakeholders.")
1024
- })
1025
- ).min(1).max(5).describe("A set of up to two advanced question + SQL pairs.")
1026
- }),
1027
- prompt: (state) => {
1028
- return dedent3`
1029
- ${thirdPersonPrompt()}
1030
-
1031
- <identity>
1032
- You are a senior analytics strategist who proposes ambitious business questions
1033
- and drafts the SQL needed to answer them. You specialize in identifying ideas
1034
- that combine multiple tables, apply segmentation or time analysis, and surface
1035
- metrics that drive executive decisions.
1036
- </identity>
1037
-
1038
- ${databaseSchemaPrompt(state)}
1039
-
1040
- <instructions>
1041
- - Recommend one or two UNIQUE questions that go beyond simple counts or listings.
1042
- - Favor questions that require joins, aggregates, time comparisons, cohort analysis,
1043
- or window functions.
1044
- - For each question, explain the business reason stakeholders care about it.
1045
- - Provide the complete SQL query that could answer the question in the given schema.
1046
- - Keep result sets scoped with LIMIT clauses (max 50 rows) when returning raw rows.
1047
- - Ensure table/column names match the provided schema exactly.
1048
- - Use columns marked [LowCardinality: ...] to identify meaningful categorical filters or segmentations.
1049
- - Leverage table [rows / size] hints to determine whether to aggregate (large tables) or inspect detailed data (tiny tables).
1050
- - Reference PK/Indexed annotations and the Indexes list to recommend queries that use efficient join/filter paths.
1051
- - Column annotations may expose ranges/null percentages—use them to suggest realistic thresholds or quality checks.
1052
- - Consult <relationship_examples> to anchor your recommendations in the actual join paths between tables.
1053
- - Output only information grounded in the schema/context provided.
1054
- </instructions>
1055
-
1056
- <response-format>
1057
- Return valid JSON that satisfies the defined output schema.
1058
- </response-format>
1059
- `;
1119
+ // packages/text2sql/src/lib/sql.ts
1120
+ var Text2Sql = class {
1121
+ #config;
1122
+ #introspectionCache;
1123
+ constructor(config) {
1124
+ this.#config = {
1125
+ ...config,
1126
+ instructions: config.instructions ?? [],
1127
+ tools: config.tools ?? {}
1128
+ };
1129
+ this.#introspectionCache = new JsonCache(
1130
+ "introspection" + config.version
1131
+ );
1060
1132
  }
1061
- });
1062
-
1063
- // packages/text2sql/src/lib/agents/synthesizer.agent.ts
1064
- import { groq as groq5 } from "@ai-sdk/groq";
1065
- import dedent4 from "dedent";
1066
- import { agent as agent5 } from "@deepagents/agent";
1067
- var synthesizerAgent = agent5({
1068
- name: "synthesizer_agent",
1069
- model: groq5("openai/gpt-oss-20b"),
1070
- handoffDescription: "Use this tool to synthesizes the final user-facing response. This agent understands how the user interface works and can tailor the response accordingly.",
1071
- prompt: (state) => {
1072
- const contextInfo = state?.context ?? "No additional context provided.";
1073
- return dedent4`
1074
- <identity>
1075
- You are a data insights companion helping users understand information using clear, everyday language.
1076
- You communicate in a friendly, conversational manner.
1077
- You only see the user's question and the results from internal systems. You do not know how those results were produced, so never reference technical systems or implementation details.
1078
- </identity>
1079
-
1080
- <context>
1081
- ${contextInfo}
1082
- </context>
1083
-
1084
- <response-strategy>
1085
- 1. Re-read the user's question, then inspect the <data> provided to understand what it represents.
1086
- 2. Translate technical field names into friendly descriptions based on the domain context.
1087
- 3. Explain the core insight in 2-4 sentences focused on what the data reveals.
1088
- 4. When multiple records are present, highlight only the most relevant ones (max 5) with comparisons or rankings.
1089
- 5. If data is empty or contains an error, state that plainly and suggest what to clarify or try next.
1090
- 6. Close with an optional follow-up recommendation or next step based on the insights.
1091
- </response-strategy>
1092
-
1093
- <guardrails>
1094
- - Never mention technical implementation details, data structures, or internal systems.
1095
- - Keep tone casual, confident, and insight-driven; do not narrate your process.
1096
- - Base every statement strictly on the <data> provided plus the context - no speculation.
1097
- </guardrails>
1098
- `;
1133
+ async #getSql(stream2) {
1134
+ const chunks = await Array.fromAsync(
1135
+ stream2
1136
+ );
1137
+ const sql = chunks.at(-1);
1138
+ if (sql && sql.type === "data-text-delta") {
1139
+ return sql.data.text;
1140
+ }
1141
+ throw new Error("No SQL generated");
1099
1142
  }
1100
- });
1101
-
1102
- // packages/text2sql/src/lib/agents/teachables.agent.ts
1103
- import { groq as groq6 } from "@ai-sdk/groq";
1104
- import dedent5 from "dedent";
1105
- import z5 from "zod";
1106
- import { agent as agent6, thirdPersonPrompt as thirdPersonPrompt2 } from "@deepagents/agent";
1107
-
1108
- // packages/text2sql/src/lib/teach/xml.ts
1109
- function wrapBlock(tag, children) {
1110
- const content = children.filter((child) => Boolean(child)).join("\n");
1111
- if (!content) {
1112
- return "";
1143
+ async explain(sql) {
1144
+ const { experimental_output } = await generate2(
1145
+ explainerAgent,
1146
+ [user2("Explain this SQL.")],
1147
+ { sql }
1148
+ );
1149
+ return experimental_output.explanation;
1113
1150
  }
1114
- return `<${tag}>
1115
- ${indentBlock(content, 2)}
1116
- </${tag}>`;
1117
- }
1118
- function list(tag, values, childTag) {
1119
- if (!values.length) {
1120
- return "";
1151
+ async toSql(input) {
1152
+ const [introspection, adapterInfo] = await Promise.all([
1153
+ this.index(),
1154
+ this.#config.adapter.info()
1155
+ ]);
1156
+ const context = await generateBrief(introspection, this.#config.cache);
1157
+ return {
1158
+ generate: async () => {
1159
+ const { experimental_output: output } = await generate2(
1160
+ text2sqlOnly,
1161
+ [user2(input)],
1162
+ {
1163
+ adapterInfo: this.#config.adapter.formatInfo(adapterInfo),
1164
+ context,
1165
+ introspection,
1166
+ teachings: toInstructions(...this.#config.instructions)
1167
+ }
1168
+ );
1169
+ return output.sql;
1170
+ }
1171
+ };
1121
1172
  }
1122
- const children = values.map((value) => leaf(childTag, value)).join("\n");
1123
- return `<${tag}>
1124
- ${indentBlock(children, 2)}
1125
- </${tag}>`;
1126
- }
1127
- function leaf(tag, value) {
1128
- const safe = escapeXml(value);
1129
- if (safe.includes("\n")) {
1130
- return `<${tag}>
1131
- ${indentBlock(safe, 2)}
1132
- </${tag}>`;
1173
+ async inspect() {
1174
+ const [introspection, adapterInfo] = await Promise.all([
1175
+ this.index(),
1176
+ this.#config.adapter.info()
1177
+ ]);
1178
+ const context = await generateBrief(introspection, this.#config.cache);
1179
+ return text2sqlOnly.instructions({
1180
+ adapterInfo: this.#config.adapter.formatInfo(adapterInfo),
1181
+ context,
1182
+ introspection,
1183
+ teachings: toInstructions(...this.#config.instructions)
1184
+ });
1133
1185
  }
1134
- return `<${tag}>${safe}</${tag}>`;
1135
- }
1136
- function indentBlock(text, spaces) {
1137
- if (!text.trim()) {
1138
- return "";
1186
+ instruct(...dataset) {
1187
+ this.#config.instructions.push(...dataset);
1139
1188
  }
1140
- const padding = " ".repeat(spaces);
1141
- return text.split("\n").map((line) => line.length ? padding + line : padding).join("\n");
1142
- }
1143
- function escapeXml(value) {
1144
- return value.replaceAll(/&/g, "&amp;").replaceAll(/</g, "&lt;").replaceAll(/>/g, "&gt;").replaceAll(/"/g, "&quot;").replaceAll(/'/g, "&apos;");
1145
- }
1189
+ async index(options) {
1190
+ const cached = await this.#introspectionCache.read();
1191
+ if (cached) {
1192
+ return cached;
1193
+ }
1194
+ const introspection = await this.#config.adapter.introspect(options);
1195
+ await this.#introspectionCache.write(introspection);
1196
+ return introspection;
1197
+ }
1198
+ async teach(input) {
1199
+ const [introspection, adapterInfo] = await Promise.all([
1200
+ this.index(),
1201
+ this.#config.adapter.info()
1202
+ ]);
1203
+ const context = await generateBrief(introspection, this.#config.cache);
1204
+ const { experimental_output } = await generate2(
1205
+ teachablesAuthorAgent,
1206
+ [user2(input)],
1207
+ {
1208
+ introspection,
1209
+ adapterInfo: this.#config.adapter.formatInfo(adapterInfo),
1210
+ context
1211
+ }
1212
+ );
1213
+ const teachables = toTeachables(experimental_output.teachables);
1214
+ this.#config.instructions.push(...teachables);
1215
+ return {
1216
+ teachables,
1217
+ teachings: toInstructions(...this.#config.instructions)
1218
+ };
1219
+ }
1220
+ async tag(input) {
1221
+ const [introspection, adapterInfo] = await Promise.all([
1222
+ this.index(),
1223
+ this.#config.adapter.info()
1224
+ ]);
1225
+ const pipeline = pipe(
1226
+ {
1227
+ input,
1228
+ adapter: this.#config.adapter,
1229
+ cache: this.#config.cache,
1230
+ introspection,
1231
+ adapterInfo: this.#config.adapter.formatInfo(adapterInfo),
1232
+ messages: [user2(input)],
1233
+ renderingTools: this.#config.tools || {},
1234
+ teachings: toInstructions(...this.#config.instructions)
1235
+ },
1236
+ toBrief(),
1237
+ async (state, update) => {
1238
+ const { experimental_output: output } = await generate2(
1239
+ text2sqlOnly,
1240
+ state.messages,
1241
+ state
1242
+ );
1243
+ update({
1244
+ messages: [
1245
+ user2(
1246
+ dedent6`
1247
+ Based on the data provided, please explain in clear, conversational language what insights this reveals.
1146
1248
 
1147
- // packages/text2sql/src/lib/teach/teachables.ts
1148
- function term(name, definition) {
1149
- return {
1150
- type: "term",
1151
- format: () => wrapBlock("term", [leaf("name", name), leaf("definition", definition)])
1152
- };
1153
- }
1154
- function hint(text) {
1155
- return {
1156
- type: "hint",
1157
- format: () => leaf("hint", text)
1158
- };
1159
- }
1160
- function guardrail(input) {
1161
- const { rule, reason, action } = input;
1162
- return {
1163
- type: "guardrail",
1164
- format: () => wrapBlock("guardrail", [
1165
- leaf("rule", rule),
1166
- reason ? leaf("reason", reason) : "",
1167
- action ? leaf("action", action) : ""
1168
- ])
1169
- };
1170
- }
1171
- function explain(input) {
1172
- const { concept, explanation, therefore } = input;
1173
- return {
1174
- type: "explain",
1175
- format: () => wrapBlock("explanation", [
1176
- leaf("concept", concept),
1177
- leaf("details", explanation),
1178
- therefore ? leaf("therefore", therefore) : ""
1179
- ])
1180
- };
1181
- }
1182
- function example(input) {
1183
- const { question, sql, note } = input;
1184
- return {
1185
- type: "example",
1186
- format: () => wrapBlock("example", [
1187
- leaf("question", question),
1188
- leaf("sql", sql),
1189
- note ? leaf("note", note) : ""
1190
- ])
1191
- };
1192
- }
1193
- function clarification(input) {
1194
- const { when, ask, reason } = input;
1195
- return {
1196
- type: "clarification",
1197
- format: () => wrapBlock("clarification", [
1198
- leaf("when", when),
1199
- leaf("ask", ask),
1200
- leaf("reason", reason)
1201
- ])
1202
- };
1203
- }
1204
- function workflow(input) {
1205
- const { task, steps, triggers, notes } = input;
1206
- return {
1207
- type: "workflow",
1208
- format: () => wrapBlock("workflow", [
1209
- leaf("task", task),
1210
- triggers?.length ? list("triggers", triggers, "trigger") : "",
1211
- list("steps", steps, "step"),
1212
- notes ? leaf("notes", notes) : ""
1213
- ])
1214
- };
1215
- }
1216
- function quirk(input) {
1217
- const { issue, workaround } = input;
1218
- return {
1219
- type: "quirk",
1220
- format: () => wrapBlock("quirk", [
1221
- leaf("issue", issue),
1222
- leaf("workaround", workaround)
1223
- ])
1224
- };
1225
- }
1226
- function styleGuide(input) {
1227
- const { prefer, never, always } = input;
1228
- return {
1229
- type: "styleGuide",
1230
- format: () => wrapBlock("style_guide", [
1231
- leaf("prefer", prefer),
1232
- always ? leaf("always", always) : "",
1233
- never ? leaf("never", never) : ""
1234
- ])
1235
- };
1236
- }
1237
- function analogy(input) {
1238
- const { concept, relationship, insight, therefore, pitfall } = input;
1239
- return {
1240
- type: "analogy",
1241
- format: () => wrapBlock("analogy", [
1242
- list("concepts", concept, "concept"),
1243
- leaf("relationship", relationship),
1244
- insight ? leaf("insight", insight) : "",
1245
- therefore ? leaf("therefore", therefore) : "",
1246
- pitfall ? leaf("pitfall", pitfall) : ""
1247
- ])
1248
- };
1249
- }
1250
- function toInstructions(...teachables) {
1251
- if (!teachables.length) {
1252
- return "";
1249
+ <user_question>${state.input}</user_question>
1250
+ <data>${JSON.stringify(this.#config.adapter.execute(output.sql))}</data>
1251
+ `
1252
+ )
1253
+ ]
1254
+ });
1255
+ return output.sql;
1256
+ },
1257
+ synthesizerAgent
1258
+ );
1259
+ const stream2 = pipeline();
1260
+ return {
1261
+ generate: async () => {
1262
+ const sql = await this.#getSql(stream2);
1263
+ return sql;
1264
+ },
1265
+ stream: () => {
1266
+ return stream2;
1267
+ }
1268
+ };
1253
1269
  }
1254
- const grouped = /* @__PURE__ */ new Map();
1255
- for (const teachable of teachables) {
1256
- const existing = grouped.get(teachable.type) ?? [];
1257
- existing.push(teachable);
1258
- grouped.set(teachable.type, existing);
1270
+ async suggest() {
1271
+ const [introspection, adapterInfo] = await Promise.all([
1272
+ this.index(),
1273
+ this.#config.adapter.info()
1274
+ ]);
1275
+ const context = await generateBrief(introspection, this.#config.cache);
1276
+ const { experimental_output: output } = await generate2(
1277
+ suggestionsAgent,
1278
+ [
1279
+ user2(
1280
+ "Suggest high-impact business questions and matching SQL queries for this database."
1281
+ )
1282
+ ],
1283
+ {
1284
+ introspection,
1285
+ adapterInfo: this.#config.adapter.formatInfo(adapterInfo),
1286
+ context
1287
+ }
1288
+ );
1289
+ return output.suggestions;
1259
1290
  }
1260
- const sections = SECTION_ORDER.map(({ type, tag }) => {
1261
- const items = grouped.get(type);
1262
- if (!items?.length) {
1263
- return "";
1264
- }
1265
- const renderedItems = items.map((item) => item.format().trim()).filter(Boolean).map((item) => indentBlock(item, 2)).join("\n");
1266
- if (!renderedItems.length) {
1267
- return "";
1268
- }
1269
- return `<${tag}>
1270
- ${renderedItems}
1271
- </${tag}>`;
1272
- }).filter((section) => Boolean(section));
1273
- if (!sections.length) {
1274
- return "";
1291
+ async single(input) {
1292
+ const [introspection, adapterInfo] = await Promise.all([
1293
+ this.index(),
1294
+ this.#config.adapter.info()
1295
+ ]);
1296
+ return stream(
1297
+ text2sqlMonolith.clone({
1298
+ tools: {
1299
+ ...text2sqlMonolith.handoff.tools,
1300
+ ...this.#config.tools
1301
+ }
1302
+ }),
1303
+ [user2(input)],
1304
+ {
1305
+ teachings: toInstructions(...this.#config.instructions),
1306
+ adapter: this.#config.adapter,
1307
+ introspection,
1308
+ adapterInfo: this.#config.adapter.formatInfo(adapterInfo),
1309
+ context: await generateBrief(introspection, this.#config.cache),
1310
+ renderingTools: this.#config.tools || {}
1311
+ }
1312
+ );
1275
1313
  }
1276
- const content = indentBlock(sections.join("\n"), 2);
1277
- return `<teachings>
1278
- ${content}
1279
- </teachings>`;
1280
- }
1281
- var SECTION_ORDER = [
1282
- { type: "guardrail", tag: "guardrails" },
1283
- { type: "styleGuide", tag: "style_guides" },
1284
- { type: "hint", tag: "hints" },
1285
- { type: "clarification", tag: "clarifications" },
1286
- { type: "workflow", tag: "workflows" },
1287
- { type: "quirk", tag: "quirks" },
1288
- { type: "term", tag: "terminology" },
1289
- { type: "explain", tag: "explanations" },
1290
- { type: "analogy", tag: "analogies" },
1291
- { type: "example", tag: "examples" }
1292
- ];
1293
- function toTeachables(generated) {
1294
- return generated.map((item) => {
1295
- switch (item.type) {
1296
- case "term":
1297
- return term(item.name, item.definition);
1298
- case "hint":
1299
- return hint(item.text);
1300
- case "guardrail":
1301
- return guardrail({
1302
- rule: item.rule,
1303
- reason: item.reason,
1304
- action: item.action
1305
- });
1306
- case "explain":
1307
- return explain({
1308
- concept: item.concept,
1309
- explanation: item.explanation,
1310
- therefore: item.therefore
1311
- });
1312
- case "example":
1313
- return example({
1314
- question: item.question,
1315
- sql: item.sql,
1316
- note: item.note
1317
- });
1318
- case "clarification":
1319
- return clarification({
1320
- when: item.when,
1321
- ask: item.ask,
1322
- reason: item.reason
1323
- });
1324
- case "workflow":
1325
- return workflow({
1326
- task: item.task,
1327
- steps: item.steps,
1328
- triggers: item.triggers,
1329
- notes: item.notes
1330
- });
1331
- case "quirk":
1332
- return quirk({
1333
- issue: item.issue,
1334
- workaround: item.workaround
1335
- });
1336
- case "styleGuide":
1337
- return styleGuide({
1338
- prefer: item.prefer,
1339
- never: item.never,
1340
- always: item.always
1341
- });
1342
- case "analogy":
1343
- return analogy({
1344
- concept: item.concept,
1345
- relationship: item.relationship,
1346
- insight: item.insight,
1347
- therefore: item.therefore,
1348
- pitfall: item.pitfall
1314
+ async chat(messages, params, model) {
1315
+ const [introspection, adapterInfo] = await Promise.all([
1316
+ this.index({ onProgress: console.log }),
1317
+ this.#config.adapter.info()
1318
+ ]);
1319
+ const chat = await this.#config.history.upsertChat({
1320
+ id: params.chatId,
1321
+ userId: params.userId,
1322
+ title: "Chat " + params.chatId
1323
+ });
1324
+ const userProfileStore = new UserProfileStore(params.userId);
1325
+ const userProfileXml = await userProfileStore.toXml();
1326
+ const result = stream(
1327
+ text2sqlMonolith.clone({
1328
+ model,
1329
+ tools: {
1330
+ ...text2sqlMonolith.handoff.tools,
1331
+ ...this.#config.tools,
1332
+ update_user_profile: tool3({
1333
+ description: `Update the user's profile with new facts, preferences, or present context.
1334
+ Use this when the user explicitly states a preference (e.g., "I like dark mode", "Call me Ezz")
1335
+ or when their working context changes (e.g., "I'm working on a hackathon").`,
1336
+ inputSchema: z6.object({
1337
+ type: z6.enum(["fact", "preference", "present"]).describe("The type of information to update."),
1338
+ text: z6.string().describe(
1339
+ "The content of the fact, preference, or present context."
1340
+ ),
1341
+ action: z6.enum(["add", "remove"]).default("add").describe("Whether to add or remove the item.")
1342
+ }),
1343
+ execute: async ({ type, text, action }) => {
1344
+ if (action === "remove") {
1345
+ await userProfileStore.remove(type, text);
1346
+ return `Removed ${type}: ${text}`;
1347
+ }
1348
+ await userProfileStore.add(type, text);
1349
+ return `Added ${type}: ${text}`;
1350
+ }
1351
+ })
1352
+ }
1353
+ }),
1354
+ [...chat.messages.map((it) => it.content), ...messages],
1355
+ {
1356
+ teachings: toInstructions(...this.#config.instructions),
1357
+ adapter: this.#config.adapter,
1358
+ renderingTools: this.#config.tools || {},
1359
+ introspection,
1360
+ adapterInfo: this.#config.adapter.formatInfo(adapterInfo),
1361
+ context: await generateBrief(introspection, this.#config.cache),
1362
+ userProfile: userProfileXml
1363
+ }
1364
+ );
1365
+ return result.toUIMessageStream({
1366
+ onError: (error) => {
1367
+ if (NoSuchToolError.isInstance(error)) {
1368
+ return "The model tried to call a unknown tool.";
1369
+ } else if (InvalidToolInputError.isInstance(error)) {
1370
+ return "The model called a tool with invalid arguments.";
1371
+ } else if (ToolCallRepairError.isInstance(error)) {
1372
+ return "The model tried to call a tool with invalid arguments, but it was repaired.";
1373
+ } else {
1374
+ return "An unknown error occurred.";
1375
+ }
1376
+ },
1377
+ sendStart: true,
1378
+ sendFinish: true,
1379
+ sendReasoning: true,
1380
+ sendSources: true,
1381
+ originalMessages: messages,
1382
+ onFinish: async ({ messages: messages2 }) => {
1383
+ const userMessage = messages2.at(-2);
1384
+ const botMessage = messages2.at(-1);
1385
+ if (!userMessage || !botMessage) {
1386
+ throw new Error("Not implemented yet");
1387
+ }
1388
+ await this.#config.history.addMessage({
1389
+ id: v7(),
1390
+ chatId: params.chatId,
1391
+ role: userMessage.role,
1392
+ content: userMessage
1393
+ });
1394
+ await this.#config.history.addMessage({
1395
+ id: v7(),
1396
+ chatId: params.chatId,
1397
+ role: botMessage.role,
1398
+ content: botMessage
1349
1399
  });
1350
- }
1351
- });
1352
- }
1353
- function userProfile(input) {
1354
- return {
1355
- type: "user_profile",
1356
- format: () => {
1357
- return "";
1358
- }
1359
- };
1360
- }
1361
-
1362
- // packages/text2sql/src/lib/agents/teachables.agent.ts
1363
- var teachableSchema = z5.discriminatedUnion("type", [
1364
- z5.object({
1365
- type: z5.literal("term"),
1366
- name: z5.string(),
1367
- definition: z5.string()
1368
- }),
1369
- z5.object({
1370
- type: z5.literal("hint"),
1371
- text: z5.string()
1372
- }),
1373
- z5.object({
1374
- type: z5.literal("guardrail"),
1375
- rule: z5.string(),
1376
- reason: z5.string().optional(),
1377
- action: z5.string().optional()
1378
- }),
1379
- z5.object({
1380
- type: z5.literal("explain"),
1381
- concept: z5.string(),
1382
- explanation: z5.string(),
1383
- therefore: z5.string().optional()
1384
- }),
1385
- z5.object({
1386
- type: z5.literal("example"),
1387
- question: z5.string(),
1388
- sql: z5.string(),
1389
- note: z5.string().optional()
1390
- }),
1391
- z5.object({
1392
- type: z5.literal("clarification"),
1393
- when: z5.string(),
1394
- ask: z5.string(),
1395
- reason: z5.string()
1396
- }),
1397
- z5.object({
1398
- type: z5.literal("workflow"),
1399
- task: z5.string(),
1400
- steps: z5.array(z5.string()).min(2),
1401
- triggers: z5.array(z5.string()).optional(),
1402
- notes: z5.string().optional()
1403
- }),
1404
- z5.object({
1405
- type: z5.literal("quirk"),
1406
- issue: z5.string(),
1407
- workaround: z5.string()
1408
- }),
1409
- z5.object({
1410
- type: z5.literal("styleGuide"),
1411
- prefer: z5.string(),
1412
- never: z5.string().optional(),
1413
- always: z5.string().optional()
1414
- }),
1415
- z5.object({
1416
- type: z5.literal("analogy"),
1417
- concept: z5.array(z5.string()).min(2),
1418
- relationship: z5.string(),
1419
- insight: z5.string().optional(),
1420
- therefore: z5.string().optional(),
1421
- pitfall: z5.string().optional()
1422
- })
1423
- ]);
1424
- var teachablesAuthorAgent = agent6({
1425
- name: "teachables-author",
1426
- model: groq6("openai/gpt-oss-20b"),
1427
- output: z5.object({
1428
- teachables: z5.array(teachableSchema).min(3).max(10).describe(
1429
- "A concise, high-value set of teachables grounded in the provided schema."
1430
- )
1431
- }),
1432
- prompt: (state) => dedent5`
1433
- ${thirdPersonPrompt2()}
1434
-
1435
- <identity>
1436
- You design "teachables" for a Text2SQL system. Teachables become structured XML instructions.
1437
- Choose only high-impact items that improve accuracy, safety, or clarity for this database.
1438
- </identity>
1439
-
1440
- ${databaseSchemaPrompt(state)}
1441
-
1442
- <teachables_catalog>
1443
- term: name + definition for domain vocabulary.
1444
- hint: behavioral rule/constraint to apply by default.
1445
- guardrail: hard safety/performance boundary with action and optional reason.
1446
- explain: deeper concept metaphor/explanation (+ optional therefore).
1447
- example: question + SQL (+ optional note).
1448
- clarification: when/ask/reason to prompt the user before querying.
1449
- workflow: task + ordered steps (+ optional triggers/notes).
1450
- quirk: data edge case with workaround.
1451
- styleGuide: prefer/never/always guidance for SQL output.
1452
- analogy: comparison of two concepts with relationship (+ optional insight/therefore/pitfall).
1453
- </teachables_catalog>
1454
-
1455
- <instructions>
1456
- - Ground everything in the provided schema/context; do not invent tables/columns.
1457
- - Prefer guardrails + clarifications for performance, safety, and ambiguity.
1458
- - Use examples only when a clear, schema-valid pattern is evident.
1459
- - Keep the set lean (3-10 items) and non-duplicative; combine overlapping ideas.
1460
- - Return JSON that satisfies the output schema; do not wrap in prose.
1461
- </instructions>
1462
- `
1463
- });
1464
-
1465
- // packages/text2sql/src/lib/history/history.ts
1466
- var History = class {
1467
- };
1468
-
1469
- // packages/text2sql/src/lib/memory/user-profile.ts
1470
- import { existsSync as existsSync2 } from "node:fs";
1471
- import { readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
1472
- import { tmpdir as tmpdir2 } from "node:os";
1473
- import path2 from "node:path";
1474
- var UserProfileStore = class {
1475
- constructor(userId) {
1476
- this.userId = userId;
1477
- const safeUserId = userId.replace(/[^a-z0-9]/gi, "_").toLowerCase();
1478
- this.path = path2.join(tmpdir2(), `user-profile-${safeUserId}.json`);
1479
- }
1480
- path;
1481
- /**
1482
- * Retrieve the full user profile data.
1483
- */
1484
- async get() {
1485
- if (existsSync2(this.path)) {
1486
- try {
1487
- const content = await readFile2(this.path, "utf-8");
1488
- return JSON.parse(content);
1489
- } catch (error) {
1490
- console.error("Failed to read user profile:", error);
1491
1400
  }
1492
- }
1493
- return {
1494
- items: [],
1495
- lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
1496
- };
1497
- }
1498
- /**
1499
- * Save the user profile data.
1500
- */
1501
- async save(data) {
1502
- data.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
1503
- await writeFile2(this.path, JSON.stringify(data, null, 2), "utf-8");
1504
- }
1505
- /**
1506
- * Add an item to the profile.
1507
- */
1508
- async add(type, text) {
1509
- const data = await this.get();
1510
- const exists = data.items.some(
1511
- (item) => item.type === type && item.text === text
1512
- );
1513
- if (!exists) {
1514
- data.items.push({ type, text });
1515
- await this.save(data);
1516
- }
1517
- }
1518
- /**
1519
- * Remove a specific item from the profile.
1520
- */
1521
- async remove(type, text) {
1522
- const data = await this.get();
1523
- const filtered = data.items.filter((item) => {
1524
- return !(item.type === type && item.text === text);
1525
1401
  });
1526
- await this.save({ ...data, items: filtered });
1527
- }
1528
- /**
1529
- * Clear the entire profile.
1530
- */
1531
- async clear() {
1532
- await this.save({ items: [], lastUpdated: (/* @__PURE__ */ new Date()).toISOString() });
1533
- }
1534
- /**
1535
- * Get the formatted XML string for the system prompt.
1536
- */
1537
- async toXml() {
1538
- const data = await this.get();
1539
- return toUserProfileXml(data.items);
1540
1402
  }
1541
1403
  };
1542
- function toUserProfileXml(items) {
1543
- if (items.length === 0) {
1544
- return "";
1545
- }
1546
- const facts = items.filter((i) => i.type === "fact");
1547
- const preferences = items.filter((i) => i.type === "preference");
1548
- const present = items.filter((i) => i.type === "present");
1549
- const sections = [];
1550
- if (facts.length > 0) {
1551
- const lines = facts.map((f) => `- ${f.text}`);
1552
- sections.push(wrapBlock2("identity", lines));
1553
- }
1554
- if (preferences.length > 0) {
1555
- const lines = preferences.map((p) => `- ${p.text}`);
1556
- sections.push(wrapBlock2("preferences", lines));
1557
- }
1558
- if (present.length > 0) {
1559
- const lines = present.map((c) => `- ${c.text}`);
1560
- sections.push(wrapBlock2("working_context", lines));
1561
- }
1562
- if (sections.length === 0) return "";
1563
- return `<user_profile>
1564
- ${indentBlock2(sections.join("\n"), 2)}
1565
- </user_profile>`;
1566
- }
1567
- function wrapBlock2(tag, lines) {
1568
- if (lines.length === 0) return "";
1569
- return `<${tag}>
1570
- ${indentBlock2(lines.join("\n"), 2)}
1571
- </${tag}>`;
1572
- }
1573
- function indentBlock2(text, spaces) {
1574
- const padding = " ".repeat(spaces);
1575
- return text.split("\n").map((line) => line.length ? padding + line : padding).join("\n");
1404
+ if (import.meta.main) {
1576
1405
  }
1577
1406
 
1578
- // packages/text2sql/src/lib/sql.ts
1579
- var Text2Sql = class {
1580
- #config;
1581
- constructor(config) {
1582
- this.#config = {
1583
- ...config,
1584
- instructions: config.instructions ?? [],
1585
- tools: config.tools ?? {}
1407
+ // packages/text2sql/src/lib/adapters/sqlite.ts
1408
+ var SQL_ERROR_MAP = [
1409
+ {
1410
+ pattern: /^no such table: .+$/,
1411
+ type: "MISSING_TABLE",
1412
+ hint: "Check the database schema for the correct table name. The table you referenced does not exist."
1413
+ },
1414
+ {
1415
+ pattern: /^no such column: .+$/,
1416
+ type: "INVALID_COLUMN",
1417
+ hint: "Check the table schema for correct column names. The column may not exist or is ambiguous (exists in multiple joined tables)."
1418
+ },
1419
+ {
1420
+ pattern: /^ambiguous column name: .+$/,
1421
+ type: "INVALID_COLUMN",
1422
+ hint: "Check the table schema for correct column names. The column may not exist or is ambiguous (exists in multiple joined tables)."
1423
+ },
1424
+ {
1425
+ pattern: /^near ".+": syntax error$/,
1426
+ type: "SYNTAX_ERROR",
1427
+ hint: "There is a SQL syntax error. Review the query structure, keywords, and punctuation."
1428
+ },
1429
+ {
1430
+ pattern: /^no tables specified$/,
1431
+ type: "SYNTAX_ERROR",
1432
+ hint: "There is a SQL syntax error. Review the query structure, keywords, and punctuation."
1433
+ },
1434
+ {
1435
+ pattern: /^attempt to write a readonly database$/,
1436
+ type: "CONSTRAINT_ERROR",
1437
+ hint: "A database constraint was violated. This should not happen with read-only queries."
1438
+ }
1439
+ ];
1440
+ var LOW_CARDINALITY_LIMIT = 20;
1441
+ function formatError(sql, error) {
1442
+ const errorMessage = error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error occurred";
1443
+ const errorInfo = SQL_ERROR_MAP.find((it) => it.pattern.test(errorMessage));
1444
+ if (!errorInfo) {
1445
+ return {
1446
+ error: errorMessage,
1447
+ error_type: "UNKNOWN_ERROR",
1448
+ suggestion: "Review the query and try again",
1449
+ sql_attempted: sql
1586
1450
  };
1587
1451
  }
1588
- async #getSql(stream2) {
1589
- const chunks = await Array.fromAsync(
1590
- stream2
1591
- );
1592
- const sql = chunks.at(-1);
1593
- if (sql && sql.type === "data-text-delta") {
1594
- return sql.data.text;
1452
+ return {
1453
+ error: errorMessage,
1454
+ error_type: errorInfo.type,
1455
+ suggestion: errorInfo.hint,
1456
+ sql_attempted: sql
1457
+ };
1458
+ }
1459
+ var Sqlite = class extends Adapter {
1460
+ #options;
1461
+ #introspection = null;
1462
+ #info = null;
1463
+ constructor(options) {
1464
+ super();
1465
+ if (!options || typeof options.execute !== "function") {
1466
+ throw new Error("Sqlite adapter requires an execute function.");
1595
1467
  }
1596
- throw new Error("No SQL generated");
1468
+ this.#options = options;
1597
1469
  }
1598
- async explain(sql) {
1599
- const { experimental_output } = await generate2(
1600
- explainerAgent,
1601
- [user2("Explain this SQL.")],
1602
- { sql }
1470
+ async introspect(options) {
1471
+ const onProgress = options?.onProgress;
1472
+ if (this.#introspection) {
1473
+ return this.#introspection;
1474
+ }
1475
+ if (this.#options.introspect) {
1476
+ this.#introspection = await this.#options.introspect();
1477
+ return this.#introspection;
1478
+ }
1479
+ const allTables = await this.#loadTables();
1480
+ const allRelationships = await this.#loadRelationships(
1481
+ allTables.map((t) => t.name)
1603
1482
  );
1604
- return experimental_output.explanation;
1483
+ const { tables, relationships } = this.#applyTablesFilter(
1484
+ allTables,
1485
+ allRelationships
1486
+ );
1487
+ onProgress?.({
1488
+ phase: "tables",
1489
+ message: `Loaded ${tables.length} tables`,
1490
+ total: tables.length
1491
+ });
1492
+ onProgress?.({ phase: "row_counts", message: "Counting table rows..." });
1493
+ await this.#annotateRowCounts(tables, onProgress);
1494
+ onProgress?.({
1495
+ phase: "column_stats",
1496
+ message: "Collecting column statistics..."
1497
+ });
1498
+ await this.#annotateColumnStats(tables, onProgress);
1499
+ onProgress?.({ phase: "indexes", message: "Loading index information..." });
1500
+ await this.#annotateIndexes(tables, onProgress);
1501
+ onProgress?.({
1502
+ phase: "low_cardinality",
1503
+ message: "Identifying low cardinality columns..."
1504
+ });
1505
+ await this.#annotateLowCardinalityColumns(tables, onProgress);
1506
+ onProgress?.({
1507
+ phase: "relationships",
1508
+ message: "Loading foreign key relationships..."
1509
+ });
1510
+ this.#introspection = { tables, relationships };
1511
+ return this.#introspection;
1605
1512
  }
1606
- async toSql(input) {
1607
- const [introspection, adapterInfo] = await Promise.all([
1608
- this.#config.adapter.introspect(),
1609
- this.#config.adapter.info()
1610
- ]);
1611
- const context = await generateBrief(introspection, this.#config.cache);
1612
- return {
1613
- generate: async () => {
1614
- const { experimental_output: output } = await generate2(
1615
- text2sqlOnly,
1616
- [user2(input)],
1617
- {
1618
- adapterInfo: this.#config.adapter.formatInfo(adapterInfo),
1619
- context,
1620
- introspection,
1621
- teachings: toInstructions(...this.#config.instructions)
1622
- }
1623
- );
1624
- return output.sql;
1625
- }
1626
- };
1513
+ async execute(sql) {
1514
+ return this.#options.execute(sql);
1627
1515
  }
1628
- async inspect() {
1629
- const [introspection, adapterInfo] = await Promise.all([
1630
- this.#config.adapter.introspect(),
1631
- this.#config.adapter.info()
1632
- ]);
1633
- const context = await generateBrief(introspection, this.#config.cache);
1634
- return text2sqlOnly.instructions({
1635
- adapterInfo: this.#config.adapter.formatInfo(adapterInfo),
1636
- context,
1637
- introspection,
1638
- teachings: toInstructions(...this.#config.instructions)
1516
+ async validate(sql) {
1517
+ const validator = this.#options.validate ?? (async (text) => {
1518
+ await this.#options.execute(`EXPLAIN ${text}`);
1639
1519
  });
1520
+ try {
1521
+ return await validator(sql);
1522
+ } catch (error) {
1523
+ return JSON.stringify(formatError(sql, error));
1524
+ }
1640
1525
  }
1641
- instruct(...dataset) {
1642
- this.#config.instructions.push(...dataset);
1526
+ async info() {
1527
+ if (this.#info) {
1528
+ return this.#info;
1529
+ }
1530
+ this.#info = await this.#resolveInfo();
1531
+ return this.#info;
1532
+ }
1533
+ formatInfo(info) {
1534
+ const lines = [`Dialect: ${info.dialect ?? "unknown"}`];
1535
+ if (info.version) {
1536
+ lines.push(`Version: ${info.version}`);
1537
+ }
1538
+ if (info.database) {
1539
+ lines.push(`Database: ${info.database}`);
1540
+ }
1541
+ if (info.host) {
1542
+ lines.push(`Host: ${info.host}`);
1543
+ }
1544
+ if (info.details && Object.keys(info.details).length) {
1545
+ lines.push(`Details: ${JSON.stringify(info.details)}`);
1546
+ }
1547
+ return lines.join("\n");
1643
1548
  }
1644
- async teach(input) {
1645
- const [introspection, adapterInfo] = await Promise.all([
1646
- this.#config.adapter.introspect(),
1647
- this.#config.adapter.info()
1648
- ]);
1649
- const context = await generateBrief(introspection, this.#config.cache);
1650
- const { experimental_output } = await generate2(
1651
- teachablesAuthorAgent,
1652
- [user2(input)],
1653
- {
1654
- introspection,
1655
- adapterInfo: this.#config.adapter.formatInfo(adapterInfo),
1656
- context
1657
- }
1549
+ async getTables() {
1550
+ const allTables = await this.#loadTables();
1551
+ const allRelationships = await this.#loadRelationships(
1552
+ allTables.map((t) => t.name)
1658
1553
  );
1659
- const teachables = toTeachables(experimental_output.teachables);
1660
- this.#config.instructions.push(...teachables);
1661
- return {
1662
- teachables,
1663
- teachings: toInstructions(...this.#config.instructions)
1664
- };
1554
+ return this.#applyTablesFilter(allTables, allRelationships).tables;
1665
1555
  }
1666
- async tag(input) {
1667
- const [introspection, adapterInfo] = await Promise.all([
1668
- this.#config.adapter.introspect(),
1669
- this.#config.adapter.info()
1670
- ]);
1671
- const pipeline = pipe(
1672
- {
1673
- input,
1674
- adapter: this.#config.adapter,
1675
- cache: this.#config.cache,
1676
- introspection,
1677
- adapterInfo: this.#config.adapter.formatInfo(adapterInfo),
1678
- messages: [user2(input)],
1679
- renderingTools: this.#config.tools || {},
1680
- teachings: toInstructions(...this.#config.instructions)
1681
- },
1682
- toBrief(),
1683
- async (state, update) => {
1684
- const { experimental_output: output } = await generate2(
1685
- text2sqlOnly,
1686
- state.messages,
1687
- state
1556
+ async #loadTables() {
1557
+ const rows = await this.#runQuery(
1558
+ `SELECT name FROM sqlite_master WHERE type='table' ORDER BY name`
1559
+ );
1560
+ const tableNames = rows.map((row) => row.name).filter(
1561
+ (name) => typeof name === "string" && !name.startsWith("sqlite_")
1562
+ );
1563
+ const tables = await Promise.all(
1564
+ tableNames.map(async (tableName) => {
1565
+ const columns = await this.#runQuery(
1566
+ `PRAGMA table_info(${this.#quoteIdentifier(tableName)})`
1688
1567
  );
1689
- update({
1690
- messages: [
1691
- user2(
1692
- dedent6`
1693
- Based on the data provided, please explain in clear, conversational language what insights this reveals.
1694
-
1695
- <user_question>${state.input}</user_question>
1696
- <data>${JSON.stringify(this.#config.adapter.execute(output.sql))}</data>
1697
- `
1698
- )
1699
- ]
1700
- });
1701
- return output.sql;
1702
- },
1703
- synthesizerAgent
1568
+ return {
1569
+ name: tableName,
1570
+ rawName: tableName,
1571
+ columns: columns.map((col) => ({
1572
+ name: col.name ?? "unknown",
1573
+ type: col.type ?? "unknown",
1574
+ isPrimaryKey: (col.pk ?? 0) > 0
1575
+ }))
1576
+ };
1577
+ })
1704
1578
  );
1705
- const stream2 = pipeline();
1706
- return {
1707
- generate: async () => {
1708
- const sql = await this.#getSql(stream2);
1709
- return sql;
1710
- },
1711
- stream: () => {
1712
- return stream2;
1713
- }
1714
- };
1579
+ return tables;
1715
1580
  }
1716
- async suggest() {
1717
- const [introspection, adapterInfo] = await Promise.all([
1718
- this.#config.adapter.introspect(),
1719
- this.#config.adapter.info()
1720
- ]);
1721
- const context = await generateBrief(introspection, this.#config.cache);
1722
- const { experimental_output: output } = await generate2(
1723
- suggestionsAgent,
1724
- [
1725
- user2(
1726
- "Suggest high-impact business questions and matching SQL queries for this database."
1727
- )
1728
- ],
1729
- {
1730
- introspection,
1731
- adapterInfo: this.#config.adapter.formatInfo(adapterInfo),
1732
- context
1733
- }
1581
+ async getRelationships() {
1582
+ const allTables = await this.#loadTables();
1583
+ const allRelationships = await this.#loadRelationships(
1584
+ allTables.map((t) => t.name)
1734
1585
  );
1735
- return output.suggestions;
1586
+ return this.#applyTablesFilter(allTables, allRelationships).relationships;
1736
1587
  }
1737
- async single(input) {
1738
- const [introspection, adapterInfo] = await Promise.all([
1739
- this.#config.adapter.introspect(),
1740
- this.#config.adapter.info()
1741
- ]);
1742
- return stream(
1743
- text2sqlMonolith.clone({
1744
- tools: {
1745
- ...text2sqlMonolith.handoff.tools,
1746
- ...this.#config.tools
1588
+ async #loadRelationships(tableNames) {
1589
+ const names = tableNames ?? (await this.#loadTables()).map((table) => table.name);
1590
+ const relationshipGroups = await Promise.all(
1591
+ names.map(async (tableName) => {
1592
+ const rows = await this.#runQuery(
1593
+ `PRAGMA foreign_key_list(${this.#quoteIdentifier(tableName)})`
1594
+ );
1595
+ const groups = /* @__PURE__ */ new Map();
1596
+ for (const row of rows) {
1597
+ if (row.id == null || row.table == null || row.from == null || row.to == null) {
1598
+ continue;
1599
+ }
1600
+ const id = Number(row.id);
1601
+ const existing = groups.get(id);
1602
+ if (!existing) {
1603
+ groups.set(id, {
1604
+ table: tableName,
1605
+ from: [String(row.from)],
1606
+ referenced_table: String(row.table),
1607
+ to: [String(row.to)]
1608
+ });
1609
+ } else {
1610
+ existing.from.push(String(row.from));
1611
+ existing.to.push(String(row.to));
1612
+ }
1747
1613
  }
1748
- }),
1749
- [user2(input)],
1750
- {
1751
- teachings: toInstructions(...this.#config.instructions),
1752
- adapter: this.#config.adapter,
1753
- introspection,
1754
- adapterInfo: this.#config.adapter.formatInfo(adapterInfo),
1755
- context: await generateBrief(introspection, this.#config.cache),
1756
- renderingTools: this.#config.tools || {}
1757
- }
1614
+ return Array.from(groups.values());
1615
+ })
1758
1616
  );
1617
+ return relationshipGroups.flat();
1759
1618
  }
1760
- async chat(messages, params, model) {
1761
- const [introspection, adapterInfo] = await Promise.all([
1762
- this.#config.adapter.introspect(),
1763
- this.#config.adapter.info()
1764
- ]);
1765
- const chat = await this.#config.history.upsertChat({
1766
- id: params.chatId,
1767
- userId: params.userId,
1768
- title: "Chat " + params.chatId
1769
- });
1770
- const userProfileStore = new UserProfileStore(params.userId);
1771
- const userProfileXml = await userProfileStore.toXml();
1772
- const result = stream(
1773
- text2sqlMonolith.clone({
1774
- model,
1775
- tools: {
1776
- ...text2sqlMonolith.handoff.tools,
1777
- ...this.#config.tools,
1778
- update_user_profile: tool3({
1779
- description: `Update the user's profile with new facts, preferences, or present context.
1780
- Use this when the user explicitly states a preference (e.g., "I like dark mode", "Call me Ezz")
1781
- or when their working context changes (e.g., "I'm working on a hackathon").`,
1782
- inputSchema: z6.object({
1783
- type: z6.enum(["fact", "preference", "present"]).describe("The type of information to update."),
1784
- text: z6.string().describe(
1785
- "The content of the fact, preference, or present context."
1786
- ),
1787
- action: z6.enum(["add", "remove"]).default("add").describe("Whether to add or remove the item.")
1788
- }),
1789
- execute: async ({ type, text, action }) => {
1790
- if (action === "remove") {
1791
- await userProfileStore.remove(type, text);
1792
- return `Removed ${type}: ${text}`;
1619
+ async #annotateRowCounts(tables, onProgress) {
1620
+ const total = tables.length;
1621
+ for (let i = 0; i < tables.length; i++) {
1622
+ const table = tables[i];
1623
+ const tableIdentifier = this.#formatTableIdentifier(table);
1624
+ onProgress?.({
1625
+ phase: "row_counts",
1626
+ message: `Counting rows in ${table.name}...`,
1627
+ current: i + 1,
1628
+ total
1629
+ });
1630
+ try {
1631
+ const rows = await this.#runQuery(
1632
+ `SELECT COUNT(*) as count FROM ${tableIdentifier}`
1633
+ );
1634
+ const rowCount = this.#toNumber(rows[0]?.count);
1635
+ if (rowCount != null) {
1636
+ table.rowCount = rowCount;
1637
+ table.sizeHint = this.#classifyRowCount(rowCount);
1638
+ }
1639
+ } catch {
1640
+ continue;
1641
+ }
1642
+ }
1643
+ }
1644
+ async #annotateColumnStats(tables, onProgress) {
1645
+ const total = tables.length;
1646
+ for (let i = 0; i < tables.length; i++) {
1647
+ const table = tables[i];
1648
+ const tableIdentifier = this.#formatTableIdentifier(table);
1649
+ onProgress?.({
1650
+ phase: "column_stats",
1651
+ message: `Collecting stats for ${table.name}...`,
1652
+ current: i + 1,
1653
+ total
1654
+ });
1655
+ for (const column of table.columns) {
1656
+ if (!this.#shouldCollectStats(column.type)) {
1657
+ continue;
1658
+ }
1659
+ const columnIdentifier = this.#quoteSqlIdentifier(column.name);
1660
+ const sql = `
1661
+ SELECT
1662
+ MIN(${columnIdentifier}) AS min_value,
1663
+ MAX(${columnIdentifier}) AS max_value,
1664
+ AVG(CASE WHEN ${columnIdentifier} IS NULL THEN 1.0 ELSE 0.0 END) AS null_fraction
1665
+ FROM ${tableIdentifier}
1666
+ `;
1667
+ try {
1668
+ const rows = await this.#runQuery(sql);
1669
+ if (!rows.length) {
1670
+ continue;
1671
+ }
1672
+ const min = this.#normalizeValue(rows[0]?.min_value);
1673
+ const max = this.#normalizeValue(rows[0]?.max_value);
1674
+ const nullFraction = this.#toNumber(rows[0]?.null_fraction);
1675
+ if (min != null || max != null || nullFraction != null) {
1676
+ column.stats = {
1677
+ min: min ?? void 0,
1678
+ max: max ?? void 0,
1679
+ nullFraction: nullFraction != null && Number.isFinite(nullFraction) ? Math.max(0, Math.min(1, nullFraction)) : void 0
1680
+ };
1681
+ }
1682
+ } catch {
1683
+ continue;
1684
+ }
1685
+ }
1686
+ }
1687
+ }
1688
+ async #annotateIndexes(tables, onProgress) {
1689
+ const total = tables.length;
1690
+ for (let i = 0; i < tables.length; i++) {
1691
+ const table = tables[i];
1692
+ const tableIdentifier = this.#quoteIdentifier(
1693
+ table.rawName ?? table.name
1694
+ );
1695
+ onProgress?.({
1696
+ phase: "indexes",
1697
+ message: `Loading indexes for ${table.name}...`,
1698
+ current: i + 1,
1699
+ total
1700
+ });
1701
+ let indexes = [];
1702
+ try {
1703
+ const indexList = await this.#runQuery(
1704
+ `PRAGMA index_list(${tableIdentifier})`
1705
+ );
1706
+ indexes = await Promise.all(
1707
+ indexList.filter((index) => index.name).map(async (index) => {
1708
+ const indexName = String(index.name);
1709
+ const indexInfo = await this.#runQuery(
1710
+ `PRAGMA index_info('${indexName.replace(/'/g, "''")}')`
1711
+ );
1712
+ const columns = indexInfo.map((col) => col.name).filter((name) => Boolean(name));
1713
+ for (const columnName of columns) {
1714
+ const column = table.columns.find(
1715
+ (col) => col.name === columnName
1716
+ );
1717
+ if (column) {
1718
+ column.isIndexed = true;
1793
1719
  }
1794
- await userProfileStore.add(type, text);
1795
- return `Added ${type}: ${text}`;
1796
1720
  }
1721
+ return {
1722
+ name: indexName,
1723
+ columns,
1724
+ unique: index.unique === 1,
1725
+ primary: index.origin === "pk",
1726
+ type: index.origin ?? void 0
1727
+ };
1797
1728
  })
1798
- }
1799
- }),
1800
- [...chat.messages.map((it) => it.content), ...messages],
1801
- {
1802
- teachings: toInstructions(...this.#config.instructions),
1803
- adapter: this.#config.adapter,
1804
- renderingTools: this.#config.tools || {},
1805
- introspection,
1806
- adapterInfo: this.#config.adapter.formatInfo(adapterInfo),
1807
- context: await generateBrief(introspection, this.#config.cache),
1808
- userProfile: userProfileXml
1729
+ );
1730
+ } catch {
1731
+ indexes = [];
1809
1732
  }
1810
- );
1811
- return result.toUIMessageStream({
1812
- onError: (error) => {
1813
- if (NoSuchToolError.isInstance(error)) {
1814
- return "The model tried to call a unknown tool.";
1815
- } else if (InvalidToolInputError.isInstance(error)) {
1816
- return "The model called a tool with invalid arguments.";
1817
- } else if (ToolCallRepairError.isInstance(error)) {
1818
- return "The model tried to call a tool with invalid arguments, but it was repaired.";
1819
- } else {
1820
- return "An unknown error occurred.";
1733
+ if (indexes.length) {
1734
+ table.indexes = indexes;
1735
+ }
1736
+ }
1737
+ }
1738
+ async #annotateLowCardinalityColumns(tables, onProgress) {
1739
+ const total = tables.length;
1740
+ for (let i = 0; i < tables.length; i++) {
1741
+ const table = tables[i];
1742
+ const tableIdentifier = this.#formatTableIdentifier(table);
1743
+ onProgress?.({
1744
+ phase: "low_cardinality",
1745
+ message: `Analyzing cardinality in ${table.name}...`,
1746
+ current: i + 1,
1747
+ total
1748
+ });
1749
+ for (const column of table.columns) {
1750
+ const columnIdentifier = this.#quoteSqlIdentifier(column.name);
1751
+ const limit = LOW_CARDINALITY_LIMIT + 1;
1752
+ const sql = `
1753
+ SELECT DISTINCT ${columnIdentifier} AS value
1754
+ FROM ${tableIdentifier}
1755
+ WHERE ${columnIdentifier} IS NOT NULL
1756
+ LIMIT ${limit}
1757
+ `;
1758
+ let rows = [];
1759
+ try {
1760
+ rows = await this.#runQuery(sql);
1761
+ } catch {
1762
+ continue;
1821
1763
  }
1822
- },
1823
- sendStart: true,
1824
- sendFinish: true,
1825
- sendReasoning: true,
1826
- sendSources: true,
1827
- originalMessages: messages,
1828
- onFinish: async ({ messages: messages2 }) => {
1829
- const userMessage = messages2.at(-2);
1830
- const botMessage = messages2.at(-1);
1831
- if (!userMessage || !botMessage) {
1832
- throw new Error("Not implemented yet");
1764
+ if (!rows.length || rows.length > LOW_CARDINALITY_LIMIT) {
1765
+ continue;
1833
1766
  }
1834
- await this.#config.history.addMessage({
1835
- id: v7(),
1836
- chatId: params.chatId,
1837
- role: userMessage.role,
1838
- content: userMessage
1839
- });
1840
- await this.#config.history.addMessage({
1841
- id: v7(),
1842
- chatId: params.chatId,
1843
- role: botMessage.role,
1844
- content: botMessage
1845
- });
1767
+ const values = [];
1768
+ let shouldSkip = false;
1769
+ for (const row of rows) {
1770
+ const formatted = this.#normalizeValue(row.value);
1771
+ if (formatted == null) {
1772
+ shouldSkip = true;
1773
+ break;
1774
+ }
1775
+ values.push(formatted);
1776
+ }
1777
+ if (shouldSkip || !values.length) {
1778
+ continue;
1779
+ }
1780
+ column.kind = "LowCardinality";
1781
+ column.values = values;
1846
1782
  }
1847
- });
1783
+ }
1784
+ }
1785
+ #quoteIdentifier(name) {
1786
+ return `'${name.replace(/'/g, "''")}'`;
1787
+ }
1788
+ #quoteSqlIdentifier(identifier) {
1789
+ return `"${identifier.replace(/"/g, '""')}"`;
1790
+ }
1791
+ #applyTablesFilter(tables, relationships) {
1792
+ return applyTablesFilter(tables, relationships, this.#options.tables);
1793
+ }
1794
+ #formatTableIdentifier(table) {
1795
+ const name = table.rawName ?? table.name;
1796
+ if (table.schema) {
1797
+ return `${this.#quoteSqlIdentifier(table.schema)}.${this.#quoteSqlIdentifier(name)}`;
1798
+ }
1799
+ return this.#quoteSqlIdentifier(name);
1800
+ }
1801
+ #toNumber(value) {
1802
+ if (typeof value === "number" && Number.isFinite(value)) {
1803
+ return value;
1804
+ }
1805
+ if (typeof value === "bigint") {
1806
+ return Number(value);
1807
+ }
1808
+ if (typeof value === "string" && value.trim() !== "") {
1809
+ const parsed = Number(value);
1810
+ return Number.isFinite(parsed) ? parsed : null;
1811
+ }
1812
+ return null;
1813
+ }
1814
+ #classifyRowCount(count) {
1815
+ if (count < 100) {
1816
+ return "tiny";
1817
+ }
1818
+ if (count < 1e3) {
1819
+ return "small";
1820
+ }
1821
+ if (count < 1e4) {
1822
+ return "medium";
1823
+ }
1824
+ if (count < 1e5) {
1825
+ return "large";
1826
+ }
1827
+ return "huge";
1828
+ }
1829
+ #shouldCollectStats(type) {
1830
+ if (!type) {
1831
+ return false;
1832
+ }
1833
+ const normalized = type.toLowerCase();
1834
+ return /int|real|numeric|double|float|decimal|date|time|bool/.test(
1835
+ normalized
1836
+ );
1837
+ }
1838
+ #normalizeValue(value) {
1839
+ if (value === null || value === void 0) {
1840
+ return null;
1841
+ }
1842
+ if (typeof value === "string") {
1843
+ return value;
1844
+ }
1845
+ if (typeof value === "number" || typeof value === "bigint") {
1846
+ return String(value);
1847
+ }
1848
+ if (typeof value === "boolean") {
1849
+ return value ? "true" : "false";
1850
+ }
1851
+ if (value instanceof Date) {
1852
+ return value.toISOString();
1853
+ }
1854
+ if (typeof Buffer !== "undefined" && Buffer.isBuffer(value)) {
1855
+ return value.toString("utf-8");
1856
+ }
1857
+ return null;
1858
+ }
1859
+ async #runQuery(sql) {
1860
+ const result = await this.#options.execute(sql);
1861
+ if (Array.isArray(result)) {
1862
+ return result;
1863
+ }
1864
+ if (result && typeof result === "object" && "rows" in result && Array.isArray(result.rows)) {
1865
+ return result.rows;
1866
+ }
1867
+ throw new Error(
1868
+ "Sqlite adapter execute() must return an array of rows or an object with a rows array when introspecting."
1869
+ );
1870
+ }
1871
+ async #resolveInfo() {
1872
+ const { info } = this.#options;
1873
+ if (!info) {
1874
+ return { dialect: "sqlite" };
1875
+ }
1876
+ if (typeof info === "function") {
1877
+ return info();
1878
+ }
1879
+ return info;
1848
1880
  }
1849
1881
  };
1850
- if (import.meta.main) {
1851
- }
1852
1882
 
1853
1883
  // packages/text2sql/src/lib/adapters/postgres.ts
1854
1884
  var POSTGRES_ERROR_MAP = {
@@ -3070,14 +3100,15 @@ var InMemoryHistory = class extends SqliteHistory {
3070
3100
  };
3071
3101
  export {
3072
3102
  Adapter,
3073
- BriefCache,
3074
3103
  History,
3075
3104
  InMemoryHistory,
3105
+ JsonCache,
3076
3106
  Postgres,
3077
3107
  SqlServer,
3078
3108
  Sqlite,
3079
3109
  SqliteHistory,
3080
3110
  Text2Sql,
3111
+ TmpCache,
3081
3112
  analogy,
3082
3113
  applyTablesFilter,
3083
3114
  clarification,