@deepagents/text2sql 0.12.0 → 0.12.1

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
@@ -603,7 +603,7 @@ function message(content) {
603
603
  } : content;
604
604
  return {
605
605
  id: message2.id,
606
- name: "message",
606
+ name: message2.role,
607
607
  data: "content",
608
608
  type: "message",
609
609
  persist: true,
@@ -625,6 +625,22 @@ function assistantText(content, options) {
625
625
  parts: [{ type: "text", text: content }]
626
626
  });
627
627
  }
628
+ var LAZY_ID = Symbol("lazy-id");
629
+ function isLazyFragment(fragment2) {
630
+ return LAZY_ID in fragment2;
631
+ }
632
+ function lastAssistantMessage(content) {
633
+ return {
634
+ name: "assistant",
635
+ type: "message",
636
+ persist: true,
637
+ data: "content",
638
+ [LAZY_ID]: {
639
+ type: "last-assistant",
640
+ content
641
+ }
642
+ };
643
+ }
628
644
  var ContextRenderer = class {
629
645
  options;
630
646
  constructor(options = {}) {
@@ -1121,6 +1137,12 @@ var ContextEngine = class {
1121
1137
  if (this.#pendingMessages.length === 0) {
1122
1138
  return;
1123
1139
  }
1140
+ for (let i = 0; i < this.#pendingMessages.length; i++) {
1141
+ const fragment2 = this.#pendingMessages[i];
1142
+ if (isLazyFragment(fragment2)) {
1143
+ this.#pendingMessages[i] = await this.#resolveLazyFragment(fragment2);
1144
+ }
1145
+ }
1124
1146
  let parentId = this.#branch.headMessageId;
1125
1147
  const now = Date.now();
1126
1148
  for (const fragment2 of this.#pendingMessages) {
@@ -1140,6 +1162,39 @@ var ContextEngine = class {
1140
1162
  this.#branch.headMessageId = parentId;
1141
1163
  this.#pendingMessages = [];
1142
1164
  }
1165
+ /**
1166
+ * Resolve a lazy fragment by finding the appropriate ID.
1167
+ */
1168
+ async #resolveLazyFragment(fragment2) {
1169
+ const lazy = fragment2[LAZY_ID];
1170
+ if (lazy.type === "last-assistant") {
1171
+ const lastId = await this.#getLastAssistantId();
1172
+ return assistantText(lazy.content, { id: lastId ?? crypto.randomUUID() });
1173
+ }
1174
+ throw new Error(`Unknown lazy fragment type: ${lazy.type}`);
1175
+ }
1176
+ /**
1177
+ * Find the most recent assistant message ID (pending or persisted).
1178
+ */
1179
+ async #getLastAssistantId() {
1180
+ for (let i = this.#pendingMessages.length - 1; i >= 0; i--) {
1181
+ const msg = this.#pendingMessages[i];
1182
+ if (msg.name === "assistant" && !isLazyFragment(msg)) {
1183
+ return msg.id;
1184
+ }
1185
+ }
1186
+ if (this.#branch?.headMessageId) {
1187
+ const chain = await this.#store.getMessageChain(
1188
+ this.#branch.headMessageId
1189
+ );
1190
+ for (let i = chain.length - 1; i >= 0; i--) {
1191
+ if (chain[i].name === "assistant") {
1192
+ return chain[i].id;
1193
+ }
1194
+ }
1195
+ }
1196
+ return void 0;
1197
+ }
1143
1198
  /**
1144
1199
  * Estimate token count and cost for the full context.
1145
1200
  *
@@ -1419,6 +1474,35 @@ var ContextEngine = class {
1419
1474
  consolidate() {
1420
1475
  return void 0;
1421
1476
  }
1477
+ /**
1478
+ * Extract skill path mappings from available_skills fragments.
1479
+ * Returns array of { host, sandbox } for mounting in sandbox filesystem.
1480
+ *
1481
+ * Reads the original `paths` configuration stored in fragment metadata
1482
+ * by the skills() fragment helper.
1483
+ *
1484
+ * @example
1485
+ * ```ts
1486
+ * const context = new ContextEngine({ store, chatId, userId })
1487
+ * .set(skills({ paths: [{ host: './skills', sandbox: '/skills' }] }));
1488
+ *
1489
+ * const mounts = context.getSkillMounts();
1490
+ * // [{ host: './skills', sandbox: '/skills' }]
1491
+ * ```
1492
+ */
1493
+ getSkillMounts() {
1494
+ const mounts = [];
1495
+ for (const fragment2 of this.#fragments) {
1496
+ if (fragment2.name === "available_skills" && fragment2.metadata && Array.isArray(fragment2.metadata.paths)) {
1497
+ for (const mapping of fragment2.metadata.paths) {
1498
+ if (typeof mapping === "object" && mapping !== null && typeof mapping.host === "string" && typeof mapping.sandbox === "string") {
1499
+ mounts.push({ host: mapping.host, sandbox: mapping.sandbox });
1500
+ }
1501
+ }
1502
+ }
1503
+ }
1504
+ return mounts;
1505
+ }
1422
1506
  /**
1423
1507
  * Inspect the full context state for debugging.
1424
1508
  * Returns a JSON-serializable object with context information.
@@ -1660,7 +1744,7 @@ var errorRecoveryGuardrail = {
1660
1744
  }
1661
1745
  return logAndFail(
1662
1746
  "Unknown error",
1663
- `An error occurred: ${errorText.slice(0, 100)}. Let me try a different approach.`
1747
+ `An error occurred: ${errorText}. Let me try a different approach.`
1664
1748
  );
1665
1749
  }
1666
1750
  };
@@ -1908,18 +1992,19 @@ var SqliteContextStore = class extends ContextStore {
1908
1992
  // Message Operations (Graph Nodes)
1909
1993
  // ==========================================================================
1910
1994
  async addMessage(message2) {
1995
+ const existingParent = message2.parentId === message2.id ? this.#db.prepare("SELECT parentId FROM messages WHERE id = ?").get(message2.id) : void 0;
1996
+ const parentId = message2.parentId === message2.id ? existingParent?.parentId ?? null : message2.parentId;
1911
1997
  this.#db.prepare(
1912
1998
  `INSERT INTO messages (id, chatId, parentId, name, type, data, createdAt)
1913
1999
  VALUES (?, ?, ?, ?, ?, ?, ?)
1914
2000
  ON CONFLICT(id) DO UPDATE SET
1915
- parentId = excluded.parentId,
1916
2001
  name = excluded.name,
1917
2002
  type = excluded.type,
1918
2003
  data = excluded.data`
1919
2004
  ).run(
1920
2005
  message2.id,
1921
2006
  message2.chatId,
1922
- message2.parentId,
2007
+ parentId,
1923
2008
  message2.name,
1924
2009
  message2.type ?? null,
1925
2010
  JSON.stringify(message2.data),
@@ -2387,13 +2472,9 @@ var Agent = class _Agent {
2387
2472
  writer.write({ type: "finish" });
2388
2473
  return;
2389
2474
  }
2390
- writer.write({
2391
- type: "text-delta",
2392
- id: generateId2(),
2393
- delta: ` ${failureFeedback}`
2394
- });
2475
+ writeText(writer, failureFeedback);
2395
2476
  const selfCorrectionText = accumulatedText + " " + failureFeedback;
2396
- context.set(assistantText(selfCorrectionText));
2477
+ context.set(lastAssistantMessage(selfCorrectionText));
2397
2478
  await context.save();
2398
2479
  currentResult = await this.#createRawStream(
2399
2480
  contextVariables,
@@ -2498,6 +2579,22 @@ var repairToolCall = async ({
2498
2579
  });
2499
2580
  return { ...toolCall, input: JSON.stringify(output) };
2500
2581
  };
2582
+ function writeText(writer, text) {
2583
+ const feedbackPartId = generateId2();
2584
+ writer.write({
2585
+ id: feedbackPartId,
2586
+ type: "text-start"
2587
+ });
2588
+ writer.write({
2589
+ id: feedbackPartId,
2590
+ type: "text-delta",
2591
+ delta: ` ${text}`
2592
+ });
2593
+ writer.write({
2594
+ id: feedbackPartId,
2595
+ type: "text-end"
2596
+ });
2597
+ }
2501
2598
 
2502
2599
  // packages/text2sql/src/lib/agents/explainer.agent.ts
2503
2600
  import { groq as groq2 } from "@ai-sdk/groq";
@@ -2606,6 +2703,7 @@ import { createBashTool as createBashTool2 } from "bash-tool";
2606
2703
  import chalk3 from "chalk";
2607
2704
  import {
2608
2705
  Bash,
2706
+ InMemoryFs,
2609
2707
  MountableFs,
2610
2708
  OverlayFs,
2611
2709
  ReadWriteFs,
@@ -2613,6 +2711,7 @@ import {
2613
2711
  } from "just-bash";
2614
2712
  import * as fs from "node:fs/promises";
2615
2713
  import * as path from "node:path";
2714
+ import { v7 } from "uuid";
2616
2715
  function createCommand(name, subcommands) {
2617
2716
  const usageLines = Object.entries(subcommands).map(([, def]) => ` ${name} ${def.usage.padEnd(30)} ${def.description}`).join("\n");
2618
2717
  return defineCommand2(name, async (args, ctx) => {
@@ -2663,7 +2762,7 @@ function createSqlCommand(adapter) {
2663
2762
  try {
2664
2763
  const rows = await adapter.execute(query);
2665
2764
  const rowsArray = Array.isArray(rows) ? rows : [];
2666
- const filePath = `/results/${crypto.randomUUID()}.json`;
2765
+ const filePath = `/results/${v7()}.json`;
2667
2766
  await ctx.fs.writeFile(filePath, JSON.stringify(rowsArray, null, 2));
2668
2767
  const columns = rowsArray.length > 0 ? Object.keys(rowsArray[0]) : [];
2669
2768
  return {
@@ -2714,14 +2813,24 @@ function createSqlCommand(adapter) {
2714
2813
  });
2715
2814
  }
2716
2815
  async function createResultTools(options) {
2717
- const { adapter, chatId, messageId } = options;
2816
+ const { adapter, chatId, messageId, skillMounts = [] } = options;
2718
2817
  const sqlCommand = createSqlCommand(adapter);
2719
- const chatDir = path.join(process.cwd(), "artifacts", chatId);
2818
+ const root = process.env.TEXT2SQL_FS_ROOT || process.cwd();
2819
+ const chatDir = path.join(root, "artifacts", chatId);
2720
2820
  const resultsDir = path.join(chatDir, messageId, "results");
2721
2821
  await fs.mkdir(resultsDir, { recursive: true });
2822
+ const fsMounts = skillMounts.map(({ host, sandbox: sandbox2 }) => ({
2823
+ mountPoint: sandbox2,
2824
+ filesystem: new OverlayFs({
2825
+ root: host,
2826
+ mountPoint: "/",
2827
+ readOnly: true
2828
+ })
2829
+ }));
2722
2830
  const filesystem = new MountableFs({
2723
- base: new OverlayFs({ root: process.cwd() }),
2831
+ base: new InMemoryFs(),
2724
2832
  mounts: [
2833
+ ...fsMounts,
2725
2834
  {
2726
2835
  mountPoint: "/results",
2727
2836
  filesystem: new ReadWriteFs({ root: resultsDir })
@@ -2739,10 +2848,6 @@ async function createResultTools(options) {
2739
2848
  const { bash, sandbox } = await createBashTool2({
2740
2849
  sandbox: bashInstance,
2741
2850
  destination: "/",
2742
- uploadDirectory: {
2743
- source: process.cwd(),
2744
- include: "packages/text2sql/src/skills/**/*.md"
2745
- },
2746
2851
  onBeforeBashCall: ({ command }) => {
2747
2852
  console.log(chalk3.cyan(`[onBeforeBashCall]: ${command}`));
2748
2853
  return { command };
@@ -2765,7 +2870,9 @@ import {
2765
2870
  NoContentGeneratedError,
2766
2871
  NoObjectGeneratedError,
2767
2872
  NoOutputGeneratedError,
2768
- TypeValidationError
2873
+ TypeValidationError,
2874
+ defaultSettingsMiddleware,
2875
+ wrapLanguageModel
2769
2876
  } from "ai";
2770
2877
  import { Console } from "node:console";
2771
2878
  import { createWriteStream } from "node:fs";
@@ -2777,6 +2884,7 @@ var logger = new Console({
2777
2884
  stderr: createWriteStream("./sql-agent-error.log", { flags: "a" }),
2778
2885
  inspectOptions: { depth: null }
2779
2886
  });
2887
+ var RETRY_TEMPERATURES = [0, 0.2, 0.3];
2780
2888
  function extractSql(output) {
2781
2889
  const match = output.match(/```sql\n?([\s\S]*?)```/);
2782
2890
  return match ? match[1].trim() : output.trim();
@@ -2830,8 +2938,14 @@ async function toSql(options) {
2830
2938
  } else {
2831
2939
  context.set(user(options.input));
2832
2940
  }
2941
+ const temperature = RETRY_TEMPERATURES[attemptNumber - 1] ?? RETRY_TEMPERATURES[RETRY_TEMPERATURES.length - 1];
2942
+ const baseModel = options.model ?? groq3("openai/gpt-oss-20b");
2943
+ const model = wrapLanguageModel({
2944
+ model: baseModel,
2945
+ middleware: defaultSettingsMiddleware({ settings: { temperature } })
2946
+ });
2833
2947
  const sqlOutput = structuredOutput({
2834
- model: options.model ?? groq3("openai/gpt-oss-20b"),
2948
+ model,
2835
2949
  context,
2836
2950
  schema: z3.union([
2837
2951
  z3.object({
@@ -3672,9 +3786,15 @@ var Text2Sql = class {
3672
3786
  "For large results, slice first: cat <path> | jq '.[:10]'"
3673
3787
  ]
3674
3788
  }),
3789
+ hint(
3790
+ `You cannot access sql through a tool, it'll fail so the proper way to access it is through the bash tool using "sql run" and "sql validate" commands.`
3791
+ ),
3675
3792
  hint(
3676
3793
  "The sql command outputs: file path, column names (comma-separated), and row count. Use column names to construct precise jq queries."
3677
3794
  ),
3795
+ hint(
3796
+ 'This is virtual bash environment and "sql" commands proxy to the database hence you cannot access sql files directly.'
3797
+ ),
3678
3798
  hint(
3679
3799
  "If a query fails, the sql command returns an error message in stderr."
3680
3800
  )
@@ -3683,14 +3803,16 @@ var Text2Sql = class {
3683
3803
  );
3684
3804
  const userMsg = messages.at(-1);
3685
3805
  if (userMsg) {
3686
- context.set(user(userMsg));
3806
+ context.set(message(userMsg));
3687
3807
  await context.save();
3688
3808
  }
3689
3809
  const messageId = userMsg?.id ?? generateId3();
3810
+ const skillMounts = context.getSkillMounts();
3690
3811
  const { bash } = await createResultTools({
3691
3812
  adapter: this.#config.adapter,
3692
3813
  chatId: params.chatId,
3693
- messageId
3814
+ messageId,
3815
+ skillMounts
3694
3816
  });
3695
3817
  const chatAgent = agent({
3696
3818
  name: "text2sql",
@@ -3710,6 +3832,7 @@ var Text2Sql = class {
3710
3832
  sendFinish: true,
3711
3833
  sendReasoning: true,
3712
3834
  sendSources: true,
3835
+ originalMessages: messages,
3713
3836
  generateMessageId: generateId3,
3714
3837
  onFinish: async ({ responseMessage }) => {
3715
3838
  context.set(assistant(responseMessage));
@@ -3734,7 +3857,7 @@ var Text2Sql = class {
3734
3857
  );
3735
3858
  const userMsg = messages.at(-1);
3736
3859
  if (userMsg) {
3737
- context.set(user(userMsg));
3860
+ context.set(message(userMsg));
3738
3861
  await context.save();
3739
3862
  }
3740
3863
  const developerAgent = agent({