@deepagents/context 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -247,7 +247,7 @@ function assistantText(content, options) {
247
247
  parts: [{ type: "text", text: content }]
248
248
  });
249
249
  }
250
- var LAZY_ID = Symbol("lazy-id");
250
+ var LAZY_ID = Symbol.for("@deepagents/context:lazy-id");
251
251
  function isLazyFragment(fragment2) {
252
252
  return LAZY_ID in fragment2;
253
253
  }
@@ -1102,7 +1102,7 @@ var ContextEngine = class {
1102
1102
  async #createBranchFrom(messageId, switchTo) {
1103
1103
  const branches = await this.#store.listBranches(this.#chatId);
1104
1104
  const samePrefix = branches.filter(
1105
- (b) => b.name === this.#branchName || b.name.startsWith(`${this.#branchName}-v`)
1105
+ (it) => it.name === this.#branchName || it.name.startsWith(`${this.#branchName}-v`)
1106
1106
  );
1107
1107
  const newBranchName = `${this.#branchName}-v${samePrefix.length + 1}`;
1108
1108
  const newBranch = {
@@ -1130,6 +1130,15 @@ var ContextEngine = class {
1130
1130
  createdAt: newBranch.createdAt
1131
1131
  };
1132
1132
  }
1133
+ /**
1134
+ * Rewind to a message without clearing pending messages.
1135
+ * Used internally when saving an update to an existing message.
1136
+ */
1137
+ async #rewindForUpdate(messageId) {
1138
+ const pendingBackup = [...this.#pendingMessages];
1139
+ await this.rewind(messageId);
1140
+ this.#pendingMessages = pendingBackup;
1141
+ }
1133
1142
  /**
1134
1143
  * Get the current chat ID.
1135
1144
  */
@@ -1214,7 +1223,18 @@ var ContextEngine = class {
1214
1223
  messages.push(message(msg.data).codec?.decode());
1215
1224
  }
1216
1225
  }
1226
+ for (let i = 0; i < this.#pendingMessages.length; i++) {
1227
+ const fragment2 = this.#pendingMessages[i];
1228
+ if (isLazyFragment(fragment2)) {
1229
+ this.#pendingMessages[i] = await this.#resolveLazyFragment(fragment2);
1230
+ }
1231
+ }
1217
1232
  for (const fragment2 of this.#pendingMessages) {
1233
+ if (!fragment2.codec) {
1234
+ throw new Error(
1235
+ `Fragment "${fragment2.name}" is missing codec. Lazy fragments must be resolved before decode.`
1236
+ );
1237
+ }
1218
1238
  const decoded = fragment2.codec.decode();
1219
1239
  messages.push(decoded);
1220
1240
  }
@@ -1245,9 +1265,24 @@ var ContextEngine = class {
1245
1265
  this.#pendingMessages[i] = await this.#resolveLazyFragment(fragment2);
1246
1266
  }
1247
1267
  }
1268
+ for (const fragment2 of this.#pendingMessages) {
1269
+ if (fragment2.id) {
1270
+ const existing = await this.#store.getMessage(fragment2.id);
1271
+ if (existing && existing.parentId) {
1272
+ await this.#rewindForUpdate(existing.parentId);
1273
+ fragment2.id = crypto.randomUUID();
1274
+ break;
1275
+ }
1276
+ }
1277
+ }
1248
1278
  let parentId = this.#branch.headMessageId;
1249
1279
  const now = Date.now();
