@agentforge/tools 0.17.2 → 0.18.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.cjs CHANGED
@@ -14,6 +14,7 @@ var neo4j = require('neo4j-driver');
14
14
  var drizzleOrm = require('drizzle-orm');
15
15
  var events = require('events');
16
16
  var stream = require('stream');
17
+ var async_hooks = require('async_hooks');
17
18
  var crypto = require('crypto');
18
19
  var fs = require('fs');
19
20
  var path = require('path');
@@ -1023,7 +1024,7 @@ function getDefaultSlackClient() {
1023
1024
  }
1024
1025
  };
1025
1026
  }
1026
- function createSendSlackMessageTool(getSlackClient, logger19) {
1027
+ function createSendSlackMessageTool(getSlackClient, logger20) {
1027
1028
  return core.toolBuilder().name("send-slack-message").description("Send a message to a Slack channel for team communication and notifications").category(core.ToolCategory.WEB).tags(["slack", "messaging", "communication"]).usageNotes(
1028
1029
  "Use this for general team communication. For notifications with @mentions, consider using notify-slack instead. Use get-slack-channels first if you need to find the right channel."
1029
1030
  ).suggests(["get-slack-channels"]).schema(
@@ -1032,7 +1033,7 @@ function createSendSlackMessageTool(getSlackClient, logger19) {
1032
1033
  message: zod.z.string().describe("Message content to send")
1033
1034
  })
1034
1035
  ).implementSafe(async ({ channel, message }) => {
1035
- logger19.info("send-slack-message called", { channel, messageLength: message.length });
1036
+ logger20.info("send-slack-message called", { channel, messageLength: message.length });
1036
1037
  try {
1037
1038
  const { client: slack, config } = getSlackClient();
1038
1039
  const result = await slack.chat.postMessage({
@@ -1041,7 +1042,7 @@ function createSendSlackMessageTool(getSlackClient, logger19) {
1041
1042
  username: config.botName,
1042
1043
  icon_emoji: config.botIcon
1043
1044
  });
1044
- logger19.info("send-slack-message result", {
1045
+ logger20.info("send-slack-message result", {
1045
1046
  ...result.channel ? { channel: result.channel } : {},
1046
1047
  ...result.ts ? { timestamp: result.ts } : {},
1047
1048
  messageLength: message.length,
@@ -1054,7 +1055,7 @@ function createSendSlackMessageTool(getSlackClient, logger19) {
1054
1055
  message_id: result.ts
1055
1056
  };
1056
1057
  } catch (error) {
1057
- logger19.error("send-slack-message failed", {
1058
+ logger20.error("send-slack-message failed", {
1058
1059
  channel,
1059
1060
  error: error.message,
1060
1061
  data: error.data
@@ -1063,7 +1064,7 @@ function createSendSlackMessageTool(getSlackClient, logger19) {
1063
1064
  }
1064
1065
  }).build();
1065
1066
  }
1066
- function createNotifySlackTool(getSlackClient, logger19) {
1067
+ function createNotifySlackTool(getSlackClient, logger20) {
1067
1068
  return core.toolBuilder().name("notify-slack").description("Send a notification to a Slack channel with optional @mentions for urgent alerts").category(core.ToolCategory.WEB).tags(["slack", "notification", "alert"]).usageNotes(
1068
1069
  "Use this for urgent notifications that require @mentions. For general messages without mentions, use send-slack-message instead."
1069
1070
  ).suggests(["get-slack-channels"]).schema(
@@ -1073,7 +1074,7 @@ function createNotifySlackTool(getSlackClient, logger19) {
1073
1074
  mentions: zod.z.array(zod.z.string()).optional().describe("List of usernames to mention (without @)")
1074
1075
  })
1075
1076
  ).implementSafe(async ({ channel, message, mentions = [] }) => {
1076
- logger19.info("notify-slack called", {
1077
+ logger20.info("notify-slack called", {
1077
1078
  channel,
1078
1079
  messageLength: message.length,
1079
1080
  mentionCount: mentions.length
@@ -1087,7 +1088,7 @@ function createNotifySlackTool(getSlackClient, logger19) {
1087
1088
  username: config.botName,
1088
1089
  icon_emoji: config.botIcon
1089
1090
  });
1090
- logger19.info("notify-slack result", {
1091
+ logger20.info("notify-slack result", {
1091
1092
  ...result.channel ? { channel: result.channel } : {},
1092
1093
  ...result.ts ? { timestamp: result.ts } : {},
1093
1094
  mentions: mentions.length
@@ -1101,7 +1102,7 @@ function createNotifySlackTool(getSlackClient, logger19) {
1101
1102
  };
1102
1103
  }).build();
1103
1104
  }
1104
- function createGetSlackChannelsTool(getSlackClient, logger19) {
1105
+ function createGetSlackChannelsTool(getSlackClient, logger20) {
1105
1106
  return core.toolBuilder().name("get-slack-channels").description("Get a list of available Slack channels to find the right channel for messaging").category(core.ToolCategory.WEB).tags(["slack", "channels", "list"]).usageNotes(
1106
1107
  "Use this first to discover available channels before sending messages. Helps ensure you are sending to the correct channel."
1107
1108
  ).follows(["send-slack-message", "notify-slack"]).schema(
@@ -1109,7 +1110,7 @@ function createGetSlackChannelsTool(getSlackClient, logger19) {
1109
1110
  include_private: zod.z.boolean().optional().describe("Include private channels (default: false)")
1110
1111
  })
1111
1112
  ).implementSafe(async ({ include_private = false }) => {
1112
- logger19.info("get-slack-channels called", { include_private });
1113
+ logger20.info("get-slack-channels called", { include_private });
1113
1114
  const { client: slack } = getSlackClient();
1114
1115
  const publicChannels = await slack.conversations.list({
1115
1116
  types: "public_channel",
@@ -1123,7 +1124,7 @@ function createGetSlackChannelsTool(getSlackClient, logger19) {
1123
1124
  });
1124
1125
  allChannels = [...allChannels, ...privateChannels.channels || []];
1125
1126
  }
1126
- logger19.info("get-slack-channels result", {
1127
+ logger20.info("get-slack-channels result", {
1127
1128
  channelCount: allChannels.length,
1128
1129
  includePrivate: include_private
1129
1130
  });
@@ -1138,7 +1139,7 @@ function createGetSlackChannelsTool(getSlackClient, logger19) {
1138
1139
  };
1139
1140
  }).build();
1140
1141
  }
1141
- function createGetSlackMessagesTool(getSlackClient, logger19) {
1142
+ function createGetSlackMessagesTool(getSlackClient, logger20) {
1142
1143
  return core.toolBuilder().name("get-slack-messages").description("Retrieve message history from a Slack channel to read recent conversations").category(core.ToolCategory.WEB).tags(["slack", "messages", "history", "read"]).usageNotes(
1143
1144
  "Use this to read recent messages from a channel. Use get-slack-channels first if you need to find the channel ID. Returns messages in reverse chronological order (newest first)."
1144
1145
  ).suggests(["get-slack-channels"]).schema(
@@ -1147,7 +1148,7 @@ function createGetSlackMessagesTool(getSlackClient, logger19) {
1147
1148
  limit: zod.z.number().int().min(1).max(100).optional().describe("Number of messages to retrieve (default: 20, max: 100)")
1148
1149
  })
1149
1150
  ).implementSafe(async ({ channel, limit = 20 }) => {
1150
- logger19.info("get-slack-messages called", { channel, limit });
1151
+ logger20.info("get-slack-messages called", { channel, limit });
1151
1152
  try {
1152
1153
  const { client: slack } = getSlackClient();
1153
1154
  let channelId = channel;
@@ -1158,7 +1159,7 @@ function createGetSlackMessagesTool(getSlackClient, logger19) {
1158
1159
  });
1159
1160
  const found = channels.channels?.find((c) => c.name === channel);
1160
1161
  if (!found) {
1161
- logger19.error("get-slack-messages: channel not found", { channel });
1162
+ logger20.error("get-slack-messages: channel not found", { channel });
1162
1163
  throw new Error(
1163
1164
  `Channel '${channel}' not found. Use get-slack-channels to see available channels.`
1164
1165
  );
@@ -1170,7 +1171,7 @@ function createGetSlackMessagesTool(getSlackClient, logger19) {
1170
1171
  limit: Math.min(limit, 100)
1171
1172
  // Cap at 100 for performance
1172
1173
  });
1173
- logger19.info("get-slack-messages result", {
1174
+ logger20.info("get-slack-messages result", {
1174
1175
  channel: channelId,
1175
1176
  messageCount: result.messages?.length || 0,
1176
1177
  limit
@@ -1188,7 +1189,7 @@ function createGetSlackMessagesTool(getSlackClient, logger19) {
1188
1189
  })) || []
1189
1190
  };
1190
1191
  } catch (error) {
1191
- logger19.error("get-slack-messages failed", {
1192
+ logger20.error("get-slack-messages failed", {
1192
1193
  channel,
1193
1194
  error: error.message,
1194
1195
  data: error.data
@@ -1269,12 +1270,12 @@ function getAuthHeader() {
1269
1270
  const auth2 = Buffer.from(`${ATLASSIAN_EMAIL}:${ATLASSIAN_API_KEY}`).toString("base64");
1270
1271
  return `Basic ${auth2}`;
1271
1272
  }
1272
- function createSearchConfluenceTool(getAuth, getAuthHeader2, logger19) {
1273
+ function createSearchConfluenceTool(getAuth, getAuthHeader2, logger20) {
1273
1274
  return core.toolBuilder().name("search-confluence").description("Search for pages in Confluence using keywords or CQL (Confluence Query Language). Returns matching pages with titles, IDs, and excerpts.").category(core.ToolCategory.WEB).tag("confluence").tag("search").tag("knowledge-base").usageNotes("Use this to find relevant documentation, policies, or information in Confluence. You can search by keywords or use CQL for advanced queries (e.g., 'space=AI AND type=page'). Use get-confluence-page to retrieve full content of specific pages.").suggests(["get-confluence-page"]).schema(zod.z.object({
1274
1275
  query: zod.z.string().describe("Search query or CQL expression (e.g., 'payment processing' or 'space=BL3 AND title~payment')"),
1275
1276
  limit: zod.z.number().optional().describe("Maximum number of results to return (default: 10, max: 25)")
1276
1277
  })).implement(async ({ query, limit = 10 }) => {
1277
- logger19.info("search-confluence called", { query, limit });
1278
+ logger20.info("search-confluence called", { query, limit });
1278
1279
  try {
1279
1280
  const { ATLASSIAN_SITE_URL } = getAuth();
1280
1281
  const response = await axios11__default.default.get(`${ATLASSIAN_SITE_URL}/wiki/rest/api/content/search`, {
@@ -1299,13 +1300,13 @@ function createSearchConfluenceTool(getAuth, getAuthHeader2, logger19) {
1299
1300
  lastModified: page.version?.when || ""
1300
1301
  }));
1301
1302
  if (results.length === 0) {
1302
- logger19.warn("search-confluence returned NO RESULTS - this is a valid outcome, agent should not retry", {
1303
+ logger20.warn("search-confluence returned NO RESULTS - this is a valid outcome, agent should not retry", {
1303
1304
  query,
1304
1305
  limit,
1305
1306
  totalSize: response.data.totalSize
1306
1307
  });
1307
1308
  } else {
1308
- logger19.info("search-confluence result", {
1309
+ logger20.info("search-confluence result", {
1309
1310
  query,
1310
1311
  resultCount: results.length,
1311
1312
  totalSize: response.data.totalSize,
@@ -1320,7 +1321,7 @@ function createSearchConfluenceTool(getAuth, getAuthHeader2, logger19) {
1320
1321
  results
1321
1322
  });
1322
1323
  } catch (error) {
1323
- logger19.error("search-confluence error", {
1324
+ logger20.error("search-confluence error", {
1324
1325
  query,
1325
1326
  error: error.response?.data?.message || error.message,
1326
1327
  status: error.response?.status
@@ -1332,11 +1333,11 @@ function createSearchConfluenceTool(getAuth, getAuthHeader2, logger19) {
1332
1333
  }
1333
1334
  }).build();
1334
1335
  }
1335
- function createGetConfluencePageTool(getAuth, getAuthHeader2, logger19) {
1336
+ function createGetConfluencePageTool(getAuth, getAuthHeader2, logger20) {
1336
1337
  return core.toolBuilder().name("get-confluence-page").description("Get the full content of a specific Confluence page by its ID. Returns the page title, content (in storage format), space, and metadata.").category(core.ToolCategory.WEB).tag("confluence").tag("page").tag("content").usageNotes("Use this after search-confluence to retrieve the full content of a specific page. The page ID can be found in search results.").requires(["search-confluence"]).schema(zod.z.object({
1337
1338
  page_id: zod.z.string().describe("The Confluence page ID (from search results)")
1338
1339
  })).implement(async ({ page_id }) => {
1339
- logger19.info("get-confluence-page called", { page_id });
1340
+ logger20.info("get-confluence-page called", { page_id });
1340
1341
  try {
1341
1342
  const { ATLASSIAN_SITE_URL } = getAuth();
1342
1343
  const response = await axios11__default.default.get(`${ATLASSIAN_SITE_URL}/wiki/rest/api/content/${page_id}`, {
@@ -1349,7 +1350,7 @@ function createGetConfluencePageTool(getAuth, getAuthHeader2, logger19) {
1349
1350
  }
1350
1351
  });
1351
1352
  const page = response.data;
1352
- logger19.info("get-confluence-page result", {
1353
+ logger20.info("get-confluence-page result", {
1353
1354
  page_id,
1354
1355
  title: page.title,
1355
1356
  space: page.space?.name,
@@ -1371,7 +1372,7 @@ function createGetConfluencePageTool(getAuth, getAuthHeader2, logger19) {
1371
1372
  }
1372
1373
  });
1373
1374
  } catch (error) {
1374
- logger19.error("get-confluence-page error", {
1375
+ logger20.error("get-confluence-page error", {
1375
1376
  page_id,
1376
1377
  error: error.response?.data?.message || error.message,
1377
1378
  status: error.response?.status
@@ -1383,11 +1384,11 @@ function createGetConfluencePageTool(getAuth, getAuthHeader2, logger19) {
1383
1384
  }
1384
1385
  }).build();
1385
1386
  }
1386
- function createListConfluenceSpacesTool(getAuth, getAuthHeader2, logger19) {
1387
+ function createListConfluenceSpacesTool(getAuth, getAuthHeader2, logger20) {
1387
1388
  return core.toolBuilder().name("list-confluence-spaces").description("List all available Confluence spaces. Returns space names, keys, types, and descriptions to help identify where to search for information.").category(core.ToolCategory.WEB).tag("confluence").tag("spaces").tag("list").usageNotes("Use this first to discover available spaces before searching. Helps narrow down searches to specific areas (e.g., 'AI', 'BL3', 'Finance').").follows(["search-confluence"]).schema(zod.z.object({
1388
1389
  limit: zod.z.number().optional().describe("Maximum number of spaces to return (default: 25)")
1389
1390
  })).implement(async ({ limit = 25 }) => {
1390
- logger19.info("list-confluence-spaces called", { limit });
1391
+ logger20.info("list-confluence-spaces called", { limit });
1391
1392
  try {
1392
1393
  const { ATLASSIAN_SITE_URL } = getAuth();
1393
1394
  const response = await axios11__default.default.get(`${ATLASSIAN_SITE_URL}/wiki/rest/api/space`, {
@@ -1406,7 +1407,7 @@ function createListConfluenceSpacesTool(getAuth, getAuthHeader2, logger19) {
1406
1407
  description: space.description?.plain?.value || "",
1407
1408
  url: `${ATLASSIAN_SITE_URL}/wiki${space._links.webui}`
1408
1409
  }));
1409
- logger19.info("list-confluence-spaces result", {
1410
+ logger20.info("list-confluence-spaces result", {
1410
1411
  spaceCount: spaces.length,
1411
1412
  spaceKeys: spaces.map((s) => s.key).slice(0, 5)
1412
1413
  // Log first 5 space keys
@@ -1417,7 +1418,7 @@ function createListConfluenceSpacesTool(getAuth, getAuthHeader2, logger19) {
1417
1418
  spaces
1418
1419
  });
1419
1420
  } catch (error) {
1420
- logger19.error("list-confluence-spaces error", {
1421
+ logger20.error("list-confluence-spaces error", {
1421
1422
  error: error.response?.data?.message || error.message,
1422
1423
  status: error.response?.status
1423
1424
  });
@@ -1428,12 +1429,12 @@ function createListConfluenceSpacesTool(getAuth, getAuthHeader2, logger19) {
1428
1429
  }
1429
1430
  }).build();
1430
1431
  }
1431
- function createGetSpacePagesTool(getAuth, getAuthHeader2, logger19) {
1432
+ function createGetSpacePagesTool(getAuth, getAuthHeader2, logger20) {
1432
1433
  return core.toolBuilder().name("get-space-pages").description("Get all pages from a specific Confluence space by space key. Useful for browsing content in a particular area.").category(core.ToolCategory.WEB).tag("confluence").tag("space").tag("pages").usageNotes("Use this to explore all pages in a specific space. Get the space key from list-confluence-spaces first.").requires(["list-confluence-spaces"]).schema(zod.z.object({
1433
1434
  space_key: zod.z.string().describe("The space key (e.g., 'AI', 'BL3', 'FIN')"),
1434
1435
  limit: zod.z.number().optional().describe("Maximum number of pages to return (default: 25)")
1435
1436
  })).implement(async ({ space_key, limit = 25 }) => {
1436
- logger19.info("get-space-pages called", { space_key, limit });
1437
+ logger20.info("get-space-pages called", { space_key, limit });
1437
1438
  try {
1438
1439
  const { ATLASSIAN_SITE_URL } = getAuth();
1439
1440
  const response = await axios11__default.default.get(`${ATLASSIAN_SITE_URL}/wiki/rest/api/content`, {
@@ -1455,12 +1456,12 @@ function createGetSpacePagesTool(getAuth, getAuthHeader2, logger19) {
1455
1456
  lastModified: page.version?.when || ""
1456
1457
  }));
1457
1458
  if (pages.length === 0) {
1458
- logger19.warn("get-space-pages returned NO PAGES - this is a valid outcome, agent should not retry", {
1459
+ logger20.warn("get-space-pages returned NO PAGES - this is a valid outcome, agent should not retry", {
1459
1460
  space_key,
1460
1461
  limit
1461
1462
  });
1462
1463
  } else {
1463
- logger19.info("get-space-pages result", {
1464
+ logger20.info("get-space-pages result", {
1464
1465
  space_key,
1465
1466
  pageCount: pages.length,
1466
1467
  titles: pages.map((p) => p.title).slice(0, 3)
@@ -1474,7 +1475,7 @@ function createGetSpacePagesTool(getAuth, getAuthHeader2, logger19) {
1474
1475
  pages
1475
1476
  });
1476
1477
  } catch (error) {
1477
- logger19.error("get-space-pages error", {
1478
+ logger20.error("get-space-pages error", {
1478
1479
  space_key,
1479
1480
  error: error.response?.data?.message || error.message,
1480
1481
  status: error.response?.status
@@ -1486,14 +1487,14 @@ function createGetSpacePagesTool(getAuth, getAuthHeader2, logger19) {
1486
1487
  }
1487
1488
  }).build();
1488
1489
  }
1489
- function createCreateConfluencePageTool(getAuth, getAuthHeader2, logger19) {
1490
+ function createCreateConfluencePageTool(getAuth, getAuthHeader2, logger20) {
1490
1491
  return core.toolBuilder().name("create-confluence-page").description("Create a new page in a Confluence space. Requires space key, page title, and content (in HTML storage format).").category(core.ToolCategory.WEB).tag("confluence").tag("create").tag("write").usageNotes("Use this to create new documentation pages. Content should be in Confluence storage format (HTML). Get the space key from list-confluence-spaces first. Be mindful of creating duplicate pages.").requires(["list-confluence-spaces"]).schema(zod.z.object({
1491
1492
  space_key: zod.z.string().describe("The space key where the page will be created (e.g., 'AI', 'BL3')"),
1492
1493
  title: zod.z.string().describe("The title of the new page"),
1493
1494
  content: zod.z.string().describe("The page content in HTML format (Confluence storage format)"),
1494
1495
  parent_page_id: zod.z.string().optional().describe("Optional parent page ID to create this as a child page")
1495
1496
  })).implement(async ({ space_key, title, content, parent_page_id }) => {
1496
- logger19.info("create-confluence-page called", { space_key, title, hasParent: !!parent_page_id });
1497
+ logger20.info("create-confluence-page called", { space_key, title, hasParent: !!parent_page_id });
1497
1498
  try {
1498
1499
  const { ATLASSIAN_SITE_URL } = getAuth();
1499
1500
  const pageData = {
@@ -1520,7 +1521,7 @@ function createCreateConfluencePageTool(getAuth, getAuthHeader2, logger19) {
1520
1521
  }
1521
1522
  }
1522
1523
  );
1523
- logger19.info("create-confluence-page result", {
1524
+ logger20.info("create-confluence-page result", {
1524
1525
  page_id: response.data.id,
1525
1526
  title: response.data.title,
1526
1527
  space: space_key
@@ -1536,7 +1537,7 @@ function createCreateConfluencePageTool(getAuth, getAuthHeader2, logger19) {
1536
1537
  }
1537
1538
  });
1538
1539
  } catch (error) {
1539
- logger19.error("create-confluence-page error", {
1540
+ logger20.error("create-confluence-page error", {
1540
1541
  space_key,
1541
1542
  title,
1542
1543
  error: error.response?.data?.message || error.message,
@@ -1549,13 +1550,13 @@ function createCreateConfluencePageTool(getAuth, getAuthHeader2, logger19) {
1549
1550
  }
1550
1551
  }).build();
1551
1552
  }
1552
- function createUpdateConfluencePageTool(getAuth, getAuthHeader2, logger19) {
1553
+ function createUpdateConfluencePageTool(getAuth, getAuthHeader2, logger20) {
1553
1554
  return core.toolBuilder().name("update-confluence-page").description("Update an existing Confluence page's content. Requires page ID, new title, and new content.").category(core.ToolCategory.WEB).tag("confluence").tag("update").tag("write").usageNotes("Use this to update existing documentation. You must provide the page ID (from search results). The tool will automatically handle version incrementing. Always get the current page content first to avoid overwriting important information.").requires(["get-confluence-page"]).schema(zod.z.object({
1554
1555
  page_id: zod.z.string().describe("The ID of the page to update"),
1555
1556
  title: zod.z.string().describe("The new title for the page"),
1556
1557
  content: zod.z.string().describe("The new content in HTML format (Confluence storage format)")
1557
1558
  })).implement(async ({ page_id, title, content }) => {
1558
- logger19.info("update-confluence-page called", { page_id, title });
1559
+ logger20.info("update-confluence-page called", { page_id, title });
1559
1560
  try {
1560
1561
  const { ATLASSIAN_SITE_URL } = getAuth();
1561
1562
  const getResponse = await axios11__default.default.get(
@@ -1588,7 +1589,7 @@ function createUpdateConfluencePageTool(getAuth, getAuthHeader2, logger19) {
1588
1589
  }
1589
1590
  }
1590
1591
  );
1591
- logger19.info("update-confluence-page result", {
1592
+ logger20.info("update-confluence-page result", {
1592
1593
  page_id,
1593
1594
  title: updateResponse.data.title,
1594
1595
  previousVersion: currentVersion,
@@ -1605,7 +1606,7 @@ function createUpdateConfluencePageTool(getAuth, getAuthHeader2, logger19) {
1605
1606
  }
1606
1607
  });
1607
1608
  } catch (error) {
1608
- logger19.error("update-confluence-page error", {
1609
+ logger20.error("update-confluence-page error", {
1609
1610
  page_id,
1610
1611
  title,
1611
1612
  error: error.response?.data?.message || error.message,
@@ -1618,12 +1619,12 @@ function createUpdateConfluencePageTool(getAuth, getAuthHeader2, logger19) {
1618
1619
  }
1619
1620
  }).build();
1620
1621
  }
1621
- function createArchiveConfluencePageTool(getAuth, getAuthHeader2, logger19) {
1622
+ function createArchiveConfluencePageTool(getAuth, getAuthHeader2, logger20) {
1622
1623
  return core.toolBuilder().name("archive-confluence-page").description("Archive a Confluence page by moving it to trash. The page can be restored by space admins. Note: UI may require a note explaining why the page was archived.").category(core.ToolCategory.WEB).tag("confluence").tag("archive").tag("delete").usageNotes("Use this to archive outdated or obsolete documentation. The page is moved to trash, not permanently deleted. Space admins can restore it if needed. Be very careful - only archive pages that are truly obsolete.").conflicts(["create-confluence-page"]).schema(zod.z.object({
1623
1624
  page_id: zod.z.string().describe("The ID of the page to archive"),
1624
1625
  reason: zod.z.string().optional().describe("Optional reason for archiving (for audit trail)")
1625
1626
  })).implement(async ({ page_id, reason }) => {
1626
- logger19.info("archive-confluence-page called", {
1627
+ logger20.info("archive-confluence-page called", {
1627
1628
  page_id,
1628
1629
  ...reason ? { reason } : {}
1629
1630
  });
@@ -1657,7 +1658,7 @@ function createArchiveConfluencePageTool(getAuth, getAuthHeader2, logger19) {
1657
1658
  }
1658
1659
  }
1659
1660
  );
1660
- logger19.info("archive-confluence-page result", {
1661
+ logger20.info("archive-confluence-page result", {
1661
1662
  page_id,
1662
1663
  title: pageData.title,
1663
1664
  previousVersion: currentVersion,
@@ -1675,7 +1676,7 @@ function createArchiveConfluencePageTool(getAuth, getAuthHeader2, logger19) {
1675
1676
  }
1676
1677
  });
1677
1678
  } catch (error) {
1678
- logger19.error("archive-confluence-page error", {
1679
+ logger20.error("archive-confluence-page error", {
1679
1680
  page_id,
1680
1681
  error: error.response?.data?.message || error.message,
1681
1682
  status: error.response?.status
@@ -4185,11 +4186,41 @@ function getInstallationInstructions(vendor) {
4185
4186
 
4186
4187
  // src/data/relational/utils/sql-sanitizer.ts
4187
4188
  var DANGEROUS_SQL_STATEMENT_PATTERN = /^(create|drop|truncate|alter)\b/i;
4189
+ var TRANSACTION_CONTROL_STATEMENT_PATTERN = /^(begin|start\s+transaction|commit|end|abort|rollback|prepare\s+transaction|savepoint|release(?:\s+savepoint)?|set\s+(?:(?:local|session|global)\s+)?(?:characteristics\s+as\s+)?transaction)\b/i;
4190
+ var AUTOCOMMIT_STATEMENT_PATTERN = /^set\s+(?:(?:session|local|global)\s+|@@(?:(?:session|local|global)\.)?)?autocommit\b/i;
4191
+ var MYSQL_IMPLICIT_COMMIT_STATEMENT_PATTERN = /^(?:(?:alter|create|drop)\s+|truncate\s+table\b|rename\s+(?:table|user)\b|(?:lock|unlock)\s+tables\b|(?:grant|revoke)\b|set\s+password\b|(?:analyze|check|optimize|repair)\s+table\b|cache\s+index\b|load\s+(?:data|xml|index\s+into\s+cache)\b|flush\b|reset\b|(?:start|stop)\s+(?:replica|slave)\b|change\s+(?:replication\s+source|master)\s+to\b|(?:install|uninstall)\s+(?:plugin|component)\b)/i;
4192
+ var MYSQL_INDIRECT_EXECUTION_STATEMENT_PATTERN = /^(?:call\b|prepare\b|execute(?:\s+immediate)?\b|deallocate\s+prepare\b)/i;
4193
+ var MANAGED_TRANSACTION_CONTROL_ERROR_MESSAGE = "Transaction control statements are not allowed in scoped Tools.";
4188
4194
  var NUMBERED_PLACEHOLDER_PATTERN = /\$(\d+)/;
4189
4195
  var QUESTION_PLACEHOLDER_PATTERN = /\?/;
4190
4196
  var NAMED_PLACEHOLDER_PATTERN = /(?<!:):[a-zA-Z_][a-zA-Z0-9_]*/;
4191
4197
  var PARAMETER_REQUIRED_PATTERN = /^(insert|update|delete)\b/i;
4192
4198
  var MUTATION_PATTERN = /\b(insert|update|delete)\b/i;
4199
+ function sqlStripOptions(vendor, rejectExecutableComments = false, backslashEscapes = vendor === "mysql") {
4200
+ return {
4201
+ backslashEscapes,
4202
+ hashComments: vendor === "mysql",
4203
+ mysqlDashDashComments: vendor === "mysql",
4204
+ nestedBlockComments: vendor === "postgresql",
4205
+ postgresEscapeStrings: vendor === "postgresql",
4206
+ rejectExecutableComments
4207
+ };
4208
+ }
4209
+ function isPostgresEscapeStringQuote(sqlString, quoteIndex) {
4210
+ const prefixIndex = quoteIndex - 1;
4211
+ if (prefixIndex < 0 || !/[eE]/.test(sqlString[prefixIndex])) {
4212
+ return false;
4213
+ }
4214
+ const beforePrefix = prefixIndex - 1;
4215
+ return beforePrefix < 0 || !/[A-Za-z0-9_$]/.test(sqlString[beforePrefix]);
4216
+ }
4217
+ function isWhitespaceOrControlCharacter(value) {
4218
+ if (!value) {
4219
+ return false;
4220
+ }
4221
+ const codePoint = value.charCodeAt(0);
4222
+ return /\s/u.test(value) || codePoint < 32 || codePoint === 127;
4223
+ }
4193
4224
  function stripSqlCommentsAndStrings(sqlString, options) {
4194
4225
  const { backslashEscapes } = options;
4195
4226
  let result = "";
@@ -4198,7 +4229,17 @@ function stripSqlCommentsAndStrings(sqlString, options) {
4198
4229
  while (i < len) {
4199
4230
  const ch = sqlString[i];
4200
4231
  const next = i + 1 < len ? sqlString[i + 1] : "";
4201
- if (ch === "-" && next === "-") {
4232
+ if (options.hashComments && ch === "#") {
4233
+ result += " ";
4234
+ i += 1;
4235
+ while (i < len && sqlString[i] !== "\n") {
4236
+ i += 1;
4237
+ }
4238
+ continue;
4239
+ }
4240
+ const afterDoubleDash = i + 2 < len ? sqlString[i + 2] : "";
4241
+ const doubleDashStartsComment = ch === "-" && next === "-" && (!options.mysqlDashDashComments || isWhitespaceOrControlCharacter(afterDoubleDash));
4242
+ if (doubleDashStartsComment) {
4202
4243
  result += " ";
4203
4244
  i += 2;
4204
4245
  while (i < len && sqlString[i] !== "\n") {
@@ -4207,12 +4248,23 @@ function stripSqlCommentsAndStrings(sqlString, options) {
4207
4248
  continue;
4208
4249
  }
4209
4250
  if (ch === "/" && next === "*") {
4251
+ const executableComment = sqlString[i + 2] === "!" || sqlString[i + 2]?.toLowerCase() === "m" && sqlString[i + 3] === "!";
4252
+ if (options.rejectExecutableComments && executableComment) {
4253
+ throw new Error(MANAGED_TRANSACTION_CONTROL_ERROR_MESSAGE);
4254
+ }
4210
4255
  result += " ";
4211
4256
  i += 2;
4212
- while (i < len) {
4257
+ let depth = 1;
4258
+ while (i < len && depth > 0) {
4259
+ if (options.nestedBlockComments && sqlString[i] === "/" && i + 1 < len && sqlString[i + 1] === "*") {
4260
+ depth += 1;
4261
+ i += 2;
4262
+ continue;
4263
+ }
4213
4264
  if (sqlString[i] === "*" && i + 1 < len && sqlString[i + 1] === "/") {
4265
+ depth -= 1;
4214
4266
  i += 2;
4215
- break;
4267
+ continue;
4216
4268
  }
4217
4269
  i += 1;
4218
4270
  }
@@ -4220,9 +4272,10 @@ function stripSqlCommentsAndStrings(sqlString, options) {
4220
4272
  }
4221
4273
  if (ch === "'") {
4222
4274
  result += "''";
4275
+ const singleQuoteBackslashEscapes = backslashEscapes || options.postgresEscapeStrings && isPostgresEscapeStringQuote(sqlString, i);
4223
4276
  i += 1;
4224
4277
  while (i < len) {
4225
- if (backslashEscapes && sqlString[i] === "\\" && i + 1 < len) {
4278
+ if (singleQuoteBackslashEscapes && sqlString[i] === "\\" && i + 1 < len) {
4226
4279
  i += 2;
4227
4280
  continue;
4228
4281
  }
@@ -4280,6 +4333,20 @@ function stripSqlCommentsAndStrings(sqlString, options) {
4280
4333
  }
4281
4334
  return result;
4282
4335
  }
4336
+ function strippedSqlVariants(sqlString, vendor, rejectExecutableComments = false, analyzeMysqlNoBackslashEscapes = false) {
4337
+ const variants = [
4338
+ stripSqlCommentsAndStrings(sqlString, sqlStripOptions(vendor, rejectExecutableComments))
4339
+ ];
4340
+ if (vendor === "mysql" && analyzeMysqlNoBackslashEscapes) {
4341
+ variants.push(
4342
+ stripSqlCommentsAndStrings(
4343
+ sqlString,
4344
+ sqlStripOptions(vendor, rejectExecutableComments, false)
4345
+ )
4346
+ );
4347
+ }
4348
+ return [...new Set(variants)];
4349
+ }
4283
4350
  function nextNonWhitespaceChar(sqlString, index) {
4284
4351
  for (let i = index; i < sqlString.length; i += 1) {
4285
4352
  if (!/\s/.test(sqlString[i])) {
@@ -4329,6 +4396,16 @@ function hasParameters(params) {
4329
4396
  }
4330
4397
  return Object.keys(params).length > 0;
4331
4398
  }
4399
+ function sqlStatements(sqlString, vendor, rejectExecutableComments = false, analyzeMysqlNoBackslashEscapes = false) {
4400
+ return strippedSqlVariants(
4401
+ sqlString,
4402
+ vendor,
4403
+ rejectExecutableComments,
4404
+ analyzeMysqlNoBackslashEscapes
4405
+ ).flatMap(
4406
+ (normalized) => normalized.split(";").map((statement) => statement.trim()).filter((statement) => statement.length > 0)
4407
+ );
4408
+ }
4332
4409
  function validateSqlString(sqlString, vendor) {
4333
4410
  if (typeof sqlString !== "string" || sqlString.trim().length === 0) {
4334
4411
  throw new Error("SQL query must not be empty");
@@ -4336,18 +4413,20 @@ function validateSqlString(sqlString, vendor) {
4336
4413
  if (sqlString.includes("\0")) {
4337
4414
  throw new Error("SQL query contains null bytes");
4338
4415
  }
4339
- const normalizedForSafetyCheck = stripSqlCommentsAndStrings(sqlString, {
4340
- backslashEscapes: vendor === "mysql"
4341
- });
4342
- const statements = normalizedForSafetyCheck.split(";").map((statement) => statement.trim()).filter((statement) => statement.length > 0);
4416
+ const statements = sqlStatements(sqlString, vendor);
4343
4417
  if (statements.some((statement) => DANGEROUS_SQL_STATEMENT_PATTERN.test(statement))) {
4344
4418
  throw new Error("Detected dangerous SQL operation. CREATE, DROP, TRUNCATE, and ALTER are not allowed.");
4345
4419
  }
4346
4420
  }
4421
+ function validateManagedTransactionSql(sqlString, vendor) {
4422
+ if (sqlStatements(sqlString, vendor, true, true).some(
4423
+ (statement) => TRANSACTION_CONTROL_STATEMENT_PATTERN.test(statement) || AUTOCOMMIT_STATEMENT_PATTERN.test(statement) || vendor === "mysql" && (MYSQL_IMPLICIT_COMMIT_STATEMENT_PATTERN.test(statement) || MYSQL_INDIRECT_EXECUTION_STATEMENT_PATTERN.test(statement))
4424
+ )) {
4425
+ throw new Error(MANAGED_TRANSACTION_CONTROL_ERROR_MESSAGE);
4426
+ }
4427
+ }
4347
4428
  function enforceParameterizedQueryUsage(sqlString, params, vendor) {
4348
- const normalizedForAnalysis = stripSqlCommentsAndStrings(sqlString, {
4349
- backslashEscapes: vendor === "mysql"
4350
- });
4429
+ const normalizedForAnalysis = stripSqlCommentsAndStrings(sqlString, sqlStripOptions(vendor));
4351
4430
  const normalized = normalizedForAnalysis.trim().toLowerCase();
4352
4431
  const hasPlaceholders = hasSqlPlaceholders(normalizedForAnalysis, vendor);
4353
4432
  const hasParams = hasParameters(params);
@@ -4406,19 +4485,19 @@ var ConnectionState = /* @__PURE__ */ ((ConnectionState2) => {
4406
4485
  ConnectionState2["ERROR"] = "error";
4407
4486
  return ConnectionState2;
4408
4487
  })(ConnectionState || {});
4409
- function cancelPendingReconnection(timer, logger19, vendor, message) {
4488
+ function cancelPendingReconnection(timer, logger20, vendor, message) {
4410
4489
  if (!timer) {
4411
4490
  return null;
4412
4491
  }
4413
- logger19.debug(message, { vendor });
4492
+ logger20.debug(message, { vendor });
4414
4493
  clearTimeout(timer);
4415
4494
  return null;
4416
4495
  }
4417
- async function waitForInFlightConnection(promise, logger19, vendor, phase) {
4496
+ async function waitForInFlightConnection(promise, logger20, vendor, phase) {
4418
4497
  if (!promise) {
4419
4498
  return null;
4420
4499
  }
4421
- logger19.debug(`Waiting for in-flight connection attempt to complete before ${phase}`, {
4500
+ logger20.debug(`Waiting for in-flight connection attempt to complete before ${phase}`, {
4422
4501
  vendor
4423
4502
  });
4424
4503
  try {
@@ -4435,9 +4514,9 @@ function shutdownClient(vendor, client) {
4435
4514
  client.close();
4436
4515
  }
4437
4516
  }
4438
- function scheduleReconnection(context, logger19) {
4517
+ function scheduleReconnection(context, logger20) {
4439
4518
  if (context.reconnectionConfig.maxAttempts > 0 && context.reconnectionAttempts >= context.reconnectionConfig.maxAttempts) {
4440
- logger19.error("Max reconnection attempts reached", {
4519
+ logger20.error("Max reconnection attempts reached", {
4441
4520
  vendor: context.vendor,
4442
4521
  attempts: context.reconnectionAttempts,
4443
4522
  maxAttempts: context.reconnectionConfig.maxAttempts
@@ -4451,7 +4530,7 @@ function scheduleReconnection(context, logger19) {
4451
4530
  const nextAttempt = context.reconnectionAttempts + 1;
4452
4531
  context.setReconnectionAttempts(nextAttempt);
4453
4532
  context.setState("reconnecting" /* RECONNECTING */);
4454
- logger19.info("Scheduling reconnection attempt", {
4533
+ logger20.info("Scheduling reconnection attempt", {
4455
4534
  vendor: context.vendor,
4456
4535
  attempt: nextAttempt,
4457
4536
  maxAttempts: context.reconnectionConfig.maxAttempts,
@@ -4465,7 +4544,7 @@ function scheduleReconnection(context, logger19) {
4465
4544
  const timer = setTimeout(async () => {
4466
4545
  context.setReconnectionTimer(null);
4467
4546
  try {
4468
- logger19.info("Attempting reconnection", {
4547
+ logger20.info("Attempting reconnection", {
4469
4548
  vendor: context.vendor,
4470
4549
  attempt: nextAttempt
4471
4550
  });
@@ -4475,7 +4554,7 @@ function scheduleReconnection(context, logger19) {
4475
4554
  context.setConnectPromise(promise);
4476
4555
  await promise;
4477
4556
  } catch (error) {
4478
- logger19.error("Reconnection attempt failed", {
4557
+ logger20.error("Reconnection attempt failed", {
4479
4558
  vendor: context.vendor,
4480
4559
  attempt: nextAttempt,
4481
4560
  error: error instanceof Error ? error.message : String(error)
@@ -4486,17 +4565,17 @@ function scheduleReconnection(context, logger19) {
4486
4565
  }
4487
4566
 
4488
4567
  // src/data/relational/connection/connection-cleanup.ts
4489
- async function cleanupConnectionResources(vendor, client, clearConnection, logger19) {
4568
+ async function cleanupConnectionResources(vendor, client, clearConnection, logger20) {
4490
4569
  if (!client) {
4491
4570
  return;
4492
4571
  }
4493
- logger19.debug("Cleaning up cancelled connection", {
4572
+ logger20.debug("Cleaning up cancelled connection", {
4494
4573
  vendor
4495
4574
  });
4496
4575
  try {
4497
4576
  await shutdownClient(vendor, client);
4498
4577
  } catch (error) {
4499
- logger19.debug("Error during cancelled connection cleanup", {
4578
+ logger20.debug("Error during cancelled connection cleanup", {
4500
4579
  vendor,
4501
4580
  error: error instanceof Error ? error.message : String(error)
4502
4581
  });
@@ -4504,10 +4583,10 @@ async function cleanupConnectionResources(vendor, client, clearConnection, logge
4504
4583
  clearConnection();
4505
4584
  }
4506
4585
  }
4507
- async function closeManagedConnection(runtime, logger19) {
4586
+ async function closeManagedConnection(runtime, logger20) {
4508
4587
  const client = runtime.getClient();
4509
4588
  if (client) {
4510
- logger19.info("Closing database connection", {
4589
+ logger20.info("Closing database connection", {
4511
4590
  vendor: runtime.vendor,
4512
4591
  state: runtime.getState()
4513
4592
  });
@@ -4515,12 +4594,12 @@ async function closeManagedConnection(runtime, logger19) {
4515
4594
  await shutdownClient(runtime.vendor, client);
4516
4595
  runtime.setState("disconnected" /* DISCONNECTED */);
4517
4596
  runtime.emitDisconnected();
4518
- logger19.debug("Database connection closed successfully", {
4597
+ logger20.debug("Database connection closed successfully", {
4519
4598
  vendor: runtime.vendor,
4520
4599
  state: runtime.getState()
4521
4600
  });
4522
4601
  } catch (error) {
4523
- logger19.error("Error closing database connection", {
4602
+ logger20.error("Error closing database connection", {
4524
4603
  vendor: runtime.vendor,
4525
4604
  error: error instanceof Error ? error.message : String(error),
4526
4605
  state: runtime.getState()
@@ -4693,11 +4772,11 @@ async function initializeSQLiteConnection(connection) {
4693
4772
  }
4694
4773
 
4695
4774
  // src/data/relational/connection/connection-initialization.ts
4696
- async function initializeManagedConnection(runtime, logger19) {
4775
+ async function initializeManagedConnection(runtime, logger20) {
4697
4776
  const startTime = Date.now();
4698
4777
  const currentGeneration = runtime.getConnectionGeneration();
4699
4778
  if (runtime.getState() === "connected" /* CONNECTED */ && runtime.getClient()) {
4700
- logger19.warn("Re-initializing an already connected manager; emitting disconnected before cleanup", {
4779
+ logger20.warn("Re-initializing an already connected manager; emitting disconnected before cleanup", {
4701
4780
  vendor: runtime.vendor
4702
4781
  });
4703
4782
  runtime.setState("disconnected" /* DISCONNECTED */);
@@ -4709,11 +4788,11 @@ async function initializeManagedConnection(runtime, logger19) {
4709
4788
  runtime.setClient(null);
4710
4789
  runtime.setDb(null);
4711
4790
  },
4712
- logger19
4791
+ logger20
4713
4792
  );
4714
4793
  }
4715
4794
  runtime.setState("connecting" /* CONNECTING */);
4716
- logger19.info("Initializing database connection", {
4795
+ logger20.info("Initializing database connection", {
4717
4796
  vendor: runtime.vendor,
4718
4797
  connectionType: typeof runtime.config.connection,
4719
4798
  state: runtime.getState()
@@ -4723,7 +4802,7 @@ async function initializeManagedConnection(runtime, logger19) {
4723
4802
  runtime.setClient(initialized.client);
4724
4803
  runtime.setDb(initialized.db);
4725
4804
  if (currentGeneration !== runtime.getConnectionGeneration()) {
4726
- logger19.debug("Connection cancelled during initialization", {
4805
+ logger20.debug("Connection cancelled during initialization", {
4727
4806
  vendor: runtime.vendor,
4728
4807
  currentGeneration,
4729
4808
  newGeneration: runtime.getConnectionGeneration()
@@ -4735,18 +4814,18 @@ async function initializeManagedConnection(runtime, logger19) {
4735
4814
  runtime.setClient(null);
4736
4815
  runtime.setDb(null);
4737
4816
  },
4738
- logger19
4817
+ logger20
4739
4818
  );
4740
4819
  runtime.setState("disconnected" /* DISCONNECTED */);
4741
4820
  throw new Error("Connection cancelled during initialization");
4742
4821
  }
4743
- logger19.debug("Validating connection health", { vendor: runtime.vendor });
4822
+ logger20.debug("Validating connection health", { vendor: runtime.vendor });
4744
4823
  const healthy = await runtime.isHealthy();
4745
4824
  if (!healthy) {
4746
4825
  throw new Error(`Failed to establish healthy connection to ${runtime.vendor} database`);
4747
4826
  }
4748
4827
  if (currentGeneration !== runtime.getConnectionGeneration()) {
4749
- logger19.debug("Connection cancelled during health check", {
4828
+ logger20.debug("Connection cancelled during health check", {
4750
4829
  vendor: runtime.vendor,
4751
4830
  currentGeneration,
4752
4831
  newGeneration: runtime.getConnectionGeneration()
@@ -4758,7 +4837,7 @@ async function initializeManagedConnection(runtime, logger19) {
4758
4837
  runtime.setClient(null);
4759
4838
  runtime.setDb(null);
4760
4839
  },
4761
- logger19
4840
+ logger20
4762
4841
  );
4763
4842
  runtime.setState("disconnected" /* DISCONNECTED */);
4764
4843
  throw new Error("Connection cancelled during health check");
@@ -4766,7 +4845,7 @@ async function initializeManagedConnection(runtime, logger19) {
4766
4845
  runtime.setState("connected" /* CONNECTED */);
4767
4846
  runtime.emitConnected();
4768
4847
  runtime.resetReconnectionAttempts();
4769
- logger19.info("Database connection initialized successfully", {
4848
+ logger20.info("Database connection initialized successfully", {
4770
4849
  vendor: runtime.vendor,
4771
4850
  duration: Date.now() - startTime,
4772
4851
  state: runtime.getState()
@@ -4787,7 +4866,7 @@ async function initializeManagedConnection(runtime, logger19) {
4787
4866
  if (runtime.errorListenerCount() > 0) {
4788
4867
  runtime.emitError(normalizedError);
4789
4868
  }
4790
- logger19.error(errorMessage, {
4869
+ logger20.error(errorMessage, {
4791
4870
  vendor: runtime.vendor,
4792
4871
  error: normalizedError.message,
4793
4872
  duration: Date.now() - startTime,
@@ -4800,13 +4879,13 @@ async function initializeManagedConnection(runtime, logger19) {
4800
4879
  runtime.setClient(null);
4801
4880
  runtime.setDb(null);
4802
4881
  },
4803
- logger19
4882
+ logger20
4804
4883
  );
4805
4884
  if (runtime.getReconnectionConfig().enabled && currentGeneration === runtime.getConnectionGeneration()) {
4806
4885
  runtime.scheduleReconnection();
4807
4886
  }
4808
4887
  } else {
4809
- logger19.debug("Connection initialization cancelled", {
4888
+ logger20.debug("Connection initialization cancelled", {
4810
4889
  vendor: runtime.vendor,
4811
4890
  duration: Date.now() - startTime,
4812
4891
  state: runtime.getState()
@@ -4824,17 +4903,17 @@ function isSqliteNonQueryError(error) {
4824
4903
  const hasNonQueryMessage = error.message.includes("does not return data");
4825
4904
  return isSqliteOrTypeError && hasNonQueryMessage;
4826
4905
  }
4827
- async function checkConnectionHealth(args, logger19) {
4906
+ async function checkConnectionHealth(args, logger20) {
4828
4907
  if (!args.db || !args.client) {
4829
- logger19.debug("Health check failed: connection not initialized", { vendor: args.vendor });
4908
+ logger20.debug("Health check failed: connection not initialized", { vendor: args.vendor });
4830
4909
  return false;
4831
4910
  }
4832
4911
  try {
4833
4912
  await args.execute(drizzleOrm.sql`SELECT 1`);
4834
- logger19.debug("Health check passed", { vendor: args.vendor });
4913
+ logger20.debug("Health check passed", { vendor: args.vendor });
4835
4914
  return true;
4836
4915
  } catch (error) {
4837
- logger19.debug("Health check failed", {
4916
+ logger20.debug("Health check failed", {
4838
4917
  vendor: args.vendor,
4839
4918
  error: error instanceof Error ? error.message : String(error)
4840
4919
  });
@@ -4877,8 +4956,8 @@ function getConnectionPoolMetrics(vendor, client) {
4877
4956
  }
4878
4957
 
4879
4958
  // src/data/relational/connection/query-execution.ts
4880
- async function executeQuery(context, query, logger19) {
4881
- logger19.debug("Executing SQL query", {
4959
+ async function executeQuery(context, query, logger20) {
4960
+ logger20.debug("Executing SQL query", {
4882
4961
  vendor: context.vendor
4883
4962
  });
4884
4963
  if (context.vendor === "sqlite") {
@@ -4929,7 +5008,7 @@ async function executeInDedicatedConnection(context, callback) {
4929
5008
  try {
4930
5009
  const { drizzle } = await import('drizzle-orm/node-postgres');
4931
5010
  const sessionDb = drizzle({ client: poolClient });
4932
- return await callback((query) => sessionDb.execute(query));
5011
+ return await callback(async (query) => sessionDb.execute(query));
4933
5012
  } finally {
4934
5013
  poolClient.release();
4935
5014
  }
@@ -5254,7 +5333,7 @@ function buildParameterizedQuery(sqlString, params) {
5254
5333
  return sqlChunks.length > 0 ? drizzleOrm.sql.join(sqlChunks, drizzleOrm.sql.raw("")) : drizzleOrm.sql.raw(sqlString);
5255
5334
  }
5256
5335
  }
5257
- async function executeQuery2(manager, input, context) {
5336
+ async function executeQuery2(executor, input, context) {
5258
5337
  const startTime = Date.now();
5259
5338
  logger8.debug("Executing query", {
5260
5339
  vendor: input.vendor,
@@ -5263,10 +5342,13 @@ async function executeQuery2(manager, input, context) {
5263
5342
  });
5264
5343
  try {
5265
5344
  validateSqlString(input.sql, input.vendor);
5345
+ if (context?.transaction) {
5346
+ validateManagedTransactionSql(input.sql, input.vendor);
5347
+ }
5266
5348
  enforceParameterizedQueryUsage(input.sql, input.params, input.vendor);
5267
5349
  const parameterizedQuery = buildParameterizedQuery(input.sql, input.params);
5268
- const executor = context?.transaction ?? manager;
5269
- const result = await executor.execute(parameterizedQuery);
5350
+ const session = context?.transaction ?? executor;
5351
+ const result = await session.execute(parameterizedQuery);
5270
5352
  const executionTime = Date.now() - startTime;
5271
5353
  const rows = Array.isArray(result) ? result : result.rows || [];
5272
5354
  const rowCount = result.rowCount || result.affectedRows || rows.length;
@@ -5289,7 +5371,7 @@ async function executeQuery2(manager, input, context) {
5289
5371
  });
5290
5372
  if (error instanceof Error) {
5291
5373
  const message = error.message;
5292
- if (message.includes("Missing parameter") || message.includes("Missing parameters") || message.includes("Parameters provided but no placeholders") || message.includes("Mixed parameter styles") || message.includes("SQL query must not be empty") || message.includes("SQL query contains null bytes") || message.includes("Detected dangerous SQL operation") || message.includes("Parameters are required for INSERT/UPDATE/DELETE queries")) {
5374
+ if (message.includes("Missing parameter") || message.includes("Missing parameters") || message.includes("Parameters provided but no placeholders") || message.includes("Mixed parameter styles") || message.includes("SQL query must not be empty") || message.includes("SQL query contains null bytes") || message.includes("Detected dangerous SQL operation") || message === MANAGED_TRANSACTION_CONTROL_ERROR_MESSAGE || message.includes("Parameters are required for INSERT/UPDATE/DELETE queries")) {
5293
5375
  throw error;
5294
5376
  }
5295
5377
  }
@@ -6146,6 +6228,8 @@ var ManagedTransaction = class {
6146
6228
  executeQuery;
6147
6229
  completed = false;
6148
6230
  cancelledReason = null;
6231
+ inFlight = /* @__PURE__ */ new Set();
6232
+ inFlightFailure = null;
6149
6233
  savepointCounter = 0;
6150
6234
  sqliteReadUncommittedOriginal = null;
6151
6235
  shouldRestoreSqliteReadUncommitted = false;
@@ -6158,14 +6242,36 @@ var ManagedTransaction = class {
6158
6242
  isActive() {
6159
6243
  return !this.completed;
6160
6244
  }
6161
- async execute(query) {
6245
+ execute(query) {
6246
+ try {
6247
+ return this.executeScopedQuery(query);
6248
+ } catch (error) {
6249
+ return Promise.reject(error instanceof Error ? error : new Error(String(error)));
6250
+ }
6251
+ }
6252
+ async settle() {
6253
+ while (this.inFlight.size > 0) {
6254
+ await Promise.allSettled([...this.inFlight]);
6255
+ }
6256
+ return this.inFlightFailure;
6257
+ }
6258
+ executeScopedQuery(query) {
6162
6259
  if (this.cancelledReason) {
6163
6260
  throw new Error(this.cancelledReason);
6164
6261
  }
6165
6262
  if (!this.isActive()) {
6166
6263
  throw new Error("Transaction is no longer active");
6167
6264
  }
6168
- return this.executeQuery(query);
6265
+ const execution = this.executeQuery(query);
6266
+ this.inFlight.add(execution);
6267
+ void execution.then(
6268
+ () => this.inFlight.delete(execution),
6269
+ (reason) => {
6270
+ this.inFlightFailure ??= { reason };
6271
+ this.inFlight.delete(execution);
6272
+ }
6273
+ );
6274
+ return execution;
6169
6275
  }
6170
6276
  async begin() {
6171
6277
  if (this.vendor === "mysql") {
@@ -6202,7 +6308,7 @@ var ManagedTransaction = class {
6202
6308
  throw new Error("Transaction is no longer active");
6203
6309
  }
6204
6310
  const savepointName = this.normalizeSavepointName(name ?? `sp_${++this.savepointCounter}`);
6205
- await this.executeQuery(drizzleOrm.sql.raw(`SAVEPOINT ${savepointName}`));
6311
+ await this.executeScopedQuery(drizzleOrm.sql.raw(`SAVEPOINT ${savepointName}`));
6206
6312
  return savepointName;
6207
6313
  }
6208
6314
  async rollbackToSavepoint(name) {
@@ -6210,14 +6316,14 @@ var ManagedTransaction = class {
6210
6316
  throw new Error("Transaction is no longer active");
6211
6317
  }
6212
6318
  const savepointName = this.normalizeSavepointName(name);
6213
- await this.executeQuery(drizzleOrm.sql.raw(`ROLLBACK TO SAVEPOINT ${savepointName}`));
6319
+ await this.executeScopedQuery(drizzleOrm.sql.raw(`ROLLBACK TO SAVEPOINT ${savepointName}`));
6214
6320
  }
6215
6321
  async releaseSavepoint(name) {
6216
6322
  if (!this.isActive()) {
6217
6323
  throw new Error("Transaction is no longer active");
6218
6324
  }
6219
6325
  const savepointName = this.normalizeSavepointName(name);
6220
- await this.executeQuery(drizzleOrm.sql.raw(`RELEASE SAVEPOINT ${savepointName}`));
6326
+ await this.executeScopedQuery(drizzleOrm.sql.raw(`RELEASE SAVEPOINT ${savepointName}`));
6221
6327
  }
6222
6328
  async withSavepoint(operation, name) {
6223
6329
  const savepointName = await this.createSavepoint(name);
@@ -6316,17 +6422,24 @@ async function withTransaction(manager, operation, options) {
6316
6422
  executeQuery: executeQuery3,
6317
6423
  options: resolvedOptions
6318
6424
  });
6319
- await transaction.begin();
6425
+ let timedOut = false;
6320
6426
  try {
6427
+ await transaction.begin();
6321
6428
  const result = await withTransactionTimeout(
6322
6429
  operation(transaction),
6323
6430
  resolvedOptions.timeoutMs,
6324
6431
  () => {
6325
6432
  if (resolvedOptions.timeoutMs) {
6433
+ timedOut = true;
6326
6434
  transaction.cancel(`Transaction timed out after ${resolvedOptions.timeoutMs}ms`);
6327
6435
  }
6328
6436
  }
6329
6437
  );
6438
+ transaction.cancel("Transaction is no longer active");
6439
+ const inFlightFailure = await transaction.settle();
6440
+ if (inFlightFailure) {
6441
+ throw inFlightFailure.reason;
6442
+ }
6330
6443
  if (transaction.isActive()) {
6331
6444
  await transaction.commit();
6332
6445
  }
@@ -6337,6 +6450,10 @@ async function withTransaction(manager, operation, options) {
6337
6450
  });
6338
6451
  return result;
6339
6452
  } catch (error) {
6453
+ if (!timedOut) {
6454
+ transaction.cancel(error instanceof Error ? error.message : "Transaction failed");
6455
+ }
6456
+ await transaction.settle();
6340
6457
  if (transaction.isActive()) {
6341
6458
  try {
6342
6459
  await transaction.rollback();
@@ -6841,16 +6958,33 @@ async function inspectSQLite(runQueryRows) {
6841
6958
  // src/data/relational/schema/schema-inspector.ts
6842
6959
  var logger11 = core.createLogger("agentforge:tools:data:relational:schema-inspector");
6843
6960
  var DEFAULT_CACHE_TTL_MS = 6e4;
6844
- var schemaCache = /* @__PURE__ */ new Map();
6845
- var SchemaInspector = class _SchemaInspector {
6846
- constructor(manager, vendor, config) {
6847
- this.manager = manager;
6961
+ var SchemaCache = class {
6962
+ entries = /* @__PURE__ */ new Map();
6963
+ get(cacheKey) {
6964
+ return this.entries.get(cacheKey);
6965
+ }
6966
+ set(cacheKey, entry) {
6967
+ this.entries.set(cacheKey, entry);
6968
+ }
6969
+ delete(cacheKey) {
6970
+ this.entries.delete(cacheKey);
6971
+ }
6972
+ clear() {
6973
+ this.entries.clear();
6974
+ }
6975
+ };
6976
+ var schemaCache = new SchemaCache();
6977
+ var SchemaInspector = class {
6978
+ constructor(executor, vendor, config) {
6979
+ this.executor = executor;
6848
6980
  this.vendor = vendor;
6849
6981
  this.cacheTtlMs = config?.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
6850
6982
  this.cacheKey = config?.cacheKey;
6983
+ this.cache = config?.cache ?? schemaCache;
6851
6984
  }
6852
6985
  cacheTtlMs;
6853
6986
  cacheKey;
6987
+ cache;
6854
6988
  static clearCache(cacheKey) {
6855
6989
  if (cacheKey) {
6856
6990
  schemaCache.delete(cacheKey);
@@ -6859,21 +6993,25 @@ var SchemaInspector = class _SchemaInspector {
6859
6993
  schemaCache.clear();
6860
6994
  }
6861
6995
  invalidateCache() {
6862
- _SchemaInspector.clearCache(this.cacheKey);
6996
+ if (this.cacheKey) {
6997
+ this.cache.delete(this.cacheKey);
6998
+ return;
6999
+ }
7000
+ this.cache.clear();
6863
7001
  }
6864
7002
  async inspect(options) {
6865
7003
  const tableFilters = validateTableFilters(options?.tables);
6866
7004
  const bypassCache = options?.bypassCache ?? false;
6867
- if (!bypassCache && this.cacheKey) {
6868
- const cached = schemaCache.get(this.cacheKey);
7005
+ if (!bypassCache && this.cacheKey && this.cacheTtlMs > 0) {
7006
+ const cached = this.cache.get(this.cacheKey);
6869
7007
  if (cached && cached.expiresAt > Date.now()) {
6870
7008
  logger11.debug("Schema cache hit", { vendor: this.vendor });
6871
7009
  return filterSchemaTables(cloneSchema(cached.schema), tableFilters);
6872
7010
  }
6873
7011
  }
6874
7012
  const schema = await this.inspectFromDatabase();
6875
- if (this.cacheKey && this.cacheTtlMs > 0) {
6876
- schemaCache.set(this.cacheKey, {
7013
+ if (!bypassCache && this.cacheKey && this.cacheTtlMs > 0) {
7014
+ this.cache.set(this.cacheKey, {
6877
7015
  schema: cloneSchema(schema),
6878
7016
  expiresAt: Date.now() + this.cacheTtlMs
6879
7017
  });
@@ -6905,7 +7043,7 @@ var SchemaInspector = class _SchemaInspector {
6905
7043
  }
6906
7044
  }
6907
7045
  async runQueryRows(query) {
6908
- const result = await executeQuery2(this.manager, {
7046
+ const result = await executeQuery2(this.executor, {
6909
7047
  sql: query,
6910
7048
  vendor: this.vendor
6911
7049
  });
@@ -7364,95 +7502,29 @@ function sortedReplacer(_key, value) {
7364
7502
  }
7365
7503
  return value;
7366
7504
  }
7367
- var relationalQuerySchema = zod.z.object({
7368
- sql: zod.z.string().describe("SQL query string to execute"),
7369
- params: zod.z.union([
7370
- zod.z.array(zod.z.unknown()).describe("Positional parameters (e.g., [value1, value2])"),
7371
- zod.z.record(zod.z.string(), zod.z.unknown()).describe('Named parameters (e.g., { name: "John", age: 30 })')
7372
- ]).optional().describe("Query parameters for parameter binding (prevents SQL injection)"),
7373
- vendor: zod.z.enum(["postgresql", "mysql", "sqlite"]).describe("Database vendor"),
7374
- connectionString: zod.z.string().min(1, "Database connection string is required").describe("Database connection string used to create a new connection")
7375
- });
7376
- var relationalQuery = core.toolBuilder().name("relational-query").displayName("Relational Query").description("Execute raw SQL queries against relational databases (PostgreSQL, MySQL, SQLite) with parameter binding to prevent SQL injection").category(core.ToolCategory.DATABASE).tags(["database", "sql", "query", "postgresql", "mysql", "sqlite"]).schema(relationalQuerySchema).example({
7377
- description: "SELECT query with positional parameters",
7378
- input: {
7379
- sql: "SELECT * FROM users WHERE id = $1",
7380
- params: [42],
7381
- vendor: "postgresql",
7382
- connectionString: "postgresql://user:pass@localhost:5432/mydb"
7383
- }
7384
- }).example({
7385
- description: "INSERT query with named parameters",
7386
- input: {
7387
- sql: "INSERT INTO users (name, email) VALUES (:name, :email)",
7388
- params: { name: "John Doe", email: "john@example.com" },
7389
- vendor: "postgresql",
7390
- connectionString: "postgresql://user:pass@localhost:5432/mydb"
7391
- }
7392
- }).example({
7393
- description: "UPDATE query with positional parameters",
7394
- input: {
7395
- sql: "UPDATE users SET status = ? WHERE id = ?",
7396
- params: ["active", 42],
7397
- vendor: "mysql",
7398
- connectionString: "mysql://user:pass@localhost:3306/mydb"
7399
- }
7400
- }).usageNotes("Always use parameter binding (params) instead of string concatenation to prevent SQL injection. Supports positional ($1, ?) and named (:name) parameters.").limitation("Requires database-specific driver as peer dependency (pg, mysql2, or better-sqlite3)").limitation("Connection string must be valid for the specified vendor").limitation("Large result sets may impact performance").limitation("Placeholder characters ($n, ?, :name) inside SQL string literals are not supported. Use parameter binding for all dynamic values instead of embedding them in string literals").implement(async (input) => {
7401
- const manager = new ConnectionManager({
7402
- vendor: input.vendor,
7403
- connection: input.connectionString || ""
7404
- });
7405
- try {
7406
- await manager.initialize();
7407
- const result = await executeQuery2(manager, {
7408
- sql: input.sql,
7409
- params: input.params,
7410
- vendor: input.vendor
7411
- });
7412
- return {
7413
- success: true,
7414
- rows: result.rows,
7415
- rowCount: result.rowCount,
7416
- executionTime: result.executionTime
7417
- };
7418
- } catch (error) {
7419
- return {
7420
- success: false,
7421
- error: error instanceof Error ? error.message : "Unknown error occurred",
7422
- rows: [],
7423
- rowCount: 0
7424
- };
7425
- } finally {
7426
- await manager.close();
7427
- }
7428
- }).build();
7429
- var whereOperatorSchema = zod.z.enum([
7505
+
7506
+ // src/data/relational/connection-failure-messages.ts
7507
+ var QUERY_CONNECTION_FAILURE = "Failed to connect to the configured database.";
7508
+ var SELECT_CONNECTION_FAILURE = "Failed to execute SELECT query. Please verify the configured database connection.";
7509
+ var INSERT_CONNECTION_FAILURE = "Failed to execute INSERT query. Please verify the configured database connection.";
7510
+ var UPDATE_CONNECTION_FAILURE = "Failed to execute UPDATE query. Please verify the configured database connection.";
7511
+ var DELETE_CONNECTION_FAILURE = "Failed to execute DELETE query. Please verify the configured database connection.";
7512
+ var deleteWhereOperatorSchema = zod.z.enum([
7430
7513
  "eq",
7431
- // equals
7432
7514
  "ne",
7433
- // not equals
7434
7515
  "gt",
7435
- // greater than
7436
7516
  "lt",
7437
- // less than
7438
7517
  "gte",
7439
- // greater than or equal
7440
7518
  "lte",
7441
- // less than or equal
7442
7519
  "like",
7443
- // LIKE pattern matching
7444
7520
  "in",
7445
- // IN array
7446
7521
  "notIn",
7447
- // NOT IN array
7448
7522
  "isNull",
7449
- // IS NULL
7450
7523
  "isNotNull"
7451
- // IS NOT NULL
7452
7524
  ]);
7453
- var whereConditionSchema = zod.z.object({
7525
+ var deleteWhereConditionSchema = zod.z.object({
7454
7526
  column: zod.z.string().min(1, "Column name must not be empty").describe("Column name to filter on"),
7455
- operator: whereOperatorSchema.describe("Comparison operator"),
7527
+ operator: deleteWhereOperatorSchema.describe("Comparison operator"),
7456
7528
  value: zod.z.union([
7457
7529
  zod.z.string(),
7458
7530
  zod.z.number(),
@@ -7460,10 +7532,10 @@ var whereConditionSchema = zod.z.object({
7460
7532
  zod.z.array(zod.z.union([zod.z.string(), zod.z.number()])),
7461
7533
  zod.z.null()
7462
7534
  ]).optional().describe("Value to compare against (not required for isNull/isNotNull)")
7463
- }).superRefine((val, ctx) => {
7464
- const op = val.operator;
7535
+ }).superRefine((value, ctx) => {
7536
+ const op = value.operator;
7465
7537
  if (op === "isNull" || op === "isNotNull") {
7466
- if (val.value !== void 0) {
7538
+ if (value.value !== void 0) {
7467
7539
  ctx.addIssue({
7468
7540
  code: zod.z.ZodIssueCode.custom,
7469
7541
  path: ["value"],
@@ -7472,7 +7544,7 @@ var whereConditionSchema = zod.z.object({
7472
7544
  }
7473
7545
  return;
7474
7546
  }
7475
- if (val.value === void 0) {
7547
+ if (value.value === void 0) {
7476
7548
  ctx.addIssue({
7477
7549
  code: zod.z.ZodIssueCode.custom,
7478
7550
  path: ["value"],
@@ -7481,13 +7553,13 @@ var whereConditionSchema = zod.z.object({
7481
7553
  return;
7482
7554
  }
7483
7555
  if (op === "in" || op === "notIn") {
7484
- if (!Array.isArray(val.value)) {
7556
+ if (!Array.isArray(value.value)) {
7485
7557
  ctx.addIssue({
7486
7558
  code: zod.z.ZodIssueCode.custom,
7487
7559
  path: ["value"],
7488
7560
  message: `${op === "in" ? "IN" : "NOT IN"} operator requires an array value`
7489
7561
  });
7490
- } else if (val.value.length === 0) {
7562
+ } else if (value.value.length === 0) {
7491
7563
  ctx.addIssue({
7492
7564
  code: zod.z.ZodIssueCode.custom,
7493
7565
  path: ["value"],
@@ -7496,7 +7568,7 @@ var whereConditionSchema = zod.z.object({
7496
7568
  }
7497
7569
  return;
7498
7570
  }
7499
- if (val.value === null) {
7571
+ if (value.value === null) {
7500
7572
  ctx.addIssue({
7501
7573
  code: zod.z.ZodIssueCode.custom,
7502
7574
  path: ["value"],
@@ -7504,7 +7576,7 @@ var whereConditionSchema = zod.z.object({
7504
7576
  });
7505
7577
  return;
7506
7578
  }
7507
- if (op === "like" && typeof val.value !== "string") {
7579
+ if (op === "like" && typeof value.value !== "string") {
7508
7580
  ctx.addIssue({
7509
7581
  code: zod.z.ZodIssueCode.custom,
7510
7582
  path: ["value"],
@@ -7512,326 +7584,124 @@ var whereConditionSchema = zod.z.object({
7512
7584
  });
7513
7585
  return;
7514
7586
  }
7515
- if ((op === "gt" || op === "lt" || op === "gte" || op === "lte") && typeof val.value !== "string" && typeof val.value !== "number") {
7587
+ if ((op === "gt" || op === "lt" || op === "gte" || op === "lte") && typeof value.value !== "string" && typeof value.value !== "number") {
7516
7588
  ctx.addIssue({
7517
7589
  code: zod.z.ZodIssueCode.custom,
7518
7590
  path: ["value"],
7519
7591
  message: `${op.toUpperCase()} operator requires a string or number value`
7520
7592
  });
7521
- return;
7522
7593
  }
7523
- if ((op === "eq" || op === "ne") && Array.isArray(val.value)) {
7594
+ });
7595
+ var deleteSoftDeleteSchema = zod.z.object({
7596
+ column: zod.z.string().min(1, "Soft delete column must not be empty").default("deleted_at").describe("Column to write deletion timestamp/value into"),
7597
+ value: zod.z.union([zod.z.string(), zod.z.number()]).optional().describe("Optional explicit value for the soft-delete column (defaults to ISO timestamp)")
7598
+ }).describe("Enable soft delete by updating a column instead of physically deleting rows");
7599
+ var deleteBatchOperationSchema = zod.z.object({
7600
+ where: zod.z.array(deleteWhereConditionSchema).optional().describe("WHERE conditions (combined with AND)"),
7601
+ allowFullTableDelete: zod.z.boolean().default(false).describe("Explicitly allow DELETE without WHERE conditions for this operation"),
7602
+ cascade: zod.z.boolean().default(false).describe("Enable cascade-aware error messaging for this operation"),
7603
+ softDelete: deleteSoftDeleteSchema.optional().describe("Use soft-delete semantics instead of hard DELETE for this operation")
7604
+ }).superRefine((value, ctx) => {
7605
+ const hasWhere = (value.where?.length ?? 0) > 0;
7606
+ if (!value.allowFullTableDelete && !hasWhere) {
7524
7607
  ctx.addIssue({
7525
7608
  code: zod.z.ZodIssueCode.custom,
7526
- path: ["value"],
7527
- message: `${op.toUpperCase()} operator requires a scalar value`
7609
+ path: ["where"],
7610
+ message: "WHERE conditions are required unless allowFullTableDelete is true"
7528
7611
  });
7529
7612
  }
7530
7613
  });
7531
- var orderDirectionSchema = zod.z.enum(["asc", "desc"]);
7532
- var orderBySchema = zod.z.object({
7533
- column: zod.z.string().min(1, "Column name must not be empty").describe("Column name to order by"),
7534
- direction: orderDirectionSchema.describe("Sort direction (ascending or descending)")
7535
- });
7536
- var streamingOptionsSchema = zod.z.object({
7537
- enabled: zod.z.boolean().default(true).describe("Enable chunked streaming mode for large result sets"),
7538
- chunkSize: zod.z.number().int().min(1).max(5e3).optional().describe("Rows fetched per chunk (default: 100)"),
7539
- maxRows: zod.z.number().int().positive().optional().describe("Optional cap on streamed rows"),
7540
- sampleSize: zod.z.number().int().min(0).max(5e3).optional().describe("Number of rows to include in the response payload"),
7541
- benchmark: zod.z.boolean().optional().default(false).describe("Run memory benchmark comparing regular vs streaming execution (adds two extra query executions; use side-effect-free SELECT statements)")
7614
+ var deleteBatchOptionsSchema = zod.z.object({
7615
+ enabled: zod.z.boolean().optional().default(true).describe("Enable batched processing for operations[] payloads"),
7616
+ batchSize: zod.z.number().int().positive().max(5e3).optional().default(100).describe("Operations per batch chunk"),
7617
+ continueOnError: zod.z.boolean().optional().default(true).describe("Continue processing remaining chunks when one chunk fails"),
7618
+ maxRetries: zod.z.number().int().min(0).max(5).optional().default(0).describe("Retry attempts per failed chunk"),
7619
+ retryDelayMs: zod.z.number().int().min(0).max(6e4).optional().default(0).describe("Delay between retry attempts in milliseconds"),
7620
+ benchmark: zod.z.boolean().optional().default(false).describe("Run synthetic benchmark metadata comparing individual vs batched execution callbacks")
7542
7621
  });
7543
- var relationalSelectSchema = zod.z.object({
7622
+ var relationalDeleteSchema = zod.z.object({
7544
7623
  table: zod.z.string().min(1, "Table name is required").regex(
7545
7624
  VALID_QUALIFIED_IDENTIFIER_PATTERN,
7546
7625
  "Table name contains invalid characters. Only alphanumeric characters, underscores, and dots (for schema qualification) are allowed."
7547
- ).describe("Table name to select from (schema-qualified names supported, e.g. public.users)"),
7548
- columns: zod.z.array(zod.z.string().min(1, "Column name must not be empty")).min(1, "Columns array must not be empty").optional().describe("Columns to select (omit for SELECT *)"),
7549
- where: zod.z.array(whereConditionSchema).optional().describe("WHERE conditions (combined with AND)"),
7550
- orderBy: zod.z.array(orderBySchema).optional().describe("ORDER BY clauses"),
7551
- limit: zod.z.number().int().positive().optional().describe("Maximum number of rows to return"),
7552
- offset: zod.z.number().int().nonnegative().optional().describe("Number of rows to skip"),
7553
- streaming: streamingOptionsSchema.optional().describe("Optional streaming mode configuration"),
7626
+ ).describe("Table name to delete from (schema-qualified names supported, e.g. public.users)"),
7627
+ where: zod.z.array(deleteWhereConditionSchema).optional().describe("WHERE conditions (combined with AND)"),
7628
+ allowFullTableDelete: zod.z.boolean().default(false).describe("Explicitly allow DELETE without WHERE conditions"),
7629
+ cascade: zod.z.boolean().default(false).describe("Enable cascade-aware error messaging for foreign key constraints"),
7630
+ softDelete: deleteSoftDeleteSchema.optional().describe("Use soft-delete semantics instead of hard DELETE"),
7631
+ operations: zod.z.array(deleteBatchOperationSchema).min(1, "operations must contain at least one batch item").optional().describe("Batch delete operation list"),
7632
+ batch: deleteBatchOptionsSchema.optional().describe("Optional batch execution controls for operations payloads"),
7554
7633
  vendor: zod.z.enum(["postgresql", "mysql", "sqlite"]).describe("Database vendor"),
7555
7634
  connectionString: zod.z.string().min(1, "Database connection string is required").describe("Database connection string")
7635
+ }).superRefine((value, ctx) => {
7636
+ const hasOperations = (value.operations?.length ?? 0) > 0;
7637
+ if (hasOperations && (value.where?.length ?? 0) > 0) {
7638
+ ctx.addIssue({
7639
+ code: zod.z.ZodIssueCode.custom,
7640
+ path: ["where"],
7641
+ message: "where cannot be provided when operations[] is used"
7642
+ });
7643
+ }
7644
+ if (hasOperations && value.allowFullTableDelete) {
7645
+ ctx.addIssue({
7646
+ code: zod.z.ZodIssueCode.custom,
7647
+ path: ["allowFullTableDelete"],
7648
+ message: "allowFullTableDelete cannot be provided when operations[] is used"
7649
+ });
7650
+ }
7651
+ if (hasOperations && value.cascade) {
7652
+ ctx.addIssue({
7653
+ code: zod.z.ZodIssueCode.custom,
7654
+ path: ["cascade"],
7655
+ message: "cascade cannot be provided when operations[] is used"
7656
+ });
7657
+ }
7658
+ if (hasOperations && value.softDelete) {
7659
+ ctx.addIssue({
7660
+ code: zod.z.ZodIssueCode.custom,
7661
+ path: ["softDelete"],
7662
+ message: "softDelete cannot be provided when operations[] is used"
7663
+ });
7664
+ }
7665
+ if (hasOperations) {
7666
+ return;
7667
+ }
7668
+ const hasWhere = (value.where?.length ?? 0) > 0;
7669
+ if (!value.allowFullTableDelete && !hasWhere) {
7670
+ ctx.addIssue({
7671
+ code: zod.z.ZodIssueCode.custom,
7672
+ path: ["where"],
7673
+ message: "WHERE conditions are required unless allowFullTableDelete is true"
7674
+ });
7675
+ }
7556
7676
  });
7557
7677
 
7558
- // src/data/relational/tools/relational-select/query-builder.ts
7559
- function buildSelectQuery2(input) {
7560
- const queryInput = {
7561
- table: input.table,
7562
- columns: input.columns,
7563
- where: input.where,
7564
- orderBy: input.orderBy,
7565
- limit: input.limit,
7566
- offset: input.offset,
7567
- vendor: input.vendor
7568
- };
7569
- return buildSelectQuery(queryInput);
7570
- }
7571
-
7572
- // src/data/relational/tools/relational-select/error-utils.ts
7573
- var SAFE_VALIDATION_ERROR_PATTERNS = [
7678
+ // src/data/relational/tools/relational-delete/error-utils.ts
7679
+ var SAFE_DELETE_VALIDATION_PATTERNS = [
7574
7680
  "must not be empty",
7575
7681
  "contains invalid characters",
7576
- "requires an array value",
7577
- "requires a non-empty array value",
7578
- "must be an integer between",
7579
- "must be a positive integer",
7682
+ "WHERE conditions are required for DELETE queries",
7683
+ "WHERE conditions are required unless allowFullTableDelete is true",
7684
+ "value is required for this operator",
7580
7685
  "null is only allowed with isNull/isNotNull operators",
7581
- "LIKE operator requires a string value",
7686
+ "operator requires a value",
7582
7687
  "operator requires a string or number value",
7583
- "operator requires a scalar value",
7584
- "Stream cancelled by caller"
7585
- ];
7586
- function isSafeValidationError(error) {
7587
- if (!(error instanceof Error)) {
7588
- return false;
7589
- }
7590
- return SAFE_VALIDATION_ERROR_PATTERNS.some(
7591
- (pattern) => error.message.includes(pattern)
7592
- );
7593
- }
7594
-
7595
- // src/data/relational/tools/relational-select/executor.ts
7596
- var logger16 = core.createLogger("agentforge:tools:data:relational:select");
7597
- function toSelectQueryInput(input) {
7598
- return {
7599
- table: input.table,
7600
- columns: input.columns,
7601
- where: input.where,
7602
- orderBy: input.orderBy,
7603
- limit: input.limit,
7604
- offset: input.offset,
7605
- vendor: input.vendor
7606
- };
7607
- }
7608
- async function executeSelect(manager, input, context) {
7609
- const startTime = Date.now();
7610
- logger16.debug("Building SELECT query", {
7611
- vendor: input.vendor,
7612
- table: input.table,
7613
- hasWhere: !!input.where,
7614
- hasOrderBy: !!input.orderBy,
7615
- hasLimit: !!input.limit,
7616
- streamingEnabled: !!input.streaming?.enabled
7617
- });
7618
- try {
7619
- const executor = context?.transaction ?? manager;
7620
- if (input.streaming?.enabled) {
7621
- const streamOptions = {
7622
- chunkSize: input.streaming.chunkSize,
7623
- maxRows: input.streaming.maxRows,
7624
- sampleSize: input.streaming.sampleSize
7625
- };
7626
- const streamInput = toSelectQueryInput(input);
7627
- if (input.streaming.benchmark) {
7628
- logger16.warn("Streaming benchmark enabled; SELECT will execute up to three times (result + benchmark regular + benchmark streaming).", {
7629
- vendor: input.vendor,
7630
- table: input.table,
7631
- chunkSize: streamOptions.chunkSize ?? DEFAULT_CHUNK_SIZE,
7632
- ...streamOptions.maxRows !== void 0 ? { maxRows: streamOptions.maxRows } : {},
7633
- ...streamOptions.sampleSize !== void 0 ? { sampleSize: streamOptions.sampleSize } : {}
7634
- });
7635
- }
7636
- const streamResult = await executeStreamingSelect(executor, streamInput, streamOptions);
7637
- const benchmark = input.streaming.benchmark ? await benchmarkStreamingSelectMemory(executor, streamInput, streamOptions) : void 0;
7638
- const executionTime2 = Date.now() - startTime;
7639
- logger16.debug("Streaming SELECT query executed successfully", {
7640
- vendor: input.vendor,
7641
- table: input.table,
7642
- rowCount: streamResult.rowCount,
7643
- chunkCount: streamResult.chunkCount,
7644
- executionTime: executionTime2,
7645
- cancelled: streamResult.cancelled
7646
- });
7647
- return {
7648
- rows: streamResult.rows,
7649
- rowCount: streamResult.rowCount,
7650
- executionTime: executionTime2,
7651
- streaming: {
7652
- enabled: true,
7653
- chunkSize: input.streaming.chunkSize ?? DEFAULT_CHUNK_SIZE,
7654
- chunkCount: streamResult.chunkCount,
7655
- sampledRowCount: streamResult.rows.length,
7656
- streamedRowCount: streamResult.rowCount,
7657
- cancelled: streamResult.cancelled,
7658
- memoryUsage: streamResult.memoryUsage,
7659
- benchmark
7660
- }
7661
- };
7662
- }
7663
- const query = buildSelectQuery2(input);
7664
- const result = await executor.execute(query);
7665
- const executionTime = Date.now() - startTime;
7666
- const rows = Array.isArray(result) ? result : result.rows || [];
7667
- const rowCount = rows.length;
7668
- logger16.debug("SELECT query executed successfully", {
7669
- vendor: input.vendor,
7670
- table: input.table,
7671
- rowCount,
7672
- executionTime
7673
- });
7674
- return {
7675
- rows,
7676
- rowCount,
7677
- executionTime
7678
- };
7679
- } catch (error) {
7680
- const executionTime = Date.now() - startTime;
7681
- logger16.error("SELECT query execution failed", {
7682
- vendor: input.vendor,
7683
- table: input.table,
7684
- error: error instanceof Error ? error.message : String(error),
7685
- executionTime
7686
- });
7687
- if (isSafeValidationError(error)) {
7688
- throw error;
7689
- }
7690
- throw new Error("SELECT query failed. See logs for details.", { cause: error });
7691
- }
7692
- }
7693
-
7694
- // src/data/relational/tools/relational-select/index.ts
7695
- var relationalSelect = core.toolBuilder().name("relational-select").displayName("Relational SELECT").description("Execute type-safe SELECT queries with WHERE conditions, ORDER BY, LIMIT, and OFFSET using Drizzle ORM query builder").category(core.ToolCategory.DATABASE).tags(["database", "sql", "select", "query", "postgresql", "mysql", "sqlite"]).schema(relationalSelectSchema).example({
7696
- description: "SELECT with WHERE condition",
7697
- input: {
7698
- table: "users",
7699
- columns: ["id", "name", "email"],
7700
- where: [{ column: "status", operator: "eq", value: "active" }],
7701
- vendor: "postgresql",
7702
- connectionString: "postgresql://localhost/mydb"
7703
- }
7704
- }).example({
7705
- description: "SELECT with pagination",
7706
- input: {
7707
- table: "products",
7708
- orderBy: [{ column: "created_at", direction: "desc" }],
7709
- limit: 20,
7710
- offset: 0,
7711
- vendor: "mysql",
7712
- connectionString: "mysql://localhost/shop"
7713
- }
7714
- }).example({
7715
- description: "SELECT with streaming mode",
7716
- input: {
7717
- table: "events",
7718
- orderBy: [{ column: "id", direction: "asc" }],
7719
- streaming: {
7720
- enabled: true,
7721
- chunkSize: 250,
7722
- sampleSize: 25
7723
- },
7724
- vendor: "postgresql",
7725
- connectionString: "postgresql://localhost/analytics"
7726
- }
7727
- }).implement(async (input) => {
7728
- const manager = new ConnectionManager({
7729
- vendor: input.vendor,
7730
- connection: input.connectionString
7731
- });
7732
- try {
7733
- await manager.connect();
7734
- const result = await executeSelect(manager, input);
7735
- return {
7736
- success: true,
7737
- rows: result.rows,
7738
- rowCount: result.rowCount,
7739
- executionTime: result.executionTime,
7740
- streaming: result.streaming
7741
- };
7742
- } catch (error) {
7743
- let errorMessage = "Failed to execute SELECT query. Please verify your input and database connection.";
7744
- if (isSafeValidationError(error)) {
7745
- errorMessage = error.message;
7746
- }
7747
- return {
7748
- success: false,
7749
- error: errorMessage,
7750
- rows: [],
7751
- rowCount: 0
7752
- };
7753
- } finally {
7754
- await manager.disconnect();
7755
- }
7756
- }).build();
7757
- var insertValueSchema = zod.z.union([
7758
- zod.z.string().describe("String value"),
7759
- zod.z.number().describe("Numeric value"),
7760
- zod.z.boolean().describe("Boolean value"),
7761
- zod.z.null().describe("Null value")
7762
- ]).describe("Scalar value for an INSERT column");
7763
- var insertRowSchema = zod.z.record(
7764
- zod.z.string().min(1, "Column name must not be empty").describe("Column name"),
7765
- insertValueSchema
7766
- ).describe("One row object where keys are column names and values are scalar values");
7767
- var insertReturningModeSchema = zod.z.enum(["none", "id", "row"]);
7768
- var insertReturningSchema = zod.z.object({
7769
- mode: insertReturningModeSchema.default("none").describe("Returning mode: none, id, or full row"),
7770
- idColumn: zod.z.string().min(1, "idColumn must not be empty").optional().describe('Primary key column to return when mode is "id"')
7771
- }).superRefine((value, ctx) => {
7772
- if (value.idColumn && value.mode !== "id") {
7773
- ctx.addIssue({
7774
- code: zod.z.ZodIssueCode.custom,
7775
- path: ["idColumn"],
7776
- message: 'idColumn can only be provided when returning.mode is "id"'
7777
- });
7778
- }
7779
- });
7780
- var insertBatchOptionsSchema = zod.z.object({
7781
- enabled: zod.z.boolean().optional().default(true).describe("Enable chunked batch execution for array insert payloads"),
7782
- batchSize: zod.z.number().int().positive().max(5e3).optional().default(100).describe("Rows per batch chunk"),
7783
- continueOnError: zod.z.boolean().optional().default(true).describe("Continue processing remaining chunks when one chunk fails"),
7784
- maxRetries: zod.z.number().int().min(0).max(5).optional().default(0).describe("Retry attempts per failed chunk"),
7785
- retryDelayMs: zod.z.number().int().min(0).max(6e4).optional().default(0).describe("Delay between retry attempts in milliseconds"),
7786
- benchmark: zod.z.boolean().optional().default(false).describe("Run synthetic benchmark metadata comparing individual vs batched execution callbacks")
7787
- });
7788
- var relationalInsertSchema = zod.z.object({
7789
- table: zod.z.string().min(1, "Table name is required").regex(
7790
- VALID_QUALIFIED_IDENTIFIER_PATTERN,
7791
- "Table name contains invalid characters. Only alphanumeric characters, underscores, and dots (for schema qualification) are allowed."
7792
- ).describe("Table name to insert into (schema-qualified names supported, e.g. public.users)"),
7793
- data: zod.z.union([
7794
- insertRowSchema,
7795
- zod.z.array(insertRowSchema).min(1, "Insert data array must not be empty")
7796
- ]).describe("Single row object or array of row objects to insert"),
7797
- batch: insertBatchOptionsSchema.optional().describe("Optional batch execution controls for array insert payloads"),
7798
- returning: insertReturningSchema.optional().describe("Optional RETURNING behavior"),
7799
- vendor: zod.z.enum(["postgresql", "mysql", "sqlite"]).describe("Database vendor"),
7800
- connectionString: zod.z.string().min(1, "Database connection string is required").describe("Database connection string")
7801
- });
7802
-
7803
- // src/data/relational/tools/relational-insert/error-utils.ts
7804
- var SAFE_INSERT_VALIDATION_PATTERNS = [
7805
- "must not be empty",
7806
- "contains invalid characters",
7807
- "must be an object",
7808
- "must not be an empty array",
7809
- "must not be undefined",
7810
- "idColumn can only be provided",
7811
- "Returning full rows is not supported for mysql",
7812
- "Batch INSERT with only DEFAULT VALUES is not supported"
7688
+ "operator requires a string value",
7689
+ "operator requires a non-empty array value",
7690
+ "must not include value"
7813
7691
  ];
7814
7692
  var CONSTRAINT_VIOLATION_PATTERNS = [
7815
7693
  {
7816
- pattern: /(unique constraint|duplicate key|duplicate entry)/i,
7817
- message: "Insert failed: unique constraint violation."
7818
- },
7819
- {
7820
- pattern: /(foreign key constraint|violates foreign key constraint)/i,
7821
- message: "Insert failed: foreign key constraint violation."
7822
- },
7823
- {
7824
- pattern: /(not null constraint|cannot be null)/i,
7825
- message: "Insert failed: NOT NULL constraint violation."
7694
+ pattern: /(foreign key constraint|violates foreign key constraint|foreign key mismatch|a foreign key constraint fails)/i,
7695
+ message: "Delete failed: foreign key constraint violation."
7826
7696
  }
7827
7697
  ];
7828
- function isSafeInsertValidationError(error) {
7698
+ function isSafeDeleteValidationError(error) {
7829
7699
  if (!(error instanceof Error)) {
7830
7700
  return false;
7831
7701
  }
7832
- return SAFE_INSERT_VALIDATION_PATTERNS.some((pattern) => error.message.includes(pattern));
7702
+ return SAFE_DELETE_VALIDATION_PATTERNS.some((pattern) => error.message.includes(pattern));
7833
7703
  }
7834
- function getConstraintViolationMessage(error) {
7704
+ function getDeleteConstraintViolationMessage(error, cascade) {
7835
7705
  if (!(error instanceof Error)) {
7836
7706
  return null;
7837
7707
  }
@@ -7841,79 +7711,43 @@ function getConstraintViolationMessage(error) {
7841
7711
  }
7842
7712
  for (const entry of CONSTRAINT_VIOLATION_PATTERNS) {
7843
7713
  if (messages.some((msg) => entry.pattern.test(msg))) {
7714
+ if (cascade) {
7715
+ return `${entry.message} Verify database-level ON DELETE CASCADE is configured for related foreign keys.`;
7716
+ }
7844
7717
  return entry.message;
7845
7718
  }
7846
7719
  }
7847
7720
  return null;
7848
7721
  }
7849
- function isSafeInsertError(error) {
7722
+ function isSafeDeleteError(error) {
7850
7723
  if (!(error instanceof Error)) {
7851
7724
  return false;
7852
7725
  }
7853
- return isSafeInsertValidationError(error) || getConstraintViolationMessage(error) !== null;
7726
+ return SAFE_DELETE_VALIDATION_PATTERNS.some((pattern) => error.message.includes(pattern)) || error.message.startsWith("Delete failed:");
7854
7727
  }
7855
- var insertExecutorLogger = core.createLogger("agentforge:tools:data:relational:insert");
7728
+ var deleteExecutorLogger = core.createLogger("agentforge:tools:data:relational:delete");
7856
7729
  function toNumber(value) {
7857
7730
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
7858
7731
  }
7859
- function isPlainObject(value) {
7860
- return !!value && typeof value === "object" && !Array.isArray(value);
7861
- }
7862
- function normalizeExecutionResult(result) {
7732
+ function normalizeAffectedRows(result) {
7863
7733
  if (Array.isArray(result)) {
7864
- if (result.length > 0 && isPlainObject(result[0])) {
7734
+ if (result.length > 0 && result[0] && typeof result[0] === "object") {
7865
7735
  const first = result[0];
7866
7736
  const affectedRows = toNumber(first.affectedRows) ?? toNumber(first.rowCount) ?? toNumber(first.changes);
7867
- const insertId = toNumber(first.insertId);
7868
- const lastInsertRowid = toNumber(first.lastInsertRowid);
7869
- if (affectedRows !== void 0 || insertId !== void 0 || lastInsertRowid !== void 0) {
7870
- return {
7871
- rows: [],
7872
- rowCount: affectedRows ?? 0,
7873
- insertId,
7874
- lastInsertRowid
7875
- };
7737
+ if (affectedRows !== void 0) {
7738
+ return affectedRows;
7876
7739
  }
7877
7740
  }
7878
- return {
7879
- rows: result,
7880
- rowCount: result.length
7881
- };
7741
+ return result.length;
7882
7742
  }
7883
- if (isPlainObject(result)) {
7884
- const rows = Array.isArray(result.rows) ? result.rows : [];
7885
- const rowCount = toNumber(result.rowCount) ?? toNumber(result.affectedRows) ?? toNumber(result.changes) ?? rows.length;
7886
- return {
7887
- rows,
7888
- rowCount,
7889
- insertId: toNumber(result.insertId),
7890
- lastInsertRowid: toNumber(result.lastInsertRowid)
7891
- };
7743
+ if (result && typeof result === "object") {
7744
+ const resultRecord = result;
7745
+ return toNumber(resultRecord.rowCount) ?? toNumber(resultRecord.affectedRows) ?? toNumber(resultRecord.changes) ?? (Array.isArray(resultRecord.rows) ? resultRecord.rows.length : 0);
7892
7746
  }
7893
- return {
7894
- rows: [],
7895
- rowCount: 0
7896
- };
7747
+ return 0;
7897
7748
  }
7898
- function deriveInsertedIds(options) {
7899
- const { idColumn, inputRows, returnedRows, rowCount, insertId, lastInsertRowid } = options;
7900
- if (returnedRows.length > 0) {
7901
- const idsFromReturn = returnedRows.map((row) => isPlainObject(row) ? row[idColumn] : void 0).filter((id) => typeof id === "number" || typeof id === "string");
7902
- if (idsFromReturn.length > 0) {
7903
- return idsFromReturn;
7904
- }
7905
- }
7906
- if (inputRows.every((row) => typeof row[idColumn] === "number" || typeof row[idColumn] === "string")) {
7907
- return inputRows.map((row) => row[idColumn]);
7908
- }
7909
- if (insertId !== void 0 && rowCount > 0) {
7910
- return Array.from({ length: rowCount }, (_, index) => insertId + index);
7911
- }
7912
- if (lastInsertRowid !== void 0 && rowCount > 0) {
7913
- const startId = lastInsertRowid - rowCount + 1;
7914
- return Array.from({ length: rowCount }, (_, index) => startId + index);
7915
- }
7916
- return [];
7749
+ function toErrorMessage(error) {
7750
+ return error instanceof Error ? error.message : String(error);
7917
7751
  }
7918
7752
  function resolveBatchOptions(batch) {
7919
7753
  if (!batch || batch.enabled === false) {
@@ -7928,79 +7762,87 @@ function resolveBatchOptions(batch) {
7928
7762
  benchmark: batch.benchmark ?? false
7929
7763
  };
7930
7764
  }
7931
- function toSingleInsertRows(row) {
7932
- return [row];
7765
+ function toSingleDeleteOperation(input) {
7766
+ return {
7767
+ where: input.where,
7768
+ allowFullTableDelete: input.allowFullTableDelete,
7769
+ cascade: input.cascade,
7770
+ softDelete: input.softDelete
7771
+ };
7933
7772
  }
7934
7773
 
7935
- // src/data/relational/tools/relational-insert/executor-single.ts
7936
- async function executeInsertOnce(manager, input, context) {
7937
- const built = buildInsertQuery({
7774
+ // src/data/relational/tools/relational-delete/executor-single.ts
7775
+ async function executeSingleDelete(executor, input, operation, context) {
7776
+ const built = buildDeleteQuery({
7938
7777
  table: input.table,
7939
- data: input.data,
7940
- returning: input.returning,
7778
+ where: operation.where,
7779
+ allowFullTableDelete: operation.allowFullTableDelete,
7780
+ softDelete: operation.softDelete,
7941
7781
  vendor: input.vendor
7942
7782
  });
7943
- const executor = manager;
7944
- if (Array.isArray(built.query)) {
7945
- let totalRowCount = 0;
7946
- const allRows = [];
7947
- const allIds = [];
7948
- for (const singleQuery of built.query) {
7949
- const rawResult2 = await executor.execute(singleQuery);
7950
- const normalized2 = normalizeExecutionResult(rawResult2);
7951
- totalRowCount += normalized2.rowCount > 0 ? normalized2.rowCount : 1;
7952
- if (built.returningMode === "row") {
7953
- allRows.push(...normalized2.rows);
7954
- }
7955
- if (built.returningMode === "id") {
7956
- const ids = deriveInsertedIds({
7957
- idColumn: built.idColumn,
7958
- inputRows: toSingleInsertRows(built.rows[allIds.length]),
7959
- returnedRows: normalized2.rows,
7960
- rowCount: 1,
7961
- insertId: normalized2.insertId,
7962
- lastInsertRowid: normalized2.lastInsertRowid
7963
- });
7964
- allIds.push(...ids);
7965
- }
7966
- }
7967
- return {
7968
- rowCount: totalRowCount,
7969
- rows: allRows,
7970
- insertedIds: allIds,
7971
- executionTime: 0
7972
- };
7973
- }
7974
- const rawResult = await executor.execute(built.query);
7975
- const normalized = normalizeExecutionResult(rawResult);
7976
- const rowCount = normalized.rowCount > 0 ? normalized.rowCount : built.rows.length;
7977
- const rows = built.returningMode === "row" ? normalized.rows : [];
7978
- const insertedIds = built.returningMode === "id" ? deriveInsertedIds({
7979
- idColumn: built.idColumn,
7980
- inputRows: built.rows,
7981
- returnedRows: normalized.rows,
7982
- rowCount,
7983
- insertId: normalized.insertId,
7984
- lastInsertRowid: normalized.lastInsertRowid
7985
- }) : [];
7783
+ const session = context?.transaction ?? executor;
7784
+ const rawResult = await session.execute(built.query);
7785
+ const rowCount = normalizeAffectedRows(rawResult);
7986
7786
  return {
7987
7787
  rowCount,
7988
- rows,
7989
- insertedIds,
7990
- executionTime: 0
7788
+ executionTime: 0,
7789
+ softDeleted: built.usesSoftDelete
7991
7790
  };
7992
7791
  }
7993
7792
 
7994
- // src/data/relational/tools/relational-insert/executor-batch.ts
7995
- async function executeInsertInBatchMode(manager, input, context, options) {
7793
+ // src/data/relational/tools/relational-delete/executor-batch.ts
7794
+ async function executeDeleteInBatchMode(executor, input, context, options) {
7996
7795
  const batchResult = await executeBatchedTask(
7997
7796
  {
7998
- operation: "insert",
7999
- items: input.data,
8000
- executeBatch: async (rows2) => executeInsertOnce(manager, {
8001
- ...input,
8002
- data: rows2}),
8003
- getBatchSuccessCount: (result, rows2) => Math.min(result.rowCount, rows2.length)
7797
+ operation: "delete",
7798
+ items: input.operations,
7799
+ executeBatch: async (operations, batchIndex) => {
7800
+ let rowCount2 = 0;
7801
+ let successfulItems = 0;
7802
+ let failedItems = 0;
7803
+ let softDeletedCount = 0;
7804
+ const failures = [];
7805
+ for (const operation of operations) {
7806
+ try {
7807
+ const result = await executeSingleDelete(
7808
+ executor,
7809
+ input,
7810
+ {
7811
+ where: operation.where,
7812
+ allowFullTableDelete: operation.allowFullTableDelete,
7813
+ cascade: operation.cascade,
7814
+ softDelete: operation.softDelete
7815
+ },
7816
+ context
7817
+ );
7818
+ rowCount2 += result.rowCount;
7819
+ successfulItems += 1;
7820
+ if (result.softDeleted) {
7821
+ softDeletedCount += 1;
7822
+ }
7823
+ } catch (error) {
7824
+ if (!options.continueOnError) {
7825
+ throw error;
7826
+ }
7827
+ failedItems += 1;
7828
+ failures.push({
7829
+ operation: "delete",
7830
+ batchIndex,
7831
+ batchSize: operations.length,
7832
+ attempts: 1,
7833
+ error: toErrorMessage(error)
7834
+ });
7835
+ }
7836
+ }
7837
+ return {
7838
+ rowCount: rowCount2,
7839
+ successfulItems,
7840
+ failedItems,
7841
+ softDeletedCount,
7842
+ failures
7843
+ };
7844
+ },
7845
+ getBatchSuccessCount: (result) => result.successfulItems
8004
7846
  },
8005
7847
  {
8006
7848
  batchSize: options.batchSize,
@@ -8008,7 +7850,7 @@ async function executeInsertInBatchMode(manager, input, context, options) {
8008
7850
  maxRetries: options.maxRetries,
8009
7851
  retryDelayMs: options.retryDelayMs,
8010
7852
  onProgress: (progress) => {
8011
- insertExecutorLogger.debug("INSERT batch progress", {
7853
+ deleteExecutorLogger.debug("DELETE batch progress", {
8012
7854
  vendor: input.vendor,
8013
7855
  table: input.table,
8014
7856
  ...progress
@@ -8017,18 +7859,21 @@ async function executeInsertInBatchMode(manager, input, context, options) {
8017
7859
  }
8018
7860
  );
8019
7861
  const rowCount = batchResult.results.reduce((total, result) => total + result.rowCount, 0);
8020
- const rows = batchResult.results.flatMap((result) => result.rows);
8021
- const insertedIds = batchResult.results.flatMap((result) => result.insertedIds);
7862
+ const softDeleted = batchResult.results.some((result) => result.softDeletedCount > 0);
7863
+ const operationFailures = batchResult.results.flatMap((result) => result.failures);
8022
7864
  let benchmark;
8023
7865
  if (options.benchmark) {
8024
- insertExecutorLogger.warn("INSERT batch benchmark enabled. Synthetic benchmark callbacks are side-effect free and do not execute SQL.", {
8025
- vendor: input.vendor,
8026
- table: input.table,
8027
- totalRows: input.data.length,
8028
- batchSize: options.batchSize
8029
- });
7866
+ deleteExecutorLogger.warn(
7867
+ "DELETE batch benchmark enabled. Synthetic benchmark callbacks are side-effect free and do not execute SQL.",
7868
+ {
7869
+ vendor: input.vendor,
7870
+ table: input.table,
7871
+ operationCount: input.operations.length,
7872
+ batchSize: options.batchSize
7873
+ }
7874
+ );
8030
7875
  benchmark = await benchmarkBatchExecution({
8031
- items: input.data,
7876
+ items: input.operations,
8032
7877
  batchSize: options.batchSize,
8033
7878
  runIndividual: async () => {
8034
7879
  },
@@ -8038,9 +7883,8 @@ async function executeInsertInBatchMode(manager, input, context, options) {
8038
7883
  }
8039
7884
  return {
8040
7885
  rowCount,
8041
- rows,
8042
- insertedIds,
8043
7886
  executionTime: batchResult.executionTime,
7887
+ softDeleted,
8044
7888
  batch: {
8045
7889
  enabled: true,
8046
7890
  batchSize: options.batchSize,
@@ -8051,38 +7895,41 @@ async function executeInsertInBatchMode(manager, input, context, options) {
8051
7895
  totalBatches: batchResult.totalBatches,
8052
7896
  retries: batchResult.retries,
8053
7897
  partialSuccess: batchResult.partialSuccess,
8054
- failures: batchResult.failures,
7898
+ failures: [...batchResult.failures, ...operationFailures],
8055
7899
  benchmark
8056
7900
  }
8057
7901
  };
8058
7902
  }
8059
7903
 
8060
- // src/data/relational/tools/relational-insert/executor.ts
8061
- async function executeInsert(manager, input, context) {
7904
+ // src/data/relational/tools/relational-delete/executor.ts
7905
+ async function executeDelete(executor, input, context) {
8062
7906
  const startTime = Date.now();
8063
- insertExecutorLogger.debug("Building INSERT query", {
7907
+ deleteExecutorLogger.debug("Building DELETE query", {
8064
7908
  vendor: input.vendor,
8065
7909
  table: input.table,
8066
- isBatch: Array.isArray(input.data),
8067
- batchModeEnabled: !!input.batch?.enabled,
8068
- returningMode: input.returning?.mode ?? "none"
7910
+ hasWhere: !!input.where?.length,
7911
+ ...input.allowFullTableDelete !== void 0 ? { allowFullTableDelete: input.allowFullTableDelete } : {},
7912
+ ...input.cascade !== void 0 ? { cascade: input.cascade } : {},
7913
+ softDelete: !!input.softDelete,
7914
+ operationCount: input.operations?.length ?? 0,
7915
+ batchModeEnabled: !!input.batch?.enabled
8069
7916
  });
8070
7917
  try {
8071
7918
  const batchOptions = resolveBatchOptions(input.batch);
8072
- const result = Array.isArray(input.data) && batchOptions ? await executeInsertInBatchMode(
8073
- manager,
7919
+ const result = input.operations && batchOptions ? await executeDeleteInBatchMode(
7920
+ executor,
8074
7921
  input,
8075
7922
  context,
8076
7923
  batchOptions
8077
- ) : await executeInsertOnce(manager, input, context);
7924
+ ) : await executeSingleDelete(executor, input, toSingleDeleteOperation(input), context);
8078
7925
  const executionTime = Date.now() - startTime;
8079
- insertExecutorLogger.debug("INSERT query executed successfully", {
7926
+ deleteExecutorLogger.debug("DELETE query executed successfully", {
8080
7927
  vendor: input.vendor,
8081
7928
  table: input.table,
8082
7929
  rowCount: result.rowCount,
8083
- returnedRows: result.rows.length,
8084
- returnedIds: result.insertedIds.length,
8085
7930
  executionTime,
7931
+ softDelete: result.softDeleted,
7932
+ ...input.cascade !== void 0 ? { cascade: input.cascade } : {},
8086
7933
  batchMode: !!result.batch,
8087
7934
  partialSuccess: result.batch?.partialSuccess ?? false
8088
7935
  });
@@ -8092,314 +7939,332 @@ async function executeInsert(manager, input, context) {
8092
7939
  };
8093
7940
  } catch (error) {
8094
7941
  const executionTime = Date.now() - startTime;
8095
- insertExecutorLogger.error("INSERT query execution failed", {
7942
+ deleteExecutorLogger.error("DELETE query execution failed", {
8096
7943
  vendor: input.vendor,
8097
7944
  table: input.table,
8098
7945
  error: error instanceof Error ? error.message : String(error),
8099
- executionTime
7946
+ executionTime,
7947
+ ...input.cascade !== void 0 ? { cascade: input.cascade } : {},
7948
+ softDelete: !!input.softDelete
8100
7949
  });
8101
- const constraintMessage = getConstraintViolationMessage(error);
7950
+ const constraintMessage = getDeleteConstraintViolationMessage(error, input.cascade ?? false);
8102
7951
  if (constraintMessage) {
8103
7952
  throw new Error(constraintMessage, { cause: error });
8104
7953
  }
8105
- if (isSafeInsertValidationError(error)) {
7954
+ if (isSafeDeleteValidationError(error)) {
8106
7955
  throw error;
8107
7956
  }
8108
- throw new Error("INSERT query failed. See logs for details.", { cause: error });
7957
+ throw new Error("DELETE query failed. See logs for details.", { cause: error });
8109
7958
  }
8110
7959
  }
8111
7960
 
8112
- // src/data/relational/tools/relational-insert/index.ts
8113
- var relationalInsert = core.toolBuilder().name("relational-insert").displayName("Relational INSERT").description("Execute type-safe INSERT queries with single-row and batch insert support").category(core.ToolCategory.DATABASE).tags(["database", "sql", "insert", "postgresql", "mysql", "sqlite"]).schema(relationalInsertSchema).example({
8114
- description: "Insert a single row and return generated ID",
8115
- input: {
8116
- table: "users",
8117
- data: { name: "Alice", email: "alice@example.com" },
8118
- returning: { mode: "id", idColumn: "id" },
8119
- vendor: "postgresql",
8120
- connectionString: "postgresql://localhost/mydb"
8121
- }
8122
- }).example({
8123
- description: "Batch insert rows",
8124
- input: {
8125
- table: "users",
8126
- data: [
8127
- { name: "Bob", email: "bob@example.com" },
8128
- { name: "Carol", email: "carol@example.com" }
8129
- ],
8130
- batch: {
8131
- batchSize: 250,
8132
- continueOnError: true,
8133
- maxRetries: 1
8134
- },
8135
- vendor: "sqlite",
8136
- connectionString: "data.db"
7961
+ // src/data/relational/tools/legacy-mutation-adapter.ts
7962
+ function replaceMutationConnectionFailure(result, configuredMessage, legacyMessage) {
7963
+ if (!result.success && result.error === configuredMessage) {
7964
+ return { ...result, error: legacyMessage };
8137
7965
  }
8138
- }).implement(async (input) => {
8139
- const manager = new ConnectionManager({
8140
- vendor: input.vendor,
8141
- connection: input.connectionString
7966
+ return result;
7967
+ }
7968
+ async function withEphemeralRelationalMutationToolSet(database, invoke, toErrorResponse) {
7969
+ const toolSet = createLegacyRelationalMutationToolSet({
7970
+ vendor: database.vendor,
7971
+ connection: database.connectionString
8142
7972
  });
8143
7973
  try {
8144
- await manager.connect();
8145
- const result = await executeInsert(manager, input);
7974
+ try {
7975
+ return await invoke(toolSet);
7976
+ } catch (error) {
7977
+ if (error instanceof MissingPeerDependencyError) {
7978
+ throw error;
7979
+ }
7980
+ return toErrorResponse(error);
7981
+ }
7982
+ } finally {
7983
+ await toolSet.dispose();
7984
+ }
7985
+ }
7986
+
7987
+ // src/data/relational/tools/relational-delete/index.ts
7988
+ function toDeleteErrorResponse(error) {
7989
+ const errorMessage = isSafeDeleteError(error) ? error.message : "Failed to execute DELETE query. Please verify your input and database connection.";
7990
+ return {
7991
+ success: false,
7992
+ error: errorMessage,
7993
+ rowCount: 0,
7994
+ softDeleted: false
7995
+ };
7996
+ }
7997
+ async function invokeRelationalDelete(execution, input) {
7998
+ try {
7999
+ const result = await executeDelete(
8000
+ execution.executor,
8001
+ { ...input, vendor: execution.vendor },
8002
+ execution.transaction ? { transaction: execution.transaction } : void 0
8003
+ );
8146
8004
  return {
8147
8005
  success: true,
8148
8006
  rowCount: result.rowCount,
8149
- insertedIds: result.insertedIds,
8150
- rows: result.rows,
8151
8007
  executionTime: result.executionTime,
8008
+ softDeleted: result.softDeleted,
8152
8009
  batch: result.batch
8153
8010
  };
8154
8011
  } catch (error) {
8155
- let errorMessage = "Failed to execute INSERT query. Please verify your input and database connection.";
8156
- if (isSafeInsertError(error)) {
8157
- errorMessage = error.message;
8158
- }
8159
- return {
8160
- success: false,
8161
- error: errorMessage,
8162
- rowCount: 0,
8163
- insertedIds: [],
8164
- rows: []
8165
- };
8166
- } finally {
8167
- await manager.disconnect();
8012
+ return toDeleteErrorResponse(error);
8013
+ }
8014
+ }
8015
+ var relationalDelete = core.toolBuilder().name("relational-delete").displayName("Relational DELETE").description("Execute type-safe DELETE queries with single and batched operation support").category(core.ToolCategory.DATABASE).tags(["database", "sql", "delete", "postgresql", "mysql", "sqlite"]).schema(relationalDeleteSchema).example({
8016
+ description: "Delete one row by condition",
8017
+ input: {
8018
+ table: "users",
8019
+ where: [{ column: "id", operator: "eq", value: 123 }],
8020
+ vendor: "postgresql",
8021
+ connectionString: "postgresql://localhost/mydb"
8022
+ }
8023
+ }).example({
8024
+ description: "Soft delete rows",
8025
+ input: {
8026
+ table: "users",
8027
+ where: [{ column: "status", operator: "eq", value: "inactive" }],
8028
+ softDelete: { column: "deleted_at" },
8029
+ vendor: "sqlite",
8030
+ connectionString: "data.db"
8031
+ }
8032
+ }).example({
8033
+ description: "Batch delete operations",
8034
+ input: {
8035
+ table: "users",
8036
+ operations: [
8037
+ {
8038
+ where: [{ column: "status", operator: "eq", value: "inactive" }],
8039
+ softDelete: { column: "deleted_at" }
8040
+ },
8041
+ {
8042
+ where: [{ column: "status", operator: "eq", value: "pending_delete" }],
8043
+ softDelete: { column: "deleted_at" }
8044
+ }
8045
+ ],
8046
+ batch: {
8047
+ batchSize: 100,
8048
+ continueOnError: true
8049
+ },
8050
+ vendor: "postgresql",
8051
+ connectionString: "postgresql://localhost/mydb"
8168
8052
  }
8053
+ }).implement(async (input) => {
8054
+ const { connectionString, vendor, ...operation } = input;
8055
+ return withEphemeralRelationalMutationToolSet(
8056
+ { vendor, connectionString },
8057
+ async (toolSet) => replaceMutationConnectionFailure(
8058
+ await toolSet.delete.invoke(operation),
8059
+ DELETE_CONNECTION_FAILURE,
8060
+ "Failed to execute DELETE query. Please verify your input and database connection."
8061
+ ),
8062
+ toDeleteErrorResponse
8063
+ );
8169
8064
  }).build();
8170
- var updateValueSchema = zod.z.union([
8171
- zod.z.string().describe("String value"),
8172
- zod.z.number().describe("Numeric value"),
8173
- zod.z.boolean().describe("Boolean value"),
8174
- zod.z.null().describe("Null value")
8175
- ]).describe("Scalar value for an UPDATE column");
8176
- var updateDataSchema = zod.z.record(
8177
- zod.z.string().min(1, "Column name must not be empty").describe("Column name"),
8178
- updateValueSchema
8179
- ).superRefine((value, ctx) => {
8180
- if (Object.keys(value).length === 0) {
8181
- ctx.addIssue({
8182
- code: zod.z.ZodIssueCode.custom,
8183
- message: "Update data must not be empty"
8184
- });
8065
+
8066
+ // src/data/relational/tools/relational-get-schema-error-utils.ts
8067
+ var SAFE_VALIDATION_ERROR_PATTERNS = [
8068
+ "must not be empty",
8069
+ "contains invalid characters",
8070
+ "Invalid table filter"
8071
+ ];
8072
+ function isSafeGetSchemaValidationError(error) {
8073
+ if (!(error instanceof Error)) {
8074
+ return false;
8185
8075
  }
8186
- }).describe("Object containing column-value pairs to update");
8187
- var updateWhereOperatorSchema = zod.z.enum([
8188
- "eq",
8189
- "ne",
8190
- "gt",
8191
- "lt",
8192
- "gte",
8193
- "lte",
8194
- "like",
8195
- "in",
8196
- "notIn",
8197
- "isNull",
8198
- "isNotNull"
8199
- ]);
8200
- var updateWhereConditionSchema = zod.z.object({
8201
- column: zod.z.string().min(1, "Column name must not be empty").describe("Column name to filter on"),
8202
- operator: updateWhereOperatorSchema.describe("Comparison operator"),
8203
- value: zod.z.union([
8204
- zod.z.string(),
8205
- zod.z.number(),
8206
- zod.z.boolean(),
8207
- zod.z.array(zod.z.union([zod.z.string(), zod.z.number()])),
8208
- zod.z.null()
8209
- ]).optional().describe("Value to compare against (not required for isNull/isNotNull)")
8210
- }).superRefine((value, ctx) => {
8211
- const op = value.operator;
8212
- if (op === "isNull" || op === "isNotNull") {
8213
- if (value.value !== void 0) {
8214
- ctx.addIssue({
8215
- code: zod.z.ZodIssueCode.custom,
8216
- path: ["value"],
8217
- message: "value must not be provided when using isNull or isNotNull operator"
8218
- });
8219
- }
8220
- return;
8076
+ return SAFE_VALIDATION_ERROR_PATTERNS.some(
8077
+ (pattern) => error.message.includes(pattern)
8078
+ );
8079
+ }
8080
+
8081
+ // src/data/relational/tools/legacy-read-adapter.ts
8082
+ function replaceConnectionFailureMessage(result, configuredMessage, legacyMessage) {
8083
+ if (!result.success && result.error === configuredMessage) {
8084
+ return { ...result, error: legacyMessage };
8221
8085
  }
8222
- if (value.value === void 0) {
8223
- ctx.addIssue({
8224
- code: zod.z.ZodIssueCode.custom,
8225
- path: ["value"],
8226
- message: "value is required for this operator"
8227
- });
8228
- return;
8086
+ return result;
8087
+ }
8088
+ async function withEphemeralRelationalToolSet(database, invoke, options = {}, sharedSchemaCacheKey) {
8089
+ const config = { vendor: database.vendor, connection: database.connectionString };
8090
+ const toolSet = createLegacyRelationalReadToolSet(config, options, sharedSchemaCacheKey);
8091
+ try {
8092
+ return await invoke(toolSet);
8093
+ } finally {
8094
+ await toolSet.dispose();
8229
8095
  }
8230
- if (op === "in" || op === "notIn") {
8231
- if (!Array.isArray(value.value)) {
8232
- ctx.addIssue({
8233
- code: zod.z.ZodIssueCode.custom,
8234
- path: ["value"],
8235
- message: `${op === "in" ? "IN" : "NOT IN"} operator requires an array value`
8236
- });
8237
- } else if (value.value.length === 0) {
8238
- ctx.addIssue({
8239
- code: zod.z.ZodIssueCode.custom,
8240
- path: ["value"],
8241
- message: `${op === "in" ? "IN" : "NOT IN"} operator requires a non-empty array value`
8242
- });
8096
+ }
8097
+
8098
+ // src/data/relational/tools/relational-get-schema.ts
8099
+ var logger16 = core.createLogger("agentforge:tools:data:relational:get-schema");
8100
+ function buildSchemaCacheKey(vendor, connectionString, database) {
8101
+ const databaseScope = database ?? "default";
8102
+ const connectionHash = crypto.createHash("sha256").update(connectionString).digest("hex");
8103
+ return `${vendor}:${databaseScope}:${connectionHash}`;
8104
+ }
8105
+ var relationalGetSchemaInputSchema = zod.z.object({
8106
+ vendor: zod.z.enum(["postgresql", "mysql", "sqlite"]).describe("Database vendor"),
8107
+ connectionString: zod.z.string().min(1, "Database connection string is required").describe("Database connection string used to create a new connection"),
8108
+ database: zod.z.string().min(1).optional().describe("Optional logical database name used for cache scoping"),
8109
+ tables: zod.z.array(
8110
+ zod.z.string().regex(
8111
+ VALID_TABLE_FILTER_PATTERN,
8112
+ "Table filter contains invalid characters. Use alphanumeric, underscore, and optional schema qualification."
8113
+ )
8114
+ ).optional().describe("Optional table filter list. When omitted, all tables are returned."),
8115
+ cacheTtlMs: zod.z.number().int().min(0).optional().describe("Schema cache TTL in milliseconds (0 disables caching)"),
8116
+ refreshCache: zod.z.boolean().optional().describe("Force cache invalidation before introspecting schema")
8117
+ });
8118
+ function toGetSchemaErrorResponse(execution, input, error) {
8119
+ logger16.error("Schema introspection failed", {
8120
+ vendor: execution.vendor,
8121
+ hasTablesFilter: Array.isArray(input.tables),
8122
+ tablesFilterCount: input.tables?.length ?? 0,
8123
+ refreshCache: input.refreshCache ?? false,
8124
+ error: error instanceof Error ? error.message : String(error)
8125
+ });
8126
+ const errorMessage = isSafeGetSchemaValidationError(error) ? error.message : "Failed to inspect schema. See logs for details.";
8127
+ return {
8128
+ success: false,
8129
+ error: errorMessage,
8130
+ schema: null
8131
+ };
8132
+ }
8133
+ async function invokeRelationalGetSchema(execution, input) {
8134
+ const cacheKey = execution.schemaCacheKey ?? (execution.schemaCache ? input.database ?? "default" : void 0);
8135
+ const inspector = new SchemaInspector(
8136
+ execution.transaction ?? execution.executor,
8137
+ execution.vendor,
8138
+ {
8139
+ cacheTtlMs: input.cacheTtlMs,
8140
+ cacheKey,
8141
+ cache: execution.schemaCache
8243
8142
  }
8244
- return;
8143
+ );
8144
+ try {
8145
+ if (input.refreshCache && cacheKey) {
8146
+ inspector.invalidateCache();
8147
+ }
8148
+ const schema = await inspector.inspect(
8149
+ execution.transaction ? { tables: input.tables, bypassCache: true } : { tables: input.tables }
8150
+ );
8151
+ const summary = schema.tables.reduce(
8152
+ (accumulator, table) => {
8153
+ accumulator.tableCount += 1;
8154
+ accumulator.columnCount += table.columns.length;
8155
+ accumulator.foreignKeyCount += table.foreignKeys.length;
8156
+ accumulator.indexCount += table.indexes.length;
8157
+ return accumulator;
8158
+ },
8159
+ { tableCount: 0, columnCount: 0, foreignKeyCount: 0, indexCount: 0 }
8160
+ );
8161
+ return { success: true, schema, summary };
8162
+ } catch (error) {
8163
+ return toGetSchemaErrorResponse(execution, input, error);
8245
8164
  }
8246
- if (value.value === null) {
8247
- ctx.addIssue({
8248
- code: zod.z.ZodIssueCode.custom,
8249
- path: ["value"],
8250
- message: "null is only allowed with isNull/isNotNull operators"
8251
- });
8252
- return;
8165
+ }
8166
+ var relationalGetSchema = core.toolBuilder().name("relational-get-schema").displayName("Relational Get Schema").description("Inspect relational database schema (tables, columns, primary keys, foreign keys, indexes) across PostgreSQL, MySQL, and SQLite").category(core.ToolCategory.DATABASE).tags(["database", "schema", "introspection", "postgresql", "mysql", "sqlite"]).schema(relationalGetSchemaInputSchema).example({
8167
+ description: "Inspect all SQLite tables",
8168
+ input: {
8169
+ vendor: "sqlite",
8170
+ connectionString: ":memory:"
8253
8171
  }
8254
- if (op === "like" && typeof value.value !== "string") {
8255
- ctx.addIssue({
8256
- code: zod.z.ZodIssueCode.custom,
8257
- path: ["value"],
8258
- message: "LIKE operator requires a string value"
8259
- });
8260
- return;
8172
+ }).example({
8173
+ description: "Inspect selected PostgreSQL tables with cache control",
8174
+ input: {
8175
+ vendor: "postgresql",
8176
+ connectionString: "postgresql://localhost/app",
8177
+ tables: ["public.users", "public.orders"],
8178
+ cacheTtlMs: 3e4,
8179
+ refreshCache: true
8261
8180
  }
8262
- if ((op === "gt" || op === "lt" || op === "gte" || op === "lte") && typeof value.value !== "string" && typeof value.value !== "number") {
8181
+ }).implement(async (input) => {
8182
+ const { connectionString, vendor, database, cacheTtlMs, tables, refreshCache } = input;
8183
+ return withEphemeralRelationalToolSet(
8184
+ { vendor, connectionString },
8185
+ (toolSet) => toolSet.getSchema.invoke({ tables, refreshCache }),
8186
+ { schemaCacheTtlMs: cacheTtlMs },
8187
+ buildSchemaCacheKey(vendor, connectionString, database)
8188
+ );
8189
+ }).build();
8190
+ var insertValueSchema = zod.z.union([
8191
+ zod.z.string().describe("String value"),
8192
+ zod.z.number().describe("Numeric value"),
8193
+ zod.z.boolean().describe("Boolean value"),
8194
+ zod.z.null().describe("Null value")
8195
+ ]).describe("Scalar value for an INSERT column");
8196
+ var insertRowSchema = zod.z.record(
8197
+ zod.z.string().min(1, "Column name must not be empty").describe("Column name"),
8198
+ insertValueSchema
8199
+ ).describe("One row object where keys are column names and values are scalar values");
8200
+ var insertReturningModeSchema = zod.z.enum(["none", "id", "row"]);
8201
+ var insertReturningSchema = zod.z.object({
8202
+ mode: insertReturningModeSchema.default("none").describe("Returning mode: none, id, or full row"),
8203
+ idColumn: zod.z.string().min(1, "idColumn must not be empty").optional().describe('Primary key column to return when mode is "id"')
8204
+ }).superRefine((value, ctx) => {
8205
+ if (value.idColumn && value.mode !== "id") {
8263
8206
  ctx.addIssue({
8264
8207
  code: zod.z.ZodIssueCode.custom,
8265
- path: ["value"],
8266
- message: `${op.toUpperCase()} operator requires a string or number value`
8208
+ path: ["idColumn"],
8209
+ message: 'idColumn can only be provided when returning.mode is "id"'
8267
8210
  });
8268
8211
  }
8269
8212
  });
8270
- var updateOptimisticLockSchema = zod.z.object({
8271
- column: zod.z.string().min(1, "Optimistic lock column must not be empty").describe("Version or lock column name"),
8272
- expectedValue: zod.z.union([
8273
- zod.z.string().describe("Expected string version value"),
8274
- zod.z.number().describe("Expected numeric version value")
8275
- ]).describe("Expected current value for the lock column")
8276
- }).describe("Optional optimistic lock condition");
8277
- var updateBatchOperationSchema = zod.z.object({
8278
- data: updateDataSchema.describe("Columns and values to update"),
8279
- where: zod.z.array(updateWhereConditionSchema).optional().describe("WHERE conditions (combined with AND)"),
8280
- allowFullTableUpdate: zod.z.boolean().default(false).describe("Explicitly allow UPDATE without WHERE conditions for this operation"),
8281
- optimisticLock: updateOptimisticLockSchema.optional().describe("Optional optimistic locking condition for this operation")
8282
- }).superRefine((value, ctx) => {
8283
- const hasWhere = (value.where?.length ?? 0) > 0;
8284
- const hasOptimisticLock = !!value.optimisticLock;
8285
- if (!value.allowFullTableUpdate && !hasWhere && !hasOptimisticLock) {
8286
- ctx.addIssue({
8287
- code: zod.z.ZodIssueCode.custom,
8288
- path: ["where"],
8289
- message: "WHERE conditions are required unless allowFullTableUpdate is true"
8290
- });
8291
- }
8292
- });
8293
- var updateBatchOptionsSchema = zod.z.object({
8294
- enabled: zod.z.boolean().optional().default(true).describe("Enable batched processing for operations[] payloads"),
8295
- batchSize: zod.z.number().int().positive().max(5e3).optional().default(100).describe("Operations per batch chunk"),
8213
+ var insertBatchOptionsSchema = zod.z.object({
8214
+ enabled: zod.z.boolean().optional().default(true).describe("Enable chunked batch execution for array insert payloads"),
8215
+ batchSize: zod.z.number().int().positive().max(5e3).optional().default(100).describe("Rows per batch chunk"),
8296
8216
  continueOnError: zod.z.boolean().optional().default(true).describe("Continue processing remaining chunks when one chunk fails"),
8297
8217
  maxRetries: zod.z.number().int().min(0).max(5).optional().default(0).describe("Retry attempts per failed chunk"),
8298
8218
  retryDelayMs: zod.z.number().int().min(0).max(6e4).optional().default(0).describe("Delay between retry attempts in milliseconds"),
8299
8219
  benchmark: zod.z.boolean().optional().default(false).describe("Run synthetic benchmark metadata comparing individual vs batched execution callbacks")
8300
8220
  });
8301
- var relationalUpdateSchema = zod.z.object({
8221
+ var relationalInsertSchema = zod.z.object({
8302
8222
  table: zod.z.string().min(1, "Table name is required").regex(
8303
8223
  VALID_QUALIFIED_IDENTIFIER_PATTERN,
8304
8224
  "Table name contains invalid characters. Only alphanumeric characters, underscores, and dots (for schema qualification) are allowed."
8305
- ).describe("Table name to update (schema-qualified names supported, e.g. public.users)"),
8306
- data: updateDataSchema.optional().describe("Columns and values to update"),
8307
- where: zod.z.array(updateWhereConditionSchema).optional().describe("WHERE conditions (combined with AND)"),
8308
- allowFullTableUpdate: zod.z.boolean().default(false).describe("Explicitly allow UPDATE without WHERE conditions"),
8309
- optimisticLock: updateOptimisticLockSchema.optional().describe("Optional optimistic locking condition"),
8310
- operations: zod.z.array(updateBatchOperationSchema).min(1, "operations must contain at least one batch item").optional().describe("Batch update operation list"),
8311
- batch: updateBatchOptionsSchema.optional().describe("Optional batch execution controls for operations payloads"),
8225
+ ).describe("Table name to insert into (schema-qualified names supported, e.g. public.users)"),
8226
+ data: zod.z.union([
8227
+ insertRowSchema,
8228
+ zod.z.array(insertRowSchema).min(1, "Insert data array must not be empty")
8229
+ ]).describe("Single row object or array of row objects to insert"),
8230
+ batch: insertBatchOptionsSchema.optional().describe("Optional batch execution controls for array insert payloads"),
8231
+ returning: insertReturningSchema.optional().describe("Optional RETURNING behavior"),
8312
8232
  vendor: zod.z.enum(["postgresql", "mysql", "sqlite"]).describe("Database vendor"),
8313
8233
  connectionString: zod.z.string().min(1, "Database connection string is required").describe("Database connection string")
8314
- }).superRefine((value, ctx) => {
8315
- const hasOperations = (value.operations?.length ?? 0) > 0;
8316
- if (hasOperations && value.data) {
8317
- ctx.addIssue({
8318
- code: zod.z.ZodIssueCode.custom,
8319
- path: ["data"],
8320
- message: "data cannot be provided when operations[] is used"
8321
- });
8322
- }
8323
- if (!hasOperations && !value.data) {
8324
- ctx.addIssue({
8325
- code: zod.z.ZodIssueCode.custom,
8326
- path: ["data"],
8327
- message: "data is required when operations[] is not provided"
8328
- });
8329
- }
8330
- if (hasOperations && (value.where?.length ?? 0) > 0) {
8331
- ctx.addIssue({
8332
- code: zod.z.ZodIssueCode.custom,
8333
- path: ["where"],
8334
- message: "where cannot be provided when operations[] is used"
8335
- });
8336
- }
8337
- if (hasOperations && value.optimisticLock) {
8338
- ctx.addIssue({
8339
- code: zod.z.ZodIssueCode.custom,
8340
- path: ["optimisticLock"],
8341
- message: "optimisticLock cannot be provided when operations[] is used"
8342
- });
8343
- }
8344
- if (hasOperations && value.allowFullTableUpdate) {
8345
- ctx.addIssue({
8346
- code: zod.z.ZodIssueCode.custom,
8347
- path: ["allowFullTableUpdate"],
8348
- message: "allowFullTableUpdate cannot be provided when operations[] is used"
8349
- });
8350
- }
8351
- if (hasOperations) {
8352
- return;
8353
- }
8354
- const hasWhere = (value.where?.length ?? 0) > 0;
8355
- const hasOptimisticLock = !!value.optimisticLock;
8356
- if (!value.allowFullTableUpdate && !hasWhere && !hasOptimisticLock) {
8357
- ctx.addIssue({
8358
- code: zod.z.ZodIssueCode.custom,
8359
- path: ["where"],
8360
- message: "WHERE conditions are required unless allowFullTableUpdate is true"
8361
- });
8362
- }
8363
8234
  });
8364
8235
 
8365
- // src/data/relational/tools/relational-update/error-utils.ts
8366
- var SAFE_UPDATE_VALIDATION_PATTERNS = [
8236
+ // src/data/relational/tools/relational-insert/error-utils.ts
8237
+ var SAFE_INSERT_VALIDATION_PATTERNS = [
8367
8238
  "must not be empty",
8368
8239
  "contains invalid characters",
8369
- "Update data must be an object",
8370
- "Update data must not be empty",
8240
+ "must be an object",
8241
+ "must not be an empty array",
8371
8242
  "must not be undefined",
8372
- "WHERE conditions are required for UPDATE queries",
8373
- "null is only allowed with isNull/isNotNull operators",
8374
- "operator requires a string or number value",
8375
- "operator requires a non-empty array value",
8376
- "operator requires a string value",
8377
- "must not include value",
8378
- "Optimistic lock expectedValue must not be empty",
8379
- "optimistic lock check failed",
8380
- "WHERE conditions are required unless allowFullTableUpdate is true"
8243
+ "idColumn can only be provided",
8244
+ "Returning full rows is not supported for mysql",
8245
+ "Batch INSERT with only DEFAULT VALUES is not supported"
8381
8246
  ];
8382
8247
  var CONSTRAINT_VIOLATION_PATTERNS2 = [
8383
8248
  {
8384
8249
  pattern: /(unique constraint|duplicate key|duplicate entry)/i,
8385
- message: "Update failed: unique constraint violation."
8250
+ message: "Insert failed: unique constraint violation."
8386
8251
  },
8387
8252
  {
8388
8253
  pattern: /(foreign key constraint|violates foreign key constraint)/i,
8389
- message: "Update failed: foreign key constraint violation."
8254
+ message: "Insert failed: foreign key constraint violation."
8390
8255
  },
8391
8256
  {
8392
8257
  pattern: /(not null constraint|cannot be null)/i,
8393
- message: "Update failed: NOT NULL constraint violation."
8258
+ message: "Insert failed: NOT NULL constraint violation."
8394
8259
  }
8395
8260
  ];
8396
- function isSafeUpdateValidationError(error) {
8261
+ function isSafeInsertValidationError(error) {
8397
8262
  if (!(error instanceof Error)) {
8398
8263
  return false;
8399
8264
  }
8400
- return SAFE_UPDATE_VALIDATION_PATTERNS.some((pattern) => error.message.includes(pattern));
8265
+ return SAFE_INSERT_VALIDATION_PATTERNS.some((pattern) => error.message.includes(pattern));
8401
8266
  }
8402
- function getUpdateConstraintViolationMessage(error) {
8267
+ function getConstraintViolationMessage(error) {
8403
8268
  if (!(error instanceof Error)) {
8404
8269
  return null;
8405
8270
  }
@@ -8414,35 +8279,74 @@ function getUpdateConstraintViolationMessage(error) {
8414
8279
  }
8415
8280
  return null;
8416
8281
  }
8417
- function isSafeUpdateError(error) {
8282
+ function isSafeInsertError(error) {
8418
8283
  if (!(error instanceof Error)) {
8419
8284
  return false;
8420
8285
  }
8421
- return isSafeUpdateValidationError(error) || getUpdateConstraintViolationMessage(error) !== null;
8286
+ return isSafeInsertValidationError(error) || getConstraintViolationMessage(error) !== null;
8422
8287
  }
8423
- var updateExecutorLogger = core.createLogger("agentforge:tools:data:relational:update");
8288
+ var insertExecutorLogger = core.createLogger("agentforge:tools:data:relational:insert");
8424
8289
  function toNumber2(value) {
8425
8290
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
8426
8291
  }
8427
- function normalizeAffectedRows(result) {
8292
+ function isPlainObject(value) {
8293
+ return !!value && typeof value === "object" && !Array.isArray(value);
8294
+ }
8295
+ function normalizeExecutionResult(result) {
8428
8296
  if (Array.isArray(result)) {
8429
- if (result.length > 0 && result[0] && typeof result[0] === "object") {
8297
+ if (result.length > 0 && isPlainObject(result[0])) {
8430
8298
  const first = result[0];
8431
8299
  const affectedRows = toNumber2(first.affectedRows) ?? toNumber2(first.rowCount) ?? toNumber2(first.changes);
8432
- if (affectedRows !== void 0) {
8433
- return affectedRows;
8300
+ const insertId = toNumber2(first.insertId);
8301
+ const lastInsertRowid = toNumber2(first.lastInsertRowid);
8302
+ if (affectedRows !== void 0 || insertId !== void 0 || lastInsertRowid !== void 0) {
8303
+ return {
8304
+ rows: [],
8305
+ rowCount: affectedRows ?? 0,
8306
+ insertId,
8307
+ lastInsertRowid
8308
+ };
8434
8309
  }
8435
8310
  }
8436
- return result.length;
8311
+ return {
8312
+ rows: result,
8313
+ rowCount: result.length
8314
+ };
8437
8315
  }
8438
- if (result && typeof result === "object") {
8439
- const resultRecord = result;
8440
- return toNumber2(resultRecord.rowCount) ?? toNumber2(resultRecord.affectedRows) ?? toNumber2(resultRecord.changes) ?? (Array.isArray(resultRecord.rows) ? resultRecord.rows.length : 0);
8316
+ if (isPlainObject(result)) {
8317
+ const rows = Array.isArray(result.rows) ? result.rows : [];
8318
+ const rowCount = toNumber2(result.rowCount) ?? toNumber2(result.affectedRows) ?? toNumber2(result.changes) ?? rows.length;
8319
+ return {
8320
+ rows,
8321
+ rowCount,
8322
+ insertId: toNumber2(result.insertId),
8323
+ lastInsertRowid: toNumber2(result.lastInsertRowid)
8324
+ };
8441
8325
  }
8442
- return 0;
8326
+ return {
8327
+ rows: [],
8328
+ rowCount: 0
8329
+ };
8443
8330
  }
8444
- function toErrorMessage(error) {
8445
- return error instanceof Error ? error.message : String(error);
8331
+ function deriveInsertedIds(options) {
8332
+ const { idColumn, inputRows, returnedRows, rowCount, insertId, lastInsertRowid } = options;
8333
+ if (returnedRows.length > 0) {
8334
+ const idsFromReturn = returnedRows.map((row) => isPlainObject(row) ? row[idColumn] : void 0).filter((id) => typeof id === "number" || typeof id === "string");
8335
+ if (idsFromReturn.length > 0) {
8336
+ return idsFromReturn;
8337
+ }
8338
+ }
8339
+ if (inputRows.every((row) => typeof row[idColumn] === "number" || typeof row[idColumn] === "string")) {
8340
+ return inputRows.map((row) => row[idColumn]);
8341
+ }
8342
+ if (insertId !== void 0 && rowCount > 0) {
8343
+ return Array.from({ length: rowCount }, (_, index) => insertId + index);
8344
+ }
8345
+ if (lastInsertRowid !== void 0 && rowCount > 0) {
8346
+ const startId = lastInsertRowid - rowCount + 1;
8347
+ return Array.from({ length: rowCount }, (_, index) => startId + index);
8348
+ }
8349
+ return [];
8446
8350
  }
8447
8351
  function resolveBatchOptions2(batch) {
8448
8352
  if (!batch || batch.enabled === false) {
@@ -8457,88 +8361,79 @@ function resolveBatchOptions2(batch) {
8457
8361
  benchmark: batch.benchmark ?? false
8458
8362
  };
8459
8363
  }
8460
- function toSingleUpdateOperation(input) {
8461
- if (!input.data) {
8462
- throw new Error("UPDATE data is required when operations[] is not provided.");
8463
- }
8464
- return {
8465
- data: input.data,
8466
- where: input.where,
8467
- allowFullTableUpdate: input.allowFullTableUpdate,
8468
- optimisticLock: input.optimisticLock
8469
- };
8364
+ function toSingleInsertRows(row) {
8365
+ return [row];
8470
8366
  }
8471
8367
 
8472
- // src/data/relational/tools/relational-update/executor-single.ts
8473
- async function executeSingleUpdate(manager, input, operation, context) {
8474
- const built = buildUpdateQuery({
8368
+ // src/data/relational/tools/relational-insert/executor-single.ts
8369
+ async function executeInsertOnce(executor, input, context) {
8370
+ const built = buildInsertQuery({
8475
8371
  table: input.table,
8476
- data: operation.data,
8477
- where: operation.where,
8478
- allowFullTableUpdate: operation.allowFullTableUpdate,
8479
- optimisticLock: operation.optimisticLock,
8372
+ data: input.data,
8373
+ returning: input.returning,
8480
8374
  vendor: input.vendor
8481
8375
  });
8482
- const executor = manager;
8483
- const rawResult = await executor.execute(built.query);
8484
- const rowCount = normalizeAffectedRows(rawResult);
8485
- if (built.usesOptimisticLock && rowCount === 0) {
8486
- throw new Error("Update failed: optimistic lock check failed.");
8376
+ const session = context?.transaction ?? executor;
8377
+ if (Array.isArray(built.query)) {
8378
+ let totalRowCount = 0;
8379
+ const allRows = [];
8380
+ const allIds = [];
8381
+ for (const singleQuery of built.query) {
8382
+ const rawResult2 = await session.execute(singleQuery);
8383
+ const normalized2 = normalizeExecutionResult(rawResult2);
8384
+ totalRowCount += normalized2.rowCount > 0 ? normalized2.rowCount : 1;
8385
+ if (built.returningMode === "row") {
8386
+ allRows.push(...normalized2.rows);
8387
+ }
8388
+ if (built.returningMode === "id") {
8389
+ const ids = deriveInsertedIds({
8390
+ idColumn: built.idColumn,
8391
+ inputRows: toSingleInsertRows(built.rows[allIds.length]),
8392
+ returnedRows: normalized2.rows,
8393
+ rowCount: 1,
8394
+ insertId: normalized2.insertId,
8395
+ lastInsertRowid: normalized2.lastInsertRowid
8396
+ });
8397
+ allIds.push(...ids);
8398
+ }
8399
+ }
8400
+ return {
8401
+ rowCount: totalRowCount,
8402
+ rows: allRows,
8403
+ insertedIds: allIds,
8404
+ executionTime: 0
8405
+ };
8487
8406
  }
8407
+ const rawResult = await session.execute(built.query);
8408
+ const normalized = normalizeExecutionResult(rawResult);
8409
+ const rowCount = normalized.rowCount > 0 ? normalized.rowCount : built.rows.length;
8410
+ const rows = built.returningMode === "row" ? normalized.rows : [];
8411
+ const insertedIds = built.returningMode === "id" ? deriveInsertedIds({
8412
+ idColumn: built.idColumn,
8413
+ inputRows: built.rows,
8414
+ returnedRows: normalized.rows,
8415
+ rowCount,
8416
+ insertId: normalized.insertId,
8417
+ lastInsertRowid: normalized.lastInsertRowid
8418
+ }) : [];
8488
8419
  return {
8489
8420
  rowCount,
8421
+ rows,
8422
+ insertedIds,
8490
8423
  executionTime: 0
8491
8424
  };
8492
8425
  }
8493
8426
 
8494
- // src/data/relational/tools/relational-update/executor-batch.ts
8495
- async function executeUpdateInBatchMode(manager, input, context, options) {
8427
+ // src/data/relational/tools/relational-insert/executor-batch.ts
8428
+ async function executeInsertInBatchMode(executor, input, context, options) {
8496
8429
  const batchResult = await executeBatchedTask(
8497
8430
  {
8498
- operation: "update",
8499
- items: input.operations,
8500
- executeBatch: async (operations, batchIndex) => {
8501
- let rowCount2 = 0;
8502
- let successfulItems = 0;
8503
- let failedItems = 0;
8504
- const failures = [];
8505
- for (const operation of operations) {
8506
- try {
8507
- const result = await executeSingleUpdate(
8508
- manager,
8509
- input,
8510
- {
8511
- data: operation.data,
8512
- where: operation.where,
8513
- allowFullTableUpdate: operation.allowFullTableUpdate,
8514
- optimisticLock: operation.optimisticLock
8515
- },
8516
- context
8517
- );
8518
- rowCount2 += result.rowCount;
8519
- successfulItems += 1;
8520
- } catch (error) {
8521
- if (!options.continueOnError) {
8522
- throw error;
8523
- }
8524
- failedItems += 1;
8525
- failures.push({
8526
- operation: "update",
8527
- batchIndex,
8528
- batchSize: operations.length,
8529
- attempts: 1,
8530
- error: toErrorMessage(error)
8531
- });
8532
- }
8533
- }
8534
- return {
8535
- rowCount: rowCount2,
8536
- successfulItems,
8537
- failedItems,
8538
- failures
8539
- };
8540
- },
8541
- getBatchSuccessCount: (result) => result.successfulItems
8431
+ operation: "insert",
8432
+ items: input.data,
8433
+ executeBatch: async (rows2) => executeInsertOnce(executor, {
8434
+ ...input,
8435
+ data: rows2}, context),
8436
+ getBatchSuccessCount: (result, rows2) => Math.min(result.rowCount, rows2.length)
8542
8437
  },
8543
8438
  {
8544
8439
  batchSize: options.batchSize,
@@ -8546,7 +8441,7 @@ async function executeUpdateInBatchMode(manager, input, context, options) {
8546
8441
  maxRetries: options.maxRetries,
8547
8442
  retryDelayMs: options.retryDelayMs,
8548
8443
  onProgress: (progress) => {
8549
- updateExecutorLogger.debug("UPDATE batch progress", {
8444
+ insertExecutorLogger.debug("INSERT batch progress", {
8550
8445
  vendor: input.vendor,
8551
8446
  table: input.table,
8552
8447
  ...progress
@@ -8555,165 +8450,601 @@ async function executeUpdateInBatchMode(manager, input, context, options) {
8555
8450
  }
8556
8451
  );
8557
8452
  const rowCount = batchResult.results.reduce((total, result) => total + result.rowCount, 0);
8558
- const operationFailures = batchResult.results.flatMap((result) => result.failures);
8453
+ const rows = batchResult.results.flatMap((result) => result.rows);
8454
+ const insertedIds = batchResult.results.flatMap((result) => result.insertedIds);
8559
8455
  let benchmark;
8560
8456
  if (options.benchmark) {
8561
- updateExecutorLogger.warn("UPDATE batch benchmark enabled. Synthetic benchmark callbacks are side-effect free and do not execute SQL.", {
8457
+ insertExecutorLogger.warn("INSERT batch benchmark enabled. Synthetic benchmark callbacks are side-effect free and do not execute SQL.", {
8562
8458
  vendor: input.vendor,
8563
8459
  table: input.table,
8564
- operationCount: input.operations.length,
8460
+ totalRows: input.data.length,
8565
8461
  batchSize: options.batchSize
8566
8462
  });
8567
- benchmark = await benchmarkBatchExecution({
8568
- items: input.operations,
8569
- batchSize: options.batchSize,
8570
- runIndividual: async () => {
8571
- },
8572
- runBatch: async () => {
8573
- }
8463
+ benchmark = await benchmarkBatchExecution({
8464
+ items: input.data,
8465
+ batchSize: options.batchSize,
8466
+ runIndividual: async () => {
8467
+ },
8468
+ runBatch: async () => {
8469
+ }
8470
+ });
8471
+ }
8472
+ return {
8473
+ rowCount,
8474
+ rows,
8475
+ insertedIds,
8476
+ executionTime: batchResult.executionTime,
8477
+ batch: {
8478
+ enabled: true,
8479
+ batchSize: options.batchSize,
8480
+ totalItems: batchResult.totalItems,
8481
+ processedItems: batchResult.processedItems,
8482
+ successfulItems: batchResult.successfulItems,
8483
+ failedItems: batchResult.failedItems,
8484
+ totalBatches: batchResult.totalBatches,
8485
+ retries: batchResult.retries,
8486
+ partialSuccess: batchResult.partialSuccess,
8487
+ failures: batchResult.failures,
8488
+ benchmark
8489
+ }
8490
+ };
8491
+ }
8492
+
8493
+ // src/data/relational/tools/relational-insert/executor.ts
8494
+ function getCauseMetadata(error) {
8495
+ if (!(error instanceof Error) || !error.cause || typeof error.cause !== "object") {
8496
+ return {};
8497
+ }
8498
+ const cause = error.cause;
8499
+ return {
8500
+ causeType: error.cause instanceof Error ? error.cause.name : "object",
8501
+ ...typeof cause.code === "string" ? { causeCode: cause.code } : {},
8502
+ ...typeof cause.constraint === "string" ? { causeConstraint: cause.constraint } : {}
8503
+ };
8504
+ }
8505
+ async function executeInsert(executor, input, context) {
8506
+ const startTime = Date.now();
8507
+ insertExecutorLogger.debug("Building INSERT query", {
8508
+ vendor: input.vendor,
8509
+ table: input.table,
8510
+ isBatch: Array.isArray(input.data),
8511
+ batchModeEnabled: !!input.batch?.enabled,
8512
+ returningMode: input.returning?.mode ?? "none"
8513
+ });
8514
+ try {
8515
+ const batchOptions = resolveBatchOptions2(input.batch);
8516
+ const result = Array.isArray(input.data) && batchOptions ? await executeInsertInBatchMode(
8517
+ executor,
8518
+ input,
8519
+ context,
8520
+ batchOptions
8521
+ ) : await executeInsertOnce(executor, input, context);
8522
+ const executionTime = Date.now() - startTime;
8523
+ insertExecutorLogger.debug("INSERT query executed successfully", {
8524
+ vendor: input.vendor,
8525
+ table: input.table,
8526
+ rowCount: result.rowCount,
8527
+ returnedRows: result.rows.length,
8528
+ returnedIds: result.insertedIds.length,
8529
+ executionTime,
8530
+ batchMode: !!result.batch,
8531
+ partialSuccess: result.batch?.partialSuccess ?? false
8532
+ });
8533
+ return {
8534
+ ...result,
8535
+ executionTime
8536
+ };
8537
+ } catch (error) {
8538
+ const executionTime = Date.now() - startTime;
8539
+ insertExecutorLogger.error("INSERT query execution failed", {
8540
+ vendor: input.vendor,
8541
+ table: input.table,
8542
+ error: error instanceof Error ? error.message : String(error),
8543
+ ...getCauseMetadata(error),
8544
+ executionTime
8545
+ });
8546
+ const constraintMessage = getConstraintViolationMessage(error);
8547
+ if (constraintMessage) {
8548
+ throw new Error(constraintMessage, { cause: error });
8549
+ }
8550
+ if (isSafeInsertValidationError(error)) {
8551
+ throw error;
8552
+ }
8553
+ throw new Error("INSERT query failed. See logs for details.", { cause: error });
8554
+ }
8555
+ }
8556
+
8557
+ // src/data/relational/tools/relational-insert/index.ts
8558
+ function toInsertErrorResponse(error) {
8559
+ const errorMessage = isSafeInsertError(error) ? error.message : "Failed to execute INSERT query. Please verify your input and database connection.";
8560
+ return {
8561
+ success: false,
8562
+ error: errorMessage,
8563
+ rowCount: 0,
8564
+ insertedIds: [],
8565
+ rows: []
8566
+ };
8567
+ }
8568
+ async function invokeRelationalInsert(execution, input) {
8569
+ try {
8570
+ const result = await executeInsert(
8571
+ execution.executor,
8572
+ { ...input, vendor: execution.vendor },
8573
+ execution.transaction ? { transaction: execution.transaction } : void 0
8574
+ );
8575
+ return {
8576
+ success: true,
8577
+ rowCount: result.rowCount,
8578
+ insertedIds: result.insertedIds,
8579
+ rows: result.rows,
8580
+ executionTime: result.executionTime,
8581
+ batch: result.batch
8582
+ };
8583
+ } catch (error) {
8584
+ return toInsertErrorResponse(error);
8585
+ }
8586
+ }
8587
+ var relationalInsert = core.toolBuilder().name("relational-insert").displayName("Relational INSERT").description("Execute type-safe INSERT queries with single-row and batch insert support").category(core.ToolCategory.DATABASE).tags(["database", "sql", "insert", "postgresql", "mysql", "sqlite"]).schema(relationalInsertSchema).example({
8588
+ description: "Insert a single row and return generated ID",
8589
+ input: {
8590
+ table: "users",
8591
+ data: { name: "Alice", email: "alice@example.com" },
8592
+ returning: { mode: "id", idColumn: "id" },
8593
+ vendor: "postgresql",
8594
+ connectionString: "postgresql://localhost/mydb"
8595
+ }
8596
+ }).example({
8597
+ description: "Batch insert rows",
8598
+ input: {
8599
+ table: "users",
8600
+ data: [
8601
+ { name: "Bob", email: "bob@example.com" },
8602
+ { name: "Carol", email: "carol@example.com" }
8603
+ ],
8604
+ batch: {
8605
+ batchSize: 250,
8606
+ continueOnError: true,
8607
+ maxRetries: 1
8608
+ },
8609
+ vendor: "sqlite",
8610
+ connectionString: "data.db"
8611
+ }
8612
+ }).implement(async (input) => {
8613
+ const { connectionString, vendor, ...operation } = input;
8614
+ return withEphemeralRelationalMutationToolSet(
8615
+ { vendor, connectionString },
8616
+ async (toolSet) => replaceMutationConnectionFailure(
8617
+ await toolSet.insert.invoke(operation),
8618
+ INSERT_CONNECTION_FAILURE,
8619
+ "Failed to execute INSERT query. Please verify your input and database connection."
8620
+ ),
8621
+ toInsertErrorResponse
8622
+ );
8623
+ }).build();
8624
+ var relationalQuerySchema = zod.z.object({
8625
+ sql: zod.z.string().describe("SQL query string to execute"),
8626
+ params: zod.z.union([
8627
+ zod.z.array(zod.z.unknown()).describe("Positional parameters (e.g., [value1, value2])"),
8628
+ zod.z.record(zod.z.string(), zod.z.unknown()).describe('Named parameters (e.g., { name: "John", age: 30 })')
8629
+ ]).optional().describe("Query parameters for parameter binding (prevents SQL injection)"),
8630
+ vendor: zod.z.enum(["postgresql", "mysql", "sqlite"]).describe("Database vendor"),
8631
+ connectionString: zod.z.string().min(1, "Database connection string is required").describe("Database connection string used to create a new connection")
8632
+ });
8633
+ function toQueryErrorResponse(error) {
8634
+ return {
8635
+ success: false,
8636
+ error: error instanceof Error ? error.message : "Unknown error occurred",
8637
+ rows: [],
8638
+ rowCount: 0
8639
+ };
8640
+ }
8641
+ async function invokeRelationalQuery(execution, input) {
8642
+ try {
8643
+ const queryInput = {
8644
+ sql: input.sql,
8645
+ params: input.params,
8646
+ vendor: execution.vendor
8647
+ };
8648
+ const result = execution.transaction ? await executeQuery2(execution.executor, queryInput, { transaction: execution.transaction }) : await executeQuery2(execution.executor, queryInput);
8649
+ return {
8650
+ success: true,
8651
+ rows: result.rows,
8652
+ rowCount: result.rowCount,
8653
+ executionTime: result.executionTime
8654
+ };
8655
+ } catch (error) {
8656
+ return toQueryErrorResponse(error);
8657
+ }
8658
+ }
8659
+ var relationalQuery = core.toolBuilder().name("relational-query").displayName("Relational Query").description("Execute raw SQL queries against relational databases (PostgreSQL, MySQL, SQLite) with parameter binding to prevent SQL injection").category(core.ToolCategory.DATABASE).tags(["database", "sql", "query", "postgresql", "mysql", "sqlite"]).schema(relationalQuerySchema).example({
8660
+ description: "SELECT query with positional parameters",
8661
+ input: {
8662
+ sql: "SELECT * FROM users WHERE id = $1",
8663
+ params: [42],
8664
+ vendor: "postgresql",
8665
+ connectionString: "postgresql://user:pass@localhost:5432/mydb"
8666
+ }
8667
+ }).example({
8668
+ description: "INSERT query with named parameters",
8669
+ input: {
8670
+ sql: "INSERT INTO users (name, email) VALUES (:name, :email)",
8671
+ params: { name: "John Doe", email: "john@example.com" },
8672
+ vendor: "postgresql",
8673
+ connectionString: "postgresql://user:pass@localhost:5432/mydb"
8674
+ }
8675
+ }).example({
8676
+ description: "UPDATE query with positional parameters",
8677
+ input: {
8678
+ sql: "UPDATE users SET status = ? WHERE id = ?",
8679
+ params: ["active", 42],
8680
+ vendor: "mysql",
8681
+ connectionString: "mysql://user:pass@localhost:3306/mydb"
8682
+ }
8683
+ }).usageNotes("Always use parameter binding (params) instead of string concatenation to prevent SQL injection. Supports positional ($1, ?) and named (:name) parameters.").limitation("Requires database-specific driver as peer dependency (pg, mysql2, or better-sqlite3)").limitation("Connection string must be valid for the specified vendor").limitation("Large result sets may impact performance").limitation("Placeholder characters ($n, ?, :name) inside SQL string literals are not supported. Use parameter binding for all dynamic values instead of embedding them in string literals").implement(async (input) => {
8684
+ const { connectionString, vendor, ...operation } = input;
8685
+ return withEphemeralRelationalToolSet(
8686
+ { vendor, connectionString },
8687
+ async (toolSet) => replaceConnectionFailureMessage(
8688
+ await toolSet.query.invoke(operation),
8689
+ QUERY_CONNECTION_FAILURE,
8690
+ `Failed to initialize ${vendor} connection`
8691
+ )
8692
+ );
8693
+ }).build();
8694
+ var whereOperatorSchema = zod.z.enum([
8695
+ "eq",
8696
+ // equals
8697
+ "ne",
8698
+ // not equals
8699
+ "gt",
8700
+ // greater than
8701
+ "lt",
8702
+ // less than
8703
+ "gte",
8704
+ // greater than or equal
8705
+ "lte",
8706
+ // less than or equal
8707
+ "like",
8708
+ // LIKE pattern matching
8709
+ "in",
8710
+ // IN array
8711
+ "notIn",
8712
+ // NOT IN array
8713
+ "isNull",
8714
+ // IS NULL
8715
+ "isNotNull"
8716
+ // IS NOT NULL
8717
+ ]);
8718
+ var whereConditionSchema = zod.z.object({
8719
+ column: zod.z.string().min(1, "Column name must not be empty").describe("Column name to filter on"),
8720
+ operator: whereOperatorSchema.describe("Comparison operator"),
8721
+ value: zod.z.union([
8722
+ zod.z.string(),
8723
+ zod.z.number(),
8724
+ zod.z.boolean(),
8725
+ zod.z.array(zod.z.union([zod.z.string(), zod.z.number()])),
8726
+ zod.z.null()
8727
+ ]).optional().describe("Value to compare against (not required for isNull/isNotNull)")
8728
+ }).superRefine((val, ctx) => {
8729
+ const op = val.operator;
8730
+ if (op === "isNull" || op === "isNotNull") {
8731
+ if (val.value !== void 0) {
8732
+ ctx.addIssue({
8733
+ code: zod.z.ZodIssueCode.custom,
8734
+ path: ["value"],
8735
+ message: "value must not be provided when using isNull or isNotNull operator"
8736
+ });
8737
+ }
8738
+ return;
8739
+ }
8740
+ if (val.value === void 0) {
8741
+ ctx.addIssue({
8742
+ code: zod.z.ZodIssueCode.custom,
8743
+ path: ["value"],
8744
+ message: "value is required for this operator"
8745
+ });
8746
+ return;
8747
+ }
8748
+ if (op === "in" || op === "notIn") {
8749
+ if (!Array.isArray(val.value)) {
8750
+ ctx.addIssue({
8751
+ code: zod.z.ZodIssueCode.custom,
8752
+ path: ["value"],
8753
+ message: `${op === "in" ? "IN" : "NOT IN"} operator requires an array value`
8754
+ });
8755
+ } else if (val.value.length === 0) {
8756
+ ctx.addIssue({
8757
+ code: zod.z.ZodIssueCode.custom,
8758
+ path: ["value"],
8759
+ message: `${op === "in" ? "IN" : "NOT IN"} operator requires a non-empty array value`
8760
+ });
8761
+ }
8762
+ return;
8763
+ }
8764
+ if (val.value === null) {
8765
+ ctx.addIssue({
8766
+ code: zod.z.ZodIssueCode.custom,
8767
+ path: ["value"],
8768
+ message: "null is only allowed with isNull/isNotNull operators"
8769
+ });
8770
+ return;
8771
+ }
8772
+ if (op === "like" && typeof val.value !== "string") {
8773
+ ctx.addIssue({
8774
+ code: zod.z.ZodIssueCode.custom,
8775
+ path: ["value"],
8776
+ message: "LIKE operator requires a string value"
8777
+ });
8778
+ return;
8779
+ }
8780
+ if ((op === "gt" || op === "lt" || op === "gte" || op === "lte") && typeof val.value !== "string" && typeof val.value !== "number") {
8781
+ ctx.addIssue({
8782
+ code: zod.z.ZodIssueCode.custom,
8783
+ path: ["value"],
8784
+ message: `${op.toUpperCase()} operator requires a string or number value`
8574
8785
  });
8786
+ return;
8575
8787
  }
8576
- return {
8577
- rowCount,
8578
- executionTime: batchResult.executionTime,
8579
- batch: {
8580
- enabled: true,
8581
- batchSize: options.batchSize,
8582
- totalItems: batchResult.totalItems,
8583
- processedItems: batchResult.processedItems,
8584
- successfulItems: batchResult.successfulItems,
8585
- failedItems: batchResult.failedItems,
8586
- totalBatches: batchResult.totalBatches,
8587
- retries: batchResult.retries,
8588
- partialSuccess: batchResult.partialSuccess,
8589
- failures: [...batchResult.failures, ...operationFailures],
8590
- benchmark
8591
- }
8788
+ if ((op === "eq" || op === "ne") && Array.isArray(val.value)) {
8789
+ ctx.addIssue({
8790
+ code: zod.z.ZodIssueCode.custom,
8791
+ path: ["value"],
8792
+ message: `${op.toUpperCase()} operator requires a scalar value`
8793
+ });
8794
+ }
8795
+ });
8796
+ var orderDirectionSchema = zod.z.enum(["asc", "desc"]);
8797
+ var orderBySchema = zod.z.object({
8798
+ column: zod.z.string().min(1, "Column name must not be empty").describe("Column name to order by"),
8799
+ direction: orderDirectionSchema.describe("Sort direction (ascending or descending)")
8800
+ });
8801
+ var streamingOptionsSchema = zod.z.object({
8802
+ enabled: zod.z.boolean().default(true).describe("Enable chunked streaming mode for large result sets"),
8803
+ chunkSize: zod.z.number().int().min(1).max(5e3).optional().describe("Rows fetched per chunk (default: 100)"),
8804
+ maxRows: zod.z.number().int().positive().optional().describe("Optional cap on streamed rows"),
8805
+ sampleSize: zod.z.number().int().min(0).max(5e3).optional().describe("Number of rows to include in the response payload"),
8806
+ benchmark: zod.z.boolean().optional().default(false).describe("Run memory benchmark comparing regular vs streaming execution (adds two extra query executions; use side-effect-free SELECT statements)")
8807
+ });
8808
+ var relationalSelectSchema = zod.z.object({
8809
+ table: zod.z.string().min(1, "Table name is required").regex(
8810
+ VALID_QUALIFIED_IDENTIFIER_PATTERN,
8811
+ "Table name contains invalid characters. Only alphanumeric characters, underscores, and dots (for schema qualification) are allowed."
8812
+ ).describe("Table name to select from (schema-qualified names supported, e.g. public.users)"),
8813
+ columns: zod.z.array(zod.z.string().min(1, "Column name must not be empty")).min(1, "Columns array must not be empty").optional().describe("Columns to select (omit for SELECT *)"),
8814
+ where: zod.z.array(whereConditionSchema).optional().describe("WHERE conditions (combined with AND)"),
8815
+ orderBy: zod.z.array(orderBySchema).optional().describe("ORDER BY clauses"),
8816
+ limit: zod.z.number().int().positive().optional().describe("Maximum number of rows to return"),
8817
+ offset: zod.z.number().int().nonnegative().optional().describe("Number of rows to skip"),
8818
+ streaming: streamingOptionsSchema.optional().describe("Optional streaming mode configuration"),
8819
+ vendor: zod.z.enum(["postgresql", "mysql", "sqlite"]).describe("Database vendor"),
8820
+ connectionString: zod.z.string().min(1, "Database connection string is required").describe("Database connection string")
8821
+ });
8822
+
8823
+ // src/data/relational/tools/relational-select/query-builder.ts
8824
+ function buildSelectQuery2(input) {
8825
+ const queryInput = {
8826
+ table: input.table,
8827
+ columns: input.columns,
8828
+ where: input.where,
8829
+ orderBy: input.orderBy,
8830
+ limit: input.limit,
8831
+ offset: input.offset,
8832
+ vendor: input.vendor
8592
8833
  };
8834
+ return buildSelectQuery(queryInput);
8593
8835
  }
8594
8836
 
8595
- // src/data/relational/tools/relational-update/executor.ts
8596
- async function executeUpdate(manager, input, context) {
8837
+ // src/data/relational/tools/relational-select/error-utils.ts
8838
+ var SAFE_VALIDATION_ERROR_PATTERNS2 = [
8839
+ "must not be empty",
8840
+ "contains invalid characters",
8841
+ "requires an array value",
8842
+ "requires a non-empty array value",
8843
+ "must be an integer between",
8844
+ "must be a positive integer",
8845
+ "null is only allowed with isNull/isNotNull operators",
8846
+ "LIKE operator requires a string value",
8847
+ "operator requires a string or number value",
8848
+ "operator requires a scalar value",
8849
+ "Stream cancelled by caller"
8850
+ ];
8851
+ function isSafeValidationError(error) {
8852
+ if (!(error instanceof Error)) {
8853
+ return false;
8854
+ }
8855
+ return SAFE_VALIDATION_ERROR_PATTERNS2.some(
8856
+ (pattern) => error.message.includes(pattern)
8857
+ );
8858
+ }
8859
+
8860
+ // src/data/relational/tools/relational-select/executor.ts
8861
+ var logger17 = core.createLogger("agentforge:tools:data:relational:select");
8862
+ function toSelectQueryInput(input) {
8863
+ return {
8864
+ table: input.table,
8865
+ columns: input.columns,
8866
+ where: input.where,
8867
+ orderBy: input.orderBy,
8868
+ limit: input.limit,
8869
+ offset: input.offset,
8870
+ vendor: input.vendor
8871
+ };
8872
+ }
8873
+ async function executeSelect(executor, input, context) {
8597
8874
  const startTime = Date.now();
8598
- updateExecutorLogger.debug("Building UPDATE query", {
8875
+ logger17.debug("Building SELECT query", {
8599
8876
  vendor: input.vendor,
8600
8877
  table: input.table,
8601
- hasWhere: !!input.where?.length,
8602
- ...input.allowFullTableUpdate !== void 0 ? { allowFullTableUpdate: input.allowFullTableUpdate } : {},
8603
- hasOptimisticLock: !!input.optimisticLock,
8604
- operationCount: input.operations?.length ?? 0,
8605
- batchModeEnabled: !!input.batch?.enabled
8878
+ hasWhere: !!input.where,
8879
+ hasOrderBy: !!input.orderBy,
8880
+ hasLimit: !!input.limit,
8881
+ streamingEnabled: !!input.streaming?.enabled
8606
8882
  });
8607
8883
  try {
8608
- const batchOptions = resolveBatchOptions2(input.batch);
8609
- const result = input.operations && batchOptions ? await executeUpdateInBatchMode(
8610
- manager,
8611
- input,
8612
- context,
8613
- batchOptions
8614
- ) : await executeSingleUpdate(manager, input, toSingleUpdateOperation(input), context);
8884
+ const session = context?.transaction ?? executor;
8885
+ if (input.streaming?.enabled) {
8886
+ const streamOptions = {
8887
+ chunkSize: input.streaming.chunkSize,
8888
+ maxRows: input.streaming.maxRows,
8889
+ sampleSize: input.streaming.sampleSize
8890
+ };
8891
+ const streamInput = toSelectQueryInput(input);
8892
+ if (input.streaming.benchmark) {
8893
+ logger17.warn("Streaming benchmark enabled; SELECT will execute up to three times (result + benchmark regular + benchmark streaming).", {
8894
+ vendor: input.vendor,
8895
+ table: input.table,
8896
+ chunkSize: streamOptions.chunkSize ?? DEFAULT_CHUNK_SIZE,
8897
+ ...streamOptions.maxRows !== void 0 ? { maxRows: streamOptions.maxRows } : {},
8898
+ ...streamOptions.sampleSize !== void 0 ? { sampleSize: streamOptions.sampleSize } : {}
8899
+ });
8900
+ }
8901
+ const streamResult = await executeStreamingSelect(session, streamInput, streamOptions);
8902
+ const benchmark = input.streaming.benchmark ? await benchmarkStreamingSelectMemory(session, streamInput, streamOptions) : void 0;
8903
+ const executionTime2 = Date.now() - startTime;
8904
+ logger17.debug("Streaming SELECT query executed successfully", {
8905
+ vendor: input.vendor,
8906
+ table: input.table,
8907
+ rowCount: streamResult.rowCount,
8908
+ chunkCount: streamResult.chunkCount,
8909
+ executionTime: executionTime2,
8910
+ cancelled: streamResult.cancelled
8911
+ });
8912
+ return {
8913
+ rows: streamResult.rows,
8914
+ rowCount: streamResult.rowCount,
8915
+ executionTime: executionTime2,
8916
+ streaming: {
8917
+ enabled: true,
8918
+ chunkSize: input.streaming.chunkSize ?? DEFAULT_CHUNK_SIZE,
8919
+ chunkCount: streamResult.chunkCount,
8920
+ sampledRowCount: streamResult.rows.length,
8921
+ streamedRowCount: streamResult.rowCount,
8922
+ cancelled: streamResult.cancelled,
8923
+ memoryUsage: streamResult.memoryUsage,
8924
+ benchmark
8925
+ }
8926
+ };
8927
+ }
8928
+ const query = buildSelectQuery2(input);
8929
+ const result = await session.execute(query);
8615
8930
  const executionTime = Date.now() - startTime;
8616
- updateExecutorLogger.debug("UPDATE query executed successfully", {
8931
+ const rows = Array.isArray(result) ? result : result.rows || [];
8932
+ const rowCount = rows.length;
8933
+ logger17.debug("SELECT query executed successfully", {
8617
8934
  vendor: input.vendor,
8618
8935
  table: input.table,
8619
- rowCount: result.rowCount,
8620
- executionTime,
8621
- batchMode: !!result.batch,
8622
- partialSuccess: result.batch?.partialSuccess ?? false
8936
+ rowCount,
8937
+ executionTime
8623
8938
  });
8624
8939
  return {
8625
- ...result,
8940
+ rows,
8941
+ rowCount,
8626
8942
  executionTime
8627
8943
  };
8628
8944
  } catch (error) {
8629
8945
  const executionTime = Date.now() - startTime;
8630
- updateExecutorLogger.error("UPDATE query execution failed", {
8946
+ logger17.error("SELECT query execution failed", {
8631
8947
  vendor: input.vendor,
8632
8948
  table: input.table,
8633
8949
  error: error instanceof Error ? error.message : String(error),
8634
8950
  executionTime
8635
8951
  });
8636
- const constraintMessage = getUpdateConstraintViolationMessage(error);
8637
- if (constraintMessage) {
8638
- throw new Error(constraintMessage, { cause: error });
8639
- }
8640
- if (isSafeUpdateValidationError(error)) {
8952
+ if (isSafeValidationError(error)) {
8641
8953
  throw error;
8642
8954
  }
8643
- throw new Error("UPDATE query failed. See logs for details.", { cause: error });
8955
+ throw new Error("SELECT query failed. See logs for details.", { cause: error });
8644
8956
  }
8645
8957
  }
8646
8958
 
8647
- // src/data/relational/tools/relational-update/index.ts
8648
- var relationalUpdate = core.toolBuilder().name("relational-update").displayName("Relational UPDATE").description("Execute type-safe UPDATE queries with single and batched operation support").category(core.ToolCategory.DATABASE).tags(["database", "sql", "update", "postgresql", "mysql", "sqlite"]).schema(relationalUpdateSchema).example({
8649
- description: "Update one row by condition",
8959
+ // src/data/relational/tools/relational-select/index.ts
8960
+ function toSelectErrorResponse(error) {
8961
+ const errorMessage = isSafeValidationError(error) ? error.message : "Failed to execute SELECT query. Please verify your input and database connection.";
8962
+ return {
8963
+ success: false,
8964
+ error: errorMessage,
8965
+ rows: [],
8966
+ rowCount: 0
8967
+ };
8968
+ }
8969
+ async function invokeRelationalSelect(execution, input) {
8970
+ try {
8971
+ const result = await executeSelect(
8972
+ execution.executor,
8973
+ { ...input, vendor: execution.vendor },
8974
+ execution.transaction ? { transaction: execution.transaction } : void 0
8975
+ );
8976
+ return {
8977
+ success: true,
8978
+ rows: result.rows,
8979
+ rowCount: result.rowCount,
8980
+ executionTime: result.executionTime,
8981
+ streaming: result.streaming
8982
+ };
8983
+ } catch (error) {
8984
+ return toSelectErrorResponse(error);
8985
+ }
8986
+ }
8987
+ var relationalSelect = core.toolBuilder().name("relational-select").displayName("Relational SELECT").description("Execute type-safe SELECT queries with WHERE conditions, ORDER BY, LIMIT, and OFFSET using Drizzle ORM query builder").category(core.ToolCategory.DATABASE).tags(["database", "sql", "select", "query", "postgresql", "mysql", "sqlite"]).schema(relationalSelectSchema).example({
8988
+ description: "SELECT with WHERE condition",
8650
8989
  input: {
8651
8990
  table: "users",
8652
- data: { status: "inactive" },
8653
- where: [{ column: "id", operator: "eq", value: 123 }],
8991
+ columns: ["id", "name", "email"],
8992
+ where: [{ column: "status", operator: "eq", value: "active" }],
8654
8993
  vendor: "postgresql",
8655
8994
  connectionString: "postgresql://localhost/mydb"
8656
8995
  }
8657
8996
  }).example({
8658
- description: "Optimistic lock update",
8997
+ description: "SELECT with pagination",
8659
8998
  input: {
8660
- table: "users",
8661
- data: { status: "active" },
8662
- where: [{ column: "id", operator: "eq", value: 123 }],
8663
- optimisticLock: { column: "version", expectedValue: 5 },
8664
- vendor: "sqlite",
8665
- connectionString: "data.db"
8999
+ table: "products",
9000
+ orderBy: [{ column: "created_at", direction: "desc" }],
9001
+ limit: 20,
9002
+ offset: 0,
9003
+ vendor: "mysql",
9004
+ connectionString: "mysql://localhost/shop"
8666
9005
  }
8667
9006
  }).example({
8668
- description: "Batch update operations",
9007
+ description: "SELECT with streaming mode",
8669
9008
  input: {
8670
- table: "users",
8671
- operations: [
8672
- {
8673
- data: { status: "inactive" },
8674
- where: [{ column: "id", operator: "eq", value: 1 }]
8675
- },
8676
- {
8677
- data: { status: "inactive" },
8678
- where: [{ column: "id", operator: "eq", value: 2 }]
8679
- }
8680
- ],
8681
- batch: {
8682
- batchSize: 100,
8683
- continueOnError: true
9009
+ table: "events",
9010
+ orderBy: [{ column: "id", direction: "asc" }],
9011
+ streaming: {
9012
+ enabled: true,
9013
+ chunkSize: 250,
9014
+ sampleSize: 25
8684
9015
  },
8685
9016
  vendor: "postgresql",
8686
- connectionString: "postgresql://localhost/mydb"
9017
+ connectionString: "postgresql://localhost/analytics"
8687
9018
  }
8688
9019
  }).implement(async (input) => {
8689
- const manager = new ConnectionManager({
8690
- vendor: input.vendor,
8691
- connection: input.connectionString
8692
- });
8693
- try {
8694
- await manager.connect();
8695
- const result = await executeUpdate(manager, input);
8696
- return {
8697
- success: true,
8698
- rowCount: result.rowCount,
8699
- executionTime: result.executionTime,
8700
- batch: result.batch
8701
- };
8702
- } catch (error) {
8703
- let errorMessage = "Failed to execute UPDATE query. Please verify your input and database connection.";
8704
- if (isSafeUpdateError(error)) {
8705
- errorMessage = error.message;
8706
- }
8707
- return {
8708
- success: false,
8709
- error: errorMessage,
8710
- rowCount: 0
8711
- };
8712
- } finally {
8713
- await manager.disconnect();
8714
- }
9020
+ const { connectionString, vendor, ...operation } = input;
9021
+ return withEphemeralRelationalToolSet(
9022
+ { vendor, connectionString },
9023
+ async (toolSet) => replaceConnectionFailureMessage(
9024
+ await toolSet.select.invoke(operation),
9025
+ SELECT_CONNECTION_FAILURE,
9026
+ "Failed to execute SELECT query. Please verify your input and database connection."
9027
+ )
9028
+ );
8715
9029
  }).build();
8716
- var deleteWhereOperatorSchema = zod.z.enum([
9030
+ var updateValueSchema = zod.z.union([
9031
+ zod.z.string().describe("String value"),
9032
+ zod.z.number().describe("Numeric value"),
9033
+ zod.z.boolean().describe("Boolean value"),
9034
+ zod.z.null().describe("Null value")
9035
+ ]).describe("Scalar value for an UPDATE column");
9036
+ var updateDataSchema = zod.z.record(
9037
+ zod.z.string().min(1, "Column name must not be empty").describe("Column name"),
9038
+ updateValueSchema
9039
+ ).superRefine((value, ctx) => {
9040
+ if (Object.keys(value).length === 0) {
9041
+ ctx.addIssue({
9042
+ code: zod.z.ZodIssueCode.custom,
9043
+ message: "Update data must not be empty"
9044
+ });
9045
+ }
9046
+ }).describe("Object containing column-value pairs to update");
9047
+ var updateWhereOperatorSchema = zod.z.enum([
8717
9048
  "eq",
8718
9049
  "ne",
8719
9050
  "gt",
@@ -8726,9 +9057,9 @@ var deleteWhereOperatorSchema = zod.z.enum([
8726
9057
  "isNull",
8727
9058
  "isNotNull"
8728
9059
  ]);
8729
- var deleteWhereConditionSchema = zod.z.object({
9060
+ var updateWhereConditionSchema = zod.z.object({
8730
9061
  column: zod.z.string().min(1, "Column name must not be empty").describe("Column name to filter on"),
8731
- operator: deleteWhereOperatorSchema.describe("Comparison operator"),
9062
+ operator: updateWhereOperatorSchema.describe("Comparison operator"),
8732
9063
  value: zod.z.union([
8733
9064
  zod.z.string(),
8734
9065
  zod.z.number(),
@@ -8796,26 +9127,30 @@ var deleteWhereConditionSchema = zod.z.object({
8796
9127
  });
8797
9128
  }
8798
9129
  });
8799
- var deleteSoftDeleteSchema = zod.z.object({
8800
- column: zod.z.string().min(1, "Soft delete column must not be empty").default("deleted_at").describe("Column to write deletion timestamp/value into"),
8801
- value: zod.z.union([zod.z.string(), zod.z.number()]).optional().describe("Optional explicit value for the soft-delete column (defaults to ISO timestamp)")
8802
- }).describe("Enable soft delete by updating a column instead of physically deleting rows");
8803
- var deleteBatchOperationSchema = zod.z.object({
8804
- where: zod.z.array(deleteWhereConditionSchema).optional().describe("WHERE conditions (combined with AND)"),
8805
- allowFullTableDelete: zod.z.boolean().default(false).describe("Explicitly allow DELETE without WHERE conditions for this operation"),
8806
- cascade: zod.z.boolean().default(false).describe("Enable cascade-aware error messaging for this operation"),
8807
- softDelete: deleteSoftDeleteSchema.optional().describe("Use soft-delete semantics instead of hard DELETE for this operation")
9130
+ var updateOptimisticLockSchema = zod.z.object({
9131
+ column: zod.z.string().min(1, "Optimistic lock column must not be empty").describe("Version or lock column name"),
9132
+ expectedValue: zod.z.union([
9133
+ zod.z.string().describe("Expected string version value"),
9134
+ zod.z.number().describe("Expected numeric version value")
9135
+ ]).describe("Expected current value for the lock column")
9136
+ }).describe("Optional optimistic lock condition");
9137
+ var updateBatchOperationSchema = zod.z.object({
9138
+ data: updateDataSchema.describe("Columns and values to update"),
9139
+ where: zod.z.array(updateWhereConditionSchema).optional().describe("WHERE conditions (combined with AND)"),
9140
+ allowFullTableUpdate: zod.z.boolean().default(false).describe("Explicitly allow UPDATE without WHERE conditions for this operation"),
9141
+ optimisticLock: updateOptimisticLockSchema.optional().describe("Optional optimistic locking condition for this operation")
8808
9142
  }).superRefine((value, ctx) => {
8809
9143
  const hasWhere = (value.where?.length ?? 0) > 0;
8810
- if (!value.allowFullTableDelete && !hasWhere) {
9144
+ const hasOptimisticLock = !!value.optimisticLock;
9145
+ if (!value.allowFullTableUpdate && !hasWhere && !hasOptimisticLock) {
8811
9146
  ctx.addIssue({
8812
9147
  code: zod.z.ZodIssueCode.custom,
8813
9148
  path: ["where"],
8814
- message: "WHERE conditions are required unless allowFullTableDelete is true"
9149
+ message: "WHERE conditions are required unless allowFullTableUpdate is true"
8815
9150
  });
8816
9151
  }
8817
9152
  });
8818
- var deleteBatchOptionsSchema = zod.z.object({
9153
+ var updateBatchOptionsSchema = zod.z.object({
8819
9154
  enabled: zod.z.boolean().optional().default(true).describe("Enable batched processing for operations[] payloads"),
8820
9155
  batchSize: zod.z.number().int().positive().max(5e3).optional().default(100).describe("Operations per batch chunk"),
8821
9156
  continueOnError: zod.z.boolean().optional().default(true).describe("Continue processing remaining chunks when one chunk fails"),
@@ -8823,89 +9158,108 @@ var deleteBatchOptionsSchema = zod.z.object({
8823
9158
  retryDelayMs: zod.z.number().int().min(0).max(6e4).optional().default(0).describe("Delay between retry attempts in milliseconds"),
8824
9159
  benchmark: zod.z.boolean().optional().default(false).describe("Run synthetic benchmark metadata comparing individual vs batched execution callbacks")
8825
9160
  });
8826
- var relationalDeleteSchema = zod.z.object({
9161
+ var relationalUpdateSchema = zod.z.object({
8827
9162
  table: zod.z.string().min(1, "Table name is required").regex(
8828
9163
  VALID_QUALIFIED_IDENTIFIER_PATTERN,
8829
9164
  "Table name contains invalid characters. Only alphanumeric characters, underscores, and dots (for schema qualification) are allowed."
8830
- ).describe("Table name to delete from (schema-qualified names supported, e.g. public.users)"),
8831
- where: zod.z.array(deleteWhereConditionSchema).optional().describe("WHERE conditions (combined with AND)"),
8832
- allowFullTableDelete: zod.z.boolean().default(false).describe("Explicitly allow DELETE without WHERE conditions"),
8833
- cascade: zod.z.boolean().default(false).describe("Enable cascade-aware error messaging for foreign key constraints"),
8834
- softDelete: deleteSoftDeleteSchema.optional().describe("Use soft-delete semantics instead of hard DELETE"),
8835
- operations: zod.z.array(deleteBatchOperationSchema).min(1, "operations must contain at least one batch item").optional().describe("Batch delete operation list"),
8836
- batch: deleteBatchOptionsSchema.optional().describe("Optional batch execution controls for operations payloads"),
9165
+ ).describe("Table name to update (schema-qualified names supported, e.g. public.users)"),
9166
+ data: updateDataSchema.optional().describe("Columns and values to update"),
9167
+ where: zod.z.array(updateWhereConditionSchema).optional().describe("WHERE conditions (combined with AND)"),
9168
+ allowFullTableUpdate: zod.z.boolean().default(false).describe("Explicitly allow UPDATE without WHERE conditions"),
9169
+ optimisticLock: updateOptimisticLockSchema.optional().describe("Optional optimistic locking condition"),
9170
+ operations: zod.z.array(updateBatchOperationSchema).min(1, "operations must contain at least one batch item").optional().describe("Batch update operation list"),
9171
+ batch: updateBatchOptionsSchema.optional().describe("Optional batch execution controls for operations payloads"),
8837
9172
  vendor: zod.z.enum(["postgresql", "mysql", "sqlite"]).describe("Database vendor"),
8838
9173
  connectionString: zod.z.string().min(1, "Database connection string is required").describe("Database connection string")
8839
9174
  }).superRefine((value, ctx) => {
8840
9175
  const hasOperations = (value.operations?.length ?? 0) > 0;
8841
- if (hasOperations && (value.where?.length ?? 0) > 0) {
9176
+ if (hasOperations && value.data) {
8842
9177
  ctx.addIssue({
8843
9178
  code: zod.z.ZodIssueCode.custom,
8844
- path: ["where"],
8845
- message: "where cannot be provided when operations[] is used"
9179
+ path: ["data"],
9180
+ message: "data cannot be provided when operations[] is used"
8846
9181
  });
8847
9182
  }
8848
- if (hasOperations && value.allowFullTableDelete) {
9183
+ if (!hasOperations && !value.data) {
8849
9184
  ctx.addIssue({
8850
9185
  code: zod.z.ZodIssueCode.custom,
8851
- path: ["allowFullTableDelete"],
8852
- message: "allowFullTableDelete cannot be provided when operations[] is used"
9186
+ path: ["data"],
9187
+ message: "data is required when operations[] is not provided"
8853
9188
  });
8854
9189
  }
8855
- if (hasOperations && value.cascade) {
9190
+ if (hasOperations && (value.where?.length ?? 0) > 0) {
8856
9191
  ctx.addIssue({
8857
9192
  code: zod.z.ZodIssueCode.custom,
8858
- path: ["cascade"],
8859
- message: "cascade cannot be provided when operations[] is used"
9193
+ path: ["where"],
9194
+ message: "where cannot be provided when operations[] is used"
8860
9195
  });
8861
9196
  }
8862
- if (hasOperations && value.softDelete) {
9197
+ if (hasOperations && value.optimisticLock) {
8863
9198
  ctx.addIssue({
8864
9199
  code: zod.z.ZodIssueCode.custom,
8865
- path: ["softDelete"],
8866
- message: "softDelete cannot be provided when operations[] is used"
9200
+ path: ["optimisticLock"],
9201
+ message: "optimisticLock cannot be provided when operations[] is used"
9202
+ });
9203
+ }
9204
+ if (hasOperations && value.allowFullTableUpdate) {
9205
+ ctx.addIssue({
9206
+ code: zod.z.ZodIssueCode.custom,
9207
+ path: ["allowFullTableUpdate"],
9208
+ message: "allowFullTableUpdate cannot be provided when operations[] is used"
8867
9209
  });
8868
9210
  }
8869
9211
  if (hasOperations) {
8870
9212
  return;
8871
9213
  }
8872
9214
  const hasWhere = (value.where?.length ?? 0) > 0;
8873
- if (!value.allowFullTableDelete && !hasWhere) {
9215
+ const hasOptimisticLock = !!value.optimisticLock;
9216
+ if (!value.allowFullTableUpdate && !hasWhere && !hasOptimisticLock) {
8874
9217
  ctx.addIssue({
8875
9218
  code: zod.z.ZodIssueCode.custom,
8876
9219
  path: ["where"],
8877
- message: "WHERE conditions are required unless allowFullTableDelete is true"
9220
+ message: "WHERE conditions are required unless allowFullTableUpdate is true"
8878
9221
  });
8879
9222
  }
8880
9223
  });
8881
9224
 
8882
- // src/data/relational/tools/relational-delete/error-utils.ts
8883
- var SAFE_DELETE_VALIDATION_PATTERNS = [
9225
+ // src/data/relational/tools/relational-update/error-utils.ts
9226
+ var SAFE_UPDATE_VALIDATION_PATTERNS = [
8884
9227
  "must not be empty",
8885
9228
  "contains invalid characters",
8886
- "WHERE conditions are required for DELETE queries",
8887
- "WHERE conditions are required unless allowFullTableDelete is true",
8888
- "value is required for this operator",
9229
+ "Update data must be an object",
9230
+ "Update data must not be empty",
9231
+ "must not be undefined",
9232
+ "WHERE conditions are required for UPDATE queries",
8889
9233
  "null is only allowed with isNull/isNotNull operators",
8890
- "operator requires a value",
8891
9234
  "operator requires a string or number value",
8892
- "operator requires a string value",
8893
9235
  "operator requires a non-empty array value",
8894
- "must not include value"
9236
+ "operator requires a string value",
9237
+ "must not include value",
9238
+ "Optimistic lock expectedValue must not be empty",
9239
+ "optimistic lock check failed",
9240
+ "WHERE conditions are required unless allowFullTableUpdate is true"
8895
9241
  ];
8896
9242
  var CONSTRAINT_VIOLATION_PATTERNS3 = [
8897
9243
  {
8898
- pattern: /(foreign key constraint|violates foreign key constraint|foreign key mismatch|a foreign key constraint fails)/i,
8899
- message: "Delete failed: foreign key constraint violation."
9244
+ pattern: /(unique constraint|duplicate key|duplicate entry)/i,
9245
+ message: "Update failed: unique constraint violation."
9246
+ },
9247
+ {
9248
+ pattern: /(foreign key constraint|violates foreign key constraint)/i,
9249
+ message: "Update failed: foreign key constraint violation."
9250
+ },
9251
+ {
9252
+ pattern: /(not null constraint|cannot be null)/i,
9253
+ message: "Update failed: NOT NULL constraint violation."
8900
9254
  }
8901
9255
  ];
8902
- function isSafeDeleteValidationError(error) {
9256
+ function isSafeUpdateValidationError(error) {
8903
9257
  if (!(error instanceof Error)) {
8904
9258
  return false;
8905
9259
  }
8906
- return SAFE_DELETE_VALIDATION_PATTERNS.some((pattern) => error.message.includes(pattern));
9260
+ return SAFE_UPDATE_VALIDATION_PATTERNS.some((pattern) => error.message.includes(pattern));
8907
9261
  }
8908
- function getDeleteConstraintViolationMessage(error, cascade) {
9262
+ function getUpdateConstraintViolationMessage(error) {
8909
9263
  if (!(error instanceof Error)) {
8910
9264
  return null;
8911
9265
  }
@@ -8915,21 +9269,18 @@ function getDeleteConstraintViolationMessage(error, cascade) {
8915
9269
  }
8916
9270
  for (const entry of CONSTRAINT_VIOLATION_PATTERNS3) {
8917
9271
  if (messages.some((msg) => entry.pattern.test(msg))) {
8918
- if (cascade) {
8919
- return `${entry.message} Verify database-level ON DELETE CASCADE is configured for related foreign keys.`;
8920
- }
8921
9272
  return entry.message;
8922
9273
  }
8923
9274
  }
8924
9275
  return null;
8925
9276
  }
8926
- function isSafeDeleteError(error) {
9277
+ function isSafeUpdateError(error) {
8927
9278
  if (!(error instanceof Error)) {
8928
9279
  return false;
8929
9280
  }
8930
- return SAFE_DELETE_VALIDATION_PATTERNS.some((pattern) => error.message.includes(pattern)) || error.message.startsWith("Delete failed:");
9281
+ return isSafeUpdateValidationError(error) || getUpdateConstraintViolationMessage(error) !== null;
8931
9282
  }
8932
- var deleteExecutorLogger = core.createLogger("agentforge:tools:data:relational:delete");
9283
+ var updateExecutorLogger = core.createLogger("agentforge:tools:data:relational:update");
8933
9284
  function toNumber3(value) {
8934
9285
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
8935
9286
  }
@@ -8966,71 +9317,73 @@ function resolveBatchOptions3(batch) {
8966
9317
  benchmark: batch.benchmark ?? false
8967
9318
  };
8968
9319
  }
8969
- function toSingleDeleteOperation(input) {
9320
+ function toSingleUpdateOperation(input) {
9321
+ if (!input.data) {
9322
+ throw new Error("UPDATE data is required when operations[] is not provided.");
9323
+ }
8970
9324
  return {
9325
+ data: input.data,
8971
9326
  where: input.where,
8972
- allowFullTableDelete: input.allowFullTableDelete,
8973
- cascade: input.cascade,
8974
- softDelete: input.softDelete
9327
+ allowFullTableUpdate: input.allowFullTableUpdate,
9328
+ optimisticLock: input.optimisticLock
8975
9329
  };
8976
9330
  }
8977
9331
 
8978
- // src/data/relational/tools/relational-delete/executor-single.ts
8979
- async function executeSingleDelete(manager, input, operation, context) {
8980
- const built = buildDeleteQuery({
9332
+ // src/data/relational/tools/relational-update/executor-single.ts
9333
+ async function executeSingleUpdate(executor, input, operation, context) {
9334
+ const built = buildUpdateQuery({
8981
9335
  table: input.table,
9336
+ data: operation.data,
8982
9337
  where: operation.where,
8983
- allowFullTableDelete: operation.allowFullTableDelete,
8984
- softDelete: operation.softDelete,
9338
+ allowFullTableUpdate: operation.allowFullTableUpdate,
9339
+ optimisticLock: operation.optimisticLock,
8985
9340
  vendor: input.vendor
8986
9341
  });
8987
- const executor = manager;
8988
- const rawResult = await executor.execute(built.query);
9342
+ const session = context?.transaction ?? executor;
9343
+ const rawResult = await session.execute(built.query);
8989
9344
  const rowCount = normalizeAffectedRows2(rawResult);
9345
+ if (built.usesOptimisticLock && rowCount === 0) {
9346
+ throw new Error("Update failed: optimistic lock check failed.");
9347
+ }
8990
9348
  return {
8991
9349
  rowCount,
8992
- executionTime: 0,
8993
- softDeleted: built.usesSoftDelete
9350
+ executionTime: 0
8994
9351
  };
8995
9352
  }
8996
9353
 
8997
- // src/data/relational/tools/relational-delete/executor-batch.ts
8998
- async function executeDeleteInBatchMode(manager, input, context, options) {
9354
+ // src/data/relational/tools/relational-update/executor-batch.ts
9355
+ async function executeUpdateInBatchMode(executor, input, context, options) {
8999
9356
  const batchResult = await executeBatchedTask(
9000
9357
  {
9001
- operation: "delete",
9358
+ operation: "update",
9002
9359
  items: input.operations,
9003
9360
  executeBatch: async (operations, batchIndex) => {
9004
9361
  let rowCount2 = 0;
9005
9362
  let successfulItems = 0;
9006
9363
  let failedItems = 0;
9007
- let softDeletedCount = 0;
9008
9364
  const failures = [];
9009
9365
  for (const operation of operations) {
9010
9366
  try {
9011
- const result = await executeSingleDelete(
9012
- manager,
9367
+ const result = await executeSingleUpdate(
9368
+ executor,
9013
9369
  input,
9014
9370
  {
9371
+ data: operation.data,
9015
9372
  where: operation.where,
9016
- allowFullTableDelete: operation.allowFullTableDelete,
9017
- cascade: operation.cascade,
9018
- softDelete: operation.softDelete
9373
+ allowFullTableUpdate: operation.allowFullTableUpdate,
9374
+ optimisticLock: operation.optimisticLock
9019
9375
  },
9020
9376
  context
9021
9377
  );
9022
9378
  rowCount2 += result.rowCount;
9023
9379
  successfulItems += 1;
9024
- if (result.softDeleted) {
9025
- softDeletedCount += 1;
9026
- }
9027
9380
  } catch (error) {
9028
9381
  if (!options.continueOnError) {
9029
9382
  throw error;
9030
9383
  }
9031
9384
  failedItems += 1;
9032
9385
  failures.push({
9033
- operation: "delete",
9386
+ operation: "update",
9034
9387
  batchIndex,
9035
9388
  batchSize: operations.length,
9036
9389
  attempts: 1,
@@ -9042,7 +9395,6 @@ async function executeDeleteInBatchMode(manager, input, context, options) {
9042
9395
  rowCount: rowCount2,
9043
9396
  successfulItems,
9044
9397
  failedItems,
9045
- softDeletedCount,
9046
9398
  failures
9047
9399
  };
9048
9400
  },
@@ -9054,7 +9406,7 @@ async function executeDeleteInBatchMode(manager, input, context, options) {
9054
9406
  maxRetries: options.maxRetries,
9055
9407
  retryDelayMs: options.retryDelayMs,
9056
9408
  onProgress: (progress) => {
9057
- deleteExecutorLogger.debug("DELETE batch progress", {
9409
+ updateExecutorLogger.debug("UPDATE batch progress", {
9058
9410
  vendor: input.vendor,
9059
9411
  table: input.table,
9060
9412
  ...progress
@@ -9063,19 +9415,15 @@ async function executeDeleteInBatchMode(manager, input, context, options) {
9063
9415
  }
9064
9416
  );
9065
9417
  const rowCount = batchResult.results.reduce((total, result) => total + result.rowCount, 0);
9066
- const softDeleted = batchResult.results.some((result) => result.softDeletedCount > 0);
9067
9418
  const operationFailures = batchResult.results.flatMap((result) => result.failures);
9068
9419
  let benchmark;
9069
9420
  if (options.benchmark) {
9070
- deleteExecutorLogger.warn(
9071
- "DELETE batch benchmark enabled. Synthetic benchmark callbacks are side-effect free and do not execute SQL.",
9072
- {
9073
- vendor: input.vendor,
9074
- table: input.table,
9075
- operationCount: input.operations.length,
9076
- batchSize: options.batchSize
9077
- }
9078
- );
9421
+ updateExecutorLogger.warn("UPDATE batch benchmark enabled. Synthetic benchmark callbacks are side-effect free and do not execute SQL.", {
9422
+ vendor: input.vendor,
9423
+ table: input.table,
9424
+ operationCount: input.operations.length,
9425
+ batchSize: options.batchSize
9426
+ });
9079
9427
  benchmark = await benchmarkBatchExecution({
9080
9428
  items: input.operations,
9081
9429
  batchSize: options.batchSize,
@@ -9088,7 +9436,6 @@ async function executeDeleteInBatchMode(manager, input, context, options) {
9088
9436
  return {
9089
9437
  rowCount,
9090
9438
  executionTime: batchResult.executionTime,
9091
- softDeleted,
9092
9439
  batch: {
9093
9440
  enabled: true,
9094
9441
  batchSize: options.batchSize,
@@ -9105,35 +9452,32 @@ async function executeDeleteInBatchMode(manager, input, context, options) {
9105
9452
  };
9106
9453
  }
9107
9454
 
9108
- // src/data/relational/tools/relational-delete/executor.ts
9109
- async function executeDelete(manager, input, context) {
9455
+ // src/data/relational/tools/relational-update/executor.ts
9456
+ async function executeUpdate(executor, input, context) {
9110
9457
  const startTime = Date.now();
9111
- deleteExecutorLogger.debug("Building DELETE query", {
9458
+ updateExecutorLogger.debug("Building UPDATE query", {
9112
9459
  vendor: input.vendor,
9113
9460
  table: input.table,
9114
9461
  hasWhere: !!input.where?.length,
9115
- ...input.allowFullTableDelete !== void 0 ? { allowFullTableDelete: input.allowFullTableDelete } : {},
9116
- ...input.cascade !== void 0 ? { cascade: input.cascade } : {},
9117
- softDelete: !!input.softDelete,
9462
+ ...input.allowFullTableUpdate !== void 0 ? { allowFullTableUpdate: input.allowFullTableUpdate } : {},
9463
+ hasOptimisticLock: !!input.optimisticLock,
9118
9464
  operationCount: input.operations?.length ?? 0,
9119
9465
  batchModeEnabled: !!input.batch?.enabled
9120
9466
  });
9121
9467
  try {
9122
9468
  const batchOptions = resolveBatchOptions3(input.batch);
9123
- const result = input.operations && batchOptions ? await executeDeleteInBatchMode(
9124
- manager,
9469
+ const result = input.operations && batchOptions ? await executeUpdateInBatchMode(
9470
+ executor,
9125
9471
  input,
9126
9472
  context,
9127
9473
  batchOptions
9128
- ) : await executeSingleDelete(manager, input, toSingleDeleteOperation(input), context);
9474
+ ) : await executeSingleUpdate(executor, input, toSingleUpdateOperation(input), context);
9129
9475
  const executionTime = Date.now() - startTime;
9130
- deleteExecutorLogger.debug("DELETE query executed successfully", {
9476
+ updateExecutorLogger.debug("UPDATE query executed successfully", {
9131
9477
  vendor: input.vendor,
9132
9478
  table: input.table,
9133
9479
  rowCount: result.rowCount,
9134
9480
  executionTime,
9135
- softDelete: result.softDeleted,
9136
- ...input.cascade !== void 0 ? { cascade: input.cascade } : {},
9137
9481
  batchMode: !!result.batch,
9138
9482
  partialSuccess: result.batch?.partialSuccess ?? false
9139
9483
  });
@@ -9143,55 +9487,80 @@ async function executeDelete(manager, input, context) {
9143
9487
  };
9144
9488
  } catch (error) {
9145
9489
  const executionTime = Date.now() - startTime;
9146
- deleteExecutorLogger.error("DELETE query execution failed", {
9490
+ updateExecutorLogger.error("UPDATE query execution failed", {
9147
9491
  vendor: input.vendor,
9148
9492
  table: input.table,
9149
9493
  error: error instanceof Error ? error.message : String(error),
9150
- executionTime,
9151
- ...input.cascade !== void 0 ? { cascade: input.cascade } : {},
9152
- softDelete: !!input.softDelete
9494
+ executionTime
9153
9495
  });
9154
- const constraintMessage = getDeleteConstraintViolationMessage(error, input.cascade ?? false);
9496
+ const constraintMessage = getUpdateConstraintViolationMessage(error);
9155
9497
  if (constraintMessage) {
9156
9498
  throw new Error(constraintMessage, { cause: error });
9157
9499
  }
9158
- if (isSafeDeleteValidationError(error)) {
9500
+ if (isSafeUpdateValidationError(error)) {
9159
9501
  throw error;
9160
9502
  }
9161
- throw new Error("DELETE query failed. See logs for details.", { cause: error });
9503
+ throw new Error("UPDATE query failed. See logs for details.", { cause: error });
9162
9504
  }
9163
9505
  }
9164
9506
 
9165
- // src/data/relational/tools/relational-delete/index.ts
9166
- var relationalDelete = core.toolBuilder().name("relational-delete").displayName("Relational DELETE").description("Execute type-safe DELETE queries with single and batched operation support").category(core.ToolCategory.DATABASE).tags(["database", "sql", "delete", "postgresql", "mysql", "sqlite"]).schema(relationalDeleteSchema).example({
9167
- description: "Delete one row by condition",
9507
+ // src/data/relational/tools/relational-update/index.ts
9508
+ function toUpdateErrorResponse(error) {
9509
+ const errorMessage = isSafeUpdateError(error) ? error.message : "Failed to execute UPDATE query. Please verify your input and database connection.";
9510
+ return {
9511
+ success: false,
9512
+ error: errorMessage,
9513
+ rowCount: 0
9514
+ };
9515
+ }
9516
+ async function invokeRelationalUpdate(execution, input) {
9517
+ try {
9518
+ const result = await executeUpdate(
9519
+ execution.executor,
9520
+ { ...input, vendor: execution.vendor },
9521
+ execution.transaction ? { transaction: execution.transaction } : void 0
9522
+ );
9523
+ return {
9524
+ success: true,
9525
+ rowCount: result.rowCount,
9526
+ executionTime: result.executionTime,
9527
+ batch: result.batch
9528
+ };
9529
+ } catch (error) {
9530
+ return toUpdateErrorResponse(error);
9531
+ }
9532
+ }
9533
+ var relationalUpdate = core.toolBuilder().name("relational-update").displayName("Relational UPDATE").description("Execute type-safe UPDATE queries with single and batched operation support").category(core.ToolCategory.DATABASE).tags(["database", "sql", "update", "postgresql", "mysql", "sqlite"]).schema(relationalUpdateSchema).example({
9534
+ description: "Update one row by condition",
9168
9535
  input: {
9169
9536
  table: "users",
9537
+ data: { status: "inactive" },
9170
9538
  where: [{ column: "id", operator: "eq", value: 123 }],
9171
9539
  vendor: "postgresql",
9172
9540
  connectionString: "postgresql://localhost/mydb"
9173
9541
  }
9174
9542
  }).example({
9175
- description: "Soft delete rows",
9543
+ description: "Optimistic lock update",
9176
9544
  input: {
9177
9545
  table: "users",
9178
- where: [{ column: "status", operator: "eq", value: "inactive" }],
9179
- softDelete: { column: "deleted_at" },
9546
+ data: { status: "active" },
9547
+ where: [{ column: "id", operator: "eq", value: 123 }],
9548
+ optimisticLock: { column: "version", expectedValue: 5 },
9180
9549
  vendor: "sqlite",
9181
9550
  connectionString: "data.db"
9182
9551
  }
9183
9552
  }).example({
9184
- description: "Batch delete operations",
9553
+ description: "Batch update operations",
9185
9554
  input: {
9186
9555
  table: "users",
9187
9556
  operations: [
9188
9557
  {
9189
- where: [{ column: "status", operator: "eq", value: "inactive" }],
9190
- softDelete: { column: "deleted_at" }
9558
+ data: { status: "inactive" },
9559
+ where: [{ column: "id", operator: "eq", value: 1 }]
9191
9560
  },
9192
9561
  {
9193
- where: [{ column: "status", operator: "eq", value: "pending_delete" }],
9194
- softDelete: { column: "deleted_at" }
9562
+ data: { status: "inactive" },
9563
+ where: [{ column: "id", operator: "eq", value: 2 }]
9195
9564
  }
9196
9565
  ],
9197
9566
  batch: {
@@ -9202,141 +9571,503 @@ var relationalDelete = core.toolBuilder().name("relational-delete").displayName(
9202
9571
  connectionString: "postgresql://localhost/mydb"
9203
9572
  }
9204
9573
  }).implement(async (input) => {
9205
- const manager = new ConnectionManager({
9206
- vendor: input.vendor,
9207
- connection: input.connectionString
9208
- });
9209
- try {
9210
- await manager.connect();
9211
- const result = await executeDelete(manager, input);
9212
- return {
9213
- success: true,
9214
- rowCount: result.rowCount,
9215
- executionTime: result.executionTime,
9216
- softDeleted: result.softDeleted,
9217
- batch: result.batch
9218
- };
9219
- } catch (error) {
9220
- let errorMessage = "Failed to execute DELETE query. Please verify your input and database connection.";
9221
- if (isSafeDeleteError(error)) {
9222
- errorMessage = error.message;
9223
- }
9224
- return {
9225
- success: false,
9226
- error: errorMessage,
9227
- rowCount: 0,
9228
- softDeleted: false
9229
- };
9230
- } finally {
9231
- await manager.disconnect();
9232
- }
9574
+ const { connectionString, vendor, ...operation } = input;
9575
+ return withEphemeralRelationalMutationToolSet(
9576
+ { vendor, connectionString },
9577
+ async (toolSet) => replaceMutationConnectionFailure(
9578
+ await toolSet.update.invoke(operation),
9579
+ UPDATE_CONNECTION_FAILURE,
9580
+ "Failed to execute UPDATE query. Please verify your input and database connection."
9581
+ ),
9582
+ toUpdateErrorResponse
9583
+ );
9233
9584
  }).build();
9234
9585
 
9235
- // src/data/relational/tools/relational-get-schema-error-utils.ts
9236
- var SAFE_VALIDATION_ERROR_PATTERNS2 = [
9237
- "must not be empty",
9238
- "contains invalid characters",
9239
- "Invalid table filter"
9240
- ];
9241
- function isSafeGetSchemaValidationError(error) {
9242
- if (!(error instanceof Error)) {
9586
+ // src/data/relational/tool-set.ts
9587
+ var DEFAULT_SCHEMA_CACHE_TTL_MS = 6e4;
9588
+ var TOOL_SET_SCHEMA_CACHE_KEY = "relational-tool-set";
9589
+ var PREFIX_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
9590
+ var ROLLBACK_ONLY_MESSAGE = "Transaction is rollback-only; no database work was executed.";
9591
+ var logger18 = core.createLogger("agentforge:tools:data:relational:tool-set");
9592
+ var RelationalToolSetConfigurationError = class extends Error {
9593
+ name = "RelationalToolSetConfigurationError";
9594
+ };
9595
+ var RelationalToolSetDisposedError = class extends Error {
9596
+ name = "RelationalToolSetDisposedError";
9597
+ constructor() {
9598
+ super("The Relational Tool Set is disposing or has been disposed.");
9599
+ }
9600
+ };
9601
+ var RelationalTransactionError = class extends Error {
9602
+ constructor(message, code) {
9603
+ super(message);
9604
+ this.code = code;
9605
+ }
9606
+ name = "RelationalTransactionError";
9607
+ };
9608
+ function isFailedOrPartialResult(result) {
9609
+ if (!result || typeof result !== "object") {
9243
9610
  return false;
9244
9611
  }
9245
- return SAFE_VALIDATION_ERROR_PATTERNS2.some(
9246
- (pattern) => error.message.includes(pattern)
9612
+ const response = result;
9613
+ return response.success === false || response.batch?.partialSuccess === true || (response.batch?.failedItems ?? 0) > 0;
9614
+ }
9615
+ var TransactionToolScope = class {
9616
+ constructor(execution) {
9617
+ this.execution = execution;
9618
+ }
9619
+ active = true;
9620
+ rollbackOnly = false;
9621
+ tail = Promise.resolve();
9622
+ run(operation, failure) {
9623
+ if (!this.active) {
9624
+ return Promise.reject(
9625
+ new RelationalTransactionError(
9626
+ "Transaction-scoped Tools can no longer be used because their callback has settled.",
9627
+ "SCOPE_EXPIRED"
9628
+ )
9629
+ );
9630
+ }
9631
+ const invocation = this.tail.then(async () => {
9632
+ if (this.rollbackOnly) {
9633
+ return failure(ROLLBACK_ONLY_MESSAGE);
9634
+ }
9635
+ const result = await operation(this.execution);
9636
+ if (isFailedOrPartialResult(result)) {
9637
+ this.rollbackOnly = true;
9638
+ }
9639
+ return result;
9640
+ });
9641
+ this.tail = invocation.then(
9642
+ () => void 0,
9643
+ () => void 0
9644
+ );
9645
+ return invocation;
9646
+ }
9647
+ async settle() {
9648
+ this.active = false;
9649
+ await this.tail;
9650
+ }
9651
+ isRollbackOnly() {
9652
+ return this.rollbackOnly;
9653
+ }
9654
+ };
9655
+ function createConfiguredTools(options, execution) {
9656
+ const query = core.createTool(
9657
+ configuredMetadata(relationalQuery, options.prefix),
9658
+ relationalQuerySchema.omit({ vendor: true, connectionString: true }),
9659
+ (input) => execution(
9660
+ (context) => invokeRelationalQuery(context, input),
9661
+ (error) => ({ success: false, error, rows: [], rowCount: 0 }),
9662
+ QUERY_CONNECTION_FAILURE
9663
+ )
9664
+ );
9665
+ const select = core.createTool(
9666
+ configuredMetadata(relationalSelect, options.prefix),
9667
+ relationalSelectSchema.omit({ vendor: true, connectionString: true }),
9668
+ (input) => execution(
9669
+ (context) => invokeRelationalSelect(context, input),
9670
+ (error) => ({ success: false, error, rows: [], rowCount: 0 }),
9671
+ SELECT_CONNECTION_FAILURE
9672
+ )
9673
+ );
9674
+ const insert = core.createTool(
9675
+ configuredMetadata(relationalInsert, options.prefix),
9676
+ relationalInsertSchema.omit({ vendor: true, connectionString: true }),
9677
+ (input) => execution(
9678
+ (context) => invokeRelationalInsert(context, input),
9679
+ (error) => ({
9680
+ success: false,
9681
+ error,
9682
+ rowCount: 0,
9683
+ insertedIds: [],
9684
+ rows: []
9685
+ }),
9686
+ INSERT_CONNECTION_FAILURE
9687
+ )
9688
+ );
9689
+ const update = core.createTool(
9690
+ configuredMetadata(relationalUpdate, options.prefix),
9691
+ relationalUpdateOperationSchema,
9692
+ (input) => execution(
9693
+ (context) => invokeRelationalUpdate(context, input),
9694
+ (error) => ({ success: false, error, rowCount: 0 }),
9695
+ UPDATE_CONNECTION_FAILURE
9696
+ )
9697
+ );
9698
+ const deleteTool = core.createTool(
9699
+ configuredMetadata(relationalDelete, options.prefix),
9700
+ relationalDeleteOperationSchema,
9701
+ (input) => execution(
9702
+ (context) => invokeRelationalDelete(context, input),
9703
+ (error) => ({ success: false, error, rowCount: 0, softDeleted: false }),
9704
+ DELETE_CONNECTION_FAILURE
9705
+ )
9706
+ );
9707
+ const getSchema = core.createTool(
9708
+ configuredMetadata(relationalGetSchema, options.prefix),
9709
+ relationalGetSchemaInputSchema.omit({
9710
+ vendor: true,
9711
+ connectionString: true,
9712
+ database: true,
9713
+ cacheTtlMs: true
9714
+ }),
9715
+ (input) => execution(
9716
+ (context) => invokeRelationalGetSchema(context, {
9717
+ ...input,
9718
+ cacheTtlMs: options.schemaCacheTtlMs ?? DEFAULT_SCHEMA_CACHE_TTL_MS
9719
+ }),
9720
+ (error) => ({ success: false, error, schema: null }),
9721
+ "Failed to inspect schema. See logs for details."
9722
+ )
9247
9723
  );
9724
+ const tools = [query, select, insert, update, deleteTool, getSchema];
9725
+ return Object.freeze({
9726
+ query,
9727
+ select,
9728
+ insert,
9729
+ update,
9730
+ delete: deleteTool,
9731
+ getSchema,
9732
+ [Symbol.iterator]: () => tools[Symbol.iterator]()
9733
+ });
9248
9734
  }
9249
-
9250
- // src/data/relational/tools/relational-get-schema.ts
9251
- var logger17 = core.createLogger("agentforge:tools:data:relational:get-schema");
9252
- function buildSchemaCacheKey(vendor, connectionString, database) {
9253
- const databaseScope = database ?? "default";
9254
- const connectionHash = crypto.createHash("sha256").update(connectionString).digest("hex");
9255
- return `${vendor}:${databaseScope}:${connectionHash}`;
9735
+ function validateConfiguration(config, options) {
9736
+ if (!config || !["postgresql", "mysql", "sqlite"].includes(config.vendor)) {
9737
+ throw new RelationalToolSetConfigurationError(
9738
+ "Database vendor must be postgresql, mysql, or sqlite."
9739
+ );
9740
+ }
9741
+ if (typeof config.connection === "string" && config.connection.trim().length === 0 || typeof config.connection !== "string" && (!config.connection || Array.isArray(config.connection))) {
9742
+ throw new RelationalToolSetConfigurationError(
9743
+ "Database connection must be a non-empty string or configuration object."
9744
+ );
9745
+ }
9746
+ if (config.vendor === "sqlite" && typeof config.connection !== "string" && (typeof config.connection.url !== "string" || config.connection.url.trim().length === 0)) {
9747
+ throw new RelationalToolSetConfigurationError(
9748
+ "SQLite object configuration requires a non-empty url."
9749
+ );
9750
+ }
9751
+ if (options.prefix !== void 0 && !PREFIX_PATTERN.test(options.prefix)) {
9752
+ throw new RelationalToolSetConfigurationError(
9753
+ "Tool name prefix must be non-empty kebab-case starting with a letter."
9754
+ );
9755
+ }
9756
+ if (options.schemaCacheTtlMs !== void 0 && (!Number.isInteger(options.schemaCacheTtlMs) || options.schemaCacheTtlMs < 0)) {
9757
+ throw new RelationalToolSetConfigurationError(
9758
+ "Schema cache TTL must be a non-negative integer."
9759
+ );
9760
+ }
9761
+ const longestName = configuredName("relational-get-schema", options.prefix);
9762
+ if (longestName.length > 50) {
9763
+ throw new RelationalToolSetConfigurationError(
9764
+ "Tool name prefix is too long; configured Tool names must not exceed 50 characters."
9765
+ );
9766
+ }
9256
9767
  }
9257
- var relationalGetSchemaInputSchema = zod.z.object({
9258
- vendor: zod.z.enum(["postgresql", "mysql", "sqlite"]).describe("Database vendor"),
9259
- connectionString: zod.z.string().min(1, "Database connection string is required").describe("Database connection string used to create a new connection"),
9260
- database: zod.z.string().min(1).optional().describe("Optional logical database name used for cache scoping"),
9261
- tables: zod.z.array(
9262
- zod.z.string().regex(
9263
- VALID_TABLE_FILTER_PATTERN,
9264
- "Table filter contains invalid characters. Use alphanumeric, underscore, and optional schema qualification."
9265
- )
9266
- ).optional().describe("Optional table filter list. When omitted, all tables are returned."),
9267
- cacheTtlMs: zod.z.number().int().min(0).optional().describe("Schema cache TTL in milliseconds (0 disables caching)"),
9268
- refreshCache: zod.z.boolean().optional().describe("Force cache invalidation before introspecting schema")
9269
- });
9270
- var relationalGetSchema = core.toolBuilder().name("relational-get-schema").displayName("Relational Get Schema").description("Inspect relational database schema (tables, columns, primary keys, foreign keys, indexes) across PostgreSQL, MySQL, and SQLite").category(core.ToolCategory.DATABASE).tags(["database", "schema", "introspection", "postgresql", "mysql", "sqlite"]).schema(relationalGetSchemaInputSchema).example({
9271
- description: "Inspect all SQLite tables",
9272
- input: {
9273
- vendor: "sqlite",
9274
- connectionString: ":memory:"
9768
+ function cloneImmutableValue(value) {
9769
+ if (Array.isArray(value)) {
9770
+ return Object.freeze(value.map(cloneImmutableValue));
9275
9771
  }
9276
- }).example({
9277
- description: "Inspect selected PostgreSQL tables with cache control",
9278
- input: {
9279
- vendor: "postgresql",
9280
- connectionString: "postgresql://localhost/app",
9281
- tables: ["public.users", "public.orders"],
9282
- cacheTtlMs: 3e4,
9283
- refreshCache: true
9772
+ if (value instanceof Date) {
9773
+ return new Date(value.getTime());
9284
9774
  }
9285
- }).implement(async (input) => {
9286
- const manager = new ConnectionManager({
9287
- vendor: input.vendor,
9288
- connection: input.connectionString
9289
- });
9290
- const cacheKey = buildSchemaCacheKey(
9291
- input.vendor,
9292
- input.connectionString,
9293
- input.database
9294
- );
9295
- const inspector = new SchemaInspector(manager, input.vendor, {
9296
- cacheTtlMs: input.cacheTtlMs,
9297
- cacheKey
9775
+ if (value instanceof ArrayBuffer) {
9776
+ return value.slice(0);
9777
+ }
9778
+ if (Buffer.isBuffer(value)) {
9779
+ return Buffer.from(value);
9780
+ }
9781
+ if (ArrayBuffer.isView(value)) {
9782
+ return structuredClone(value);
9783
+ }
9784
+ if (value && typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype) {
9785
+ return Object.freeze(
9786
+ Object.fromEntries(
9787
+ Object.entries(value).map(([key, nestedValue]) => [key, cloneImmutableValue(nestedValue)])
9788
+ )
9789
+ );
9790
+ }
9791
+ if (value && typeof value === "object") {
9792
+ throw new RelationalToolSetConfigurationError(
9793
+ "Database configuration contains an unsupported mutable object."
9794
+ );
9795
+ }
9796
+ return value;
9797
+ }
9798
+ function snapshotConfiguration(config) {
9799
+ const connection = cloneImmutableValue(config.connection);
9800
+ return Object.freeze({ vendor: config.vendor, connection });
9801
+ }
9802
+ function configuredName(name, prefix) {
9803
+ return prefix ? `${prefix}-${name}` : name;
9804
+ }
9805
+ function configuredMetadata(tool, prefix) {
9806
+ return {
9807
+ ...tool.metadata,
9808
+ name: configuredName(tool.metadata.name, prefix),
9809
+ examples: void 0
9810
+ };
9811
+ }
9812
+ function preserveOperationValidation(legacySchema, input, context) {
9813
+ const result = legacySchema.safeParse({
9814
+ ...input,
9815
+ vendor: "sqlite",
9816
+ connectionString: "configured"
9298
9817
  });
9299
- try {
9300
- await manager.connect();
9301
- if (input.refreshCache) {
9302
- inspector.invalidateCache();
9818
+ if (!result.success) {
9819
+ for (const issue of result.error.issues) {
9820
+ context.addIssue(issue);
9821
+ }
9822
+ }
9823
+ }
9824
+ var relationalUpdateOperationSchema = relationalUpdateSchema.innerType().omit({ vendor: true, connectionString: true }).superRefine((input, context) => {
9825
+ preserveOperationValidation(relationalUpdateSchema, input, context);
9826
+ });
9827
+ var relationalDeleteOperationSchema = relationalDeleteSchema.innerType().omit({ vendor: true, connectionString: true }).superRefine((input, context) => {
9828
+ preserveOperationValidation(relationalDeleteSchema, input, context);
9829
+ });
9830
+ var RelationalToolSetImplementation = class {
9831
+ constructor(config, options, sharedSchemaCacheKey) {
9832
+ this.config = config;
9833
+ this.sharedSchemaCacheKey = sharedSchemaCacheKey;
9834
+ this.prefix = options.prefix;
9835
+ const configuredTools = createConfiguredTools(
9836
+ options,
9837
+ (operation, failure, failureMessage) => this.run(operation, () => failure(failureMessage))
9838
+ );
9839
+ this.query = configuredTools.query;
9840
+ this.select = configuredTools.select;
9841
+ this.insert = configuredTools.insert;
9842
+ this.update = configuredTools.update;
9843
+ this.delete = configuredTools.delete;
9844
+ this.getSchema = configuredTools.getSchema;
9845
+ this.tools = Object.freeze([
9846
+ this.query,
9847
+ this.select,
9848
+ this.insert,
9849
+ this.update,
9850
+ this.delete,
9851
+ this.getSchema
9852
+ ]);
9853
+ }
9854
+ query;
9855
+ select;
9856
+ insert;
9857
+ update;
9858
+ delete;
9859
+ getSchema;
9860
+ tools;
9861
+ schemaCache = new SchemaCache();
9862
+ prefix;
9863
+ transactionContext = new async_hooks.AsyncLocalStorage();
9864
+ sqliteWorkTail = Promise.resolve();
9865
+ manager;
9866
+ initialization;
9867
+ activeWork = 0;
9868
+ state = "open";
9869
+ drain;
9870
+ resolveDrain;
9871
+ disposal;
9872
+ [Symbol.iterator]() {
9873
+ return this.tools[Symbol.iterator]();
9874
+ }
9875
+ async transaction(operation, options) {
9876
+ if (this.transactionContext.getStore()?.active) {
9877
+ throw new RelationalTransactionError(
9878
+ "Nested Relational Tool Set transactions are not supported.",
9879
+ "NESTED_TRANSACTION"
9880
+ );
9303
9881
  }
9304
- const schema = await inspector.inspect({
9305
- tables: input.tables
9882
+ return this.runTransaction((manager) => {
9883
+ const transactionState = { active: true };
9884
+ return this.transactionContext.run(
9885
+ transactionState,
9886
+ () => withTransaction(
9887
+ manager,
9888
+ async (transaction) => {
9889
+ const scope = new TransactionToolScope(this.transactionExecutionFor(transaction));
9890
+ const scopedTools = createConfiguredTools(
9891
+ { prefix: this.prefix },
9892
+ (scopedOperation, failure) => scope.run(scopedOperation, failure)
9893
+ );
9894
+ let result;
9895
+ try {
9896
+ result = await operation(scopedTools);
9897
+ } finally {
9898
+ try {
9899
+ await scope.settle();
9900
+ } finally {
9901
+ transactionState.active = false;
9902
+ }
9903
+ }
9904
+ if (scope.isRollbackOnly()) {
9905
+ throw new RelationalTransactionError(
9906
+ "The transaction was rolled back because a scoped Tool failed.",
9907
+ "ROLLBACK_ONLY"
9908
+ );
9909
+ }
9910
+ return result;
9911
+ },
9912
+ options
9913
+ )
9914
+ );
9306
9915
  });
9307
- const summary = schema.tables.reduce(
9308
- (accumulator, table) => {
9309
- accumulator.tableCount += 1;
9310
- accumulator.columnCount += table.columns.length;
9311
- accumulator.foreignKeyCount += table.foreignKeys.length;
9312
- accumulator.indexCount += table.indexes.length;
9313
- return accumulator;
9314
- },
9315
- { tableCount: 0, columnCount: 0, foreignKeyCount: 0, indexCount: 0 }
9916
+ }
9917
+ refreshSchema() {
9918
+ if (this.state !== "open") {
9919
+ throw new RelationalToolSetDisposedError();
9920
+ }
9921
+ if (this.sharedSchemaCacheKey) {
9922
+ SchemaInspector.clearCache(this.sharedSchemaCacheKey);
9923
+ } else {
9924
+ this.schemaCache.delete(TOOL_SET_SCHEMA_CACHE_KEY);
9925
+ }
9926
+ }
9927
+ dispose() {
9928
+ if (!this.disposal) {
9929
+ this.state = "closing";
9930
+ this.disposal = this.finishDisposal();
9931
+ }
9932
+ return this.disposal;
9933
+ }
9934
+ async run(operation, connectionFailure) {
9935
+ if (this.transactionContext.getStore()?.active) {
9936
+ throw new RelationalTransactionError(
9937
+ "Use the scoped Tools supplied to the transaction callback.",
9938
+ "UNSCOPED_TOOL_IN_TRANSACTION"
9939
+ );
9940
+ }
9941
+ if (this.state !== "open") {
9942
+ throw new RelationalToolSetDisposedError();
9943
+ }
9944
+ this.activeWork += 1;
9945
+ try {
9946
+ let manager;
9947
+ try {
9948
+ manager = await this.ensureConnection();
9949
+ } catch (error) {
9950
+ if (error instanceof MissingPeerDependencyError) {
9951
+ throw error;
9952
+ }
9953
+ return connectionFailure();
9954
+ }
9955
+ const execute = () => operation(this.schemaExecutionFor(manager));
9956
+ return this.config.vendor === "sqlite" ? await this.serializeSqliteWork(execute) : await execute();
9957
+ } finally {
9958
+ this.activeWork -= 1;
9959
+ if (this.activeWork === 0) {
9960
+ this.resolveDrain?.();
9961
+ }
9962
+ }
9963
+ }
9964
+ async runTransaction(operation) {
9965
+ if (this.state !== "open") {
9966
+ throw new RelationalToolSetDisposedError();
9967
+ }
9968
+ this.activeWork += 1;
9969
+ try {
9970
+ let manager;
9971
+ try {
9972
+ manager = await this.ensureConnection();
9973
+ } catch (error) {
9974
+ if (error instanceof MissingPeerDependencyError) {
9975
+ throw error;
9976
+ }
9977
+ throw new RelationalTransactionError(
9978
+ "Failed to start the managed database transaction.",
9979
+ "START_FAILED"
9980
+ );
9981
+ }
9982
+ if (this.config.vendor !== "sqlite") {
9983
+ return await operation(manager);
9984
+ }
9985
+ return await this.serializeSqliteWork(() => operation(manager));
9986
+ } finally {
9987
+ this.activeWork -= 1;
9988
+ if (this.activeWork === 0) {
9989
+ this.resolveDrain?.();
9990
+ }
9991
+ }
9992
+ }
9993
+ async serializeSqliteWork(operation) {
9994
+ const work = this.sqliteWorkTail.then(operation);
9995
+ this.sqliteWorkTail = work.then(
9996
+ () => void 0,
9997
+ () => void 0
9316
9998
  );
9317
- return {
9318
- success: true,
9319
- schema,
9320
- summary
9999
+ return work;
10000
+ }
10001
+ ensureConnection() {
10002
+ if (this.manager?.isConnected()) {
10003
+ return Promise.resolve(this.manager);
10004
+ }
10005
+ if (!this.initialization) {
10006
+ const manager = new ConnectionManager(this.config);
10007
+ this.manager = manager;
10008
+ this.initialization = manager.connect().then(() => manager).catch(async (error) => {
10009
+ this.manager = void 0;
10010
+ this.initialization = void 0;
10011
+ if (!(error instanceof MissingPeerDependencyError)) {
10012
+ logger18.error("Relational Tool Set connection initialization failed", {
10013
+ vendor: this.config.vendor,
10014
+ errorType: error instanceof Error ? error.name : typeof error
10015
+ });
10016
+ }
10017
+ try {
10018
+ await manager.dispose();
10019
+ } catch {
10020
+ }
10021
+ throw error;
10022
+ });
10023
+ }
10024
+ return this.initialization;
10025
+ }
10026
+ schemaExecutionFor(manager) {
10027
+ const execution = { executor: manager, vendor: this.config.vendor };
10028
+ return this.sharedSchemaCacheKey ? { ...execution, schemaCacheKey: this.sharedSchemaCacheKey } : {
10029
+ ...execution,
10030
+ schemaCache: this.schemaCache,
10031
+ schemaCacheKey: TOOL_SET_SCHEMA_CACHE_KEY
9321
10032
  };
9322
- } catch (error) {
9323
- logger17.error("Schema introspection failed", {
9324
- vendor: input.vendor,
9325
- hasTablesFilter: Array.isArray(input.tables),
9326
- tablesFilterCount: input.tables?.length ?? 0,
9327
- refreshCache: input.refreshCache ?? false,
9328
- error: error instanceof Error ? error.message : String(error)
9329
- });
9330
- const errorMessage = isSafeGetSchemaValidationError(error) ? error.message : "Failed to inspect schema. See logs for details.";
10033
+ }
10034
+ transactionExecutionFor(transaction) {
9331
10035
  return {
9332
- success: false,
9333
- error: errorMessage,
9334
- schema: null
10036
+ executor: transaction,
10037
+ transaction,
10038
+ vendor: this.config.vendor
9335
10039
  };
9336
- } finally {
9337
- await manager.disconnect();
9338
10040
  }
9339
- }).build();
10041
+ async finishDisposal() {
10042
+ if (this.activeWork > 0) {
10043
+ this.drain = new Promise((resolve3) => {
10044
+ this.resolveDrain = resolve3;
10045
+ });
10046
+ await this.drain;
10047
+ }
10048
+ this.schemaCache.clear();
10049
+ try {
10050
+ await this.manager?.dispose();
10051
+ } finally {
10052
+ this.state = "closed";
10053
+ this.resolveDrain = void 0;
10054
+ }
10055
+ }
10056
+ };
10057
+ function createRelationalToolSet(config, options = {}) {
10058
+ validateConfiguration(config, options);
10059
+ return new RelationalToolSetImplementation(snapshotConfiguration(config), { ...options });
10060
+ }
10061
+ function createLegacyRelationalReadToolSet(config, options = {}, schemaCacheKey) {
10062
+ return new RelationalToolSetImplementation(
10063
+ snapshotConfiguration(config),
10064
+ { ...options },
10065
+ schemaCacheKey
10066
+ );
10067
+ }
10068
+ function createLegacyRelationalMutationToolSet(config) {
10069
+ return new RelationalToolSetImplementation(snapshotConfiguration(config), {});
10070
+ }
9340
10071
  var fileReaderSchema = zod.z.object({
9341
10072
  path: zod.z.string().describe("Path to the file to read"),
9342
10073
  encoding: zod.z.enum(["utf8", "utf-8", "ascii", "base64", "hex", "binary"]).default("utf8").describe("File encoding")
@@ -10668,7 +11399,7 @@ var AskHumanInputSchema = zod.z.object({
10668
11399
  suggestions: zod.z.array(zod.z.string()).optional().describe("Suggested responses for the human")
10669
11400
  });
10670
11401
  var logLevel3 = process.env.LOG_LEVEL?.toLowerCase() || core.LogLevel.INFO;
10671
- var logger18 = core.createLogger("agentforge:tools:agent:ask-human", { level: logLevel3 });
11402
+ var logger19 = core.createLogger("agentforge:tools:agent:ask-human", { level: logLevel3 });
10672
11403
  function isRecord3(value) {
10673
11404
  return typeof value === "object" && value !== null;
10674
11405
  }
@@ -10733,7 +11464,7 @@ function createAskHumanTool() {
10733
11464
  suggestions: validatedInput.suggestions,
10734
11465
  status: "pending"
10735
11466
  };
10736
- logger18.debug("About to call interrupt()", {
11467
+ logger19.debug("About to call interrupt()", {
10737
11468
  humanRequest: {
10738
11469
  id: humanRequest.id,
10739
11470
  question: humanRequest.question,
@@ -10749,13 +11480,13 @@ function createAskHumanTool() {
10749
11480
  let response;
10750
11481
  try {
10751
11482
  response = interrupt(humanRequest);
10752
- logger18.debug("interrupt() returned successfully", {
11483
+ logger19.debug("interrupt() returned successfully", {
10753
11484
  responseType: typeof response,
10754
11485
  hasResponse: response != null,
10755
11486
  ...typeof response === "string" ? { responseLength: response.length } : {}
10756
11487
  });
10757
11488
  } catch (error) {
10758
- logger18.debug("interrupt() threw error (expected for GraphInterrupt)", {
11489
+ logger19.debug("interrupt() threw error (expected for GraphInterrupt)", {
10759
11490
  ...error && typeof error === "object" && "constructor" in error ? { errorType: error.constructor?.name } : {},
10760
11491
  error: error instanceof Error ? error.message : String(error)
10761
11492
  });
@@ -10809,6 +11540,10 @@ exports.MissingPeerDependencyError = MissingPeerDependencyError;
10809
11540
  exports.OpenAIEmbeddingProvider = OpenAIEmbeddingProvider;
10810
11541
  exports.PhoneValidatorSchema = PhoneValidatorSchema;
10811
11542
  exports.RandomNumberSchema = RandomNumberSchema;
11543
+ exports.RelationalToolSetConfigurationError = RelationalToolSetConfigurationError;
11544
+ exports.RelationalToolSetDisposedError = RelationalToolSetDisposedError;
11545
+ exports.RelationalTransactionError = RelationalTransactionError;
11546
+ exports.SchemaCache = SchemaCache;
10812
11547
  exports.SchemaInspector = SchemaInspector;
10813
11548
  exports.SerperProvider = SerperProvider;
10814
11549
  exports.StatisticsSchema = StatisticsSchema;
@@ -10912,6 +11647,7 @@ exports.createPathResolveTool = createPathResolveTool;
10912
11647
  exports.createPathUtilityTools = createPathUtilityTools;
10913
11648
  exports.createPhoneValidatorTool = createPhoneValidatorTool;
10914
11649
  exports.createRandomNumberTool = createRandomNumberTool;
11650
+ exports.createRelationalToolSet = createRelationalToolSet;
10915
11651
  exports.createScraperTools = createScraperTools;
10916
11652
  exports.createSelectReadableStream = createSelectReadableStream;
10917
11653
  exports.createSerperProvider = createSerperProvider;