@deepagents/text2sql 0.12.1 → 0.13.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
@@ -392,6 +392,7 @@ import z2 from "zod";
392
392
  import { toState } from "@deepagents/agent";
393
393
 
394
394
  // packages/context/dist/index.js
395
+ import { mergeWith } from "lodash-es";
395
396
  import { encode } from "gpt-tokenizer";
396
397
  import { generateId } from "ai";
397
398
  import pluralize from "pluralize";
@@ -967,6 +968,8 @@ var ContextEngine = class {
967
968
  #branch = null;
968
969
  #chatData = null;
969
970
  #initialized = false;
971
+ /** Initial metadata to merge on first initialization */
972
+ #initialMetadata;
970
973
  constructor(options) {
971
974
  if (!options.chatId) {
972
975
  throw new Error("chatId is required");
@@ -978,6 +981,7 @@ var ContextEngine = class {
978
981
  this.#chatId = options.chatId;
979
982
  this.#userId = options.userId;
980
983
  this.#branchName = "main";
984
+ this.#initialMetadata = options.metadata;
981
985
  }
982
986
  /**
983
987
  * Initialize the chat and branch if they don't exist.
@@ -990,6 +994,15 @@ var ContextEngine = class {
990
994
  id: this.#chatId,
991
995
  userId: this.#userId
992
996
  });
997
+ if (this.#initialMetadata) {
998
+ this.#chatData = await this.#store.updateChat(this.#chatId, {
999
+ metadata: {
1000
+ ...this.#chatData.metadata,
1001
+ ...this.#initialMetadata
1002
+ }
1003
+ });
1004
+ this.#initialMetadata = void 0;
1005
+ }
993
1006
  this.#branch = await this.#store.getActiveBranch(this.#chatId);
994
1007
  this.#initialized = true;
995
1008
  }
@@ -1463,6 +1476,36 @@ var ContextEngine = class {
1463
1476
  }
1464
1477
  this.#chatData = await this.#store.updateChat(this.#chatId, storeUpdates);
1465
1478
  }
1479
+ /**
1480
+ * Track token usage for the current chat.
1481
+ * Accumulates usage metrics in chat.metadata.usage.
1482
+ *
1483
+ * @param usage - Token usage from AI SDK (LanguageModelUsage)
1484
+ *
1485
+ * @example
1486
+ * ```ts
1487
+ * // In onFinish callback
1488
+ * const usage = await result.totalUsage;
1489
+ * await context.trackUsage(usage);
1490
+ * ```
1491
+ */
1492
+ async trackUsage(usage) {
1493
+ await this.#ensureInitialized();
1494
+ const freshChatData = await this.#store.getChat(this.#chatId);
1495
+ const currentUsage = freshChatData?.metadata?.usage ?? {};
1496
+ const updatedUsage = mergeWith(
1497
+ {},
1498
+ currentUsage,
1499
+ usage,
1500
+ (a, b) => typeof a === "number" || typeof b === "number" ? (a ?? 0) + (b ?? 0) : void 0
1501
+ );
1502
+ this.#chatData = await this.#store.updateChat(this.#chatId, {
1503
+ metadata: {
1504
+ ...freshChatData?.metadata,
1505
+ usage: updatedUsage
1506
+ }
1507
+ });
1508
+ }
1466
1509
  /**
1467
1510
  * Consolidate context fragments (no-op for now).
1468
1511
  *
@@ -1742,6 +1785,7 @@ var errorRecoveryGuardrail = {
1742
1785
  "My response format was invalid. Let me try again with a properly formatted response."
1743
1786
  );
1744
1787
  }
1788
+ console.dir({ part }, { depth: null });
1745
1789
  return logAndFail(
1746
1790
  "Unknown error",
1747
1791
  `An error occurred: ${errorText}. Let me try a different approach.`
@@ -1929,12 +1973,20 @@ var SqliteContextStore = class extends ContextStore {
1929
1973
  }
1930
1974
  async listChats(options) {
1931
1975
  const params = [];
1932
- let whereClause = "";
1976
+ const whereClauses = [];
1933
1977
  let limitClause = "";
1934
1978
  if (options?.userId) {
1935
- whereClause = "WHERE c.userId = ?";
1979
+ whereClauses.push("c.userId = ?");
1936
1980
  params.push(options.userId);
1937
1981
  }
1982
+ if (options?.metadata) {
1983
+ whereClauses.push(`json_extract(c.metadata, '$.' || ?) = ?`);
1984
+ params.push(options.metadata.key);
1985
+ params.push(
1986
+ typeof options.metadata.value === "boolean" ? options.metadata.value ? 1 : 0 : options.metadata.value
1987
+ );
1988
+ }
1989
+ const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : "";
1938
1990
  if (options?.limit !== void 0) {
1939
1991
  limitClause = " LIMIT ?";
1940
1992
  params.push(options.limit);
@@ -1948,6 +2000,7 @@ var SqliteContextStore = class extends ContextStore {
1948
2000
  c.id,
1949
2001
  c.userId,
1950
2002
  c.title,
2003
+ c.metadata,
1951
2004
  c.createdAt,
1952
2005
  c.updatedAt,
1953
2006
  COUNT(DISTINCT m.id) as messageCount,
@@ -1963,6 +2016,7 @@ var SqliteContextStore = class extends ContextStore {
1963
2016
  id: row.id,
1964
2017
  userId: row.userId,
1965
2018
  title: row.title ?? void 0,
2019
+ metadata: row.metadata ? JSON.parse(row.metadata) : void 0,
1966
2020
  messageCount: row.messageCount,
1967
2021
  branchCount: row.branchCount,
1968
2022
  createdAt: row.createdAt,
@@ -1992,11 +2046,17 @@ var SqliteContextStore = class extends ContextStore {
1992
2046
  // Message Operations (Graph Nodes)
1993
2047
  // ==========================================================================
1994
2048
  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;
1997
2049
  this.#db.prepare(
1998
2050
  `INSERT INTO messages (id, chatId, parentId, name, type, data, createdAt)
1999
- VALUES (?, ?, ?, ?, ?, ?, ?)
2051
+ VALUES (
2052
+ ?1,
2053
+ ?2,
2054
+ CASE WHEN ?3 = ?1 THEN (SELECT parentId FROM messages WHERE id = ?1) ELSE ?3 END,
2055
+ ?4,
2056
+ ?5,
2057
+ ?6,
2058
+ ?7
2059
+ )
2000
2060
  ON CONFLICT(id) DO UPDATE SET
2001
2061
  name = excluded.name,
2002
2062
  type = excluded.type,
@@ -2004,7 +2064,7 @@ var SqliteContextStore = class extends ContextStore {
2004
2064
  ).run(
2005
2065
  message2.id,
2006
2066
  message2.chatId,
2007
- parentId,
2067
+ message2.parentId,
2008
2068
  message2.name,
2009
2069
  message2.type ?? null,
2010
2070
  JSON.stringify(message2.data),
@@ -2565,10 +2625,10 @@ var repairToolCall = async ({
2565
2625
  if (NoSuchToolError.isInstance(error)) {
2566
2626
  return null;
2567
2627
  }
2568
- const tool3 = tools3[toolCall.toolName];
2628
+ const tool4 = tools3[toolCall.toolName];
2569
2629
  const { output } = await generateText({
2570
2630
  model: groq("openai/gpt-oss-20b"),
2571
- output: Output.object({ schema: tool3.inputSchema }),
2631
+ output: Output.object({ schema: tool4.inputSchema }),
2572
2632
  prompt: [
2573
2633
  `The model tried to call the tool "${toolCall.toolName}" with the following inputs:`,
2574
2634
  JSON.stringify(toolCall.input),
@@ -2699,6 +2759,7 @@ var fragments = [
2699
2759
  var developer_agent_default = { tools, fragments };
2700
2760
 
2701
2761
  // packages/text2sql/src/lib/agents/result-tools.ts
2762
+ import "ai";
2702
2763
  import { createBashTool as createBashTool2 } from "bash-tool";
2703
2764
  import chalk3 from "chalk";
2704
2765
  import {
@@ -2762,12 +2823,20 @@ function createSqlCommand(adapter) {
2762
2823
  try {
2763
2824
  const rows = await adapter.execute(query);
2764
2825
  const rowsArray = Array.isArray(rows) ? rows : [];
2765
- const filePath = `/results/${v7()}.json`;
2766
- await ctx.fs.writeFile(filePath, JSON.stringify(rowsArray, null, 2));
2826
+ const content = JSON.stringify(rowsArray, null, 2);
2827
+ const filename = `${v7()}.json`;
2828
+ const isolatedPath = `/results/${filename}`;
2829
+ const sharedPath = `/artifacts/${filename}`;
2830
+ await Promise.all([
2831
+ ctx.fs.writeFile(isolatedPath, content),
2832
+ // Current turn's isolated copy
2833
+ ctx.fs.writeFile(sharedPath, content)
2834
+ // Shared copy for cross-turn access
2835
+ ]);
2767
2836
  const columns = rowsArray.length > 0 ? Object.keys(rowsArray[0]) : [];
2768
2837
  return {
2769
2838
  stdout: [
2770
- filePath,
2839
+ `results stored in ${sharedPath}`,
2771
2840
  `columns: ${columns.join(", ") || "(none)"}`,
2772
2841
  `rows: ${rowsArray.length}`
2773
2842
  ].join("\n") + "\n",
@@ -3085,12 +3154,12 @@ var suggestionsAgent = agent2({
3085
3154
  });
3086
3155
 
3087
3156
  // packages/text2sql/src/lib/agents/text2sql.agent.ts
3088
- import { tool as tool2 } from "ai";
3157
+ import { tool as tool3 } from "ai";
3089
3158
  import z5 from "zod";
3090
3159
  import { toState as toState2 } from "@deepagents/agent";
3091
3160
  import { scratchpad_tool } from "@deepagents/toolbox";
3092
3161
  var tools2 = {
3093
- validate_query: tool2({
3162
+ validate_query: tool3({
3094
3163
  description: `Validate SQL query syntax before execution. Use this to check if your SQL is valid before running db_query. This helps catch errors early and allows you to correct the query if needed.`,
3095
3164
  inputSchema: z5.object({
3096
3165
  sql: z5.string().describe("The SQL query to validate.")
@@ -3104,7 +3173,7 @@ var tools2 = {
3104
3173
  return "Query is valid.";
3105
3174
  }
3106
3175
  }),
3107
- db_query: tool2({
3176
+ db_query: tool3({
3108
3177
  description: `Internal tool to fetch data from the store's database. Write a SQL query to retrieve the information needed to answer the user's question. The results will be returned as data that you can then present to the user in natural language.`,
3109
3178
  inputSchema: z5.object({
3110
3179
  reasoning: z5.string().describe(
@@ -3441,6 +3510,10 @@ function reasoningFramework() {
3441
3510
  principle({
3442
3511
  title: "Inhibit your response",
3443
3512
  description: "Only take an action after all the above reasoning is completed. Once you've taken an action, you cannot take it back."
3513
+ }),
3514
+ principle({
3515
+ title: "Continuous self-monitoring",
3516
+ description: "Constantly evaluate your own reasoning process for any gaps, biases, or errors. Apply the above principles iteratively as needed."
3444
3517
  })
3445
3518
  )
3446
3519
  ];
@@ -3654,7 +3727,31 @@ Action: Ask user: "Top by what metric\u2014total revenue, number of orders, or m
3654
3727
  "Maintain consistency with previous query structure when possible."
3655
3728
  ],
3656
3729
  notes: "If reference is ambiguous, ask which previous result or entity the user means."
3657
- })
3730
+ }),
3731
+ fragment(
3732
+ "Bash tool usage",
3733
+ workflow({
3734
+ task: "Query execution",
3735
+ steps: [
3736
+ 'Execute SQL through bash tool: sql run "SELECT ..."',
3737
+ "Read the output: file path, column names, and row count.",
3738
+ "Use column names to construct jq filters: cat <path> | jq '.[] | {col1, col2}'",
3739
+ "For large results, slice first: cat <path> | jq '.[:10]'"
3740
+ ]
3741
+ }),
3742
+ hint(
3743
+ `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.`
3744
+ ),
3745
+ hint(
3746
+ "The sql command outputs: file path, column names (comma-separated), and row count. Use column names to construct precise jq queries."
3747
+ ),
3748
+ hint(
3749
+ 'This is virtual bash environment and "sql" commands proxy to the database hence you cannot access sql files directly.'
3750
+ ),
3751
+ hint(
3752
+ "If a query fails, the sql command returns an error message in stderr."
3753
+ )
3754
+ )
3658
3755
  ];
3659
3756
  if (date === "strict") {
3660
3757
  baseTeachings.push(
@@ -3687,14 +3784,12 @@ var Text2Sql = class {
3687
3784
  #config;
3688
3785
  constructor(config) {
3689
3786
  this.#config = {
3787
+ teachingsOptions: config.teachingsOptions,
3690
3788
  adapter: config.adapter,
3691
- store: config.store,
3692
- instructions: [
3693
- ...guidelines(config.teachingsOptions),
3694
- ...config.instructions ?? []
3695
- ],
3789
+ context: config.context,
3696
3790
  tools: config.tools ?? {},
3697
3791
  model: config.model,
3792
+ transform: config.transform,
3698
3793
  introspection: new JsonCache(
3699
3794
  "introspection-" + config.version
3700
3795
  )
@@ -3706,7 +3801,7 @@ var Text2Sql = class {
3706
3801
  input,
3707
3802
  adapter: this.#config.adapter,
3708
3803
  schemaFragments,
3709
- instructions: this.#config.instructions,
3804
+ instructions: [],
3710
3805
  model: this.#config.model
3711
3806
  });
3712
3807
  return result.sql;
@@ -3766,40 +3861,11 @@ var Text2Sql = class {
3766
3861
  })
3767
3862
  ];
3768
3863
  }
3769
- async chat(messages, params) {
3770
- const schemaFragments = await this.index();
3771
- const context = new ContextEngine({
3772
- store: this.#config.store,
3773
- chatId: params.chatId,
3774
- userId: params.userId
3775
- }).set(
3776
- ...schemaFragments,
3777
- ...this.#buildRenderingInstructions(),
3778
- fragment(
3779
- "Bash tool usage",
3780
- workflow({
3781
- task: "Query execution",
3782
- steps: [
3783
- 'Execute SQL through bash tool: sql run "SELECT ..."',
3784
- "Read the output: file path, column names, and row count.",
3785
- "Use column names to construct jq filters: cat <path> | jq '.[] | {col1, col2}'",
3786
- "For large results, slice first: cat <path> | jq '.[:10]'"
3787
- ]
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
- ),
3792
- hint(
3793
- "The sql command outputs: file path, column names (comma-separated), and row count. Use column names to construct precise jq queries."
3794
- ),
3795
- hint(
3796
- 'This is virtual bash environment and "sql" commands proxy to the database hence you cannot access sql files directly.'
3797
- ),
3798
- hint(
3799
- "If a query fails, the sql command returns an error message in stderr."
3800
- )
3801
- ),
3802
- ...this.#config.instructions
3864
+ async chat(messages) {
3865
+ const context = this.#config.context(
3866
+ ...guidelines(this.#config.teachingsOptions),
3867
+ ...await this.index(),
3868
+ ...this.#buildRenderingInstructions()
3803
3869
  );
3804
3870
  const userMsg = messages.at(-1);
3805
3871
  if (userMsg) {
@@ -3810,7 +3876,7 @@ var Text2Sql = class {
3810
3876
  const skillMounts = context.getSkillMounts();
3811
3877
  const { bash } = await createResultTools({
3812
3878
  adapter: this.#config.adapter,
3813
- chatId: params.chatId,
3879
+ chatId: context.chatId,
3814
3880
  messageId,
3815
3881
  skillMounts
3816
3882
  });
@@ -3825,7 +3891,10 @@ var Text2Sql = class {
3825
3891
  guardrails: [errorRecoveryGuardrail],
3826
3892
  maxGuardrailRetries: 3
3827
3893
  });
3828
- const result = await chatAgent.stream({});
3894
+ const result = await chatAgent.stream(
3895
+ {},
3896
+ { transform: this.#config.transform }
3897
+ );
3829
3898
  return result.toUIMessageStream({
3830
3899
  onError: (error) => this.#formatError(error),
3831
3900
  sendStart: true,
@@ -3837,23 +3906,16 @@ var Text2Sql = class {
3837
3906
  onFinish: async ({ responseMessage }) => {
3838
3907
  context.set(assistant(responseMessage));
3839
3908
  await context.save();
3909
+ await context.trackUsage(await result.totalUsage);
3840
3910
  }
3841
3911
  });
3842
3912
  }
3843
- /**
3844
- * Developer chat interface - power-user mode for SQL generation.
3845
- * Uses db_query tool for direct SQL execution (LLM writes SQL).
3846
- */
3847
- async developer(messages, params) {
3848
- const schemaFragments = await this.index();
3849
- const context = new ContextEngine({
3850
- store: this.#config.store,
3851
- chatId: params.chatId,
3852
- userId: params.userId
3853
- }).set(
3913
+ async developer(messages) {
3914
+ const context = this.#config.context(
3915
+ ...guidelines(this.#config.teachingsOptions),
3854
3916
  ...developer_agent_default.fragments,
3855
- ...this.#config.instructions,
3856
- ...schemaFragments
3917
+ ...await this.index(),
3918
+ ...this.#buildRenderingInstructions()
3857
3919
  );
3858
3920
  const userMsg = messages.at(-1);
3859
3921
  if (userMsg) {
@@ -3879,6 +3941,7 @@ var Text2Sql = class {
3879
3941
  onFinish: async ({ responseMessage }) => {
3880
3942
  context.set(assistant(responseMessage));
3881
3943
  await context.save();
3944
+ await context.trackUsage(await result.totalUsage);
3882
3945
  }
3883
3946
  });
3884
3947
  }