1250
1280
  for (const fragment2 of this.#pendingMessages) {
1281
+ if (!fragment2.codec) {
1282
+ throw new Error(
1283
+ `Fragment "${fragment2.name}" is missing codec. Lazy fragments must be resolved before encode.`
1284
+ );
1285
+ }
1251
1286
  const messageData = {
1252
1287
  id: fragment2.id ?? crypto.randomUUID(),
1253
1288
  chatId: this.#chatId,
@@ -1607,33 +1642,28 @@ var ContextEngine = class {
1607
1642
  return void 0;
1608
1643
  }
1609
1644
  /**
1610
- * Extract skill path mappings from available_skills fragments.
1611
- * Returns array of { host, sandbox } for mounting in sandbox filesystem.
1612
- *
1613
- * Reads the original `paths` configuration stored in fragment metadata
1614
- * by the skills() fragment helper.
1645
+ * Extract skill mounts from available_skills fragments.
1646
+ * Returns unified mount array where entries with `name` are individual skills.
1615
1647
  *
1616
1648
  * @example
1617
1649
  * ```ts
1618
1650
  * const context = new ContextEngine({ store, chatId, userId })
1619
1651
  * .set(skills({ paths: [{ host: './skills', sandbox: '/skills' }] }));
1620
1652
  *
1621
- * const mounts = context.getSkillMounts();
1622
- * // [{ host: './skills', sandbox: '/skills' }]
1653
+ * const { mounts } = context.getSkillMounts();
1654
+ * // mounts: [{ name: 'bi-dashboards', host: './skills/bi-dashboards/SKILL.md', sandbox: '/skills/bi-dashboards/SKILL.md' }]
1655
+ *
1656
+ * // Extract skills only (entries with name)
1657
+ * const skills = mounts.filter(m => m.name);
1623
1658
  * ```
1624
1659
  */
1625
1660
  getSkillMounts() {
1626
- const mounts = [];
1627
1661
  for (const fragment2 of this.#fragments) {
1628
- if (fragment2.name === "available_skills" && fragment2.metadata && Array.isArray(fragment2.metadata.paths)) {
1629
- for (const mapping of fragment2.metadata.paths) {
1630
- if (typeof mapping === "object" && mapping !== null && typeof mapping.host === "string" && typeof mapping.sandbox === "string") {
1631
- mounts.push({ host: mapping.host, sandbox: mapping.sandbox });
1632
- }
1633
- }
1662
+ if (fragment2.name === "available_skills" && fragment2.metadata?.mounts) {
1663
+ return { mounts: fragment2.metadata.mounts };
1634
1664
  }
1635
1665
  }
1636
- return mounts;
1666
+ return { mounts: [] };
1637
1667
  }
1638
1668
  /**
1639
1669
  * Inspect the full context state for debugging.
@@ -1870,11 +1900,14 @@ function pass(part) {
1870
1900
  function fail(feedback) {
1871
1901
  return { type: "fail", feedback };
1872
1902
  }
1903
+ function stop(part) {
1904
+ return { type: "stop", part };
1905
+ }
1873
1906
  function runGuardrailChain(part, guardrails, context) {
1874
1907
  let currentPart = part;
1875
1908
  for (const guardrail2 of guardrails) {
1876
1909
  const result = guardrail2.handle(currentPart, context);
1877
- if (result.type === "fail") {
1910
+ if (result.type === "fail" || result.type === "stop") {
1878
1911
  return result;
1879
1912
  }
1880
1913
  currentPart = result.part;
@@ -1920,6 +1953,15 @@ var errorRecoveryGuardrail = {
1920
1953
  if (errorText.includes("not in request.tools") || errorText.includes("tool") && errorText.includes("not found")) {
1921
1954
  const toolMatch = errorText.match(/tool '([^']+)'/);
1922
1955
  const toolName = toolMatch ? toolMatch[1] : "unknown";
1956
+ const matchingSkill = context.availableSkills.find(
1957
+ (skill) => skill.name === toolName
1958
+ );
1959
+ if (matchingSkill) {
1960
+ return logAndFail(
1961
+ `Skill confused as tool: ${toolName}`,
1962
+ `"${toolName}" is a skill, not a tool. Read the skill at ${matchingSkill.sandbox} to use it.`
1963
+ );
1964
+ }
1923
1965
  if (context.availableTools.length > 0) {
1924
1966
  return logAndFail(
1925
1967
  `Unregistered tool: ${toolName}`,
@@ -1937,17 +1979,37 @@ var errorRecoveryGuardrail = {
1937
1979
  "I generated malformed JSON for the tool arguments. Let me format my tool call properly with valid JSON."
1938
1980
  );
1939
1981
  }
1982
+ if (errorText.includes("validation failed") && errorText.includes("did not match schema")) {
1983
+ const toolMatch = errorText.match(/parameters for tool (\w+)/);
1984
+ const toolName = toolMatch ? toolMatch[1] : "unknown";
1985
+ const schemaErrors = errorText.match(/errors: \[([^\]]+)\]/)?.[1] || "";
1986
+ return logAndFail(
1987
+ `Schema validation: ${toolName}`,
1988
+ `I called "${toolName}" with invalid parameters. Schema errors: ${schemaErrors}. Let me fix the parameters and try again.`
1989
+ );
1990
+ }
1940
1991
  if (errorText.includes("Parsing failed")) {
1941
1992
  return logAndFail(
1942
1993
  "Parsing failed",
1943
1994
  "My response format was invalid. Let me try again with a properly formatted response."
1944
1995
  );
1945
1996
  }
1946
- console.dir({ part }, { depth: null });
1947
- return logAndFail(
1948
- "Unknown error",
1949
- `An error occurred: ${errorText}. Let me try a different approach.`
1997
+ if (errorText.includes("Failed to call a function") || errorText.includes("failed_generation")) {
1998
+ if (context.availableTools.length > 0) {
1999
+ return logAndFail(
2000
+ "Failed function call",
2001
+ `My function call was malformed. Available tools: ${context.availableTools.join(", ")}. Let me format my tool call correctly.`
2002
+ );
2003
+ }
2004
+ return logAndFail(
2005
+ "Failed function call (no tools)",
2006
+ "My function call was malformed. Let me respond with plain text instead."
2007
+ );
2008
+ }
2009
+ console.log(
2010
+ `${prefix} ${chalk.yellow("Unknown error - stopping without retry")}`
1950
2011
  );
2012
+ return stop(part);
1951
2013
  }
1952
2014
  };
1953
2015
 
@@ -2713,6 +2775,9 @@ async function createContainerTool(options = {}) {
2713
2775
  };
2714
2776
  }
2715
2777
 
2778
+ // packages/context/src/lib/skills/fragments.ts
2779
+ import dedent from "dedent";
2780
+
2716
2781
  // packages/context/src/lib/skills/loader.ts
2717
2782
  import * as fs from "node:fs";
2718
2783
  import * as path2 from "node:path";
@@ -2793,7 +2858,14 @@ function skills(options) {
2793
2858
  (s) => !options.exclude.includes(s.name)
2794
2859
  );
2795
2860
  }
2796
- const skillFragments = filteredSkills.map((skill) => {
2861
+ if (filteredSkills.length === 0) {
2862
+ return {
2863
+ name: "available_skills",
2864
+ data: [],
2865
+ metadata: { mounts: [] }
2866
+ };
2867
+ }
2868
+ const mounts = filteredSkills.map((skill) => {
2797
2869
  const originalPath = skill.skillMdPath;
2798
2870
  let sandboxPath = originalPath;
2799
2871
  for (const [host, sandbox] of pathMapping) {
@@ -2803,14 +2875,21 @@ function skills(options) {
2803
2875
  break;
2804
2876
  }
2805
2877
  }
2878
+ return {
2879
+ name: skill.name,
2880
+ description: skill.description,
2881
+ host: originalPath,
2882
+ sandbox: sandboxPath
2883
+ };
2884
+ });
2885
+ const skillFragments = mounts.map((skill) => {
2806
2886
  return {
2807
2887
  name: "skill",
2808
2888
  data: {
2809
2889
  name: skill.name,
2810
- path: sandboxPath,
2890
+ path: skill.sandbox,
2811
2891
  description: skill.description
2812
- },
2813
- metadata: { originalPath }
2892
+ }
2814
2893
  };
2815
2894
  });
2816
2895
  return {
@@ -2823,100 +2902,57 @@ function skills(options) {
2823
2902
  ...skillFragments
2824
2903
  ],
2825
2904
  metadata: {
2826
- paths: options.paths
2827
- // Store original path mappings for getSkillMounts()
2905
+ mounts
2828
2906
  }
2829
2907
  };
2830
2908
  }
2831
- var SKILLS_INSTRUCTIONS = `When a user's request matches one of the skills listed below, read the skill's SKILL.md file to get detailed instructions before proceeding. Skills provide specialized knowledge and workflows for specific tasks.
2909
+ var SKILLS_INSTRUCTIONS = dedent`A skill is a set of local instructions to follow that is stored in a \`SKILL.md\` file. Below is the list of skills that can be used. Each entry includes a name, description, and file path so you can open the source for full instructions when using a specific skill.
2832
2910
 
2833
- To use a skill:
2834
- 1. Identify if the user's request matches a skill's description
2835
- 2. Read the SKILL.md file at the skill's path to load full instructions
2836
- 3. Follow the skill's guidance to complete the task
2837
-
2838
- Skills are only loaded when relevant - don't read skill files unless needed.`;
2911
+ ### How to use skills
2912
+ - Discovery: The list below shows the skills available in this session (name + description + file path). Skill bodies live on disk at the listed paths.
2913
+ - Trigger rules: If the user names a skill (with \`$SkillName\` or plain text) OR the task clearly matches a skill's description shown below, you must use that skill for that turn before doing anything else. Multiple mentions mean use them all. Do not carry skills across turns unless re-mentioned.
2914
+ - Missing/blocked: If a named skill isn't in the list or the path can't be read, say so briefly and continue with the best fallback.
2915
+ - How to use a skill (progressive disclosure):
2916
+ 1) After deciding to use a skill, open its \`SKILL.md\`. Read only enough to follow the workflow.
2917
+ 2) If \`SKILL.md\` points to extra folders such as \`references/\`, load only the specific files needed for the request; don't bulk-load everything.
2918
+ 3) If \`scripts/\` exist, prefer running or patching them instead of retyping large code blocks.
2919
+ 4) If \`assets/\` or templates exist, reuse them instead of recreating from scratch.
2920
+ - Coordination and sequencing:
2921
+ - If multiple skills apply, choose the minimal set that covers the request and state the order you'll use them.
2922
+ - Announce which skill(s) you're using and why (one short line). If you skip an obvious skill, say why.
2923
+ - Context hygiene:
2924
+ - Keep context small: summarize long sections instead of pasting them; only load extra files when needed.
2925
+ - Avoid deep reference-chasing: prefer opening only files directly linked from \`SKILL.md\` unless you're blocked.
2926
+ - When variants exist (frameworks, providers, domains), pick only the relevant reference file(s) and note that choice.
2927
+ - Safety and fallback: If a skill can't be applied cleanly (missing files, unclear instructions), state the issue, pick the next-best approach, and continue.`;
2839
2928
 
2840
2929
  // packages/context/src/lib/store/sqlite.store.ts
2841
2930
  import { DatabaseSync } from "node:sqlite";
2842
- var STORE_DDL = `
2843
- -- Chats table
2844
- -- createdAt/updatedAt: DEFAULT for insert, inline SET for updates
2845
- CREATE TABLE IF NOT EXISTS chats (
2846
- id TEXT PRIMARY KEY,
2847
- userId TEXT NOT NULL,
2848
- title TEXT,
2849
- metadata TEXT,
2850
- createdAt INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),
2851
- updatedAt INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000)
2852
- );
2853
-
2854
- CREATE INDEX IF NOT EXISTS idx_chats_updatedAt ON chats(updatedAt);
2855
- CREATE INDEX IF NOT EXISTS idx_chats_userId ON chats(userId);
2856
-
2857
- -- Messages table (nodes in the DAG)
2858
- CREATE TABLE IF NOT EXISTS messages (
2859
- id TEXT PRIMARY KEY,
2860
- chatId TEXT NOT NULL,
2861
- parentId TEXT,
2862
- name TEXT NOT NULL,
2863
- type TEXT,
2864
- data TEXT NOT NULL,
2865
- createdAt INTEGER NOT NULL,
2866
- FOREIGN KEY (chatId) REFERENCES chats(id) ON DELETE CASCADE,
2867
- FOREIGN KEY (parentId) REFERENCES messages(id)
2868
- );
2869
-
2870
- CREATE INDEX IF NOT EXISTS idx_messages_chatId ON messages(chatId);
2871
- CREATE INDEX IF NOT EXISTS idx_messages_parentId ON messages(parentId);
2872
2931
 
2873
- -- Branches table (pointers to head messages)
2874
- CREATE TABLE IF NOT EXISTS branches (
2875
- id TEXT PRIMARY KEY,
2876
- chatId TEXT NOT NULL,
2877
- name TEXT NOT NULL,
2878
- headMessageId TEXT,
2879
- isActive INTEGER NOT NULL DEFAULT 0,
2880
- createdAt INTEGER NOT NULL,
2881
- FOREIGN KEY (chatId) REFERENCES chats(id) ON DELETE CASCADE,
2882
- FOREIGN KEY (headMessageId) REFERENCES messages(id),
2883
- UNIQUE(chatId, name)
2884
- );
2932
+ // packages/context/src/lib/store/ddl.sqlite.sql
2933
+ var ddl_sqlite_default = "-- Context Store DDL for SQLite\n-- This schema implements a DAG-based message history with branching and checkpoints.\n\n-- Performance PRAGMAs (session-level, run on each connection)\nPRAGMA journal_mode = WAL;\nPRAGMA synchronous = NORMAL;\nPRAGMA cache_size = -64000;\nPRAGMA temp_store = MEMORY;\nPRAGMA mmap_size = 268435456;\n\n-- Integrity\nPRAGMA foreign_keys = ON;\n\n-- Chats table\n-- createdAt/updatedAt: DEFAULT for insert, inline SET for updates\nCREATE TABLE IF NOT EXISTS chats (\n id TEXT PRIMARY KEY,\n userId TEXT NOT NULL,\n title TEXT,\n metadata TEXT,\n createdAt INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),\n updatedAt INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000)\n);\n\nCREATE INDEX IF NOT EXISTS idx_chats_updatedAt ON chats(updatedAt);\nCREATE INDEX IF NOT EXISTS idx_chats_userId ON chats(userId);\n-- Composite index for listChats(): WHERE userId = ? ORDER BY updatedAt DESC\nCREATE INDEX IF NOT EXISTS idx_chats_userId_updatedAt ON chats(userId, updatedAt DESC);\n\n-- Messages table (nodes in the DAG)\nCREATE TABLE IF NOT EXISTS messages (\n id TEXT PRIMARY KEY,\n chatId TEXT NOT NULL,\n parentId TEXT,\n name TEXT NOT NULL,\n type TEXT,\n data TEXT NOT NULL,\n createdAt INTEGER NOT NULL,\n FOREIGN KEY (chatId) REFERENCES chats(id) ON DELETE CASCADE,\n FOREIGN KEY (parentId) REFERENCES messages(id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_messages_chatId ON messages(chatId);\nCREATE INDEX IF NOT EXISTS idx_messages_parentId ON messages(parentId);\n-- Composite index for recursive CTE parent traversal in getMessageChain()\nCREATE INDEX IF NOT EXISTS idx_messages_chatId_parentId ON messages(chatId, parentId);\n\n-- Branches table (pointers to head messages)\nCREATE TABLE IF NOT EXISTS branches (\n id TEXT PRIMARY KEY,\n chatId TEXT NOT NULL,\n name TEXT NOT NULL,\n headMessageId TEXT,\n isActive INTEGER NOT NULL DEFAULT 0,\n createdAt INTEGER NOT NULL,\n FOREIGN KEY (chatId) REFERENCES chats(id) ON DELETE CASCADE,\n FOREIGN KEY (headMessageId) REFERENCES messages(id),\n UNIQUE(chatId, name)\n);\n\nCREATE INDEX IF NOT EXISTS idx_branches_chatId ON branches(chatId);\n-- Composite index for getActiveBranch(): WHERE chatId = ? AND isActive = 1\nCREATE INDEX IF NOT EXISTS idx_branches_chatId_isActive ON branches(chatId, isActive);\n\n-- Checkpoints table (pointers to message nodes)\nCREATE TABLE IF NOT EXISTS checkpoints (\n id TEXT PRIMARY KEY,\n chatId TEXT NOT NULL,\n name TEXT NOT NULL,\n messageId TEXT NOT NULL,\n createdAt INTEGER NOT NULL,\n FOREIGN KEY (chatId) REFERENCES chats(id) ON DELETE CASCADE,\n FOREIGN KEY (messageId) REFERENCES messages(id),\n UNIQUE(chatId, name)\n);\n\nCREATE INDEX IF NOT EXISTS idx_checkpoints_chatId ON checkpoints(chatId);\n\n-- FTS5 virtual table for full-text search\n-- messageId/chatId/name are UNINDEXED (stored but not searchable, used for filtering/joining)\n-- Only 'content' is indexed for full-text search\nCREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(\n messageId UNINDEXED,\n chatId UNINDEXED,\n name UNINDEXED,\n content,\n tokenize='porter unicode61'\n);\n";
2885
2934
 
2886
- CREATE INDEX IF NOT EXISTS idx_branches_chatId ON branches(chatId);
2887
-
2888
- -- Checkpoints table (pointers to message nodes)
2889
- CREATE TABLE IF NOT EXISTS checkpoints (
2890
- id TEXT PRIMARY KEY,
2891
- chatId TEXT NOT NULL,
2892
- name TEXT NOT NULL,
2893
- messageId TEXT NOT NULL,
2894
- createdAt INTEGER NOT NULL,
2895
- FOREIGN KEY (chatId) REFERENCES chats(id) ON DELETE CASCADE,
2896
- FOREIGN KEY (messageId) REFERENCES messages(id),
2897
- UNIQUE(chatId, name)
2898
- );
2899
-
2900
- CREATE INDEX IF NOT EXISTS idx_checkpoints_chatId ON checkpoints(chatId);
2901
-
2902
- -- FTS5 virtual table for full-text search
2903
- -- messageId/chatId/name are UNINDEXED (stored but not searchable, used for filtering/joining)
2904
- -- Only 'content' is indexed for full-text search
2905
- CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(
2906
- messageId UNINDEXED,
2907
- chatId UNINDEXED,
2908
- name UNINDEXED,
2909
- content,
2910
- tokenize='porter unicode61'
2911
- );
2912
- `;
2935
+ // packages/context/src/lib/store/sqlite.store.ts
2913
2936
  var SqliteContextStore = class extends ContextStore {
2914
2937
  #db;
2938
+ #statements = /* @__PURE__ */ new Map();
2939
+ /**
2940
+ * Get or create a prepared statement.
2941
+ * Statements are cached for the lifetime of the store to avoid
2942
+ * repeated SQL parsing and compilation overhead.
2943
+ */
2944
+ #stmt(sql) {
2945
+ let stmt = this.#statements.get(sql);
2946
+ if (!stmt) {
2947
+ stmt = this.#db.prepare(sql);
2948
+ this.#statements.set(sql, stmt);
2949
+ }
2950
+ return stmt;
2951
+ }
2915
2952
  constructor(path3) {
2916
2953
  super();
2917
2954
  this.#db = new DatabaseSync(path3);
2918
- this.#db.exec("PRAGMA foreign_keys = ON");
2919
- this.#db.exec(STORE_DDL);
2955
+ this.#db.exec(ddl_sqlite_default);
2920
2956
  }
2921
2957
  /**
2922
2958
  * Execute a function within a transaction.
@@ -2937,11 +2973,12 @@ var SqliteContextStore = class extends ContextStore {
2937
2973
  // Chat Operations
2938
2974
  // ==========================================================================
2939
2975
  async createChat(chat) {
2940
- this.#useTransaction(() => {
2941
- this.#db.prepare(
2976
+ return this.#useTransaction(() => {
2977
+ const row = this.#db.prepare(
2942
2978
  `INSERT INTO chats (id, userId, title, metadata)
2943
- VALUES (?, ?, ?, ?)`
2944
- ).run(
2979
+ VALUES (?, ?, ?, ?)
2980
+ RETURNING *`
2981
+ ).get(
2945
2982
  chat.id,
2946
2983
  chat.userId,
2947
2984
  chat.title ?? null,
@@ -2951,6 +2988,14 @@ var SqliteContextStore = class extends ContextStore {
2951
2988
  `INSERT INTO branches (id, chatId, name, headMessageId, isActive, createdAt)
2952
2989
  VALUES (?, ?, 'main', NULL, 1, ?)`
2953
2990
  ).run(crypto.randomUUID(), chat.id, Date.now());
2991
+ return {
2992
+ id: row.id,
2993
+ userId: row.userId,
2994
+ title: row.title ?? void 0,
2995
+ metadata: row.metadata ? JSON.parse(row.metadata) : void 0,
2996
+ createdAt: row.createdAt,
2997
+ updatedAt: row.updatedAt
2998
+ };
2954
2999
  });
2955
3000
  }
2956
3001
  async upsertChat(chat) {
@@ -3093,21 +3138,16 @@ var SqliteContextStore = class extends ContextStore {
3093
3138
  // Message Operations (Graph Nodes)
3094
3139
  // ==========================================================================
3095
3140
  async addMessage(message2) {
3096
- this.#db.prepare(
3141
+ if (message2.parentId === message2.id) {
3142
+ throw new Error(`Message ${message2.id} cannot be its own parent`);
3143
+ }
3144
+ this.#stmt(
3097
3145
  `INSERT INTO messages (id, chatId, parentId, name, type, data, createdAt)
3098
- VALUES (
3099
- ?1,
3100
- ?2,
3101
- CASE WHEN ?3 = ?1 THEN (SELECT parentId FROM messages WHERE id = ?1) ELSE ?3 END,
3102
- ?4,
3103
- ?5,
3104
- ?6,
3105
- ?7
3106
- )
3107
- ON CONFLICT(id) DO UPDATE SET
3108
- name = excluded.name,
3109
- type = excluded.type,
3110
- data = excluded.data`
3146
+ VALUES (?, ?, ?, ?, ?, ?, ?)
3147
+ ON CONFLICT(id) DO UPDATE SET
3148
+ name = excluded.name,
3149
+ type = excluded.type,
3150
+ data = excluded.data`
3111
3151
  ).run(
3112
3152
  message2.id,
3113
3153
  message2.chatId,
@@ -3118,14 +3158,16 @@ var SqliteContextStore = class extends ContextStore {
3118
3158
  message2.createdAt
3119
3159
  );
3120
3160
  const content = typeof message2.data === "string" ? message2.data : JSON.stringify(message2.data);
3121
- this.#db.prepare(`DELETE FROM messages_fts WHERE messageId = ?`).run(message2.id);
3122
- this.#db.prepare(
3161
+ this.#stmt(`DELETE FROM messages_fts WHERE messageId = ?`).run(message2.id);
3162
+ this.#stmt(
3123
3163
  `INSERT INTO messages_fts(messageId, chatId, name, content)
3124
- VALUES (?, ?, ?, ?)`
3164
+ VALUES (?, ?, ?, ?)`
3125
3165
  ).run(message2.id, message2.chatId, message2.name, content);
3126
3166
  }
3127
3167
  async getMessage(messageId) {
3128
- const row = this.#db.prepare("SELECT * FROM messages WHERE id = ?").get(messageId);
3168
+ const row = this.#stmt("SELECT * FROM messages WHERE id = ?").get(
3169
+ messageId
3170
+ );
3129
3171
  if (!row) {
3130
3172
  return void 0;
3131
3173
  }
@@ -3140,15 +3182,16 @@ var SqliteContextStore = class extends ContextStore {
3140
3182
  };
3141
3183
  }
3142
3184
  async getMessageChain(headId) {
3143
- const rows = this.#db.prepare(
3185
+ const rows = this.#stmt(
3144
3186
  `WITH RECURSIVE chain AS (
3145
- SELECT *, 0 as depth FROM messages WHERE id = ?
3146
- UNION ALL
3147
- SELECT m.*, c.depth + 1 FROM messages m
3148
- INNER JOIN chain c ON m.id = c.parentId
3149
- )
3150
- SELECT * FROM chain
3151
- ORDER BY depth DESC`
3187
+ SELECT *, 0 as depth FROM messages WHERE id = ?
3188
+ UNION ALL
3189
+ SELECT m.*, c.depth + 1 FROM messages m
3190
+ INNER JOIN chain c ON m.id = c.parentId
3191
+ WHERE c.depth < 100000
3192
+ )
3193
+ SELECT * FROM chain
3194
+ ORDER BY depth DESC`
3152
3195
  ).all(headId);
3153
3196
  return rows.map((row) => ({
3154
3197
  id: row.id,
@@ -3161,7 +3204,7 @@ var SqliteContextStore = class extends ContextStore {
3161
3204
  }));
3162
3205
  }
3163
3206
  async hasChildren(messageId) {
3164
- const row = this.#db.prepare(
3207
+ const row = this.#stmt(
3165
3208
  "SELECT EXISTS(SELECT 1 FROM messages WHERE parentId = ?) as hasChildren"
3166
3209
  ).get(messageId);
3167
3210
  return row.hasChildren === 1;
@@ -3208,7 +3251,9 @@ var SqliteContextStore = class extends ContextStore {
3208
3251
  };
3209
3252
  }
3210
3253
  async getActiveBranch(chatId) {
3211
- const row = this.#db.prepare("SELECT * FROM branches WHERE chatId = ? AND isActive = 1").get(chatId);
3254
+ const row = this.#stmt(
3255
+ "SELECT * FROM branches WHERE chatId = ? AND isActive = 1"
3256
+ ).get(chatId);
3212
3257
  if (!row) {
3213
3258
  return void 0;
3214
3259
  }
@@ -3226,45 +3271,43 @@ var SqliteContextStore = class extends ContextStore {
3226
3271
  this.#db.prepare("UPDATE branches SET isActive = 1 WHERE id = ?").run(branchId);
3227
3272
  }
3228
3273
  async updateBranchHead(branchId, messageId) {
3229
- this.#db.prepare("UPDATE branches SET headMessageId = ? WHERE id = ?").run(messageId, branchId);
3274
+ this.#stmt("UPDATE branches SET headMessageId = ? WHERE id = ?").run(
3275
+ messageId,
3276
+ branchId
3277
+ );
3230
3278
  }
3231
3279
  async listBranches(chatId) {
3232
- const branches = this.#db.prepare(
3280
+ const rows = this.#db.prepare(
3233
3281
  `SELECT
3234
3282
  b.id,
3235
3283
  b.name,
3236
3284
  b.headMessageId,
3237
3285
  b.isActive,
3238
- b.createdAt
3286
+ b.createdAt,
3287
+ COALESCE(
3288
+ (
3289
+ WITH RECURSIVE chain AS (
3290
+ SELECT id, parentId FROM messages WHERE id = b.headMessageId
3291
+ UNION ALL
3292
+ SELECT m.id, m.parentId FROM messages m
3293
+ INNER JOIN chain c ON m.id = c.parentId
3294
+ )
3295
+ SELECT COUNT(*) FROM chain
3296
+ ),
3297
+ 0
3298
+ ) as messageCount
3239
3299
  FROM branches b
3240
3300
  WHERE b.chatId = ?
3241
3301
  ORDER BY b.createdAt ASC`
3242
3302
  ).all(chatId);
3243
- const result = [];
3244
- for (const branch of branches) {
3245
- let messageCount = 0;
3246
- if (branch.headMessageId) {
3247
- const countRow = this.#db.prepare(
3248
- `WITH RECURSIVE chain AS (
3249
- SELECT id, parentId FROM messages WHERE id = ?
3250
- UNION ALL
3251
- SELECT m.id, m.parentId FROM messages m
3252
- INNER JOIN chain c ON m.id = c.parentId
3253
- )
3254
- SELECT COUNT(*) as count FROM chain`
3255
- ).get(branch.headMessageId);
3256
- messageCount = countRow.count;
3257
- }
3258
- result.push({
3259
- id: branch.id,
3260
- name: branch.name,
3261
- headMessageId: branch.headMessageId,
3262
- isActive: branch.isActive === 1,
3263
- messageCount,
3264
- createdAt: branch.createdAt
3265
- });
3266
- }
3267
- return result;
3303
+ return rows.map((row) => ({
3304
+ id: row.id,
3305
+ name: row.name,
3306
+ headMessageId: row.headMessageId,
3307
+ isActive: row.isActive === 1,
3308
+ messageCount: row.messageCount,
3309
+ createdAt: row.createdAt
3310
+ }));
3268
3311
  }
3269
3312
  // ==========================================================================
3270
3313
  // Checkpoint Operations
@@ -3417,152 +3460,1450 @@ var InMemoryContextStore = class extends SqliteContextStore {
3417
3460
  }
3418
3461
  };
3419
3462
 
3420
- // packages/context/src/lib/visualize.ts
3421
- function visualizeGraph(data) {
3422
- if (data.nodes.length === 0) {
3423
- return `[chat: ${data.chatId}]
3463
+ // packages/context/src/lib/store/postgres.store.ts
3464
+ import { createRequire } from "node:module";
3424
3465
 
3425
- (empty)`;
3426
- }
3427
- const childrenByParentId = /* @__PURE__ */ new Map();
3428
- const branchHeads = /* @__PURE__ */ new Map();
3429
- const checkpointsByMessageId = /* @__PURE__ */ new Map();
3430
- for (const node of data.nodes) {
3431
- const children = childrenByParentId.get(node.parentId) ?? [];
3432
- children.push(node);
3433
- childrenByParentId.set(node.parentId, children);
3434
- }
3435
- for (const branch of data.branches) {
3436
- if (branch.headMessageId) {
3437
- const heads = branchHeads.get(branch.headMessageId) ?? [];
3438
- heads.push(branch.isActive ? `${branch.name} *` : branch.name);
3439
- branchHeads.set(branch.headMessageId, heads);
3440
- }
3441
- }
3442
- for (const checkpoint of data.checkpoints) {
3443
- const cps = checkpointsByMessageId.get(checkpoint.messageId) ?? [];
3444
- cps.push(checkpoint.name);
3445
- checkpointsByMessageId.set(checkpoint.messageId, cps);
3446
- }
3447
- const roots = childrenByParentId.get(null) ?? [];
3448
- const lines = [`[chat: ${data.chatId}]`, ""];
3449
- function renderNode(node, prefix, isLast, isRoot) {
3450
- const connector = isRoot ? "" : isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
3451
- const contentPreview = node.content.replace(/\n/g, " ");
3452
- let line = `${prefix}${connector}${node.id.slice(0, 8)} (${node.role}): "${contentPreview}"`;
3453
- const branches = branchHeads.get(node.id);
3454
- if (branches) {
3455
- line += ` <- [${branches.join(", ")}]`;
3456
- }
3457
- const checkpoints = checkpointsByMessageId.get(node.id);
3458
- if (checkpoints) {
3459
- line += ` {${checkpoints.join(", ")}}`;
3460
- }
3461
- lines.push(line);
3462
- const children = childrenByParentId.get(node.id) ?? [];
3463
- const childPrefix = isRoot ? "" : prefix + (isLast ? " " : "\u2502 ");
3464
- for (let i = 0; i < children.length; i++) {
3465
- renderNode(children[i], childPrefix, i === children.length - 1, false);
3466
- }
3467
- }
3468
- for (let i = 0; i < roots.length; i++) {
3469
- renderNode(roots[i], "", i === roots.length - 1, true);
3470
- }
3471
- lines.push("");
3472
- lines.push("Legend: * = active branch, {...} = checkpoint");
3473
- return lines.join("\n");
3474
- }
3466
+ // packages/context/src/lib/store/ddl.postgres.sql
3467
+ var ddl_postgres_default = "-- Context Store DDL for PostgreSQL\n-- This schema implements a DAG-based message history with branching and checkpoints.\n\n-- Chats table\n-- createdAt/updatedAt: DEFAULT for insert, inline SET for updates\nCREATE TABLE IF NOT EXISTS chats (\n id TEXT PRIMARY KEY,\n userId TEXT NOT NULL,\n title TEXT,\n metadata JSONB,\n createdAt BIGINT NOT NULL DEFAULT (EXTRACT(EPOCH FROM NOW()) * 1000)::BIGINT,\n updatedAt BIGINT NOT NULL DEFAULT (EXTRACT(EPOCH FROM NOW()) * 1000)::BIGINT\n);\n\nCREATE INDEX IF NOT EXISTS idx_chats_updatedAt ON chats(updatedAt);\nCREATE INDEX IF NOT EXISTS idx_chats_userId ON chats(userId);\nCREATE INDEX IF NOT EXISTS idx_chats_metadata ON chats USING GIN (metadata);\n\n-- Messages table (nodes in the DAG)\nCREATE TABLE IF NOT EXISTS messages (\n id TEXT PRIMARY KEY,\n chatId TEXT NOT NULL,\n parentId TEXT,\n name TEXT NOT NULL,\n type TEXT,\n data JSONB NOT NULL,\n createdAt BIGINT NOT NULL,\n FOREIGN KEY (chatId) REFERENCES chats(id) ON DELETE CASCADE,\n FOREIGN KEY (parentId) REFERENCES messages(id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_messages_chatId ON messages(chatId);\nCREATE INDEX IF NOT EXISTS idx_messages_parentId ON messages(parentId);\n\n-- Branches table (pointers to head messages)\nCREATE TABLE IF NOT EXISTS branches (\n id TEXT PRIMARY KEY,\n chatId TEXT NOT NULL,\n name TEXT NOT NULL,\n headMessageId TEXT,\n isActive BOOLEAN NOT NULL DEFAULT FALSE,\n createdAt BIGINT NOT NULL,\n FOREIGN KEY (chatId) REFERENCES chats(id) ON DELETE CASCADE,\n FOREIGN KEY (headMessageId) REFERENCES messages(id),\n UNIQUE(chatId, name)\n);\n\nCREATE INDEX IF NOT EXISTS idx_branches_chatId ON branches(chatId);\n\n-- Checkpoints table (pointers to message nodes)\nCREATE TABLE IF NOT EXISTS checkpoints (\n id TEXT PRIMARY KEY,\n chatId TEXT NOT NULL,\n name TEXT NOT NULL,\n messageId TEXT NOT NULL,\n createdAt BIGINT NOT NULL,\n FOREIGN KEY (chatId) REFERENCES chats(id) ON DELETE CASCADE,\n FOREIGN KEY (messageId) REFERENCES messages(id),\n UNIQUE(chatId, name)\n);\n\nCREATE INDEX IF NOT EXISTS idx_checkpoints_chatId ON checkpoints(chatId);\n\n-- Full-text search using tsvector + GIN index\nCREATE TABLE IF NOT EXISTS messages_fts (\n messageId TEXT PRIMARY KEY REFERENCES messages(id) ON DELETE CASCADE,\n chatId TEXT NOT NULL,\n name TEXT NOT NULL,\n content TEXT NOT NULL,\n content_vector TSVECTOR\n);\n\nCREATE INDEX IF NOT EXISTS idx_messages_fts_vector ON messages_fts USING GIN(content_vector);\nCREATE INDEX IF NOT EXISTS idx_messages_fts_chatId ON messages_fts(chatId);\n\n-- Trigger to automatically update tsvector on insert/update\nCREATE OR REPLACE FUNCTION messages_fts_update_vector() RETURNS TRIGGER AS $$\nBEGIN\n NEW.content_vector := to_tsvector('english', NEW.content);\n RETURN NEW;\nEND;\n$$ LANGUAGE plpgsql;\n\nDROP TRIGGER IF EXISTS messages_fts_vector_update ON messages_fts;\nCREATE TRIGGER messages_fts_vector_update\n BEFORE INSERT OR UPDATE ON messages_fts\n FOR EACH ROW\n EXECUTE FUNCTION messages_fts_update_vector();\n";
3475
3468
 
3476
- // packages/context/src/lib/agent.ts
3477
- import { groq } from "@ai-sdk/groq";
3478
- import {
3479
- NoSuchToolError,
3480
- Output,
3481
- convertToModelMessages,
3482
- createUIMessageStream,
3483
- generateId as generateId2,
3484
- generateText,
3485
- smoothStream,
3486
- stepCountIs,
3487
- streamText
3488
- } from "ai";
3489
- import chalk2 from "chalk";
3490
- import "zod";
3491
- import "@deepagents/agent";
3492
- var Agent = class _Agent {
3493
- #options;
3494
- #guardrails = [];
3495
- tools;
3469
+ // packages/context/src/lib/store/postgres.store.ts
3470
+ var PostgresContextStore = class _PostgresContextStore extends ContextStore {
3471
+ #pool;
3472
+ #initialized;
3496
3473
  constructor(options) {
3497
- this.#options = options;
3498
- this.tools = options.tools || {};
3499
- this.#guardrails = options.guardrails || [];
3474
+ super();
3475
+ const pg = _PostgresContextStore.#requirePg();
3476
+ this.#pool = typeof options.pool === "string" ? new pg.Pool({ connectionString: options.pool }) : new pg.Pool(options.pool);
3477
+ this.#initialized = this.#initialize();
3500
3478
  }
3501
- async generate(contextVariables, config) {
3502
- if (!this.#options.context) {
3503
- throw new Error(`Agent ${this.#options.name} is missing a context.`);
3504
- }
3505
- if (!this.#options.model) {
3506
- throw new Error(`Agent ${this.#options.name} is missing a model.`);
3479
+ static #requirePg() {
3480
+ try {
3481
+ const require2 = createRequire(import.meta.url);
3482
+ return require2("pg");
3483
+ } catch {
3484
+ throw new Error(
3485
+ 'PostgresContextStore requires the "pg" package. Install it with: npm install pg'
3486
+ );
3507
3487
  }
3508
- const { messages, systemPrompt } = await this.#options.context.resolve({
3509
- renderer: new XmlRenderer()
3510
- });
3511
- return generateText({
3512
- abortSignal: config?.abortSignal,
3513
- providerOptions: this.#options.providerOptions,
3514
- model: this.#options.model,
3515
- system: systemPrompt,
3516
- messages: await convertToModelMessages(messages),
3517
- stopWhen: stepCountIs(25),
3518
- tools: this.#options.tools,
3519
- experimental_context: contextVariables,
3520
- experimental_repairToolCall: repairToolCall,
3521
- toolChoice: this.#options.toolChoice,
3522
- onStepFinish: (step) => {
3523
- const toolCall = step.toolCalls.at(-1);
3524
- if (toolCall) {
3525
- console.log(
3526
- `Debug: ${chalk2.yellow("ToolCalled")}: ${toolCall.toolName}(${JSON.stringify(toolCall.input)})`
3527
- );
3528
- }
3529
- }
3530
- });
3488
+ }
3489
+ async #initialize() {
3490
+ await this.#pool.query(ddl_postgres_default);
3531
3491
  }
3532
3492
  /**
3533
- * Stream a response from the agent.
3534
- *
3535
- * When guardrails are configured, `toUIMessageStream()` is wrapped to provide
3536
- * self-correction behavior. Direct access to fullStream/textStream bypasses guardrails.
3537
- *
3538
- * @example
3539
- * ```typescript
3540
- * const stream = await agent.stream({});
3541
- *
3542
- * // With guardrails - use toUIMessageStream for protection
3543
- * await printer.readableStream(stream.toUIMessageStream());
3544
- *
3545
- * // Or use printer.stdout which uses toUIMessageStream internally
3546
- * await printer.stdout(stream);
3547
- * ```
3493
+ * Ensure initialization is complete before any operation.
3548
3494
  */
3549
- async stream(contextVariables, config) {
3550
- if (!this.#options.context) {
3551
- throw new Error(`Agent ${this.#options.name} is missing a context.`);
3552
- }
3553
- if (!this.#options.model) {
3554
- throw new Error(`Agent ${this.#options.name} is missing a model.`);
3555
- }
3556
- const result = await this.#createRawStream(contextVariables, config);
3557
- if (this.#guardrails.length === 0) {
3495
+ async #ensureInitialized() {
3496
+ await this.#initialized;
3497
+ }
3498
+ /**
3499
+ * Execute a function within a transaction.
3500
+ * Automatically commits on success or rolls back on error.
3501
+ */
3502
+ async #useTransaction(fn) {
3503
+ await this.#ensureInitialized();
3504
+ const client = await this.#pool.connect();
3505
+ try {
3506
+ await client.query("BEGIN");
3507
+ const result = await fn(client);
3508
+ await client.query("COMMIT");
3558
3509
  return result;
3510
+ } catch (error) {
3511
+ await client.query("ROLLBACK");
3512
+ throw error;
3513
+ } finally {
3514
+ client.release();
3559
3515
  }
3560
- return this.#wrapWithGuardrails(result, contextVariables, config);
3561
3516
  }
3562
3517
  /**
3563
- * Create a raw stream without guardrail processing.
3518
+ * Execute a query using the pool (no transaction).
3564
3519
  */
3565
- async #createRawStream(contextVariables, config) {
3520
+ async #query(sql, params) {
3521
+ await this.#ensureInitialized();
3522
+ const result = await this.#pool.query(sql, params);
3523
+ return result.rows;
3524
+ }
3525
+ /**
3526
+ * Close the pool connection.
3527
+ * Call this when done with the store.
3528
+ */
3529
+ async close() {
3530
+ await this.#pool.end();
3531
+ }
3532
+ // ==========================================================================
3533
+ // Chat Operations
3534
+ // ==========================================================================
3535
+ async createChat(chat) {
3536
+ return this.#useTransaction(async (client) => {
3537
+ const result = await client.query(
3538
+ `INSERT INTO chats (id, userId, title, metadata)
3539
+ VALUES ($1, $2, $3, $4)
3540
+ RETURNING *`,
3541
+ [
3542
+ chat.id,
3543
+ chat.userId,
3544
+ chat.title ?? null,
3545
+ chat.metadata ? JSON.stringify(chat.metadata) : null
3546
+ ]
3547
+ );
3548
+ const row = result.rows[0];
3549
+ await client.query(
3550
+ `INSERT INTO branches (id, chatId, name, headMessageId, isActive, createdAt)
3551
+ VALUES ($1, $2, 'main', NULL, TRUE, $3)`,
3552
+ [crypto.randomUUID(), chat.id, Date.now()]
3553
+ );
3554
+ return {
3555
+ id: row.id,
3556
+ userId: row.userid,
3557
+ title: row.title ?? void 0,
3558
+ metadata: row.metadata ?? void 0,
3559
+ createdAt: Number(row.createdat),
3560
+ updatedAt: Number(row.updatedat)
3561
+ };
3562
+ });
3563
+ }
3564
+ async upsertChat(chat) {
3565
+ return this.#useTransaction(async (client) => {
3566
+ const result = await client.query(
3567
+ `INSERT INTO chats (id, userId, title, metadata)
3568
+ VALUES ($1, $2, $3, $4)
3569
+ ON CONFLICT(id) DO UPDATE SET id = EXCLUDED.id
3570
+ RETURNING *`,
3571
+ [
3572
+ chat.id,
3573
+ chat.userId,
3574
+ chat.title ?? null,
3575
+ chat.metadata ? JSON.stringify(chat.metadata) : null
3576
+ ]
3577
+ );
3578
+ const row = result.rows[0];
3579
+ await client.query(
3580
+ `INSERT INTO branches (id, chatId, name, headMessageId, isActive, createdAt)
3581
+ VALUES ($1, $2, 'main', NULL, TRUE, $3)
3582
+ ON CONFLICT(chatId, name) DO NOTHING`,
3583
+ [crypto.randomUUID(), chat.id, Date.now()]
3584
+ );
3585
+ return {
3586
+ id: row.id,
3587
+ userId: row.userid,
3588
+ title: row.title ?? void 0,
3589
+ metadata: row.metadata ?? void 0,
3590
+ createdAt: Number(row.createdat),
3591
+ updatedAt: Number(row.updatedat)
3592
+ };
3593
+ });
3594
+ }
3595
+ async getChat(chatId) {
3596
+ const rows = await this.#query("SELECT * FROM chats WHERE id = $1", [chatId]);
3597
+ if (rows.length === 0) {
3598
+ return void 0;
3599
+ }
3600
+ const row = rows[0];
3601
+ return {
3602
+ id: row.id,
3603
+ userId: row.userid,
3604
+ title: row.title ?? void 0,
3605
+ metadata: row.metadata ?? void 0,
3606
+ createdAt: Number(row.createdat),
3607
+ updatedAt: Number(row.updatedat)
3608
+ };
3609
+ }
3610
+ async updateChat(chatId, updates) {
3611
+ const setClauses = [
3612
+ "updatedAt = (EXTRACT(EPOCH FROM NOW()) * 1000)::BIGINT"
3613
+ ];
3614
+ const params = [];
3615
+ let paramIndex = 1;
3616
+ if (updates.title !== void 0) {
3617
+ setClauses.push(`title = $${paramIndex++}`);
3618
+ params.push(updates.title ?? null);
3619
+ }
3620
+ if (updates.metadata !== void 0) {
3621
+ setClauses.push(`metadata = $${paramIndex++}`);
3622
+ params.push(JSON.stringify(updates.metadata));
3623
+ }
3624
+ params.push(chatId);
3625
+ const rows = await this.#query(
3626
+ `UPDATE chats SET ${setClauses.join(", ")} WHERE id = $${paramIndex} RETURNING *`,
3627
+ params
3628
+ );
3629
+ const row = rows[0];
3630
+ return {
3631
+ id: row.id,
3632
+ userId: row.userid,
3633
+ title: row.title ?? void 0,
3634
+ metadata: row.metadata ?? void 0,
3635
+ createdAt: Number(row.createdat),
3636
+ updatedAt: Number(row.updatedat)
3637
+ };
3638
+ }
3639
+ async listChats(options) {
3640
+ const params = [];
3641
+ const whereClauses = [];
3642
+ let paramIndex = 1;
3643
+ if (options?.userId) {
3644
+ whereClauses.push(`c.userId = $${paramIndex++}`);
3645
+ params.push(options.userId);
3646
+ }
3647
+ if (options?.metadata) {
3648
+ const keyParam = paramIndex++;
3649
+ const valueParam = paramIndex++;
3650
+ whereClauses.push(`c.metadata->$${keyParam} = $${valueParam}::jsonb`);
3651
+ params.push(options.metadata.key);
3652
+ params.push(JSON.stringify(options.metadata.value));
3653
+ }
3654
+ const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : "";
3655
+ let limitClause = "";
3656
+ if (options?.limit !== void 0) {
3657
+ limitClause = ` LIMIT $${paramIndex++}`;
3658
+ params.push(options.limit);
3659
+ if (options.offset !== void 0) {
3660
+ limitClause += ` OFFSET $${paramIndex++}`;
3661
+ params.push(options.offset);
3662
+ }
3663
+ }
3664
+ const rows = await this.#query(
3665
+ `SELECT
3666
+ c.id,
3667
+ c.userId,
3668
+ c.title,
3669
+ c.metadata,
3670
+ c.createdAt,
3671
+ c.updatedAt,
3672
+ COUNT(DISTINCT m.id) as messageCount,
3673
+ COUNT(DISTINCT b.id) as branchCount
3674
+ FROM chats c
3675
+ LEFT JOIN messages m ON m.chatId = c.id
3676
+ LEFT JOIN branches b ON b.chatId = c.id
3677
+ ${whereClause}
3678
+ GROUP BY c.id
3679
+ ORDER BY c.updatedAt DESC${limitClause}`,
3680
+ params
3681
+ );
3682
+ return rows.map((row) => ({
3683
+ id: row.id,
3684
+ userId: row.userid,
3685
+ title: row.title ?? void 0,
3686
+ metadata: row.metadata ?? void 0,
3687
+ messageCount: Number(row.messagecount),
3688
+ branchCount: Number(row.branchcount),
3689
+ createdAt: Number(row.createdat),
3690
+ updatedAt: Number(row.updatedat)
3691
+ }));
3692
+ }
3693
+ async deleteChat(chatId, options) {
3694
+ return this.#useTransaction(async (client) => {
3695
+ let sql = "DELETE FROM chats WHERE id = $1";
3696
+ const params = [chatId];
3697
+ if (options?.userId !== void 0) {
3698
+ sql += " AND userId = $2";
3699
+ params.push(options.userId);
3700
+ }
3701
+ const result = await client.query(sql, params);
3702
+ return (result.rowCount ?? 0) > 0;
3703
+ });
3704
+ }
3705
+ // ==========================================================================
3706
+ // Message Operations (Graph Nodes)
3707
+ // ==========================================================================
3708
+ async addMessage(message2) {
3709
+ if (message2.parentId === message2.id) {
3710
+ throw new Error(`Message ${message2.id} cannot be its own parent`);
3711
+ }
3712
+ await this.#useTransaction(async (client) => {
3713
+ await client.query(
3714
+ `INSERT INTO messages (id, chatId, parentId, name, type, data, createdAt)
3715
+ VALUES ($1, $2, $3, $4, $5, $6, $7)
3716
+ ON CONFLICT(id) DO UPDATE SET
3717
+ name = EXCLUDED.name,
3718
+ type = EXCLUDED.type,
3719
+ data = EXCLUDED.data`,
3720
+ [
3721
+ message2.id,
3722
+ message2.chatId,
3723
+ message2.parentId,
3724
+ message2.name,
3725
+ message2.type ?? null,
3726
+ JSON.stringify(message2.data),
3727
+ message2.createdAt
3728
+ ]
3729
+ );
3730
+ const content = typeof message2.data === "string" ? message2.data : JSON.stringify(message2.data);
3731
+ await client.query(
3732
+ `INSERT INTO messages_fts (messageId, chatId, name, content)
3733
+ VALUES ($1, $2, $3, $4)
3734
+ ON CONFLICT(messageId) DO UPDATE SET
3735
+ chatId = EXCLUDED.chatId,
3736
+ name = EXCLUDED.name,
3737
+ content = EXCLUDED.content`,
3738
+ [message2.id, message2.chatId, message2.name, content]
3739
+ );
3740
+ });
3741
+ }
3742
+ async getMessage(messageId) {
3743
+ const rows = await this.#query("SELECT * FROM messages WHERE id = $1", [messageId]);
3744
+ if (rows.length === 0) {
3745
+ return void 0;
3746
+ }
3747
+ const row = rows[0];
3748
+ return {
3749
+ id: row.id,
3750
+ chatId: row.chatid,
3751
+ parentId: row.parentid,
3752
+ name: row.name,
3753
+ type: row.type ?? void 0,
3754
+ data: row.data,
3755
+ createdAt: Number(row.createdat)
3756
+ };
3757
+ }
3758
+ async getMessageChain(headId) {
3759
+ const rows = await this.#query(
3760
+ `WITH RECURSIVE chain AS (
3761
+ SELECT *, 0 as depth FROM messages WHERE id = $1
3762
+ UNION ALL
3763
+ SELECT m.*, c.depth + 1 FROM messages m
3764
+ INNER JOIN chain c ON m.id = c.parentId
3765
+ WHERE c.depth < 10000
3766
+ )
3767
+ SELECT * FROM chain
3768
+ ORDER BY depth DESC`,
3769
+ [headId]
3770
+ );
3771
+ return rows.map((row) => ({
3772
+ id: row.id,
3773
+ chatId: row.chatid,
3774
+ parentId: row.parentid,
3775
+ name: row.name,
3776
+ type: row.type ?? void 0,
3777
+ data: row.data,
3778
+ createdAt: Number(row.createdat)
3779
+ }));
3780
+ }
3781
+ async hasChildren(messageId) {
3782
+ const rows = await this.#query(
3783
+ "SELECT EXISTS(SELECT 1 FROM messages WHERE parentId = $1) as exists",
3784
+ [messageId]
3785
+ );
3786
+ return rows[0].exists;
3787
+ }
3788
+ async getMessages(chatId) {
3789
+ const chat = await this.getChat(chatId);
3790
+ if (!chat) {
3791
+ throw new Error(`Chat "${chatId}" not found`);
3792
+ }
3793
+ const activeBranch = await this.getActiveBranch(chatId);
3794
+ if (!activeBranch?.headMessageId) {
3795
+ return [];
3796
+ }
3797
+ return this.getMessageChain(activeBranch.headMessageId);
3798
+ }
3799
+ // ==========================================================================
3800
+ // Branch Operations
3801
+ // ==========================================================================
3802
+ async createBranch(branch) {
3803
+ await this.#query(
3804
+ `INSERT INTO branches (id, chatId, name, headMessageId, isActive, createdAt)
3805
+ VALUES ($1, $2, $3, $4, $5, $6)`,
3806
+ [
3807
+ branch.id,
3808
+ branch.chatId,
3809
+ branch.name,
3810
+ branch.headMessageId,
3811
+ branch.isActive,
3812
+ branch.createdAt
3813
+ ]
3814
+ );
3815
+ }
3816
+ async getBranch(chatId, name) {
3817
+ const rows = await this.#query("SELECT * FROM branches WHERE chatId = $1 AND name = $2", [
3818
+ chatId,
3819
+ name
3820
+ ]);
3821
+ if (rows.length === 0) {
3822
+ return void 0;
3823
+ }
3824
+ const row = rows[0];
3825
+ return {
3826
+ id: row.id,
3827
+ chatId: row.chatid,
3828
+ name: row.name,
3829
+ headMessageId: row.headmessageid,
3830
+ isActive: row.isactive,
3831
+ createdAt: Number(row.createdat)
3832
+ };
3833
+ }
3834
+ async getActiveBranch(chatId) {
3835
+ const rows = await this.#query("SELECT * FROM branches WHERE chatId = $1 AND isActive = TRUE", [
3836
+ chatId
3837
+ ]);
3838
+ if (rows.length === 0) {
3839
+ return void 0;
3840
+ }
3841
+ const row = rows[0];
3842
+ return {
3843
+ id: row.id,
3844
+ chatId: row.chatid,
3845
+ name: row.name,
3846
+ headMessageId: row.headmessageid,
3847
+ isActive: true,
3848
+ createdAt: Number(row.createdat)
3849
+ };
3850
+ }
3851
+ async setActiveBranch(chatId, branchId) {
3852
+ await this.#useTransaction(async (client) => {
3853
+ await client.query(
3854
+ "UPDATE branches SET isActive = FALSE WHERE chatId = $1",
3855
+ [chatId]
3856
+ );
3857
+ await client.query("UPDATE branches SET isActive = TRUE WHERE id = $1", [
3858
+ branchId
3859
+ ]);
3860
+ });
3861
+ }
3862
+ async updateBranchHead(branchId, messageId) {
3863
+ await this.#query("UPDATE branches SET headMessageId = $1 WHERE id = $2", [
3864
+ messageId,
3865
+ branchId
3866
+ ]);
3867
+ }
3868
+ async listBranches(chatId) {
3869
+ const branches = await this.#query(
3870
+ `SELECT
3871
+ id,
3872
+ name,
3873
+ headMessageId,
3874
+ isActive,
3875
+ createdAt
3876
+ FROM branches
3877
+ WHERE chatId = $1
3878
+ ORDER BY createdAt ASC`,
3879
+ [chatId]
3880
+ );
3881
+ const result = [];
3882
+ for (const branch of branches) {
3883
+ let messageCount = 0;
3884
+ if (branch.headmessageid) {
3885
+ const countRows = await this.#query(
3886
+ `WITH RECURSIVE chain AS (
3887
+ SELECT id, parentId FROM messages WHERE id = $1
3888
+ UNION ALL
3889
+ SELECT m.id, m.parentId FROM messages m
3890
+ INNER JOIN chain c ON m.id = c.parentId
3891
+ )
3892
+ SELECT COUNT(*) as count FROM chain`,
3893
+ [branch.headmessageid]
3894
+ );
3895
+ messageCount = Number(countRows[0].count);
3896
+ }
3897
+ result.push({
3898
+ id: branch.id,
3899
+ name: branch.name,
3900
+ headMessageId: branch.headmessageid,
3901
+ isActive: branch.isactive,
3902
+ messageCount,
3903
+ createdAt: Number(branch.createdat)
3904
+ });
3905
+ }
3906
+ return result;
3907
+ }
3908
+ // ==========================================================================
3909
+ // Checkpoint Operations
3910
+ // ==========================================================================
3911
+ async createCheckpoint(checkpoint) {
3912
+ await this.#query(
3913
+ `INSERT INTO checkpoints (id, chatId, name, messageId, createdAt)
3914
+ VALUES ($1, $2, $3, $4, $5)
3915
+ ON CONFLICT(chatId, name) DO UPDATE SET
3916
+ messageId = EXCLUDED.messageId,
3917
+ createdAt = EXCLUDED.createdAt`,
3918
+ [
3919
+ checkpoint.id,
3920
+ checkpoint.chatId,
3921
+ checkpoint.name,
3922
+ checkpoint.messageId,
3923
+ checkpoint.createdAt
3924
+ ]
3925
+ );
3926
+ }
3927
+ async getCheckpoint(chatId, name) {
3928
+ const rows = await this.#query("SELECT * FROM checkpoints WHERE chatId = $1 AND name = $2", [
3929
+ chatId,
3930
+ name
3931
+ ]);
3932
+ if (rows.length === 0) {
3933
+ return void 0;
3934
+ }
3935
+ const row = rows[0];
3936
+ return {
3937
+ id: row.id,
3938
+ chatId: row.chatid,
3939
+ name: row.name,
3940
+ messageId: row.messageid,
3941
+ createdAt: Number(row.createdat)
3942
+ };
3943
+ }
3944
+ async listCheckpoints(chatId) {
3945
+ const rows = await this.#query(
3946
+ `SELECT id, name, messageId, createdAt
3947
+ FROM checkpoints
3948
+ WHERE chatId = $1
3949
+ ORDER BY createdAt DESC`,
3950
+ [chatId]
3951
+ );
3952
+ return rows.map((row) => ({
3953
+ id: row.id,
3954
+ name: row.name,
3955
+ messageId: row.messageid,
3956
+ createdAt: Number(row.createdat)
3957
+ }));
3958
+ }
3959
+ async deleteCheckpoint(chatId, name) {
3960
+ await this.#query(
3961
+ "DELETE FROM checkpoints WHERE chatId = $1 AND name = $2",
3962
+ [chatId, name]
3963
+ );
3964
+ }
3965
+ // ==========================================================================
3966
+ // Search Operations
3967
+ // ==========================================================================
3968
+ async searchMessages(chatId, query, options) {
3969
+ const limit = options?.limit ?? 20;
3970
+ const roles = options?.roles;
3971
+ let sql = `
3972
+ SELECT
3973
+ m.id,
3974
+ m.chatId,
3975
+ m.parentId,
3976
+ m.name,
3977
+ m.type,
3978
+ m.data,
3979
+ m.createdAt,
3980
+ ts_rank(fts.content_vector, plainto_tsquery('english', $2)) as rank,
3981
+ ts_headline('english', fts.content, plainto_tsquery('english', $2),
3982
+ 'StartSel=<mark>, StopSel=</mark>, MaxWords=32, MinWords=5, MaxFragments=1') as snippet
3983
+ FROM messages_fts fts
3984
+ JOIN messages m ON m.id = fts.messageId
3985
+ WHERE fts.content_vector @@ plainto_tsquery('english', $2)
3986
+ AND fts.chatId = $1
3987
+ `;
3988
+ const params = [chatId, query];
3989
+ let paramIndex = 3;
3990
+ if (roles && roles.length > 0) {
3991
+ const placeholders = roles.map(() => `$${paramIndex++}`).join(", ");
3992
+ sql += ` AND fts.name IN (${placeholders})`;
3993
+ params.push(...roles);
3994
+ }
3995
+ sql += ` ORDER BY rank DESC LIMIT $${paramIndex}`;
3996
+ params.push(limit);
3997
+ const rows = await this.#query(sql, params);
3998
+ return rows.map((row) => ({
3999
+ message: {
4000
+ id: row.id,
4001
+ chatId: row.chatid,
4002
+ parentId: row.parentid,
4003
+ name: row.name,
4004
+ type: row.type ?? void 0,
4005
+ data: row.data,
4006
+ createdAt: Number(row.createdat)
4007
+ },
4008
+ rank: row.rank,
4009
+ snippet: row.snippet
4010
+ }));
4011
+ }
4012
+ // ==========================================================================
4013
+ // Visualization Operations
4014
+ // ==========================================================================
4015
+ async getGraph(chatId) {
4016
+ const messageRows = await this.#query(
4017
+ `SELECT id, parentId, name, data, createdAt
4018
+ FROM messages
4019
+ WHERE chatId = $1
4020
+ ORDER BY createdAt ASC`,
4021
+ [chatId]
4022
+ );
4023
+ const nodes = messageRows.map((row) => {
4024
+ const data = row.data;
4025
+ const content = typeof data === "string" ? data : JSON.stringify(data);
4026
+ return {
4027
+ id: row.id,
4028
+ parentId: row.parentid,
4029
+ role: row.name,
4030
+ content: content.length > 50 ? content.slice(0, 50) + "..." : content,
4031
+ createdAt: Number(row.createdat)
4032
+ };
4033
+ });
4034
+ const branchRows = await this.#query(
4035
+ `SELECT name, headMessageId, isActive
4036
+ FROM branches
4037
+ WHERE chatId = $1
4038
+ ORDER BY createdAt ASC`,
4039
+ [chatId]
4040
+ );
4041
+ const branches = branchRows.map((row) => ({
4042
+ name: row.name,
4043
+ headMessageId: row.headmessageid,
4044
+ isActive: row.isactive
4045
+ }));
4046
+ const checkpointRows = await this.#query(
4047
+ `SELECT name, messageId
4048
+ FROM checkpoints
4049
+ WHERE chatId = $1
4050
+ ORDER BY createdAt ASC`,
4051
+ [chatId]
4052
+ );
4053
+ const checkpoints = checkpointRows.map((row) => ({
4054
+ name: row.name,
4055
+ messageId: row.messageid
4056
+ }));
4057
+ return {
4058
+ chatId,
4059
+ nodes,
4060
+ branches,
4061
+ checkpoints
4062
+ };
4063
+ }
4064
+ };
4065
+
4066
+ // packages/context/src/lib/store/sqlserver.store.ts
4067
+ import { createRequire as createRequire2 } from "node:module";
4068
+
4069
+ // packages/context/src/lib/store/ddl.sqlserver.sql
4070
+ var ddl_sqlserver_default = "-- Context Store DDL for SQL Server\n-- This schema implements a DAG-based message history with branching and checkpoints.\n\n-- Chats table\n-- createdAt/updatedAt: DEFAULT for insert, inline SET for updates\nIF OBJECT_ID('chats', 'U') IS NULL\nBEGIN\n CREATE TABLE chats (\n id NVARCHAR(255) PRIMARY KEY,\n userId NVARCHAR(255) NOT NULL,\n title NVARCHAR(MAX),\n metadata NVARCHAR(MAX),\n createdAt BIGINT NOT NULL DEFAULT DATEDIFF_BIG(ms, '1970-01-01', GETUTCDATE()),\n updatedAt BIGINT NOT NULL DEFAULT DATEDIFF_BIG(ms, '1970-01-01', GETUTCDATE())\n );\nEND;\n\nIF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'idx_chats_updatedAt' AND object_id = OBJECT_ID('chats'))\n CREATE INDEX idx_chats_updatedAt ON chats(updatedAt);\n\nIF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'idx_chats_userId' AND object_id = OBJECT_ID('chats'))\n CREATE INDEX idx_chats_userId ON chats(userId);\n\n-- Messages table (nodes in the DAG)\nIF OBJECT_ID('messages', 'U') IS NULL\nBEGIN\n CREATE TABLE messages (\n id NVARCHAR(255) PRIMARY KEY,\n chatId NVARCHAR(255) NOT NULL,\n parentId NVARCHAR(255),\n name NVARCHAR(255) NOT NULL,\n type NVARCHAR(255),\n data NVARCHAR(MAX) NOT NULL,\n createdAt BIGINT NOT NULL,\n FOREIGN KEY (chatId) REFERENCES chats(id) ON DELETE CASCADE,\n FOREIGN KEY (parentId) REFERENCES messages(id)\n );\nEND;\n\nIF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'idx_messages_chatId' AND object_id = OBJECT_ID('messages'))\n CREATE INDEX idx_messages_chatId ON messages(chatId);\n\nIF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'idx_messages_parentId' AND object_id = OBJECT_ID('messages'))\n CREATE INDEX idx_messages_parentId ON messages(parentId);\n\nIF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'idx_messages_chatId_parentId' AND object_id = OBJECT_ID('messages'))\n CREATE INDEX idx_messages_chatId_parentId ON messages(chatId, parentId);\n\n-- Branches table (pointers to head messages)\nIF OBJECT_ID('branches', 'U') IS NULL\nBEGIN\n CREATE TABLE branches (\n id NVARCHAR(255) PRIMARY KEY,\n chatId NVARCHAR(255) NOT NULL,\n name NVARCHAR(255) NOT NULL,\n headMessageId NVARCHAR(255),\n isActive BIT NOT NULL DEFAULT 0,\n createdAt BIGINT NOT NULL,\n FOREIGN KEY (chatId) REFERENCES chats(id) ON DELETE CASCADE,\n FOREIGN KEY (headMessageId) REFERENCES messages(id),\n CONSTRAINT UQ_branches_chatId_name UNIQUE(chatId, name)\n );\nEND;\n\nIF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'idx_branches_chatId' AND object_id = OBJECT_ID('branches'))\n CREATE INDEX idx_branches_chatId ON branches(chatId);\n\nIF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'idx_branches_chatId_isActive' AND object_id = OBJECT_ID('branches'))\n CREATE INDEX idx_branches_chatId_isActive ON branches(chatId, isActive);\n\n-- Checkpoints table (pointers to message nodes)\nIF OBJECT_ID('checkpoints', 'U') IS NULL\nBEGIN\n CREATE TABLE checkpoints (\n id NVARCHAR(255) PRIMARY KEY,\n chatId NVARCHAR(255) NOT NULL,\n name NVARCHAR(255) NOT NULL,\n messageId NVARCHAR(255) NOT NULL,\n createdAt BIGINT NOT NULL,\n FOREIGN KEY (chatId) REFERENCES chats(id) ON DELETE CASCADE,\n FOREIGN KEY (messageId) REFERENCES messages(id),\n CONSTRAINT UQ_checkpoints_chatId_name UNIQUE(chatId, name)\n );\nEND;\n\nIF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'idx_checkpoints_chatId' AND object_id = OBJECT_ID('checkpoints'))\n CREATE INDEX idx_checkpoints_chatId ON checkpoints(chatId);\n\n-- Full-text search table\nIF OBJECT_ID('messages_fts', 'U') IS NULL\nBEGIN\n CREATE TABLE messages_fts (\n messageId NVARCHAR(255) NOT NULL,\n chatId NVARCHAR(255) NOT NULL,\n name NVARCHAR(255) NOT NULL,\n content NVARCHAR(MAX) NOT NULL,\n CONSTRAINT PK_messages_fts PRIMARY KEY (messageId),\n FOREIGN KEY (messageId) REFERENCES messages(id) ON DELETE CASCADE\n );\nEND;\n\nIF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'idx_messages_fts_chatId' AND object_id = OBJECT_ID('messages_fts'))\n CREATE INDEX idx_messages_fts_chatId ON messages_fts(chatId);\n\n-- Full-text catalog and index (only if FTS is installed)\n-- FTS is optional - search will gracefully degrade without it\nIF SERVERPROPERTY('IsFullTextInstalled') = 1\nBEGIN\n -- Create catalog if not exists\n IF NOT EXISTS (SELECT * FROM sys.fulltext_catalogs WHERE name = 'context_store_catalog')\n CREATE FULLTEXT CATALOG context_store_catalog AS DEFAULT;\n\n -- Create full-text index on messages_fts.content\n -- Note: This requires the table to have a unique index, which PK provides\n IF NOT EXISTS (SELECT * FROM sys.fulltext_indexes WHERE object_id = OBJECT_ID('messages_fts'))\n BEGIN\n CREATE FULLTEXT INDEX ON messages_fts(content)\n KEY INDEX PK_messages_fts\n ON context_store_catalog\n WITH STOPLIST = SYSTEM;\n END;\nEND;\n";
4071
+
4072
+ // packages/context/src/lib/store/sqlserver.store.ts
4073
+ var SqlServerContextStore = class _SqlServerContextStore extends ContextStore {
4074
+ #pool;
4075
+ #initialized;
4076
+ constructor(options) {
4077
+ super();
4078
+ const mssql = _SqlServerContextStore.#requireMssql();
4079
+ this.#pool = typeof options.pool === "string" ? new mssql.ConnectionPool(options.pool) : new mssql.ConnectionPool(options.pool);
4080
+ this.#initialized = this.#initialize();
4081
+ }
4082
+ static #requireMssql() {
4083
+ try {
4084
+ const require2 = createRequire2(import.meta.url);
4085
+ return require2("mssql");
4086
+ } catch {
4087
+ throw new Error(
4088
+ 'SqlServerContextStore requires the "mssql" package. Install it with: npm install mssql'
4089
+ );
4090
+ }
4091
+ }
4092
+ async #initialize() {
4093
+ await this.#pool.connect();
4094
+ const batches = ddl_sqlserver_default.split(/\bGO\b/i).filter((b) => b.trim());
4095
+ for (const batch of batches) {
4096
+ if (batch.trim()) {
4097
+ await this.#pool.request().batch(batch);
4098
+ }
4099
+ }
4100
+ }
4101
+ /**
4102
+ * Ensure initialization is complete before any operation.
4103
+ */
4104
+ async #ensureInitialized() {
4105
+ await this.#initialized;
4106
+ }
4107
+ /**
4108
+ * Execute a function within a transaction.
4109
+ * Automatically commits on success or rolls back on error.
4110
+ */
4111
+ async #useTransaction(fn) {
4112
+ await this.#ensureInitialized();
4113
+ const mssql = _SqlServerContextStore.#requireMssql();
4114
+ const transaction = new mssql.Transaction(this.#pool);
4115
+ try {
4116
+ await transaction.begin();
4117
+ const result = await fn(transaction);
4118
+ await transaction.commit();
4119
+ return result;
4120
+ } catch (error) {
4121
+ await transaction.rollback();
4122
+ throw error;
4123
+ }
4124
+ }
4125
+ /**
4126
+ * Execute a query using the pool (no transaction).
4127
+ * Converts positional params to SQL Server named params (@p0, @p1, ...).
4128
+ */
4129
+ async #query(sql, params) {
4130
+ await this.#ensureInitialized();
4131
+ const request = this.#pool.request();
4132
+ params?.forEach((value, index) => {
4133
+ request.input(`p${index}`, value);
4134
+ });
4135
+ const result = await request.query(sql);
4136
+ return result.recordset;
4137
+ }
4138
+ /**
4139
+ * Close the pool connection.
4140
+ * Call this when done with the store.
4141
+ */
4142
+ async close() {
4143
+ await this.#pool.close();
4144
+ }
4145
+ // ==========================================================================
4146
+ // Chat Operations
4147
+ // ==========================================================================
4148
+ async createChat(chat) {
4149
+ return this.#useTransaction(async (transaction) => {
4150
+ const mssql = _SqlServerContextStore.#requireMssql();
4151
+ const request = transaction.request();
4152
+ request.input("p0", mssql.NVarChar, chat.id);
4153
+ request.input("p1", mssql.NVarChar, chat.userId);
4154
+ request.input("p2", mssql.NVarChar, chat.title ?? null);
4155
+ request.input(
4156
+ "p3",
4157
+ mssql.NVarChar,
4158
+ chat.metadata ? JSON.stringify(chat.metadata) : null
4159
+ );
4160
+ const result = await request.query(`
4161
+ INSERT INTO chats (id, userId, title, metadata)
4162
+ OUTPUT INSERTED.*
4163
+ VALUES (@p0, @p1, @p2, @p3)
4164
+ `);
4165
+ const row = result.recordset[0];
4166
+ const branchRequest = transaction.request();
4167
+ branchRequest.input("p0", mssql.NVarChar, crypto.randomUUID());
4168
+ branchRequest.input("p1", mssql.NVarChar, chat.id);
4169
+ branchRequest.input("p2", mssql.BigInt, Date.now());
4170
+ await branchRequest.query(`
4171
+ INSERT INTO branches (id, chatId, name, headMessageId, isActive, createdAt)
4172
+ VALUES (@p0, @p1, 'main', NULL, 1, @p2)
4173
+ `);
4174
+ return {
4175
+ id: row.id,
4176
+ userId: row.userId,
4177
+ title: row.title ?? void 0,
4178
+ metadata: row.metadata ? JSON.parse(row.metadata) : void 0,
4179
+ createdAt: Number(row.createdAt),
4180
+ updatedAt: Number(row.updatedAt)
4181
+ };
4182
+ });
4183
+ }
4184
+ async upsertChat(chat) {
4185
+ return this.#useTransaction(async (transaction) => {
4186
+ const mssql = _SqlServerContextStore.#requireMssql();
4187
+ const request = transaction.request();
4188
+ request.input("p0", mssql.NVarChar, chat.id);
4189
+ request.input("p1", mssql.NVarChar, chat.userId);
4190
+ request.input("p2", mssql.NVarChar, chat.title ?? null);
4191
+ request.input(
4192
+ "p3",
4193
+ mssql.NVarChar,
4194
+ chat.metadata ? JSON.stringify(chat.metadata) : null
4195
+ );
4196
+ request.input("p4", mssql.BigInt, BigInt(Date.now()));
4197
+ const result = await request.query(`
4198
+ MERGE chats AS target
4199
+ USING (SELECT @p0 AS id, @p1 AS userId, @p2 AS title, @p3 AS metadata) AS source
4200
+ ON target.id = source.id
4201
+ WHEN MATCHED THEN
4202
+ UPDATE SET id = target.id
4203
+ WHEN NOT MATCHED THEN
4204
+ INSERT (id, userId, title, metadata, createdAt, updatedAt)
4205
+ VALUES (source.id, source.userId, source.title, source.metadata, @p4, @p4)
4206
+ OUTPUT INSERTED.*;
4207
+ `);
4208
+ const row = result.recordset[0];
4209
+ const branchRequest = transaction.request();
4210
+ branchRequest.input("p0", mssql.NVarChar, crypto.randomUUID());
4211
+ branchRequest.input("p1", mssql.NVarChar, chat.id);
4212
+ branchRequest.input("p2", mssql.BigInt, Date.now());
4213
+ await branchRequest.query(`
4214
+ IF NOT EXISTS (SELECT 1 FROM branches WHERE chatId = @p1 AND name = 'main')
4215
+ BEGIN
4216
+ INSERT INTO branches (id, chatId, name, headMessageId, isActive, createdAt)
4217
+ VALUES (@p0, @p1, 'main', NULL, 1, @p2)
4218
+ END
4219
+ `);
4220
+ return {
4221
+ id: row.id,
4222
+ userId: row.userId,
4223
+ title: row.title ?? void 0,
4224
+ metadata: row.metadata ? JSON.parse(row.metadata) : void 0,
4225
+ createdAt: Number(row.createdAt),
4226
+ updatedAt: Number(row.updatedAt)
4227
+ };
4228
+ });
4229
+ }
4230
+ async getChat(chatId) {
4231
+ const rows = await this.#query("SELECT * FROM chats WHERE id = @p0", [chatId]);
4232
+ if (rows.length === 0) {
4233
+ return void 0;
4234
+ }
4235
+ const row = rows[0];
4236
+ return {
4237
+ id: row.id,
4238
+ userId: row.userId,
4239
+ title: row.title ?? void 0,
4240
+ metadata: row.metadata ? JSON.parse(row.metadata) : void 0,
4241
+ createdAt: Number(row.createdAt),
4242
+ updatedAt: Number(row.updatedAt)
4243
+ };
4244
+ }
4245
+ async updateChat(chatId, updates) {
4246
+ const setClauses = [
4247
+ "updatedAt = DATEDIFF_BIG(ms, '1970-01-01', GETUTCDATE())"
4248
+ ];
4249
+ const params = [];
4250
+ let paramIndex = 0;
4251
+ if (updates.title !== void 0) {
4252
+ setClauses.push(`title = @p${paramIndex++}`);
4253
+ params.push(updates.title ?? null);
4254
+ }
4255
+ if (updates.metadata !== void 0) {
4256
+ setClauses.push(`metadata = @p${paramIndex++}`);
4257
+ params.push(JSON.stringify(updates.metadata));
4258
+ }
4259
+ params.push(chatId);
4260
+ const rows = await this.#query(
4261
+ `UPDATE chats SET ${setClauses.join(", ")} OUTPUT INSERTED.* WHERE id = @p${paramIndex}`,
4262
+ params
4263
+ );
4264
+ const row = rows[0];
4265
+ return {
4266
+ id: row.id,
4267
+ userId: row.userId,
4268
+ title: row.title ?? void 0,
4269
+ metadata: row.metadata ? JSON.parse(row.metadata) : void 0,
4270
+ createdAt: Number(row.createdAt),
4271
+ updatedAt: Number(row.updatedAt)
4272
+ };
4273
+ }
4274
+ async listChats(options) {
4275
+ const params = [];
4276
+ const whereClauses = [];
4277
+ let paramIndex = 0;
4278
+ if (options?.userId) {
4279
+ whereClauses.push(`c.userId = @p${paramIndex++}`);
4280
+ params.push(options.userId);
4281
+ }
4282
+ if (options?.metadata) {
4283
+ whereClauses.push(
4284
+ `JSON_VALUE(c.metadata, '$.' + @p${paramIndex}) = @p${paramIndex + 1}`
4285
+ );
4286
+ params.push(options.metadata.key);
4287
+ params.push(String(options.metadata.value));
4288
+ paramIndex += 2;
4289
+ }
4290
+ const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : "";
4291
+ let paginationClause = "";
4292
+ if (options?.limit !== void 0) {
4293
+ paginationClause = ` OFFSET @p${paramIndex} ROWS FETCH NEXT @p${paramIndex + 1} ROWS ONLY`;
4294
+ params.push(options.offset ?? 0);
4295
+ params.push(options.limit);
4296
+ }
4297
+ const rows = await this.#query(
4298
+ `SELECT
4299
+ c.id,
4300
+ c.userId,
4301
+ c.title,
4302
+ c.metadata,
4303
+ c.createdAt,
4304
+ c.updatedAt,
4305
+ COUNT(DISTINCT m.id) as messageCount,
4306
+ COUNT(DISTINCT b.id) as branchCount
4307
+ FROM chats c
4308
+ LEFT JOIN messages m ON m.chatId = c.id
4309
+ LEFT JOIN branches b ON b.chatId = c.id
4310
+ ${whereClause}
4311
+ GROUP BY c.id, c.userId, c.title, c.metadata, c.createdAt, c.updatedAt
4312
+ ORDER BY c.updatedAt DESC${paginationClause}`,
4313
+ params
4314
+ );
4315
+ return rows.map((row) => ({
4316
+ id: row.id,
4317
+ userId: row.userId,
4318
+ title: row.title ?? void 0,
4319
+ metadata: row.metadata ? JSON.parse(row.metadata) : void 0,
4320
+ messageCount: Number(row.messageCount),
4321
+ branchCount: Number(row.branchCount),
4322
+ createdAt: Number(row.createdAt),
4323
+ updatedAt: Number(row.updatedAt)
4324
+ }));
4325
+ }
4326
+ async deleteChat(chatId, options) {
4327
+ return this.#useTransaction(async (transaction) => {
4328
+ const mssql = _SqlServerContextStore.#requireMssql();
4329
+ const request = transaction.request();
4330
+ request.input("p0", mssql.NVarChar, chatId);
4331
+ let sql = "DELETE FROM chats WHERE id = @p0";
4332
+ if (options?.userId !== void 0) {
4333
+ request.input("p1", mssql.NVarChar, options.userId);
4334
+ sql += " AND userId = @p1";
4335
+ }
4336
+ const result = await request.query(sql);
4337
+ return (result.rowsAffected[0] ?? 0) > 0;
4338
+ });
4339
+ }
4340
+ // ==========================================================================
4341
+ // Message Operations (Graph Nodes)
4342
+ // ==========================================================================
4343
+ async addMessage(message2) {
4344
+ if (message2.parentId === message2.id) {
4345
+ throw new Error(`Message ${message2.id} cannot be its own parent`);
4346
+ }
4347
+ await this.#useTransaction(async (transaction) => {
4348
+ const mssql = _SqlServerContextStore.#requireMssql();
4349
+ const request = transaction.request();
4350
+ request.input("p0", mssql.NVarChar, message2.id);
4351
+ request.input("p1", mssql.NVarChar, message2.chatId);
4352
+ request.input("p2", mssql.NVarChar, message2.parentId);
4353
+ request.input("p3", mssql.NVarChar, message2.name);
4354
+ request.input("p4", mssql.NVarChar, message2.type ?? null);
4355
+ request.input("p5", mssql.NVarChar, JSON.stringify(message2.data));
4356
+ request.input("p6", mssql.BigInt, message2.createdAt);
4357
+ await request.query(`
4358
+ MERGE messages AS target
4359
+ USING (SELECT @p0 AS id) AS source
4360
+ ON target.id = source.id
4361
+ WHEN MATCHED THEN
4362
+ UPDATE SET name = @p3, type = @p4, data = @p5
4363
+ WHEN NOT MATCHED THEN
4364
+ INSERT (id, chatId, parentId, name, type, data, createdAt)
4365
+ VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6);
4366
+ `);
4367
+ const content = typeof message2.data === "string" ? message2.data : JSON.stringify(message2.data);
4368
+ const ftsRequest = transaction.request();
4369
+ ftsRequest.input("p0", mssql.NVarChar, message2.id);
4370
+ ftsRequest.input("p1", mssql.NVarChar, message2.chatId);
4371
+ ftsRequest.input("p2", mssql.NVarChar, message2.name);
4372
+ ftsRequest.input("p3", mssql.NVarChar, content);
4373
+ await ftsRequest.query(`
4374
+ MERGE messages_fts AS target
4375
+ USING (SELECT @p0 AS messageId) AS source
4376
+ ON target.messageId = source.messageId
4377
+ WHEN MATCHED THEN
4378
+ UPDATE SET chatId = @p1, name = @p2, content = @p3
4379
+ WHEN NOT MATCHED THEN
4380
+ INSERT (messageId, chatId, name, content)
4381
+ VALUES (@p0, @p1, @p2, @p3);
4382
+ `);
4383
+ });
4384
+ }
4385
+ async getMessage(messageId) {
4386
+ const rows = await this.#query("SELECT * FROM messages WHERE id = @p0", [messageId]);
4387
+ if (rows.length === 0) {
4388
+ return void 0;
4389
+ }
4390
+ const row = rows[0];
4391
+ return {
4392
+ id: row.id,
4393
+ chatId: row.chatId,
4394
+ parentId: row.parentId,
4395
+ name: row.name,
4396
+ type: row.type ?? void 0,
4397
+ data: JSON.parse(row.data),
4398
+ createdAt: Number(row.createdAt)
4399
+ };
4400
+ }
4401
+ async getMessageChain(headId) {
4402
+ const rows = await this.#query(
4403
+ `WITH chain AS (
4404
+ SELECT *, 0 as depth FROM messages WHERE id = @p0
4405
+ UNION ALL
4406
+ SELECT m.*, c.depth + 1 FROM messages m
4407
+ INNER JOIN chain c ON m.id = c.parentId
4408
+ WHERE c.depth < 10000
4409
+ )
4410
+ SELECT * FROM chain
4411
+ ORDER BY depth DESC`,
4412
+ [headId]
4413
+ );
4414
+ return rows.map((row) => ({
4415
+ id: row.id,
4416
+ chatId: row.chatId,
4417
+ parentId: row.parentId,
4418
+ name: row.name,
4419
+ type: row.type ?? void 0,
4420
+ data: JSON.parse(row.data),
4421
+ createdAt: Number(row.createdAt)
4422
+ }));
4423
+ }
4424
+ async hasChildren(messageId) {
4425
+ const rows = await this.#query(
4426
+ `SELECT CASE WHEN EXISTS(SELECT 1 FROM messages WHERE parentId = @p0) THEN 1 ELSE 0 END as hasChildren`,
4427
+ [messageId]
4428
+ );
4429
+ return rows[0].hasChildren === 1;
4430
+ }
4431
+ async getMessages(chatId) {
4432
+ const chat = await this.getChat(chatId);
4433
+ if (!chat) {
4434
+ throw new Error(`Chat "${chatId}" not found`);
4435
+ }
4436
+ const activeBranch = await this.getActiveBranch(chatId);
4437
+ if (!activeBranch?.headMessageId) {
4438
+ return [];
4439
+ }
4440
+ return this.getMessageChain(activeBranch.headMessageId);
4441
+ }
4442
+ // ==========================================================================
4443
+ // Branch Operations
4444
+ // ==========================================================================
4445
+ async createBranch(branch) {
4446
+ await this.#query(
4447
+ `INSERT INTO branches (id, chatId, name, headMessageId, isActive, createdAt)
4448
+ VALUES (@p0, @p1, @p2, @p3, @p4, @p5)`,
4449
+ [
4450
+ branch.id,
4451
+ branch.chatId,
4452
+ branch.name,
4453
+ branch.headMessageId,
4454
+ branch.isActive ? 1 : 0,
4455
+ branch.createdAt
4456
+ ]
4457
+ );
4458
+ }
4459
+ async getBranch(chatId, name) {
4460
+ const rows = await this.#query("SELECT * FROM branches WHERE chatId = @p0 AND name = @p1", [
4461
+ chatId,
4462
+ name
4463
+ ]);
4464
+ if (rows.length === 0) {
4465
+ return void 0;
4466
+ }
4467
+ const row = rows[0];
4468
+ return {
4469
+ id: row.id,
4470
+ chatId: row.chatId,
4471
+ name: row.name,
4472
+ headMessageId: row.headMessageId,
4473
+ isActive: row.isActive === true || row.isActive === 1,
4474
+ createdAt: Number(row.createdAt)
4475
+ };
4476
+ }
4477
+ async getActiveBranch(chatId) {
4478
+ const rows = await this.#query("SELECT * FROM branches WHERE chatId = @p0 AND isActive = 1", [chatId]);
4479
+ if (rows.length === 0) {
4480
+ return void 0;
4481
+ }
4482
+ const row = rows[0];
4483
+ return {
4484
+ id: row.id,
4485
+ chatId: row.chatId,
4486
+ name: row.name,
4487
+ headMessageId: row.headMessageId,
4488
+ isActive: true,
4489
+ createdAt: Number(row.createdAt)
4490
+ };
4491
+ }
4492
+ async setActiveBranch(chatId, branchId) {
4493
+ await this.#useTransaction(async (transaction) => {
4494
+ const mssql = _SqlServerContextStore.#requireMssql();
4495
+ const deactivateRequest = transaction.request();
4496
+ deactivateRequest.input("p0", mssql.NVarChar, chatId);
4497
+ await deactivateRequest.query(
4498
+ "UPDATE branches SET isActive = 0 WHERE chatId = @p0"
4499
+ );
4500
+ const activateRequest = transaction.request();
4501
+ activateRequest.input("p0", mssql.NVarChar, branchId);
4502
+ await activateRequest.query(
4503
+ "UPDATE branches SET isActive = 1 WHERE id = @p0"
4504
+ );
4505
+ });
4506
+ }
4507
+ async updateBranchHead(branchId, messageId) {
4508
+ await this.#query(
4509
+ "UPDATE branches SET headMessageId = @p0 WHERE id = @p1",
4510
+ [messageId, branchId]
4511
+ );
4512
+ }
4513
+ async listBranches(chatId) {
4514
+ const branches = await this.#query(
4515
+ `SELECT
4516
+ id,
4517
+ name,
4518
+ headMessageId,
4519
+ isActive,
4520
+ createdAt
4521
+ FROM branches
4522
+ WHERE chatId = @p0
4523
+ ORDER BY createdAt ASC`,
4524
+ [chatId]
4525
+ );
4526
+ const result = [];
4527
+ for (const branch of branches) {
4528
+ let messageCount = 0;
4529
+ if (branch.headMessageId) {
4530
+ const countRows = await this.#query(
4531
+ `WITH chain AS (
4532
+ SELECT id, parentId FROM messages WHERE id = @p0
4533
+ UNION ALL
4534
+ SELECT m.id, m.parentId FROM messages m
4535
+ INNER JOIN chain c ON m.id = c.parentId
4536
+ )
4537
+ SELECT COUNT(*) as count FROM chain`,
4538
+ [branch.headMessageId]
4539
+ );
4540
+ messageCount = Number(countRows[0].count);
4541
+ }
4542
+ result.push({
4543
+ id: branch.id,
4544
+ name: branch.name,
4545
+ headMessageId: branch.headMessageId,
4546
+ isActive: branch.isActive === true || branch.isActive === 1,
4547
+ messageCount,
4548
+ createdAt: Number(branch.createdAt)
4549
+ });
4550
+ }
4551
+ return result;
4552
+ }
4553
+ // ==========================================================================
4554
+ // Checkpoint Operations
4555
+ // ==========================================================================
4556
+ async createCheckpoint(checkpoint) {
4557
+ await this.#useTransaction(async (transaction) => {
4558
+ const mssql = _SqlServerContextStore.#requireMssql();
4559
+ const request = transaction.request();
4560
+ request.input("p0", mssql.NVarChar, checkpoint.id);
4561
+ request.input("p1", mssql.NVarChar, checkpoint.chatId);
4562
+ request.input("p2", mssql.NVarChar, checkpoint.name);
4563
+ request.input("p3", mssql.NVarChar, checkpoint.messageId);
4564
+ request.input("p4", mssql.BigInt, checkpoint.createdAt);
4565
+ await request.query(`
4566
+ MERGE checkpoints AS target
4567
+ USING (SELECT @p1 AS chatId, @p2 AS name) AS source
4568
+ ON target.chatId = source.chatId AND target.name = source.name
4569
+ WHEN MATCHED THEN
4570
+ UPDATE SET messageId = @p3, createdAt = @p4
4571
+ WHEN NOT MATCHED THEN
4572
+ INSERT (id, chatId, name, messageId, createdAt)
4573
+ VALUES (@p0, @p1, @p2, @p3, @p4);
4574
+ `);
4575
+ });
4576
+ }
4577
+ async getCheckpoint(chatId, name) {
4578
+ const rows = await this.#query("SELECT * FROM checkpoints WHERE chatId = @p0 AND name = @p1", [
4579
+ chatId,
4580
+ name
4581
+ ]);
4582
+ if (rows.length === 0) {
4583
+ return void 0;
4584
+ }
4585
+ const row = rows[0];
4586
+ return {
4587
+ id: row.id,
4588
+ chatId: row.chatId,
4589
+ name: row.name,
4590
+ messageId: row.messageId,
4591
+ createdAt: Number(row.createdAt)
4592
+ };
4593
+ }
4594
+ async listCheckpoints(chatId) {
4595
+ const rows = await this.#query(
4596
+ `SELECT id, name, messageId, createdAt
4597
+ FROM checkpoints
4598
+ WHERE chatId = @p0
4599
+ ORDER BY createdAt DESC`,
4600
+ [chatId]
4601
+ );
4602
+ return rows.map((row) => ({
4603
+ id: row.id,
4604
+ name: row.name,
4605
+ messageId: row.messageId,
4606
+ createdAt: Number(row.createdAt)
4607
+ }));
4608
+ }
4609
+ async deleteCheckpoint(chatId, name) {
4610
+ await this.#query(
4611
+ "DELETE FROM checkpoints WHERE chatId = @p0 AND name = @p1",
4612
+ [chatId, name]
4613
+ );
4614
+ }
4615
+ // ==========================================================================
4616
+ // Search Operations
4617
+ // ==========================================================================
4618
+ async searchMessages(chatId, query, options) {
4619
+ const limit = options?.limit ?? 20;
4620
+ const roles = options?.roles;
4621
+ const ftsCheck = await this.#query(
4622
+ `SELECT CAST(SERVERPROPERTY('IsFullTextInstalled') AS INT) as ftsInstalled`
4623
+ );
4624
+ const ftsAvailable = ftsCheck[0]?.ftsInstalled === 1;
4625
+ if (ftsAvailable) {
4626
+ let sql = `
4627
+ SELECT
4628
+ m.id,
4629
+ m.chatId,
4630
+ m.parentId,
4631
+ m.name,
4632
+ m.type,
4633
+ m.data,
4634
+ m.createdAt,
4635
+ ct.RANK as rank,
4636
+ SUBSTRING(fts.content, 1, 200) as snippet
4637
+ FROM messages_fts fts
4638
+ INNER JOIN CONTAINSTABLE(messages_fts, content, @p0) ct
4639
+ ON fts.messageId = ct.[KEY]
4640
+ INNER JOIN messages m ON m.id = fts.messageId
4641
+ WHERE fts.chatId = @p1
4642
+ `;
4643
+ const params = [query, chatId];
4644
+ let paramIndex = 2;
4645
+ if (roles && roles.length > 0) {
4646
+ const placeholders = roles.map(() => `@p${paramIndex++}`).join(", ");
4647
+ sql += ` AND fts.name IN (${placeholders})`;
4648
+ params.push(...roles);
4649
+ }
4650
+ sql += ` ORDER BY ct.RANK DESC OFFSET 0 ROWS FETCH NEXT @p${paramIndex} ROWS ONLY`;
4651
+ params.push(limit);
4652
+ const rows = await this.#query(sql, params);
4653
+ return rows.map((row) => ({
4654
+ message: {
4655
+ id: row.id,
4656
+ chatId: row.chatId,
4657
+ parentId: row.parentId,
4658
+ name: row.name,
4659
+ type: row.type ?? void 0,
4660
+ data: JSON.parse(row.data),
4661
+ createdAt: Number(row.createdAt)
4662
+ },
4663
+ rank: row.rank,
4664
+ snippet: row.snippet
4665
+ }));
4666
+ } else {
4667
+ let sql = `
4668
+ SELECT
4669
+ m.id,
4670
+ m.chatId,
4671
+ m.parentId,
4672
+ m.name,
4673
+ m.type,
4674
+ m.data,
4675
+ m.createdAt,
4676
+ 1 as rank,
4677
+ SUBSTRING(fts.content, 1, 200) as snippet
4678
+ FROM messages_fts fts
4679
+ INNER JOIN messages m ON m.id = fts.messageId
4680
+ WHERE fts.chatId = @p0 AND fts.content LIKE '%' + @p1 + '%'
4681
+ `;
4682
+ const params = [chatId, query];
4683
+ let paramIndex = 2;
4684
+ if (roles && roles.length > 0) {
4685
+ const placeholders = roles.map(() => `@p${paramIndex++}`).join(", ");
4686
+ sql += ` AND fts.name IN (${placeholders})`;
4687
+ params.push(...roles);
4688
+ }
4689
+ sql += ` ORDER BY m.createdAt DESC OFFSET 0 ROWS FETCH NEXT @p${paramIndex} ROWS ONLY`;
4690
+ params.push(limit);
4691
+ const rows = await this.#query(sql, params);
4692
+ return rows.map((row) => ({
4693
+ message: {
4694
+ id: row.id,
4695
+ chatId: row.chatId,
4696
+ parentId: row.parentId,
4697
+ name: row.name,
4698
+ type: row.type ?? void 0,
4699
+ data: JSON.parse(row.data),
4700
+ createdAt: Number(row.createdAt)
4701
+ },
4702
+ rank: row.rank,
4703
+ snippet: row.snippet
4704
+ }));
4705
+ }
4706
+ }
4707
+ // ==========================================================================
4708
+ // Visualization Operations
4709
+ // ==========================================================================
4710
+ async getGraph(chatId) {
4711
+ const messageRows = await this.#query(
4712
+ `SELECT id, parentId, name, data, createdAt
4713
+ FROM messages
4714
+ WHERE chatId = @p0
4715
+ ORDER BY createdAt ASC`,
4716
+ [chatId]
4717
+ );
4718
+ const nodes = messageRows.map((row) => {
4719
+ const data = JSON.parse(row.data);
4720
+ const content = typeof data === "string" ? data : JSON.stringify(data);
4721
+ return {
4722
+ id: row.id,
4723
+ parentId: row.parentId,
4724
+ role: row.name,
4725
+ content: content.length > 50 ? content.slice(0, 50) + "..." : content,
4726
+ createdAt: Number(row.createdAt)
4727
+ };
4728
+ });
4729
+ const branchRows = await this.#query(
4730
+ `SELECT name, headMessageId, isActive
4731
+ FROM branches
4732
+ WHERE chatId = @p0
4733
+ ORDER BY createdAt ASC`,
4734
+ [chatId]
4735
+ );
4736
+ const branches = branchRows.map((row) => ({
4737
+ name: row.name,
4738
+ headMessageId: row.headMessageId,
4739
+ isActive: row.isActive === true || row.isActive === 1
4740
+ }));
4741
+ const checkpointRows = await this.#query(
4742
+ `SELECT name, messageId
4743
+ FROM checkpoints
4744
+ WHERE chatId = @p0
4745
+ ORDER BY createdAt ASC`,
4746
+ [chatId]
4747
+ );
4748
+ const checkpoints = checkpointRows.map((row) => ({
4749
+ name: row.name,
4750
+ messageId: row.messageId
4751
+ }));
4752
+ return {
4753
+ chatId,
4754
+ nodes,
4755
+ branches,
4756
+ checkpoints
4757
+ };
4758
+ }
4759
+ };
4760
+
4761
+ // packages/context/src/lib/visualize.ts
4762
+ function visualizeGraph(data) {
4763
+ if (data.nodes.length === 0) {
4764
+ return `[chat: ${data.chatId}]
4765
+
4766
+ (empty)`;
4767
+ }
4768
+ const childrenByParentId = /* @__PURE__ */ new Map();
4769
+ const branchHeads = /* @__PURE__ */ new Map();
4770
+ const checkpointsByMessageId = /* @__PURE__ */ new Map();
4771
+ for (const node of data.nodes) {
4772
+ const children = childrenByParentId.get(node.parentId) ?? [];
4773
+ children.push(node);
4774
+ childrenByParentId.set(node.parentId, children);
4775
+ }
4776
+ for (const branch of data.branches) {
4777
+ if (branch.headMessageId) {
4778
+ const heads = branchHeads.get(branch.headMessageId) ?? [];
4779
+ heads.push(branch.isActive ? `${branch.name} *` : branch.name);
4780
+ branchHeads.set(branch.headMessageId, heads);
4781
+ }
4782
+ }
4783
+ for (const checkpoint of data.checkpoints) {
4784
+ const cps = checkpointsByMessageId.get(checkpoint.messageId) ?? [];
4785
+ cps.push(checkpoint.name);
4786
+ checkpointsByMessageId.set(checkpoint.messageId, cps);
4787
+ }
4788
+ const roots = childrenByParentId.get(null) ?? [];
4789
+ const lines = [`[chat: ${data.chatId}]`, ""];
4790
+ function renderNode(node, prefix, isLast, isRoot) {
4791
+ const connector = isRoot ? "" : isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
4792
+ const contentPreview = node.content.replace(/\n/g, " ");
4793
+ let line = `${prefix}${connector}${node.id.slice(0, 8)} (${node.role}): "${contentPreview}"`;
4794
+ const branches = branchHeads.get(node.id);
4795
+ if (branches) {
4796
+ line += ` <- [${branches.join(", ")}]`;
4797
+ }
4798
+ const checkpoints = checkpointsByMessageId.get(node.id);
4799
+ if (checkpoints) {
4800
+ line += ` {${checkpoints.join(", ")}}`;
4801
+ }
4802
+ lines.push(line);
4803
+ const children = childrenByParentId.get(node.id) ?? [];
4804
+ const childPrefix = isRoot ? "" : prefix + (isLast ? " " : "\u2502 ");
4805
+ for (let i = 0; i < children.length; i++) {
4806
+ renderNode(children[i], childPrefix, i === children.length - 1, false);
4807
+ }
4808
+ }
4809
+ for (let i = 0; i < roots.length; i++) {
4810
+ renderNode(roots[i], "", i === roots.length - 1, true);
4811
+ }
4812
+ lines.push("");
4813
+ lines.push("Legend: * = active branch, {...} = checkpoint");
4814
+ return lines.join("\n");
4815
+ }
4816
+
4817
+ // packages/context/src/lib/agent.ts
4818
+ import { groq } from "@ai-sdk/groq";
4819
+ import {
4820
+ NoSuchToolError,
4821
+ Output,
4822
+ convertToModelMessages,
4823
+ createUIMessageStream,
4824
+ generateId as generateId2,
4825
+ generateText,
4826
+ smoothStream,
4827
+ stepCountIs,
4828
+ streamText
4829
+ } from "ai";
4830
+ import chalk2 from "chalk";
4831
+ import "zod";
4832
+ import "@deepagents/agent";
4833
+ var Agent = class _Agent {
4834
+ #options;
4835
+ #guardrails = [];
4836
+ tools;
4837
+ constructor(options) {
4838
+ this.#options = options;
4839
+ this.tools = options.tools || {};
4840
+ this.#guardrails = options.guardrails || [];
4841
+ }
4842
+ async generate(contextVariables, config) {
4843
+ if (!this.#options.context) {
4844
+ throw new Error(`Agent ${this.#options.name} is missing a context.`);
4845
+ }
4846
+ if (!this.#options.model) {
4847
+ throw new Error(`Agent ${this.#options.name} is missing a model.`);
4848
+ }
4849
+ const { messages, systemPrompt } = await this.#options.context.resolve({
4850
+ renderer: new XmlRenderer()
4851
+ });
4852
+ return generateText({
4853
+ abortSignal: config?.abortSignal,
4854
+ providerOptions: this.#options.providerOptions,
4855
+ model: this.#options.model,
4856
+ system: systemPrompt,
4857
+ messages: await convertToModelMessages(messages),
4858
+ stopWhen: stepCountIs(25),
4859
+ tools: this.#options.tools,
4860
+ experimental_context: contextVariables,
4861
+ experimental_repairToolCall: repairToolCall,
4862
+ toolChoice: this.#options.toolChoice,
4863
+ onStepFinish: (step) => {
4864
+ const toolCall = step.toolCalls.at(-1);
4865
+ if (toolCall) {
4866
+ console.log(
4867
+ `Debug: ${chalk2.yellow("ToolCalled")}: ${toolCall.toolName}(${JSON.stringify(toolCall.input)})`
4868
+ );
4869
+ }
4870
+ }
4871
+ });
4872
+ }
4873
+ /**
4874
+ * Stream a response from the agent.
4875
+ *
4876
+ * When guardrails are configured, `toUIMessageStream()` is wrapped to provide
4877
+ * self-correction behavior. Direct access to fullStream/textStream bypasses guardrails.
4878
+ *
4879
+ * @example
4880
+ * ```typescript
4881
+ * const stream = await agent.stream({});
4882
+ *
4883
+ * // With guardrails - use toUIMessageStream for protection
4884
+ * await printer.readableStream(stream.toUIMessageStream());
4885
+ *
4886
+ * // Or use printer.stdout which uses toUIMessageStream internally
4887
+ * await printer.stdout(stream);
4888
+ * ```
4889
+ */
4890
+ async stream(contextVariables, config) {
4891
+ if (!this.#options.context) {
4892
+ throw new Error(`Agent ${this.#options.name} is missing a context.`);
4893
+ }
4894
+ if (!this.#options.model) {
4895
+ throw new Error(`Agent ${this.#options.name} is missing a model.`);
4896
+ }
4897
+ const result = await this.#createRawStream(contextVariables, config);
4898
+ if (this.#guardrails.length === 0) {
4899
+ return result;
4900
+ }
4901
+ return this.#wrapWithGuardrails(result, contextVariables, config);
4902
+ }
4903
+ /**
4904
+ * Create a raw stream without guardrail processing.
4905
+ */
4906
+ async #createRawStream(contextVariables, config) {
3566
4907
  const { messages, systemPrompt } = await this.#options.context.resolve({
