@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/README.md +51 -7
- package/dist/index.cjs +2038 -1302
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1201 -256
- package/dist/index.d.ts +1201 -256
- package/dist/index.js +2035 -1304
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
|
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,
|
|
4488
|
+
function cancelPendingReconnection(timer, logger20, vendor, message) {
|
|
4410
4489
|
if (!timer) {
|
|
4411
4490
|
return null;
|
|
4412
4491
|
}
|
|
4413
|
-
|
|
4492
|
+
logger20.debug(message, { vendor });
|
|
4414
4493
|
clearTimeout(timer);
|
|
4415
4494
|
return null;
|
|
4416
4495
|
}
|
|
4417
|
-
async function waitForInFlightConnection(promise,
|
|
4496
|
+
async function waitForInFlightConnection(promise, logger20, vendor, phase) {
|
|
4418
4497
|
if (!promise) {
|
|
4419
4498
|
return null;
|
|
4420
4499
|
}
|
|
4421
|
-
|
|
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,
|
|
4517
|
+
function scheduleReconnection(context, logger20) {
|
|
4439
4518
|
if (context.reconnectionConfig.maxAttempts > 0 && context.reconnectionAttempts >= context.reconnectionConfig.maxAttempts) {
|
|
4440
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
4568
|
+
async function cleanupConnectionResources(vendor, client, clearConnection, logger20) {
|
|
4490
4569
|
if (!client) {
|
|
4491
4570
|
return;
|
|
4492
4571
|
}
|
|
4493
|
-
|
|
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
|
-
|
|
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,
|
|
4586
|
+
async function closeManagedConnection(runtime, logger20) {
|
|
4508
4587
|
const client = runtime.getClient();
|
|
4509
4588
|
if (client) {
|
|
4510
|
-
|
|
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
|
-
|
|
4597
|
+
logger20.debug("Database connection closed successfully", {
|
|
4519
4598
|
vendor: runtime.vendor,
|
|
4520
4599
|
state: runtime.getState()
|
|
4521
4600
|
});
|
|
4522
4601
|
} catch (error) {
|
|
4523
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
4791
|
+
logger20
|
|
4713
4792
|
);
|
|
4714
4793
|
}
|
|
4715
4794
|
runtime.setState("connecting" /* CONNECTING */);
|
|
4716
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4817
|
+
logger20
|
|
4739
4818
|
);
|
|
4740
4819
|
runtime.setState("disconnected" /* DISCONNECTED */);
|
|
4741
4820
|
throw new Error("Connection cancelled during initialization");
|
|
4742
4821
|
}
|
|
4743
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4882
|
+
logger20
|
|
4804
4883
|
);
|
|
4805
4884
|
if (runtime.getReconnectionConfig().enabled && currentGeneration === runtime.getConnectionGeneration()) {
|
|
4806
4885
|
runtime.scheduleReconnection();
|
|
4807
4886
|
}
|
|
4808
4887
|
} else {
|
|
4809
|
-
|
|
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,
|
|
4906
|
+
async function checkConnectionHealth(args, logger20) {
|
|
4828
4907
|
if (!args.db || !args.client) {
|
|
4829
|
-
|
|
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
|
-
|
|
4913
|
+
logger20.debug("Health check passed", { vendor: args.vendor });
|
|
4835
4914
|
return true;
|
|
4836
4915
|
} catch (error) {
|
|
4837
|
-
|
|
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,
|
|
4881
|
-
|
|
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(
|
|
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
|
|
5269
|
-
const result = await
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
6845
|
-
|
|
6846
|
-
|
|
6847
|
-
this.
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
7368
|
-
|
|
7369
|
-
|
|
7370
|
-
|
|
7371
|
-
|
|
7372
|
-
|
|
7373
|
-
|
|
7374
|
-
|
|
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
|
|
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:
|
|
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((
|
|
7464
|
-
const op =
|
|
7535
|
+
}).superRefine((value, ctx) => {
|
|
7536
|
+
const op = value.operator;
|
|
7465
7537
|
if (op === "isNull" || op === "isNotNull") {
|
|
7466
|
-
if (
|
|
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 (
|
|
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(
|
|
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 (
|
|
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 (
|
|
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
|
|
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
|
|
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
|
-
|
|
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: ["
|
|
7527
|
-
message:
|
|
7609
|
+
path: ["where"],
|
|
7610
|
+
message: "WHERE conditions are required unless allowFullTableDelete is true"
|
|
7528
7611
|
});
|
|
7529
7612
|
}
|
|
7530
7613
|
});
|
|
7531
|
-
var
|
|
7532
|
-
|
|
7533
|
-
|
|
7534
|
-
|
|
7535
|
-
|
|
7536
|
-
|
|
7537
|
-
|
|
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
|
|
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
|
|
7548
|
-
|
|
7549
|
-
|
|
7550
|
-
|
|
7551
|
-
|
|
7552
|
-
|
|
7553
|
-
|
|
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-
|
|
7559
|
-
|
|
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
|
-
"
|
|
7577
|
-
"
|
|
7578
|
-
"
|
|
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
|
-
"
|
|
7686
|
+
"operator requires a value",
|
|
7582
7687
|
"operator requires a string or number value",
|
|
7583
|
-
"operator requires a
|
|
7584
|
-
"
|
|
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: /(
|
|
7817
|
-
message: "
|
|
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
|
|
7698
|
+
function isSafeDeleteValidationError(error) {
|
|
7829
7699
|
if (!(error instanceof Error)) {
|
|
7830
7700
|
return false;
|
|
7831
7701
|
}
|
|
7832
|
-
return
|
|
7702
|
+
return SAFE_DELETE_VALIDATION_PATTERNS.some((pattern) => error.message.includes(pattern));
|
|
7833
7703
|
}
|
|
7834
|
-
function
|
|
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
|
|
7722
|
+
function isSafeDeleteError(error) {
|
|
7850
7723
|
if (!(error instanceof Error)) {
|
|
7851
7724
|
return false;
|
|
7852
7725
|
}
|
|
7853
|
-
return
|
|
7726
|
+
return SAFE_DELETE_VALIDATION_PATTERNS.some((pattern) => error.message.includes(pattern)) || error.message.startsWith("Delete failed:");
|
|
7854
7727
|
}
|
|
7855
|
-
var
|
|
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
|
|
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 &&
|
|
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
|
-
|
|
7868
|
-
|
|
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 (
|
|
7884
|
-
const
|
|
7885
|
-
|
|
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
|
|
7899
|
-
|
|
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
|
|
7932
|
-
return
|
|
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-
|
|
7936
|
-
async function
|
|
7937
|
-
const built =
|
|
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
|
-
|
|
7940
|
-
|
|
7778
|
+
where: operation.where,
|
|
7779
|
+
allowFullTableDelete: operation.allowFullTableDelete,
|
|
7780
|
+
softDelete: operation.softDelete,
|
|
7941
7781
|
vendor: input.vendor
|
|
7942
7782
|
});
|
|
7943
|
-
const
|
|
7944
|
-
|
|
7945
|
-
|
|
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
|
-
|
|
7989
|
-
|
|
7990
|
-
executionTime: 0
|
|
7788
|
+
executionTime: 0,
|
|
7789
|
+
softDeleted: built.usesSoftDelete
|
|
7991
7790
|
};
|
|
7992
7791
|
}
|
|
7993
7792
|
|
|
7994
|
-
// src/data/relational/tools/relational-
|
|
7995
|
-
async function
|
|
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: "
|
|
7999
|
-
items: input.
|
|
8000
|
-
executeBatch: async (
|
|
8001
|
-
|
|
8002
|
-
|
|
8003
|
-
|
|
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
|
-
|
|
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
|
|
8021
|
-
const
|
|
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
|
-
|
|
8025
|
-
|
|
8026
|
-
|
|
8027
|
-
|
|
8028
|
-
|
|
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.
|
|
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-
|
|
8061
|
-
async function
|
|
7904
|
+
// src/data/relational/tools/relational-delete/executor.ts
|
|
7905
|
+
async function executeDelete(executor, input, context) {
|
|
8062
7906
|
const startTime = Date.now();
|
|
8063
|
-
|
|
7907
|
+
deleteExecutorLogger.debug("Building DELETE query", {
|
|
8064
7908
|
vendor: input.vendor,
|
|
8065
7909
|
table: input.table,
|
|
8066
|
-
|
|
8067
|
-
|
|
8068
|
-
|
|
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 =
|
|
8073
|
-
|
|
7919
|
+
const result = input.operations && batchOptions ? await executeDeleteInBatchMode(
|
|
7920
|
+
executor,
|
|
8074
7921
|
input,
|
|
8075
7922
|
context,
|
|
8076
7923
|
batchOptions
|
|
8077
|
-
) : await
|
|
7924
|
+
) : await executeSingleDelete(executor, input, toSingleDeleteOperation(input), context);
|
|
8078
7925
|
const executionTime = Date.now() - startTime;
|
|
8079
|
-
|
|
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
|
-
|
|
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 =
|
|
7950
|
+
const constraintMessage = getDeleteConstraintViolationMessage(error, input.cascade ?? false);
|
|
8102
7951
|
if (constraintMessage) {
|
|
8103
7952
|
throw new Error(constraintMessage, { cause: error });
|
|
8104
7953
|
}
|
|
8105
|
-
if (
|
|
7954
|
+
if (isSafeDeleteValidationError(error)) {
|
|
8106
7955
|
throw error;
|
|
8107
7956
|
}
|
|
8108
|
-
throw new 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/
|
|
8113
|
-
|
|
8114
|
-
|
|
8115
|
-
|
|
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
|
-
|
|
8139
|
-
|
|
8140
|
-
|
|
8141
|
-
|
|
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
|
-
|
|
8145
|
-
|
|
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
|
-
|
|
8156
|
-
|
|
8157
|
-
|
|
8158
|
-
|
|
8159
|
-
|
|
8160
|
-
|
|
8161
|
-
|
|
8162
|
-
|
|
8163
|
-
|
|
8164
|
-
|
|
8165
|
-
|
|
8166
|
-
|
|
8167
|
-
|
|
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
|
-
|
|
8171
|
-
|
|
8172
|
-
|
|
8173
|
-
|
|
8174
|
-
|
|
8175
|
-
|
|
8176
|
-
|
|
8177
|
-
|
|
8178
|
-
|
|
8179
|
-
|
|
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
|
-
|
|
8187
|
-
|
|
8188
|
-
|
|
8189
|
-
|
|
8190
|
-
|
|
8191
|
-
|
|
8192
|
-
|
|
8193
|
-
|
|
8194
|
-
|
|
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
|
-
|
|
8223
|
-
|
|
8224
|
-
|
|
8225
|
-
|
|
8226
|
-
|
|
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
|
-
|
|
8231
|
-
|
|
8232
|
-
|
|
8233
|
-
|
|
8234
|
-
|
|
8235
|
-
|
|
8236
|
-
|
|
8237
|
-
|
|
8238
|
-
|
|
8239
|
-
|
|
8240
|
-
|
|
8241
|
-
|
|
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
|
-
|
|
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
|
-
|
|
8247
|
-
|
|
8248
|
-
|
|
8249
|
-
|
|
8250
|
-
|
|
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
|
-
|
|
8255
|
-
|
|
8256
|
-
|
|
8257
|
-
|
|
8258
|
-
|
|
8259
|
-
|
|
8260
|
-
|
|
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
|
-
|
|
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: ["
|
|
8266
|
-
message:
|
|
8208
|
+
path: ["idColumn"],
|
|
8209
|
+
message: 'idColumn can only be provided when returning.mode is "id"'
|
|
8267
8210
|
});
|
|
8268
8211
|
}
|
|
8269
8212
|
});
|
|
8270
|
-
var
|
|
8271
|
-
|
|
8272
|
-
|
|
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
|
|
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
|
|
8306
|
-
data:
|
|
8307
|
-
|
|
8308
|
-
|
|
8309
|
-
|
|
8310
|
-
|
|
8311
|
-
|
|
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-
|
|
8366
|
-
var
|
|
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
|
-
"
|
|
8370
|
-
"
|
|
8240
|
+
"must be an object",
|
|
8241
|
+
"must not be an empty array",
|
|
8371
8242
|
"must not be undefined",
|
|
8372
|
-
"
|
|
8373
|
-
"
|
|
8374
|
-
"
|
|
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: "
|
|
8250
|
+
message: "Insert failed: unique constraint violation."
|
|
8386
8251
|
},
|
|
8387
8252
|
{
|
|
8388
8253
|
pattern: /(foreign key constraint|violates foreign key constraint)/i,
|
|
8389
|
-
message: "
|
|
8254
|
+
message: "Insert failed: foreign key constraint violation."
|
|
8390
8255
|
},
|
|
8391
8256
|
{
|
|
8392
8257
|
pattern: /(not null constraint|cannot be null)/i,
|
|
8393
|
-
message: "
|
|
8258
|
+
message: "Insert failed: NOT NULL constraint violation."
|
|
8394
8259
|
}
|
|
8395
8260
|
];
|
|
8396
|
-
function
|
|
8261
|
+
function isSafeInsertValidationError(error) {
|
|
8397
8262
|
if (!(error instanceof Error)) {
|
|
8398
8263
|
return false;
|
|
8399
8264
|
}
|
|
8400
|
-
return
|
|
8265
|
+
return SAFE_INSERT_VALIDATION_PATTERNS.some((pattern) => error.message.includes(pattern));
|
|
8401
8266
|
}
|
|
8402
|
-
function
|
|
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
|
|
8282
|
+
function isSafeInsertError(error) {
|
|
8418
8283
|
if (!(error instanceof Error)) {
|
|
8419
8284
|
return false;
|
|
8420
8285
|
}
|
|
8421
|
-
return
|
|
8286
|
+
return isSafeInsertValidationError(error) || getConstraintViolationMessage(error) !== null;
|
|
8422
8287
|
}
|
|
8423
|
-
var
|
|
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
|
|
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]
|
|
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
|
-
|
|
8433
|
-
|
|
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
|
|
8311
|
+
return {
|
|
8312
|
+
rows: result,
|
|
8313
|
+
rowCount: result.length
|
|
8314
|
+
};
|
|
8437
8315
|
}
|
|
8438
|
-
if (result
|
|
8439
|
-
const
|
|
8440
|
-
|
|
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
|
|
8326
|
+
return {
|
|
8327
|
+
rows: [],
|
|
8328
|
+
rowCount: 0
|
|
8329
|
+
};
|
|
8443
8330
|
}
|
|
8444
|
-
function
|
|
8445
|
-
|
|
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
|
|
8461
|
-
|
|
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-
|
|
8473
|
-
async function
|
|
8474
|
-
const built =
|
|
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:
|
|
8477
|
-
|
|
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
|
|
8483
|
-
|
|
8484
|
-
|
|
8485
|
-
|
|
8486
|
-
|
|
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-
|
|
8495
|
-
async function
|
|
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: "
|
|
8499
|
-
items: input.
|
|
8500
|
-
executeBatch: async (
|
|
8501
|
-
|
|
8502
|
-
|
|
8503
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
8460
|
+
totalRows: input.data.length,
|
|
8565
8461
|
batchSize: options.batchSize
|
|
8566
8462
|
});
|
|
8567
|
-
benchmark = await benchmarkBatchExecution({
|
|
8568
|
-
items: input.
|
|
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
|
-
|
|
8577
|
-
|
|
8578
|
-
|
|
8579
|
-
|
|
8580
|
-
|
|
8581
|
-
|
|
8582
|
-
|
|
8583
|
-
|
|
8584
|
-
|
|
8585
|
-
|
|
8586
|
-
|
|
8587
|
-
|
|
8588
|
-
|
|
8589
|
-
|
|
8590
|
-
|
|
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-
|
|
8596
|
-
|
|
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
|
-
|
|
8875
|
+
logger17.debug("Building SELECT query", {
|
|
8599
8876
|
vendor: input.vendor,
|
|
8600
8877
|
table: input.table,
|
|
8601
|
-
hasWhere: !!input.where
|
|
8602
|
-
|
|
8603
|
-
|
|
8604
|
-
|
|
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
|
|
8609
|
-
|
|
8610
|
-
|
|
8611
|
-
|
|
8612
|
-
|
|
8613
|
-
|
|
8614
|
-
|
|
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
|
-
|
|
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
|
|
8620
|
-
executionTime
|
|
8621
|
-
batchMode: !!result.batch,
|
|
8622
|
-
partialSuccess: result.batch?.partialSuccess ?? false
|
|
8936
|
+
rowCount,
|
|
8937
|
+
executionTime
|
|
8623
8938
|
});
|
|
8624
8939
|
return {
|
|
8625
|
-
|
|
8940
|
+
rows,
|
|
8941
|
+
rowCount,
|
|
8626
8942
|
executionTime
|
|
8627
8943
|
};
|
|
8628
8944
|
} catch (error) {
|
|
8629
8945
|
const executionTime = Date.now() - startTime;
|
|
8630
|
-
|
|
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
|
-
|
|
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("
|
|
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-
|
|
8648
|
-
|
|
8649
|
-
|
|
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
|
-
|
|
8653
|
-
where: [{ column: "
|
|
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: "
|
|
8997
|
+
description: "SELECT with pagination",
|
|
8659
8998
|
input: {
|
|
8660
|
-
table: "
|
|
8661
|
-
|
|
8662
|
-
|
|
8663
|
-
|
|
8664
|
-
vendor: "
|
|
8665
|
-
connectionString: "
|
|
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: "
|
|
9007
|
+
description: "SELECT with streaming mode",
|
|
8669
9008
|
input: {
|
|
8670
|
-
table: "
|
|
8671
|
-
|
|
8672
|
-
|
|
8673
|
-
|
|
8674
|
-
|
|
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/
|
|
9017
|
+
connectionString: "postgresql://localhost/analytics"
|
|
8687
9018
|
}
|
|
8688
9019
|
}).implement(async (input) => {
|
|
8689
|
-
const
|
|
8690
|
-
|
|
8691
|
-
|
|
8692
|
-
|
|
8693
|
-
|
|
8694
|
-
|
|
8695
|
-
|
|
8696
|
-
|
|
8697
|
-
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
8800
|
-
column: zod.z.string().min(1, "
|
|
8801
|
-
|
|
8802
|
-
|
|
8803
|
-
|
|
8804
|
-
|
|
8805
|
-
|
|
8806
|
-
|
|
8807
|
-
|
|
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
|
-
|
|
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
|
|
9149
|
+
message: "WHERE conditions are required unless allowFullTableUpdate is true"
|
|
8815
9150
|
});
|
|
8816
9151
|
}
|
|
8817
9152
|
});
|
|
8818
|
-
var
|
|
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
|
|
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
|
|
8831
|
-
|
|
8832
|
-
|
|
8833
|
-
|
|
8834
|
-
|
|
8835
|
-
operations: zod.z.array(
|
|
8836
|
-
batch:
|
|
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 &&
|
|
9176
|
+
if (hasOperations && value.data) {
|
|
8842
9177
|
ctx.addIssue({
|
|
8843
9178
|
code: zod.z.ZodIssueCode.custom,
|
|
8844
|
-
path: ["
|
|
8845
|
-
message: "
|
|
9179
|
+
path: ["data"],
|
|
9180
|
+
message: "data cannot be provided when operations[] is used"
|
|
8846
9181
|
});
|
|
8847
9182
|
}
|
|
8848
|
-
if (hasOperations && value.
|
|
9183
|
+
if (!hasOperations && !value.data) {
|
|
8849
9184
|
ctx.addIssue({
|
|
8850
9185
|
code: zod.z.ZodIssueCode.custom,
|
|
8851
|
-
path: ["
|
|
8852
|
-
message: "
|
|
9186
|
+
path: ["data"],
|
|
9187
|
+
message: "data is required when operations[] is not provided"
|
|
8853
9188
|
});
|
|
8854
9189
|
}
|
|
8855
|
-
if (hasOperations && value.
|
|
9190
|
+
if (hasOperations && (value.where?.length ?? 0) > 0) {
|
|
8856
9191
|
ctx.addIssue({
|
|
8857
9192
|
code: zod.z.ZodIssueCode.custom,
|
|
8858
|
-
path: ["
|
|
8859
|
-
message: "
|
|
9193
|
+
path: ["where"],
|
|
9194
|
+
message: "where cannot be provided when operations[] is used"
|
|
8860
9195
|
});
|
|
8861
9196
|
}
|
|
8862
|
-
if (hasOperations && value.
|
|
9197
|
+
if (hasOperations && value.optimisticLock) {
|
|
8863
9198
|
ctx.addIssue({
|
|
8864
9199
|
code: zod.z.ZodIssueCode.custom,
|
|
8865
|
-
path: ["
|
|
8866
|
-
message: "
|
|
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
|
-
|
|
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
|
|
9220
|
+
message: "WHERE conditions are required unless allowFullTableUpdate is true"
|
|
8878
9221
|
});
|
|
8879
9222
|
}
|
|
8880
9223
|
});
|
|
8881
9224
|
|
|
8882
|
-
// src/data/relational/tools/relational-
|
|
8883
|
-
var
|
|
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
|
-
"
|
|
8887
|
-
"
|
|
8888
|
-
"
|
|
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
|
-
"
|
|
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: /(
|
|
8899
|
-
message: "
|
|
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
|
|
9256
|
+
function isSafeUpdateValidationError(error) {
|
|
8903
9257
|
if (!(error instanceof Error)) {
|
|
8904
9258
|
return false;
|
|
8905
9259
|
}
|
|
8906
|
-
return
|
|
9260
|
+
return SAFE_UPDATE_VALIDATION_PATTERNS.some((pattern) => error.message.includes(pattern));
|
|
8907
9261
|
}
|
|
8908
|
-
function
|
|
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
|
|
9277
|
+
function isSafeUpdateError(error) {
|
|
8927
9278
|
if (!(error instanceof Error)) {
|
|
8928
9279
|
return false;
|
|
8929
9280
|
}
|
|
8930
|
-
return
|
|
9281
|
+
return isSafeUpdateValidationError(error) || getUpdateConstraintViolationMessage(error) !== null;
|
|
8931
9282
|
}
|
|
8932
|
-
var
|
|
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
|
|
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
|
-
|
|
8973
|
-
|
|
8974
|
-
softDelete: input.softDelete
|
|
9327
|
+
allowFullTableUpdate: input.allowFullTableUpdate,
|
|
9328
|
+
optimisticLock: input.optimisticLock
|
|
8975
9329
|
};
|
|
8976
9330
|
}
|
|
8977
9331
|
|
|
8978
|
-
// src/data/relational/tools/relational-
|
|
8979
|
-
async function
|
|
8980
|
-
const built =
|
|
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
|
-
|
|
8984
|
-
|
|
9338
|
+
allowFullTableUpdate: operation.allowFullTableUpdate,
|
|
9339
|
+
optimisticLock: operation.optimisticLock,
|
|
8985
9340
|
vendor: input.vendor
|
|
8986
9341
|
});
|
|
8987
|
-
const
|
|
8988
|
-
const rawResult = await
|
|
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-
|
|
8998
|
-
async function
|
|
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: "
|
|
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
|
|
9012
|
-
|
|
9367
|
+
const result = await executeSingleUpdate(
|
|
9368
|
+
executor,
|
|
9013
9369
|
input,
|
|
9014
9370
|
{
|
|
9371
|
+
data: operation.data,
|
|
9015
9372
|
where: operation.where,
|
|
9016
|
-
|
|
9017
|
-
|
|
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: "
|
|
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
|
-
|
|
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
|
-
|
|
9071
|
-
|
|
9072
|
-
|
|
9073
|
-
|
|
9074
|
-
|
|
9075
|
-
|
|
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-
|
|
9109
|
-
async function
|
|
9455
|
+
// src/data/relational/tools/relational-update/executor.ts
|
|
9456
|
+
async function executeUpdate(executor, input, context) {
|
|
9110
9457
|
const startTime = Date.now();
|
|
9111
|
-
|
|
9458
|
+
updateExecutorLogger.debug("Building UPDATE query", {
|
|
9112
9459
|
vendor: input.vendor,
|
|
9113
9460
|
table: input.table,
|
|
9114
9461
|
hasWhere: !!input.where?.length,
|
|
9115
|
-
...input.
|
|
9116
|
-
|
|
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
|
|
9124
|
-
|
|
9469
|
+
const result = input.operations && batchOptions ? await executeUpdateInBatchMode(
|
|
9470
|
+
executor,
|
|
9125
9471
|
input,
|
|
9126
9472
|
context,
|
|
9127
9473
|
batchOptions
|
|
9128
|
-
) : await
|
|
9474
|
+
) : await executeSingleUpdate(executor, input, toSingleUpdateOperation(input), context);
|
|
9129
9475
|
const executionTime = Date.now() - startTime;
|
|
9130
|
-
|
|
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
|
-
|
|
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 =
|
|
9496
|
+
const constraintMessage = getUpdateConstraintViolationMessage(error);
|
|
9155
9497
|
if (constraintMessage) {
|
|
9156
9498
|
throw new Error(constraintMessage, { cause: error });
|
|
9157
9499
|
}
|
|
9158
|
-
if (
|
|
9500
|
+
if (isSafeUpdateValidationError(error)) {
|
|
9159
9501
|
throw error;
|
|
9160
9502
|
}
|
|
9161
|
-
throw new 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-
|
|
9166
|
-
|
|
9167
|
-
|
|
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: "
|
|
9543
|
+
description: "Optimistic lock update",
|
|
9176
9544
|
input: {
|
|
9177
9545
|
table: "users",
|
|
9178
|
-
|
|
9179
|
-
|
|
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
|
|
9553
|
+
description: "Batch update operations",
|
|
9185
9554
|
input: {
|
|
9186
9555
|
table: "users",
|
|
9187
9556
|
operations: [
|
|
9188
9557
|
{
|
|
9189
|
-
|
|
9190
|
-
|
|
9558
|
+
data: { status: "inactive" },
|
|
9559
|
+
where: [{ column: "id", operator: "eq", value: 1 }]
|
|
9191
9560
|
},
|
|
9192
9561
|
{
|
|
9193
|
-
|
|
9194
|
-
|
|
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
|
|
9206
|
-
|
|
9207
|
-
|
|
9208
|
-
|
|
9209
|
-
|
|
9210
|
-
|
|
9211
|
-
|
|
9212
|
-
|
|
9213
|
-
|
|
9214
|
-
|
|
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/
|
|
9236
|
-
var
|
|
9237
|
-
|
|
9238
|
-
|
|
9239
|
-
|
|
9240
|
-
|
|
9241
|
-
|
|
9242
|
-
|
|
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
|
-
|
|
9246
|
-
|
|
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
|
-
|
|
9251
|
-
|
|
9252
|
-
|
|
9253
|
-
|
|
9254
|
-
|
|
9255
|
-
|
|
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
|
-
|
|
9258
|
-
|
|
9259
|
-
|
|
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
|
-
|
|
9277
|
-
|
|
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
|
-
|
|
9286
|
-
|
|
9287
|
-
|
|
9288
|
-
|
|
9289
|
-
|
|
9290
|
-
|
|
9291
|
-
|
|
9292
|
-
|
|
9293
|
-
|
|
9294
|
-
)
|
|
9295
|
-
|
|
9296
|
-
|
|
9297
|
-
|
|
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
|
-
|
|
9300
|
-
|
|
9301
|
-
|
|
9302
|
-
|
|
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
|
-
|
|
9305
|
-
|
|
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
|
-
|
|
9308
|
-
|
|
9309
|
-
|
|
9310
|
-
|
|
9311
|
-
|
|
9312
|
-
|
|
9313
|
-
|
|
9314
|
-
|
|
9315
|
-
|
|
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
|
-
|
|
9319
|
-
|
|
9320
|
-
|
|
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
|
-
}
|
|
9323
|
-
|
|
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
|
-
|
|
9333
|
-
|
|
9334
|
-
|
|
10036
|
+
executor: transaction,
|
|
10037
|
+
transaction,
|
|
10038
|
+
vendor: this.config.vendor
|
|
9335
10039
|
};
|
|
9336
|
-
} finally {
|
|
9337
|
-
await manager.disconnect();
|
|
9338
10040
|
}
|
|
9339
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|