3567
4908
  renderer: new XmlRenderer()
3568
4909
  });
@@ -3607,8 +4948,10 @@ var Agent = class _Agent {
3607
4948
  execute: async ({ writer }) => {
3608
4949
  let currentResult = result;
3609
4950
  let attempt = 0;
4951
+ const { mounts } = context.getSkillMounts();
3610
4952
  const guardrailContext = {
3611
- availableTools: Object.keys(this.tools)
4953
+ availableTools: Object.keys(this.tools),
4954
+ availableSkills: mounts
3612
4955
  };
3613
4956
  while (attempt < maxRetries) {
3614
4957
  if (config?.abortSignal?.aborted) {
@@ -3636,10 +4979,20 @@ var Agent = class _Agent {
3636
4979
  );
3637
4980
  break;
3638
4981
  }
4982
+ if (checkResult.type === "stop") {
4983
+ console.log(
4984
+ chalk2.red(
4985
+ `[${this.#options.name}] Guardrail stopped - unrecoverable error, no retry`
4986
+ )
4987
+ );
4988
+ writer.write(part);
4989
+ writer.write({ type: "finish" });
4990
+ return;
4991
+ }
3639
4992
  if (checkResult.part.type === "text-delta") {
3640
4993
  accumulatedText += checkResult.part.delta;
3641
4994
  }
3642
- writer.write(checkResult.part);
4995
+ writer.write(part);
3643
4996
  }
3644
4997
  if (!guardrailFailed) {
3645
4998
  writer.write({ type: "finish" });
@@ -3806,7 +5159,9 @@ export {
3806
5159
  ModelsRegistry,
3807
5160
  MountPathError,
3808
5161
  PackageInstallError,
5162
+ PostgresContextStore,
3809
5163
  RuntimeStrategy,
5164
+ SqlServerContextStore,
3810
5165
  SqliteContextStore,
3811
5166
  TomlRenderer,
3812
5167
  ToonRenderer,
@@ -3854,6 +5209,7 @@ export {
3854
5209
  role,
3855
5210
  runGuardrailChain,
3856
5211
  skills,
5212
+ stop,
3857
5213
  structuredOutput,
3858
5214
  styleGuide,
3859
5215
  term,